1 /*
2 ** s_advsound.cpp
3 ** Routines for managing SNDINFO lumps and ambient sounds
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2008 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 ** notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 ** notice, this list of conditions and the following disclaimer in the
17 ** documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 ** derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34
35 // HEADER FILES ------------------------------------------------------------
36
37 #include "templates.h"
38 #include "actor.h"
39 #include "a_sharedglobal.h"
40 #include "s_sound.h"
41 #include "c_dispatch.h"
42 #include "w_wad.h"
43 #include "sc_man.h"
44 #include "g_level.h"
45 #include "cmdlib.h"
46 #include "gi.h"
47 #include "doomstat.h"
48 #include "i_sound.h"
49 #include "m_random.h"
50 #include "d_netinf.h"
51 #include "i_system.h"
52 #include "d_player.h"
53 #include "farchive.h"
54
55 // MACROS ------------------------------------------------------------------
56
57 #define RANDOM 1
58 #define PERIODIC 2
59 #define CONTINUOUS 3
60 #define POSITIONAL 4
61 #define SURROUND 16
62
63 // TYPES -------------------------------------------------------------------
64
65 struct FRandomSoundList
66 {
FRandomSoundListFRandomSoundList67 FRandomSoundList()
68 : Sounds(0), SfxHead(0), NumSounds(0)
69 {
70 }
~FRandomSoundListFRandomSoundList71 ~FRandomSoundList()
72 {
73 if (Sounds != NULL)
74 {
75 delete[] Sounds;
76 Sounds = NULL;
77 }
78 }
79
80 WORD *Sounds; // A list of sounds that can result for the following id
81 WORD SfxHead; // The sound id used to reference this list
82 WORD NumSounds;
83 };
84
85 struct FPlayerClassLookup
86 {
87 FString Name;
88 WORD ListIndex[3]; // indices into PlayerSounds (0xffff means empty)
89 };
90
91 // Used to lookup a sound like "*grunt". This contains all player sounds for
92 // a particular class and gender.
93 class FPlayerSoundHashTable
94 {
95 public:
96 FPlayerSoundHashTable();
97 FPlayerSoundHashTable(const FPlayerSoundHashTable &other);
98 ~FPlayerSoundHashTable();
99
100 void AddSound (int player_sound_id, int sfx_id);
101 int LookupSound (int player_sound_id);
102 FPlayerSoundHashTable &operator= (const FPlayerSoundHashTable &other);
103 void MarkUsed();
104
105 protected:
106 struct Entry
107 {
108 Entry *Next;
109 int PlayerSoundID;
110 int SfxID;
111 };
112 enum { NUM_BUCKETS = 23 };
113 Entry *Buckets[NUM_BUCKETS];
114
115 void Init ();
116 void Free ();
117 };
118
119 struct FAmbientSound
120 {
121 unsigned type; // type of ambient sound
122 int periodmin; // # of tics between repeats
123 int periodmax; // max # of tics for random ambients
124 float volume; // relative volume of sound
125 float attenuation;
126 FSoundID sound; // Sound to play
127 };
128 TMap<int, FAmbientSound> Ambients;
129
130 enum SICommands
131 {
132 SI_Ambient,
133 SI_Random,
134 SI_PlayerSound,
135 SI_PlayerSoundDup,
136 SI_PlayerCompat,
137 SI_PlayerAlias,
138 SI_Alias,
139 SI_Limit,
140 SI_Singular,
141 SI_PitchShift,
142 SI_PitchShiftRange,
143 SI_Map,
144 SI_Registered,
145 SI_ArchivePath,
146 SI_MusicVolume,
147 SI_MidiDevice,
148 SI_IfDoom,
149 SI_IfHeretic,
150 SI_IfHexen,
151 SI_IfStrife,
152 SI_Rolloff,
153 SI_Volume,
154 SI_MusicAlias,
155 SI_EDFOverride,
156 SI_Attenuation,
157 };
158
159 // Blood was a cool game. If Monolith ever releases the source for it,
160 // you can bet I'll port it.
161
162 struct FBloodSFX
163 {
164 DWORD RelVol; // volume, 0-255
165 fixed_t Pitch; // pitch change
166 fixed_t PitchRange; // range of random pitch
167 DWORD Format; // format of audio 1=11025 5=22050
168 SDWORD LoopStart; // loop position (-1 means no looping)
169 char RawName[9]; // name of RAW resource
170 };
171
172 // music volume multipliers
173 struct FMusicVolume
174 {
175 FMusicVolume *Next;
176 float Volume;
177 char MusicName[1];
178 };
179
180 // This is used to recreate the skin sounds after reloading SNDINFO due to a changed local one.
181 struct FSavedPlayerSoundInfo
182 {
183 FName pclass;
184 int gender;
185 int refid;
186 int lumpnum;
187 bool alias;
188 };
189
190 // This specifies whether Timidity or Windows playback is preferred for a certain song (only useful for Windows.)
191 MusicAliasMap MusicAliases;
192 MidiDeviceMap MidiDevices;
193
194 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
195
196 extern bool IsFloat (const char *str);
197
198 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
199
200 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
201
202 static int STACK_ARGS SortPlayerClasses (const void *a, const void *b);
203 static int S_DupPlayerSound (const char *pclass, int gender, int refid, int aliasref);
204 static void S_SavePlayerSound (const char *pclass, int gender, int refid, int lumpnum, bool alias);
205 static void S_RestorePlayerSounds();
206 static int S_AddPlayerClass (const char *name);
207 static int S_AddPlayerGender (int classnum, int gender);
208 static int S_FindPlayerClass (const char *name);
209 static int S_LookupPlayerSound (int classidx, int gender, FSoundID refid);
210 static void S_ParsePlayerSoundCommon (FScanner &sc, FString &pclass, int &gender, int &refid);
211 static void S_AddSNDINFO (int lumpnum);
212 static void S_AddBloodSFX (int lumpnum);
213 static void S_AddStrifeVoice (int lumpnum);
214 static int S_AddSound (const char *logicalname, int lumpnum, FScanner *sc=NULL);
215
216 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
217
218 extern int sfx_empty;
219
220 // PUBLIC DATA DEFINITIONS -------------------------------------------------
221
222 TArray<sfxinfo_t> S_sfx (128);
223 TMap<int, FString> HexenMusic;
224
225 // PRIVATE DATA DEFINITIONS ------------------------------------------------
226
227 static const char *SICommandStrings[] =
228 {
229 "$ambient",
230 "$random",
231 "$playersound",
232 "$playersounddup",
233 "$playercompat",
234 "$playeralias",
235 "$alias",
236 "$limit",
237 "$singular",
238 "$pitchshift",
239 "$pitchshiftrange",
240 "$map",
241 "$registered",
242 "$archivepath",
243 "$musicvolume",
244 "$mididevice",
245 "$ifdoom",
246 "$ifheretic",
247 "$ifhexen",
248 "$ifstrife",
249 "$rolloff",
250 "$volume",
251 "$musicalias",
252 "$edfoverride",
253 "$attenuation",
254 NULL
255 };
256
257 static TArray<FRandomSoundList> S_rnd;
258 static FMusicVolume *MusicVolumes;
259 static TArray<FSavedPlayerSoundInfo> SavedPlayerSounds;
260
261 static int NumPlayerReserves;
262 static bool PlayerClassesIsSorted;
263
264 static TArray<FPlayerClassLookup> PlayerClassLookups;
265 static TArray<FPlayerSoundHashTable> PlayerSounds;
266
267
268 static FString DefPlayerClassName;
269 static int DefPlayerClass;
270
271 static BYTE CurrentPitchMask;
272
273 static FRandom pr_randsound ("RandSound");
274
275 // CODE --------------------------------------------------------------------
276
277 //==========================================================================
278 //
279 // S_GetMusicVolume
280 //
281 // Gets the relative volume for the given music track
282 //==========================================================================
283
S_GetMusicVolume(const char * music)284 float S_GetMusicVolume (const char *music)
285 {
286 FMusicVolume *musvol = MusicVolumes;
287
288 while (musvol != NULL)
289 {
290 if (!stricmp (music, musvol->MusicName))
291 {
292 return musvol->Volume;
293 }
294 musvol = musvol->Next;
295 }
296 return 1.f;
297 }
298
299 //==========================================================================
300
301 //
302 // S_HashSounds
303 //
304 // Fills in the next and index fields of S_sfx to form a working hash table.
305 //==========================================================================
306
S_HashSounds()307 void S_HashSounds ()
308 {
309 unsigned int i;
310 unsigned int j;
311 unsigned int size;
312
313 S_sfx.ShrinkToFit ();
314 size = S_sfx.Size ();
315
316 // Mark all buckets as empty
317 for (i = 0; i < size; i++)
318 S_sfx[i].index = 0;
319
320 // Now set up the chains
321 for (i = 1; i < size; i++)
322 {
323 j = MakeKey (S_sfx[i].name) % size;
324 S_sfx[i].next = S_sfx[j].index;
325 S_sfx[j].index = i;
326 }
327 }
328
329 //==========================================================================
330 //
331 // S_PickReplacement
332 //
333 // Picks a replacement sound from the associated random list. If this sound
334 // is not the head of a random list, then the sound passed is returned.
335 //==========================================================================
336
S_PickReplacement(int refid)337 int S_PickReplacement (int refid)
338 {
339 if (S_sfx[refid].bRandomHeader)
340 {
341 const FRandomSoundList *list = &S_rnd[S_sfx[refid].link];
342
343 return list->Sounds[pr_randsound() % list->NumSounds];
344 }
345 return refid;
346 }
347
348 //==========================================================================
349 //
350 // S_GetSoundMSLength
351 //
352 // Returns duration of sound
353 //
354 //==========================================================================
355
S_GetMSLength(FSoundID sound)356 unsigned int S_GetMSLength(FSoundID sound)
357 {
358 if ((unsigned int)sound >= S_sfx.Size())
359 {
360 return 0;
361 }
362
363 sfxinfo_t *sfx = &S_sfx[sound];
364
365 // Resolve player sounds, random sounds, and aliases
366 if (sfx->link != sfxinfo_t::NO_LINK)
367 {
368 if (sfx->bPlayerReserve)
369 {
370 sfx = &S_sfx[S_FindSkinnedSound (NULL, sound)];
371 }
372 else if (sfx->bRandomHeader)
373 {
374 // Hm... What should we do here?
375 // Pick the longest or the shortest sound?
376 // I think the longest one makes more sense.
377
378 int length = 0;
379 const FRandomSoundList *list = &S_rnd[sfx->link];
380
381 for (int i=0; i < list->NumSounds; i++)
382 {
383 // unfortunately we must load all sounds to find the longest one... :(
384 int thislen = S_GetMSLength(list->Sounds[i]);
385 if (thislen > length) length = thislen;
386 }
387 return length;
388 }
389 else
390 {
391 sfx = &S_sfx[sfx->link];
392 }
393 }
394
395 sfx = S_LoadSound(sfx);
396 if (sfx != NULL) return GSnd->GetMSLength(sfx->data);
397 else return 0;
398 }
399
400
401 //==========================================================================
402 //
403 // S_CacheRandomSound
404 //
405 // Loads all sounds a random sound might play.
406 //
407 //==========================================================================
408
S_CacheRandomSound(sfxinfo_t * sfx)409 void S_CacheRandomSound (sfxinfo_t *sfx)
410 {
411 if (sfx->bRandomHeader)
412 {
413 const FRandomSoundList *list = &S_rnd[sfx->link];
414 for (int i = 0; i < list->NumSounds; ++i)
415 {
416 sfx = &S_sfx[list->Sounds[i]];
417 sfx->bUsed = true;
418 S_CacheSound (&S_sfx[list->Sounds[i]]);
419 }
420 }
421 }
422
423 //==========================================================================
424 //
425 // S_FindSound
426 //
427 // Given a logical name, find the sound's index in S_sfx.
428 //==========================================================================
429
S_FindSound(const char * logicalname)430 int S_FindSound (const char *logicalname)
431 {
432 int i;
433
434 if (logicalname != NULL)
435 {
436 i = S_sfx[MakeKey (logicalname) % S_sfx.Size ()].index;
437
438 while ((i != 0) && stricmp (S_sfx[i].name, logicalname))
439 i = S_sfx[i].next;
440
441 return i;
442 }
443 else
444 {
445 return 0;
446 }
447 }
448
449 //==========================================================================
450 //
451 // S_FindSoundNoHash
452 //
453 // Given a logical name, find the sound's index in S_sfx without
454 // using the hash table.
455 //==========================================================================
456
S_FindSoundNoHash(const char * logicalname)457 int S_FindSoundNoHash (const char *logicalname)
458 {
459 unsigned int i;
460
461 for (i = 1; i < S_sfx.Size (); i++)
462 {
463 if (stricmp (S_sfx[i].name, logicalname) == 0)
464 {
465 return i;
466 }
467 }
468 return 0;
469 }
470
471 //==========================================================================
472 //
473 // S_FindSoundByLump
474 //
475 // Given a sound lump, find the sound's index in S_sfx.
476 //==========================================================================
477
S_FindSoundByLump(int lump)478 int S_FindSoundByLump (int lump)
479 {
480 if (lump != -1)
481 {
482 unsigned int i;
483
484 for (i = 1; i < S_sfx.Size (); i++)
485 if (S_sfx[i].lumpnum == lump)
486 return i;
487 }
488 return 0;
489 }
490
491 //==========================================================================
492 //
493 // S_AddSoundLump
494 //
495 // Adds a new sound mapping to S_sfx.
496 //==========================================================================
497
S_AddSoundLump(const char * logicalname,int lump)498 int S_AddSoundLump (const char *logicalname, int lump)
499 {
500 sfxinfo_t newsfx;
501
502 newsfx.data.Clear();
503 newsfx.name = logicalname;
504 newsfx.lumpnum = lump;
505 newsfx.next = 0;
506 newsfx.index = 0;
507 newsfx.Volume = 1;
508 newsfx.Attenuation = 1;
509 newsfx.PitchMask = CurrentPitchMask;
510 newsfx.NearLimit = 2;
511 newsfx.LimitRange = 256*256;
512 newsfx.bRandomHeader = false;
513 newsfx.bPlayerReserve = false;
514 newsfx.bLoadRAW = false;
515 newsfx.bPlayerCompat = false;
516 newsfx.b16bit = false;
517 newsfx.bUsed = false;
518 newsfx.bSingular = false;
519 newsfx.bTentative = false;
520 newsfx.bPlayerSilent = false;
521 newsfx.RawRate = 0;
522 newsfx.link = sfxinfo_t::NO_LINK;
523 newsfx.Rolloff.RolloffType = ROLLOFF_Doom;
524 newsfx.Rolloff.MinDistance = 0;
525 newsfx.Rolloff.MaxDistance = 0;
526 newsfx.LoopStart = -1;
527
528 return (int)S_sfx.Push (newsfx);
529 }
530
531 //==========================================================================
532 //
533 // S_FindSoundTentative
534 //
535 // Given a logical name, find the sound's index in S_sfx without
536 // using the hash table. If it does not exist, a new sound without
537 // an associated lump is created.
538 //==========================================================================
539
S_FindSoundTentative(const char * name)540 int S_FindSoundTentative (const char *name)
541 {
542 int id = S_FindSoundNoHash (name);
543 if (id == 0)
544 {
545 id = S_AddSoundLump (name, -1);
546 S_sfx[id].bTentative = true;
547 }
548 return id;
549 }
550
551 //==========================================================================
552 //
553 // S_AddSound
554 //
555 // If logical name is already in S_sfx, updates it to use the new sound
556 // lump. Otherwise, adds the new mapping by using S_AddSoundLump().
557 //==========================================================================
558
S_AddSound(const char * logicalname,const char * lumpname,FScanner * sc)559 int S_AddSound (const char *logicalname, const char *lumpname, FScanner *sc)
560 {
561 int lump = Wads.CheckNumForFullName (lumpname, true, ns_sounds);
562 return S_AddSound (logicalname, lump);
563 }
564
S_AddSound(const char * logicalname,int lumpnum,FScanner * sc)565 static int S_AddSound (const char *logicalname, int lumpnum, FScanner *sc)
566 {
567 int sfxid;
568
569 sfxid = S_FindSoundNoHash (logicalname);
570
571 if (sfxid > 0)
572 { // If the sound has already been defined, change the old definition
573 sfxinfo_t *sfx = &S_sfx[sfxid];
574
575 if (sfx->bPlayerReserve)
576 {
577 if (sc != NULL)
578 {
579 sc->ScriptError ("Sounds that are reserved for players cannot be reassigned");
580 }
581 else
582 {
583 I_Error ("Sounds that are reserved for players cannot be reassigned");
584 }
585 }
586 // Redefining a player compatibility sound will redefine the target instead.
587 if (sfx->bPlayerCompat)
588 {
589 sfx = &S_sfx[sfx->link];
590 }
591 if (sfx->bRandomHeader)
592 {
593 FRandomSoundList *rnd = &S_rnd[sfx->link];
594 delete[] rnd->Sounds;
595 rnd->Sounds = NULL;
596 rnd->NumSounds = 0;
597 rnd->SfxHead = 0;
598 }
599 sfx->lumpnum = lumpnum;
600 sfx->bRandomHeader = false;
601 sfx->link = sfxinfo_t::NO_LINK;
602 sfx->bTentative = false;
603 if (sfx->NearLimit == -1)
604 {
605 sfx->NearLimit = 2;
606 sfx->LimitRange = 256*256;
607 }
608 //sfx->PitchMask = CurrentPitchMask;
609 }
610 else
611 { // Otherwise, create a new definition.
612 sfxid = S_AddSoundLump (logicalname, lumpnum);
613 }
614
615 return sfxid;
616 }
617
618 //==========================================================================
619 //
620 // S_AddPlayerSound
621 //
622 // Adds the given sound lump to the player sound lists.
623 //==========================================================================
624
S_AddPlayerSound(const char * pclass,int gender,int refid,const char * lumpname)625 int S_AddPlayerSound (const char *pclass, int gender, int refid,
626 const char *lumpname)
627 {
628 int lump=-1;
629
630 if (lumpname)
631 {
632 lump = Wads.CheckNumForFullName (lumpname, true, ns_sounds);
633 }
634
635 return S_AddPlayerSound (pclass, gender, refid, lump);
636 }
637
S_AddPlayerSound(const char * pclass,int gender,int refid,int lumpnum,bool fromskin)638 int S_AddPlayerSound (const char *pclass, int gender, int refid, int lumpnum, bool fromskin)
639 {
640 FString fakename;
641 int id;
642
643 fakename = pclass;
644 fakename += '"';
645 fakename += '0' + gender;
646 fakename += '"';
647 fakename += S_sfx[refid].name;
648
649 id = S_AddSoundLump (fakename, lumpnum);
650 int classnum = S_AddPlayerClass (pclass);
651 int soundlist = S_AddPlayerGender (classnum, gender);
652
653 PlayerSounds[soundlist].AddSound (S_sfx[refid].link, id);
654
655 if (fromskin) S_SavePlayerSound(pclass, gender, refid, lumpnum, false);
656
657 return id;
658 }
659
660 //==========================================================================
661 //
662 // S_AddPlayerSoundExisting
663 //
664 // Adds the player sound as an alias to an existing sound.
665 //==========================================================================
666
S_AddPlayerSoundExisting(const char * pclass,int gender,int refid,int aliasto,bool fromskin)667 int S_AddPlayerSoundExisting (const char *pclass, int gender, int refid,
668 int aliasto, bool fromskin)
669 {
670 int classnum = S_AddPlayerClass (pclass);
671 int soundlist = S_AddPlayerGender (classnum, gender);
672
673 PlayerSounds[soundlist].AddSound (S_sfx[refid].link, aliasto);
674
675 if (fromskin) S_SavePlayerSound(pclass, gender, refid, aliasto, true);
676
677 return aliasto;
678 }
679
680 //==========================================================================
681 //
682 // S_DupPlayerSound
683 //
684 // Adds a player sound that uses the same sound as an existing player sound.
685 //==========================================================================
686
S_DupPlayerSound(const char * pclass,int gender,int refid,int aliasref)687 int S_DupPlayerSound (const char *pclass, int gender, int refid, int aliasref)
688 {
689 int aliasto = S_LookupPlayerSound (pclass, gender, aliasref);
690 return S_AddPlayerSoundExisting (pclass, gender, refid, aliasto);
691 }
692
693 //==========================================================================
694 //
695 // FPlayerSoundHashTable constructor
696 //
697 //==========================================================================
698
FPlayerSoundHashTable()699 FPlayerSoundHashTable::FPlayerSoundHashTable ()
700 {
701 Init();
702 }
703
704 //==========================================================================
705 //
706 // FPlayerSoundHashTable copy constructor
707 //
708 //==========================================================================
709
FPlayerSoundHashTable(const FPlayerSoundHashTable & other)710 FPlayerSoundHashTable::FPlayerSoundHashTable (const FPlayerSoundHashTable &other)
711 {
712 Init();
713 *this = other;
714 }
715
716 //==========================================================================
717 //
718 // FPlayerSoundHashTable destructor
719 //
720 //==========================================================================
721
~FPlayerSoundHashTable()722 FPlayerSoundHashTable::~FPlayerSoundHashTable ()
723 {
724 Free ();
725 }
726
727 //==========================================================================
728 //
729 // FPlayerSoundHashTable :: Init
730 //
731 //==========================================================================
732
Init()733 void FPlayerSoundHashTable::Init ()
734 {
735 for (int i = 0; i < NUM_BUCKETS; ++i)
736 {
737 Buckets[i] = NULL;
738 }
739 }
740
741 //==========================================================================
742 //
743 // FPlayerSoundHashTable :: Free
744 //
745 //==========================================================================
746
Free()747 void FPlayerSoundHashTable::Free ()
748 {
749 for (int i = 0; i < NUM_BUCKETS; ++i)
750 {
751 Entry *entry, *next;
752
753 for (entry = Buckets[i]; entry != NULL; )
754 {
755 next = entry->Next;
756 delete entry;
757 entry = next;
758 }
759 Buckets[i] = NULL;
760 }
761 }
762
763 //==========================================================================
764 //
765 // FPlayerSoundHashTable :: operator=
766 //
767 //==========================================================================
768
operator =(const FPlayerSoundHashTable & other)769 FPlayerSoundHashTable &FPlayerSoundHashTable::operator= (const FPlayerSoundHashTable &other)
770 {
771 Free ();
772 for (int i = 0; i < NUM_BUCKETS; ++i)
773 {
774 Entry *entry;
775
776 for (entry = other.Buckets[i]; entry != NULL; entry = entry->Next)
777 {
778 AddSound (entry->PlayerSoundID, entry->SfxID);
779 }
780 }
781 return *this;
782 }
783
784 //==========================================================================
785 //
786 // FPlayerSoundHashTable :: AddSound
787 //
788 //==========================================================================
789
AddSound(int player_sound_id,int sfx_id)790 void FPlayerSoundHashTable::AddSound (int player_sound_id, int sfx_id)
791 {
792 Entry *entry;
793 unsigned bucket_num = (unsigned)player_sound_id % NUM_BUCKETS;
794
795 // See if the entry exists already.
796 for (entry = Buckets[bucket_num];
797 entry != NULL && entry->PlayerSoundID != player_sound_id;
798 entry = entry->Next)
799 { }
800
801 if (entry != NULL)
802 { // If the player sound is already present, redefine it.
803 entry->SfxID = sfx_id;
804 }
805 else
806 { // Otherwise, add it to the start of its bucket.
807 entry = new Entry;
808 entry->Next = Buckets[bucket_num];
809 entry->PlayerSoundID = player_sound_id;
810 entry->SfxID = sfx_id;
811 Buckets[bucket_num] = entry;
812 }
813 }
814
815 //==========================================================================
816 //
817 // FPlayerSoundHashTable :: LookupSound
818 //
819 //==========================================================================
820
LookupSound(int player_sound_id)821 int FPlayerSoundHashTable::LookupSound (int player_sound_id)
822 {
823 Entry *entry;
824 unsigned bucket_num = (unsigned)player_sound_id % NUM_BUCKETS;
825
826 // See if the entry exists already.
827 for (entry = Buckets[bucket_num];
828 entry != NULL && entry->PlayerSoundID != player_sound_id;
829 entry = entry->Next)
830 { }
831
832 return entry != NULL ? entry->SfxID : 0;
833 }
834
835 //==========================================================================
836 //
837 // FPlayerSoundHashTable :: Mark
838 //
839 // Marks all sounds defined for this class/gender as used.
840 //
841 //==========================================================================
842
MarkUsed()843 void FPlayerSoundHashTable::MarkUsed()
844 {
845 for (size_t i = 0; i < NUM_BUCKETS; ++i)
846 {
847 for (Entry *probe = Buckets[i]; probe != NULL; probe = probe->Next)
848 {
849 S_sfx[probe->SfxID].bUsed = true;
850 }
851 }
852 }
853
854 //==========================================================================
855 //
856 // S_ClearSoundData
857 //
858 // clears all sound tables
859 // When we want to allow level specific SNDINFO lumps this has to
860 // be cleared for each level
861 //==========================================================================
862
S_ClearSoundData()863 static void S_ClearSoundData()
864 {
865 unsigned int i;
866
867 S_StopAllChannels();
868 for (i = 0; i < S_sfx.Size(); ++i)
869 {
870 S_UnloadSound(&S_sfx[i]);
871 }
872 S_sfx.Clear();
873 Ambients.Clear();
874 while (MusicVolumes != NULL)
875 {
876 FMusicVolume *me = MusicVolumes;
877 MusicVolumes = me->Next;
878 M_Free(me);
879 }
880 S_rnd.Clear();
881
882 NumPlayerReserves = 0;
883 PlayerClassesIsSorted = false;
884 PlayerClassLookups.Clear();
885 PlayerSounds.Clear();
886 DefPlayerClass = 0;
887 DefPlayerClassName = "";
888 MusicAliases.Clear();
889 MidiDevices.Clear();
890 HexenMusic.Clear();
891 }
892
893 //==========================================================================
894 //
895 // S_ParseSndInfo
896 //
897 // Parses all loaded SNDINFO lumps.
898 // Also registers Blood SFX files and Strife's voices.
899 //==========================================================================
900
S_ParseSndInfo(bool redefine)901 void S_ParseSndInfo (bool redefine)
902 {
903 int lump;
904
905 if (!redefine) SavedPlayerSounds.Clear(); // clear skin sounds only for initial parsing.
906 atterm (S_ClearSoundData);
907 S_ClearSoundData(); // remove old sound data first!
908
909 CurrentPitchMask = 0;
910 S_AddSound ("{ no sound }", "DSEMPTY"); // Sound 0 is no sound at all
911 for (lump = 0; lump < Wads.GetNumLumps(); ++lump)
912 {
913 switch (Wads.GetLumpNamespace (lump))
914 {
915 case ns_global:
916 if (Wads.CheckLumpName (lump, "SNDINFO"))
917 {
918 S_AddSNDINFO (lump);
919 }
920 break;
921
922 case ns_bloodsfx:
923 S_AddBloodSFX (lump);
924 break;
925
926 case ns_strifevoices:
927 S_AddStrifeVoice (lump);
928 break;
929 }
930 }
931 S_RestorePlayerSounds();
932 S_HashSounds ();
933 S_sfx.ShrinkToFit ();
934
935 if (S_rnd.Size() > 0)
936 {
937 S_rnd.ShrinkToFit ();
938 }
939
940 S_ShrinkPlayerSoundLists ();
941
942 sfx_empty = Wads.CheckNumForName ("dsempty", ns_sounds);
943 }
944
945 //==========================================================================
946 //
947 // Adds a level specific SNDINFO lump
948 //
949 //==========================================================================
950
S_AddLocalSndInfo(int lump)951 void S_AddLocalSndInfo(int lump)
952 {
953 S_AddSNDINFO(lump);
954 S_HashSounds ();
955 S_sfx.ShrinkToFit ();
956
957 if (S_rnd.Size() > 0)
958 {
959 S_rnd.ShrinkToFit ();
960 }
961
962 S_ShrinkPlayerSoundLists ();
963 }
964
965 //==========================================================================
966 //
967 // S_AddSNDINFO
968 //
969 // Reads a SNDINFO and does what it says.
970 //
971 //==========================================================================
972
S_AddSNDINFO(int lump)973 static void S_AddSNDINFO (int lump)
974 {
975 bool skipToEndIf;
976 TArray<WORD> list;
977
978 FScanner sc(lump);
979 skipToEndIf = false;
980
981 while (sc.GetString ())
982 {
983 if (skipToEndIf)
984 {
985 if (sc.Compare ("$endif"))
986 {
987 skipToEndIf = false;
988 }
989 continue;
990 }
991
992 if (sc.String[0] == '$')
993 { // Got a command
994 switch (sc.MatchString (SICommandStrings))
995 {
996 case SI_Ambient: {
997 // $ambient <num> <logical name> [point [atten] | surround | [world]]
998 // <continuous | random <minsecs> <maxsecs> | periodic <secs>>
999 // <volume>
1000 FAmbientSound *ambient;
1001
1002 sc.MustGetNumber ();
1003 ambient = &Ambients[sc.Number];
1004 ambient->type = 0;
1005 ambient->periodmin = 0;
1006 ambient->periodmax = 0;
1007 ambient->volume = 0;
1008 ambient->attenuation = 0;
1009 ambient->sound = 0;
1010
1011 sc.MustGetString ();
1012 ambient->sound = FSoundID(S_FindSoundTentative(sc.String));
1013 ambient->attenuation = 0;
1014
1015 sc.MustGetString ();
1016 if (sc.Compare ("point"))
1017 {
1018 float attenuation;
1019
1020 ambient->type = POSITIONAL;
1021 sc.MustGetString ();
1022
1023 if (IsFloat (sc.String))
1024 {
1025 attenuation = (float)atof (sc.String);
1026 sc.MustGetString ();
1027 if (attenuation > 0)
1028 {
1029 ambient->attenuation = attenuation;
1030 }
1031 else
1032 {
1033 ambient->attenuation = 1;
1034 }
1035 }
1036 else
1037 {
1038 ambient->attenuation = 1;
1039 }
1040 }
1041 else if (sc.Compare ("surround"))
1042 {
1043 ambient->type = SURROUND;
1044 sc.MustGetString ();
1045 ambient->attenuation = -1;
1046 }
1047 else
1048 { // World is an optional keyword
1049 if (sc.Compare ("world"))
1050 {
1051 sc.MustGetString ();
1052 }
1053 }
1054
1055 if (sc.Compare ("continuous"))
1056 {
1057 ambient->type |= CONTINUOUS;
1058 }
1059 else if (sc.Compare ("random"))
1060 {
1061 ambient->type |= RANDOM;
1062 sc.MustGetFloat ();
1063 ambient->periodmin = (int)(sc.Float * TICRATE);
1064 sc.MustGetFloat ();
1065 ambient->periodmax = (int)(sc.Float * TICRATE);
1066 }
1067 else if (sc.Compare ("periodic"))
1068 {
1069 ambient->type |= PERIODIC;
1070 sc.MustGetFloat ();
1071 ambient->periodmin = (int)(sc.Float * TICRATE);
1072 }
1073 else
1074 {
1075 Printf ("Unknown ambient type (%s)\n", sc.String);
1076 }
1077
1078 sc.MustGetFloat ();
1079 ambient->volume = (float)sc.Float;
1080 if (ambient->volume > 1)
1081 ambient->volume = 1;
1082 else if (ambient->volume < 0)
1083 ambient->volume = 0;
1084 }
1085 break;
1086
1087 case SI_Map: {
1088 // Hexen-style $MAP command
1089 int mapnum;
1090
1091 sc.MustGetNumber();
1092 mapnum = sc.Number;
1093 sc.MustGetString();
1094 if (mapnum != 0)
1095 {
1096 HexenMusic[mapnum] = sc.String;
1097 }
1098 }
1099 break;
1100
1101 case SI_Registered:
1102 // I don't think Hexen even pays attention to the $registered command.
1103 case SI_EDFOverride:
1104 break;
1105
1106 case SI_ArchivePath:
1107 sc.MustGetString (); // Unused for now
1108 break;
1109
1110 case SI_PlayerSound: {
1111 // $playersound <player class> <gender> <logical name> <lump name>
1112 FString pclass;
1113 int gender, refid, sfxnum;
1114
1115 S_ParsePlayerSoundCommon (sc, pclass, gender, refid);
1116 sfxnum = S_AddPlayerSound (pclass, gender, refid, sc.String);
1117 if (0 == stricmp(sc.String, "dsempty"))
1118 {
1119 S_sfx[sfxnum].bPlayerSilent = true;
1120 }
1121 }
1122 break;
1123
1124 case SI_PlayerSoundDup: {
1125 // $playersounddup <player class> <gender> <logical name> <target sound name>
1126 FString pclass;
1127 int gender, refid, targid;
1128
1129 S_ParsePlayerSoundCommon (sc, pclass, gender, refid);
1130 targid = S_FindSoundNoHash (sc.String);
1131 if (!S_sfx[targid].bPlayerReserve)
1132 {
1133 sc.ScriptError ("%s is not a player sound", sc.String);
1134 }
1135 S_DupPlayerSound (pclass, gender, refid, targid);
1136 }
1137 break;
1138
1139 case SI_PlayerCompat: {
1140 // $playercompat <player class> <gender> <logical name> <compat sound name>
1141 FString pclass;
1142 int gender, refid;
1143 int sfxfrom, aliasto;
1144
1145 S_ParsePlayerSoundCommon (sc, pclass, gender, refid);
1146 sfxfrom = S_AddSound (sc.String, -1, &sc);
1147 aliasto = S_LookupPlayerSound (pclass, gender, refid);
1148 S_sfx[sfxfrom].link = aliasto;
1149 S_sfx[sfxfrom].bPlayerCompat = true;
1150 }
1151 break;
1152
1153 case SI_PlayerAlias: {
1154 // $playeralias <player class> <gender> <logical name> <logical name of existing sound>
1155 FString pclass;
1156 int gender, refid;
1157 int soundnum;
1158
1159 S_ParsePlayerSoundCommon (sc, pclass, gender, refid);
1160 soundnum = S_FindSoundTentative (sc.String);
1161 S_AddPlayerSoundExisting (pclass, gender, refid, soundnum);
1162 }
1163 break;
1164
1165 case SI_Alias: {
1166 // $alias <name of alias> <name of real sound>
1167 int sfxfrom;
1168
1169 sc.MustGetString ();
1170 sfxfrom = S_AddSound (sc.String, -1, &sc);
1171 sc.MustGetString ();
1172 if (S_sfx[sfxfrom].bPlayerCompat)
1173 {
1174 sfxfrom = S_sfx[sfxfrom].link;
1175 }
1176 S_sfx[sfxfrom].link = S_FindSoundTentative (sc.String);
1177 S_sfx[sfxfrom].NearLimit = -1; // Aliases must use the original sound's limit.
1178 }
1179 break;
1180
1181 case SI_Limit: {
1182 // $limit <logical name> <max channels> [<distance>]
1183 int sfx;
1184
1185 sc.MustGetString ();
1186 sfx = S_FindSoundTentative (sc.String);
1187 sc.MustGetNumber ();
1188 S_sfx[sfx].NearLimit = MIN(MAX(sc.Number, 0), 255);
1189 if (sc.CheckFloat())
1190 {
1191 S_sfx[sfx].LimitRange = float(sc.Float * sc.Float);
1192 }
1193 }
1194 break;
1195
1196 case SI_Singular: {
1197 // $singular <logical name>
1198 int sfx;
1199
1200 sc.MustGetString ();
1201 sfx = S_FindSoundTentative (sc.String);
1202 S_sfx[sfx].bSingular = true;
1203 }
1204 break;
1205
1206 case SI_PitchShift: {
1207 // $pitchshift <logical name> <pitch shift amount>
1208 int sfx;
1209
1210 sc.MustGetString ();
1211 sfx = S_FindSoundTentative (sc.String);
1212 sc.MustGetNumber ();
1213 S_sfx[sfx].PitchMask = (1 << clamp (sc.Number, 0, 7)) - 1;
1214 }
1215 break;
1216
1217 case SI_PitchShiftRange:
1218 // $pitchshiftrange <pitch shift amount>
1219 sc.MustGetNumber ();
1220 CurrentPitchMask = (1 << clamp (sc.Number, 0, 7)) - 1;
1221 break;
1222
1223 case SI_Volume: {
1224 // $volume <logical name> <volume>
1225 int sfx;
1226
1227 sc.MustGetString();
1228 sfx = S_FindSoundTentative(sc.String);
1229 sc.MustGetFloat();
1230 S_sfx[sfx].Volume = (float)sc.Float;
1231 }
1232 break;
1233
1234 case SI_Attenuation: {
1235 // $attenuation <logical name> <attenuation>
1236 int sfx;
1237
1238 sc.MustGetString();
1239 sfx = S_FindSoundTentative(sc.String);
1240 sc.MustGetFloat();
1241 S_sfx[sfx].Attenuation = (float)sc.Float;
1242 }
1243 break;
1244
1245 case SI_Rolloff: {
1246 // $rolloff *|<logical name> [linear|log|custom] <min dist> <max dist/rolloff factor>
1247 // Using * for the name makes it the default for sounds that don't specify otherwise.
1248 FRolloffInfo *rolloff;
1249 int type;
1250 int sfx;
1251
1252 sc.MustGetString();
1253 if (sc.Compare("*"))
1254 {
1255 sfx = -1;
1256 rolloff = &S_Rolloff;
1257 }
1258 else
1259 {
1260 sfx = S_FindSoundTentative(sc.String);
1261 rolloff = &S_sfx[sfx].Rolloff;
1262 }
1263 type = ROLLOFF_Doom;
1264 if (!sc.CheckFloat())
1265 {
1266 sc.MustGetString();
1267 if (sc.Compare("linear"))
1268 {
1269 rolloff->RolloffType = ROLLOFF_Linear;
1270 }
1271 else if (sc.Compare("log"))
1272 {
1273 rolloff->RolloffType = ROLLOFF_Log;
1274 }
1275 else if (sc.Compare("custom"))
1276 {
1277 rolloff->RolloffType = ROLLOFF_Custom;
1278 }
1279 else
1280 {
1281 sc.ScriptError("Unknown rolloff type '%s'", sc.String);
1282 }
1283 sc.MustGetFloat();
1284 }
1285 rolloff->MinDistance = (float)sc.Float;
1286 sc.MustGetFloat();
1287 rolloff->MaxDistance = (float)sc.Float;
1288 break;
1289 }
1290
1291 case SI_Random: {
1292 // $random <logical name> { <logical name> ... }
1293 FRandomSoundList random;
1294
1295 list.Clear ();
1296 sc.MustGetString ();
1297 random.SfxHead = S_AddSound (sc.String, -1, &sc);
1298 sc.MustGetStringName ("{");
1299 while (sc.GetString () && !sc.Compare ("}"))
1300 {
1301 WORD sfxto = S_FindSoundTentative (sc.String);
1302 if (sfxto == random.SfxHead)
1303 {
1304 Printf("Definition of random sound '%s' refers to itself recursively.", sc.String);
1305 continue;
1306 }
1307 list.Push (sfxto);
1308 }
1309 if (list.Size() == 1)
1310 { // Only one sound: treat as $alias
1311 S_sfx[random.SfxHead].link = list[0];
1312 S_sfx[random.SfxHead].NearLimit = -1;
1313 }
1314 else if (list.Size() > 1)
1315 { // Only add non-empty random lists
1316 random.NumSounds = (WORD)list.Size();
1317 S_sfx[random.SfxHead].link = (WORD)S_rnd.Push (random);
1318 S_sfx[random.SfxHead].bRandomHeader = true;
1319 S_rnd[S_sfx[random.SfxHead].link].Sounds = new WORD[random.NumSounds];
1320 memcpy (S_rnd[S_sfx[random.SfxHead].link].Sounds, &list[0], sizeof(WORD)*random.NumSounds);
1321 S_sfx[random.SfxHead].NearLimit = -1;
1322 }
1323 }
1324 break;
1325
1326 case SI_MusicVolume: {
1327 sc.MustGetString();
1328 FString musname (sc.String);
1329 sc.MustGetFloat();
1330 FMusicVolume *mv = (FMusicVolume *)M_Malloc (sizeof(*mv) + musname.Len());
1331 mv->Volume = (float)sc.Float;
1332 strcpy (mv->MusicName, musname);
1333 mv->Next = MusicVolumes;
1334 MusicVolumes = mv;
1335 }
1336 break;
1337
1338 case SI_MusicAlias: {
1339 sc.MustGetString();
1340 int lump = Wads.CheckNumForName(sc.String, ns_music);
1341 if (lump >= 0)
1342 {
1343 // do not set the alias if a later WAD defines its own music of this name
1344 int file = Wads.GetLumpFile(lump);
1345 int sndifile = Wads.GetLumpFile(sc.LumpNum);
1346 if (file > sndifile)
1347 {
1348 sc.MustGetString();
1349 continue;
1350 }
1351 }
1352 FName alias = sc.String;
1353 sc.MustGetString();
1354 FName mapped = sc.String;
1355
1356 // only set the alias if the lump it maps to exists.
1357 if (mapped == NAME_None || Wads.CheckNumForName(sc.String, ns_music) >= 0)
1358 {
1359 MusicAliases[alias] = mapped;
1360 }
1361 }
1362 break;
1363
1364 case SI_MidiDevice: {
1365 sc.MustGetString();
1366 FName nm = sc.String;
1367 FScanner::SavedPos save = sc.SavePos();
1368
1369 sc.SetCMode(true);
1370 sc.MustGetString();
1371 MidiDeviceSetting devset;
1372 if (sc.Compare("timidity")) devset.device = MDEV_TIMIDITY;
1373 else if (sc.Compare("fmod") || sc.Compare("sndsys")) devset.device = MDEV_SNDSYS;
1374 else if (sc.Compare("standard")) devset.device = MDEV_MMAPI;
1375 else if (sc.Compare("opl")) devset.device = MDEV_OPL;
1376 else if (sc.Compare("default")) devset.device = MDEV_DEFAULT;
1377 else if (sc.Compare("fluidsynth")) devset.device = MDEV_FLUIDSYNTH;
1378 else if (sc.Compare("gus")) devset.device = MDEV_GUS;
1379 else if (sc.Compare("wildmidi")) devset.device = MDEV_WILDMIDI;
1380 else sc.ScriptError("Unknown MIDI device %s\n", sc.String);
1381
1382 if (sc.CheckString(","))
1383 {
1384 sc.SetCMode(false);
1385 sc.MustGetString();
1386 devset.args = sc.String;
1387 }
1388 else
1389 {
1390 // This does not really do what one might expect, because the next token has already been parsed and can be a '$'.
1391 // So in order to continue parsing without C-Mode, we need to reset and parse the last token again.
1392 sc.SetCMode(false);
1393 sc.RestorePos(save);
1394 sc.MustGetString();
1395 }
1396 MidiDevices[nm] = devset;
1397 }
1398 break;
1399
1400 case SI_IfDoom: //also Chex
1401 case SI_IfStrife:
1402 case SI_IfHeretic:
1403 case SI_IfHexen:
1404 skipToEndIf = !CheckGame(sc.String+3, true);
1405 break;
1406 }
1407 }
1408 else
1409 { // Got a logical sound mapping
1410 FString name (sc.String);
1411 sc.MustGetString ();
1412 S_AddSound (name, sc.String, &sc);
1413 }
1414 }
1415 }
1416
1417 //==========================================================================
1418 //
1419 // S_AddBloodSFX
1420 //
1421 // Registers a new sound with the name "<lumpname>.sfx"
1422 // Actual sound data is searched for in the ns_bloodraw namespace.
1423 //
1424 //==========================================================================
1425
S_AddBloodSFX(int lumpnum)1426 static void S_AddBloodSFX (int lumpnum)
1427 {
1428 FMemLump sfxlump = Wads.ReadLump(lumpnum);
1429 const FBloodSFX *sfx = (FBloodSFX *)sfxlump.GetMem();
1430 int rawlump = Wads.CheckNumForName(sfx->RawName, ns_bloodraw);
1431 int sfxnum;
1432
1433 if (rawlump != -1)
1434 {
1435 const char *name = Wads.GetLumpFullName(lumpnum);
1436 sfxnum = S_AddSound(name, rawlump);
1437 if (sfx->Format < 5 || sfx->Format > 12)
1438 { // [0..4] + invalid formats
1439 S_sfx[sfxnum].RawRate = 11025;
1440 }
1441 else if (sfx->Format < 9)
1442 { // [5..8]
1443 S_sfx[sfxnum].RawRate = 22050;
1444 }
1445 else
1446 { // [9..12]
1447 S_sfx[sfxnum].RawRate = 44100;
1448 }
1449 S_sfx[sfxnum].bLoadRAW = true;
1450 S_sfx[sfxnum].LoopStart = LittleLong(sfx->LoopStart);
1451 // Make an ambient sound out of it, whether it has a loop point
1452 // defined or not. (Because none of the standard Blood ambient
1453 // sounds are explicitly defined as looping.)
1454 FAmbientSound *ambient = &Ambients[Wads.GetLumpIndexNum(lumpnum)];
1455 ambient->type = CONTINUOUS;
1456 ambient->periodmin = 0;
1457 ambient->periodmax = 0;
1458 ambient->volume = 1;
1459 ambient->attenuation = 1;
1460 ambient->sound = FSoundID(sfxnum);
1461 }
1462 }
1463
1464 //==========================================================================
1465 //
1466 // S_AddStrifeVoice
1467 //
1468 // Registers a new sound with the name "svox/<lumpname>"
1469 //
1470 //==========================================================================
1471
S_AddStrifeVoice(int lumpnum)1472 static void S_AddStrifeVoice (int lumpnum)
1473 {
1474 char name[16] = "svox/";
1475 Wads.GetLumpName (name+5, lumpnum);
1476 S_AddSound (name, lumpnum);
1477 }
1478
1479 //==========================================================================
1480 //
1481 // S_ParsePlayerSoundCommon
1482 //
1483 // Parses the common part of playersound commands in SNDINFO
1484 // (player class, gender, and ref id)
1485 //==========================================================================
1486
S_ParsePlayerSoundCommon(FScanner & sc,FString & pclass,int & gender,int & refid)1487 static void S_ParsePlayerSoundCommon (FScanner &sc, FString &pclass, int &gender, int &refid)
1488 {
1489 sc.MustGetString ();
1490 pclass = sc.String;
1491 sc.MustGetString ();
1492 gender = D_GenderToInt (sc.String);
1493 sc.MustGetString ();
1494 refid = S_FindSoundNoHash (sc.String);
1495 if (refid != 0 && !S_sfx[refid].bPlayerReserve && !S_sfx[refid].bTentative)
1496 {
1497 sc.ScriptError ("%s has already been used for a non-player sound.", sc.String);
1498 }
1499 if (refid == 0)
1500 {
1501 refid = S_AddSound (sc.String, -1, &sc);
1502 S_sfx[refid].bTentative = true;
1503 }
1504 if (S_sfx[refid].bTentative)
1505 {
1506 S_sfx[refid].link = NumPlayerReserves++;
1507 S_sfx[refid].bTentative = false;
1508 S_sfx[refid].bPlayerReserve = true;
1509 }
1510 sc.MustGetString ();
1511 }
1512
1513 //==========================================================================
1514 //
1515 // S_AddPlayerClass
1516 //
1517 // Adds the new player sound class if it doesn't exist. If it does, then
1518 // the existing class is returned.
1519 //==========================================================================
1520
S_AddPlayerClass(const char * name)1521 static int S_AddPlayerClass (const char *name)
1522 {
1523 int cnum = S_FindPlayerClass (name);
1524 if (cnum == -1)
1525 {
1526 FPlayerClassLookup lookup;
1527
1528 lookup.Name = name;
1529 lookup.ListIndex[2] = lookup.ListIndex[1] = lookup.ListIndex[0] = 0xffff;
1530 cnum = (int)PlayerClassLookups.Push (lookup);
1531 PlayerClassesIsSorted = false;
1532
1533 // The default player class is the first one added
1534 if (DefPlayerClassName.IsEmpty())
1535 {
1536 DefPlayerClassName = lookup.Name;
1537 DefPlayerClass = cnum;
1538 }
1539 }
1540 return cnum;
1541 }
1542
1543 //==========================================================================
1544 //
1545 // S_FindPlayerClass
1546 //
1547 // Finds the given player class. Returns -1 if not found.
1548 //==========================================================================
1549
S_FindPlayerClass(const char * name)1550 static int S_FindPlayerClass (const char *name)
1551 {
1552 if (!PlayerClassesIsSorted)
1553 {
1554 unsigned int i;
1555
1556 for (i = 0; i < PlayerClassLookups.Size(); ++i)
1557 {
1558 if (stricmp (name, PlayerClassLookups[i].Name) == 0)
1559 {
1560 return (int)i;
1561 }
1562 }
1563 }
1564 else
1565 {
1566 int min = 0;
1567 int max = (int)(PlayerClassLookups.Size() - 1);
1568
1569 while (min <= max)
1570 {
1571 int mid = (min + max) / 2;
1572 int lexx = stricmp (PlayerClassLookups[mid].Name, name);
1573 if (lexx == 0)
1574 {
1575 return mid;
1576 }
1577 else if (lexx < 0)
1578 {
1579 min = mid + 1;
1580 }
1581 else
1582 {
1583 max = mid - 1;
1584 }
1585 }
1586 }
1587 return -1;
1588 }
1589
1590 //==========================================================================
1591 //
1592 // S_AddPlayerGender
1593 //
1594 // Adds a list of sounds for the given class and gender or just returns
1595 // an existing list if one is present.
1596 //==========================================================================
1597
S_AddPlayerGender(int classnum,int gender)1598 static int S_AddPlayerGender (int classnum, int gender)
1599 {
1600 unsigned int index;
1601
1602 index = PlayerClassLookups[classnum].ListIndex[gender];
1603 if (index == 0xffff)
1604 {
1605 index = PlayerSounds.Reserve (1);
1606 PlayerClassLookups[classnum].ListIndex[gender] = (WORD)index;
1607 }
1608 return index;
1609 }
1610
1611 //==========================================================================
1612 //
1613 // S_ShrinkPlayerSoundLists
1614 //
1615 // Shrinks the arrays used by the player sounds to be just large enough
1616 // and also sorts the PlayerClassLookups array.
1617 //==========================================================================
1618
S_ShrinkPlayerSoundLists()1619 void S_ShrinkPlayerSoundLists ()
1620 {
1621 PlayerSounds.ShrinkToFit ();
1622 PlayerClassLookups.ShrinkToFit ();
1623
1624 qsort (&PlayerClassLookups[0], PlayerClassLookups.Size(),
1625 sizeof(FPlayerClassLookup), SortPlayerClasses);
1626 PlayerClassesIsSorted = true;
1627 DefPlayerClass = S_FindPlayerClass (DefPlayerClassName);
1628 }
1629
SortPlayerClasses(const void * a,const void * b)1630 static int STACK_ARGS SortPlayerClasses (const void *a, const void *b)
1631 {
1632 return stricmp (((const FPlayerClassLookup *)a)->Name,
1633 ((const FPlayerClassLookup *)b)->Name);
1634 }
1635
1636 //==========================================================================
1637 //
1638 // S_LookupPlayerSound
1639 //
1640 // Returns the sound for the given player class, gender, and sound name.
1641 //==========================================================================
1642
S_LookupPlayerSound(const char * pclass,int gender,const char * name)1643 int S_LookupPlayerSound (const char *pclass, int gender, const char *name)
1644 {
1645 int refid = S_FindSound (name);
1646 if (refid != 0)
1647 {
1648 refid = S_LookupPlayerSound (pclass, gender, refid);
1649 }
1650 return refid;
1651 }
1652
S_LookupPlayerSound(const char * pclass,int gender,FSoundID refid)1653 int S_LookupPlayerSound (const char *pclass, int gender, FSoundID refid)
1654 {
1655 if (!S_sfx[refid].bPlayerReserve)
1656 { // Not a player sound, so just return this sound
1657 return refid;
1658 }
1659
1660 return S_LookupPlayerSound (S_FindPlayerClass (pclass), gender, refid);
1661 }
1662
S_LookupPlayerSound(int classidx,int gender,FSoundID refid)1663 static int S_LookupPlayerSound (int classidx, int gender, FSoundID refid)
1664 {
1665 int ingender = gender;
1666
1667 if (classidx == -1)
1668 {
1669 classidx = DefPlayerClass;
1670 }
1671
1672 int listidx = PlayerClassLookups[classidx].ListIndex[gender];
1673 if (listidx == 0xffff)
1674 {
1675 int g;
1676
1677 for (g = 0; g < 3 && listidx == 0xffff; ++g)
1678 {
1679 listidx = PlayerClassLookups[classidx].ListIndex[g];
1680 }
1681 if (g == 3)
1682 { // No sounds defined at all for this class (can this happen?)
1683 if (classidx != DefPlayerClass)
1684 {
1685 return S_LookupPlayerSound (DefPlayerClass, gender, refid);
1686 }
1687 return 0;
1688 }
1689 gender = g;
1690 }
1691
1692 int sndnum = PlayerSounds[listidx].LookupSound (S_sfx[refid].link);
1693
1694 // If we're not done parsing SNDINFO yet, assume that the target sound is valid
1695 if (PlayerClassesIsSorted &&
1696 (sndnum == 0 ||
1697 ((S_sfx[sndnum].lumpnum == -1 || S_sfx[sndnum].lumpnum == sfx_empty) &&
1698 S_sfx[sndnum].link == sfxinfo_t::NO_LINK &&
1699 !S_sfx[sndnum].bPlayerSilent)))
1700 { // This sound is unavailable.
1701 if (ingender != 0)
1702 { // Try "male"
1703 return S_LookupPlayerSound (classidx, 0, refid);
1704 }
1705 if (classidx != DefPlayerClass)
1706 { // Try the default class.
1707 return S_LookupPlayerSound (DefPlayerClass, gender, refid);
1708 }
1709 }
1710 return sndnum;
1711 }
1712
1713
1714 //==========================================================================
1715 //
1716 // S_SavePlayerSound / S_RestorePlayerSounds
1717 //
1718 // Restores all skin-based player sounds after changing the local SNDINFO
1719 // which forces a reload of the global one as well
1720 //
1721 //==========================================================================
1722
S_SavePlayerSound(const char * pclass,int gender,int refid,int lumpnum,bool alias)1723 static void S_SavePlayerSound (const char *pclass, int gender, int refid, int lumpnum, bool alias)
1724 {
1725 FSavedPlayerSoundInfo spi;
1726
1727 spi.pclass = pclass;
1728 spi.gender = gender;
1729 spi.refid = refid;
1730 spi.lumpnum = lumpnum;
1731 spi.alias = alias;
1732 SavedPlayerSounds.Push(spi);
1733 }
1734
S_RestorePlayerSounds()1735 static void S_RestorePlayerSounds()
1736 {
1737 for(unsigned int i = 0; i < SavedPlayerSounds.Size(); i++)
1738 {
1739 FSavedPlayerSoundInfo * spi = &SavedPlayerSounds[i];
1740 if (spi->alias)
1741 {
1742 S_AddPlayerSoundExisting(spi->pclass, spi->gender, spi->refid, spi->lumpnum);
1743 }
1744 else
1745 {
1746 S_AddPlayerSound(spi->pclass, spi->gender, spi->refid, spi->lumpnum);
1747 }
1748 }
1749 }
1750
1751 //==========================================================================
1752 //
1753 // S_AreSoundsEquivalent
1754 //
1755 // Returns true if two sounds are essentially the same thing
1756 //==========================================================================
1757
S_AreSoundsEquivalent(AActor * actor,const char * name1,const char * name2)1758 bool S_AreSoundsEquivalent (AActor *actor, const char *name1, const char *name2)
1759 {
1760 return S_AreSoundsEquivalent (actor, S_FindSound (name1), S_FindSound (name2));
1761 }
1762
S_AreSoundsEquivalent(AActor * actor,int id1,int id2)1763 bool S_AreSoundsEquivalent (AActor *actor, int id1, int id2)
1764 {
1765 sfxinfo_t *sfx;
1766
1767 if (id1 == id2)
1768 {
1769 return true;
1770 }
1771 if (id1 == 0 || id2 == 0)
1772 {
1773 return false;
1774 }
1775 // Dereference aliases, but not random or player sounds
1776 while ((sfx = &S_sfx[id1])->link != sfxinfo_t::NO_LINK)
1777 {
1778 if (sfx->bPlayerReserve)
1779 {
1780 id1 = S_FindSkinnedSound (actor, id1);
1781 }
1782 else if (sfx->bRandomHeader)
1783 {
1784 break;
1785 }
1786 else
1787 {
1788 id1 = sfx->link;
1789 }
1790 }
1791 while ((sfx = &S_sfx[id2])->link != sfxinfo_t::NO_LINK)
1792 {
1793 if (sfx->bPlayerReserve)
1794 {
1795 id2 = S_FindSkinnedSound (actor, id2);
1796 }
1797 else if (sfx->bRandomHeader)
1798 {
1799 break;
1800 }
1801 else
1802 {
1803 id2 = sfx->link;
1804 }
1805 }
1806 return id1 == id2;
1807 }
1808
1809 //==========================================================================
1810 //
1811 // S_FindSkinnedSound
1812 //
1813 // Calls S_LookupPlayerSound, deducing the class and gender from actor.
1814 //==========================================================================
1815
S_FindSkinnedSound(AActor * actor,FSoundID refid)1816 int S_FindSkinnedSound (AActor *actor, FSoundID refid)
1817 {
1818 const char *pclass;
1819 int gender = GENDER_MALE;
1820
1821 if (actor != NULL && actor->IsKindOf(RUNTIME_CLASS(APlayerPawn)))
1822 {
1823 pclass = static_cast<APlayerPawn*>(actor)->GetSoundClass ();
1824 if (actor->player != NULL) gender = actor->player->userinfo.GetGender();
1825 }
1826 else
1827 {
1828 pclass = gameinfo.gametype == GAME_Hexen? "fighter" : "player";
1829 }
1830
1831 return S_LookupPlayerSound (pclass, gender, refid);
1832 }
1833
1834 //==========================================================================
1835 //
1836 // S_FindSkinnedSoundEx
1837 //
1838 // Tries looking for both "name-extendedname" and "name" in that order.
1839 //==========================================================================
1840
S_FindSkinnedSoundEx(AActor * actor,const char * name,const char * extendedname)1841 int S_FindSkinnedSoundEx (AActor *actor, const char *name, const char *extendedname)
1842 {
1843 FString fullname;
1844 FSoundID id;
1845
1846 // Look for "name-extendedname";
1847 fullname = name;
1848 fullname += '-';
1849 fullname += extendedname;
1850 id = fullname;
1851
1852 if (id == 0)
1853 { // Look for "name"
1854 id = name;
1855 }
1856 return S_FindSkinnedSound (actor, id);
1857 }
1858
1859 //==========================================================================
1860 //
1861 // S_ParseTimeTag
1862 //
1863 // Passed the value of a loop point tag, converts it to numbers.
1864 //
1865 // This may be of the form 00:00:00.00 (HH:MM:SS.ss) to specify by play
1866 // time. Various parts may be left off. The only requirement is that it
1867 // contain a colon. e.g. To start the loop at 20 seconds in, you can use
1868 // ":20", "0:20", "00:00:20", ":20.0", etc. Values after the decimal are
1869 // fractions of a second.
1870 //
1871 // If you don't include a colon but just have a raw number, then it's
1872 // the number of PCM samples at which to loop.
1873 //
1874 // Returns true if the tag made sense, false if not.
1875 //
1876 //==========================================================================
1877
S_ParseTimeTag(const char * tag,bool * as_samples,unsigned int * time)1878 bool S_ParseTimeTag(const char *tag, bool *as_samples, unsigned int *time)
1879 {
1880 const char *bit = tag;
1881 char ms[3] = { 0 };
1882 unsigned int times[3] = { 0 };
1883 int ms_pos = 0, time_pos = 0;
1884 bool pcm = true, in_ms = false;
1885
1886 for (bit = tag; *bit != '\0'; ++bit)
1887 {
1888 if (*bit >= '0' && *bit <= '9')
1889 {
1890 if (in_ms)
1891 {
1892 // Ignore anything past three fractional digits.
1893 if (ms_pos < 3)
1894 {
1895 ms[ms_pos++] = *bit - '0';
1896 }
1897 }
1898 else
1899 {
1900 times[time_pos] = times[time_pos] * 10 + *bit - '0';
1901 }
1902 }
1903 else if (*bit == ':')
1904 {
1905 if (in_ms)
1906 { // If we already specified milliseconds, we can't take any more parts.
1907 return false;
1908 }
1909 pcm = false;
1910 if (++time_pos == countof(times))
1911 { // Time too long. (Seriously, starting the loop days in?)
1912 return false;
1913 }
1914 }
1915 else if (*bit == '.')
1916 {
1917 if (pcm || in_ms)
1918 { // It doesn't make sense to have fractional PCM values.
1919 // It also doesn't make sense to have more than one dot.
1920 return false;
1921 }
1922 in_ms = true;
1923 }
1924 else
1925 { // Anything else: We don't understand this.
1926 return false;
1927 }
1928 }
1929 if (pcm)
1930 {
1931 *as_samples = true;
1932 *time = times[0];
1933 }
1934 else
1935 {
1936 unsigned int mytime = 0;
1937
1938 // Add in hours, minutes, and seconds
1939 for (int i = 0; i <= time_pos; ++i)
1940 {
1941 mytime = mytime * 60 + times[i];
1942 }
1943
1944 // Add in milliseconds
1945 mytime = mytime * 1000 + ms[0] * 100 + ms[1] * 10 + ms[2];
1946
1947 *as_samples = false;
1948 *time = mytime;
1949 }
1950 return true;
1951 }
1952
1953 //==========================================================================
1954 //
1955 // sfxinfo_t :: MarkUsed
1956 //
1957 // Marks this sound for precaching.
1958 //
1959 //==========================================================================
1960
MarkUsed()1961 void sfxinfo_t::MarkUsed()
1962 {
1963 bUsed = true;
1964 }
1965
1966 //==========================================================================
1967 //
1968 // S_MarkPlayerSounds
1969 //
1970 // Marks all sounds from a particular player class for precaching.
1971 //
1972 //==========================================================================
1973
S_MarkPlayerSounds(const char * playerclass)1974 void S_MarkPlayerSounds (const char *playerclass)
1975 {
1976 int classidx = S_FindPlayerClass(playerclass);
1977 if (classidx < 0)
1978 {
1979 classidx = DefPlayerClass;
1980 }
1981 for (int g = 0; g < 3; ++g)
1982 {
1983 int listidx = PlayerClassLookups[classidx].ListIndex[0];
1984 if (listidx != 0xffff)
1985 {
1986 PlayerSounds[listidx].MarkUsed();
1987 }
1988 }
1989 }
1990
1991 //==========================================================================
1992 //
1993 // CCMD soundlist
1994 //
1995 //==========================================================================
1996
CCMD(soundlist)1997 CCMD (soundlist)
1998 {
1999 char lumpname[9];
2000 unsigned int i;
2001
2002 lumpname[8] = 0;
2003 for (i = 0; i < S_sfx.Size (); i++)
2004 {
2005 const sfxinfo_t *sfx = &S_sfx[i];
2006 if (sfx->bRandomHeader)
2007 {
2008 Printf ("%3d. %s -> #%d {", i, sfx->name.GetChars(), sfx->link);
2009 const FRandomSoundList *list = &S_rnd[sfx->link];
2010 for (size_t j = 0; j < list->NumSounds; ++j)
2011 {
2012 Printf (" %s ", S_sfx[list->Sounds[j]].name.GetChars());
2013 }
2014 Printf ("}\n");
2015 }
2016 else if (sfx->bPlayerReserve)
2017 {
2018 Printf ("%3d. %s <<player sound %d>>\n", i, sfx->name.GetChars(), sfx->link);
2019 }
2020 else if (S_sfx[i].lumpnum != -1)
2021 {
2022 Wads.GetLumpName (lumpname, sfx->lumpnum);
2023 Printf ("%3d. %s (%s)\n", i, sfx->name.GetChars(), lumpname);
2024 }
2025 else if (S_sfx[i].link != sfxinfo_t::NO_LINK)
2026 {
2027 Printf ("%3d. %s -> %s\n", i, sfx->name.GetChars(), S_sfx[sfx->link].name.GetChars());
2028 }
2029 else
2030 {
2031 Printf ("%3d. %s **not present**\n", i, sfx->name.GetChars());
2032 }
2033 }
2034 }
2035
2036 //==========================================================================
2037 //
2038 // CCMD soundlinks
2039 //
2040 //==========================================================================
2041
CCMD(soundlinks)2042 CCMD (soundlinks)
2043 {
2044 unsigned int i;
2045
2046 for (i = 0; i < S_sfx.Size (); i++)
2047 {
2048 const sfxinfo_t *sfx = &S_sfx[i];
2049
2050 if (sfx->link != sfxinfo_t::NO_LINK &&
2051 !sfx->bRandomHeader &&
2052 !sfx->bPlayerReserve)
2053 {
2054 Printf ("%s -> %s\n", sfx->name.GetChars(), S_sfx[sfx->link].name.GetChars());
2055 }
2056 }
2057 }
2058
2059 //==========================================================================
2060 //
2061 // CCMD playersounds
2062 //
2063 //==========================================================================
2064
CCMD(playersounds)2065 CCMD (playersounds)
2066 {
2067 const char *reserveNames[256];
2068 unsigned int i;
2069 int j, k, l;
2070
2071 // Find names for the player sounds
2072 memset (reserveNames, 0, sizeof(reserveNames));
2073 for (i = j = 0; j < NumPlayerReserves && i < S_sfx.Size(); ++i)
2074 {
2075 if (S_sfx[i].bPlayerReserve)
2076 {
2077 ++j;
2078 reserveNames[S_sfx[i].link] = S_sfx[i].name;
2079 }
2080 }
2081
2082 for (i = 0; i < PlayerClassLookups.Size(); ++i)
2083 {
2084 for (j = 0; j < 3; ++j)
2085 {
2086 if ((l = PlayerClassLookups[i].ListIndex[j]) != 0xffff)
2087 {
2088 Printf ("\n%s, %s:\n", PlayerClassLookups[i].Name.GetChars(), GenderNames[j]);
2089 for (k = 0; k < NumPlayerReserves; ++k)
2090 {
2091 Printf (" %-16s%s\n", reserveNames[k], S_sfx[PlayerSounds[l].LookupSound (k)].name.GetChars());
2092 }
2093 }
2094 }
2095 }
2096 }
2097
2098 // AAmbientSound implementation ---------------------------------------------
2099
2100 class AAmbientSound : public AActor
2101 {
2102 DECLARE_CLASS (AAmbientSound, AActor)
2103 public:
2104 void Serialize (FArchive &arc);
2105
2106 void MarkPrecacheSounds () const;
2107 void BeginPlay ();
2108 void Tick ();
2109 void Activate (AActor *activator);
2110 void Deactivate (AActor *activator);
2111
2112 protected:
2113 bool bActive;
2114 private:
2115 void SetTicker (struct FAmbientSound *ambient);
2116 int NextCheck;
2117 };
2118
IMPLEMENT_CLASS(AAmbientSound)2119 IMPLEMENT_CLASS (AAmbientSound)
2120
2121 //==========================================================================
2122 //
2123 // AmbientSound :: Serialize
2124 //
2125 //==========================================================================
2126
2127 void AAmbientSound::Serialize (FArchive &arc)
2128 {
2129 Super::Serialize (arc);
2130 arc << bActive << NextCheck;
2131 }
2132
2133 //==========================================================================
2134 //
2135 // AmbientSound :: MarkPrecacheSounds
2136 //
2137 //==========================================================================
2138
MarkPrecacheSounds() const2139 void AAmbientSound::MarkPrecacheSounds() const
2140 {
2141 Super::MarkPrecacheSounds();
2142 FAmbientSound *ambient = Ambients.CheckKey(args[0]);
2143 if (ambient != NULL)
2144 {
2145 ambient->sound.MarkUsed();
2146 }
2147 }
2148
2149 //==========================================================================
2150 //
2151 // AmbientSound :: Tick
2152 //
2153 //==========================================================================
2154
Tick()2155 void AAmbientSound::Tick ()
2156 {
2157 Super::Tick ();
2158
2159 if (!bActive || level.maptime < NextCheck)
2160 return;
2161
2162 FAmbientSound *ambient;
2163 int loop = 0;
2164
2165 ambient = Ambients.CheckKey(args[0]);
2166 if (ambient == NULL)
2167 {
2168 return;
2169 }
2170
2171 if ((ambient->type & CONTINUOUS) == CONTINUOUS)
2172 {
2173 loop = CHAN_LOOP;
2174 }
2175
2176 if (ambient->sound != 0)
2177 {
2178 // The second argument scales the ambient sound's volume.
2179 // 0 and 100 are normal volume. The maximum volume level
2180 // possible is always 1.
2181 float volscale = args[1] == 0 ? 1 : args[1] / 100.f;
2182 float usevol = clamp(ambient->volume * volscale, 0.f, 1.f);
2183
2184 // The third argument is the minimum distance for audible fading, and
2185 // the fourth argument is the maximum distance for audibility. Setting
2186 // either of these to 0 or setting min distance > max distance will
2187 // use the standard rolloff.
2188 if ((args[2] | args[3]) == 0 || args[2] > args[3])
2189 {
2190 S_Sound(this, CHAN_BODY | loop, ambient->sound, usevol, ambient->attenuation);
2191 }
2192 else
2193 {
2194 float min = float(args[2]), max = float(args[3]);
2195 // The fifth argument acts as a scalar for the preceding two, if it's non-zero.
2196 if (args[4] > 0)
2197 {
2198 min *= args[4];
2199 max *= args[4];
2200 }
2201 S_SoundMinMaxDist(this, CHAN_BODY | loop, ambient->sound, usevol, min, max);
2202 }
2203 if (!loop)
2204 {
2205 SetTicker (ambient);
2206 }
2207 else
2208 {
2209 NextCheck = INT_MAX;
2210 }
2211 }
2212 else
2213 {
2214 Destroy ();
2215 }
2216 }
2217
2218 //==========================================================================
2219 //
2220 // AmbientSound :: SetTicker
2221 //
2222 //==========================================================================
2223
SetTicker(struct FAmbientSound * ambient)2224 void AAmbientSound::SetTicker (struct FAmbientSound *ambient)
2225 {
2226 if ((ambient->type & CONTINUOUS) == CONTINUOUS)
2227 {
2228 NextCheck += 1;
2229 }
2230 else if (ambient->type & RANDOM)
2231 {
2232 NextCheck += (int)(((float)rand() / (float)RAND_MAX) *
2233 (float)(ambient->periodmax - ambient->periodmin)) +
2234 ambient->periodmin;
2235 }
2236 else
2237 {
2238 NextCheck += ambient->periodmin;
2239 }
2240 }
2241
2242 //==========================================================================
2243 //
2244 // AmbientSound :: BeginPlay
2245 //
2246 //==========================================================================
2247
BeginPlay()2248 void AAmbientSound::BeginPlay ()
2249 {
2250 Super::BeginPlay ();
2251 Activate (NULL);
2252 }
2253
2254 //==========================================================================
2255 //
2256 // AmbientSound :: Activate
2257 //
2258 // Starts playing a sound (or does nothing if the sound is already playing).
2259 //
2260 //==========================================================================
2261
Activate(AActor * activator)2262 void AAmbientSound::Activate (AActor *activator)
2263 {
2264 Super::Activate (activator);
2265
2266 FAmbientSound *amb = Ambients.CheckKey(args[0]);
2267
2268 if (amb == NULL)
2269 {
2270 Destroy ();
2271 return;
2272 }
2273
2274 if (!bActive)
2275 {
2276 if ((amb->type & 3) == 0 && amb->periodmin == 0)
2277 {
2278 int sndnum = S_FindSound(amb->sound);
2279 if (sndnum == 0)
2280 {
2281 Destroy ();
2282 return;
2283 }
2284 amb->periodmin = Scale(S_GetMSLength(sndnum), TICRATE, 1000);
2285 }
2286
2287 NextCheck = level.maptime;
2288 if (amb->type & (RANDOM|PERIODIC))
2289 SetTicker (amb);
2290
2291 bActive = true;
2292 }
2293 }
2294
2295 //==========================================================================
2296 //
2297 // AmbientSound :: Deactivate
2298 //
2299 // Stops playing CONTINUOUS sounds immediately. Also prevents further
2300 // occurrences of repeated sounds.
2301 //
2302 //==========================================================================
2303
Deactivate(AActor * activator)2304 void AAmbientSound::Deactivate (AActor *activator)
2305 {
2306 Super::Deactivate (activator);
2307 if (bActive)
2308 {
2309 bActive = false;
2310 FAmbientSound *ambient = Ambients.CheckKey(args[0]);
2311 if (ambient != NULL && (ambient->type & CONTINUOUS) == CONTINUOUS)
2312 {
2313 S_StopSound (this, CHAN_BODY);
2314 }
2315 }
2316 }
2317
2318
2319 //==========================================================================
2320 //
2321 // S_ParseMusInfo
2322 // Parses MUSINFO lump.
2323 //
2324 //==========================================================================
2325
S_ParseMusInfo()2326 void S_ParseMusInfo()
2327 {
2328 int lastlump = 0, lump;
2329
2330 while ((lump = Wads.FindLump ("MUSINFO", &lastlump)) != -1)
2331 {
2332 FScanner sc(lump);
2333
2334 while (sc.GetString())
2335 {
2336 level_info_t *map = FindLevelInfo(sc.String);
2337
2338 if (map == NULL)
2339 {
2340 // Don't abort for invalid maps
2341 sc.ScriptMessage("Unknown map '%s'", sc.String);
2342 }
2343 while (sc.CheckNumber())
2344 {
2345 int index = sc.Number;
2346 sc.MustGetString();
2347 if (index > 0)
2348 {
2349 FName music = sc.String;
2350 if (map != NULL)
2351 {
2352 map->MusicMap[index] = music;
2353 }
2354 }
2355 }
2356 }
2357 }
2358 }
2359
2360
2361 //==========================================================================
2362 //
2363 // Music changer. Uses the sector action class to do its job
2364 //
2365 //==========================================================================
2366
2367 class AMusicChanger : public ASectorAction
2368 {
2369 DECLARE_CLASS (AMusicChanger, ASectorAction)
2370 public:
2371 virtual bool DoTriggerAction (AActor *triggerer, int activationType);
2372 virtual void PostBeginPlay();
2373 };
2374
IMPLEMENT_CLASS(AMusicChanger)2375 IMPLEMENT_CLASS(AMusicChanger)
2376
2377 bool AMusicChanger::DoTriggerAction (AActor *triggerer, int activationType)
2378 {
2379 if (activationType & SECSPAC_Enter && triggerer->player != NULL)
2380 {
2381 if (triggerer->player->MUSINFOactor != this)
2382 {
2383 triggerer->player->MUSINFOactor = this;
2384 triggerer->player->MUSINFOtics = 30;
2385 }
2386 }
2387 return Super::DoTriggerAction (triggerer, activationType);
2388 }
2389
PostBeginPlay()2390 void AMusicChanger::PostBeginPlay()
2391 {
2392 // The music changer should consider itself activated if the player
2393 // spawns in its sector as well as if it enters the sector during a P_TryMove.
2394 Super::PostBeginPlay();
2395 for (int i = 0; i < MAXPLAYERS; ++i)
2396 {
2397 if (playeringame[i] && players[i].mo && players[i].mo->Sector == this->Sector)
2398 {
2399 TriggerAction(players[i].mo, SECSPAC_Enter);
2400 }
2401 }
2402 }
2403