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