/* -------------------------------------------------------------- KF_Slot_Machine -------------------------------------------------------------- Interactive prop for the 2013 Summer Sideshow map. Can be used by players to randomly dole out prizes. Author : Alex Quick -------------------------------------------------------------- */ #exec OBJ LOAD FILE=Pier_anim.ukx class KF_Slot_Machine extends Actor placeable; const NUMREELS = 3; const NUMSYMBOLS = 6; const NUMAMBIENTSNDS = 3; var() edfindable KF_Slot_Reel Reels[NUMREELS] ; var float DesiredRolls[NUMREELS]; var int NumSpinning; var() float SpinDuration; var float LastSpinTime; var float LastReelStopTime; var string Snd_ReelStoppedRef,Snd_ReelSpinningRef,Snd_MonsterPrizeRef,Snd_CashPrizeRef,Snd_JackpotRef, Snd_LeverPullRef; var Sound Snd_ReelStopped,Snd_ReelSpinning,Snd_MonsterPrize,Snd_CashPrize,Snd_Jackpot,Snd_LeverPull; // The volume to play these slot machine sounds at var float ReelStoppedVolume, CashPrizeVolume, MonsterPrizeVolume, JackpotVolume, LeverPullVolume; // The sound radius to use for these slot machine sounds var float ReelSpinningRadius, ReelStoppedRadius, CashPrizeRadius, MonsterPrizeRadius, JackpotRadius, LeverPullRadius; // The volume of the ambient sound when the real spinning is playing var byte ReelSpinningVolume; var string Snd_AmbientActiveRef[NUMAMBIENTSNDS]; var Sound Snd_AmbientActive[NUMAMBIENTSNDS]; var Pawn CurrentPlayer; var() const name YouLostEvent; var() const name YouWonEvent; var int RemainingPayOut; // rate at which you accrue 'good karma' if you keep pulling and not hitting. var() const float JackPotChanceIncreaseRate; var() int MaxBet; var int RemainingBonusSpins; var() const edfindable KF_StoryWaveDesigner AssociatedDesigner; var float JackPotChance; var bool bFirstSpin; /* ==== Animation Stuff =================================*/ /* Animation the slot machine plays when it is used */ var name SlotPullAnim; var byte RepAnimByte,LastRepAnimByte; struct SReelSymbol { var float ReelPosition; // The Rotation (Roll) that this symbol inhabits on the reel. var string SymbolName; // Name of the Symbol. var int NumReqHits; // The number of hits this symbol requires to pay out. var int NumHits; // The number of times this symbol was on the payline during the current spin. Cleared each new spin. }; var SReelSymbol ReelSymbols[NUMSYMBOLS]; var bool bActive; var() const bool bStartActive; replication { reliable if(Role == Role_Authority && bNetDirty) RepAnimByte; } function PreBeginPlay() { PreLoadSounds(); } function PostBeginPlay() { SetActive(bStartActive); } function PreLoadSounds() { local int i; if ( default.Snd_ReelStoppedRef != "" ) { Snd_ReelStopped = sound(DynamicLoadObject(default.Snd_ReelStoppedRef, class'Sound', true)); } if( default.Snd_ReelSpinningRef != "") { Snd_ReelSpinning = sound(DynamicLoadObject(default.Snd_ReelSpinningRef, class'Sound', true)); } if( default.Snd_MonsterPrizeRef != "") { Snd_MonsterPrize = sound(DynamicLoadObject(default.Snd_MonsterPrizeRef, class'Sound', true)); } if( default.Snd_CashPrizeRef != "") { Snd_CashPrize = sound(DynamicLoadObject(default.Snd_CashPrizeRef, class'Sound', true)); } if( default.Snd_JackpotRef != "") { Snd_Jackpot = sound(DynamicLoadObject(default.Snd_JackpotRef, class'Sound', true)); } if( default.Snd_LeverPullRef != "") { Snd_LeverPull = sound(DynamicLoadObject(default.Snd_LeverPullRef, class'Sound', true)); } for(i = 0 ; i < NUMAMBIENTSNDS ; i ++) { if( default.Snd_AmbientActiveRef[i] != "") { Snd_AmbientActive[i] = sound(DynamicLoadObject(default.Snd_AmbientActiveRef[i], class'Sound', true)); } } } /* A player threw some cash at a slot machine . */ simulated event Touch( Actor Other ) { local Cashpickup DroppedCash; DroppedCash = CashPickup(Other); if(DroppedCash != none) { if(DroppedCash.bDroppedCash && DroppedCash.CashAmount >= MaxBet && DroppedCash.DroppedBy != none && DroppedCash.DroppedBy.Pawn != none && AttemptSpin(DroppedCash.DroppedBy.Pawn)) { DroppedCash.SetRespawn(); } else { /* Flings the dropped cash back at the guy who threw it. */ DroppedCash.Velocity = vect(0,0,0); } } } /* Attempts to spin the slot reels. Returns true if successfull */ function bool AttemptSpin(Pawn SlotPlayer) { /* Dont' allow a new spin if the reels are already spinning or the machine is still paying out a prize */ if(bActive && !ReelsAreSpinning() && RemainingPayOut == 0 && !IsAnimating()) { if(Snd_LeverPull != none) { PlaySound(Snd_LeverPull,SLOT_None,LeverPullVolume,false,LeverPullRadius,SoundPitch / 64.0); } AmbientSound = Snd_ReelSpinning; SoundVolume = ReelSpinningVolume; SoundRadius = ReelSpinningRadius; bNetNotify = true; NetUpdateTime = Level.TimeSeconds - 1; CurrentPlayer = SlotPlayer; if(RemainingBonusSpins > 0) { RemainingBonusSpins = Max(RemainingBonusSpins -1, 0) ; } CalculateReelPositions(); if(SlotPullAnim != '') { RepAnimByte ++ ; PlayPullAnim(); } return true; } return false; } event Trigger( Actor Other, Pawn EventInstigator ) { SetActive(!bActive); } function bool ReelsAreSpinning() { return NumSpinning > 0; } /* The final positions for each reel are calculated before they actually start spinning */ function CalculateReelPositions() { local int i,idx; local string SymbolName; for(i = 0 ; i < ArrayCount(ReelSymbols) ; i ++) { ReelSymbols[i].NumHits = 0; } // log("CHANCE TO HIT JACKPOINT : "@JackPotChance*100$"%"); for(i = 0 ; i < ArrayCount(Reels) ; i ++) { if(FRand() < JackPotChance) { DesiredRolls[i] = 0.f; } else { DesiredRolls[i] = 11000.f * Round(RandRange(0,arraycount(ReelSymbols)-1)) ; } SymbolName = GetSymbolAtPosition(DesiredRolls[i]); // log("["$i$"] : "$SymbolName); for(idx = 0 ; idx < arraycount(ReelSymbols) ; idx ++) { if(ReelSymbols[idx].SymbolName == SymbolName) { ReelSymbols[idx].NumHits ++; } } } } function string GetSymbolAtPosition(float Position, optional out int Index) { local int i; for(i = 0 ; i < arrayCount(ReelSymbols); i ++ ) { if(Position == ReelSymbols[i].ReelPosition) { Index = i; return ReelSymbols[i].SymbolName; } } return "COULD NOT FIND A REEL SYMBOL AT POSITION :"$Position; } function SpinReels() { local int i; if(Level.NetMode == NM_Client) { return; } LastSpinTime = Level.TimeSeconds; for(i = 0 ; i < ArrayCount(Reels) ; i ++) { Reels[i].bFixedRotationDir = true; Reels[i].bRotateToDesired = false; Reels[i].NetUpdateFrequency = 5; Reels[i].NetUpdateTime = Level.TimeSeconds - 1; NumSpinning ++ ; } } /* Is this Machine open for business ? */ function SetActive(bool On) { local int i; for(i = 0 ; i < NUMREELS ; i ++) { if(Reels[i] != none) { Reels[i].bHidden = !On; Reels[i].NetUpdateTime = Level.TimeSeconds - 1; } } // bHidden = !On; bActive = On; if(bActive) { AmbientSound = Snd_AmbientActive[Rand(NUMAMBIENTSNDS)] ; SoundVolume = default.SoundVolume; SoundRadius = default.SoundRadius; } else { AmbientSound = none; } } function Tick(Float DeltaTime) { local int i; if(RemainingBonusSpins > 0) { AttemptSpin(CurrentPlayer); } if(!ReelsAreSpinning()) { return; } if(Level.TimeSeconds - LastSpinTime >= SpinDuration) { for(i = 0 ; i < ArrayCount(Reels) ; i ++) { if(Reels[i].bFixedRotationDir && Level.TimeSeconds - LastReelStopTime >= 1.f) { LastReelStopTime = Level.TimeSeconds; Reels[i].bFixedRotationDir = false; Reels[i].bRotateToDesired = true; Reels[i].DesiredRotation.Roll = DesiredRolls[i]; Reels[i].NetUpdateFrequency = Reels[i].default.NetUpdateFrequency ; NumSpinning -- ; if(Snd_ReelStopped != none) { PlaySound(Snd_ReelStopped,,ReelStoppedVolume,false,ReelStoppedRadius,SoundPitch / 64.0); } } } } if(NumSpinning == 0) { PayOut(); } } /* Spits out a reward based on the symbols on the reels */ function PayOut() { local int i,idx; local bool bWon; local array WinningSymbols; local bool bAlreadyWonOnThisSymbol; AmbientSound = Snd_AmbientActive[Rand(NUMAMBIENTSNDS)]; SoundVolume = default.SoundVolume; SoundRadius = default.SoundRadius; for(i=0; i < ArrayCount(ReelSymbols) ; i ++) { bAlreadyWonOnThisSymbol = false; for(idx = 0 ; idx < WinningSymbols.length ; idx ++) { if(ReelSymbols[i].SymbolName == WinningSymbols[idx]) { bAlreadyWonOnThisSymbol = true; break; } } if(!bAlreadyWonOnThisSymbol && ReelSymbols[i].NumHits >= ReelSymbols[i].NumReqHits) { WinningSymbols[WinningSymbols.length] = ReelSymbols[i].SymbolName; if(!bWon && ReelSymbols[i].SymbolName == "Fuel") { bWon = true; } switch(ReelSymbols[i].SymbolName) { Case "Fuel" : SpawnFuel() ; break; Case "Ammo" : SpawnAmmo(ReelSymbols[i].NumHits - 1); break; Case "Monster" : SpawnEnemy(ReelSymbols[i].NumHits); break; } } } if(!bWon) // didnt win anything, or won something bad. { if(YouLostEvent != '') { TriggerEvent(YouLostEvent,self,CurrentPlayer); } // give the player a helping hand if he fails enough times. JackPotChance = FMin(JackPotChance + JackPotChanceIncreaseRate,1.f); } else { if(YouWonEvent != '') { TriggerEvent(YouWonEvent,self,CurrentPlayer); } JackPotChance = 0.f; // reset. } if(bFirstSpin) { bFirstSpin = false; } } function SpawnAmmo(int NumToSpawn) { local KF_Slot_AmmoPickup AmmoPickup; local int i; if(Snd_CashPrize != none) { PlaySound(Snd_CashPrize,SLOT_None,CashPrizeVolume,false,CashPrizeRadius,SoundPitch / 64.0); } for(i = 0 ; i < NumToSpawn ; i ++) { AmmoPickup = Spawn(class 'KF_Slot_AmmoPickup',self,,GetPayoutLocation(),Rotation); if(AmmoPickup != none) { AmmoPickup.Velocity = GetPayoutVelocity() + (i * GetPayoutVelocity()/2) ; } } } function SpawnFuel() { local Pickup_GasCan FuelPickup; FuelPickup = Spawn(class 'Pickup_GasCan',self,,GetPayoutLocation(),Rotation); if(FuelPickup != none) { FuelPickup.Velocity = GetPayoutVelocity(); } if(Snd_Jackpot != none) { PlaySound(Snd_Jackpot,SLOT_None,JackpotVolume,,JackpotRadius,SoundPitch / 64.0); } if(bFirstSpin) { UnlockAchievement(); } } // The player got the fuel on his first spin! function UnlockAchievement() { local Controller C; local KFPlayerController KFPC; local KFSteamStatsAndAchievements KFAchievements; for (C = Level.ControllerList; C != None; C = C.NextController) { KFPC = KFPlayerController(C); if(KFPC != none) { KFAchievements = KFSteamStatsAndAchievements( KFPC.SteamStatsAndAchievements ); if(KFAchievements != none) { KFAchievements.CheckAndSetAchievementComplete( KFAchievements.KFACHIEVEMENT_777 ); } } } } function SpawnEnemy(int NumHits) { local int i; local string Type; if(AssociatedDesigner == none) { log("Warning - No wave Designer associated with : "@self@" it will not be able to spawn ZEDs."); return; } Switch(NumHits) { case 1 : Type = "Siren"; break; case 2 : Type = "Scrake"; break; case 3 : Type = "FleshPound"; break; } if(Type == "FleshPound" && Snd_MonsterPrize != none) { if(Snd_MonsterPrize != none) { PlaySound(Snd_MonsterPrize,SLOT_None,MonsterPrizeVolume,false,MonsterPrizeRadius,SoundPitch / 64.0); } } for(i = 0 ; i < AssociatedDesigner.Waves.Length ; i ++) { if(AssociatedDesigner.Waves[i].Wave_Spawns[0].SquadList[0] ~= Type) { if(AssociatedDesigner.Waves[i].WaveController != none) { AssociatedDesigner.Waves[i].WaveController.Trigger(self,none); } } } } /* The machine was just used by a player. Make the lever animate*/ simulated function PlayPullAnim() { PlayAnim(SlotPullAnim,1.f,0.1); } /* A variable was replicated. Check if we need to play a new animation on the client Network : Clients */ simulated event PostNetReceive() { if(RepAnimByte != LastRepAnimByte) { LastRepAnimByte = RepAnimByte; PlayPullAnim(); } } /* Give the player some dosh */ function SpawnCash(int Amount) { local PlayerController PC; if(Snd_CashPrize != none) { PlaySound(Snd_CashPrize,SLOT_None,CashPrizeVolume,false,CashPrizeRadius); } if(CurrentPlayer != none && CurrentPlayer.Controller != none) { PC = PlayerController(Currentplayer.Controller); if(PC != none) { PC.ClientPlaySound(class 'CashPickup'.default.PickupSound); PC.ReceiveLocalizedMessage(class 'Msg_CashReward',Amount); PC.PlayerReplicationInfo.Score += Amount; PC.PlayerReplicationInfo.NetUpdateTime = Level.TimeSeconds - 1; } } } function AddBonusSpins(int NumToAdd) { RemainingBonusSpins += NumToAdd; AttemptSpin(CurrentPlayer); } /* Fling prizes in the direction of the player who played the machine*/ simulated function vector GetPayoutVelocity() { return Normal(Vector(Rotation)) * 50.f + (Vect(0,0,1) * 25.f) ; } function Timer() { SpawnCash(RemainingPayOut); } function vector GetPayoutLocation(optional float SpawnOffset) { return Location + Normal(Vector(Rotation)) * ((CollisionRadius/2) + (SpawnOffset/2)) ; } defaultproperties { bFirstSpin = true JackPotChanceIncreaseRate = 0.02 NetUpdateFrequency = 1 RemoteRole = Role_SimulatedProxy SlotPullAnim="Pull" bNetNotify = true bUseDynamicLights = true bDirectional = true bCollideActors = true bUseCylinderCollision = true CollisionHeight = 60 CollisionRadius = 35 DrawType = DT_Mesh Mesh = SkeletalMesh'FrightYard_SKM.GasPump_Slots' Snd_ReelStoppedRef = "SteamLand_SND.SlotMachine_ReelStop" Snd_ReelSpinningRef = "FreakCircus_Snd_two.Test.arcade6" Snd_MonsterPrizeRef = "Hellride_Snd.General.KF_HellRide_EvilLaugh_02" Snd_CashPrizeRef = "SteamLand_SND.SlotMachine_Dosh" Snd_JackpotRef = "SteamLand_SND.SlotMachine_JackPot" Snd_LeverPullRef = "SteamLand_SND.SlotMachine_LeverPull" Snd_AmbientActiveRef(0)="SteamLand_SND.Ambient_SlotMachine_1" Snd_AmbientActiveRef(1)="SteamLand_SND.Ambient_SlotMachine_2" Snd_AmbientActiveRef(2)="SteamLand_SND.Ambient_SlotMachine_3" Snd_ReelSpinning=Sound'FreakCircus_Snd_two.Test.arcade6' ReelSpinningVolume=255 ReelStoppedVolume=2.0 CashPrizeVolume=2.0 MonsterPrizeVolume=2.0 JackpotVolume=2.0 LeverPullVolume=2.0 ReelSpinningRadius=750 ReelStoppedRadius=500.0 CashPrizeRadius=500.0 MonsterPrizeRadius=500.0 JackpotRadius=750.0 LeverPullRadius=500.0 SoundVolume=128 SoundRadius=250 bFullVolume=true SpinDuration = 6 Maxbet = 50 ReelSymbols(0)=(ReelPosition=0,SymbolName="Fuel",NumReqHits=3) ReelSymbols(1)=(ReelPosition=11000,SymbolName="Ammo",NumReqHits=2) ReelSymbols(2)=(ReelPosition=22000,SymbolName="Monster",NumReqHits=1) ReelSymbols(3)=(ReelPosition=33000,SymbolName="Fuel",NumReqHits=3) ReelSymbols(4)=(ReelPosition=44000,SymbolName="Ammo",NumReqHits=2) ReelSymbols(5)=(ReelPosition=55000,SymbolName="Monster",NumReqHits=1) }