1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name sound.cpp - The sound. */
12 //
13 //      (c) Copyright 1998-2015 by Lutz Sammer, Fabrice Rossi,
14 //		Jimmy Salmon and Andrettin
15 //
16 //      This program is free software; you can redistribute it and/or modify
17 //      it under the terms of the GNU General Public License as published by
18 //      the Free Software Foundation; only version 2 of the License.
19 //
20 //      This program is distributed in the hope that it will be useful,
21 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
22 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 //      GNU General Public License for more details.
24 //
25 //      You should have received a copy of the GNU General Public License
26 //      along with this program; if not, write to the Free Software
27 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
28 //      02111-1307, USA.
29 //
30 
31 //@{
32 
33 /*----------------------------------------------------------------------------
34 --  Includes
35 ----------------------------------------------------------------------------*/
36 
37 #include "stratagus.h"
38 
39 #include "sound.h"
40 
41 #include "action/action_resource.h"
42 #include "civilization.h"
43 #include "config.h"
44 #include "map/map.h"
45 #include "map/map_layer.h"
46 #include "map/tileset.h"
47 #include "mod.h"
48 #include "missile.h"
49 #include "sound_server.h"
50 #include "ui/ui.h"
51 #include "unit/unit.h"
52 #include "video.h"
53 #include "widgets.h"
54 
55 /*----------------------------------------------------------------------------
56 --  Variables
57 ----------------------------------------------------------------------------*/
58 
59 /**
60 **  Various sounds used in game.
61 */
62 GameSound GameSounds;
63 
64 /**
65 **  Selection handling
66 */
67 struct SelectionHandling {
68 	Origin Source;         /// origin of the sound
69 	CSound *Sound;         /// last sound played by this unit
70 	unsigned char HowMany; /// number of sound played in this group
71 };
72 
73 /// FIXME: docu
74 SelectionHandling SelectionHandler;
75 
76 static int ViewPointOffset;      /// Distance to Volume Mapping
77 int DistanceSilent;              /// silent distance
78 
79 /*----------------------------------------------------------------------------
80 --  Functions
81 ----------------------------------------------------------------------------*/
82 
83 /**
84 **  "Randomly" choose a sample from a sound group.
85 */
SimpleChooseSample(const CSound & sound)86 static CSample *SimpleChooseSample(const CSound &sound)
87 {
88 	if (sound.Number == ONE_SOUND) {
89 		return sound.Sound.OneSound;
90 	} else {
91 		//FIXME: check for errors
92 		//FIXME: valid only in shared memory context (FrameCounter)
93 		return sound.Sound.OneGroup[FrameCounter % sound.Number];
94 	}
95 }
96 
97 /**
98 **  Choose the sample to play
99 */
ChooseSample(CSound * sound,bool selection,Origin & source)100 static CSample *ChooseSample(CSound *sound, bool selection, Origin &source)
101 {
102 	CSample *result = nullptr;
103 
104 	if (!sound || !SoundEnabled()) {
105 		return nullptr;
106 	}
107 
108 	if (sound->Number == TWO_GROUPS) {
109 		// handle a special sound (selection)
110 		if (SelectionHandler.Sound != nullptr && (SelectionHandler.Source.Base == source.Base && SelectionHandler.Source.Id == source.Id)) {
111 			if (SelectionHandler.Sound == sound->Sound.TwoGroups.First) {
112 				result = SimpleChooseSample(*SelectionHandler.Sound);
113 				SelectionHandler.HowMany++;
114 				if (SelectionHandler.HowMany >= 3) {
115 					SelectionHandler.HowMany = 0;
116 					SelectionHandler.Sound = sound->Sound.TwoGroups.Second;
117 				}
118 			} else {
119 				//FIXME: checks for error
120 				// check whether the second group is really a group
121 				if (SelectionHandler.Sound->Number > 1) {
122 					//Wyrmgus start
123 //					result = SelectionHandler.Sound->Sound.OneGroup[SelectionHandler.HowMany];
124 					result = SimpleChooseSample(*SelectionHandler.Sound);
125 					//Wyrmgus end
126 					SelectionHandler.HowMany++;
127 					if (SelectionHandler.HowMany >= SelectionHandler.Sound->Number) {
128 						SelectionHandler.HowMany = 0;
129 						SelectionHandler.Sound = sound->Sound.TwoGroups.First;
130 					}
131 				} else {
132 					result = SelectionHandler.Sound->Sound.OneSound;
133 					SelectionHandler.HowMany = 0;
134 					SelectionHandler.Sound = sound->Sound.TwoGroups.First;
135 				}
136 			}
137 		} else {
138 			SelectionHandler.Source = source;
139 			SelectionHandler.Sound = sound->Sound.TwoGroups.First;
140 			result = SimpleChooseSample(*SelectionHandler.Sound);
141 			SelectionHandler.HowMany = 1;
142 		}
143 	} else {
144 		// normal sound/sound group handling
145 		result = SimpleChooseSample(*sound);
146 		if (SelectionHandler.Source.Base == source.Base && SelectionHandler.Source.Id == source.Id) {
147 			SelectionHandler.HowMany = 0;
148 			SelectionHandler.Sound = nullptr;
149 		}
150 		if (selection) {
151 			SelectionHandler.Source = source;
152 		}
153 	}
154 
155 	return result;
156 }
157 
158 /**
159 **  Maps a UnitVoiceGroup to a CSound*.
160 **
161 **  @param unit    Sound initiator
162 **  @param voice   Type of sound wanted
163 **
164 **  @return        Sound identifier
165 */
ChooseUnitVoiceSound(const CUnit & unit,UnitVoiceGroup voice)166 static CSound *ChooseUnitVoiceSound(const CUnit &unit, UnitVoiceGroup voice)
167 {
168 	//Wyrmgus start
169 	const CMapField &mf = *unit.MapLayer->Field(unit.tilePos);
170 	//Wyrmgus end
171 	switch (voice) {
172 		case VoiceAcknowledging:
173 			//Wyrmgus start
174 //			return unit.Type->MapSound.Acknowledgement.Sound;
175 			if (unit.Type->MapSound.Acknowledgement.Sound) {
176 				return unit.Type->MapSound.Acknowledgement.Sound;
177 			} else if (unit.Type->BoolFlag[ORGANIC_INDEX].value && unit.Type->Civilization != -1) {
178 				int civilization = unit.Type->Civilization;
179 				if (unit.Player->Race != -1 && unit.Player->Race != civilization && unit.Player->Faction != -1 && unit.Type->Slot == PlayerRaces.GetFactionClassUnitType(unit.Player->Faction, unit.Type->Class)) {
180 					civilization = unit.Player->Race;
181 				}
182 				return CCivilization::Civilizations[civilization]->UnitSounds.Acknowledgement.Sound;
183 			} else {
184 				return nullptr;
185 			}
186 			//Wyrmgus end
187 		case VoiceAttack:
188 			if (unit.Type->MapSound.Attack.Sound) {
189 				return unit.Type->MapSound.Attack.Sound;
190 			//Wyrmgus start
191 			} else if (unit.Type->BoolFlag[ORGANIC_INDEX].value && unit.Type->Civilization != -1) {
192 				int civilization = unit.Type->Civilization;
193 				if (unit.Player->Race != -1 && unit.Player->Race != civilization && unit.Player->Faction != -1 && unit.Type->Slot == PlayerRaces.GetFactionClassUnitType(unit.Player->Faction, unit.Type->Class)) {
194 					civilization = unit.Player->Race;
195 				}
196 				if (CCivilization::Civilizations[civilization]->UnitSounds.Attack.Sound) {
197 					return CCivilization::Civilizations[civilization]->UnitSounds.Attack.Sound;
198 				} else {
199 					return ChooseUnitVoiceSound(unit, VoiceAcknowledging);
200 				}
201 			//Wyrmgus end
202 			} else {
203 				//Wyrmgus start
204 //				return unit.Type->MapSound.Acknowledgement.Sound;
205 				return ChooseUnitVoiceSound(unit, VoiceAcknowledging);
206 				//Wyrmgus end
207 			}
208 		//Wyrmgus start
209 		case VoiceIdle:
210 			return unit.Type->MapSound.Idle.Sound;
211 		case VoiceHit:
212 			return unit.Type->MapSound.Hit.Sound;
213 		case VoiceMiss:
214 			if (unit.Type->MapSound.Miss.Sound) {
215 				return unit.Type->MapSound.Miss.Sound;
216 			} else {
217 				return unit.Type->MapSound.Hit.Sound;
218 			}
219 		case VoiceFireMissile:
220 			return unit.Type->MapSound.FireMissile.Sound;
221 		case VoiceStep:
222 			if (unit.Type->MapSound.StepMud.Sound && ((mf.getFlag() & MapFieldMud) || (mf.getFlag() & MapFieldSnow))) {
223 				return unit.Type->MapSound.StepMud.Sound;
224 			} else if (unit.Type->MapSound.StepDirt.Sound && ((mf.getFlag() & MapFieldDirt) || (mf.getFlag() & MapFieldIce))) {
225 				return unit.Type->MapSound.StepDirt.Sound;
226 			} else if (unit.Type->MapSound.StepGravel.Sound && mf.getFlag() & MapFieldGravel) {
227 				return unit.Type->MapSound.StepGravel.Sound;
228 			} else if (unit.Type->MapSound.StepGrass.Sound && ((mf.getFlag() & MapFieldGrass) || (mf.getFlag() & MapFieldStumps))) {
229 				return unit.Type->MapSound.StepGrass.Sound;
230 			} else if (unit.Type->MapSound.StepStone.Sound && mf.getFlag() & MapFieldStoneFloor) {
231 				return unit.Type->MapSound.StepStone.Sound;
232 			} else {
233 				return unit.Type->MapSound.Step.Sound;
234 			}
235 		case VoiceUsed:
236 			return unit.Type->MapSound.Used.Sound;
237 		//Wyrmgus end
238 		case VoiceBuild:
239 			//Wyrmgus start
240 //			return unit.Type->MapSound.Build.Sound;
241 			if (unit.Type->MapSound.Build.Sound) {
242 				return unit.Type->MapSound.Build.Sound;
243 			//Wyrmgus start
244 			} else if (unit.Type->BoolFlag[ORGANIC_INDEX].value && unit.Type->Civilization != -1) {
245 				int civilization = unit.Type->Civilization;
246 				if (unit.Player->Race != -1 && unit.Player->Race != civilization && unit.Player->Faction != -1 && unit.Type->Slot == PlayerRaces.GetFactionClassUnitType(unit.Player->Faction, unit.Type->Class)) {
247 					civilization = unit.Player->Race;
248 				}
249 				if (CCivilization::Civilizations[civilization]->UnitSounds.Build.Sound) {
250 					return CCivilization::Civilizations[civilization]->UnitSounds.Build.Sound;
251 				} else {
252 					return ChooseUnitVoiceSound(unit, VoiceAcknowledging);
253 				}
254 			//Wyrmgus end
255 			} else {
256 				return ChooseUnitVoiceSound(unit, VoiceAcknowledging);
257 			}
258 			//Wyrmgus end
259 		case VoiceReady:
260 			//Wyrmgus start
261 //			return unit.Type->MapSound.Ready.Sound;
262 			if (unit.Type->MapSound.Ready.Sound) {
263 				return unit.Type->MapSound.Ready.Sound;
264 			} else if (unit.Type->BoolFlag[ORGANIC_INDEX].value && unit.Type->Civilization != -1) {
265 				int civilization = unit.Type->Civilization;
266 				if (unit.Player->Race != -1 && unit.Player->Race != civilization && unit.Player->Faction != -1 && unit.Type->Slot == PlayerRaces.GetFactionClassUnitType(unit.Player->Faction, unit.Type->Class)) {
267 					civilization = unit.Player->Race;
268 				}
269 				return CCivilization::Civilizations[civilization]->UnitSounds.Ready.Sound;
270 			} else {
271 				return nullptr;
272 			}
273 			//Wyrmgus end
274 		case VoiceSelected:
275 			//Wyrmgus start
276 //			return unit.Type->MapSound.Selected.Sound;
277 			if (unit.Type->MapSound.Selected.Sound) {
278 				return unit.Type->MapSound.Selected.Sound;
279 			} else if (unit.Type->BoolFlag[ORGANIC_INDEX].value && unit.Type->Civilization != -1) {
280 				int civilization = unit.Type->Civilization;
281 				if (unit.Player->Race != -1 && unit.Player->Race != civilization && unit.Player->Faction != -1 && unit.Type->Slot == PlayerRaces.GetFactionClassUnitType(unit.Player->Faction, unit.Type->Class)) {
282 					civilization = unit.Player->Race;
283 				}
284 				return CCivilization::Civilizations[civilization]->UnitSounds.Selected.Sound;
285 			} else {
286 				return nullptr;
287 			}
288 			//Wyrmgus end
289 		case VoiceHelpMe:
290 			//Wyrmgus start
291 //			return unit.Type->MapSound.Help.Sound;
292 			if (unit.Type->MapSound.Help.Sound) {
293 				return unit.Type->MapSound.Help.Sound;
294 			} else if (unit.Type->Civilization != -1) {
295 				int civilization = unit.Type->Civilization;
296 				if (unit.Player->Race != -1 && unit.Player->Race != civilization && unit.Player->Faction != -1 && unit.Type->Slot == PlayerRaces.GetFactionClassUnitType(unit.Player->Faction, unit.Type->Class)) {
297 					civilization = unit.Player->Race;
298 				}
299 				if (unit.Type->BoolFlag[BUILDING_INDEX].value && CCivilization::Civilizations[civilization]->UnitSounds.HelpTown.Sound) {
300 					return CCivilization::Civilizations[civilization]->UnitSounds.HelpTown.Sound;
301 				} else {
302 					return CCivilization::Civilizations[civilization]->UnitSounds.Help.Sound;
303 				}
304 			} else {
305 				return nullptr;
306 			}
307 			//Wyrmgus end
308 		case VoiceDying:
309 			if (unit.Type->MapSound.Dead[unit.DamagedType].Sound) {
310 				return unit.Type->MapSound.Dead[unit.DamagedType].Sound;
311 			} else {
312 				return unit.Type->MapSound.Dead[ANIMATIONS_DEATHTYPES].Sound;
313 			}
314 		case VoiceWorkCompleted:
315 			return GameSounds.WorkComplete[ThisPlayer->Race].Sound;
316 		case VoiceBuilding:
317 			return GameSounds.BuildingConstruction[ThisPlayer->Race].Sound;
318 		case VoiceDocking:
319 			return GameSounds.Docking.Sound;
320 		case VoiceRepairing:
321 			if (unit.Type->MapSound.Repair.Sound) {
322 				return unit.Type->MapSound.Repair.Sound;
323 			//Wyrmgus start
324 			} else if (unit.Type->BoolFlag[ORGANIC_INDEX].value && unit.Type->Civilization != -1) {
325 				int civilization = unit.Type->Civilization;
326 				if (unit.Player->Race != -1 && unit.Player->Race != civilization && unit.Player->Faction != -1 && unit.Type->Slot == PlayerRaces.GetFactionClassUnitType(unit.Player->Faction, unit.Type->Class)) {
327 					civilization = unit.Player->Race;
328 				}
329 				if (CCivilization::Civilizations[civilization]->UnitSounds.Repair.Sound) {
330 					return CCivilization::Civilizations[civilization]->UnitSounds.Repair.Sound;
331 				} else {
332 					return ChooseUnitVoiceSound(unit, VoiceAcknowledging);
333 				}
334 			//Wyrmgus end
335 			} else {
336 				//Wyrmgus start
337 //				return unit.Type->MapSound.Acknowledgement.Sound;
338 				return ChooseUnitVoiceSound(unit, VoiceAcknowledging);
339 				//Wyrmgus end
340 			}
341 		case VoiceHarvesting:
342 			for (size_t i = 0; i != unit.Orders.size(); ++i) {
343 				if (unit.Orders[i]->Action == UnitActionResource) {
344 					COrder_Resource &order = dynamic_cast<COrder_Resource &>(*unit.Orders[i]);
345 					if (unit.Type->MapSound.Harvest[order.GetCurrentResource()].Sound) {
346 						return unit.Type->MapSound.Harvest[order.GetCurrentResource()].Sound;
347 					//Wyrmgus start
348 					} else if (unit.Type->BoolFlag[ORGANIC_INDEX].value && unit.Type->Civilization != -1) {
349 						int civilization = unit.Type->Civilization;
350 						if (unit.Player->Race != -1 && unit.Player->Race != civilization && unit.Player->Faction != -1 && unit.Type->Slot == PlayerRaces.GetFactionClassUnitType(unit.Player->Faction, unit.Type->Class)) {
351 							civilization = unit.Player->Race;
352 						}
353 						if (CCivilization::Civilizations[civilization]->UnitSounds.Harvest[order.GetCurrentResource()].Sound) {
354 							return CCivilization::Civilizations[civilization]->UnitSounds.Harvest[order.GetCurrentResource()].Sound;
355 						} else {
356 							return ChooseUnitVoiceSound(unit, VoiceAcknowledging);
357 						}
358 					//Wyrmgus end
359 					} else {
360 						//Wyrmgus start
361 //						return unit.Type->MapSound.Acknowledgement.Sound;
362 						return ChooseUnitVoiceSound(unit, VoiceAcknowledging);
363 						//Wyrmgus end
364 					}
365 				}
366 			}
367 	}
368 
369 	return NO_SOUND;
370 }
371 
372 /**
373 **  Compute a suitable volume for something taking place at a given
374 **  distance from the current view point.
375 **
376 **  @param d      distance
377 **  @param range  range
378 **
379 **  @return       volume for given distance (0..??)
380 */
VolumeForDistance(unsigned short d,unsigned char range)381 unsigned char VolumeForDistance(unsigned short d, unsigned char range)
382 {
383 	// FIXME: THIS IS SLOW!!!!!!!
384 	if (d <= ViewPointOffset || range == INFINITE_SOUND_RANGE) {
385 		return MaxVolume;
386 	} else {
387 		if (range) {
388 			d -= ViewPointOffset;
389 			int d_tmp = d * MAX_SOUND_RANGE;
390 			int range_tmp = DistanceSilent * range;
391 			if (d_tmp > range_tmp) {
392 				return 0;
393 			} else {
394 				return (unsigned char)((range_tmp - d_tmp) * MAX_SOUND_RANGE / range_tmp);
395 			}
396 		} else {
397 			return 0;
398 		}
399 	}
400 }
401 
402 /**
403 **  Calculate the volume associated with a request, either by clipping the
404 **  range parameter of this request, or by mapping this range to a volume.
405 */
CalculateVolume(bool isVolume,int power,unsigned char range)406 unsigned char CalculateVolume(bool isVolume, int power, unsigned char range)
407 {
408 	if (isVolume) {
409 		return std::min(MaxVolume, power);
410 	} else {
411 		// map distance to volume
412 		return VolumeForDistance(power, range);
413 	}
414 }
415 
416 /**
417 **  Calculate the stereo value for a unit
418 */
CalculateStereo(const CUnit & unit)419 static char CalculateStereo(const CUnit &unit)
420 {
421 	int stereo = ((unit.tilePos.x * Map.GetCurrentPixelTileSize().x + unit.Type->TileSize.x * Map.GetCurrentPixelTileSize().x / 2 +
422 				   unit.IX - UI.SelectedViewport->MapPos.x * Map.GetCurrentPixelTileSize().x) * 256 /
423 				  ((UI.SelectedViewport->MapWidth - 1) * Map.GetCurrentPixelTileSize().x)) - 128;
424 	clamp(&stereo, -128, 127);
425 	return stereo;
426 }
427 
428 /**
429 **  Ask to the sound server to play a sound attached to a unit. The
430 **  sound server may discard the sound if needed (e.g., when the same
431 **  unit is already speaking).
432 **
433 **  @param unit   Sound initiator, unit speaking
434 **  @param voice  Type of sound wanted (Ready,Die,Yes,...)
435 */
PlayUnitSound(const CUnit & unit,UnitVoiceGroup voice)436 void PlayUnitSound(const CUnit &unit, UnitVoiceGroup voice)
437 {
438 	if (!UI.CurrentMapLayer || unit.MapLayer != UI.CurrentMapLayer) {
439 		return;
440 	}
441 
442 	if (unit.Variable[STUN_INDEX].Value > 0 && voice != VoiceHit && voice != VoiceMiss && voice != VoiceFireMissile && voice != VoiceStep && voice != VoiceDying) { //don't speak if stunned
443 		return;
444 	}
445 
446 	CSound *sound = ChooseUnitVoiceSound(unit, voice);
447 	if (!sound) {
448 		return;
449 	}
450 
451 	bool selection = (voice == VoiceSelected || voice == VoiceBuilding);
452 	Origin source = {&unit, unsigned(UnitNumber(unit))};
453 
454 	//Wyrmgus start
455 //	if (UnitSoundIsPlaying(&source)) {
456 	if (voice != VoiceHit && voice != VoiceMiss && voice != VoiceFireMissile && voice != VoiceStep && UnitSoundIsPlaying(&source)) {
457 	//Wyrmgus end
458 		return;
459 	}
460 
461 	int channel = PlaySample(ChooseSample(sound, selection, source), &source);
462 	if (channel == -1) {
463 		return;
464 	}
465 	//Wyrmgus start
466 //	SetChannelVolume(channel, CalculateVolume(false, ViewPointDistanceToUnit(unit), sound->Range));
467 	SetChannelVolume(channel, CalculateVolume(false, ViewPointDistanceToUnit(unit), sound->Range) * sound->VolumePercent / 100);
468 	//Wyrmgus end
469 	SetChannelStereo(channel, CalculateStereo(unit));
470 	//Wyrmgus start
471 	SetChannelVoiceGroup(channel, voice);
472 	//Wyrmgus end
473 }
474 
475 /**
476 **  Ask to the sound server to play a sound attached to a unit. The
477 **  sound server may discard the sound if needed (e.g., when the same
478 **  unit is already speaking).
479 **
480 **  @param unit   Sound initiator, unit speaking
481 **  @param sound  Sound to be generated
482 */
PlayUnitSound(const CUnit & unit,CSound * sound)483 void PlayUnitSound(const CUnit &unit, CSound *sound)
484 {
485 	//Wyrmgus start
486 	if (!&unit) {
487 		fprintf(stderr, "Error in PlayUnitSound: unit is null.\n");
488 		return;
489 	}
490 
491 	if (!sound) {
492 		return;
493 	}
494 	//Wyrmgus start
495 	if (unit.MapLayer != UI.CurrentMapLayer) {
496 		return;
497 	}
498 	//Wyrmgus end
499 	Origin source = {&unit, unsigned(UnitNumber(unit))};
500 	//Wyrmgus start
501 //	unsigned char volume = CalculateVolume(false, ViewPointDistanceToUnit(unit), sound->Range);
502 	unsigned char volume = CalculateVolume(false, ViewPointDistanceToUnit(unit), sound->Range) * sound->VolumePercent / 100;
503 	//Wyrmgus end
504 	if (volume == 0) {
505 		return;
506 	}
507 
508 	int channel = PlaySample(ChooseSample(sound, false, source));
509 	if (channel == -1) {
510 		return;
511 	}
512 	SetChannelVolume(channel, volume);
513 	SetChannelStereo(channel, CalculateStereo(unit));
514 }
515 
516 /**
517 **  Ask the sound server to play a sound for a missile.
518 **
519 **  @param missile  Sound initiator, missile exploding
520 **  @param sound    Sound to be generated
521 */
PlayMissileSound(const Missile & missile,CSound * sound)522 void PlayMissileSound(const Missile &missile, CSound *sound)
523 {
524 	if (!sound) {
525 		return;
526 	}
527 	int stereo = ((missile.position.x + (missile.Type->G ? missile.Type->G->Width / 2 : 0) +
528 				   UI.SelectedViewport->MapPos.x * Map.GetCurrentPixelTileSize().x) * 256 /
529 				  ((UI.SelectedViewport->MapWidth - 1) * Map.GetCurrentPixelTileSize().x)) - 128;
530 	clamp(&stereo, -128, 127);
531 
532 	Origin source = {nullptr, 0};
533 	//Wyrmgus start
534 //	unsigned char volume = CalculateVolume(false, ViewPointDistanceToMissile(missile), sound->Range);
535 	unsigned char volume = CalculateVolume(false, ViewPointDistanceToMissile(missile), sound->Range) * sound->VolumePercent / 100;
536 	//Wyrmgus end
537 	if (volume == 0) {
538 		return;
539 	}
540 
541 	int channel = PlaySample(ChooseSample(sound, false, source));
542 	if (channel == -1) {
543 		return;
544 	}
545 	SetChannelVolume(channel, volume);
546 	SetChannelStereo(channel, stereo);
547 }
548 
549 /**
550 **  Play a game sound
551 **
552 **  @param sound   Sound to play
553 **  @param volume  Volume level to play the sound
554 */
PlayGameSound(CSound * sound,unsigned char volume,bool always)555 void PlayGameSound(CSound *sound, unsigned char volume, bool always)
556 {
557 	if (!sound) {
558 		return;
559 	}
560 	Origin source = {nullptr, 0};
561 
562 	CSample *sample = ChooseSample(sound, false, source);
563 
564 	if (!always && SampleIsPlaying(sample)) {
565 		return;
566 	}
567 
568 	int channel = PlaySample(sample);
569 	if (channel == -1) {
570 		return;
571 	}
572 	//Wyrmgus start
573 //	SetChannelVolume(channel, CalculateVolume(true, volume, sound->Range));
574 	SetChannelVolume(channel, CalculateVolume(true, volume, sound->Range) * sound->VolumePercent / 100);
575 	//Wyrmgus end
576 }
577 
578 static std::map<int, LuaActionListener *> ChannelMap;
579 
580 /**
581 **  Callback for PlaySoundFile
582 */
PlaySoundFileCallback(int channel)583 static void PlaySoundFileCallback(int channel)
584 {
585 	LuaActionListener *listener = ChannelMap[channel];
586 	if (listener != nullptr) {
587 		listener->action("");
588 		ChannelMap[channel] = nullptr;
589 	}
590 	delete GetChannelSample(channel);
591 }
592 
593 /**
594 **  Play a sound file
595 **
596 **  @param name      Filename of a sound to play
597 **  @param listener  Optional lua callback
598 **
599 **  @return          Channel number the sound is playing on, -1 for error
600 */
PlayFile(const std::string & name,LuaActionListener * listener)601 int PlayFile(const std::string &name, LuaActionListener *listener)
602 {
603 	int channel = -1;
604 	CSample *sample = LoadSample(name);
605 
606 	if (sample) {
607 		channel = PlaySample(sample);
608 		if (channel != -1) {
609 			SetChannelVolume(channel, MaxVolume);
610 			SetChannelFinishedCallback(channel, PlaySoundFileCallback);
611 			ChannelMap[channel] = listener;
612 		}
613 	}
614 	return channel;
615 }
616 
617 /**
618 **  Ask the sound server to change the range of a sound.
619 **
620 **  @param sound  the id of the sound to modify.
621 **  @param range  the new range for this sound.
622 */
SetSoundRange(CSound * sound,unsigned char range)623 void SetSoundRange(CSound *sound, unsigned char range)
624 {
625 	if (sound != NO_SOUND) {
626 		sound->Range = range;
627 	}
628 }
629 
630 //Wyrmgus start
631 /**
632 **  Ask the sound server to change the volume percent of a sound.
633 **
634 **  @param sound  the id of the sound to modify.
635 **  @param volume_percent  the new volume percent for this sound.
636 */
SetSoundVolumePercent(CSound * sound,int volume_percent)637 void SetSoundVolumePercent(CSound *sound, int volume_percent)
638 {
639 	if (sound != NO_SOUND) {
640 		sound->VolumePercent = volume_percent;
641 	}
642 }
643 //Wyrmgus end
644 
645 /**
646 **  Ask the sound server to register a sound (and currently to load it)
647 **  and to return an unique identifier for it. The unique identifier is
648 **  memory pointer of the server.
649 **
650 **  @param files   An array of wav files.
651 **  @param number  Number of files belonging together.
652 **
653 **  @return        the sound unique identifier
654 **
655 **  @todo FIXME: Must handle the errors better.
656 */
RegisterSound(const std::vector<std::string> & files)657 CSound *RegisterSound(const std::vector<std::string> &files)
658 {
659 	CSound *id = new CSound;
660 	size_t number = files.size();
661 
662 	if (number > 1) { // load a sound group
663 		id->Sound.OneGroup = new CSample *[number];
664 		memset(id->Sound.OneGroup, 0, sizeof(CSample *) * number);
665 		id->Number = number;
666 		for (unsigned int i = 0; i < number; ++i) {
667 			id->Sound.OneGroup[i] = LoadSample(files[i]);
668 			if (!id->Sound.OneGroup[i]) {
669 				//delete[] id->Sound.OneGroup;
670 				delete id;
671 				return NO_SOUND;
672 			}
673 		}
674 	} else { // load a unique sound
675 		id->Sound.OneSound = LoadSample(files[0]);
676 		if (!id->Sound.OneSound) {
677 			delete id;
678 			return NO_SOUND;
679 		}
680 		id->Number = ONE_SOUND;
681 	}
682 	id->Range = MAX_SOUND_RANGE;
683 	//Wyrmgus start
684 	id->VolumePercent = 100;
685 	//Wyrmgus end
686 	return id;
687 }
688 
689 /**
690 **  Ask the sound server to put together two sounds to form a special sound.
691 **
692 **  @param first   first part of the group
693 **  @param second  second part of the group
694 **
695 **  @return        the special sound unique identifier
696 */
RegisterTwoGroups(CSound * first,CSound * second)697 CSound *RegisterTwoGroups(CSound *first, CSound *second)
698 {
699 	if (first == NO_SOUND || second == NO_SOUND) {
700 		return NO_SOUND;
701 	}
702 	CSound *id = new CSound;
703 	id->Number = TWO_GROUPS;
704 	id->Sound.TwoGroups.First = first;
705 	id->Sound.TwoGroups.Second = second;
706 	id->Range = MAX_SOUND_RANGE;
707 	//Wyrmgus start
708 	id->VolumePercent = first->VolumePercent + second->VolumePercent / 2;
709 	//Wyrmgus end
710 
711 	return id;
712 }
713 
714 /**
715 **  Lookup the sound id's for the game sounds.
716 */
InitSoundClient()717 void InitSoundClient()
718 {
719 	if (!SoundEnabled()) { // No sound enabled
720 		return;
721 	}
722 	// let's map game sounds, look if already setup in ccl.
723 
724 	for (size_t i = 0; i < CCivilization::Civilizations.size(); ++i) {
725 		if (!GameSounds.PlacementError[i].Sound) {
726 			GameSounds.PlacementError[i].MapSound();
727 		}
728 	}
729 
730 	for (size_t i = 0; i < CCivilization::Civilizations.size(); ++i) {
731 		if (!GameSounds.PlacementSuccess[i].Sound) {
732 			GameSounds.PlacementSuccess[i].MapSound();
733 		}
734 	}
735 
736 	if (!GameSounds.Click.Sound) {
737 		GameSounds.Click.MapSound();
738 	}
739 	if (!GameSounds.Docking.Sound) {
740 		GameSounds.Docking.MapSound();
741 	}
742 
743 	for (size_t i = 0; i < CCivilization::Civilizations.size(); ++i) {
744 		if (!GameSounds.BuildingConstruction[i].Sound) {
745 			GameSounds.BuildingConstruction[i].MapSound();
746 		}
747 	}
748 	for (size_t i = 0; i < CCivilization::Civilizations.size(); ++i) {
749 		if (!GameSounds.WorkComplete[i].Sound) {
750 			GameSounds.WorkComplete[i].MapSound();
751 		}
752 	}
753 	for (size_t i = 0; i < CCivilization::Civilizations.size(); ++i) {
754 		if (!GameSounds.ResearchComplete[i].Sound) {
755 			GameSounds.ResearchComplete[i].MapSound();
756 		}
757 	}
758 	for (size_t i = 0; i < CCivilization::Civilizations.size(); ++i) {
759 		for (unsigned int j = 0; j < MaxCosts; ++j) {
760 			if (!GameSounds.NotEnoughRes[i][j].Sound) {
761 				GameSounds.NotEnoughRes[i][j].MapSound();
762 			}
763 		}
764 	}
765 	for (size_t i = 0; i < CCivilization::Civilizations.size(); ++i) {
766 		if (!GameSounds.NotEnoughFood[i].Sound) {
767 			GameSounds.NotEnoughFood[i].MapSound();
768 		}
769 	}
770 	for (size_t i = 0; i < CCivilization::Civilizations.size(); ++i) {
771 		if (!GameSounds.Rescue[i].Sound) {
772 			GameSounds.Rescue[i].MapSound();
773 		}
774 	}
775 	if (!GameSounds.ChatMessage.Sound) {
776 		GameSounds.ChatMessage.MapSound();
777 	}
778 
779 	int MapWidth = (UI.MapArea.EndX - UI.MapArea.X + Map.GetCurrentPixelTileSize().x) / Map.GetCurrentPixelTileSize().x;
780 	int MapHeight = (UI.MapArea.EndY - UI.MapArea.Y + Map.GetCurrentPixelTileSize().y) / Map.GetCurrentPixelTileSize().y;
781 	DistanceSilent = 3 * std::max<int>(MapWidth, MapHeight);
782 	ViewPointOffset = std::max<int>(MapWidth / 2, MapHeight / 2);
783 }
784 
~CSound()785 CSound::~CSound()
786 {
787 	if (this->Number == ONE_SOUND) {
788 		delete Sound.OneSound;
789 	} else if (this->Number == TWO_GROUPS) {
790 	} else {
791 		//Wyrmgus start
792 //		for (int i = 0; i < this->Number; ++i) {
793 		for (unsigned int i = 0; i < this->Number; ++i) {
794 		//Wyrmgus end
795 			delete this->Sound.OneGroup[i];
796 			this->Sound.OneGroup[i] = nullptr;
797 		}
798 		delete[] this->Sound.OneGroup;
799 	}
800 }
801 
ProcessConfigData(const CConfigData * config_data)802 void CSound::ProcessConfigData(const CConfigData *config_data)
803 {
804 	std::string ident = config_data->Ident;
805 	ident = FindAndReplaceString(ident, "_", "-");
806 	std::vector<std::string> files;
807 	std::vector<CSound *> group_sounds; //sounds for sound group
808 	unsigned char range = 0;
809 
810 	for (size_t i = 0; i < config_data->Properties.size(); ++i) {
811 		std::string key = config_data->Properties[i].first;
812 		std::string value = config_data->Properties[i].second;
813 
814 		if (key == "file") {
815 			std::string file = CMod::GetCurrentModPath() + value;
816 			files.push_back(file);
817 		} else if (key == "group_sound") {
818 			value = FindAndReplaceString(value, "_", "-");
819 			CSound *group_sound = SoundForName(value);
820 			if (group_sound) {
821 				group_sounds.push_back(group_sound);
822 			} else {
823 				fprintf(stderr, "Invalid sound: \"%s\".\n", value.c_str());
824 			}
825 		} else if (key == "range") {
826 			range = std::stoi(value);
827 		} else {
828 			fprintf(stderr, "Invalid sound property: \"%s\".\n", key.c_str());
829 		}
830 	}
831 
832 	CSound *sound = nullptr;
833 	if (group_sounds.size() >= 2) {
834 		sound = MakeSoundGroup(ident, group_sounds[0], group_sounds[1]);
835 	} else {
836 		sound = MakeSound(ident, files);
837 	}
838 
839 	if (range != 0) {
840 		SetSoundRange(sound, range);
841 	}
842 }
843 
844 //@}
845