global function Gauntlet_ServerInit global function EnableAllGauntlets global function DisableAllGauntlets global function EnableGauntlet global function DisableGauntlet global function Gauntlet_HideLeaderboard global function Gauntlet_ShowLeaderboard global function Gauntlet_NPC_PostSpawn global function ClientCommand_Gauntlet_PlayerRestartedFromMenu global function Gauntlet_StartGhostPlayback global function Gauntlet_StopGhostPlayback global function Gauntlet_ChallengeLeaderboardGhosts #if DEV global function Gauntlet_Player_GhostRecordOrPlayback #endif const float GAUNTLET_ENEMY_MISSED_TIME_PENALTY = 2.0 const float GAUNTLET_TARGET_DISSOLVE_TIME = 1.0 const float GAUNTLET_TARGET_DISSOLVE_TIME_MS = GAUNTLET_TARGET_DISSOLVE_TIME * 100 void function Gauntlet_ServerInit() { AddClientCommandCallback( "Gauntlet_PlayerRestartedFromMenu", ClientCommand_Gauntlet_PlayerRestartedFromMenu ) AddCallback_EntitiesDidLoad( Gauntlet_PostEntityLoadSetup ) AddCallback_OnClientConnected( Gauntlet_PlayerConnected ) AddCallback_OnLoadSaveGame( Gauntlet_OnLoadSaveGame ) RegisterSignal( "trigStart_OnStartTouch" ) RegisterSignal( "trigStart_OnEndTouch" ) RegisterSignal( "trigStart2_OnStartTouch" ) RegisterSignal( "trigStart2_OnEndTouch" ) RegisterSignal( "Gauntlet_PlayerHitStartTrig" ) RegisterSignal( "Gauntlet_PlayerWentBackwardsThroughStartTrig" ) RegisterSignal( "Gauntlet_PlayerHitFinishTrig" ) RegisterSignal( "Gauntlet_CheckpointHit" ) RegisterSignal( "Gauntlet_ForceRestart" ) RegisterSignal( "GhostAnimationPlayback_Start" ) RegisterSignal( "GhostAnimationPlayback_Stop" ) RegisterSignal( "Gauntlet_PlayerBeatChallengeGhost" ) RegisterSignal( "Gauntlet_PlayerBeatAllChallengeGhosts" ) #if DEV RegisterSignal( "RecordAnimation_Start") RegisterSignal( "Player_StartRecordingGhost_HintStart" ) #endif } void function Gauntlet_PostEntityLoadSetup() { foreach ( gauntlet in GetGauntlets() ) { InitGauntlet( gauntlet ) if ( gauntlet.startEnabled ) EnableGauntlet( gauntlet ) } } void function Gauntlet_PlayerConnected( entity player ) { foreach ( gauntlet in GetGauntlets() ) { // send ghost duration data to client- only server can read the anim durations foreach ( ghost in gauntlet.ghosts ) Remote_CallFunction_Replay( player, "ScriptCallback_Gauntlet_SetGhostDuration", gauntlet.id, ghost.id, ghost.duration ) if ( gauntlet.showLeaderboard ) { Gauntlet_ShowLeaderboard( gauntlet ) if ( gauntlet.activeGhostID != -1 ) Gauntlet_RefreshActiveGhostID( gauntlet ) } } } void function Gauntlet_OnLoadSaveGame( entity player ) { thread Gauntlet_OnLoadSaveGame_Thread( player ) } void function Gauntlet_OnLoadSaveGame_Thread( entity player ) { wait 1.0 Gauntlet_PlayerConnected( player ) } // turns on a particular gauntlet void function EnableGauntlet( GauntletInfo gauntlet ) { Assert( gauntlet.isInited, "Must run InitGauntlet before enabling" ) if ( gauntlet.isEnabled ) return Gauntlet_CreateSignalEnt( gauntlet ) foreach ( player in GetPlayerArray() ) Remote_CallFunction_Replay( player, "ScriptCallback_EnableGauntlet", gauntlet.id ) thread Gauntlet_Think( gauntlet ) gauntlet.isEnabled = true } // turns off a particular gauntlet void function DisableGauntlet( GauntletInfo gauntlet ) { if ( !gauntlet.isEnabled ) return gauntlet.signalEnt.Signal( "DisableGauntlet" ) Gauntlet_CleanupSignalEnt( gauntlet ) Gauntlet_ClearSpawnedNPCs( gauntlet ) thread ClearDroppedWeapons( GAUNTLET_TARGET_DISSOLVE_TIME + 0.1 ) // needs to be longer than gauntlet ghost dissolve time so weapons drop foreach ( player in GetPlayerArray() ) Remote_CallFunction_Replay( player, "ScriptCallback_DisableGauntlet", gauntlet.id ) gauntlet.isEnabled = false } void function Gauntlet_HideLeaderboard( GauntletInfo gauntlet ) { Gauntlet_SetLeaderboardEnabled( gauntlet, false ) foreach ( player in GetPlayerArray() ) Remote_CallFunction_Replay( player, "ScriptCallback_HideLeaderboard", gauntlet.id ) } void function Gauntlet_ShowLeaderboard( GauntletInfo gauntlet ) { Gauntlet_SetLeaderboardEnabled( gauntlet, true ) foreach ( player in GetPlayerArray() ) Remote_CallFunction_Replay( player, "ScriptCallback_ShowLeaderboard", gauntlet.id ) } void function Gauntlet_Checkpoints( GauntletInfo gauntlet ) { if ( !gauntlet.checkpoints.len() ) return foreach ( trig in gauntlet.checkpoints ) thread Gauntlet_CheckpointTrig_WaitForPlayer( gauntlet, trig ) } void function Gauntlet_CheckpointTrig_WaitForPlayer( GauntletInfo gauntlet, entity trig ) { gauntlet.player.EndSignal( "OnDestroy" ) gauntlet.player.EndSignal( "Gauntlet_RunStarted" ) gauntlet.player.EndSignal( "Gauntlet_RunStopped" ) trig.EndSignal( "OnDestroy" ) table result entity activator while ( 1 ) { result = trig.WaitSignal( "OnStartTouch" ) activator = expect entity( result.activator ) if ( !activator.IsPlayer() ) continue if ( !IsAlive( activator ) ) continue if ( activator.IsTitan() ) continue break } gauntlet.checkpointsHit++ activator.Signal( "Gauntlet_CheckpointHit" ) } void function Gauntlet_ClearSpawnedNPCs( GauntletInfo gauntlet ) { foreach ( guy in gauntlet.spawned ) { if ( IsAlive( guy ) ) { Gauntlet_UnfreezeNPC( guy ) guy.Die() } } gauntlet.spawned = [] } void function Gauntlet_SpawnNPCs( GauntletInfo gauntlet ) { Gauntlet_ClearSpawnedNPCs( gauntlet ) array spawned = SpawnFromSpawnerArray( gauntlet.spawners ) foreach ( guy in spawned ) thread Gauntlet_NPC_PostSpawn( guy, gauntlet ) gauntlet.spawned = spawned } void function Gauntlet_NPC_PostSpawn( entity npc, GauntletInfo gauntlet ) { if ( IsGrunt( npc ) ) { // TODO- pulse as player runs through course, so the effect highlights the different ranges where the enemies are Highlight_SetEnemyHighlightWithParam1( npc, "gauntlet_target_highlight", npc.EyePosition() ) npc.SetHealth( 1 ) npc.SetCanBeMeleeExecuted( false ) } npc.EndSignal( "OnDeath" ) thread Gauntlet_NPC_DeathWait( npc, gauntlet ) AddEntityCallback_OnDamaged( npc, Gauntlet_NPC_Damaged ) npc.SetNoTarget( true ) npc.SetEfficientMode( true ) npc.SetHologram() npc.SetDeathActivity( "ACT_DIESIMPLE" ) wait RandomFloatRange( 0.5, 1.0 ) // This is no good, too variable. TODO put in a pose instead npc.Freeze() } void function Gauntlet_NPC_Damaged( entity npc, var damageInfo ) { printt( "NPC Damaged!", npc.GetHealth() ) float dmg = DamageInfo_GetDamage( damageInfo ) float finalHealth = npc.GetHealth() - dmg if ( finalHealth <= 0 ) Gauntlet_UnfreezeNPC( npc ) } void function Gauntlet_NPC_DeathWait( entity npc, GauntletInfo gauntlet ) { gauntlet.signalEnt.EndSignal( "DisableGauntlet" ) npc.WaitSignal( "OnDeath" ) EmitSoundAtPosition( TEAM_UNASSIGNED, npc.GetOrigin(), "holopilot_impacts_training" ) npc.Dissolve( ENTITY_DISSOLVE_PHASESHIFT, Vector( 0, 0, 0 ), GAUNTLET_TARGET_DISSOLVE_TIME_MS ) if ( !gauntlet.isActive ) return if ( gauntlet.runFinished ) return gauntlet.enemiesKilled++ Remote_CallFunction_Replay( gauntlet.player, "ScriptCallback_Gauntlet_SetEnemyInfo", gauntlet.id, gauntlet.spawners.len(), gauntlet.enemiesKilled ) } void function Gauntlet_UnfreezeNPC( entity npc ) { if ( !npc.IsFrozen() ) return npc.Unfreeze() } string function EnableAllGauntlets() { foreach ( idx, gauntlet in GetGauntlets() ) EnableGauntlet( gauntlet ) return( "All gauntlets enabled" ) } string function DisableAllGauntlets() { foreach ( idx, gauntlet in GetGauntlets() ) DisableGauntlet( gauntlet ) return( "All gauntlets disabled" ) } void function Gauntlet_Think( GauntletInfo gauntlet ) { gauntlet.signalEnt.EndSignal( "DisableGauntlet" ) OnThreadEnd( function() : ( gauntlet ) { Gauntlet_ResetTrackerStats( gauntlet ) if ( gauntlet.player && !gauntlet.runFinished ) Gauntlet_AbortRun( gauntlet ) } ) while ( 1 ) { thread Gauntlet_StartTrigThink( gauntlet ) waitthread Gauntlet_WaitForPlayerToStart( gauntlet ) Gauntlet_ResetTrackerStats( gauntlet ) Gauntlet_StartRun( gauntlet ) thread Gauntlet_HandlePlayerForceRestart( gauntlet ) waitthread Gauntlet_WaitForStop( gauntlet ) waitthread Gauntlet_StopRun( gauntlet ) } } void function Gauntlet_StartRun( GauntletInfo gauntlet ) { printt( "Gauntlet Run Started for player " + gauntlet.player ) RestockPlayerAmmo( gauntlet.player ) EmitSoundOnEntityOnlyToPlayer( gauntlet.player, gauntlet.player, "training_scr_gaunlet_start" ) gauntlet.isActive = true gauntlet.startTime = Time() gauntlet.player.Signal( "Gauntlet_RunStarted" ) gauntlet.signalEnt.Signal( "Gauntlet_RunStarted" ) level.ui.playerRunningGauntlet = true Remote_CallFunction_Replay( gauntlet.player, "ScriptCallback_Gauntlet_StartRun", gauntlet.id ) Remote_CallFunction_Replay( gauntlet.player, "ScriptCallback_Gauntlet_SetEnemyInfo", gauntlet.id, gauntlet.spawners.len(), 0 ) thread Gauntlet_SpawnNPCs( gauntlet ) thread Gauntlet_Checkpoints( gauntlet ) } void function Gauntlet_StopRun( GauntletInfo gauntlet ) { gauntlet.isActive = false level.ui.playerRunningGauntlet = false string feedbackSound = "" ResetPlayerHealthAndStatus( gauntlet.player ) if ( !gauntlet.runFinished ) { Gauntlet_AbortRun( gauntlet ) feedbackSound = "training_scr_gaunlet_abort" } else { Gauntlet_FinishRun( gauntlet ) if ( gauntlet.lastRunDefeatedGhost ) feedbackSound = "training_scr_gaunlet_high_score" else if ( gauntlet.lastRunBestTime ) feedbackSound = "training_scr_gaunlet_high_score" else feedbackSound = "training_scr_gaunlet_end" if ( feedbackSound != "" && IsAlive( gauntlet.player ) ) EmitSoundOnEntityOnlyToPlayer( gauntlet.player, gauntlet.player, feedbackSound ) } wait 0.1 // let the gauntlet finish and count NPCs remaining before killing the remainder Gauntlet_ClearSpawnedNPCs( gauntlet ) thread ClearDroppedWeapons( GAUNTLET_TARGET_DISSOLVE_TIME + 0.1 ) // needs to be longer than gauntlet ghost dissolve time so weapons drop if ( IsValid( gauntlet.player ) ) ClearActiveProjectilesForTeam( gauntlet.player.GetTeam() ) // need to wait before firing final signal, so this signal doesn't kill Gauntlet_HandlePlayerForceRestart if ( IsValid( gauntlet.player ) ) gauntlet.player.Signal( "Gauntlet_RunStopped" ) if ( IsValid( gauntlet.signalEnt ) ) gauntlet.signalEnt.Signal( "Gauntlet_RunStopped" ) wait 0.1 // let other threads catch the signals and check the gauntlet struct before ResetTrackerStats } void function ResetPlayerHealthAndStatus( entity player ) { if ( !IsAlive( player ) ) return player.SetHealth( player.GetMaxHealth() ) array statusEffectsToStop = [] statusEffectsToStop.append( eStatusEffect.emp ) statusEffectsToStop.append( eStatusEffect.move_slow ) statusEffectsToStop.append( eStatusEffect.turn_slow ) foreach ( statusEffect in statusEffectsToStop ) { if ( StatusEffect_Get( player, statusEffect ) > 0.0 ) StatusEffect_StopAll( player, statusEffect ) // arc grenade stun } } void function Gauntlet_FinishRun( GauntletInfo gauntlet ) { RestockPlayerAmmo( gauntlet.player ) float elapsedTime = Time() - gauntlet.startTime printt( "Gauntlet Run Finished, elapsed time", elapsedTime ) // time penalties for missed enemies float enemiesMissedTimePenalty = 0.0 if ( gauntlet.spawners.len() > gauntlet.enemiesKilled ) { int numEnemiesRemaining = gauntlet.spawners.len() - gauntlet.enemiesKilled enemiesMissedTimePenalty = ( numEnemiesRemaining.tofloat() * GAUNTLET_ENEMY_MISSED_TIME_PENALTY ) elapsedTime += enemiesMissedTimePenalty } // check if new best time was set gauntlet.lastRunTime = elapsedTime if ( gauntlet.bestTime == -1.0 || elapsedTime < gauntlet.bestTime ) { printt( "New best time!" ) gauntlet.bestTime = elapsedTime gauntlet.lastRunBestTime = true // if there's a player ghost (for leaderboard), update its duration if ( gauntlet.hasPlayerGhost ) { // update player ghost GauntletGhost playerGhost = Gauntlet_GetPlayerGhost( gauntlet ) Gauntlet_SetGhostDuration( gauntlet, playerGhost, gauntlet.bestTime ) } } // did player beat a ghost racer? if ( Gauntlet_HasActiveGhost( gauntlet ) ) { GauntletGhost activeGhost = Gauntlet_GetActiveGhost( gauntlet ) if ( gauntlet.lastRunTime < activeGhost.duration ) { printt( "player beat active ghost!" ) gauntlet.lastRunDefeatedGhost = true } } Remote_CallFunction_Replay( gauntlet.player, "ScriptCallback_Gauntlet_FinishRun", gauntlet.id, elapsedTime, gauntlet.bestTime, enemiesMissedTimePenalty ) } void function Gauntlet_AbortRun( GauntletInfo gauntlet ) { entity player = gauntlet.player if ( !IsValid( player ) ) return RestockPlayerAmmo_Silent( gauntlet.player ) EmitSoundOnEntityOnlyToPlayer( player, player, "training_scr_gaunlet_abort" ) Remote_CallFunction_Replay( player, "ScriptCallback_Gauntlet_AbortRun", gauntlet.id ) } void function Gauntlet_WaitForPlayerToStart( GauntletInfo gauntlet ) { WaitSignal( gauntlet.signalEnt, "Gauntlet_PlayerHitStartTrig" ) Assert( IsValid( gauntlet.player ) ) } entity function Gauntlet_StartTrigThink( GauntletInfo gauntlet ) { entity trigStart = gauntlet.trigStart entity trigStart2 = gauntlet.trigStart2 EndSignal( gauntlet.signalEnt, "OnDestroy" ) EndSignal( trigStart, "OnDestroy" ) EndSignal( trigStart2, "OnDestroy" ) EndSignal( gauntlet.signalEnt, "Gauntlet_RunStopped" ) table result string signal entity player //printt( "WaitForPlayerToHitStartTrig started" ) // "trigStart_OnStartTouch", "trigStart_OnEndTouch", "trigStart2_OnStartTouch", "trigStart2_OnEndTouch" thread Gauntlet_PlayerStartSignals( gauntlet, trigStart, "trigStart_" ) thread Gauntlet_PlayerStartSignals( gauntlet, trigStart2, "trigStart2_" ) while ( 1 ) { entity alreadyTouchingEnt = null foreach ( p in GetPlayerArray() ) { if ( trigStart.IsTouching( p ) && Gauntlet_EntCanActivateGauntletTrigger( p ) ) { alreadyTouchingEnt = p break } } if ( IsValid( alreadyTouchingEnt ) ) { player = alreadyTouchingEnt } else { //printt( "Waiting for trigStart OnStartTouch" ) result = WaitSignal( trigStart, "OnStartTouch" ) player = expect entity( result.activator ) } if ( !Gauntlet_EntCanActivateGauntletTrigger( player ) ) continue if ( !IsAlive( player ) ) continue //printt( "WAITING for trigStart_OnEndTouch" ) while ( IsAlive( player ) ) { WaitSignal( player, "trigStart_OnEndTouch" ) //printt( "RECEIVED trigStart_OnEndTouch" ) // player exited start trig without running gauntlet if ( !trigStart2.IsTouching( player ) ) { player.Signal( "Gauntlet_PlayerWentBackwardsThroughStartTrig" ) continue } //printt( "WAITING for trigStart_OnStartTouch or trigStart2_OnEndTouch" ) // player is now in trig2 result = WaitSignal( player, "trigStart_OnStartTouch", "trigStart2_OnEndTouch" ) signal = expect string( result.signal ) if ( signal == "trigStart2_OnEndTouch" ) { //printt( "RECEIVED trigStart2_OnEndTouch" ) // player exited trig2 without touching trig1, so we know they started the gauntlet if ( !trigStart.IsTouching( player ) ) { //printt( "SENDING Gauntlet_PlayerHitStartTrig" ) gauntlet.signalEnt.Signal( "Gauntlet_PlayerHitStartTrig" ) gauntlet.player = player } } } } } void function Gauntlet_PlayerStartSignals( GauntletInfo gauntlet, entity trig, string signalPrefix ) { EndSignal( trig, "OnDestroy" ) EndSignal( gauntlet.signalEnt, "Gauntlet_RunStopped" ) /* OnThreadEnd( function() : ( ) { printt( "Gauntlet_PlayerStartSignals ENDED" ) } ) printt( "PlayerStartSignals started" ) */ while ( 1 ) { table result = WaitSignal( trig, "OnStartTouch", "OnEndTouch" ) string signal = expect string( result.signal ) entity activator = expect entity( result.activator ) if ( !Gauntlet_EntCanActivateGauntletTrigger( activator ) ) continue string outboundSignal = signalPrefix if ( signal == "OnStartTouch" ) outboundSignal += "OnStartTouch" else if ( signal == "OnEndTouch" ) outboundSignal += "OnEndTouch" Assert( outboundSignal != signalPrefix ) Signal( activator, outboundSignal ) } } void function Gauntlet_WaitForStop( GauntletInfo gauntlet ) { gauntlet.player.EndSignal( "OnDeath" ) gauntlet.player.EndSignal( "Gauntlet_PlayerWentBackwardsThroughStartTrig" ) gauntlet.player.EndSignal( "Gauntlet_ForceRestart" ) gauntlet.signalEnt.EndSignal( "DisableGauntlet" ) table result entity activator while ( 1 ) { result = gauntlet.trigFinish.WaitSignal( "OnStartTouch" ) activator = expect entity( result.activator ) if ( !activator.IsPlayer() ) continue if ( activator != gauntlet.player ) continue gauntlet.player.Signal( "Gauntlet_PlayerHitFinishTrig" ) gauntlet.runFinished = true if ( gauntlet.checkpoints.len() && gauntlet.checkpointsHit < gauntlet.checkpoints.len() ) gauntlet.runFinished = false break } } bool function Gauntlet_EntCanActivateGauntletTrigger( entity ent ) { if ( !ent.IsPlayer() ) return false if ( !IsAlive( ent ) ) return false if ( ent.IsTitan() ) return false return true } void function Gauntlet_HandlePlayerForceRestart( GauntletInfo gauntlet ) { gauntlet.player.EndSignal( "OnDestroy" ) gauntlet.player.EndSignal( "Gauntlet_RunStopped" ) gauntlet.player.WaitSignal( "Gauntlet_ForceRestart" ) thread Gauntlet_TeleportPlayerToStart( gauntlet ) } void function Gauntlet_TeleportPlayerToStart( GauntletInfo gauntlet ) { entity player = gauntlet.player entity startpoint = gauntlet.startpoint if ( !IsAlive( player ) ) return if ( !IsValid( startpoint ) ) return EndSignal( player, "OnDestroy" ) // wait for quick death to finish before continuing printt( "player doing quick death (1)?", player.p.doingQuickDeath ) while ( player.p.doingQuickDeath ) wait 0.1 //printt( "starting reset fade" ) float fadeTime = 0.1 float holdTime = 0.3 ScreenFadeToBlack( player, fadeTime, holdTime ) player.FreezeControlsOnServer() player.SetVelocity( <0,0,0> ) OnThreadEnd( function() : ( player, gauntlet ) { if ( IsValid( player ) ) { player.UnfreezeControlsOnServer() player.UnforceStand() thread Gauntlet_TeleportFailsafe( player, gauntlet ) } } ) wait fadeTime // again, wait for quick death to finish before continuing since it could have started during fadeTime printt( "player doing quick death (2)?", player.p.doingQuickDeath ) while ( player.p.doingQuickDeath ) wait 0.1 printt( "moving player back to start" ) player.FreezeControlsOnServer() // just in case they were unfrozen by quick death ending since we started waiting player.SetOrigin( OriginToGround( startpoint.GetOrigin() + <0,0,1> ) ) player.SetAngles( startpoint.GetAngles() ) player.SetVelocity( <0,0,0> ) player.ForceStand() wait holdTime } // HACK this is in case the quick death teleport happens on the exact same server frame as the gauntlet restart teleport void function Gauntlet_TeleportFailsafe( entity player, GauntletInfo gauntlet ) { // HACK this breaks in other levels that don't have the flag trigger // in the future set this up as a gauntlet setting if ( GetMapName() != "sp_training" ) return EndSignal( player, "OnDestroy" ) wait 0.5 if ( !gauntlet.isActive && !Flag( "PlayerInGauntletEntryway" ) ) { printt( "Gauntlet reset FAILSAFE!" ) thread Gauntlet_TeleportPlayerToStart( gauntlet ) } } bool function ClientCommand_Gauntlet_PlayerRestartedFromMenu( entity player, array args ) { player.Signal( "Gauntlet_ForceRestart" ) return true } void function Gauntlet_CreateSignalEnt( GauntletInfo gauntlet ) { Assert( !IsValid( gauntlet.signalEnt ) ) entity signalEnt = CreateEntity( "info_target" ) DispatchSpawn( signalEnt ) gauntlet.signalEnt = signalEnt } void function Gauntlet_CleanupSignalEnt( GauntletInfo gauntlet ) { gauntlet.signalEnt.Destroy() gauntlet.signalEnt = null } void function Gauntlet_ResetTrackerStats( GauntletInfo gauntlet ) { gauntlet.startTime = -1 gauntlet.runFinished = false gauntlet.lastRunBestTime = false gauntlet.lastRunDefeatedGhost = false gauntlet.enemiesKilled = 0 } // ===== GHOST RECORDINGS ===== void function Gauntlet_StartGhostPlayback( GauntletInfo gauntlet, string ghostFileName, string ghostDisplayName = "" )//, bool waitForPlayerToStartFirstRun = true ) { gauntlet.signalEnt.Signal( "GhostAnimationPlayback_Start" ) gauntlet.signalEnt.EndSignal( "GhostAnimationPlayback_Start" ) gauntlet.signalEnt.EndSignal( "GhostAnimationPlayback_Stop" ) gauntlet.signalEnt.EndSignal( "DisableGauntlet" ) GauntletGhost ghostInfo = Gauntlet_GetGhostByFileName( gauntlet, ghostFileName ) var rec = LoadRecordedAnimation( ghostInfo.fileAsset ) float duration = GetRecordedAnimationDuration( rec ) printt( "duration is", duration ) Gauntlet_SetActiveGhostID( gauntlet, ghostInfo.id ) entity animRef = gauntlet.startpoint bool createdIdleRef = false entity idleRef if ( gauntlet.ghostAttractSpot != null ) { idleRef = gauntlet.ghostAttractSpot } else { createdIdleRef = true idleRef = CreateScriptMover( animRef.GetOrigin(), animRef.GetAngles() ) DropToGround( idleRef ) } table g = {} OnThreadEnd( function() : ( g, idleRef, createdIdleRef, gauntlet ) { if ( IsValid( g[0] ) ) { g[0].Anim_Stop() StopSoundOnEntity( g[0], "PathHologram_Sustain_Loop_3P" ) DissolveGhost( g[0] ) } if ( createdIdleRef && IsValid( idleRef ) ) idleRef.Destroy() Gauntlet_ClearActiveGhost( gauntlet ) } ) entity ghost entity ghostWeapon bool isFirstRun = true while ( 1 ) { if ( IsValid( ghost ) ) { StopSoundOnEntity( ghost, "PathHologram_Sustain_Loop_3P" ) DissolveGhost( ghost ) } ghost = CreateGhost( idleRef.GetOrigin(), ghostDisplayName ) g[0] <- ghost //ghost.SetTitle( "Ghost Runner" ) //ShowName( ghost ) // not working ghostWeapon = Ghost_GetWeaponEnt( ghost ) ghostWeapon.kv.VisibilityFlags = ENTITY_VISIBLE_TO_NOBODY thread PlayAnimTeleport( ghost, "pt_OG_training_stand", idleRef ) if ( !gauntlet.isActive ) gauntlet.signalEnt.WaitSignal( "Gauntlet_RunStarted" ) float startTime = Time() ghostWeapon.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE EmitSoundOnEntity( ghost, "PathHologram_Sustain_Loop_3P" ) ghost.Anim_Stop() ghost.PlayRecordedAnimation( rec, <0,0,0>, <0,0,0>, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME, animRef ) thread GhostPlayback_HideGhostIfPlayerIsNear( ghost, ghostWeapon ) float ghostFadeTime = 1.2 float waitBeforeFade = duration - ghostFadeTime WaitSignalTimeout( gauntlet.signalEnt, waitBeforeFade, "Gauntlet_RunStopped" ) // ended prematurely if ( Time() - startTime < waitBeforeFade ) ghost.Anim_Stop() isFirstRun = false } } void function GhostPlayback_HideGhostIfPlayerIsNear( entity ghost, entity ghostWeapon ) { EndSignal( ghost, "OnDestroy" ) EndSignal( ghostWeapon, "OnDestroy" ) const float TICK_WAIT = 0.1 while ( 1 ) { wait TICK_WAIT entity nearbyPlayer array players = GetPlayerArray() foreach ( player in players ) { if ( !IsAlive( player ) ) continue if ( PlayerTooCloseToGhost( player, ghost ) ) { nearbyPlayer = player break } } if ( IsValid( nearbyPlayer ) ) { ghost.kv.VisibilityFlags = ENTITY_VISIBLE_TO_NOBODY ghostWeapon.kv.VisibilityFlags = ENTITY_VISIBLE_TO_NOBODY while ( PlayerTooCloseToGhost( nearbyPlayer, ghost ) ) wait TICK_WAIT ghost.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE ghostWeapon.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE } } } bool function PlayerTooCloseToGhost( entity player, entity ghost ) { if ( !IsAlive( player ) ) return false const float CLOSE_DIST = 64.0 if ( Distance( player.EyePosition(), ghost.GetOrigin() ) <= CLOSE_DIST ) return true if ( Distance( player.EyePosition(), ghost.EyePosition() ) <= CLOSE_DIST ) return true return false } void function Gauntlet_StopGhostPlayback( GauntletInfo gauntlet ) { gauntlet.signalEnt.Signal( "GhostAnimationPlayback_Stop" ) } // - Player climbs the leaderboard as her best run time improves // - Skips challenging ghosts whose times are worse than the player's void function Gauntlet_ChallengeLeaderboardGhosts( entity player, GauntletInfo gauntlet, string endFlag ) { if ( Flag( endFlag ) ) return FlagEnd( endFlag ) player.EndSignal( "OnDestroy" ) gauntlet.signalEnt.EndSignal( "OnDestroy" ) GauntletGhost playerGhost = Gauntlet_GetPlayerGhost( gauntlet ) int currPlayerIdx = GAUNTLET_LEADERBOARD_MAX_ENTRIES - 1 int nextGhostIdx = currPlayerIdx - 1 while ( currPlayerIdx > 0 ) { array leaderboard = Gauntlet_GetLeaderboard( gauntlet ) // get current player leaderboard position int maxLeaderboardIdx = leaderboard.len() - 1 if ( currPlayerIdx >= maxLeaderboardIdx ) currPlayerIdx = maxLeaderboardIdx foreach ( idx, leaderboardGhost in leaderboard ) { if ( leaderboardGhost.fileName == playerGhost.fileName ) currPlayerIdx = idx } // player is top of the leaderboard, stop racing ghosts if ( currPlayerIdx <= 0 ) break // if player is not top of leaderboard, cue the ghost above player leaderboard position int nextGhostIdx = currPlayerIdx - 1 GauntletGhost ghost = leaderboard[ nextGhostIdx ] Assert( ghost.fileName != GHOST_NAME_PLAYER, "Can't race against own player ghost- no anim recording asset" ) thread Gauntlet_StartGhostPlayback( gauntlet, ghost.fileName, ghost.displayName ) if ( !gauntlet.isActive ) WaitSignal( player, "Gauntlet_RunStarted" ) // wait for run to stop WaitSignal( player, "Gauntlet_RunStopped" ) } Gauntlet_ClearActiveGhost( gauntlet ) gauntlet.allGhostsDefeated = true } #if DEV void function Gauntlet_Player_GhostRecordOrPlayback( entity player, GauntletInfo gauntlet, string ghostFileName ) { if ( GetBugReproNum() == 55 ) { thread Gauntlet_Player_StartRecordingGhost( player, gauntlet, ghostFileName ) } else { thread Gauntlet_StartGhostPlayback( gauntlet, ghostFileName ) GauntletGhost ghost = Gauntlet_GetGhostByFileName( gauntlet, ghostFileName ) Dev_PrintMessage( player, "Ghost Playback:", "TO RECORD, set bug_reproNum 55", 4.0 ) wait 4.0 Dev_PrintMessage( player, ghost.displayName, "TO RECORD, set bug_reproNum 55", 4.0 ) } } void function Gauntlet_Player_StartRecordingGhost( entity player, GauntletInfo gauntlet, string ghostFileName ) { player.Signal( "RecordAnimation_Start" ) player.EndSignal( "RecordAnimation_Start" ) player.EndSignal( "OnDestroy" ) entity animRef = gauntlet.startpoint GauntletGhost ghost = Gauntlet_GetGhostByFileName( gauntlet, ghostFileName ) thread Gauntlet_StartGhostPlayback( gauntlet, ghostFileName ) while ( 1 ) { #if PC_PROG thread Gauntlet_Player_StartRecordingGhost_Hints( player, gauntlet, ghost ) #endif printt( "READY TO RECORD:", ghost.fileName ) player.WaitSignal( "Gauntlet_RunStarted" ) player.StartRecordingAnimation( animRef.GetOrigin(), animRef.GetAngles() ) printt( "RECORDING STARTED:", ghost.fileName ) player.WaitSignal( "Gauntlet_RunStopped" ) var recording = player.StopRecordingAnimation() if ( !gauntlet.runFinished ) continue if ( gauntlet.enemiesKilled < gauntlet.spawners.len() ) { Dev_PrintMessage( player, "RECORDING NOT SAVED!", "Must kill all the enemies on your run to save.", 7.0 ) printt( "!!!! RECORDED ANIM NOT SAVED!!!!" ) continue } #if PC_PROG SaveRecordedAnimation( recording, ghost.fileName ) Dev_PrintMessage( player, "Anim Data Saved", "BAKE and CLEAR BUG REPRO NUM and RELOAD LEVEL to play it back.", 5.5 ) printt( "RECORDED ANIM SAVED:", ghost.fileName ) wait 5.5 thread Gauntlet_StartGhostPlayback( gauntlet, ghostFileName ) #endif } } void function Gauntlet_Player_StartRecordingGhost_Hints( entity player, GauntletInfo gauntlet, GauntletGhost ghost ) { player.Signal( "Player_StartRecordingGhost_HintStart" ) player.EndSignal( "Player_StartRecordingGhost_HintStart" ) player.EndSignal( "OnDestroy" ) Dev_PrintMessage( player, "Ready To Record Ghost:", "FINISH Gauntlet and kill ALL TARGETS to SAVE GHOST.", 3.0 ) wait 3.0 Dev_PrintMessage( player, ghost.displayName, "FINISH Gauntlet and kill ALL TARGETS to SAVE GHOST.", 5.0 ) } #endif //DEV