Merge pull request #67 from cinaera/pr_sound

Add support for ingame map sounds
This commit is contained in:
Dennis Felsing 2014-10-18 18:46:47 +02:00
commit 378c34f3ff
21 changed files with 1783 additions and 143 deletions

View file

@ -45,8 +45,10 @@ struct CVoice
{
CSample *m_pSample;
CChannel *m_pChannel;
int m_Age; // increases when reused
int m_Tick;
int m_Vol; // 0 - 255
int m_FalloffDistance; // 0 - inifintee (well int)
int m_Flags;
int m_X, m_Y;
} ;
@ -67,6 +69,12 @@ static int m_NextVoice = 0;
static int *m_pMixBuffer = 0; // buffer only used by the thread callback function
static unsigned m_MaxFrames = 0;
static const void *ms_pWVBuffer = 0x0;
static int ms_WVBufferPosition = 0;
static int ms_WVBufferSize = 0;
const int DefaultDistance = 1500;
// TODO: there should be a faster way todo this
static short Int2Short(int i)
{
@ -109,8 +117,8 @@ static void Mix(short *pFinalOut, unsigned Frames)
unsigned End = v->m_pSample->m_NumFrames-v->m_Tick;
int Rvol = v->m_pChannel->m_Vol;
int Lvol = v->m_pChannel->m_Vol;
int Rvol = (int)(v->m_pChannel->m_Vol*(v->m_Vol/255.0f));
int Lvol = (int)(v->m_pChannel->m_Vol*(v->m_Vol/255.0f));
// make sure that we don't go outside the sound data
if(Frames < End)
@ -124,11 +132,11 @@ static void Mix(short *pFinalOut, unsigned Frames)
if(v->m_Flags&ISound::FLAG_POS && v->m_pChannel->m_Pan)
{
// TODO: we should respect the channel panning value
const int Range = 1500; // magic value, remove
int dx = v->m_X - m_CenterX;
int dy = v->m_Y - m_CenterY;
int Dist = (int)sqrtf((float)dx*dx+dy*dy); // float here. nasty
int p = IntAbs(dx);
int Range = v->m_FalloffDistance;
if(Dist >= 0 && Dist < Range)
{
// panning
@ -164,7 +172,10 @@ static void Mix(short *pFinalOut, unsigned Frames)
if(v->m_Flags&ISound::FLAG_LOOP)
v->m_Tick = 0;
else
{
v->m_pSample = 0;
v->m_Age++;
}
}
}
}
@ -326,27 +337,90 @@ void CSound::RateConvert(int SampleID)
mem_free(pSample->m_pData);
pSample->m_pData = pNewData;
pSample->m_NumFrames = NumFrames;
pSample->m_Rate = m_MixingRate;
}
int CSound::ReadData(void *pBuffer, int Size)
{
return io_read(ms_File, pBuffer, Size);
int ChunkSize = min(Size, ms_WVBufferSize - ms_WVBufferPosition);
mem_copy(pBuffer, (const char *)ms_pWVBuffer + ms_WVBufferPosition, ChunkSize);
ms_WVBufferPosition += ChunkSize;
return ChunkSize;
}
int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize)
{
if(SampleID == -1 || SampleID >= NUM_SAMPLES)
return -1;
CSample *pSample = &m_aSamples[SampleID];
char aError[100];
WavpackContext *pContext;
ms_pWVBuffer = pData;
ms_WVBufferSize = DataSize;
ms_WVBufferPosition = 0;
pContext = WavpackOpenFileInput(ReadData, aError);
if (pContext)
{
int NumSamples = WavpackGetNumSamples(pContext);
int BitsPerSample = WavpackGetBitsPerSample(pContext);
unsigned int SampleRate = WavpackGetSampleRate(pContext);
int NumChannels = WavpackGetNumChannels(pContext);
int *pSrc;
short *pDst;
int i;
pSample->m_Channels = NumChannels;
pSample->m_Rate = SampleRate;
if(pSample->m_Channels > 2)
{
dbg_msg("sound/wv", "file is not mono or stereo.");
return -1;
}
if(BitsPerSample != 16)
{
dbg_msg("sound/wv", "bps is %d, not 16", BitsPerSample);
return -1;
}
int *pBuffer = (int *)mem_alloc(4*NumSamples*NumChannels, 1);
WavpackUnpackSamples(pContext, pBuffer, NumSamples); // TODO: check return value
pSrc = pBuffer;
pSample->m_pData = (short *)mem_alloc(2*NumSamples*NumChannels, 1);
pDst = pSample->m_pData;
for (i = 0; i < NumSamples*NumChannels; i++)
*pDst++ = (short)*pSrc++;
mem_free(pBuffer);
pSample->m_NumFrames = NumSamples;
pSample->m_LoopStart = -1;
pSample->m_LoopEnd = -1;
pSample->m_PausedAt = 0;
}
else
{
dbg_msg("sound/wv", "failed to decode sample (%s)", aError);
}
return SampleID;
}
int CSound::LoadWV(const char *pFilename)
{
CSample *pSample;
int SampleID = -1;
char aError[100];
WavpackContext *pContext;
// don't waste memory on sound when we are stress testing
if(g_Config.m_DbgStress)
return -1;
// no need to load sound when we are running with no sound
if(!m_SoundEnabled)
return 1;
return -1;
if(!m_pStorage)
return -1;
@ -358,67 +432,18 @@ int CSound::LoadWV(const char *pFilename)
return -1;
}
SampleID = AllocID();
int SampleID = AllocID();
if(SampleID < 0)
return -1;
pSample = &m_aSamples[SampleID];
pContext = WavpackOpenFileInput(ReadData, aError);
if (pContext)
{
int m_aSamples = WavpackGetNumSamples(pContext);
int BitsPerSample = WavpackGetBitsPerSample(pContext);
unsigned int SampleRate = WavpackGetSampleRate(pContext);
int m_aChannels = WavpackGetNumChannels(pContext);
int *pData;
int *pSrc;
short *pDst;
int i;
// read the whole file into memory
int DataSize = io_length(ms_File);
char *pData = new char[DataSize];
io_read(ms_File, pData, DataSize);
pSample->m_Channels = m_aChannels;
pSample->m_Rate = SampleRate;
if(pSample->m_Channels > 2)
{
dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", pFilename);
return -1;
}
/*
if(snd->rate != 44100)
{
dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename);
return -1;
}*/
if(BitsPerSample != 16)
{
dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", BitsPerSample, pFilename);
return -1;
}
pData = (int *)mem_alloc(4*m_aSamples*m_aChannels, 1);
WavpackUnpackSamples(pContext, pData, m_aSamples); // TODO: check return value
pSrc = pData;
pSample->m_pData = (short *)mem_alloc(2*m_aSamples*m_aChannels, 1);
pDst = pSample->m_pData;
for (i = 0; i < m_aSamples*m_aChannels; i++)
*pDst++ = (short)*pSrc++;
mem_free(pData);
pSample->m_NumFrames = m_aSamples;
pSample->m_LoopStart = -1;
pSample->m_LoopEnd = -1;
pSample->m_PausedAt = 0;
}
else
{
dbg_msg("sound/wv", "failed to open %s: %s", pFilename, aError);
}
SampleID = DecodeWV(SampleID, pData, DataSize);
delete[] pData;
io_close(ms_File);
ms_File = NULL;
@ -429,12 +454,125 @@ int CSound::LoadWV(const char *pFilename)
return SampleID;
}
int CSound::LoadWVFromMem(const void *pData, unsigned DataSize)
{
// don't waste memory on sound when we are stress testing
if(g_Config.m_DbgStress)
return -1;
// no need to load sound when we are running with no sound
if(!m_SoundEnabled)
return -1;
if(!pData)
return -1;
int SampleID = AllocID();
if(SampleID < 0)
return -1;
SampleID = DecodeWV(SampleID, pData, DataSize);
RateConvert(SampleID);
return SampleID;
}
void CSound::UnloadSample(int SampleID)
{
if(SampleID == -1 || SampleID >= NUM_SAMPLES)
return;
Stop(SampleID);
mem_free(m_aSamples[SampleID].m_pData);
m_aSamples[SampleID].m_pData = 0x0;
}
float CSound::GetSampleDuration(int SampleID)
{
if(SampleID == -1 || SampleID >= NUM_SAMPLES)
return 0.0f;
return (m_aSamples[SampleID].m_NumFrames/m_aSamples[SampleID].m_Rate);
}
void CSound::SetListenerPos(float x, float y)
{
m_CenterX = (int)x;
m_CenterY = (int)y;
}
void CSound::SetVoiceVolume(CVoiceHandle Voice, float Volume)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
Volume = clamp(Volume, 0.0f, 1.0f);
m_aVoices[VoiceID].m_Vol = (int)(Volume*255.0f);
}
void CSound::SetVoiceMaxDistance(CVoiceHandle Voice, int Distance)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
if(Distance < 0)
return;
m_aVoices[VoiceID].m_FalloffDistance = Distance;
}
void CSound::SetVoiceLocation(CVoiceHandle Voice, float x, float y)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
m_aVoices[VoiceID].m_X = x;
m_aVoices[VoiceID].m_Y = y;
}
void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
lock_wait(m_SoundLock);
{
if(m_aVoices[VoiceID].m_pSample)
{
int TickOffset = m_aVoices[VoiceID].m_pSample->m_Rate * offset;
if(m_aVoices[VoiceID].m_Flags&ISound::FLAG_POS)
TickOffset = TickOffset%m_aVoices[VoiceID].m_pSample->m_NumFrames;
else
TickOffset = clamp(TickOffset, 0, m_aVoices[VoiceID].m_pSample->m_NumFrames);
// at least 2sec off
if(abs(m_aVoices[VoiceID].m_Tick-TickOffset) > 2*m_aVoices[VoiceID].m_pSample->m_Rate)
m_aVoices[VoiceID].m_Tick = TickOffset;
}
}
lock_release(m_SoundLock);
}
void CSound::SetChannel(int ChannelID, float Vol, float Pan)
{
@ -442,25 +580,26 @@ void CSound::SetChannel(int ChannelID, float Vol, float Pan)
m_aChannels[ChannelID].m_Pan = (int)(Pan*255.0f); // TODO: this is only on and off right now
}
int CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
{
int VoiceID = -1;
int Age = -1;
int i;
if(SampleID == 107) // GetSampleID(SOUND_CHAT_SERVER)
{
if(!g_Config.m_SndServerMessage)
return VoiceID;
return CVoiceHandle();
}
else if(SampleID == 108) // GetSampleID(SOUND_CHAT_CLIENT)
{}
else if(SampleID == 109) // GetSampleID(SOUND_CHAT_HIGHLIGHT)
{
if(!g_Config.m_SndHighlight)
return VoiceID;
return CVoiceHandle();
}
else if(!g_Config.m_SndGame)
return VoiceID;
return CVoiceHandle();
lock_wait(m_SoundLock);
@ -490,18 +629,20 @@ int CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
m_aVoices[VoiceID].m_Flags = Flags;
m_aVoices[VoiceID].m_X = (int)x;
m_aVoices[VoiceID].m_Y = (int)y;
m_aVoices[VoiceID].m_FalloffDistance = DefaultDistance;
Age = m_aVoices[VoiceID].m_Age;
}
lock_release(m_SoundLock);
return VoiceID;
return CreateVoiceHandle(VoiceID, Age);
}
int CSound::PlayAt(int ChannelID, int SampleID, int Flags, float x, float y)
ISound::CVoiceHandle CSound::PlayAt(int ChannelID, int SampleID, int Flags, float x, float y)
{
return Play(ChannelID, SampleID, Flags|ISound::FLAG_POS, x, y);
}
int CSound::Play(int ChannelID, int SampleID, int Flags)
ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags)
{
return Play(ChannelID, SampleID, Flags, 0, 0);
}
@ -543,6 +684,25 @@ void CSound::StopAll()
lock_release(m_SoundLock);
}
void CSound::StopVoice(CVoiceHandle Voice)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
lock_wait(m_SoundLock);
{
m_aVoices[VoiceID].m_pSample = 0;
m_aVoices[VoiceID].m_Age++;
}
lock_release(m_SoundLock);
}
IOHANDLE CSound::ms_File = 0;
IEngineSound *CreateEngineSound() { return new CSound; }

View file

@ -24,19 +24,30 @@ public:
// TODO: Refactor: clean this mess up
static IOHANDLE ms_File;
static int ReadData(void *pBuffer, int Size);
static int DecodeWV(int SampleID, const void *pData, unsigned DataSize);
virtual bool IsSoundEnabled() { return m_SoundEnabled != 0; }
virtual int LoadWV(const char *pFilename);
virtual int LoadWVFromMem(const void *pData, unsigned DataSize);
virtual void UnloadSample(int SampleID);
virtual float GetSampleDuration(int SampleID); // in s
virtual void SetListenerPos(float x, float y);
virtual void SetChannel(int ChannelID, float Vol, float Pan);
int Play(int ChannelID, int SampleID, int Flags, float x, float y);
virtual int PlayAt(int ChannelID, int SampleID, int Flags, float x, float y);
virtual int Play(int ChannelID, int SampleID, int Flags);
virtual void SetVoiceVolume(CVoiceHandle Voice, float Volume);
virtual void SetVoiceMaxDistance(CVoiceHandle Voice, int Distance);
virtual void SetVoiceLocation(CVoiceHandle Voice, float x, float y);
virtual void SetVoiceTimeOffset(CVoiceHandle Voice, float offset); // in s
CVoiceHandle Play(int ChannelID, int SampleID, int Flags, float x, float y);
virtual CVoiceHandle PlayAt(int ChannelID, int SampleID, int Flags, float x, float y);
virtual CVoiceHandle Play(int ChannelID, int SampleID, int Flags);
virtual void Stop(int SampleID);
virtual void StopAll();
virtual void StopVoice(CVoiceHandle Voice);
};
#endif

View file

@ -68,6 +68,7 @@ MACRO_CONFIG_INT(SndEnable, snd_enable, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "S
MACRO_CONFIG_INT(SndMusic, snd_enable_music, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Play background music")
MACRO_CONFIG_INT(SndVolume, snd_volume, 100, 0, 100, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound volume")
MACRO_CONFIG_INT(SndDevice, snd_device, -1, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "(deprecated) Sound device to use")
MACRO_CONFIG_INT(SndAmbientVolume, snd_ambient_volume, 70, 0, 100, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Ambient sound volume")
MACRO_CONFIG_INT(SndNonactiveMute, snd_nonactive_mute, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "")
MACRO_CONFIG_INT(SndGame, snd_game, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Enable game sounds")

View file

@ -16,17 +16,54 @@ public:
FLAG_ALL=3
};
class CVoiceHandle
{
friend class ISound;
int m_Id;
int m_Age;
public:
CVoiceHandle()
: m_Id(-1), m_Age(-1)
{}
bool IsValid() const { return (Id() >= 0) && (Age() >= 0); }
int Id() const { return m_Id; }
int Age() const { return m_Age; }
bool operator ==(const CVoiceHandle &Other) const { return m_Id == Other.m_Id && m_Age == Other.m_Age; }
};
virtual bool IsSoundEnabled() = 0;
virtual int LoadWV(const char *pFilename) = 0;
virtual int LoadWVFromMem(const void *pData, unsigned DataSize) = 0;
virtual void UnloadSample(int SampleID) = 0;
virtual float GetSampleDuration(int SampleID) = 0; // in s
virtual void SetChannel(int ChannelID, float Volume, float Panning) = 0;
virtual void SetListenerPos(float x, float y) = 0;
virtual int PlayAt(int ChannelID, int SampleID, int Flags, float x, float y) = 0;
virtual int Play(int ChannelID, int SampleID, int Flags) = 0;
virtual void SetVoiceVolume(CVoiceHandle Voice, float Volume) = 0;
virtual void SetVoiceMaxDistance(CVoiceHandle Voice, int Distance) = 0;
virtual void SetVoiceLocation(CVoiceHandle Voice, float x, float y) = 0;
virtual void SetVoiceTimeOffset(CVoiceHandle Voice, float offset) = 0; // in s
virtual CVoiceHandle PlayAt(int ChannelID, int SampleID, int Flags, float x, float y) = 0;
virtual CVoiceHandle Play(int ChannelID, int SampleID, int Flags) = 0;
virtual void Stop(int SampleID) = 0;
virtual void StopAll() = 0;
virtual void StopVoice(CVoiceHandle Voice) = 0;
protected:
inline CVoiceHandle CreateVoiceHandle(int Index, int Age)
{
CVoiceHandle Voice;
Voice.m_Id = Index;
Voice.m_Age = Age;
return Voice;
}
};

View file

@ -65,34 +65,7 @@ bool CEmoticon::OnMouseMove(float x, float y)
void CEmoticon::DrawCircle(float x, float y, float r, int Segments)
{
IGraphics::CFreeformItem Array[32];
int NumItems = 0;
float FSegments = (float)Segments;
for(int i = 0; i < Segments; i+=2)
{
float a1 = i/FSegments * 2*pi;
float a2 = (i+1)/FSegments * 2*pi;
float a3 = (i+2)/FSegments * 2*pi;
float Ca1 = cosf(a1);
float Ca2 = cosf(a2);
float Ca3 = cosf(a3);
float Sa1 = sinf(a1);
float Sa2 = sinf(a2);
float Sa3 = sinf(a3);
Array[NumItems++] = IGraphics::CFreeformItem(
x, y,
x+Ca1*r, y+Sa1*r,
x+Ca3*r, y+Sa3*r,
x+Ca2*r, y+Sa2*r);
if(NumItems == 32)
{
m_pClient->Graphics()->QuadsDrawFreeform(Array, 32);
NumItems = 0;
}
}
if(NumItems)
m_pClient->Graphics()->QuadsDrawFreeform(Array, NumItems);
RenderTools()->DrawCircle(x, y, r, Segments);
}

View file

@ -13,7 +13,6 @@ class CMapLayers : public CComponent
bool m_EnvelopeUpdate;
void MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom = 1.0f);
static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser);
public:
enum
{
@ -26,6 +25,8 @@ public:
virtual void OnRender();
void EnvelopeUpdate();
static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser);
};
#endif

View file

@ -0,0 +1,219 @@
#include <engine/engine.h>
#include <engine/sound.h>
#include <game/client/components/camera.h>
#include <game/client/components/maplayers.h> // envelope
#include <game/client/components/sounds.h>
#include "mapsounds.h"
CMapSounds::CMapSounds()
{
m_Count = 0;
}
void CMapSounds::OnMapLoad()
{
IMap *pMap = Kernel()->RequestInterface<IMap>();
Clear();
// load samples
int Start;
pMap->GetType(MAPITEMTYPE_SOUND, &Start, &m_Count);
// load new samples
for(int i = 0; i < m_Count; i++)
{
m_aSounds[i] = 0;
CMapItemSound *pSound = (CMapItemSound *)pMap->GetItem(Start+i, 0, 0);
if(pSound->m_External)
{
char Buf[256];
char *pName = (char *)pMap->GetData(pSound->m_SoundName);
str_format(Buf, sizeof(Buf), "mapres/%s.wv", pName);
m_aSounds[i] = Sound()->LoadWV(Buf);
}
else
{
void *pData = pMap->GetData(pSound->m_SoundData);
m_aSounds[i] = Sound()->LoadWVFromMem(pData, pSound->m_SoundDataSize);
pMap->UnloadData(pSound->m_SoundData);
}
}
// enqueue sound sources
m_lSourceQueue.clear();
for(int g = 0; g < Layers()->NumGroups(); g++)
{
CMapItemGroup *pGroup = Layers()->GetGroup(g);
if(!pGroup)
continue;
for(int l = 0; l < pGroup->m_NumLayers; l++)
{
CMapItemLayer *pLayer = Layers()->GetLayer(pGroup->m_StartLayer+l);
if(!pLayer)
continue;
if(pLayer->m_Type == LAYERTYPE_SOUNDS)
{
CMapItemLayerSounds *pSoundLayer = (CMapItemLayerSounds *)pLayer;
if(pSoundLayer->m_Sound == -1)
continue;
CSoundSource *pSources = (CSoundSource *)Layers()->Map()->GetDataSwapped(pSoundLayer->m_Data);
if(!pSources)
continue;
for(int i = 0; i < pSoundLayer->m_NumSources; i++) {
CSourceQueueEntry source;
source.m_Sound = pSoundLayer->m_Sound;
source.m_pSource = &pSources[i];
if(!source.m_pSource || source.m_Sound == -1)
continue;
m_lSourceQueue.add(source);
}
}
}
}
}
void CMapSounds::OnRender()
{
if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
return;
// enqueue sounds
for(int i = 0; i < m_lSourceQueue.size(); i++)
{
CSourceQueueEntry *pSource = &m_lSourceQueue[i];
static float s_Time = 0.0f;
if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
{
s_Time = mix((Client()->PrevGameTick()-m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)Client()->GameTickSpeed(),
(Client()->GameTick()-m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)Client()->GameTickSpeed(),
Client()->IntraGameTick());
}
float offset = s_Time-pSource->m_pSource->m_TimeDelay;
if(offset >= 0.0f)
{
if(pSource->m_Voice.IsValid())
{
// currently playing, set offset
Sound()->SetVoiceTimeOffset(pSource->m_Voice, offset);
}
else
{
// need to enqueue
int Flags = 0;
if(pSource->m_pSource->m_Loop) Flags |= ISound::FLAG_LOOP;
pSource->m_Voice = m_pClient->m_pSounds->PlaySampleAt(CSounds::CHN_AMBIENT, m_aSounds[pSource->m_Sound], 1.0f, vec2(fx2f(pSource->m_pSource->m_Position.x), fx2f(pSource->m_pSource->m_Position.y)), Flags);
Sound()->SetVoiceMaxDistance(pSource->m_Voice, pSource->m_pSource->m_FalloffDistance);
Sound()->SetVoiceTimeOffset(pSource->m_Voice, offset);
}
}
else
{
// stop voice
Sound()->StopVoice(pSource->m_Voice);
pSource->m_Voice = ISound::CVoiceHandle();
}
}
vec2 Center = m_pClient->m_pCamera->m_Center;
for(int g = 0; g < Layers()->NumGroups(); g++)
{
CMapItemGroup *pGroup = Layers()->GetGroup(g);
if(!pGroup)
continue;
for(int l = 0; l < pGroup->m_NumLayers; l++)
{
CMapItemLayer *pLayer = Layers()->GetLayer(pGroup->m_StartLayer+l);
if(!pLayer)
continue;
if(pLayer->m_Type == LAYERTYPE_SOUNDS)
{
CMapItemLayerSounds *pSoundLayer = (CMapItemLayerSounds *)pLayer;
CSoundSource *pSources = (CSoundSource *)Layers()->Map()->GetDataSwapped(pSoundLayer->m_Data);
if(!pSources)
continue;
for(int s = 0; s < pSoundLayer->m_NumSources; s++) {
for(int i = 0; i < m_lSourceQueue.size(); i++)
{
CSourceQueueEntry *pVoice = &m_lSourceQueue[i];
if(pVoice->m_pSource != &pSources[s])
continue;
if(!pVoice->m_Voice.IsValid())
continue;
float OffsetX = 0, OffsetY = 0;
if(pVoice->m_pSource->m_PosEnv >= 0)
{
float aChannels[4];
CMapLayers::EnvelopeEval(pVoice->m_pSource->m_PosEnvOffset/1000.0f, pVoice->m_pSource->m_PosEnv, aChannels, m_pClient->m_pMapLayersBackGround);
OffsetX = aChannels[0];
OffsetY = aChannels[1];
}
float x = fx2f(pVoice->m_pSource->m_Position.x)+OffsetX;
float y = fx2f(pVoice->m_pSource->m_Position.y)+OffsetY;
x += Center.x*(1.0f-pGroup->m_ParallaxX/100.0f);
y += Center.y*(1.0f-pGroup->m_ParallaxY/100.0f);
x -= pGroup->m_OffsetX; y -= pGroup->m_OffsetY;
Sound()->SetVoiceLocation(pVoice->m_Voice, x, y);
if(pVoice->m_pSource->m_SoundEnv >= 0)
{
float aChannels[4];
CMapLayers::EnvelopeEval(pVoice->m_pSource->m_SoundEnvOffset/1000.0f, pVoice->m_pSource->m_SoundEnv, aChannels, m_pClient->m_pMapLayersBackGround);
float Volume = clamp(aChannels[0], 0.0f, 1.0f);
Sound()->SetVoiceVolume(pVoice->m_Voice, Volume);
}
}
}
}
}
}
}
void CMapSounds::Clear()
{
// unload all samples
for(int i = 0; i < m_Count; i++)
{
Sound()->UnloadSample(m_aSounds[i]);
m_aSounds[i] = -1;
}
m_Count = 0;
}
void CMapSounds::OnStateChange(int NewState, int OldState)
{
if(NewState < IClient::STATE_ONLINE)
Clear();
}

View file

@ -0,0 +1,34 @@
#pragma once
#include <base/tl/array.h>
#include <engine/sound.h>
#include <game/client/component.h>
class CMapSounds : public CComponent
{
int m_aSounds[64];
int m_Count;
struct CSourceQueueEntry
{
int m_Sound;
ISound::CVoiceHandle m_Voice;
CSoundSource *m_pSource;
bool operator ==(const CSourceQueueEntry &Other) const { return (m_Sound == Other.m_Sound) && (m_Voice == Other.m_Voice) && (m_pSource == Other.m_pSource); }
};
array<CSourceQueueEntry> m_lSourceQueue;
void Clear();
public:
CMapSounds();
virtual void OnMapLoad();
virtual void OnRender();
virtual void OnStateChange(int NewState, int OldState);
};

View file

@ -1054,6 +1054,18 @@ void CMenus::RenderSettingsSound(CUIRect MainView)
g_Config.m_SndVolume = (int)(DoScrollbarH(&g_Config.m_SndVolume, &Button, g_Config.m_SndVolume/100.0f)*100.0f);
MainView.HSplitTop(20.0f, 0, &MainView);
}
// volume slider map sounds
{
CUIRect Button, Label;
MainView.HSplitTop(5.0f, &Button, &MainView);
MainView.HSplitTop(20.0f, &Button, &MainView);
Button.VSplitLeft(190.0f, &Label, &Button);
Button.HMargin(2.0f, &Button);
UI()->DoLabelScaled(&Label, Localize("Ambient volume"), 14.0f, -1);
g_Config.m_SndAmbientVolume = (int)(DoScrollbarH(&g_Config.m_SndAmbientVolume, &Button, g_Config.m_SndAmbientVolume/100.0f)*100.0f);
MainView.HSplitTop(20.0f, 0, &MainView);
}
}
class CLanguage

View file

@ -64,10 +64,13 @@ int CSounds::GetSampleId(int SetId)
void CSounds::OnInit()
{
// setup sound channels
m_AmbientVolume = g_Config.m_SndAmbientVolume/100.0f;
Sound()->SetChannel(CSounds::CHN_GUI, 1.0f, 0.0f);
Sound()->SetChannel(CSounds::CHN_MUSIC, 1.0f, 0.0f);
Sound()->SetChannel(CSounds::CHN_WORLD, 0.9f, 1.0f);
Sound()->SetChannel(CSounds::CHN_GLOBAL, 1.0f, 0.0f);
Sound()->SetChannel(CSounds::CHN_AMBIENT, m_AmbientVolume, 1.0f);
Sound()->SetListenerPos(0.0f, 0.0f);
@ -119,6 +122,14 @@ void CSounds::OnRender()
// set listner pos
Sound()->SetListenerPos(m_pClient->m_pCamera->m_Center.x, m_pClient->m_pCamera->m_Center.y);
// update volume
float NewAmbientVol = g_Config.m_SndAmbientVolume/100.0f;
if(NewAmbientVol != m_AmbientVolume)
{
m_AmbientVolume = NewAmbientVol;
Sound()->SetChannel(CSounds::CHN_AMBIENT, m_AmbientVolume, 1.0f);
}
// play sound from queue
if(m_QueuePos > 0)
{
@ -204,3 +215,25 @@ void CSounds::Stop(int SetId)
for(int i = 0; i < pSet->m_NumSounds; i++)
Sound()->Stop(pSet->m_aSounds[i].m_Id);
}
ISound::CVoiceHandle CSounds::PlaySample(int Chn, int SampleId, float Vol, int Flags)
{
if((Chn == CHN_MUSIC && !g_Config.m_SndMusic) || SampleId == -1)
return ISound::CVoiceHandle();
if(Chn == CHN_MUSIC)
Flags |= ISound::FLAG_LOOP;
return Sound()->Play(Chn, SampleId, Flags);
}
ISound::CVoiceHandle CSounds::PlaySampleAt(int Chn, int SampleId, float Vol, vec2 Pos, int Flags)
{
if((Chn == CHN_MUSIC && !g_Config.m_SndMusic) || SampleId == -1)
return ISound::CVoiceHandle();
if(Chn == CHN_MUSIC)
Flags |= ISound::FLAG_LOOP;
return Sound()->PlayAt(Chn, SampleId, Flags, Pos.x, Pos.y);
}

View file

@ -2,6 +2,7 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_COMPONENTS_SOUNDS_H
#define GAME_CLIENT_COMPONENTS_SOUNDS_H
#include <engine/sound.h>
#include <game/client/component.h>
class CSounds : public CComponent
@ -22,6 +23,8 @@ class CSounds : public CComponent
int GetSampleId(int SetId);
float m_AmbientVolume;
public:
// sound channels
enum
@ -30,6 +33,7 @@ public:
CHN_MUSIC,
CHN_WORLD,
CHN_GLOBAL,
CHN_AMBIENT,
};
virtual void OnInit();
@ -43,6 +47,9 @@ public:
void PlayAt(int Channel, int SetId, float Vol, vec2 Pos);
void PlayAndRecord(int Channel, int SetId, float Vol, vec2 Pos);
void Stop(int SetId);
ISound::CVoiceHandle PlaySample(int Channel, int SampleId, float Vol, int Flags = 0);
ISound::CVoiceHandle PlaySampleAt(int Channel, int SampleId, float Vol, vec2 Pos, int Flags = 0);
};

View file

@ -44,6 +44,7 @@
#include "components/killmessages.h"
#include "components/mapimages.h"
#include "components/maplayers.h"
#include "components/mapsounds.h"
#include "components/menus.h"
#include "components/motd.h"
#include "components/particles.h"
@ -94,6 +95,8 @@ static CMapImages gs_MapImages;
static CMapLayers gs_MapLayersBackGround(CMapLayers::TYPE_BACKGROUND);
static CMapLayers gs_MapLayersForeGround(CMapLayers::TYPE_FOREGROUND);
static CMapSounds gs_MapSounds;
static CRaceDemo gs_RaceDemo;
static CGhost gs_Ghost;
@ -148,6 +151,8 @@ void CGameClient::OnConsoleInit()
m_pMapLayersBackGround = &::gs_MapLayersBackGround;
m_pMapLayersForeGround = &::gs_MapLayersForeGround;
m_pMapSounds = &::gs_MapSounds;
m_pRaceDemo = &::gs_RaceDemo;
m_pGhost = &::gs_Ghost;
@ -164,6 +169,7 @@ void CGameClient::OnConsoleInit()
m_All.Add(m_pVoting);
m_All.Add(m_pParticles); // doesn't render anything, just updates all the particles
m_All.Add(m_pRaceDemo);
m_All.Add(m_pMapSounds);
m_All.Add(&gs_MapLayersBackGround); // first to render
m_All.Add(&m_pParticles->m_RenderTrail);

View file

@ -274,6 +274,8 @@ public:
class CMapLayers *m_pMapLayersBackGround;
class CMapLayers *m_pMapLayersForeGround;
class CMapSounds *m_pMapSounds;
// DDRace
class CRaceDemo *m_pRaceDemo;

View file

@ -165,6 +165,38 @@ void CRenderTools::DrawUIRect(const CUIRect *r, vec4 Color, int Corners, float R
Graphics()->QuadsEnd();
}
void CRenderTools::DrawCircle(float x, float y, float r, int Segments)
{
IGraphics::CFreeformItem Array[32];
int NumItems = 0;
float FSegments = (float)Segments;
for(int i = 0; i < Segments; i+=2)
{
float a1 = i/FSegments * 2*pi;
float a2 = (i+1)/FSegments * 2*pi;
float a3 = (i+2)/FSegments * 2*pi;
float Ca1 = cosf(a1);
float Ca2 = cosf(a2);
float Ca3 = cosf(a3);
float Sa1 = sinf(a1);
float Sa2 = sinf(a2);
float Sa3 = sinf(a3);
Array[NumItems++] = IGraphics::CFreeformItem(
x, y,
x+Ca1*r, y+Sa1*r,
x+Ca3*r, y+Sa3*r,
x+Ca2*r, y+Sa2*r);
if(NumItems == 32)
{
Graphics()->QuadsDrawFreeform(Array, 32);
NumItems = 0;
}
}
if(NumItems)
Graphics()->QuadsDrawFreeform(Array, NumItems);
}
void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, bool Alpha)
{
vec2 Direction = Dir;

View file

@ -63,6 +63,8 @@ public:
void DrawUIRect(const CUIRect *pRect, vec4 Color, int Corners, float Rounding);
void DrawCircle(float x, float y, float r, int Segments);
// larger rendering methods
void RenderTilemapGenerateSkip(class CLayers *pLayers);

View file

@ -56,6 +56,16 @@ CEditorImage::~CEditorImage()
}
}
CEditorSound::~CEditorSound()
{
m_pEditor->Sound()->UnloadSample(m_SoundID);
if(m_pData)
{
delete[] m_pData;
m_pData = 0x0;
}
}
CLayerGroup::CLayerGroup()
{
m_aName[0] = 0;
@ -731,6 +741,16 @@ CQuad *CEditor::GetSelectedQuad()
return 0;
}
CSoundSource *CEditor::GetSelectedSource()
{
CLayerSounds *pSounds = (CLayerSounds *)GetSelectedLayerType(0, LAYERTYPE_SOUNDS);
if(!pSounds)
return 0;
if(m_SelectedSource >= 0 && m_SelectedSource < pSounds->m_lSources.size())
return &pSounds->m_lSources[m_SelectedSource];
return 0;
}
void CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUser)
{
CEditor *pEditor = (CEditor*)pUser;
@ -1001,6 +1021,31 @@ void CEditor::DoToolbar(CUIRect ToolBar)
}
}
// sound source manipulation
{
// do add button
TB_Top.VSplitLeft(10.0f, &Button, &TB_Top);
TB_Top.VSplitLeft(60.0f, &Button, &TB_Top);
static int s_NewButton = 0;
CLayerSounds *pSoundLayer = (CLayerSounds *)GetSelectedLayerType(0, LAYERTYPE_SOUNDS);
if(DoButton_Editor(&s_NewButton, "Add Source", pSoundLayer?0:-1, &Button, 0, "Adds a new sound source"))
{
if(pSoundLayer)
{
float Mapping[4];
CLayerGroup *g = GetSelectedGroup();
g->Mapping(Mapping);
int AddX = f2fx(Mapping[0] + (Mapping[2]-Mapping[0])/2);
int AddY = f2fx(Mapping[1] + (Mapping[3]-Mapping[1])/2);
CSoundSource *pSource = pSoundLayer->NewSource();
pSource->m_Position.x += AddX;
pSource->m_Position.y += AddY;
}
}
}
// tile manipulation
{
TB_Bottom.VSplitLeft(40.0f, &Button, &TB_Bottom);
@ -1111,6 +1156,136 @@ static void Rotate(const CPoint *pCenter, CPoint *pPoint, float Rotation)
pPoint->y = (int)(x * sinf(Rotation) + y * cosf(Rotation) + pCenter->y);
}
void CEditor::DoSoundSource(CSoundSource *pSource, int Index)
{
enum
{
OP_NONE=0,
OP_MOVE,
OP_CONTEXT_MENU,
};
void *pID = &pSource->m_Position;
static float s_LastWx;
static float s_LastWy;
static int s_Operation = OP_NONE;
float wx = UI()->MouseWorldX();
float wy = UI()->MouseWorldY();
float CenterX = fx2f(pSource->m_Position.x);
float CenterY = fx2f(pSource->m_Position.y);
float dx = (CenterX - wx)/m_WorldZoom;
float dy = (CenterY - wy)/m_WorldZoom;
if(dx*dx+dy*dy < 50)
UI()->SetHotItem(pID);
bool IgnoreGrid;
if(Input()->KeyPressed(KEY_LALT) || Input()->KeyPressed(KEY_RALT))
IgnoreGrid = true;
else
IgnoreGrid = false;
if(UI()->ActiveItem() == pID)
{
if(m_MouseDeltaWx*m_MouseDeltaWx+m_MouseDeltaWy*m_MouseDeltaWy > 0.5f)
{
if(s_Operation == OP_MOVE)
{
if(m_GridActive && !IgnoreGrid)
{
int LineDistance = GetLineDistance();
float x = 0.0f;
float y = 0.0f;
if(wx >= 0)
x = (int)((wx+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
else
x = (int)((wx-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
if(wy >= 0)
y = (int)((wy+(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
else
y = (int)((wy-(LineDistance/2)*m_GridFactor)/(LineDistance*m_GridFactor)) * (LineDistance*m_GridFactor);
pSource->m_Position.x = f2fx(x);
pSource->m_Position.y = f2fx(y);
}
else
{
pSource->m_Position.x += f2fx(wx-s_LastWx);
pSource->m_Position.y += f2fx(wy-s_LastWy);
}
}
}
s_LastWx = wx;
s_LastWy = wy;
if(s_Operation == OP_CONTEXT_MENU)
{
if(!UI()->MouseButton(1))
{
m_Map.m_UndoModified++;
static int s_SourcePopupID = 0;
UiInvokePopupMenu(&s_SourcePopupID, 0, UI()->MouseX(), UI()->MouseY(), 120, 180, PopupSource);
m_LockMouse = false;
s_Operation = OP_NONE;
UI()->SetActiveItem(0);
}
}
else
{
if(!UI()->MouseButton(0))
{
if(s_Operation == OP_MOVE)
{
m_Map.m_UndoModified++;
}
m_LockMouse = false;
s_Operation = OP_NONE;
UI()->SetActiveItem(0);
}
}
Graphics()->SetColor(1,1,1,1);
}
else if(UI()->HotItem() == pID)
{
ms_pUiGotContext = pID;
Graphics()->SetColor(1,1,1,1);
m_pTooltip = "Left mouse button to move. Hold alt to ignore grid.";
if(UI()->MouseButton(0))
{
s_Operation = OP_MOVE;
UI()->SetActiveItem(pID);
m_SelectedSource = Index;
s_LastWx = wx;
s_LastWy = wy;
}
if(UI()->MouseButton(1))
{
m_SelectedSource = Index;
s_Operation = OP_CONTEXT_MENU;
UI()->SetActiveItem(pID);
}
}
else
{
Graphics()->SetColor(0,1,0,1);
}
IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f*m_WorldZoom, 5.0f*m_WorldZoom);
Graphics()->QuadsDraw(&QuadItem, 1);
}
void CEditor::DoQuad(CQuad *q, int Index)
{
enum
@ -2083,7 +2258,7 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
}
}
// quad editing
// quad & sound editing
{
if(!m_ShowPicker && m_Brush.IsEmpty())
{
@ -2112,10 +2287,24 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
}
Graphics()->QuadsEnd();
}
if(pEditLayers[k]->m_Type == LAYERTYPE_SOUNDS)
{
CLayerSounds *pLayer = (CLayerSounds *)pEditLayers[k];
Graphics()->TextureSet(-1);
Graphics()->QuadsBegin();
for(int i = 0; i < pLayer->m_lSources.size(); i++)
{
DoSoundSource(&pLayer->m_lSources[i], i);
}
Graphics()->QuadsEnd();
}
}
Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h);
}
}
// do panning
if(UI()->ActiveItem() == s_pEditorID)
@ -2141,7 +2330,8 @@ void CEditor::DoMapEditor(CUIRect View, CUIRect ToolBar)
UI()->SetActiveItem(0);
}
}
}
}
else if(UI()->ActiveItem() == s_pEditorID)
{
@ -2416,6 +2606,24 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *
Change = i;
}
}
else if(pProps[i].m_Type == PROPTYPE_SOUND)
{
char aBuf[64];
if(pProps[i].m_Value < 0)
str_copy(aBuf, "None", sizeof(aBuf));
else
str_format(aBuf, sizeof(aBuf),"%s", m_Map.m_lSounds[pProps[i].m_Value]->m_aName);
if(DoButton_Editor(&pIDs[i], aBuf, 0, &Shifter, 0, 0))
PopupSelectSoundInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY());
int r = PopupSelectSoundResult();
if(r >= -1)
{
*pNewVal = r;
Change = i;
}
}
}
return Change;
@ -2513,7 +2721,7 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
static int s_GroupPopupId = 0;
if(Result == 2)
UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 145, 220, PopupGroup);
UiInvokePopupMenu(&s_GroupPopupId, 0, UI()->MouseX(), UI()->MouseY(), 145, 230, PopupGroup);
if(m_Map.m_lGroups[g]->m_lLayers.size() && Input()->MouseDoubleClick())
m_Map.m_lGroups[g]->m_Collapse ^= 1;
@ -2657,6 +2865,57 @@ void CEditor::AddImage(const char *pFileName, int StorageType, void *pUser)
pEditor->m_Dialog = DIALOG_NONE;
}
void CEditor::AddSound(const char *pFileName, int StorageType, void *pUser)
{
CEditor *pEditor = (CEditor *)pUser;
// check if we have that image already
char aBuf[128];
ExtractName(pFileName, aBuf, sizeof(aBuf));
for(int i = 0; i < pEditor->m_Map.m_lSounds.size(); ++i)
{
if(!str_comp(pEditor->m_Map.m_lSounds[i]->m_aName, aBuf))
return;
}
// load external
IOHANDLE SoundFile = pEditor->Storage()->OpenFile(pFileName, IOFLAG_READ, IStorage::TYPE_ALL);
if(!SoundFile)
return;
// read the whole file into memory
unsigned DataSize = io_length(SoundFile);
void *pData = new char[DataSize];
io_read(SoundFile, pData, DataSize);
io_close(SoundFile);
// load sound
int SoundId = pEditor->Sound()->LoadWVFromMem(pData, DataSize);
if(SoundId == -1)
return;
// add sound
CEditorSound *pSound = new CEditorSound(pEditor);
pSound->m_SoundID = SoundId;
pSound->m_External = 1; // external by default
pSound->m_DataSize = DataSize;
pSound->m_pData = pData;
str_copy(pSound->m_aName, aBuf, sizeof(pSound->m_aName));
pEditor->m_Map.m_lSounds.add(pSound);
if(pEditor->m_SelectedSound > -1 && pEditor->m_SelectedSound < pEditor->m_Map.m_lSounds.size())
{
for(int i = 0; i <= pEditor->m_SelectedSound; ++i)
if(!str_comp(pEditor->m_Map.m_lSounds[i]->m_aName, aBuf))
{
pEditor->m_SelectedSound++;
break;
}
}
pEditor->m_Dialog = DIALOG_NONE;
}
static int gs_ModifyIndexDeletedIndex;
static void ModifyIndexDeleted(int *pIndex)
@ -2717,6 +2976,57 @@ int CEditor::PopupImage(CEditor *pEditor, CUIRect View)
return 0;
}
int CEditor::PopupSound(CEditor *pEditor, CUIRect View)
{
static int s_ReplaceButton = 0;
static int s_RemoveButton = 0;
CUIRect Slot;
View.HSplitTop(2.0f, &Slot, &View);
View.HSplitTop(12.0f, &Slot, &View);
CEditorSound *pSound = pEditor->m_Map.m_lSounds[pEditor->m_SelectedImage];
static int s_ExternalButton = 0;
if(pSound->m_External)
{
if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Embed", 0, &Slot, 0, "Embeds the sound into the map file."))
{
pSound->m_External = 0;
return 1;
}
}
else
{
if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Make external", 0, &Slot, 0, "Removes the sound from the map file."))
{
pSound->m_External = 1;
return 1;
}
}
/*
View.HSplitTop(10.0f, &Slot, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_ReplaceButton, "Replace", 0, &Slot, 0, "Replaces the sound with a new one"))
{
pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Replace sound", "Replace", "mapres", "", Replacesound, pEditor);
return 1;
}*/
View.HSplitTop(10.0f, &Slot, &View);
View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_RemoveButton, "Remove", 0, &Slot, 0, "Removes the sound from the map"))
{
delete pSound;
pEditor->m_Map.m_lSounds.remove_index(pEditor->m_SelectedSound);
gs_ModifyIndexDeletedIndex = pEditor->m_SelectedSound;
pEditor->m_Map.ModifySoundIndex(ModifyIndexDeleted);
return 1;
}
return 0;
}
static int CompareImageName(const void *pObject1, const void *pObject2)
{
CEditorImage *pImage1 = *(CEditorImage**)pObject1;
@ -2918,6 +3228,129 @@ void CEditor::RenderImages(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add Image", "Add", "mapres", "", AddImage, this);
}
void CEditor::RenderSounds(CUIRect ToolBox, CUIRect ToolBar, CUIRect View)
{
static int s_ScrollBar = 0;
static float s_ScrollValue = 0;
float SoundsHeight = 30.0f + 14.0f * m_Map.m_lSounds.size() + 27.0f;
float ScrollDifference = SoundsHeight - ToolBox.h;
if(SoundsHeight > ToolBox.h) // Do we even need a scrollbar?
{
CUIRect Scroll;
ToolBox.VSplitRight(15.0f, &ToolBox, &Scroll);
ToolBox.VSplitRight(3.0f, &ToolBox, 0); // extra spacing
Scroll.HMargin(5.0f, &Scroll);
s_ScrollValue = UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue);
if(UI()->MouseInside(&Scroll) || UI()->MouseInside(&ToolBox))
{
int ScrollNum = (int)((SoundsHeight-ToolBox.h)/14.0f)+1;
if(ScrollNum > 0)
{
if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP))
s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f);
if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN))
s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f);
}
else
ScrollNum = 0;
}
}
float SoundStartAt = ScrollDifference * s_ScrollValue;
if(SoundStartAt < 0.0f)
SoundStartAt = 0.0f;
float SoundStopAt = SoundsHeight - ScrollDifference * (1 - s_ScrollValue);
float ImageCur = 0.0f;
for(int e = 0; e < 2; e++) // two passes, first embedded, then external
{
CUIRect Slot;
if(ImageCur > SoundStopAt)
break;
else if(ImageCur >= SoundStartAt)
{
ToolBox.HSplitTop(15.0f, &Slot, &ToolBox);
if(e == 0)
UI()->DoLabel(&Slot, "Embedded", 12.0f, 0);
else
UI()->DoLabel(&Slot, "External", 12.0f, 0);
}
ImageCur += 15.0f;
for(int i = 0; i < m_Map.m_lSounds.size(); i++)
{
if((e && !m_Map.m_lSounds[i]->m_External) ||
(!e && m_Map.m_lSounds[i]->m_External))
{
continue;
}
if(ImageCur > SoundStopAt)
break;
else if(ImageCur < SoundStartAt)
{
ImageCur += 14.0f;
continue;
}
ImageCur += 14.0f;
char aBuf[128];
str_copy(aBuf, m_Map.m_lSounds[i]->m_aName, sizeof(aBuf));
ToolBox.HSplitTop(12.0f, &Slot, &ToolBox);
int Selected = m_SelectedSound == i;
for(int x = 0; x < m_Map.m_lGroups.size(); ++x)
for(int j = 0; j < m_Map.m_lGroups[x]->m_lLayers.size(); ++j)
if(m_Map.m_lGroups[x]->m_lLayers[j]->m_Type == LAYERTYPE_SOUNDS)
{
CLayerSounds *pLayer = static_cast<CLayerSounds *>(m_Map.m_lGroups[x]->m_lLayers[j]);
if(pLayer->m_Sound == i)
{
Selected = 2 + Selected;
goto done;
}
}
done:
if(int Result = DoButton_Editor(&m_Map.m_lSounds[i], aBuf, Selected, &Slot,
BUTTON_CONTEXT, "Select sound"))
{
m_SelectedSound = i;
static int s_PopupSoundID = 0;
if(Result == 2)
UiInvokePopupMenu(&s_PopupSoundID, 0, UI()->MouseX(), UI()->MouseY(), 120, 80, PopupSound);
}
ToolBox.HSplitTop(2.0f, 0, &ToolBox);
}
// separator
ToolBox.HSplitTop(5.0f, &Slot, &ToolBox);
ImageCur += 5.0f;
IGraphics::CLineItem LineItem(Slot.x, Slot.y+Slot.h/2, Slot.x+Slot.w, Slot.y+Slot.h/2);
Graphics()->TextureSet(-1);
Graphics()->LinesBegin();
Graphics()->LinesDraw(&LineItem, 1);
Graphics()->LinesEnd();
}
CUIRect Slot;
ToolBox.HSplitTop(5.0f, &Slot, &ToolBox);
// new Sound
static int s_NewSoundButton = 0;
ToolBox.HSplitTop(12.0f, &Slot, &ToolBox);
if(DoButton_Editor(&s_NewSoundButton, "Add", 0, &Slot, 0, "Load a new sound to use in the map"))
InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_SOUND, "Add Sound", "Add", "mapres", "", AddSound, this);
}
static int EditorListdirCallback(const char *pName, int IsDir, int StorageType, void *pUser)
{
@ -2926,15 +3359,21 @@ static int EditorListdirCallback(const char *pName, int IsDir, int StorageType,
if((pName[0] == '.' && (pName[1] == 0 ||
(pName[1] == '.' && pName[2] == 0 && (!str_comp(pEditor->m_pFileDialogPath, "maps") || !str_comp(pEditor->m_pFileDialogPath, "mapres"))))) ||
(!IsDir && ((pEditor->m_FileDialogFileType == CEditor::FILETYPE_MAP && (Length < 4 || str_comp(pName+Length-4, ".map"))) ||
(pEditor->m_FileDialogFileType == CEditor::FILETYPE_IMG && (Length < 4 || str_comp(pName+Length-4, ".png"))))))
(pEditor->m_FileDialogFileType == CEditor::FILETYPE_IMG && (Length < 4 || str_comp(pName+Length-4, ".png"))) ||
(pEditor->m_FileDialogFileType == CEditor::FILETYPE_SOUND && (Length < 3 || str_comp(pName+Length-3, ".wv"))))))
return 0;
CEditor::CFilelistItem Item;
str_copy(Item.m_aFilename, pName, sizeof(Item.m_aFilename));
if(IsDir)
str_format(Item.m_aName, sizeof(Item.m_aName), "%s/", pName);
else
{
if(pEditor->m_FileDialogFileType == CEditor::FILETYPE_SOUND)
str_copy(Item.m_aName, pName, min(static_cast<int>(sizeof(Item.m_aName)), Length-2));
else
str_copy(Item.m_aName, pName, min(static_cast<int>(sizeof(Item.m_aName)), Length-3));
}
Item.m_IsDir = IsDir != 0;
Item.m_IsLink = false;
Item.m_StorageType = StorageType;
@ -3300,11 +3739,21 @@ void CEditor::RenderModebar(CUIRect View)
View.VSplitLeft(65.0f, &Button, &View);
Button.HSplitTop(30.0f, 0, &Button);
static int s_Button = 0;
const char *pButName = m_Mode == MODE_LAYERS ? "Layers" : "Images";
if(DoButton_Tab(&s_Button, pButName, 0, &Button, 0, "Switch between images and layers managment."))
const char *pButName = "";
if(m_Mode == MODE_LAYERS)
pButName = "Layers";
else if(m_Mode == MODE_IMAGES)
pButName = "Images";
else if(m_Mode == MODE_SOUNDS)
pButName = "Sounds";
if(DoButton_Tab(&s_Button, pButName, 0, &Button, 0, "Switch between images, sounds and layers managment."))
{
if (m_Mode == MODE_LAYERS)
m_Mode = MODE_IMAGES;
else if(m_Mode == MODE_IMAGES)
m_Mode = MODE_SOUNDS;
else
m_Mode = MODE_LAYERS;
}
@ -3423,6 +3872,17 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
CUIRect Button;
CEnvelope *pNewEnv = 0;
ToolBar.VSplitRight(50.0f, &ToolBar, &Button);
static int s_NewSoundButton = 0;
if(DoButton_Editor(&s_NewSoundButton, "Sound.+", 0, &Button, 0, "Creates a new sound envelope"))
{
m_Map.m_Modified = true;
m_Map.m_UndoModified++;
pNewEnv = m_Map.NewEnvelope(1);
}
ToolBar.VSplitRight(5.0f, &ToolBar, &Button);
ToolBar.VSplitRight(50.0f, &ToolBar, &Button);
static int s_New4dButton = 0;
if(DoButton_Editor(&s_New4dButton, "Color+", 0, &Button, 0, "Creates a new color envelope"))
@ -3530,12 +3990,16 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
ToolBar.VSplitLeft(15.0f, &Button, &ToolBar);
static const char *s_paNames[2][4] = {
static const char *s_paNames[4][4] = {
{"V", "", "", ""},
{"", "", "", ""},
{"X", "Y", "R", ""},
{"R", "G", "B", "A"},
};
const char *paDescriptions[2][4] = {
const char *paDescriptions[4][4] = {
{"Volume of the envelope", "", "", ""},
{"", "", "", ""},
{"X-axis of the envelope", "Y-axis of the envelope", "Rotation of the envelope", ""},
{"Red value of the envelope", "Green value of the envelope", "Blue value of the envelope", "Alpha value of the envelope"},
};
@ -3552,7 +4016,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
else if(i == envelope->channels-1) draw_func = draw_editor_button_r;
else draw_func = draw_editor_button_m;*/
if(DoButton_Editor(&s_aChannelButtons[i], s_paNames[pEnvelope->m_Channels-3][i], s_ActiveChannels&Bit, &Button, 0, paDescriptions[pEnvelope->m_Channels-3][i]))
if(DoButton_Editor(&s_aChannelButtons[i], s_paNames[pEnvelope->m_Channels-1][i], s_ActiveChannels&Bit, &Button, 0, paDescriptions[pEnvelope->m_Channels-1][i]))
s_ActiveChannels ^= Bit;
}
@ -4091,6 +4555,8 @@ void CEditor::Render()
RenderLayers(ToolBox, ToolBar, View);
else if(m_Mode == MODE_IMAGES)
RenderImages(ToolBox, ToolBar, View);
else if(m_Mode == MODE_SOUNDS)
RenderSounds(ToolBox, ToolBar, View);
Graphics()->MapScreen(UI()->Screen()->x, UI()->Screen()->y, UI()->Screen()->w, UI()->Screen()->h);
@ -4198,6 +4664,8 @@ void CEditor::Reset(bool CreateDefault)
m_SelectedPoints = 0;
m_SelectedEnvelope = 0;
m_SelectedImage = 0;
m_SelectedSound = 0;
m_SelectedSource = -1;
m_WorldOffsetX = 0;
m_WorldOffsetY = 0;
@ -4318,6 +4786,7 @@ void CEditorMap::Clean()
m_lGroups.delete_all();
m_lEnvelopes.delete_all();
m_lImages.delete_all();
m_lSounds.delete_all();
m_MapInfo.Reset();
@ -4376,6 +4845,7 @@ void CEditor::Init()
m_pGraphics = Kernel()->RequestInterface<IGraphics>();
m_pTextRender = Kernel()->RequestInterface<ITextRender>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
m_pSound = Kernel()->RequestInterface<ISound>();
m_RenderTools.m_pGraphics = m_pGraphics;
m_RenderTools.m_pUI = &m_UI;
m_UI.SetGraphics(m_pGraphics, m_pTextRender);

View file

@ -21,6 +21,7 @@
#include <engine/shared/datafile.h>
#include <engine/editor.h>
#include <engine/graphics.h>
#include <engine/sound.h>
#include "auto_map.h"
@ -33,6 +34,7 @@ enum
{
MODE_LAYERS=0,
MODE_IMAGES,
MODE_SOUNDS,
DIALOG_NONE=0,
DIALOG_FILE,
@ -155,6 +157,7 @@ public:
virtual void ModifyImageIndex(INDEX_MODIFY_FUNC pfnFunc) {}
virtual void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) {}
virtual void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc) {}
virtual void GetSize(float *w, float *h) { *w = 0; *h = 0;}
@ -263,6 +266,12 @@ public:
for(int i = 0; i < m_lLayers.size(); i++)
m_lLayers[i]->ModifyEnvelopeIndex(Func);
}
void ModifySoundIndex(INDEX_MODIFY_FUNC Func)
{
for(int i = 0; i < m_lLayers.size(); i++)
m_lLayers[i]->ModifySoundIndex(Func);
}
};
class CEditorImage : public CImageInfo
@ -294,6 +303,32 @@ public:
class CAutoMapper m_AutoMapper;
};
class CEditorSound
{
public:
CEditor *m_pEditor;
CEditorSound(CEditor *pEditor)
{
m_pEditor = pEditor;
m_aName[0] = 0;
m_External = 0;
m_SoundID = 0;
m_pData = 0x0;
m_DataSize = 0;
}
~CEditorSound();
int m_SoundID;
int m_External;
char m_aName[128];
void *m_pData;
unsigned m_DataSize;
};
class CEditorMap
{
void MakeGameGroup(CLayerGroup *pGroup);
@ -311,6 +346,7 @@ public:
array<CLayerGroup*> m_lGroups;
array<CEditorImage*> m_lImages;
array<CEnvelope*> m_lEnvelopes;
array<CEditorSound*> m_lSounds;
class CMapInfo
{
@ -400,6 +436,14 @@ public:
m_lGroups[i]->ModifyEnvelopeIndex(pfnFunc);
}
void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc)
{
m_Modified = true;
m_UndoModified++;
for(int i = 0; i < m_lGroups.size(); i++)
m_lGroups[i]->ModifySoundIndex(pfnFunc);
}
void Clean();
void CreateDefault(int EntitiesTexture);
@ -441,6 +485,7 @@ enum
PROPTYPE_IMAGE,
PROPTYPE_ENVELOPE,
PROPTYPE_SHIFT,
PROPTYPE_SOUND,
};
typedef struct
@ -548,6 +593,7 @@ class CEditor : public IEditor
class IConsole *m_pConsole;
class IGraphics *m_pGraphics;
class ITextRender *m_pTextRender;
class ISound *m_pSound;
class IStorage *m_pStorage;
CRenderTools m_RenderTools;
CUI m_UI;
@ -556,6 +602,7 @@ public:
class IClient *Client() { return m_pClient; };
class IConsole *Console() { return m_pConsole; };
class IGraphics *Graphics() { return m_pGraphics; };
class ISound *Sound() { return m_pSound; }
class ITextRender *TextRender() { return m_pTextRender; };
class IStorage *Storage() { return m_pStorage; };
CUI *UI() { return &m_UI; }
@ -567,6 +614,7 @@ public:
m_pClient = 0;
m_pGraphics = 0;
m_pTextRender = 0;
m_pSound = 0;
m_Mode = MODE_LAYERS;
m_Dialog = 0;
@ -692,6 +740,7 @@ public:
CLayer *GetSelectedLayerType(int Index, int Type);
CLayer *GetSelectedLayer(int Index);
CLayerGroup *GetSelectedGroup();
CSoundSource *GetSelectedSource();
int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, vec4 color = vec4(1,1,1,0.5f));
@ -723,6 +772,7 @@ public:
{
FILETYPE_MAP,
FILETYPE_IMG,
FILETYPE_SOUND,
MAX_PATH_LENGTH = 512
};
@ -801,6 +851,8 @@ public:
int m_SelectedEnvelopePoint;
int m_SelectedQuadEnvelope;
int m_SelectedImage;
int m_SelectedSound;
int m_SelectedSource;
static int ms_CheckerTexture;
static int ms_BackgroundTexture;
@ -851,10 +903,13 @@ public:
static int PopupMapInfo(CEditor *pEditor, CUIRect View);
static int PopupEvent(CEditor *pEditor, CUIRect View);
static int PopupSelectImage(CEditor *pEditor, CUIRect View);
static int PopupSelectSound(CEditor *pEditor, CUIRect View);
static int PopupSelectGametileOp(CEditor *pEditor, CUIRect View);
static int PopupImage(CEditor *pEditor, CUIRect View);
static int PopupMenuFile(CEditor *pEditor, CUIRect View);
static int PopupSelectConfigAutoMap(CEditor *pEditor, CUIRect View);
static int PopupSound(CEditor *pEditor, CUIRect View);
static int PopupSource(CEditor *pEditor, CUIRect View);
static void CallbackOpenMap(const char *pFileName, int StorageType, void *pUser);
static void CallbackAppendMap(const char *pFileName, int StorageType, void *pUser);
@ -869,12 +924,17 @@ public:
void PopupSelectConfigAutoMapInvoke(float x, float y);
int PopupSelectConfigAutoMapResult();
void PopupSelectSoundInvoke(int Current, float x, float y);
int PopupSelectSoundResult();
vec4 ButtonColorMul(const void *pID);
void DoQuadEnvelopes(const array<CQuad> &m_lQuads, int TexID = -1);
void DoQuadEnvPoint(const CQuad *pQuad, int QIndex, int pIndex);
void DoQuadPoint(CQuad *pQuad, int QuadIndex, int v);
void DoSoundSource(CSoundSource *pSource, int Index);
void DoMapEditor(CUIRect View, CUIRect Toolbar);
void DoToolbar(CUIRect Toolbar);
void DoQuad(CQuad *pQuad, int Index);
@ -883,9 +943,11 @@ public:
static void ReplaceImage(const char *pFilename, int StorageType, void *pUser);
static void AddImage(const char *pFilename, int StorageType, void *pUser);
static void AddSound(const char *pFileName, int StorageType, void *pUser);
void RenderImages(CUIRect Toolbox, CUIRect Toolbar, CUIRect View);
void RenderLayers(CUIRect Toolbox, CUIRect Toolbar, CUIRect View);
void RenderSounds(CUIRect Toolbox, CUIRect Toolbar, CUIRect View);
void RenderModebar(CUIRect View);
void RenderStatusbar(CUIRect View);
void RenderEnvelopeEditor(CUIRect View);
@ -1026,5 +1088,27 @@ public:
virtual void FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect);
};
class CLayerSounds : public CLayer
{
public:
CLayerSounds();
~CLayerSounds();
virtual void Render();
CSoundSource *NewSource();
virtual void BrushSelecting(CUIRect Rect);
virtual int BrushGrab(CLayerGroup *pBrush, CUIRect Rect);
virtual void BrushPlace(CLayer *pBrush, float wx, float wy);
virtual int RenderProperties(CUIRect *pToolbox);
virtual void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc);
virtual void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc);
int m_Sound;
array<CSoundSource> m_lSources;
};
#endif

View file

@ -271,6 +271,30 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName)
df.AddItem(MAPITEMTYPE_IMAGE, i, sizeof(Item), &Item);
}
// save sounds
for(int i = 0; i < m_lSounds.size(); i++)
{
CEditorSound *pSound = m_lSounds[i];
CMapItemSound Item;
Item.m_Version = 1;
Item.m_External = pSound->m_External;
Item.m_SoundName = df.AddData(str_length(pSound->m_aName)+1, pSound->m_aName);
if(pSound->m_External)
{
Item.m_SoundDataSize = 0;
Item.m_SoundData = -1;
}
else
{
Item.m_SoundData = df.AddData(pSound->m_DataSize, pSound->m_pData);
Item.m_SoundDataSize = pSound->m_DataSize;
}
df.AddItem(MAPITEMTYPE_SOUND, i, sizeof(Item), &Item);
}
// save layers
int LayerCount = 0, GroupCount = 0;
for(int g = 0; g < m_lGroups.size(); g++)
@ -415,6 +439,30 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName)
LayerCount++;
}
}
else if(pGroup->m_lLayers[l]->m_Type == LAYERTYPE_SOUNDS)
{
m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", "saving sounds layer");
CLayerSounds *pLayer = (CLayerSounds *)pGroup->m_lLayers[l];
if(pLayer->m_lSources.size())
{
CMapItemLayerSounds Item;
Item.m_Version = CMapItemLayerSounds::CURRENT_VERSION;
Item.m_Layer.m_Flags = pLayer->m_Flags;
Item.m_Layer.m_Type = pLayer->m_Type;
Item.m_Sound = pLayer->m_Sound;
// add the data
Item.m_NumSources = pLayer->m_lSources.size();
Item.m_Data = df.AddDataSwapped(pLayer->m_lSources.size()*sizeof(CSoundSource), pLayer->m_lSources.base_ptr());
// save layer name
StrToInts(Item.m_aName, sizeof(Item.m_aName)/sizeof(int), pLayer->m_aName);
df.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item);
GItem.m_NumLayers++;
LayerCount++;
}
}
}
df.AddItem(MAPITEMTYPE_GROUP, GroupCount++, sizeof(GItem), &GItem);
@ -579,6 +627,59 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
}
}
// load sounds
{
int Start, Num;
DataFile.GetType( MAPITEMTYPE_SOUND, &Start, &Num);
for(int i = 0; i < Num; i++)
{
CMapItemSound *pItem = (CMapItemSound *)DataFile.GetItem(Start+i, 0, 0);
char *pName = (char *)DataFile.GetData(pItem->m_SoundName);
// copy base info
CEditorSound *pSound = new CEditorSound(m_pEditor);
pSound->m_External = pItem->m_External;
if(pItem->m_External)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf),"mapres/%s.wv", pName);
// load external
IOHANDLE SoundFile = pStorage->OpenFile(pName, IOFLAG_READ, IStorage::TYPE_ALL);
if(SoundFile)
{
// read the whole file into memory
pSound->m_DataSize = io_length(SoundFile);
pSound->m_pData = new char[pSound->m_DataSize];
io_read(SoundFile, pSound->m_pData, pSound->m_DataSize);
io_close(SoundFile);
pSound->m_SoundID = m_pEditor->Sound()->LoadWVFromMem(pSound->m_pData, pSound->m_DataSize);
}
}
else
{
pSound->m_DataSize = pItem->m_SoundDataSize;
// copy sample data
void *pData = DataFile.GetData(pItem->m_SoundData);
pSound->m_pData = mem_alloc(pSound->m_DataSize, 1);
mem_copy(pSound->m_pData, pData, pSound->m_DataSize);
pSound->m_SoundID = m_pEditor->Sound()->LoadWVFromMem(pSound->m_pData, pSound->m_DataSize);
}
// copy image name
if(pName)
str_copy(pSound->m_aName, pName, sizeof(pSound->m_aName));
m_lSounds.add(pSound);
// unload image
DataFile.UnloadData(pItem->m_SoundData);
DataFile.UnloadData(pItem->m_SoundName);
}
}
// load groups
{
int LayersStart, LayersNum;
@ -885,6 +986,29 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
mem_copy(pQuads->m_lQuads.base_ptr(), pData, sizeof(CQuad)*pQuadsItem->m_NumQuads);
DataFile.UnloadData(pQuadsItem->m_Data);
}
else if(pLayerItem->m_Type == LAYERTYPE_SOUNDS)
{
CMapItemLayerSounds *pSoundsItem = (CMapItemLayerSounds *)pLayerItem;
CLayerSounds *pSounds = new CLayerSounds;
pSounds->m_pEditor = m_pEditor;
pLayer = pSounds;
pSounds->m_Sound = pSoundsItem->m_Sound;
// validate m_Sound
if(pSounds->m_Sound < -1 || pSounds->m_Sound >= m_lSounds.size())
pSounds->m_Sound = -1;
// load layer name
if(pSoundsItem->m_Version >= 1)
IntsToStr(pSoundsItem->m_aName, sizeof(pSounds->m_aName)/sizeof(int), pSounds->m_aName);
// load data
void *pData = DataFile.GetDataSwapped(pSoundsItem->m_Data);
pGroup->AddLayer(pSounds);
pSounds->m_lSources.set_size(pSoundsItem->m_NumSources);
mem_copy(pSounds->m_lSources.base_ptr(), pData, sizeof(CSoundSource)*pSoundsItem->m_NumSources);
DataFile.UnloadData(pSoundsItem->m_Data);
}
if(pLayer)
pLayer->m_Flags = pLayerItem->m_Flags;

View file

@ -0,0 +1,188 @@
#include "editor.h"
static const float s_SourceVisualSize = 50.0f;
CLayerSounds::CLayerSounds()
{
m_Type = LAYERTYPE_SOUNDS;
str_copy(m_aName, "Sounds", sizeof(m_aName));
m_Sound = -1;
}
CLayerSounds::~CLayerSounds()
{
}
void CLayerSounds::Render()
{
// TODO: nice texture
Graphics()->TextureSet(-1);
Graphics()->BlendNormal();
Graphics()->QuadsBegin();
// draw falloff distance
Graphics()->SetColor(0.6f, 0.8f, 1.0f, 0.4f);
for(int i = 0; i < m_lSources.size(); i++)
{
CSoundSource *pSource = &m_lSources[i];
float OffsetX = 0;
float OffsetY = 0;
if(pSource->m_PosEnv >= 0)
{
float aChannels[4];
m_pEditor->EnvelopeEval(pSource->m_PosEnvOffset/1000.0f, pSource->m_PosEnv, aChannels, m_pEditor);
OffsetX = aChannels[0];
OffsetY = aChannels[1];
}
m_pEditor->RenderTools()->DrawCircle(fx2f(pSource->m_Position.x)+OffsetX, fx2f(pSource->m_Position.y)+OffsetY, pSource->m_FalloffDistance, 32);
}
// draw handles
Graphics()->SetColor(1.0f, 0.0f, 1.0f, 1.0f);
for(int i = 0; i < m_lSources.size(); i++)
{
CSoundSource *pSource = &m_lSources[i];
float OffsetX = 0;
float OffsetY = 0;
if(pSource->m_PosEnv >= 0)
{
float aChannels[4];
m_pEditor->EnvelopeEval(pSource->m_PosEnvOffset/1000.0f, pSource->m_PosEnv, aChannels, m_pEditor);
OffsetX = aChannels[0];
OffsetY = aChannels[1];
}
IGraphics::CQuadItem QuadItem(fx2f(pSource->m_Position.x)+OffsetX, fx2f(pSource->m_Position.y)+OffsetY, s_SourceVisualSize, s_SourceVisualSize);
Graphics()->QuadsDraw(&QuadItem, 1);
}
Graphics()->QuadsEnd();
}
CSoundSource *CLayerSounds::NewSource()
{
m_pEditor->m_Map.m_Modified = true;
CSoundSource *pSource = &m_lSources[m_lSources.add(CSoundSource())];
pSource->m_Position.x = 0;
pSource->m_Position.y = 0;
pSource->m_Loop = 1;
pSource->m_TimeDelay = 0;
pSource->m_FalloffDistance = 1500;
pSource->m_PosEnv = -1;
pSource->m_PosEnvOffset = 0;
pSource->m_SoundEnv = -1;
pSource->m_SoundEnvOffset = 0;
return pSource;
}
void CLayerSounds::BrushSelecting(CUIRect Rect)
{
// draw selection rectangle
IGraphics::CLineItem Array[4] = {
IGraphics::CLineItem(Rect.x, Rect.y, Rect.x+Rect.w, Rect.y),
IGraphics::CLineItem(Rect.x+Rect.w, Rect.y, Rect.x+Rect.w, Rect.y+Rect.h),
IGraphics::CLineItem(Rect.x+Rect.w, Rect.y+Rect.h, Rect.x, Rect.y+Rect.h),
IGraphics::CLineItem(Rect.x, Rect.y+Rect.h, Rect.x, Rect.y)};
Graphics()->TextureSet(-1);
Graphics()->LinesBegin();
Graphics()->LinesDraw(Array, 4);
Graphics()->LinesEnd();
}
int CLayerSounds::BrushGrab(CLayerGroup *pBrush, CUIRect Rect)
{
// create new layer
CLayerSounds *pGrabbed = new CLayerSounds();
pGrabbed->m_pEditor = m_pEditor;
pGrabbed->m_Sound = m_Sound;
pBrush->AddLayer(pGrabbed);
for(int i = 0; i < m_lSources.size(); i++)
{
CSoundSource *pSource = &m_lSources[i];
float px = fx2f(pSource->m_Position.x);
float py = fx2f(pSource->m_Position.y);
if(px > Rect.x && px < Rect.x+Rect.w && py > Rect.y && py < Rect.y+Rect.h)
{
CSoundSource n;
n = *pSource;
n.m_Position.x -= f2fx(Rect.x);
n.m_Position.y -= f2fx(Rect.y);
pGrabbed->m_lSources.add(n);
}
}
return pGrabbed->m_lSources.size()?1:0;
}
void CLayerSounds::BrushPlace(CLayer *pBrush, float wx, float wy)
{
CLayerSounds *l = (CLayerSounds *)pBrush;
for(int i = 0; i < l->m_lSources.size(); i++)
{
CSoundSource n = l->m_lSources[i];
n.m_Position.x += f2fx(wx);
n.m_Position.y += f2fx(wy);
m_lSources.add(n);
}
m_pEditor->m_Map.m_Modified = true;
}
int CLayerSounds::RenderProperties(CUIRect *pToolBox)
{
//
enum
{
PROP_SOUND=0,
NUM_PROPS,
};
CProperty aProps[] = {
{"Sound", m_Sound, PROPTYPE_SOUND, -1, 0},
{0},
};
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal);
if(Prop != -1)
m_pEditor->m_Map.m_Modified = true;
if(Prop == PROP_SOUND)
{
if(NewVal >= 0)
m_Sound = NewVal%m_pEditor->m_Map.m_lSounds.size();
else
m_Sound = -1;
}
return 0;
}
void CLayerSounds::ModifySoundIndex(INDEX_MODIFY_FUNC Func)
{
Func(&m_Sound);
}
void CLayerSounds::ModifyEnvelopeIndex(INDEX_MODIFY_FUNC Func)
{
for(int i = 0; i < m_lSources.size(); i++)
Func(&m_lSources[i].m_SoundEnv);
}

View file

@ -226,8 +226,8 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View)
}
}
// new tile layer
View.HSplitBottom(10.0f, &View, &Button);
// new quad layer
View.HSplitBottom(7.0f, &View, &Button);
View.HSplitBottom(12.0f, &View, &Button);
static int s_NewQuadLayerButton = 0;
if(pEditor->DoButton_Editor(&s_NewQuadLayerButton, "Add quads layer", 0, &Button, 0, "Creates a new quad layer"))
@ -240,7 +240,7 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View)
return 1;
}
// new quad layer
// new tile layer
View.HSplitBottom(5.0f, &View, &Button);
View.HSplitBottom(12.0f, &View, &Button);
static int s_NewTileLayerButton = 0;
@ -254,6 +254,20 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View)
return 1;
}
// new sound layer
View.HSplitBottom(5.0f, &View, &Button);
View.HSplitBottom(12.0f, &View, &Button);
static int s_NewSoundLayerButton = 0;
if(pEditor->DoButton_Editor(&s_NewSoundLayerButton, "Add sound layer", 0, &Button, 0, "Creates a new sound layer"))
{
CLayer *l = new CLayerSounds;
l->m_pEditor = pEditor;
pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->AddLayer(l);
pEditor->m_SelectedLayer = pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_lLayers.size()-1;
pEditor->m_Map.m_lGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
return 1;
}
// group name
if(!pEditor->GetSelectedGroup()->m_GameGroup)
{
@ -589,6 +603,100 @@ int CEditor::PopupQuad(CEditor *pEditor, CUIRect View)
return 0;
}
int CEditor::PopupSource(CEditor *pEditor, CUIRect View)
{
CSoundSource *pSource = pEditor->GetSelectedSource();
CUIRect Button;
// delete button
View.HSplitBottom(12.0f, &View, &Button);
static int s_DeleteButton = 0;
if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete", 0, &Button, 0, "Deletes the current source"))
{
CLayerSounds *pLayer = (CLayerSounds *)pEditor->GetSelectedLayerType(0, LAYERTYPE_SOUNDS);
if(pLayer)
{
pEditor->m_Map.m_Modified = true;
pLayer->m_lSources.remove_index(pEditor->m_SelectedSource);
pEditor->m_SelectedSource--;
}
return 1;
}
enum
{
PROP_POS_X=0,
PROP_POS_Y,
PROP_LOOP,
PROP_TIME_DELAY,
PROP_DISTANCE,
PROP_POS_ENV,
PROP_POS_ENV_OFFSET,
PROP_SOUND_ENV,
PROP_SOUND_ENV_OFFSET,
NUM_PROPS,
};
CProperty aProps[] = {
{"Pos X", pSource->m_Position.x/1000, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Pos Y", pSource->m_Position.y/1000, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Loop", pSource->m_Loop, PROPTYPE_BOOL, 0, 1},
{"Delay", pSource->m_TimeDelay, PROPTYPE_INT_SCROLL, 0, 1000000},
{"Distance", pSource->m_FalloffDistance, PROPTYPE_INT_SCROLL, 0, 1000000},
{"Pos. Env", pSource->m_PosEnv+1, PROPTYPE_INT_STEP, 0, pEditor->m_Map.m_lEnvelopes.size()+1},
{"Pos. TO", pSource->m_PosEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Sound Env", pSource->m_SoundEnv+1, PROPTYPE_INT_STEP, 0, pEditor->m_Map.m_lEnvelopes.size()+1},
{"Sound. TO", pSource->m_PosEnvOffset, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{0},
};
static int s_aIds[NUM_PROPS] = {0};
int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal);
if(Prop != -1)
pEditor->m_Map.m_Modified = true;
if(Prop == PROP_POS_X) pSource->m_Position.x = NewVal*1000;
if(Prop == PROP_POS_Y) pSource->m_Position.y = NewVal*1000;
if(Prop == PROP_LOOP) pSource->m_Loop = NewVal;
if(Prop == PROP_TIME_DELAY) pSource->m_TimeDelay = NewVal;
if(Prop == PROP_DISTANCE) pSource->m_FalloffDistance = NewVal;
if(Prop == PROP_POS_ENV)
{
int Index = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1);
int Step = (Index-pSource->m_PosEnv)%2;
if(Step != 0)
{
for(; Index >= -1 && Index < pEditor->m_Map.m_lEnvelopes.size(); Index += Step)
if(Index == -1 || pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 3)
{
pSource->m_PosEnv = Index;
break;
}
}
}
if(Prop == PROP_POS_ENV_OFFSET) pSource->m_PosEnvOffset = NewVal;
if(Prop == PROP_SOUND_ENV)
{
int Index = clamp(NewVal-1, -1, pEditor->m_Map.m_lEnvelopes.size()-1);
int Step = (Index-pSource->m_SoundEnv)%2;
if(Step != 0)
{
for(; Index >= -1 && Index < pEditor->m_Map.m_lEnvelopes.size(); Index += Step)
if(Index == -1 || pEditor->m_Map.m_lEnvelopes[Index]->m_Channels == 1)
{
pSource->m_SoundEnv = Index;
break;
}
}
}
if(Prop == PROP_SOUND_ENV_OFFSET) pSource->m_SoundEnvOffset = NewVal;
return 0;
}
int CEditor::PopupPoint(CEditor *pEditor, CUIRect View)
{
CQuad *pQuad = pEditor->GetSelectedQuad();
@ -978,6 +1086,101 @@ int CEditor::PopupSelectImageResult()
return g_SelectImageCurrent;
}
static int g_SelectSoundSelected = -100;
static int g_SelectSoundCurrent = -100;
int CEditor::PopupSelectSound(CEditor *pEditor, CUIRect View)
{
CUIRect ButtonBar, SoundView;
View.VSplitLeft(80.0f, &ButtonBar, &View);
View.Margin(10.0f, &SoundView);
int ShowSound = g_SelectSoundCurrent;
static int s_ScrollBar = 0;
static float s_ScrollValue = 0;
float SoundsHeight = pEditor->m_Map.m_lSounds.size() * 14;
float ScrollDifference = SoundsHeight - ButtonBar.h;
if(pEditor->m_Map.m_lSounds.size() > 20) // Do we need a scrollbar?
{
CUIRect Scroll;
ButtonBar.VSplitRight(15.0f, &ButtonBar, &Scroll);
ButtonBar.VSplitRight(3.0f, &ButtonBar, 0); // extra spacing
Scroll.HMargin(5.0f, &Scroll);
s_ScrollValue = pEditor->UiDoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue);
if(pEditor->UI()->MouseInside(&Scroll) || pEditor->UI()->MouseInside(&ButtonBar))
{
int ScrollNum = (int)((SoundsHeight-ButtonBar.h)/14.0f)+1;
if(ScrollNum > 0)
{
if(pEditor->Input()->KeyPresses(KEY_MOUSE_WHEEL_UP))
s_ScrollValue = clamp(s_ScrollValue - 1.0f/ScrollNum, 0.0f, 1.0f);
if(pEditor->Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN))
s_ScrollValue = clamp(s_ScrollValue + 1.0f/ScrollNum, 0.0f, 1.0f);
}
else
ScrollNum = 0;
}
}
float SoundStartAt = ScrollDifference * s_ScrollValue;
if(SoundStartAt < 0.0f)
SoundStartAt = 0.0f;
float SoundStopAt = SoundsHeight - ScrollDifference * (1 - s_ScrollValue);
float SoundCur = 0.0f;
for(int i = -1; i < pEditor->m_Map.m_lSounds.size(); i++)
{
if(SoundCur > SoundStopAt)
break;
if(SoundCur < SoundStartAt)
{
SoundCur += 14.0f;
continue;
}
SoundCur += 14.0f;
CUIRect Button;
ButtonBar.HSplitTop(14.0f, &Button, &ButtonBar);
if(pEditor->UI()->MouseInside(&Button))
ShowSound = i;
if(i == -1)
{
if(pEditor->DoButton_MenuItem(&pEditor->m_Map.m_lSounds[i], "None", i==g_SelectSoundCurrent, &Button))
g_SelectSoundSelected = -1;
}
else
{
if(pEditor->DoButton_MenuItem(&pEditor->m_Map.m_lSounds[i], pEditor->m_Map.m_lSounds[i]->m_aName, i==g_SelectSoundCurrent, &Button))
g_SelectSoundSelected = i;
}
}
return 0;
}
void CEditor::PopupSelectSoundInvoke(int Current, float x, float y)
{
static int s_SelectSoundPopupId = 0;
g_SelectSoundSelected = -100;
g_SelectSoundCurrent = Current;
UiInvokePopupMenu(&s_SelectSoundPopupId, 0, x, y, 400, 300, PopupSelectSound);
}
int CEditor::PopupSelectSoundResult()
{
if(g_SelectSoundSelected == -100)
return -100;
g_SelectSoundCurrent = g_SelectSoundSelected;
g_SelectSoundSelected = -100;
return g_SelectSoundCurrent;
}
static int s_GametileOpSelected = -1;
int CEditor::PopupSelectGametileOp(CEditor *pEditor, CUIRect View)

View file

@ -18,6 +18,7 @@ enum
LAYERTYPE_SPEEDUP,
LAYERTYPE_SWITCH,
LAYERTYPE_TUNE,
LAYERTYPE_SOUNDS,
MAPITEMTYPE_VERSION=0,
MAPITEMTYPE_INFO,
@ -26,6 +27,7 @@ enum
MAPITEMTYPE_GROUP,
MAPITEMTYPE_LAYER,
MAPITEMTYPE_ENVPOINTS,
MAPITEMTYPE_SOUND,
CURVETYPE_STEP=0,
@ -327,6 +329,45 @@ struct CMapItemEnvelope : public CMapItemEnvelope_v1
int m_Synchronized;
};
struct CSoundSource
{
CPoint m_Position;
int m_Loop;
int m_TimeDelay; // in s
int m_FalloffDistance;
int m_PosEnv;
int m_PosEnvOffset;
int m_SoundEnv;
int m_SoundEnvOffset;
};
struct CMapItemLayerSounds
{
enum { CURRENT_VERSION=1 };
CMapItemLayer m_Layer;
int m_Version;
int m_NumSources;
int m_Data;
int m_Sound;
int m_aName[3];
};
struct CMapItemSound
{
int m_Version;
int m_External;
int m_SoundName;
int m_SoundData;
int m_SoundDataSize;
} ;
// DDRace