diff options
Diffstat (limited to 'Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut')
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut | 1054 |
1 files changed, 1054 insertions, 0 deletions
diff --git a/Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut b/Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut new file mode 100644 index 00000000..4cde3094 --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut @@ -0,0 +1,1054 @@ +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<entity> 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<int> 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<string> 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<int,entity> 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<entity> 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<GauntletGhost> 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
\ No newline at end of file |