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 | |
15 | CMapSounds::CMapSounds() |
16 | { |
17 | m_Count = 0; |
18 | } |
19 | |
20 | void 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 | |
116 | void 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 | |
241 | void 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 | |
253 | void CMapSounds::OnStateChange(int NewState, int OldState) |
254 | { |
255 | if(NewState < IClient::STATE_ONLINE) |
256 | Clear(); |
257 | } |
258 | |