1#include "mapsounds.h"
2
3#include <base/log.h>
4
5#include <engine/demo.h>
6#include <engine/sound.h>
7
8#include <game/client/components/camera.h>
9#include <game/client/components/sounds.h>
10#include <game/client/gameclient.h>
11#include <game/layers.h>
12#include <game/localization.h>
13#include <game/mapitems.h>
14
15CMapSounds::CMapSounds()
16{
17 m_Count = 0;
18}
19
20void CMapSounds::OnMapLoad()
21{
22 IMap *pMap = Kernel()->RequestInterface<IMap>();
23
24 Clear();
25
26 if(!Sound()->IsSoundEnabled())
27 return;
28
29 // load samples
30 int Start;
31 pMap->GetType(Type: MAPITEMTYPE_SOUND, pStart: &Start, pNum: &m_Count);
32
33 m_Count = clamp<int>(val: m_Count, lo: 0, hi: MAX_MAPSOUNDS);
34
35 // load new samples
36 bool ShowWarning = false;
37 for(int i = 0; i < m_Count; i++)
38 {
39 CMapItemSound *pSound = (CMapItemSound *)pMap->GetItem(Index: Start + i);
40 if(pSound->m_External)
41 {
42 const char *pName = pMap->GetDataString(Index: pSound->m_SoundName);
43 if(pName == nullptr || pName[0] == '\0')
44 {
45 log_error("mapsounds", "Failed to load map sound %d: failed to load name.", i);
46 ShowWarning = true;
47 continue;
48 }
49
50 char aBuf[IO_MAX_PATH_LENGTH];
51 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "mapres/%s.opus", pName);
52 m_aSounds[i] = Sound()->LoadOpus(pFilename: aBuf);
53 pMap->UnloadData(Index: pSound->m_SoundName);
54 }
55 else
56 {
57 const int SoundDataSize = pMap->GetDataSize(Index: pSound->m_SoundData);
58 const void *pData = pMap->GetData(Index: pSound->m_SoundData);
59 m_aSounds[i] = Sound()->LoadOpusFromMem(pData, DataSize: SoundDataSize);
60 pMap->UnloadData(Index: pSound->m_SoundData);
61 }
62 ShowWarning = ShowWarning || m_aSounds[i] == -1;
63 }
64 if(ShowWarning)
65 {
66 Client()->AddWarning(Warning: SWarning(Localize(pStr: "Some map sounds could not be loaded. Check the local console for details.")));
67 }
68
69 // enqueue sound sources
70 for(int g = 0; g < Layers()->NumGroups(); g++)
71 {
72 CMapItemGroup *pGroup = Layers()->GetGroup(Index: g);
73
74 if(!pGroup)
75 continue;
76
77 for(int l = 0; l < pGroup->m_NumLayers; l++)
78 {
79 CMapItemLayer *pLayer = Layers()->GetLayer(Index: pGroup->m_StartLayer + l);
80
81 if(!pLayer)
82 continue;
83
84 if(pLayer->m_Type == LAYERTYPE_SOUNDS)
85 {
86 CMapItemLayerSounds *pSoundLayer = (CMapItemLayerSounds *)pLayer;
87
88 if(pSoundLayer->m_Version < 1 || pSoundLayer->m_Version > CMapItemLayerSounds::CURRENT_VERSION)
89 continue;
90
91 if(pSoundLayer->m_Sound == -1)
92 continue;
93
94 CSoundSource *pSources = (CSoundSource *)Layers()->Map()->GetDataSwapped(Index: pSoundLayer->m_Data);
95
96 if(!pSources)
97 continue;
98
99 for(int i = 0; i < pSoundLayer->m_NumSources; i++)
100 {
101 CSourceQueueEntry Source;
102 Source.m_Sound = pSoundLayer->m_Sound;
103 Source.m_pSource = &pSources[i];
104 Source.m_HighDetail = pLayer->m_Flags & LAYERFLAG_DETAIL;
105
106 if(!Source.m_pSource || Source.m_Sound < 0 || Source.m_Sound >= m_Count)
107 continue;
108
109 m_vSourceQueue.push_back(x: Source);
110 }
111 }
112 }
113 }
114}
115
116void CMapSounds::OnRender()
117{
118 if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
119 return;
120
121 bool DemoPlayerPaused = Client()->State() == IClient::STATE_DEMOPLAYBACK && DemoPlayer()->BaseInfo()->m_Paused;
122
123 // enqueue sounds
124 for(auto &Source : m_vSourceQueue)
125 {
126 static float s_Time = 0.0f;
127 if(m_pClient->m_Snap.m_pGameInfoObj) // && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
128 {
129 s_Time = mix(a: (Client()->PrevGameTick(Conn: g_Config.m_ClDummy) - m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)Client()->GameTickSpeed(),
130 b: (Client()->GameTick(Conn: g_Config.m_ClDummy) - m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / (float)Client()->GameTickSpeed(),
131 amount: Client()->IntraGameTick(Conn: g_Config.m_ClDummy));
132 }
133 float Offset = s_Time - Source.m_pSource->m_TimeDelay;
134 if(!DemoPlayerPaused && Offset >= 0.0f && g_Config.m_SndEnable && (g_Config.m_GfxHighDetail || !Source.m_HighDetail))
135 {
136 if(Source.m_Voice.IsValid())
137 {
138 // currently playing, set offset
139 Sound()->SetVoiceTimeOffset(Voice: Source.m_Voice, TimeOffset: Offset);
140 }
141 else
142 {
143 // need to enqueue
144 int Flags = 0;
145 if(Source.m_pSource->m_Loop)
146 Flags |= ISound::FLAG_LOOP;
147 if(!Source.m_pSource->m_Pan)
148 Flags |= ISound::FLAG_NO_PANNING;
149
150 Source.m_Voice = m_pClient->m_Sounds.PlaySampleAt(Channel: CSounds::CHN_MAPSOUND, SampleId: m_aSounds[Source.m_Sound], Vol: 1.0f, Pos: vec2(fx2f(v: Source.m_pSource->m_Position.x), fx2f(v: Source.m_pSource->m_Position.y)), Flags);
151 Sound()->SetVoiceTimeOffset(Voice: Source.m_Voice, TimeOffset: Offset);
152 Sound()->SetVoiceFalloff(Voice: Source.m_Voice, Falloff: Source.m_pSource->m_Falloff / 255.0f);
153 switch(Source.m_pSource->m_Shape.m_Type)
154 {
155 case CSoundShape::SHAPE_CIRCLE:
156 {
157 Sound()->SetVoiceCircle(Voice: Source.m_Voice, Radius: Source.m_pSource->m_Shape.m_Circle.m_Radius);
158 break;
159 }
160
161 case CSoundShape::SHAPE_RECTANGLE:
162 {
163 Sound()->SetVoiceRectangle(Voice: Source.m_Voice, Width: fx2f(v: Source.m_pSource->m_Shape.m_Rectangle.m_Width), Height: fx2f(v: Source.m_pSource->m_Shape.m_Rectangle.m_Height));
164 break;
165 }
166 };
167 }
168 }
169 else
170 {
171 // stop voice
172 Sound()->StopVoice(Voice: Source.m_Voice);
173 Source.m_Voice = ISound::CVoiceHandle();
174 }
175 }
176
177 vec2 Center = m_pClient->m_Camera.m_Center;
178 for(int g = 0; g < Layers()->NumGroups(); g++)
179 {
180 CMapItemGroup *pGroup = Layers()->GetGroup(Index: g);
181
182 if(!pGroup)
183 continue;
184
185 for(int l = 0; l < pGroup->m_NumLayers; l++)
186 {
187 CMapItemLayer *pLayer = Layers()->GetLayer(Index: pGroup->m_StartLayer + l);
188
189 if(!pLayer)
190 continue;
191
192 if(pLayer->m_Type == LAYERTYPE_SOUNDS)
193 {
194 CMapItemLayerSounds *pSoundLayer = (CMapItemLayerSounds *)pLayer;
195
196 if(pSoundLayer->m_Version < 1 || pSoundLayer->m_Version > CMapItemLayerSounds::CURRENT_VERSION)
197 continue;
198
199 CSoundSource *pSources = (CSoundSource *)Layers()->Map()->GetDataSwapped(Index: pSoundLayer->m_Data);
200
201 if(!pSources)
202 continue;
203
204 for(int s = 0; s < pSoundLayer->m_NumSources; s++)
205 {
206 for(auto &Voice : m_vSourceQueue)
207 {
208 if(Voice.m_pSource != &pSources[s])
209 continue;
210
211 if(!Voice.m_Voice.IsValid())
212 continue;
213
214 ColorRGBA Position = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
215 CMapLayers::EnvelopeEval(TimeOffsetMillis: Voice.m_pSource->m_PosEnvOffset, Env: Voice.m_pSource->m_PosEnv, Result&: Position, Channels: 2, pUser: &m_pClient->m_MapLayersBackground);
216
217 float x = fx2f(v: Voice.m_pSource->m_Position.x) + Position.r;
218 float y = fx2f(v: Voice.m_pSource->m_Position.y) + Position.g;
219
220 x += Center.x * (1.0f - pGroup->m_ParallaxX / 100.0f);
221 y += Center.y * (1.0f - pGroup->m_ParallaxY / 100.0f);
222
223 x -= pGroup->m_OffsetX;
224 y -= pGroup->m_OffsetY;
225
226 Sound()->SetVoiceLocation(Voice: Voice.m_Voice, x, y);
227
228 ColorRGBA Volume = ColorRGBA(1.0f, 0.0f, 0.0f, 0.0f);
229 CMapLayers::EnvelopeEval(TimeOffsetMillis: Voice.m_pSource->m_SoundEnvOffset, Env: Voice.m_pSource->m_SoundEnv, Result&: Volume, Channels: 1, pUser: &m_pClient->m_MapLayersBackground);
230 if(Volume.r < 1.0f)
231 {
232 Sound()->SetVoiceVolume(Voice: Voice.m_Voice, Volume: Volume.r);
233 }
234 }
235 }
236 }
237 }
238 }
239}
240
241void CMapSounds::Clear()
242{
243 // unload all samples
244 m_vSourceQueue.clear();
245 for(int i = 0; i < m_Count; i++)
246 {
247 Sound()->UnloadSample(SampleId: m_aSounds[i]);
248 m_aSounds[i] = -1;
249 }
250 m_Count = 0;
251}
252
253void CMapSounds::OnStateChange(int NewState, int OldState)
254{
255 if(NewState < IClient::STATE_ONLINE)
256 Clear();
257}
258