Oolite 1.91.0.7646-241128-10e222e
Loading...
Searching...
No Matches
PlayerEntitySound.m
Go to the documentation of this file.
1/*
2
3PlayerEntitySound.m
4
5Oolite
6Copyright (C) 2004-2013 Giles C Williams and contributors
7
8This program is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public License
10as published by the Free Software Foundation; either version 2
11of the License, or (at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program; if not, write to the Free Software
20Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21MA 02110-1301, USA.
22
23*/
24
25#import "PlayerEntitySound.h"
26#import "OOSound.h"
27#import "ResourceManager.h"
28#import "Universe.h"
29#import "OOSoundSourcePool.h"
30#import "OOMaths.h"
31#import "OOEquipmentType.h"
32
33
34// Sizes of sound source pools
35enum
36{
42};
43
44
55
56static NSDictionary *weaponShotMiss;
57static NSDictionary *weaponShotHit;
58static NSDictionary *weaponShieldHit;
59static NSDictionary *weaponUnshieldedHit;
60static NSDictionary *weaponLaunched;
61
62static const Vector kInterfaceBeepPosition = { 0.0f, -0.2f, 0.5f };
63static const Vector kInterfaceWarningPosition = { 0.0f, -0.2f, 0.4f };
64static const Vector kBreakPatternPosition = { 0.0f, 0.0f, 1.0f };
65static const Vector kEcmPosition = { 0.2f, 0.6f, -0.1f };
66static const Vector kWitchspacePosition = { 0.0f, -0.3f, -0.3f };
67// maybe these should actually track engine positions
68static const Vector kAfterburner1Position = { -0.1f, 0.0f, -1.0f };
69static const Vector kAfterburner2Position = { 0.1f, 0.0f, -1.0f };
70
71@implementation PlayerEntity (Sound)
72
73- (void) setUpSound
74{
75 [self destroySound];
76
77 sInterfaceBeepSource = [[OOSoundSource alloc] init];
78 [sInterfaceBeepSource setPosition:kInterfaceBeepPosition];
79
80 sBreakPatternSource = [[OOSoundSource alloc] init];
81 [sBreakPatternSource setPosition:kBreakPatternPosition];
82
83 sEcmSource = [[OOSoundSource alloc] init];
84 [sEcmSource setPosition:kEcmPosition];
85
87 [sHyperspaceSoundSource setPosition:kWitchspacePosition];
88
89 sBuySellSourcePool = [[OOSoundSourcePool alloc] initWithCount:kBuySellSourcePoolSize minRepeatTime:0.0];
90 sWarningSoundPool = [[OOSoundSourcePool alloc] initWithCount:kWarningPoolSize minRepeatTime:0.0];
91 sWeaponSoundPool = [[OOSoundSourcePool alloc] initWithCount:kWeaponPoolSize minRepeatTime:0.0];
92 sDamageSoundPool = [[OOSoundSourcePool alloc] initWithCount:kDamagePoolSize minRepeatTime:0.1]; // Repeat time limit is to avoid playing a scrape sound every frame on glancing scrapes. This does limit the number of laser hits that can be played in a furrball, though; maybe lasers and scrapes should use different pools.
93 sMiscSoundPool = [[OOSoundSourcePool alloc] initWithCount:kMiscPoolSize minRepeatTime:0.0];
94
95 // Two sources with the same sound are used to simulate looping.
96 OOSound *afterburnerSound = [ResourceManager ooSoundNamed:@"afterburner1.ogg" inFolder:@"Sounds"];
97 sAfterburnerSources[0] = [[OOSoundSource alloc] initWithSound:afterburnerSound];
98 [sAfterburnerSources[0] setPosition:kAfterburner1Position];
99 sAfterburnerSources[1] = [[OOSoundSource alloc] initWithSound:afterburnerSound];
100 [sAfterburnerSources[1] setPosition:kAfterburner2Position];
101}
102
103
104// sets up the sound key dictionaries for all the available weapons/missiles/mines defined.
105- (void) setUpWeaponSounds
106{
107 NSArray *eqTypes = [OOEquipmentType allEquipmentTypes];
108 NSMutableDictionary *shotMissSounds = [NSMutableDictionary dictionary];
109 NSMutableDictionary *shotHitSounds = [NSMutableDictionary dictionary];
110 NSMutableDictionary *shieldHitSounds = [NSMutableDictionary dictionary];
111 NSMutableDictionary *unshieldedHitSounds = [NSMutableDictionary dictionary];
112 NSMutableDictionary *weaponLaunchedSounds = [NSMutableDictionary dictionary];
113 NSEnumerator *eqTypeEnum = nil;
114 OOEquipmentType *eqType = nil;
115
116 // special case: turrets aren't defined with a "EQ_WEAPON" prefix, and plasma shots don't have a matching equipment item,
117 // so add a unique entry here. this could be overridden if an OXP creates an equipment item with this key.
118 // plasma shots don't make a sound when fired, so we only need to provide for the hit player sound keys.
119 [shieldHitSounds setObject:@"[player-hit-by-weapon]" forKey:@"EQ_WEAPON_PLASMA_SHOT"];
120 [unshieldedHitSounds setObject:@"[player-direct-hit]" forKey:@"EQ_WEAPON_PLASMA_SHOT"];
121 // grab a local copy of the sound identifiers for weapons to make the process of looking up a sound ref as fast as possible
122 // but we must ensure that no nil values are used for setObject
123 #define OO_ASSIGN_SOUNDSTR_TO_SOUNDS(soundStr, sounds) do { \
124 fxString = [eqType soundStr]; \
125 if (!fxString) fxString = @""; \
126 [sounds setObject:fxString forKey:[eqType identifier]]; \
127 } while(0)
128
129 for (eqTypeEnum = [eqTypes objectEnumerator]; (eqType = [eqTypeEnum nextObject]); )
130 {
131 NSString *fxString = nil;
132 if ([[eqType identifier] hasPrefix:@"EQ_WEAPON"])
133 {
134 OO_ASSIGN_SOUNDSTR_TO_SOUNDS(fxShotMissName, shotMissSounds);
135 OO_ASSIGN_SOUNDSTR_TO_SOUNDS(fxShotHitName, shotHitSounds);
136 OO_ASSIGN_SOUNDSTR_TO_SOUNDS(fxShieldHitName, shieldHitSounds);
137 OO_ASSIGN_SOUNDSTR_TO_SOUNDS(fxUnshieldedHitName, unshieldedHitSounds);
138 }
139 if ([eqType isMissileOrMine])
140 {
141 OO_ASSIGN_SOUNDSTR_TO_SOUNDS(fxWeaponLaunchedName, weaponLaunchedSounds);
142 OO_ASSIGN_SOUNDSTR_TO_SOUNDS(fxShieldHitName, shieldHitSounds);
143 OO_ASSIGN_SOUNDSTR_TO_SOUNDS(fxUnshieldedHitName, unshieldedHitSounds);
144 }
145 }
146
152
153 weaponShotMiss = [[NSDictionary alloc] initWithDictionary:shotMissSounds];
154 weaponShotHit = [[NSDictionary alloc] initWithDictionary:shotHitSounds];
155 weaponShieldHit = [[NSDictionary alloc] initWithDictionary:shieldHitSounds];
156 weaponUnshieldedHit = [[NSDictionary alloc] initWithDictionary:unshieldedHitSounds];
157 weaponLaunched = [[NSDictionary alloc] initWithDictionary:weaponLaunchedSounds];
158}
159
160- (void) destroySound
161{
166
169
175
181}
182
183
184- (void) playInterfaceBeep:(NSString *)beepKey
185{
186#if OOLITE_WINDOWS
187 if ([self status] == STATUS_START_GAME) { return; }
188#endif
189 [sInterfaceBeepSource playSound:[OOSound soundWithCustomSoundKey:beepKey]];
190}
191
192
193- (BOOL) isBeeping
194{
195 return [sInterfaceBeepSource isPlaying];
196}
197
198
199- (void) boop
200{
201 [self playInterfaceBeep:@"[general-boop]"];
202}
203
204
205- (void) playIdentOn
206{
207 [self playInterfaceBeep:@"[ident-on]"];
208}
209
210
211- (void) playIdentOff
212{
213 [self playInterfaceBeep:@"[ident-off]"];
214}
215
216
217- (void) playIdentLockedOn
218{
219 [self playInterfaceBeep:@"[ident-locked-on]"];
220}
221
222
223- (void) playMissileArmed
224{
225 [self playInterfaceBeep:@"[missile-armed]"];
226}
227
228
229- (void) playMineArmed
230{
231 [self playInterfaceBeep:@"[mine-armed]"];
232}
233
234
235- (void) playMissileSafe
236{
237 [self playInterfaceBeep:@"[missile-safe]"];
238}
239
240
241- (void) playMissileLockedOn
242{
243 [self playInterfaceBeep:@"[missile-locked-on]"];
244}
245
246
248{
249 [self playInterfaceBeep:@"[next-equipment-selected]"];
250}
251
252
254{
255 [self playInterfaceBeep:@"[next-missile-selected]"];
256}
257
258
259- (void) playWeaponsOnline
260{
261 [self playInterfaceBeep:@"[weapons-online]"];
262}
263
264
265- (void) playWeaponsOffline
266{
267 [self playInterfaceBeep:@"[weapons-offline]"];
268}
269
270
272{
273 [self playInterfaceBeep:@"[cargo-jettisoned]"];
274}
275
276
277- (void) playAutopilotOn
278{
279 [self playInterfaceBeep:@"[autopilot-on]"];
280}
281
282
283- (void) playAutopilotOff
284{
285 // only if still alive
286 if (energy > 0.0)
287 {
288 [self playInterfaceBeep:@"[autopilot-off]"];
289 }
290}
291
292
294{
295 [self playInterfaceBeep:@"[autopilot-out-of-range]"];
296}
297
298
300{
301 [self playInterfaceBeep:@"[autopilot-cannot-dock-with-target]"];
302}
303
304
306{
307 [self playInterfaceBeep:@"[save-overwrite-yes]"];
308}
309
310
311- (void) playSaveOverwriteNo
312{
313 [self playInterfaceBeep:@"[save-overwrite-no]"];
314}
315
316
317- (void) playHoldFull
318{
319 [self playInterfaceBeep:@"[hold-full]"];
320}
321
322
323- (void) playJumpMassLocked
324{
325 [self playInterfaceBeep:@"[jump-mass-locked]"];
326}
327
328
329- (void) playTargetLost
330{
331 [self playInterfaceBeep:@"[target-lost]"];
332}
333
334
336{
337 [self playInterfaceBeep:@"[no-target-in-memory]"];
338}
339
340
341- (void) playTargetSwitched
342{
343 [self playInterfaceBeep:@"[target-switched]"];
344}
345
346
348{
349 [self playInterfaceBeep:@"[witch-no-target]"];
350}
351
352
354{
355 [self playInterfaceBeep:@"[witch-no-fuel]"];
356}
357
358
360{
361 [self playInterfaceBeep:@"[hyperspace-blocked]"];
362}
363
365{
366 [self playInterfaceBeep:@"[witch-too-far]"];
367}
368
370{
371 [self playInterfaceBeep:@"[cloaking-device-on]"];
372}
373
374
376{
377 [self playInterfaceBeep:@"[cloaking-device-off]"];
378}
379
380
382{
383 [self playInterfaceBeep:@"[menu-navigation-up]"];
384}
385
386
388{
389 [self playInterfaceBeep:@"[menu-navigation-down]"];
390}
391
392
394{
395 [self playInterfaceBeep:@"[menu-navigation-not]"];
396}
397
398
400{
401 [self playInterfaceBeep:@"[menu-next-page]"];
402}
403
404
405- (void) playMenuPageNext
406{
407 [self playInterfaceBeep:@"[menu-previous-page]"];
408}
409
410
412{
413 [self playInterfaceBeep:@"[dismissed-report-screen]"];
414}
415
416
418{
419 [self playInterfaceBeep:@"[dismissed-mission-screen]"];
420}
421
422
423- (void) playChangedOption
424{
425 [self playInterfaceBeep:@"[changed-option]"];
426}
427
428
429- (void) updateFuelScoopSoundWithInterval:(OOTimeDelta)delta_t
430{
431 static double scoopSoundPlayTime = 0.0;
432 scoopSoundPlayTime -= delta_t;
433 if (scoopSoundPlayTime < 0.0)
434 {
435 if(![sInterfaceBeepSource isPlaying])
436 {
437 /* TODO: this should use the scoop position, not the standard
438 * interface beep position */
439 [self playInterfaceBeep:@"[scoop]"];
440 scoopSoundPlayTime = 0.5;
441 }
442 else scoopSoundPlayTime = 0.0;
443 }
444 if (![self scoopOverride])
445 {
446 scoopSoundPlayTime = 0.0;
447 }
448}
449
450
451// time delay method for playing afterburner sounds
452// this overlaps two sounds each 2 seconds long, but with a 0.75s
453// crossfade
455{
456 static uint8_t which = 0;
457
458 if (!afterburner_engaged) // end the loop cycle
459 {
460 afterburnerSoundLooping = NO;
461 }
462
463 if (afterburnerSoundLooping)
464 {
465 [sAfterburnerSources[which] play];
466 which = !which;
467
468 [self performSelector:@selector(updateAfterburnerSound)
469 withObject:NULL
470 afterDelay:1.25]; // and swap sounds in 1.25s time
471 }
472}
473
474
476{
477 if (!afterburnerSoundLooping)
478 {
479 afterburnerSoundLooping = YES;
480 [self updateAfterburnerSound];
481 }
482}
483
484
486{
487 // Do nothing, stop is detected in updateAfterburnerSound
488}
489
490
492{
493 [self playInterfaceBeep:@"[cloaking-device-insufficent-energy]"];
494}
495
496
497- (void) playBuyCommodity
498{
499 [sBuySellSourcePool playSoundWithKey:@"[buy-commodity]"];
500}
501
502
503- (void) playBuyShip
504{
505 [sBuySellSourcePool playSoundWithKey:@"[buy-ship]"];
506}
507
508
509- (void) playSellCommodity
510{
511 [sBuySellSourcePool playSoundWithKey:@"[sell-commodity]"];
512}
513
514
516{
517 [sBuySellSourcePool playSoundWithKey:@"[could-not-buy-commodity]"];
518}
519
520
522{
523 [sBuySellSourcePool playSoundWithKey:@"[could-not-sell-commodity]"];
524}
525
526
527- (void) playCantBuyShip
528{
529 [sBuySellSourcePool playSoundWithKey:@"[could-not-buy-ship]"];
530}
531
532
534{
535 [sHyperspaceSoundSource playCustomSoundWithKey:@"[hyperspace-countdown-begun]"];
536}
537
538
540{
541 [sHyperspaceSoundSource playCustomSoundWithKey:@"[galactic-hyperspace-countdown-begun]"];
542}
543
544
546{
547 [sHyperspaceSoundSource playCustomSoundWithKey:@"[hyperspace-countdown-aborted]"];
548}
549
550
551- (void) playHitByECMSound
552{
553 if (![sEcmSource isPlaying]) [sEcmSource playCustomSoundWithKey:@"[player-hit-by-ecm]"];
554}
555
556
557- (void) playFiredECMSound
558{
559 if (![sEcmSource isPlaying]) [sEcmSource playCustomSoundWithKey:@"[player-fired-ecm]"];
560}
561
562
564{
565 [sBreakPatternSource playCustomSoundWithKey:@"[player-launch-from-station]"];
566}
567
568
569- (void) playDockWithStation
570{
571 [sBreakPatternSource playCustomSoundWithKey:@"[player-dock-with-station]"];
572}
573
574
575- (void) playExitWitchspace
576{
577 [sBreakPatternSource playCustomSoundWithKey:@"[player-exit-witchspace]"];
578}
579
580
581- (void) playHostileWarning
582{
583 [sWarningSoundPool playSoundWithKey:@"[hostile-warning]" priority:1 position:kInterfaceWarningPosition];
584}
585
586
588{
589 [sWarningSoundPool playSoundWithKey:@"[alert-condition-red]" priority:2 position:kInterfaceWarningPosition];
590}
591
592
593- (void) playIncomingMissile:(Vector)missileVector
594{
595 [sWarningSoundPool playSoundWithKey:@"[incoming-missile]" priority:3 position:missileVector];
596}
597
598
599- (void) playEnergyLow
600{
601 [sWarningSoundPool playSoundWithKey:@"[energy-low]" priority:0.5 position:kInterfaceWarningPosition];
602}
603
604
605- (void) playDockingDenied
606{
607 [sWarningSoundPool playSoundWithKey:@"[autopilot-denied]" priority:1 position:kInterfaceWarningPosition];
608}
609
610
612{
613 [sWarningSoundPool playSoundWithKey:@"[witchdrive-failure]" priority:1.5 position:kWitchspacePosition];
614}
615
616
618{
619 [sWarningSoundPool playSoundWithKey:@"[witchdrive-malfunction]" priority:1.5 position:kWitchspacePosition];
620}
621
622
624{
625 [sWarningSoundPool playSoundWithKey:@"[witch-blocked-by-@]" priority:1.3 position:kWitchspacePosition];
626}
627
628
630{
631 [sWarningSoundPool playSoundWithKey:@"[witch-too-far]" priority:1.3 position:kWitchspacePosition];
632}
633
634
636{
637 [sWarningSoundPool playSoundWithKey:@"[witch-no-fuel]" priority:1.3 position:kWitchspacePosition];
638}
639
640
641- (void) playFuelLeak
642{
643 [sWarningSoundPool playSoundWithKey:@"[fuel-leak]" priority:0.5 position:kWitchspacePosition];
644}
645
646
647- (void) playShieldHit:(Vector)attackVector weaponIdentifier:(NSString *)weaponIdentifier
648{
649 NSString *identifier = [weaponShieldHit objectForKey:weaponIdentifier];
650 if (!identifier) identifier = @"[player-hit-by-weapon]";
651 [sDamageSoundPool playSoundWithKey:identifier position:attackVector];
652}
653
654
655- (void) playDirectHit:(Vector)attackVector weaponIdentifier:(NSString *) weaponIdentifier
656{
657 NSString *identifier = [weaponUnshieldedHit objectForKey:weaponIdentifier];
658 if (!identifier) identifier = @"[player-direct-hit]";
659 [sDamageSoundPool playSoundWithKey:identifier position:attackVector];
660}
661
662
663- (void) playScrapeDamage:(Vector)attackVector
664{
665 [sDamageSoundPool playSoundWithKey:@"[player-scrape-damage]" position:attackVector];
666}
667
668
669- (void) playLaserHit:(BOOL)hit offset:(Vector)weaponOffset weaponIdentifier:(NSString *)weaponIdentifier
670{
671 NSString *identifier = nil;
672 if (hit)
673 {
674 identifier = [weaponShotHit objectForKey:weaponIdentifier];
675 if (!identifier) identifier = @"[player-laser-hit]";
676 [sWeaponSoundPool playSoundWithKey:identifier priority:1.0 expiryTime:0.05 overlap:YES position:weaponOffset];
677 }
678 else
679 {
680 identifier = [weaponShotMiss objectForKey:weaponIdentifier];
681 if (!identifier) identifier = @"[player-laser-miss]";
682 [sWeaponSoundPool playSoundWithKey:identifier priority:1.0 expiryTime:0.05 overlap:YES position:weaponOffset];
683
684 }
685}
686
687
688- (void) playWeaponOverheated:(Vector)weaponOffset
689{
690 [sWeaponSoundPool playSoundWithKey:@"[weapon-overheat]" overlap:NO position:weaponOffset];
691}
692
693
694- (void) playMissileLaunched:(Vector)weaponOffset weaponIdentifier:(NSString *)weaponIdentifier
695{
696 NSString *identifier = [weaponLaunched objectForKey:weaponIdentifier];
697 if (!identifier) identifier = @"[missile_launched]";
698 [sWeaponSoundPool playSoundWithKey:identifier position:weaponOffset];
699}
700
701
702- (void) playMineLaunched:(Vector)weaponOffset weaponIdentifier:(NSString *)weaponIdentifier
703{
704 NSString *identifier = [weaponLaunched objectForKey:weaponIdentifier];
705 if (!identifier) identifier = @"[mine_launched]";
706 [sWeaponSoundPool playSoundWithKey:identifier position:weaponOffset];
707}
708
709
711{
712 [sMiscSoundPool playSoundWithKey:@"[escape-pod-scooped]" position:kInterfaceBeepPosition];
713}
714
715
717{
718 [sMiscSoundPool playSoundWithKey:@"[aegis-planet]" position:kInterfaceBeepPosition];
719}
720
721
723{
724 [sMiscSoundPool playSoundWithKey:@"[aegis-station]" position:kInterfaceBeepPosition];
725}
726
727
728- (void) playGameOver
729{
730 [sMiscSoundPool playSoundWithKey:@"[game-over]"];
731}
732
733
734- (void) playLegacyScriptSound:(NSString *)key
735{
736 [sMiscSoundPool playSoundWithKey:key priority:1.1];
737}
738
739@end
#define DESTROY(x)
Definition OOCocoa.h:77
return nil
double OOTimeDelta
Definition OOTypes.h:224
static OOSoundSourcePool * sDamageSoundPool
static OOSoundSource * sEcmSource
static NSDictionary * weaponUnshieldedHit
static OOSoundSourcePool * sWeaponSoundPool
static OOSoundSource * sHyperspaceSoundSource
static OOSoundSourcePool * sBuySellSourcePool
static const Vector kAfterburner2Position
static const Vector kEcmPosition
static NSDictionary * weaponShotHit
@ kDamagePoolSize
@ kMiscPoolSize
@ kWeaponPoolSize
@ kBuySellSourcePoolSize
@ kWarningPoolSize
static const Vector kWitchspacePosition
static OOSoundSource * sBreakPatternSource
static OOSoundSource * sAfterburnerSources[2]
static NSDictionary * weaponLaunched
static const Vector kInterfaceWarningPosition
static const Vector kBreakPatternPosition
static OOSoundSource * sInterfaceBeepSource
#define OO_ASSIGN_SOUNDSTR_TO_SOUNDS(soundStr, sounds)
static OOSoundSourcePool * sMiscSoundPool
static OOSoundSourcePool * sWarningSoundPool
static const Vector kInterfaceBeepPosition
static const Vector kAfterburner1Position
static NSDictionary * weaponShieldHit
static NSDictionary * weaponShotMiss
NSArray * allEquipmentTypes()
void playSoundWithKey:priority:expiryTime:overlap:position:(NSString *key,[priority] float priority,[expiryTime] OOTimeDelta expiryTime,[overlap] BOOL overlap,[position] Vector position)
void playSoundWithKey:position:(NSString *key,[position] Vector position)
void playSoundWithKey:priority:position:(NSString *key,[priority] float priority,[position] Vector position)
void playSoundWithKey:(NSString *key)
void playSoundWithKey:overlap:position:(NSString *key,[overlap] BOOL overlap,[position] Vector position)
void playSoundWithKey:priority:(NSString *key,[priority] float priority)
void playCustomSoundWithKey:(NSString *key)
Definition Universe.m:11096
void playSound:(OOSound *inSound)
void setPosition:(Vector inPosition)
id soundWithCustomSoundKey:(NSString *key)
Definition Universe.m:11055
OOSound * ooSoundNamed:inFolder:(NSString *fileName,[inFolder] NSString *folderName)