Prevent entering “/”, “\” and “:” in team and scheme names.
The name of teams and schems is saved in the file name itself, so these characters would cause trouble as they are used in path names in Linux and Windows.
(*
* Hedgewars, a free turn based strategy game
* Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*)
{$INCLUDE "options.inc"}
unit uSound;
(*
* This unit controls the sounds and music of the game.
* Doesn't really do anything if isSoundEnabled = false and isMusicEnabled = false
*
* There are three basic types of sound controls:
* Music - The background music of the game:
* * will only be played if isMusicEnabled = true
* * can be started, changed, paused and resumed
* Sound - Can be started and stopped
* Looped Sound - Subtype of sound: plays in a loop using a
* "channel", of which the id is returned on start.
* The channel id can be used to stop a specific sound loop.
*)
interface
uses SDLh, uConsts, uTypes;
procedure preInitModule;
procedure initModule;
procedure freeModule;
procedure InitSound; // Initiates sound-system if isSoundEnabled.
procedure ReleaseSound(complete: boolean); // Releases sound-system and used resources.
procedure ResetSound; // Reset sound state to the previous state.
procedure SetSound(enabled: boolean); // Enable/disable sound-system and backup status.
procedure SetAudioDampen(enabled: boolean); // Enable/disable automatic dampening if losing window focus.
// MUSIC
// Obvious music commands for music track
procedure SetMusic(enabled: boolean); // Enable/disable music.
procedure SetMusicName(musicname: shortstring); // Set name of the file to play.
procedure PlayMusic; // Play music from the start.
procedure PauseMusic; // Pause music.
procedure ResumeMusic; // Resume music from pause point.
procedure ChangeMusic(musicname: shortstring); // Replaces music track with musicname and plays it.
procedure StopMusic; // Stops and releases the current track.
// SOUNDS
// Plays the sound snd [from a given voicepack],
// if keepPlaying is given and true,
// then the sound's playback won't be interrupted if asked to play again.
// Returns true if sound was found and is played, false otherwise.
function PlaySound(snd: TSound): boolean;
function PlaySound(snd: TSound; keepPlaying: boolean): boolean;
function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean): boolean;
function PlaySound(snd: TSound; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
function PlaySoundV(snd: TSound; voicepack: PVoicepack): boolean;
function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean): boolean;
function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask: boolean): boolean;
function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
// Plays/stops a sound to replace the main background music temporarily.
procedure PlayMusicSound(snd: TSound);
procedure StopMusicSound(snd: TSound);
// Plays sound snd [of voicepack] in a loop, but starts with fadems milliseconds of fade-in.
// Returns sound channel of the looped sound.
function LoopSound(snd: TSound): LongInt;
function LoopSound(snd: TSound; fadems: LongInt): LongInt;
function LoopSoundV(snd: TSound; voicepack: PVoicepack): LongInt;
function LoopSoundV(snd: TSound; voicepack: PVoicepack; fadems: LongInt): LongInt;
// Stops the normal/looped sound of the given type/in the given channel
// [with a fade-out effect for fadems milliseconds].
procedure StopSound(snd: TSound);
procedure StopSound(snd: TSound; soundAsMusic: boolean);
procedure StopSoundChan(chn: LongInt);
procedure StopSoundChan(chn, fadems: LongInt);
// Add voice to the voice queue
procedure AddVoice(snd: TSound; voicepack: PVoicepack);
procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask, isFallback: boolean);
// Actually play next voice
procedure PlayNextVoice;
// GLOBAL FUNCTIONS
// Drastically lower the volume when we lose focus (and restore the previous value).
procedure DampenAudio;
procedure UndampenAudio;
// Mute/Unmute audio
procedure MuteAudio;
// MISC
// Set the initial volume
procedure SetVolume(vol: LongInt);
// Modifies the sound volume of the game by voldelta and returns the new volume level.
function ChangeVolume(voldelta: LongInt): LongInt;
// Returns the current volume in percent. Intended for display on UI.
function GetVolumePercent(): LongInt;
// Returns a pointer to the voicepack with the given name.
function AskForVoicepack(name: shortstring): Pointer;
var MusicFN: shortstring; // music file name
SDMusicFN: shortstring; // SD music file name
FallbackMusicFN: shortstring; // fallback music file name
FallbackSDMusicFN: shortstring; // fallback SD music fille name
var Volume: LongInt;
SoundTimerTicks: Longword;
LastVoiceFailed: boolean;
implementation
uses uVariables, uConsole, uCommands, uDebug, uPhysFSLayer;
const chanTPU = 32;
var cInitVolume: LongInt;
previousVolume: LongInt; // cached volume value
lastChan: array [TSound] of LongInt;
voicepacks: array[0..cMaxTeams] of TVoicepack;
defVoicepack: PVoicepack;
Mus: PMixMusic; // music pointer
isMusicEnabled: boolean;
isSoundEnabled: boolean;
isAutoDampening: boolean;
isSEBackup: boolean;
VoiceList : array[0..7] of TVoice = (
( snd: sndNone; voicepack: nil; isFallback: false),
( snd: sndNone; voicepack: nil; isFallback: false),
( snd: sndNone; voicepack: nil; isFallback: false),
( snd: sndNone; voicepack: nil; isFallback: false),
( snd: sndNone; voicepack: nil; isFallback: false),
( snd: sndNone; voicepack: nil; isFallback: false),
( snd: sndNone; voicepack: nil; isFallback: false),
( snd: sndNone; voicepack: nil; isFallback: false));
Soundz: array[TSound] of record
FileName: string[31];
Path, AltPath : TPathType;
end = (
(FileName: ''; Path: ptNone; AltPath: ptNone),// sndNone
(FileName: 'grenadeimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndGrenadeImpact
(FileName: 'explosion.ogg'; Path: ptSounds; AltPath: ptNone),// sndExplosion
(FileName: 'throwpowerup.ogg'; Path: ptSounds; AltPath: ptNone),// sndThrowPowerUp
(FileName: 'throwrelease.ogg'; Path: ptSounds; AltPath: ptNone),// sndThrowRelease
(FileName: 'splash.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndSplash
(FileName: 'shotgunreload.ogg'; Path: ptSounds; AltPath: ptNone),// sndShotgunReload
(FileName: 'shotgunfire.ogg'; Path: ptSounds; AltPath: ptNone),// sndShotgunFire
(FileName: 'graveimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndGraveImpact
(FileName: 'mineimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndMineImpact
(FileName: 'minetick.ogg'; Path: ptSounds; AltPath: ptNone),// sndMineTicks
// TODO: New mudball sound?
(FileName: 'Droplet1.ogg'; Path: ptSounds; AltPath: ptNone),// sndMudballImpact
(FileName: 'pickhammer.ogg'; Path: ptSounds; AltPath: ptNone),// sndPickhammer
(FileName: 'gun.ogg'; Path: ptSounds; AltPath: ptNone),// sndGun
(FileName: 'bee.ogg'; Path: ptSounds; AltPath: ptNone),// sndBee
(FileName: 'Jump1.ogg'; Path: ptVoices; AltPath: ptNone),// sndJump1
(FileName: 'Jump2.ogg'; Path: ptVoices; AltPath: ptNone),// sndJump2
(FileName: 'Jump3.ogg'; Path: ptVoices; AltPath: ptNone),// sndJump3
(FileName: 'Yessir.ogg'; Path: ptVoices; AltPath: ptNone),// sndYesSir
(FileName: 'Laugh.ogg'; Path: ptVoices; AltPath: ptNone),// sndLaugh
(FileName: 'Illgetyou.ogg'; Path: ptVoices; AltPath: ptNone),// sndIllGetYou
(FileName: 'Justyouwait.ogg'; Path: ptVoices; AltPath: ptNone),// sndJustyouwait
(FileName: 'Incoming.ogg'; Path: ptVoices; AltPath: ptNone),// sndIncoming
(FileName: 'Missed.ogg'; Path: ptVoices; AltPath: ptNone),// sndMissed
(FileName: 'Stupid.ogg'; Path: ptVoices; AltPath: ptNone),// sndStupid
(FileName: 'Firstblood.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirstBlood
(FileName: 'Boring.ogg'; Path: ptVoices; AltPath: ptNone),// sndBoring
(FileName: 'Byebye.ogg'; Path: ptVoices; AltPath: ptNone),// sndByeBye
(FileName: 'Sameteam.ogg'; Path: ptVoices; AltPath: ptNone),// sndSameTeam
(FileName: 'Nutter.ogg'; Path: ptVoices; AltPath: ptNone),// sndNutter
(FileName: 'Reinforcements.ogg'; Path: ptVoices; AltPath: ptNone),// sndReinforce
(FileName: 'Traitor.ogg'; Path: ptVoices; AltPath: ptNone),// sndTraitor
(FileName: 'Youllregretthat.ogg'; Path: ptVoices; AltPath: ptNone),// sndRegret
(FileName: 'Enemydown.ogg'; Path: ptVoices; AltPath: ptNone),// sndEnemyDown
(FileName: 'Coward.ogg'; Path: ptVoices; AltPath: ptNone),// sndCoward
(FileName: 'Hurry.ogg'; Path: ptVoices; AltPath: ptNone),// sndHurry
(FileName: 'Watchit.ogg'; Path: ptVoices; AltPath: ptNone),// sndWatchIt
(FileName: 'Kamikaze.ogg'; Path: ptVoices; AltPath: ptNone),// sndKamikaze
(FileName: 'cake2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCake
(FileName: 'Ow1.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw1
(FileName: 'Ow2.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw2
(FileName: 'Ow3.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw3
(FileName: 'Ow4.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw4
(FileName: 'Firepunch1.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch1
(FileName: 'Firepunch2.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch2
(FileName: 'Firepunch3.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch3
(FileName: 'Firepunch4.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch4
(FileName: 'Firepunch5.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch5
(FileName: 'Firepunch6.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch6
(FileName: 'Melon.ogg'; Path: ptVoices; AltPath: ptNone),// sndMelon
(FileName: 'Hellish.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellish
(FileName: 'Yoohoo.ogg'; Path: ptSounds; AltPath: ptNone),// sndYoohoo
(FileName: 'rcplane.ogg'; Path: ptSounds; AltPath: ptNone),// sndRCPlane
(FileName: 'whipcrack.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhipCrack
(FileName:'ride_of_the_valkyries.ogg'; Path: ptSounds; AltPath: ptNone),// sndRideOfTheValkyries
(FileName: 'denied.ogg'; Path: ptSounds; AltPath: ptNone),// sndDenied
(FileName: 'placed.ogg'; Path: ptSounds; AltPath: ptNone),// sndPlaced
(FileName: 'baseballbat.ogg'; Path: ptSounds; AltPath: ptNone),// sndBaseballBat
(FileName: 'steam.ogg'; Path: ptSounds; AltPath: ptNone),// sndVaporize
(FileName: 'warp.ogg'; Path: ptSounds; AltPath: ptNone),// sndWarp
(FileName: 'suddendeath.ogg'; Path: ptSounds; AltPath: ptNone),// sndSuddenDeath
(FileName: 'mortar.ogg'; Path: ptSounds; AltPath: ptNone),// sndMortar
(FileName: 'shutterclick.ogg'; Path: ptSounds; AltPath: ptNone),// sndShutter
(FileName: 'homerun.ogg'; Path: ptSounds; AltPath: ptNone),// sndHomerun
(FileName: 'molotov.ogg'; Path: ptSounds; AltPath: ptNone),// sndMolotov
(FileName: 'Takecover.ogg'; Path: ptVoices; AltPath: ptNone),// sndCover
(FileName: 'Uh-oh.ogg'; Path: ptVoices; AltPath: ptNone),// sndUhOh
(FileName: 'Oops.ogg'; Path: ptVoices; AltPath: ptNone),// sndOops
(FileName: 'Nooo.ogg'; Path: ptVoices; AltPath: ptNone),// sndNooo
(FileName: 'Hello.ogg'; Path: ptVoices; AltPath: ptNone),// sndHello
(FileName: 'ropeshot.ogg'; Path: ptSounds; AltPath: ptNone),// sndRopeShot
(FileName: 'ropeattach.ogg'; Path: ptSounds; AltPath: ptNone),// sndRopeAttach
(FileName: 'roperelease.ogg'; Path: ptSounds; AltPath: ptNone),// sndRopeRelease
(FileName: 'switchhog.ogg'; Path: ptSounds; AltPath: ptNone),// sndSwitchHog
(FileName: 'Victory.ogg'; Path: ptVoices; AltPath: ptNone),// sndVictory
(FileName: 'Flawless.ogg'; Path: ptVoices; AltPath: ptNone),// sndFlawless
(FileName: 'sniperreload.ogg'; Path: ptSounds; AltPath: ptNone),// sndSniperReload
(FileName: 'steps.ogg'; Path: ptSounds; AltPath: ptNone),// sndSteps
(FileName: 'lowgravity.ogg'; Path: ptSounds; AltPath: ptNone),// sndLowGravity
(FileName: 'hell_growl.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact1
(FileName: 'hell_ooff.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact2
(FileName: 'hell_ow.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact3
(FileName: 'hell_ugh.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact4
(FileName: 'melonimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndMelonImpact
(FileName: 'Droplet1.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet1
(FileName: 'Droplet2.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet2
(FileName: 'Droplet3.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet3
(FileName: 'egg.ogg'; Path: ptSounds; AltPath: ptNone),// sndEggBreak
(FileName: 'drillgun.ogg'; Path: ptSounds; AltPath: ptNone),// sndDrillRocket
(FileName: 'PoisonCough.ogg'; Path: ptVoices; AltPath: ptDefaultVoice),// sndPoisonCough
(FileName: 'PoisonMoan.ogg'; Path: ptVoices; AltPath: ptDefaultVoice),// sndPoisonMoan
(FileName: 'BirdyLay.ogg'; Path: ptSounds; AltPath: ptNone),// sndBirdyLay
(FileName: 'Whistle.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhistle
(FileName: 'beewater.ogg'; Path: ptSounds; AltPath: ptNone),// sndBeeWater
(FileName: '1C.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano0
(FileName: '2D.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano1
(FileName: '3E.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano2
(FileName: '4F.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano3
(FileName: '5G.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano4
(FileName: '6A.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano5
(FileName: '7B.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano6
(FileName: '8C.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano7
(FileName: '9D.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano8
(FileName: 'skip.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndSkip
(FileName: 'sinegun.ogg'; Path: ptSounds; AltPath: ptNone),// sndSineGun
(FileName: 'Ooff1.ogg'; Path: ptVoices; AltPath: ptNone),// sndOoff1
(FileName: 'Ooff2.ogg'; Path: ptVoices; AltPath: ptNone),// sndOoff2
(FileName: 'Ooff3.ogg'; Path: ptVoices; AltPath: ptNone),// sndOoff3
(FileName: 'hammer.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhack
(FileName: 'Comeonthen.ogg'; Path: ptVoices; AltPath: ptNone),// sndComeonthen
(FileName: 'parachute.ogg'; Path: ptSounds; AltPath: ptNone),// sndParachute
(FileName: 'bump.ogg'; Path: ptSounds; AltPath: ptNone),// sndBump
(FileName: 'hogchant3.ogg'; Path: ptSounds; AltPath: ptNone),// sndResurrector
(FileName: 'plane.ogg'; Path: ptSounds; AltPath: ptNone),// sndPlane
(FileName: 'TARDIS.ogg'; Path: ptSounds; AltPath: ptNone),// sndTardis
(FileName: 'frozen_hog_impact.ogg'; Path: ptSounds; AltPath: ptNone),// sndFrozenHogImpact
(FileName: 'ice_beam.ogg'; Path: ptSounds; AltPath: ptNone),// sndIceBeam
(FileName: 'hog_freeze.ogg'; Path: ptSounds; AltPath: ptNone), // sndHogFreeze
(FileName: 'airmine_impact.ogg'; Path: ptSounds; AltPath: ptNone),// sndAirMineImpact
(FileName: 'knife_impact.ogg'; Path: ptSounds; AltPath: ptNone),// sndKnifeImpact
(FileName: 'extratime.ogg'; Path: ptSounds; AltPath: ptNone),// sndExtraTime
(FileName: 'lasersight.ogg'; Path: ptSounds; AltPath: ptNone),// sndLaserSight
(FileName: 'invulnerable.ogg'; Path: ptSounds; AltPath: ptNone),// sndInvulnerable
(FileName: 'ufo.ogg'; Path: ptSounds; AltPath: ptNone),// sndJetpackLaunch
(FileName: 'jetpackboost.ogg'; Path: ptSounds; AltPath: ptNone),// sndJetpackBoost
(FileName: 'portalshot.ogg'; Path: ptSounds; AltPath: ptNone),// sndPortalShot
(FileName: 'portalswitch.ogg'; Path: ptSounds; AltPath: ptNone),// sndPortalSwitch
(FileName: 'portalopen.ogg'; Path: ptSounds; AltPath: ptNone),// sndPortalOpen
(FileName: 'blowtorch.ogg'; Path: ptSounds; AltPath: ptNone),// sndBlowTorch
(FileName: 'countdown1.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown1
(FileName: 'countdown2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown2
(FileName: 'countdown3.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown3
(FileName: 'countdown4.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown4
// TODO: Check which creeper (formerly rubberduck) sounds are needed, maybe rename them
(FileName: 'rubberduck_drop.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperDrop
(FileName: 'rubberduck_water.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperWater
(FileName: 'rubberduck_die.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperDie
(FileName: 'custom1.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom1
(FileName: 'custom2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom2
(FileName: 'custom3.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom3
(FileName: 'custom4.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom4
(FileName: 'custom5.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom5
(FileName: 'custom6.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom6
(FileName: 'custom7.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom7
(FileName: 'custom8.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom8
(FileName: 'minigun.ogg'; Path: ptSounds; AltPath: ptNone),// sndMinigun
(FileName: 'flamethrower.ogg'; Path: ptSounds; AltPath: ptNone),// sndFlamethrower
(FileName: 'ice_beam_idle.ogg'; Path: ptSounds; AltPath: ptNone),// sndIceBeamIdle
(FileName: 'landgun.ogg'; Path: ptSounds; AltPath: ptNone),// sndLandGun
(FileName: 'graveimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndCaseImpact
// TODO: New Extra Damage sound
(FileName: 'hell_ugh.ogg'; Path: ptSounds; AltPath: ptNone),// sndExtraDamage
(FileName: 'firepunch_hit.ogg'; Path: ptSounds; AltPath: ptNone),// sndFirePunchHit
(FileName: 'Grenade.ogg'; Path: ptVoices; AltPath: ptNone),// sndGrenade
(FileName: 'Thisoneismine.ogg'; Path: ptVoices; AltPath: ptNone),// sndThisOneIsMine
(FileName: 'Whatthe.ogg'; Path: ptVoices; AltPath: ptNone),// sndWhatThe
(FileName: 'Solong.ogg'; Path: ptVoices; AltPath: ptNone),// sndSoLong
(FileName: 'Ohdear.ogg'; Path: ptVoices; AltPath: ptNone),// sndOhDear
(FileName: 'Gonnagetyou.ogg'; Path: ptVoices; AltPath: ptNone),// sndGonnaGetYou
(FileName: 'Drat.ogg'; Path: ptVoices; AltPath: ptNone),// sndDrat
(FileName: 'Bugger.ogg'; Path: ptVoices; AltPath: ptNone),// sndBugger
(FileName: 'Amazing.ogg'; Path: ptVoices; AltPath: ptNone),// sndAmazing
(FileName: 'Brilliant.ogg'; Path: ptVoices; AltPath: ptNone),// sndBrilliant
(FileName: 'Excellent.ogg'; Path: ptVoices; AltPath: ptNone),// sndExcellent
(FileName: 'Fire.ogg'; Path: ptVoices; AltPath: ptNone),// sndFire
(FileName: 'Watchthis.ogg'; Path: ptVoices; AltPath: ptNone),// sndWatchThis
(FileName: 'Runaway.ogg'; Path: ptVoices; AltPath: ptNone),// sndRunAway
(FileName: 'Revenge.ogg'; Path: ptVoices; AltPath: ptNone),// sndRevenge
(FileName: 'Cutitout.ogg'; Path: ptVoices; AltPath: ptNone),// sndCutItOut
(FileName: 'Leavemealone.ogg'; Path: ptVoices; AltPath: ptNone),// sndLeaveMeAlone
(FileName: 'Ouch.ogg'; Path: ptVoices; AltPath: ptNone),// sndOuch
(FileName: 'Hmm.ogg'; Path: ptVoices; AltPath: ptNone),// sndHmm
(FileName: 'Kiss.ogg'; Path: ptSounds; AltPath: ptNone) // sndKiss
);
function AskForVoicepack(name: shortstring): Pointer;
var i: Longword;
langName, path: shortstring;
begin
i:= 0;
// Adjust voicepack name if there's a localised version version of the voice
if cLanguage <> 'en' then
begin
langName:= name+'_'+cLanguage;
path:= cPathz[ptVoices] + '/' + langName;
if pfsExists(path) then
name:= langName
else
if Length(cLanguage) > 3 then
begin
langName:= name+'_'+Copy(cLanguage,1,2);
path:= cPathz[ptVoices] + '/' + langName;
if pfsExists(path) then
name:= langName
end
end;
path:= cPathz[ptVoices] + '/' + name;
// Fallback to Default if voicepack can't be found at all
if (name <> 'Default') and (not pfsExists(path)) then
begin
path:= cPathz[ptVoices] + '/Default';
if pfsExists(path) then
exit(AskForVoicepack('Default'));
end;
while (voicepacks[i].name <> name) and (voicepacks[i].name <> '') and (i < cMaxTeams) do
begin
inc(i);
//TryDo(i <= cMaxTeams, 'Engine bug: AskForVoicepack i > cMaxTeams', true)
end;
voicepacks[i].name:= name;
AskForVoicepack:= @voicepacks[i]
end;
procedure InitSound;
const channels: LongInt = 2;
var success: boolean;
begin
if not (isSoundEnabled or isMusicEnabled) then
begin
isAudioMuted:= true;
cInitVolume:= 0;
exit;
end;
WriteToConsole('Init sound...');
success:= SDL_InitSubSystem(SDL_INIT_AUDIO) = 0;
if success then
begin
WriteLnToConsole(msgOK);
WriteToConsole('Open audio...');
success:= Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, channels, 1024) = 0;
end;
if success then
WriteLnToConsole(msgOK)
else
begin
WriteLnToConsole(msgFailed);
isSoundEnabled:= false;
isMusicEnabled:= false;
isAudioMuted:= true;
cInitVolume:= 0;
end;
WriteToConsole('Init SDL_mixer... ');
if (Mix_Init(MIX_INIT_OGG or MIX_INIT_OPUS) and MIX_INIT_OPUS) = 0 then
begin
WriteToConsole('Cannot init OPUS: ' + SDL_GetError());
if SDLCheck(Mix_Init(MIX_INIT_OGG) <> 0, 'Mix_Init', true) then exit;
end;
WriteLnToConsole(msgOK);
// from uVariables to be used by other modules
cIsSoundEnabled:= true;
Mix_AllocateChannels(Succ(chanTPU));
previousVolume:= cInitVolume;
ChangeVolume(cInitVolume);
end;
procedure ResetSound;
begin
isSoundEnabled:= isSEBackup;
end;
procedure SetSound(enabled: boolean);
begin
isSEBackup:= isSoundEnabled;
isSoundEnabled:= enabled;
end;
procedure SetAudioDampen(enabled: boolean);
begin
isAutoDampening:= enabled;
end;
// when complete is false, this procedure just releases some of the chucks on inactive channels
// in this way music is not stopped, nor are chucks currently being played
procedure ReleaseSound(complete: boolean);
var i: TSound;
t: Longword;
begin
// release and nil all sounds
for t:= 0 to cMaxTeams do
if voicepacks[t].name <> '' then
for i:= Low(TSound) to High(TSound) do
if voicepacks[t].chunks[i] <> nil then
if complete or (Mix_Playing(lastChan[i]) = 0) then
begin
Mix_HaltChannel(lastChan[i]);
lastChan[i]:= -1;
Mix_FreeChunk(voicepacks[t].chunks[i]);
voicepacks[t].chunks[i]:= nil;
end;
// stop music
if complete then
begin
if Mus <> nil then
begin
Mix_HaltMusic();
Mix_FreeMusic(Mus);
Mus:= nil;
end;
// make sure all instances of sdl_mixer are unloaded before continuing
while Mix_Init(0) <> 0 do
Mix_Quit();
Mix_CloseAudio();
end;
end;
// Get a fallback voice, assuming that snd is not available. Returns sndNone if none is found.
function GetFallbackV(snd: TSound): TSound;
begin
// Fallback to sndFirePunch1 / sndOw1 / sndOoff1 if a "higher-numbered" sound is missing
if (snd in [sndFirePunch2, sndFirePunch3, sndFirePunch4, sndFirePunch5, sndFirePunch6]) then
GetFallbackV := sndFirePunch1
else if (snd in [sndOw2, sndOw3, sndOw4, sndOuch]) then
GetFallbackV := sndOw1
else if (snd in [sndOoff2, sndOoff3]) then
GetFallbackV := sndOoff1
// Other fallback sounds
else if (snd = sndGrenade) then
if random(2) = 0 then
GetFallbackV := sndNooo
else
GetFallbackV := sndUhOh
else if (snd in [sndDrat, sndBugger]) then
GetFallbackV := sndStupid
else if (snd in [sndGonnaGetYou, sndIllGetYou, sndJustYouWait, sndCutItOut, sndLeaveMeAlone]) then
GetFallbackV := sndRegret
else if (snd in [sndOhDear, sndSoLong]) then
GetFallbackV := sndByeBye
else if (snd in [sndWhatThe, sndUhOh]) then
GetFallbackV := sndNooo
else if (snd = sndRunAway) then
GetFallbackV := sndOops
else if (snd = sndThisOneIsMine) then
GetFallbackV := sndReinforce
else if (snd in [sndAmazing, sndBrilliant, sndExcellent]) then
GetFallbackV := sndEnemyDown
else if (snd = sndPoisonCough) then
GetFallbackV := sndPoisonMoan
else if (snd = sndPoisonMoan) then
GetFallbackV := sndPoisonCough
else if (snd = sndFlawless) then
GetFallbackV := sndVictory
else if (snd = sndSameTeam) then
GetFallbackV := sndTraitor
else if (snd = sndMelon) then
GetFallbackV := sndCover
// sndHmm is used for enemy turn start, so sndHello is an "okay" replacement
else if (snd = sndHmm) then
GetFallbackV := sndHello
else
GetFallbackV := sndNone;
end;
function PlaySound(snd: TSound): boolean;
begin
PlaySound:= PlaySoundV(snd, nil, false, false, false);
end;
function PlaySound(snd: TSound; keepPlaying: boolean): boolean;
begin
PlaySound:= PlaySoundV(snd, nil, keepPlaying, false, false);
end;
function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean): boolean;
begin
PlaySound:= PlaySoundV(snd, nil, keepPlaying, ignoreMask, false);
end;
function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask, soundAsMusic: boolean): boolean;
begin
PlaySound:= PlaySoundV(snd, nil, keepPlaying, ignoreMask, soundAsMusic);
end;
function PlaySoundV(snd: TSound; voicepack: PVoicepack): boolean;
begin
PlaySoundV:= PlaySoundV(snd, voicepack, false, false, false);
end;
function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean): boolean;
begin
PlaySoundV:= PlaySoundV(snd, voicepack, keepPlaying, false, false);
end;
function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask: boolean): boolean;
begin
PlaySoundV:= PlaySoundV(snd, voicepack, keepPlaying, ignoreMask, false);
end;
function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
var s: shortstring;
tempSnd: TSound;
rwops: PSDL_RWops;
begin
PlaySoundV:= false;
if ((not isSoundEnabled) and (not (soundAsMusic and isMusicEnabled))) or fastUntilLag then
exit;
if keepPlaying and (lastChan[snd] <> -1) and (Mix_Playing(lastChan[snd]) <> 0) then
exit;
if (ignoreMask = false) and (MaskedSounds[snd] = true) then
exit;
if (voicepack <> nil) then
begin
if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then
begin
s:= cPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
// Fallback taunts
if (not pfsExists(s)) then
begin
tempSnd := GetFallbackV(snd);
if tempSnd <> sndNone then
begin
snd := tempSnd;
LastVoice.snd := tempSnd;
end;
s:= cPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
end;
WriteToConsole(msgLoading + s + ' ');
rwops := rwopsOpenRead(s);
if rwops = nil then
begin
s:= cPathz[Soundz[snd].AltPath] + '/' + Soundz[snd].FileName;
WriteToConsole(msgLoading + s + ' ... ');
rwops := rwopsOpenRead(s);
end;
voicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwops, 1);
if voicepack^.chunks[snd] = nil then
WriteLnToConsole(msgFailed)
else
WriteLnToConsole(msgOK)
end;
lastChan[snd]:= Mix_PlayChannelTimed(-1, voicepack^.chunks[snd], 0, -1);
PlaySoundV:= true;
end
else
begin
if (defVoicepack^.chunks[snd] = nil) and (Soundz[snd].Path <> ptVoices) and (Soundz[snd].FileName <> '') then
begin
s:= cPathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
WriteToConsole(msgLoading + s + ' ');
rwops := rwopsOpenRead(s);
if rwops = nil then
begin
s:= cPathz[Soundz[snd].AltPath] + '/' + Soundz[snd].FileName;
WriteToConsole(msgLoading + s + ' ... ');
rwops := rwopsOpenRead(s);
end;
defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwops, 1);
if SDLCheck(defVoicepack^.chunks[snd] <> nil, 'Mix_LoadWAV_RW', true) then exit;
WriteLnToConsole(msgOK);
end;
lastChan[snd]:= Mix_PlayChannelTimed(-1, defVoicepack^.chunks[snd], 0, -1);
PlaySoundV:= true;
end;
end;
procedure PlayMusicSound(snd: TSound);
begin
PauseMusic;
PlaySound(snd, false, false, true);
end;
procedure StopMusicSound(snd: TSound);
begin
StopSound(snd, true);
ResumeMusic;
end;
procedure AddVoice(snd: TSound; voicepack: PVoicepack);
begin
AddVoice(snd, voicepack, false, false);
end;
{
AddVoice: Add a voice to the voice queue.
* snd: Sound ID
* voicepack: Hedgehog voicepack
* ignoreMask: If true, the sound will be played anyway if masked by Lua
* isFallback: If true, this sound is added as fallback if the sound previously added to the
queue was not found. Useful to make sure a voice is always played, even if
a voicepack is incomplete.
Example:
AddVoice(sndRevenge, voiceAttacker);
AddVoice(sndRegret, voiceVictim, false, true);
--> plays sndRegret if sndRevenge could not be played.
}
procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask, isFallback: boolean);
var i : LongInt;
begin
if (not isSoundEnabled) or fastUntilLag or ((LastVoice.snd = snd) and (LastVoice.voicepack = voicepack)) then
exit;
if (ignoreMask = false) and (MaskedSounds[snd] = true) then
exit;
if (snd = sndVictory) or (snd = sndFlawless) then
begin
Mix_FadeOutChannel(-1, 800);
for i:= 0 to High(VoiceList) do
VoiceList[i].snd:= sndNone;
LastVoice.snd:= sndNone;
end;
i:= 0;
while (i <= High(VoiceList)) and (VoiceList[i].snd <> sndNone) do
inc(i);
// skip playing same sound for same hog twice
if (i>0) and (VoiceList[i-1].snd = snd) and (VoiceList[i-1].voicepack = voicepack) then
exit;
if(i <= High(VoiceList)) then
begin
VoiceList[i].snd:= snd;
VoiceList[i].voicepack:= voicepack;
VoiceList[i].isFallback:= isFallback;
end
end;
procedure PlayNextVoice;
var i : LongInt;
played : boolean;
begin
if (not isSoundEnabled) or fastUntilLag or ((LastVoice.snd <> sndNone) and (lastChan[LastVoice.snd] <> -1) and (Mix_Playing(lastChan[LastVoice.snd]) <> 0)) then
exit;
i:= 0;
while (i<High(VoiceList)) and (VoiceList[i].snd = sndNone) do
inc(i);
played:= false;
if (VoiceList[i].snd <> sndNone) and ((not VoiceList[i].isFallback) or LastVoiceFailed) then
begin
LastVoice.snd:= VoiceList[i].snd;
LastVoice.voicepack:= VoiceList[i].voicepack;
LastVoice.isFallback:= VoiceList[i].isFallback;
VoiceList[i].snd:= sndNone;
played:= PlaySoundV(LastVoice.snd, LastVoice.voicepack);
// Remember if sound was not played.
LastVoiceFailed:= (not played);
end
else
LastVoice.snd:= sndNone;
end;
function LoopSound(snd: TSound): LongInt;
begin
LoopSound:= LoopSoundV(snd, nil)
end;
function LoopSound(snd: TSound; fadems: LongInt): LongInt;
begin
LoopSound:= LoopSoundV(snd, nil, fadems)
end;
function LoopSoundV(snd: TSound; voicepack: PVoicepack): LongInt;
begin
voicepack:= voicepack; // avoid compiler hint
LoopSoundV:= LoopSoundV(snd, nil, 0)
end;
function LoopSoundV(snd: TSound; voicepack: PVoicepack; fadems: LongInt): LongInt;
var s: shortstring;
rwops: PSDL_RWops;
begin
if (not isSoundEnabled) or fastUntilLag then
begin
LoopSoundV:= -1;
exit
end;
if (voicepack <> nil) then
begin
if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then
begin
s:= cPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
WriteToConsole(msgLoading + s + ' ');
rwops:=rwopsOpenRead(s);
if rwops = nil then
begin
s:= cPathz[Soundz[snd].AltPath] + '/' + Soundz[snd].FileName;
WriteToConsole(msgLoading + s + ' ... ');
rwops:=rwopsOpenRead(s);
end;
voicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwops, 1);
if voicepack^.chunks[snd] = nil then
WriteLnToConsole(msgFailed)
else
WriteLnToConsole(msgOK)
end;
LoopSoundV:= Mix_PlayChannelTimed(-1, voicepack^.chunks[snd], -1, -1)
end
else
begin
if (defVoicepack^.chunks[snd] = nil) and (Soundz[snd].Path <> ptVoices) and (Soundz[snd].FileName <> '') then
begin
s:= cPathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
WriteToConsole(msgLoading + s + ' ');
defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwopsOpenRead(s), 1);
if SDLCheck(defVoicepack^.chunks[snd] <> nil, 'Mix_LoadWAV_RW', true) then exit(-1);
WriteLnToConsole(msgOK);
end;
if fadems > 0 then
LoopSoundV:= Mix_FadeInChannelTimed(-1, defVoicepack^.chunks[snd], -1, fadems, -1)
else
LoopSoundV:= Mix_PlayChannelTimed(-1, defVoicepack^.chunks[snd], -1, -1);
end;
end;
procedure StopSound(snd: TSound);
begin
StopSound(snd, false);
end;
procedure StopSound(snd: TSound; soundAsMusic: boolean);
begin
if ((not isSoundEnabled) and (not (soundAsMusic and isMusicEnabled))) then
exit;
if (lastChan[snd] <> -1) and (Mix_Playing(lastChan[snd]) <> 0) then
begin
Mix_HaltChannel(lastChan[snd]);
lastChan[snd]:= -1;
end;
end;
procedure StopSoundChan(chn: LongInt);
begin
if not isSoundEnabled then
exit;
if (chn <> -1) and (Mix_Playing(chn) <> 0) then
Mix_HaltChannel(chn);
end;
procedure StopSoundChan(chn, fadems: LongInt);
begin
if not isSoundEnabled then
exit;
if (chn <> -1) and (Mix_Playing(chn) <> 0) then
Mix_FadeOutChannel(chn, fadems);
end;
procedure PlayMusic;
var s: shortstring;
begin
if (MusicFN = '') or (not isMusicEnabled) then
exit;
if SuddenDeath and (SDMusicFN <> '') then
s:= '/Music/' + SDMusicFN
else s:= '/Music/' + MusicFN;
WriteToConsole(msgLoading + s + ' ');
// Load normal music
Mus:= Mix_LoadMUS_RW(rwopsOpenRead(s));
SDLCheck(Mus <> nil, 'Mix_LoadMUS_RW', false);
if Mus <> nil then
WriteLnToConsole(msgOK);
// If normal music failed, try to get fallback music
if Mus = nil then
begin
WriteLnToConsole('Music not found. Trying fallback music.');
if SuddenDeath and (FallbackSDMusicFN <> '') then
s:= '/Music/' + FallbackSDMusicFN
else if (not SuddenDeath) and (FallbackMusicFN <> '') then
s:= '/Music/' + FallbackMusicFN
else
begin
WriteLnToConsole('No fallback music configured!');
s:= ''
end;
if (s <> '') then
begin
WriteLnToConsole(msgLoading + s + ' ');
Mus:= Mix_LoadMUS_RW(rwopsOpenRead(s));
SDLCheck(Mus <> nil, 'Mix_LoadMUS_RW', false);
if Mus <> nil then
WriteLnToConsole(msgOK)
end;
end;
SDLCheck(Mix_FadeInMusic(Mus, -1, 3000) <> -1, 'Mix_FadeInMusic', false)
end;
procedure SetVolume(vol: LongInt);
begin
cInitVolume:= vol;
end;
function GetVolumePercent(): LongInt;
begin
GetVolumePercent:= Volume * 100 div MIX_MAX_VOLUME;
// 0 and 100 will only be displayed when at min/max values
// to avoid confusion.
if ((GetVolumePercent = 0) and (Volume > 0)) then
GetVolumePercent:= 1
else if ((GetVolumePercent = 100) and (Volume < MIX_MAX_VOLUME)) then
GetVolumePercent:= 99;
end;
function ChangeVolume(voldelta: LongInt): LongInt;
begin
ChangeVolume:= 0;
if not (isSoundEnabled or isMusicEnabled) or ((voldelta = 0) and (not (cInitVolume = 0))) then
exit;
inc(Volume, voldelta);
if Volume < 0 then
Volume:= 0;
// apply Volume to all channels
Mix_Volume(-1, Volume);
// get assigned Volume
Volume:= Mix_Volume(-1, -1);
if isMusicEnabled then
Mix_VolumeMusic(Volume * 4 div 8);
ChangeVolume:= GetVolumePercent();
if (isMusicEnabled) then
if (Volume = 0) then
PauseMusic
else
ResumeMusic;
isAudioMuted:= (Volume = 0);
end;
procedure DampenAudio;
begin
if (isAudioMuted or (not isAutoDampening)) then
exit;
previousVolume:= Volume;
ChangeVolume(-Volume * 7 div 9);
end;
procedure UndampenAudio;
begin
if (isAudioMuted or (not isAutoDampening)) then
exit;
ChangeVolume(previousVolume - Volume);
end;
procedure MuteAudio;
begin
if not (isSoundEnabled or isMusicEnabled) then
exit;
if (isAudioMuted) then
begin
ResumeMusic;
ChangeVolume(previousVolume);
end
else
begin
PauseMusic;
previousVolume:= Volume;
ChangeVolume(-Volume);
end;
// isAudioMuted is updated in ChangeVolume
end;
procedure SetMusic(enabled: boolean);
begin
isMusicEnabled:= enabled;
end;
procedure SetMusicName(musicname: shortstring);
begin
MusicFN:= musicname;
end;
procedure PauseMusic;
begin
if (MusicFN = '') or (not isMusicEnabled) then
exit;
if Mus <> nil then
Mix_PauseMusic(Mus);
end;
procedure ResumeMusic;
begin
if (MusicFN = '') or (not isMusicEnabled) then
exit;
if Mus <> nil then
Mix_ResumeMusic(Mus);
end;
procedure ChangeMusic(musicname: shortstring);
begin
MusicFN:= musicname;
if (MusicFN = '') or (not isMusicEnabled) then
exit;
StopMusic;
PlayMusic;
end;
procedure StopMusic;
begin
if (MusicFN = '') or (not isMusicEnabled) then
exit;
if Mus <> nil then
begin
Mix_FreeMusic(Mus);
Mus:= nil;
end
end;
procedure chVoicepack(var s: shortstring);
begin
if CurrentTeam = nil then
OutError(errmsgIncorrectUse + ' "/voicepack"', true);
if s[1]='"' then Delete(s, 1, 1);
if s[byte(s[0])]='"' then
Delete(s, byte(s[0]), 1);
CurrentTeam^.voicepack:= AskForVoicepack(s)
end;
procedure preInitModule;
begin
isMusicEnabled:= true;
isSoundEnabled:= true;
isAutoDampening:= true;
cInitVolume:= 100;
end;
procedure initModule;
var t: LongInt;
i: TSound;
begin
RegisterVariable('voicepack', @chVoicepack, false);
MusicFN:='';
SDMusicFN:= 'sdmusic.ogg';
Mus:= nil;
isAudioMuted:= false;
isSEBackup:= isSoundEnabled;
Volume:= 0;
SoundTimerTicks:= 0;
defVoicepack:= AskForVoicepack('Default');
LastVoiceFailed:= false;
for i:= Low(TSound) to High(TSound) do
lastChan[i]:= -1;
// initialize all voices to nil so that they can be loaded lazily
for t:= 0 to cMaxTeams do
if voicepacks[t].name <> '' then
for i:= Low(TSound) to High(TSound) do
voicepacks[t].chunks[i]:= nil;
(* on MOBILE SDL_mixer has to be compiled against Tremor (USE_OGG_TREMOR)
or sound files bigger than 32k will lockup the game on slow cpu *)
for i:= Low(TSound) to High(TSound) do
defVoicepack^.chunks[i]:= nil;
end;
procedure freeModule;
begin
if isSoundEnabled or isMusicEnabled then
ReleaseSound(true);
end;
end.