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