mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
Merge pull request #67 from cinaera/pr_sound
Add support for ingame map sounds
This commit is contained in:
commit
378c34f3ff
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
219
src/game/client/components/mapsounds.cpp
Normal file
219
src/game/client/components/mapsounds.cpp
Normal 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();
|
||||
}
|
34
src/game/client/components/mapsounds.h
Normal file
34
src/game/client/components/mapsounds.h
Normal 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);
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -274,6 +274,8 @@ public:
|
|||
class CMapLayers *m_pMapLayersBackGround;
|
||||
class CMapLayers *m_pMapLayersForeGround;
|
||||
|
||||
class CMapSounds *m_pMapSounds;
|
||||
|
||||
// DDRace
|
||||
|
||||
class CRaceDemo *m_pRaceDemo;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
188
src/game/editor/layer_sounds.cpp
Normal file
188
src/game/editor/layer_sounds.cpp
Normal 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);
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#ifndef GAME_MAPITEMS_H
|
||||
#define GAME_MAPITEMS_H
|
||||
|
||||
#include<engine/shared/protocol.h>
|
||||
#include <engine/shared/protocol.h>
|
||||
|
||||
// layer types
|
||||
enum
|
||||
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue