diff options
Diffstat (limited to 'Northstar.Coop/scripts/vscripts/sp')
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/_base_gametype_sp.gnut | 657 | ||||
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/_coop_sp_utils.gnut | 177 | ||||
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/_gamestate_sp.gnut | 793 | ||||
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut | 1054 | ||||
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/_savegame.gnut | 860 | ||||
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_spoke02.nut | 3891 | ||||
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_utility.nut | 7253 | ||||
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/sh_coop_sp_utils.gnut | 34 | ||||
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/sh_sp_objective.gnut | 537 | ||||
-rw-r--r-- | Northstar.Coop/scripts/vscripts/sp/sp_training.nut | 7554 |
10 files changed, 22810 insertions, 0 deletions
diff --git a/Northstar.Coop/scripts/vscripts/sp/_base_gametype_sp.gnut b/Northstar.Coop/scripts/vscripts/sp/_base_gametype_sp.gnut new file mode 100644 index 00000000..bdfa895a --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/_base_gametype_sp.gnut @@ -0,0 +1,657 @@ +untyped +global function BaseGametype_Init_MPSP +global function CodeCallback_OnWeaponAttack +global function CodeCallback_OnPlayerMatchmakingChanged +global function CodeCallback_OnClientConnectionCompleted +global function CodeCallback_OnClientDisconnected +global function CodeCallback_OnPlayerRespawned +global function CodeCallback_OnWeaponTouch +global function CodeCallback_WeaponDropped +global function DecideRespawnPlayer +global function ShouldEntTakeDamage_SPMP +global function ShouldUseReplacementSpawn +global function TryGameModeAnnouncement +global function CodeCallback_OnClientConnectionStarted +global function CodeCallback_OnPlayerKilled +global function CreateNoSpawnArea +global function DeleteNoSpawnArea +global function IsSpawnpointValidDrop +global function ClientCommand_OpenDifficultyMenu +global function CodeCallback_PlayerHasBeenConnectedForDuration +global function OnPlayerKilled_DeathNotify +global function GetSPLevelEnumForMapname + +global void functionref( entity, entity ) hackRespawnPlayerFunc + +struct +{ + float lastObitMsgTime = 0.0 + int wallRunKills = 0 + int slideKills = 0 + float lastMultiKillStartTime = 0.0 + int multiKillCount = 0 +} file + +void function BaseGametype_Init_MPSP() +{ + AddClientCommandCallback( "ClientCommand_OpenDifficultyMenu", ClientCommand_OpenDifficultyMenu ) + + AddCallback_OnPlayerKilled( OnPlayerKilled_DeathNotify ) + AddCallback_OnPlayerInventoryChanged( RefreshWeaponHighlights ) + AddCallback_OnPilotBecomesTitan( RefreshWeaponHighlightTitanTransfer ) + AddCallback_OnTitanBecomesPilot( RefreshWeaponHighlightTitanTransfer ) + + if ( GetMapName() != "sp_training" ) + { + AddDeathCallback( "npc_soldier", OnNPCKilled ) + AddDeathCallback( "npc_spectre", OnNPCKilled ) + AddDeathCallback( "npc_prowler", OnNPCKilled ) + AddDeathCallback( "npc_titan", OnNPCKilled ) + AddDeathCallback( "npc_stalker", OnNPCKilled ) + AddDeathCallback( "npc_drone", OnNPCKilled ) + AddDeathCallback( "npc_frag_drone", OnNPCKilled ) + AddDeathCallback( "npc_turret_sentry", OnNPCKilled ) + } + + RegisterSignal( "RevertToRegularHighlight" ) + + FlagInit( "WeaponDropsAllowed" ) + FlagSet( "WeaponDropsAllowed" ) + + hackRespawnPlayerFunc = HackRespawnPlayer +} + +void function HackRespawnPlayer( entity player, entity respawnOn ) +{ + player.RespawnPlayer( null ) +} + +void function CodeCallback_OnWeaponAttack( entity player, entity weapon, string weaponName, int ammoUsed ) +{ + +} + +void function CodeCallback_OnPlayerMatchmakingChanged( entity player ) +{ + +} + +// playerconnected +void function CodeCallback_OnClientConnectionCompleted( entity player ) +{ + if ( IsLobby() ) + { + level.Lobby_OnClientConnectionCompleted( player ) + return + } + + player.hasConnected = true + + InitMeleeAnimEventCallbacks( player ) + ZiplineInit( player ) + + FinishClientScriptInitialization( player ) + + // Added via AddCallback_OnClientConnected + foreach ( callbackFunc in svGlobal.onClientConnectedCallbacks ) + { + callbackFunc( player ) + } + + if ( !Flag( "PlayerDidSpawn") ) + { + __PlayerDidSpawn( player ) + svGlobal.levelEnt.Signal( "PlayerDidSpawn", { player = player } ) + } + + // Player was already positioned at info_player_start in SpPlayerConnecting. + // Don't reposition him, in case movers have already pushed him. + player.RespawnPlayer( null ) + AddPlayerMovementEventCallback( player, ePlayerMovementEvents.BEGIN_WALLRUN, Callback_WallrunBegin ) +} + +void function CodeCallback_OnClientDisconnected( entity player, string reason ) +{ + if ( IsLobby() ) + { + player.Signal( "_disconnectedInternal" ) + UpdateBadRepPresent() + return + } + + if ( !player.hasConnected ) + return + + // Added via AddCallback_OnClientDisconnected + foreach ( callbackFunc in svGlobal.onClientDisconnectedCallbacks ) + { + callbackFunc( player ) + } + + player.Disconnected() + player.p.isDisconnected = true + player.CleanupMPClasses() +} + +int function GetSPLevelEnumForMapname( string mapName ) +{ + switch ( mapName ) + { + case "sp_training": + return eSPLevel.TRAINING + + case "sp_crashsite": + return eSPLevel.WILDS + + case "sp_sewers1": + return eSPLevel.SEWERS + + case "sp_boomtown_start": + case "sp_boomtown": + case "sp_boomtown_end": + return eSPLevel.BOOM_TOWN + + case "sp_hub_timeshift": + case "sp_timeshift_spoke02": + return eSPLevel.TIME_SHIFT + + case "sp_beacon": + case "sp_beacon_spoke0": + return eSPLevel.BEACON + + case "sp_tday": + return eSPLevel.TDAY + + case "sp_s2s": + return eSPLevel.SHIP2SHIP + + case "sp_skyway_v1": + return eSPLevel.SKYWAY + + default: + return eSPLevel.UNKNOWN + } + + unreachable +} + +bool function ShouldGiveFullGrenades() +{ + int levelID = GetSPLevelEnumForMapname( GetMapName() ) + if ( levelID == eSPLevel.UNKNOWN ) + return true + + LevelTransitionStruct ornull trans = GetLevelTransitionStruct() + if ( trans == null ) + return true + + expect LevelTransitionStruct( trans ) + if ( trans.levelID != levelID ) + return true + + return false +} + +void function CodeCallback_OnPlayerRespawned( entity player ) +{ + SetHumanRagdollImpactTable( player ) + + player.s.respawnCount++ + player.s.respawnTime = Time() + ClearRecentDamageHistory( player ) + + player.Signal( "OnRespawned" ) + + PilotLoadoutDef loadout = GetPilotLoadoutForCurrentMapSP() + if ( !IsTestMap() ) + PopulatePilotLoadoutFromLevelTrans( loadout ) + GivePilotLoadout( player, loadout ) + + if ( ShouldGiveFullGrenades() ) + { + entity ordnanceWeapon = player.GetOffhandWeapon( OFFHAND_ORDNANCE ) + if ( IsValid( ordnanceWeapon ) ) + { + int maxClip = ordnanceWeapon.GetWeaponPrimaryClipCountMax() + if ( maxClip > 0 ) + ordnanceWeapon.SetWeaponPrimaryClipCount( maxClip ) + } + } + + + LevelTransitionStruct ornull trans = GetLevelTransitionStruct() + if ( trans != null ) + { + expect LevelTransitionStruct( trans ) + if ( trans.pilotHasBattery ) + { + entity battery = Rodeo_CreateBatteryPack() + Rodeo_PilotPicksUpBattery_Silent( player, battery ) + } + } + + NPCTitanInitModeOnPlayerRespawn( player ) + + // Added via AddCallback_OnPlayerRespawned + foreach ( callbackFunc in svGlobal.onPlayerRespawnedCallbacks ) + { + callbackFunc( player ) + } + + player.e.lastAttacker = null +} + +void function DecideRespawnPlayer( entity player ) +{ +} + + +// Returns bool or nothing +function RespawnTitanPilot( entity player ) +{ +} + +bool function ShouldEntTakeDamage_SPMP( entity ent, var damageInfo ) +{ + return true +} + +bool function ShouldUseReplacementSpawn( entity player ) +{ + return false +} + +void function TryGameModeAnnouncement( entity player ) +{ + +} + +void function CodeCallback_OnClientConnectionStarted( entity player ) +{ + // not a real player? + #if DEV + if ( player.GetPlayerName() == "Replay" ) + return + #endif + + if ( IsLobby() ) + { + level.Lobby_OnClientConnectionStarted( player ) + return + } + +// ScreenFade( player, 0, 0, 0, 255, 2.0, 0.5, FFADE_IN | FFADE_PURGE ) + + SetTargetName( player, "player" + player.entindex() ) + + player.p.controllableProjectiles_scriptManagedID = CreateScriptManagedEntArray() + player.p.npcFollowersArrayID = CreateScriptManagedEntArray() + + player.s = {} + player.s.attackerInfo <- {} + player.p.clientScriptInitialized = player.IsBot() + player.s.inPostDeath <- null + player.s.respawnCount <- 0 + player.s.respawnTime <- 0 + player.s.lostTitanTime <- 0 + player.s.cloakedShotsAllowed <- 0 + player.s.startDashMeleeTime <- 0 + player.s.respawnSelectionDone <- true // this gets set to false in postdeaththread but we need it to be true when connecting + player.s.waveSpawnProtection <- false + + player.s.nextStatUpdateFunc <- null + + player.s.activeTrapArrayId <- CreateScriptManagedEntArray() + + player.s.restartBurnCardEffectOnSpawn <- false + player.s.replacementDropInProgress <- false + + player.s.inGracePeriod <- true + + // should I just add these when playing coop? + player.s.usedLoadoutCrate <- false + player.s.restockAmmoTime <- 0 + player.s.restockAmmoCrate <- null + + player.s.autoTitanLastEngageCalloutTime <- 0 + player.s.autoTitanLastEngageCallout <- null + player.s.lastAIConversationTime <- {} // when was a conversation last played? + + player.s.updatedPersistenceOnDisconnect <- false + + player.s.lastFriendlySpawnedOn <- null + player.s.nextWaveSpawnTime <- 0.0 + + player.s.meleeSlowMoEndTime <- 0.0 + + Assert( !player._entityVars ) + + // Added via AddCallback_OnClientConnecting + foreach ( callbackFunc in svGlobal.onClientConnectingCallbacks ) + { + callbackFunc( player ) + } + + // do this after callbacks so we can define our own player script vars in callbacks + InitEntityVars( player ) + + printl( "Player connect started: " + player ) + + thread PilotHealthRegenThinkSP( player ) +} + +void function CodeCallback_OnPlayerKilled( entity player, var damageInfo ) +{ + thread PostDeathThread_SP( player, damageInfo ) +} + +function PostDeathThread_SP( entity player, damageInfo ) +{ + if ( player.p.watchingPetTitanKillReplay ) + return + + float timeOfDeath = Time() + player.p.postDeathThreadStartTime = Time() + + Assert( IsValid( player ), "Not a valid player" ) + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnRespawned" ) + + player.p.deathOrigin = player.GetOrigin() + player.p.deathAngles = player.GetAngles() + + player.s.inPostDeath = true + player.s.respawnSelectionDone = false + + player.cloakedForever = false + player.stimmedForever = false + player.SetNoTarget( false ) + player.SetNoTargetSmartAmmo( false ) + player.ClearExtraWeaponMods() + + entity attacker = DamageInfo_GetAttacker( damageInfo ) + int methodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo ) + + player.p.rematchOrigin = player.p.deathOrigin + if ( IsValid( attacker ) && methodOfDeath == eDamageSourceId.titan_execution ) + { + // execution can throw you out of the map + player.p.rematchOrigin = attacker.GetOrigin() + } + + int attackerViewIndex = attacker.GetIndexForEntity() + + player.SetPredictionEnabled( false ) + player.Signal( "RodeoOver" ) + player.ClearParent() + bool showedDeathHint = ShowDeathHintSP( player, damageInfo ) + + int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo ) + if ( damageSource != eDamageSourceId.fall ) + { + player.StartObserverMode( OBS_MODE_DEATHCAM ) + if ( ShouldSetObserverTarget( attacker ) ) + player.SetObserverTarget( attacker ) + else + player.SetObserverTarget( null ) + } + + foreach( callbackFunc in svGlobal.onPlayerKilledCallbacks ) + { + callbackFunc( player, attacker, damageInfo ) + } + + float reloadExtraDelay + if ( showedDeathHint ) + reloadExtraDelay = 3.2 + + ReloadForMissionFailure( reloadExtraDelay ) +} + + + + +function FinalPlayerUpdate( entity player ) +{ + // every player runs this either after match/evac, or when they disconnect + + // in case you disconnect during final scoreboard + if ( "ranFinalPlayerUpdate" in player.s ) + return + player.s.ranFinalPlayerUpdate <- true +} + +/*------------------------------------------------------------------------ +DUMPSITE FROM OTHER FILES +--------------------------------------------------------------------------*/ + +string function CreateNoSpawnArea( int blockSpecificTeam, int blockEnemiesOfTeam, vector origin, float timeout, float length, width = null, angles = null ) +{ + return "" +} + +void function DeleteNoSpawnArea( string id ) +{ + +} + + +bool function IsSpawnpointValidDrop( entity spawnpoint, int team ) +{ + return true +} + +bool function ClientCommand_OpenDifficultyMenu( entity player, array<string> args ) +{ + if ( IsValid( player ) ) + Remote_CallFunction_UI( player, "ServerCallback_OpenDifficultyMenu" ) + return true +} + + +void function CodeCallback_PlayerHasBeenConnectedForDuration( entity player, float durationInSeconds ) //Empty function declaration to stop load error. +{ +} + +void function OnPlayerKilled_DeathNotify( entity player, entity attacker, var damageInfo ) +{ + EmitSoundOnEntity( player, "Player_Death_Begin" ) +} + +void function CodeCallback_WeaponDropped( entity weapon ) +{ + if ( !IsValid( weapon ) ) + return + + if ( !Flag( "WeaponDropsAllowed" ) ) + { + weapon.Destroy() + return + } + + int loadoutIndex = GetSPTitanLoadoutIndexForWeapon( weapon.GetWeaponClassName() ) + if ( loadoutIndex >= 0 ) + { + if ( IsBTLoadoutUnlocked( loadoutIndex ) ) + { + weapon.Destroy() + return + } + else + { + weapon.MarkAsLoadoutPickup() + } + } + + SetTeam( weapon, TEAM_UNASSIGNED ) + + if ( weapon.IsLoadoutPickup() ) + { + HighlightWeapon( weapon ) + } + else + { + Highlight_SetOwnedHighlight( weapon, "weapon_drop_active" ) + Highlight_SetNeutralHighlight( weapon, "weapon_drop_fresh" ) + thread RevertToRegularHighlight( weapon ) + } +} + +void function RevertToRegularHighlight( entity weapon ) +{ + weapon.Signal( "RevertToRegularHighlight" ) + weapon.EndSignal( "RevertToRegularHighlight" ) + weapon.EndSignal( "OnDestroy" ) + wait 4.0 + HighlightWeapon( weapon ) +} + +void function RefreshWeaponHighlightTitanTransfer( entity player, entity titan ) +{ + RefreshWeaponHighlights( player ) +} + +void function CodeCallback_OnWeaponTouch( entity player, entity weapon, int ammoRecieved ) +{ + if ( !IsAlive( player ) ) + return + + if ( Time() - file.lastObitMsgTime < 0.1 ) + return + + if ( ammoRecieved == 0 ) + { + if ( !PlayerCanUseWeapon( player, weapon.GetWeaponClass() ) ) + return + + if ( !PlayerHasWeapon( player, weapon.GetWeaponClassName() ) ) + return + + MessageToPlayer( player, eEventNotifications.WEAP_AmmoFull, weapon, ammoRecieved ) + } + else + { + MessageToPlayer( player, eEventNotifications.WEAP_GotAmmo, weapon, ammoRecieved ) + } + + file.lastObitMsgTime = Time() +} +void function Callback_WallrunBegin( entity player ) +{ + file.wallRunKills = 0 +} + +void function OnNPCKilled( entity npc, var damageInfo ) +{ + entity player = DamageInfo_GetAttacker( damageInfo ) + + if ( !IsValid( player ) ) + return + + if ( !player.IsPlayer() ) + return + + if ( player.IsTitan() ) + { + if ( IsTitanCrushDamage( damageInfo ) ) + { + thread PlayerTitanCrushKill_TryRumble( player, npc ) + } + + if ( IsHumanSized( npc ) ) + { + TryInfantryMultiKill( player ) + } + + return + } + + entity weapon = DamageInfo_GetWeapon( damageInfo ) + int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo ) + if ( !IsValid( weapon ) ) + return + + if ( weapon.IsWeaponOffhand() && damageSource != eDamageSourceId.sp_weapon_arc_tool ) // special case let arc tool through + return + + if ( player.IsWallRunning() ) + file.wallRunKills++ + + if ( player.IsSliding() ) + { + if ( file.slideKills == 0 ) + { + thread TrackSlideEnd( player ) + } + file.slideKills++ + } + + printt( "Wall Run Kills: " + file.wallRunKills ) + printt( "Slide Kills: " + file.slideKills ) + + if ( file.wallRunKills >= 3 ) + UnlockAchievement( player, achievements.WALLRUN_KILLS ) + + if ( file.slideKills >= 3 ) + UnlockAchievement( player, achievements.SLIDE_KILLS ) +} + +void function PlayerTitanCrushKill_TryRumble( entity player, entity victim ) +{ + player.EndSignal( "OnDeath" ) + + if ( !IsHumanSized( victim ) && !IsProwler( victim ) ) + return + + float rumbleAmplitude = 80.0 + float rumbleFrequency = 150.0 + float rumbleDuration = 0.4 + + // bigger rumble if victim is tougher than a Grunt + if ( victim.IsMechanical() || IsProwler( victim ) ) + { + rumbleAmplitude = 140.0 + rumbleFrequency = 100.0 + rumbleDuration = 0.6 + } + + // delay makes it feel better with the SFX + // (sound starts at same time, but takes a moment to get loud) + wait 0.1 + + CreateAirShakeRumbleOnly( player.GetOrigin(), rumbleAmplitude, rumbleFrequency, rumbleDuration ) + //printt( "CRUSH RUMBLE", rumbleAmplitude ) +} + +void function TrackSlideEnd( entity player ) +{ + player.EndSignal( "OnDeath" ) + + OnThreadEnd( + function() : ( ) + { + file.slideKills = 0 + } + ) + + while ( player.IsSliding() ) + { + wait 0.1 + } +} + +void function TryInfantryMultiKill( entity player ) +{ + if ( file.lastMultiKillStartTime + 2.0 < Time() ) + { + file.lastMultiKillStartTime = Time() + file.multiKillCount = 0 + } + + file.multiKillCount++ + + printt( "Multi Kills: " + file.multiKillCount ) + + if ( file.multiKillCount >= 25 ) + UnlockAchievement( player, achievements.INFANTRY_MULTIKILL ) +}
\ No newline at end of file diff --git a/Northstar.Coop/scripts/vscripts/sp/_coop_sp_utils.gnut b/Northstar.Coop/scripts/vscripts/sp/_coop_sp_utils.gnut new file mode 100644 index 00000000..5783f28f --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/_coop_sp_utils.gnut @@ -0,0 +1,177 @@ +untyped +global const float COOP_RESPAWN_DELAY = 5.0 + +global function CoopSpUtils_Init + +global function GetPlayerToSpawnOn +global function RestartWithoutDroppingPlayers +global function TeleportToEntitySafe +global function GetEntitiesByEditorClass + +global function GetPlayersInTimeline + +void function CoopSpUtils_Init() +{ + AddCallback_OnClientConnecting( OnClientConnecting ) + AddDeathCallback( "player", OnPlayerDeath ) +} + +void function OnClientConnecting( entity player ) +{ + // add custom script vars + if ( IsPlayingTimeshiftLevel() ) + { + player.s.timeline <- 1 // TIMEZONE_NIGHT + player.s.isTimeTraveling <- false + player.s.lastGoodTimeshiftPosOvergrown <- <0, 0, 0> + player.s.lastGoodTimeshiftPosPristine <- <0, 0, 0> + } +} + +void function OnPlayerDeath( entity player, var damageInfo ) +{ + // add respawn delay, use networked entity var for client respawn timer + player.nv.nextRespawnTime = Time() + COOP_RESPAWN_DELAY + + if ( AreAllPlayersDead() ) + thread FailLevel( COOP_RESPAWN_DELAY ) +} + +void function FailLevel( float respawnTime ) +{ + wait respawnTime + RestartWithoutDroppingPlayers() +} + +entity ornull function GetPlayerToSpawnOn() +{ + array< entity > possiblePlayers + foreach ( entity player in GetPlayerArray()) + { + if ( IsAlive( player )) + possiblePlayers.append( player ) + } + + if ( possiblePlayers.len() == 0 ) + return null // everyone is dead + + return possiblePlayers[ RandomInt( possiblePlayers.len() ) ] +} + +void function RestartWithoutDroppingPlayers() +{ + // todo: make this deal with checkpoints/saves + ServerCommand( "changelevel " + GetMapName() ) +} + +void function TeleportToEntitySafe( entity teleported, entity target ) +{ + // teleport to other entities without getting stuck in ceilings and shit + // PLEASE dont call this often, it's weird when used on living players + // was designed only to be used on spawn/respawn code + + vector targetPos = target.GetOrigin() + + // intelligent checks + // is the spot already valid? + if ( !PlayerPosInSolidIgnoreOtherPlayers( teleported, targetPos ) ) + { + print( "TeleportToEntitySafe: pos was valid first time" ) + teleported.SetOrigin( targetPos ) + teleported.SetAngles( target.GetAngles() ) + return + } + else if ( target.IsCrouched() && !teleported.IsCrouched() && !PlayerPosInSolidIgnoreOtherPlayers( teleported, targetPos, true ) ) + { + // teleporting to sliding players while not sliding can cause clipping + // so if we set the player to be sliding maybe it'll kinda sorta work out + + // check if we can just offset ourselves downwards to a valid pos without crouching + // 25 is the diff in collision height between stood up/crouched + if ( !PlayerPosInSolidIgnoreOtherPlayers( teleported, targetPos + <0, 0, -25> ) ) + { + print( "TeleportToEntitySafe: offset to valid position to account for crouch!" ) + teleported.SetOrigin( targetPos + <0, 0, -25> ) + teleported.SetAngles( target.GetAngles() ) + return + } + else + { + print( "TeleportToEntitySafe: couldn't offset to valid pos for crouch, waiting for crouch to finish" ) + teleported.ForceCrouch() + } + } + + // last resort - give up and wait for the pos to be valid + // given this is mainly used for teleporting to players it can probably be abused + + float startTime = Time() + float timeout = 5.0 + + print( "TeleportToEntitySafe: couldn't get a safe pos with starting conditions, waiting for pos to be valid" ) + while ( PlayerPosInSolidIgnoreOtherPlayers( teleported, target.GetOrigin() ) ) + { + WaitFrame() + + if ( Time() - startTime > timeout ) + { + // if we take too long, we probably aren't gonna teleport anyway, so why bother wasting cpu time on it + print( "TeleportToEntitySafe: timed out waiting for successful teleport attempt" ) + return + } + } + + print( "TeleportToEntitySafe: done waiting for pos to be valid!" ) + teleported.SetOrigin( target.GetOrigin() ) + teleported.SetAngles( target.GetAngles() ) + teleported.UnforceCrouch() +} + +bool function PlayerPosInSolidIgnoreOtherPlayers( entity player, vector targetPos, bool fakeCrouch = false ) +{ + // playerposinsolid by default doesn't ignore other players, only the player they're checking for + // this reimplements it, but ignores all players + // don't wanna patch the original function because the original behaviour could be useful + + vector maxs = player.GetPlayerMaxs() + + if ( maxs.z == 32 ) + maxs.z = 72 // maxs seem broken when connecting so make sure they're normal for this + + if ( fakeCrouch ) + maxs.z = 47 // 47 when crouched, 72 uncrouched + + TraceResults traceResult = TraceHull( targetPos, targetPos + <0, 0, 1>, player.GetPlayerMins(), maxs, GetPlayerArray(), TRACE_MASK_PLAYERSOLID, TRACE_COLLISION_GROUP_PLAYER ) + return traceResult.startSolid +} + +array< entity > function GetEntitiesByEditorClass( string editorClass ) +{ + array< entity > ret + for ( int i = 0; i < 2048 /*max ents in source*/; i++ ) + { + entity ent = GetEntByIndex( i ) + if ( ent == null ) + continue + + if ( GetEditorClass( ent ) == editorClass ) + ret.append( ent ) + } + + return ret +} + + +//EFFECT AND CAUSE/TIMESHIFT STUFF + +array< entity > function GetPlayersInTimeline( int timeline ) +{ + array< entity > playersInTimeline + foreach ( entity player in GetPlayerArray() ) + { + if ( player.s.timeline == timeline ) + playersInTimeline.append( player ) + } + + return playersInTimeline +}
\ No newline at end of file diff --git a/Northstar.Coop/scripts/vscripts/sp/_gamestate_sp.gnut b/Northstar.Coop/scripts/vscripts/sp/_gamestate_sp.gnut new file mode 100644 index 00000000..ac741370 --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/_gamestate_sp.gnut @@ -0,0 +1,793 @@ +const RESPAWN_TRIG_RECHECK_WAIT = 1 +const DEFAULT_SP_ARRIVAL_TOLERANCE = 200 +const DEFAULT_SP_ASSAULT_TIMEOUT = 0 + +// move these to auto precache +global const TEAM_MIL_GRUNT_MODEL = $"models/humans/grunts/mlt_grunt_rifle.mdl" +global const TEAM_MIL_GRUNT_MODEL_LMG = $"models/humans/grunts/mlt_grunt_lmg.mdl" +global const TEAM_MIL_GRUNT_MODEL_RIFLE = $"models/humans/grunts/mlt_grunt_rifle.mdl" +global const TEAM_MIL_GRUNT_MODEL_ROCKET = $"models/humans/grunts/mlt_grunt_rifle.mdl" +global const TEAM_MIL_GRUNT_MODEL_SHOTGUN = $"models/humans/grunts/mlt_grunt_shotgun.mdl" +global const TEAM_MIL_GRUNT_MODEL_SMG = $"models/humans/grunts/mlt_grunt_smg.mdl" + + +global function GamemodeSp_Init +global function GiveBatteryChargeTool +global function HasBatteryChargeTool +global function FriendlyFire_MissionFailure +global function DamageAlwaysLethal +global function ServerRestartMission +global function DisableFriendlyHighlight +global function EnableFriendlyHighlight +global function SP_PlayerLastSlideTime + +//global function CodeCallback_Ping + +global function SetGameState +global function GetGameModeAnnouncement + +global function CodeCallback_GamerulesThink + +global struct SvGlobalsSP +{ + int gruntCombatState = eGruntCombatState.IDLE + + array<void functionref( entity )> onLoadSaveGameCallbacks +} + +global SvGlobalsSP svGlobalSP + +////////////////////////////// + +global function DEV_ToggleFriendlyHighlight + +////////////////////////////// + +struct +{ + LevelTransitionStruct ornull storedLevelTransitionStruct + entity spPetTitanStart + bool friendlyHighlightEnabled = true + float playerLastSlideTime +} file + +void function GamemodeSp_Init() +{ + FlagInit( "FriendlyFireStrict" ) + FlagInit( "TitanCanSavePlayer" ) + FlagInit( "TitanDeathPenalityDisabled" ) + FlagInit( "SaveGame_Enabled", true ) + FlagInit( "MissionFailed" ) + RegisterSignal( "TitanSavesPlayer" ) + + FlagSet( "PilotBot" ) + + AddSoulDeathCallback( SoulDeath_ReloadOnPetTitanDeath ) + AddClientCommandCallback( "RestartMission", ClientCommand_RestartMission ) + + SetRoundBased( false ) + + AddCallback_OnClientConnecting( SpPlayerConnecting ) + AddCallback_OnClientConnected( SpPlayerConnected ) + + AddSpawnCallback( "npc_soldier", SpNpcCommon ) + AddSpawnCallback( "npc_spectre", SpNpcCommon ) + AddSpawnCallback( "npc_titan", SpNpcCommon ) + + AddSpawnCallback( "npc_soldier", SpNpcCommonGrunt ) + + AddSpawnCallbackEditorClass( "info_target", "info_pet_titan_start", PetTitanStartSpawnInit ) + AddCallback_OnPlayerRespawned( GameStateSP_OnPlayerRespawn ) + + AddDamageCallbackSourceID( eDamageSourceId.damagedef_titan_fall, PreventFriendlyTitanfallDamage ) + AddDamageCallbackSourceID( eDamageSourceId.damagedef_reaper_fall, PreventFriendlyTitanfallDamage ) + + shGlobal.proto_soldierShieldRegenDelay = 3000.0 + svGlobal.cloakBreaksOnMelee = true + svGlobal.defaultPilotLeechTime = 2.0 + + //AddDamageCallback( "player", DiminishPlayerComboDamage ) + + SPTitanLoadout_SetupForLevelStart() + + SpSharedInit() + + AddCallback_EntitiesDidLoad( EntitiesDidLoad_SpGameState ) + AddDeathCallback( "npc_soldier", SoldierFriendlyFireCheck_OnKilled ) + + AddCallback_OnPilotBecomesTitan( PilotBecomesTitanStoreWeaponVar ) + + // SAVE THIS LEVEL IF IT'S IN THE LEVEL LIST + var dataTable = GetDataTable( $"datatable/sp_levels.rpak" ) + string mapName = GetMapName() + string startPoint = GetStartPoint() + int numRows = GetDatatableRowCount( dataTable ) + int lastLevelNum = 0 + + int bspCol = GetDataTableColumnByName( dataTable, "level" ) + int levelCol = GetDataTableColumnByName( dataTable, "levelNum" ) + + for ( int i=0; i<numRows; i++ ) + { + string levelBsp = GetDataTableString( dataTable, i, bspCol ) + int levelNum = GetDataTableInt( dataTable, i, levelCol ) + + if ( levelBsp == mapName ) + { + #if DEV + printt( "Setting this level as unlocked" ) + printt( "BSP: " + levelBsp ) + printt( "Level Num: " + levelNum ) + #endif + + SetConVarInt( "sp_lastMission", levelNum ) + lastLevelNum = levelNum + break + } + } + + int farthestLevelUnlocked = GetConVarInt( "sp_unlockedMission" ) + + if( Script_IsRunningTrialVersion() ) + SetConVarInt( "sp_unlockedMission", 1 ) // Set to sp_crashsite so if they buy the game, it starts there. + else + SetConVarInt( "sp_unlockedMission", maxint(farthestLevelUnlocked, lastLevelNum) ) + + foreach( entity player in GetPlayerArray() ) + UpdateHeroStatsForPlayer( player ) + + if ( IsTestMap() ) + { + PilotLoadoutDef loadout = GetPilotLoadoutForCurrentMapSP() + foreach ( model in GetModelsFromSetFile( loadout.setFile ) ) + { + PrecacheModel( model ) + } + + TitanLoadoutDef titanLoadout = GetTitanLoadoutForCurrentMap() + foreach ( model in GetModelsFromSetFile( titanLoadout.setFile ) ) + { + PrecacheModel( model ) + } + } +} + + +void function EntitiesDidLoad_SpGameState() +{ + SetGameState( eGameState.Playing ) + FlagSet( "ReadyToStartMatch" ) + + file.storedLevelTransitionStruct = GetLevelTransitionStruct() + + SetCustomIntroLength( 0 ) + +// level.nv.replayDisabled = true //HACK - remove once the bug about replay hud is fixed + level.nv.minimapState = eMinimapState.Hidden + + level.nv.replayDisabled = true + if ( !IsTestMap() ) + level.nv.titanAvailability = eTitanAvailability.Never + + ServerCommand( "sv_weapon_despawn_Time 180" ) +} + +/************************************************************************************************\ + + ###### ####### ## ## ## ## ######## ###### ######## +## ## ## ## ### ## ### ## ## ## ## ## +## ## ## #### ## #### ## ## ## ## +## ## ## ## ## ## ## ## ## ###### ## ## +## ## ## ## #### ## #### ## ## ## +## ## ## ## ## ### ## ### ## ## ## ## + ###### ####### ## ## ## ## ######## ###### ## + +\************************************************************************************************/ +void function SpPlayerConnecting( entity player ) +{ + if ( GetPlayerArray().len() > 1 ) + { + CodeWarning( "Can't play SP with more or less than 1 player" ) + return + } + + SetTeam( player, TEAM_MILITIA ) + InitPassives( player ) + + player.SetInventoryChangedCallbackEnabled( true ) + + entity ornull spawningOn = GetPlayerToSpawnOn() + if ( spawningOn == null ) + { + // vanilla spawning logic - spawn first player at the level's spawnpoint + entity start = GetEnt( "info_player_start" ) + player.SetOrigin( start.GetOrigin() ) + player.SetAngles( start.GetAngles() ) + } + else + { + expect entity( spawningOn ) + // use safe func otherwise we can get stuck in the ceiling and shit + thread TeleportToEntitySafe( player, spawningOn ) + } + + if ( IsValid( file.spPetTitanStart ) ) + { + CreatePetTitan( player ) + entity titan = player.GetPetTitan() + if ( titan != null ) + titan.kv.alwaysAlert = false + } +} + +void function GameStateSP_OnPlayerRespawn( entity player ) +{ + UpdateSpDifficulty( player ) + thread TrackPlayerLastSlideTime( player ) + + if ( !IsTestMap() ) + { + CheckPoint_ForcedSilent() + } +} + +void function SpPlayerConnected( entity player ) +{ + thread SpPlayerConnected_Thread( player ) +} + +void function SpPlayerConnected_Thread( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + if ( IsTestMap() ) + return + + string levelName = GetMapName() + int startIndex = GetStartPointIndexFromName( levelName, GetCurrentStartPoint() ) + + string startPointEnum = GetStartPointNameFromIndex( levelName, startIndex ) + if ( !StartPointHasDetente( levelName, startPointEnum ) ) + return + + var dataTable = GetDataTable( $"datatable/sp_introscreen.rpak" ) + int row = GetDataTableRowMatchingStringValue( dataTable, GetDataTableColumnByName( dataTable, "level" ), levelName ) + if ( row == -1 ) + return + + float delay = GetDataTableFloat( dataTable, row, GetDataTableColumnByName( dataTable, "bgFadeDelay" ) ) + float fadeTime = GetDataTableFloat( dataTable, row, GetDataTableColumnByName( dataTable, "bgFadeTime" ) ) + + printt( "Detent fade in " + delay + " blend " + fadeTime ) + + WaitFrame() + WaitFrame() // wait two frames to fix ScreenFade bug + + ScreenFade( player, 0, 0, 0, 255, fadeTime, delay, FFADE_IN | FFADE_PURGE ) + //printt( "sv SCREENFADE: " + fadeTime + " " + delay ) +} + +float function SP_PlayerLastSlideTime() +{ + return file.playerLastSlideTime +} + +void function PetTitanStartSpawnInit( entity spawn ) +{ + file.spPetTitanStart = spawn +} + +void function TrackPlayerLastSlideTime( entity player ) +{ + player.EndSignal( "OnDeath" ) + + for ( ;; ) + { + if ( player.IsSliding() ) + file.playerLastSlideTime = Time() + wait 0.5 + } +} + +void function CreatePetTitan( entity player ) +{ + Assert( IsValid( file.spPetTitanStart ) ) + // Player is in his Titan, don't create a titan + if ( player.IsTitan() ) + return + + // Player already has a pet titan, don't create one + entity petTitan = player.GetPetTitan() + if ( IsValid( petTitan ) ) + return + + // Make a pet titan at the spawn point + TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap() + entity titanStart = file.spPetTitanStart + entity titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, titanStart.GetOrigin(), titanStart.GetAngles() ) + DispatchSpawn( titan ) + player.SetPetTitan( titan ) + + titan.DisableHibernation() +} + +/************************************************************************************************\ + +######## ### ## ## ### ###### ######## +## ## ## ## ### ### ## ## ## ## ## +## ## ## ## #### #### ## ## ## ## +## ## ## ## ## #### ## ## ## ## ### ###### +## ## ######### ## ## ## ######### ## ## ## +## ## ## ## ## ## ## ## ## ## ## +######## ## ## ## ## ## ## ###### ######## + +\************************************************************************************************/ + +// putting this stuff here for now since it's only for SP at this point + + + + +bool function TryTitanSavesPlayer( entity player ) +{ + if ( !Flag( "TitanCanSavePlayer" ) ) + return false + + if ( !IsAlive( player.GetPetTitan() ) ) + return false + + entity titan = player.GetPetTitan() + if ( TitanIsCurrentlyEmbarkableForPlayer( player, titan ) ) + { + thread TitanSavesPlayer( player, titan ) + return true + } + + return false +} + +void function TitanSavesPlayer( entity player, entity titan ) +{ + player.EndSignal( "OnDestroy" ) + titan.EndSignal( "OnDeath" ) + + player.SetNoTarget( true ) + player.SetInvulnerable() + titan.SetInvulnerable() + + player.DisableWeapon() + player.FreezeControlsOnServer() + player.ForceCrouch() + + OnThreadEnd( + function() : ( player, titan ) + { + if ( IsValid( player ) ) + { + if ( !IsPlayerEmbarking( player ) ) + { + player.EnableWeapon() + } + player.UnforceCrouch() + player.UnfreezeControlsOnServer() + player.ClearInvulnerable() + player.SetNoTarget( false ) + } + + if ( IsValid( titan ) ) + titan.ClearInvulnerable() + } + ) + + wait 0.25 + + float fadeTime = 2 + float blackTime_pre = 4.0 + float blackTime_post = 1.0 + + thread ScreenFadeToBlack( player, 2.0, blackTime_pre ) + + wait blackTime_pre * 0.5 + string titanAlias = GenerateTitanOSAlias( player, "briefCriticalDamage" ) + thread EmitSoundOnEntity( player, titanAlias ) + wait blackTime_pre * 0.5 + + vector ornull clampedPos = NavMesh_ClampPointForAI( player.GetOrigin(), titan ) + if ( clampedPos != null ) + titan.SetOrigin( expect vector( clampedPos ) ) + + titan.SetAngles( Vector( 0, player.GetAngles().y, 0 ) ) + + entity soul = titan.GetTitanSoul() + SetStanceKneel( soul ) + + table criteria = { + embark = "front", + titanCanStandRequired = false + } + + var embarkAction + embarkAction = FindEmbarkActionForCriteria( criteria ) + if ( embarkAction == null ) + embarkAction = GetRandomEmbarkAction() + + table action = expect table( GenerateEmbarkActionTable( player, titan, embarkAction ) ) + thread PlayerEmbarksTitan( player, titan, action ) + thread ScreenFadeFromBlack( player, 12.0, blackTime_post ) + + player.Signal( "TitanSavesPlayer" ) +} + +bool function DamageAlwaysLethal( var damageInfo ) +{ + const damageMask = DF_INSTANT | DF_IMPACT | DF_TITAN_STEP | DF_KILLSHOT | DF_MELEE // | DF_EXPLOSION + if ( DamageInfo_GetCustomDamageType( damageInfo ) & damageMask ) + return true +// not quite sure what this returns. It doesn't seem to be the flags listed in death_package.nut +// if ( DamageInfo_GetDamageType( damageInfo ) & damageMask ) +// return false + + // damage type doesn't seem to be correct for most things so I'm forced to check GetDamageSourceIdentifier -Roger + switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) ) + { + case damagedef_crush: + case damagedef_nuclear_core: + case damagedef_suicide: + case damagedef_titan_fall: + case damagedef_titan_hotdrop: + case eDamageSourceId.bubble_shield: + case eDamageSourceId.burn: + case eDamageSourceId.droppod_impact: + case eDamageSourceId.evac_dropship_explosion: + case eDamageSourceId.fall: + case eDamageSourceId.flash_surge: + case eDamageSourceId.floor_is_lava: + case eDamageSourceId.human_execution: + case eDamageSourceId.human_melee: + case eDamageSourceId.indoor_inferno: + case eDamageSourceId.outOfBounds: + case eDamageSourceId.round_end: + case eDamageSourceId.splat: + case eDamageSourceId.stuck: + case eDamageSourceId.submerged: + case eDamageSourceId.switchback_trap: + case eDamageSourceId.team_switch: + case eDamageSourceId.titan_execution: + case eDamageSourceId.titan_explosion: + case eDamageSourceId.wall_smash: +// case eDamageSourceId.grunt_melee: +// case eDamageSourceId.prowler_melee: +// case eDamageSourceId.spectre_melee: + return true + + default: + break + } + + return false +} + +/************************************************************************************************\ + + ## ## ######## ###### + ### ## ## ## ## ## + #### ## ## ## ## + ## ## ## ######## ## + ## #### ## ## + ## ### ## ## ## + ## ## ## ###### + +\************************************************************************************************/ +void function SpNpcCommon( entity npc ) +{ + if ( npc.GetTeam() == TEAM_MILITIA ) + { + if ( !npc.IsTitan() ) + HideName( npc ) + + // this is a temporary stop gap until we get skins + if ( file.friendlyHighlightEnabled ) + Highlight_SetFriendlyHighlight( npc, "sp_friendly_pilot" ) + } +} + +void function SpNpcCommonGrunt( entity npc ) +{ + // heros clear this setting so they should keep their names + if ( npc.GetTeam() == TEAM_MILITIA ) + { + + string title + if ( npc.Dev_GetAISettingByKeyField( "IsGenericGrunt" ) == 0 ) + { + title = npc.GetSettingTitle() + } + else + { + title = GetMilitiaTitle() + } + + npc.SetTitle( title ) + ShowName( npc ) + } +} + +void function DiminishPlayerComboDamage( entity player, var damageInfo ) +{ + if ( !IsPilot( player ) ) + return + +// printt( "damage " + DamageInfo_GetDamage( damageInfo ) ) +// printt( "shield health " + player.GetShieldHealth() ) + if ( player.GetShieldHealth() > 0 ) + return + + // blunt damage from combos + float damage = DamageInfo_GetDamage( damageInfo ) + + float recentDamage = TotalDamageOverTime_BlendedOut( player, 0.5, 1.5 ) + + // damage is ramped down based on how much damage was taken recently + float damageMod = GraphCapped( recentDamage, 70, 140, 1.0, 0.1 ) + DamageInfo_ScaleDamage( damageInfo, damageMod ) +} + +void function SoulDeath_ReloadOnPetTitanDeath( entity soul, var damageInfo ) +{ + if ( Flag( "TitanDeathPenalityDisabled" ) ) + return + + if ( IsTestMap() ) + return + + thread SoulDeath_ReloadOnPetTitanDeath_Thread( soul, damageInfo ) +} + +void function SoldierFriendlyFireCheck_OnKilled( entity npc, var damageInfo ) +{ + if ( !Flag( "FriendlyFireStrict" ) ) + return + + entity attacker = DamageInfo_GetAttacker( damageInfo ) + if ( !attacker.IsPlayer() ) + return + + if ( npc.GetTeam() != attacker.GetTeam() ) + return + + thread SoldierFriendlyFireCheck_OnKilledDelayed() +} + +void function SoldierFriendlyFireCheck_OnKilledDelayed() +{ + FlagClear( "SaveGame_Enabled" ) // no more saving, you have lost + wait 0.75 + FriendlyFire_MissionFailure() +} + +void function FriendlyFire_MissionFailure() +{ + if ( Flag( "MissionFailed" ) ) + return + + foreach ( player in GetPlayerArray() ) + { + Remote_CallFunction_NonReplay( player, "ServerCallback_FriendlyFire_MissionFailure" ) + } + + ReloadForMissionFailure() +} + +void function SoulDeath_ReloadOnPetTitanDeath_Thread( entity soul, var damageInfo ) +{ + entity player = soul.GetBossPlayer() + + if ( !IsValid( player ) ) + return + + if ( !level.nv.replayDisabled ) + { + if ( player.p.watchingPetTitanKillReplay ) + return + + player.p.watchingPetTitanKillReplay = true + } + + if ( damageInfo == null ) + { + printt( "ServerCallback_TitanDied with null sourceid" ) + ReloadForMissionFailure() + return + } + + int source = DamageInfo_GetDamageSourceIdentifier( damageInfo ) + Remote_CallFunction_NonReplay( player, "ServerCallback_TitanDied", source ) + #if DEV + printt( "ServerCallback_TitanDied with sourceid " + source + " and source string " + GetObitFromDamageSourceID( source ) ) + #endif + + if ( IsInstantDeath( damageInfo ) ) + { + ReloadForMissionFailure() + return + } + entity attacker = DamageInfo_GetAttacker( damageInfo ) + if ( !IsValid( attacker ) ) + { + ReloadForMissionFailure() + return + } + + int index = attacker.GetIndexForEntity() + entity titan = soul.GetTitan() + + FlagClear( "SaveGame_Enabled" ) // no more saving, you have lost + + if ( !level.nv.replayDisabled ) + { + /* + wait 3.8 + float replayDelay = 6.5 + player.SetKillReplayDelay( replayDelay ) + player.SetViewIndex( index ) + + if ( IsValid( titan ) ) + player.SetKillReplayVictim( titan ) + + ReloadForMissionFailure( replayDelay + 2.5 ) + */ + } + else + { + ReloadForMissionFailure() // replayDelay + 2.5 + } +} + +bool function HasBatteryChargeTool( entity player ) +{ + switch ( CHARGE_TOOL ) + { + case "sp_weapon_proto_battery_charger_offhand": + return HasOffhandWeapon( player, CHARGE_TOOL ) + + case "sp_weapon_arc_tool": + return HasWeapon( player, CHARGE_TOOL ) + } + + unreachable +} + +void function GiveBatteryChargeTool( entity player ) +{ + switch ( CHARGE_TOOL ) + { + case "sp_weapon_proto_battery_charger_offhand": + player.GiveOffhandWeapon( CHARGE_TOOL, OFFHAND_SPECIAL ) + break + + case "sp_weapon_arc_tool": + player.GiveWeapon( CHARGE_TOOL ) + break + } + UpdateArcConnectorHints() +} + +/* +void function CodeCallback_Ping( entity player ) +{ + +} +*/ + +/* ---------------------------------------------------------------------------------------------- + +STUFF FORM MP THAT WE WILL SLOWLY GET RID OF + +-------------------------------------------------------------------------------------------------*/ + +string function GetGameModeAnnouncement() +{ + return "" +} + +void function SetGameState( int newState ) +{ + level.nv.gameStartTime = Time() + level.nv.gameStateChangeTime = Time() + level.nv.gameState = newState + svGlobal.levelEnt.Signal( "GameStateChanged" ) + + foreach ( callbackFunc in svGlobal.gameStateEnterCallbacks[ newState ] ) + { + callbackFunc() + } +} + +void function CodeCallback_GamerulesThink() +{ + +} + +void function DEV_ToggleFriendlyHighlight() +{ + file.friendlyHighlightEnabled = !file.friendlyHighlightEnabled + UpdateFriendlyHighlight() +} + +void function DisableFriendlyHighlight() +{ + file.friendlyHighlightEnabled = false + UpdateFriendlyHighlight() +} + +void function EnableFriendlyHighlight() +{ + file.friendlyHighlightEnabled = true + UpdateFriendlyHighlight() +} + +void function UpdateFriendlyHighlight() +{ + if ( file.friendlyHighlightEnabled ) + { + foreach ( npc in GetNPCArrayOfTeam( TEAM_MILITIA ) ) + Highlight_SetFriendlyHighlight( npc, "sp_friendly_pilot" ) + } + else + { + foreach ( npc in GetNPCArrayOfTeam( TEAM_MILITIA ) ) + Highlight_ClearFriendlyHighlight( npc ) + } +} + +bool function ClientCommand_RestartMission( entity player, array<string> args ) +{ + ServerRestartMission( player ) + return true +} + +void function ServerRestartMission( entity player ) +{ + if ( IsTestMap() ) + { + ServerCommand( "reload" ) + return + } + + string mapName = GetMapName() + LevelTransitionStruct ornull trans = file.storedLevelTransitionStruct + if ( trans == null ) + { + int startIndex = 0 + #if DEV + string startName = Dev_GetStartCommandLine( mapName ) + if ( startName != "" ) + startIndex = GetStartPointIndexFromName( mapName, startName ) + #endif + // loaded a level manually in dev or are in sp_training + ExecuteLoadingClientCommands_SetStartPoint( mapName, startIndex ) + ClientCommand( player, "map " + mapName ) + return + } + + expect LevelTransitionStruct( trans ) + ExecuteLoadingClientCommands_SetStartPoint( mapName, trans.startPointIndex ) + ChangeLevel( mapName, trans ) +} + +void function PilotBecomesTitanStoreWeaponVar( entity pilot, entity npc_titan ) +{ + entity weapon = pilot.GetMainWeapons()[0] + int index = GetTitanLoadoutIndex( weapon.GetWeaponClassName() ) + SetConVarInt( "sp_titanLoadoutCurrent", index ) +} + +void function PreventFriendlyTitanfallDamage( entity ent, var damageInfo ) +{ + entity attacker = DamageInfo_GetAttacker( damageInfo ) + + if ( attacker.GetTeam() == ent.GetTeam() ) + { + DamageInfo_SetDamage( damageInfo, 0 ) + } +}
\ No newline at end of file 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 diff --git a/Northstar.Coop/scripts/vscripts/sp/_savegame.gnut b/Northstar.Coop/scripts/vscripts/sp/_savegame.gnut new file mode 100644 index 00000000..f4f29003 --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/_savegame.gnut @@ -0,0 +1,860 @@ +global function AddCallback_OnLoadSaveGame +global function CheckPoint +global function CheckPoint_Forced +global function CheckPoint_ForcedSilent +global function CheckPoint_Silent +global function RestartFromLevelTransition +global function CodeCallback_IsSaveGameSafeToCommit +global function CodeCallback_OnLoadSaveGame +global function CodeCallback_OnSavedSaveGame +global function InitSaveGame +global function LoadSaveTimeDamageMultiplier +global function ReloadForMissionFailure +global function GetLastCheckPointLoadTime +global function SafeForCheckPoint +global function SafeToSpawnAtOrigin +global function JustLoadedFromCheckpoint +global function GetFirstPlayer + +const float LAST_SAVE_DEBOUNCE = 4.0 +const float TITAN_SAFE_DIST = 1200 +const SAVE_DEBUG = true +global const SAVE_NEW = false +global const MAX_SAFELOCATION_DIST = 5000 +global const float SAVE_SPAWN_VOFFSET = 2.0 + +global struct CheckPointData +{ + float searchTime = 5.0 + bool onGroundRequired = true + array<entity> safeLocations + bool forced + bool skipDelayedIsAliveCheck + bool skipSaveToActualPlayerLocation + bool functionref( entity ) ornull additionalCheck = null +} + +struct +{ + bool nextSaveSilent + table signalDummy + float loadSaveTime + + float lastSaveTime + + array<StoredWeapon> saveStoredWeapons + string storedTitanWeapon + + vector saveOrigin + vector saveAngles + bool saveRestoreResetsPlayer + +} file + +void function InitSaveGame() +{ + Assert( MAX_SAFELOCATION_DIST < TIME_ZOFFSET * 0.8 ) + + RegisterSignal( "StopSearchingForSafeSave" ) + RegisterSignal( "RespawnNow" ) + + FlagInit( "OnLoadSaveGame_PlayerRecoveryEnabled", true ) + FlagInit( "SaveRequires_PlayerIsTitan" ) + AddClientCommandCallback( "RestartFromLevelTransition", RestartFromLevelTransition ) + AddClientCommandCallback( "RespawnNowSP", RespawnNowSP ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// GLOBAL COMMANDS +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void function CheckPoint_Silent( CheckPointData ornull checkPointData = null ) +{ + CheckPointData data = GetCheckPointDataOrDefaults( checkPointData ) + CheckPoint_Silent_FromData( data ) +} + +void function CheckPoint_Silent_FromData( CheckPointData checkPointData ) +{ + file.nextSaveSilent = true + CheckPoint( checkPointData ) +} + +void function CheckPoint_ForcedSilent() +{ + file.nextSaveSilent = true + CheckPoint_Forced() +} + +void function CheckPoint_Forced() +{ + CheckPointData checkPointData + checkPointData.forced = true + CheckPoint_FromData( checkPointData ) +} + +void function CheckPoint( CheckPointData ornull checkPointData = null ) +{ + CheckPointData data = GetCheckPointDataOrDefaults( checkPointData ) + thread CheckPoint_FromData( data ) +} + +void function CheckPoint_FromData( CheckPointData checkPointData ) +{ + printt( "SAVEGAME: New save attempt (8/3/2016)" ) + + float searchTime = checkPointData.searchTime + bool onGroundRequired = checkPointData.onGroundRequired + bool skipDelayedIsAliveCheck = checkPointData.skipDelayedIsAliveCheck + bool skipSaveToActualPlayerLocation = checkPointData.skipSaveToActualPlayerLocation + bool forced = checkPointData.forced + array<entity> safeLocations = checkPointData.safeLocations + bool functionref( entity ) ornull additionalCheck = checkPointData.additionalCheck + + if ( !Flag( "SaveGame_Enabled" ) ) + { + printt( "SAVEGAME: failed: Saves are disabled" ) + return + } + + entity player = GetFirstPlayer() + entity safeLocation + + if ( forced ) + { + printt( "SAVEGAME: creating: Forced" ) + } + else + { + if ( Flag( "SaveRequires_PlayerIsTitan" ) && !player.IsTitan() ) + { + printt( "SAVEGAME: failed: Flag SaveRequires_PlayerIsTitan and player is not a Titan" ) + return + } + + printt( "SAVEGAME: creating: Safe for check point?" ) + EndSignal( file.signalDummy, "StopSearchingForSafeSave" ) + + bool regularSafetyCheck = GetBugReproNum() != 184641 // respawned with no UI + bool functionref( entity ) safeCheck + if ( onGroundRequired ) + safeCheck = SafeForCheckPoint_OnGround + else + safeCheck = SafeForCheckPoint + + + if ( skipSaveToActualPlayerLocation ) + { + printt( "SAVEGAME: skipSaveToActualPlayerLocation." ) + } + else + { + float rate = 0.333 + int reps = int( searchTime / rate ) + + if ( regularSafetyCheck ) + { + // wait a few seconds until it is safe to save + for ( int i = 0; i < reps; i++ ) + { + if ( safeCheck( player ) ) + break + + wait rate + } + } + else + { + printt( "SAVEGAME: regularSafetyCheck disabled for bug repro" ) + } + } + + if ( !regularSafetyCheck || skipSaveToActualPlayerLocation || !safeCheck( player ) ) + { + printt( "SAVEGAME: Not safe to save actual player position." ) + if ( !safeLocations.len() ) + { + printt( "SAVEGAME: failed: no alternate safe locations." ) + return + } + + // titan cores do funky stuff with weapons and make it hard to resume to a new position safely + if ( !IsTitanCoreFiring( player ) ) + safeLocation = GetBestSaveLocationEnt( safeLocations ) + + if ( safeLocation == null ) + { + printt( "SAVEGAME: failed: couldn't find a safe location from those provided." ) + return + } + + printt( "SAVEGAME: Found safe location " + safeLocation.GetOrigin() ) + } + else + { + printt( "SAVEGAME: Safe to save at actual player location: " + player.GetOrigin() ) + } + + if ( Time() - file.lastSaveTime < 3 ) + { + printt( "SAVEGAME: failed: last save was too recent." ) + return + } + + if ( additionalCheck != null ) + { + expect bool functionref(entity)( additionalCheck ) + if ( !additionalCheck( player ) ) + { + printt( "SAVEGAME: failed: custom check failed." ) + return + } + } + } + + if ( IsValid( safeLocation ) ) + { + printt( "SAVEGAME: saveRestoreResetsPlayer = true" ) + WriteRestoreLocationFromEntity( safeLocation ) + file.saveRestoreResetsPlayer = true + } + else + { + printt( "SAVEGAME: saveRestoreResetsPlayer = false" ) + file.saveRestoreResetsPlayer = false + } + + int startPointIndex = GetCurrentStartPointIndex() + + if ( !IsAlive( player ) ) + { + printt( "SAVEGAME: failed: Tried to save while player was dead" ) + return + } + + if ( skipDelayedIsAliveCheck || forced ) + { + printt( "SAVEGAME: SaveGame_Create" ) + file.lastSaveTime = Time() + SaveGame_Create( GetSaveName(), SAVEGAME_VERSION, startPointIndex ) + } + else + { + printt( "SAVEGAME: SaveGame_CreateWithCommitDelay" ) + file.lastSaveTime = Time() + SaveGame_CreateWithCommitDelay( GetSaveName(), SAVEGAME_VERSION, 3.5, 1, startPointIndex ) + } + + // kill any ongoing attempts to save. Note this also may end this thread, so it is called last. + Signal( file.signalDummy, "StopSearchingForSafeSave" ) +} + +CheckPointData function GetCheckPointDataOrDefaults( CheckPointData ornull checkPointData = null ) +{ + if ( checkPointData != null ) + return expect CheckPointData( checkPointData ) + + CheckPointData data + return data +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CODE CALLBACKS +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +bool function CodeCallback_IsSaveGameSafeToCommit() +{ + if ( !Flag( "SaveGame_Enabled" ) ) + return false + + foreach ( player in GetPlayerArray() ) + { + if ( !IsAlive( player ) ) + return false + } + + return true +} + + +void function ClearPlayerVelocityOnContext( entity player ) +{ + if ( player.IsTitan() ) + return + + switch ( GetCurrentStartPoint() ) + { + case "Rising World Jump": + return + } + + player.SetVelocity( <0,0,0> ) +} + + +void function CodeCallback_OnLoadSaveGame() +{ + printt( "SaveGame: OnLoadSaveGame" ) + file.loadSaveTime = Time() + + UpdateCollectiblesAfterLoadSaveGame() + array<entity> players = GetPlayerArray() + + Assert( players.len() == 1 ) + entity player = players[0] + + // restore health on load from save + if ( !IsAlive( player ) ) + return + + ClearPlayerVelocityOnContext( player ) + + thread UpdateUI( player.IsTitan(), player ) + + // run on load save game callbacks + foreach ( callbackFunc in svGlobalSP.onLoadSaveGameCallbacks ) + { + callbackFunc( player ) + } + + thread DelayedOnLoadSetup( player ) +} + +void function DelayedOnLoadSetup( entity player ) +{ + player.EndSignal( "OnDeath" ) + + WaitFrame() // fixes crash bug, also can't issue a remote call on this codecallback until waiting a frame. + + Remote_CallFunction_UI( player, "ServerCallback_GetObjectiveReminderOnLoad" ) // show objective reminder + + bool forceStanding = file.saveRestoreResetsPlayer + if ( file.saveRestoreResetsPlayer ) + { + file.saveRestoreResetsPlayer = false + + printt( "SAVEGAME: Restoring player to safe location " + file.saveOrigin ) + player.SetOrigin( file.saveOrigin ) + player.SetAngles( file.saveAngles ) + player.SetVelocity( <0,0,0> ) + + TakeAllWeapons( player ) + + player.ForceStand() + + if ( IsPilot( player ) ) + { + GiveWeaponsFromStoredArray( player, file.saveStoredWeapons ) + } + else + { + player.GiveWeapon( file.storedTitanWeapon ) + } + } + + if ( Flag( "OnLoadSaveGame_PlayerRecoveryEnabled" ) ) + { + if ( player.IsTitan() ) + { + RestoreTitan( player ) + + int index = GetConVarInt( "sp_titanLoadoutCurrent" ) + if ( index > -1 && IsBTLoadoutUnlocked( index ) ) + { + string weapon = GetTitanWeaponFromIndex( index ) + TakeAllWeapons( player ) + player.GiveWeapon( weapon ) + } + } + else + { + player.SetHealth( player.GetMaxHealth() ) + entity offhand = player.GetOffhandWeapon( OFFHAND_SPECIAL ) + if ( IsValid( offhand ) ) + { + // restore offhand, so for example, cloak will be ready to use + int max = offhand.GetWeaponPrimaryClipCountMax() + offhand.SetWeaponPrimaryClipCount(max) + } + + entity petTitan = player.GetPetTitan() + if ( IsAlive( petTitan ) ) + { + RestoreTitan( petTitan ) + } + } + } + + WaitFrame() + + if ( forceStanding ) + player.UnforceStand() + +} + +void function CodeCallback_OnSavedSaveGame( bool saved ) +{ + printt( "SAVEGAME: Success (OnSavedSaveGame)" ) + + // failed to save + if ( !saved ) + return + + // tell clients about the save + foreach ( player in GetPlayerArray() ) + { + Remote_CallFunction_UI( player, "ServerCallback_ClearObjectiveReminderOnLoad" ) // dont need objective reminder again on load + } + + BroadcastCheckpointMessage() +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SAVE UTILITIES +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + + + +entity function GetFirstPlayer() +{ + foreach ( player in GetPlayerArray() ) + { + return player + } + + unreachable +} + +bool function SafeToSpawnAtOrigin( entity player, vector origin ) +{ + if ( player.IsTitan() ) + { + array<entity> titans = GetNPCArrayEx( "npc_titan", TEAM_ANY, TEAM_MILITIA, origin, TITAN_SAFE_DIST ) + if ( titans.len() > 0 ) + return false + + array<entity> superSpectres = GetNPCArrayEx( "npc_super_spectre", TEAM_ANY, TEAM_MILITIA, origin, TITAN_SAFE_DIST ) + if ( superSpectres.len() > 0 ) + return false + } + else + { + array<entity> enemies = GetNPCArrayEx( "any", TEAM_ANY, TEAM_MILITIA, origin, 300 ) + if ( enemies.len() > 0 ) + return false + } + + return IsPlayerSafeFromProjectiles( player, origin ) +} + +entity function GetBestSaveLocationEnt( array<entity> safeLocations ) +{ + entity player = GetFirstPlayer() + vector mins = player.GetBoundingMins() + vector maxs = player.GetBoundingMaxs() + + vector playerOrg = player.GetOrigin() + array<entity> orderedLocations = ArrayClosest( safeLocations, player.GetOrigin() ) + foreach ( ent in orderedLocations ) + { + vector org = ent.GetOrigin() + <0,0,SAVE_SPAWN_VOFFSET> + + // too far to spawn + if ( Distance( playerOrg, org ) > MAX_SAFELOCATION_DIST ) + break + + if ( !SafeToSpawnAtOrigin( player, ent.GetOrigin() ) ) + continue + + // blocked at that origin? + TraceResults result = TraceHull( org, org + Vector( 0, 0, 1), mins, maxs, [ player ], TRACE_MASK_PLAYERSOLID, TRACE_COLLISION_GROUP_PLAYER ) + if ( result.startSolid ) + continue + if ( result.fraction != 1.0 ) + continue + + return ent + } + + return null +} + +void function WriteRestoreLocationFromEntity( entity safeLocation ) +{ + Assert( safeLocation != null ) + entity player = GetFirstPlayer() + Assert( IsAlive( player ), "Tried to save while player was dead" ) + + if ( IsPilot( player ) ) + { + file.saveStoredWeapons = StoreWeapons( player ) + } + else + { + array<entity> weapons = player.GetMainWeapons() + Assert( weapons.len() == 1, "Player had multiple titan weapons" ) + file.storedTitanWeapon = weapons[0].GetWeaponClassName() + } + + file.saveOrigin = safeLocation.GetOrigin() + <0,0,SAVE_SPAWN_VOFFSET> + file.saveAngles = safeLocation.GetAngles() +} + +bool function RestartFromLevelTransition( entity player, array<string> args ) +{ + thread ReloadFromLevelStart() + + return true +} + +void function ReloadForMissionFailure( float extraDelay = 0 ) +{ + // prevent automatic restarts + //thread ReloadForMissionFailure_Thread( extraDelay ) +} + +void function ReloadForMissionFailure_Thread( float extraDelay ) +{ + FlagClear( "SaveGame_Enabled" ) // no more saving, you have lost + FlagSet( "MissionFailed" ) + + Signal( file.signalDummy, "StopSearchingForSafeSave" ) + + entity player = GetFirstPlayer() + if ( IsValid( player ) ) + EmitSoundOnEntityOnlyToPlayer( player, player, "4_second_fadeout" ) + + wait 1 + + waitthread WaitForRespawnNowOrTimePass( player, 2.8 + extraDelay ) + + ReloadFromSave_RestartFallback() +} + +void function WaitForRespawnNowOrTimePass( entity player, float delay ) +{ + player.EndSignal( "RespawnNow" ) + wait delay +} + +void function SkipReloadDelay_Thread( entity player ) +{ + EmitSoundOnEntityOnlyToPlayer( player, player, "1_second_fadeout" ) + ScreenFadeToBlackForever( player, 0.5 ) + wait 1.0 + player.Signal( "RespawnNow" ) +} + +bool function RespawnNowSP( entity player, array<string> args ) +{ + // this clientcommand is always bound now so we need to make sure it isn't called while player is alive + if ( IsAlive( player )) + return true + + // wait a few seconds before we allow respawns + if ( player.nv.nextRespawnTime > Time() ) + return true + + //thread SkipReloadDelay_Thread( player ) + + // do our respawning code + entity ornull respawningOn = GetPlayerToSpawnOn() + + if ( respawningOn == null ) + { + // todo: logic for restarting level without dropping clients + print( "everyone is dead! restarting from last checkpoint..." ) + RestartWithoutDroppingPlayers() + } + else + { + expect entity( respawningOn ) + // HACK: compiler has a fit if we directly call player.RespawnPlayer in this file + // so we need to make a functionref in another file that does it + + hackRespawnPlayerFunc( player, respawningOn ) + // honestly unsure if the respawningOn arg works with players so manually set pos too + thread TeleportToEntitySafe( player, respawningOn ) + } + + + return true +} + +void function ReloadFromSave_RestartFallback() +{ + if ( LoadedFromSave() ) + return + + ReloadFromLevelStart() +} + +void function ReloadFromLevelStart() +{ + array<entity> players = GetPlayerArray() + Assert( players.len() == 1 ) + entity player = players[0] + ServerRestartMission( player ) +} + +bool function SafeForCheckPoint_OnGround( entity player ) +{ + Assert( player.IsPlayer() ) + + if ( !player.IsOnGround() ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: !player.IsOnGround()" ) + #endif + return false + } + + if ( player.IsWallRunning() ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: player.IsWallRunning()" ) + #endif + return false + } + + return SafeForCheckPoint( player ) +} + +bool function SafeForCheckPoint( entity player ) +{ + Assert( player.IsPlayer() ) + + if ( Flag( "CheckPointDisabled" ) ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: Flag( \"CheckPointDisabled\" )" ) + #endif + return false + } + + if ( !IsAlive( player ) ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: !IsAlive( player )" ) + #endif + return false + } + + if ( IsPlayerEmbarking( player ) ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: IsPlayerEmbarking( player )" ) + #endif + return false + } + + if ( IsPlayerDisembarking( player ) ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: IsPlayerDisembarking( player )" ) + #endif + return false + } + + if ( player.p.doingQuickDeath ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: player.p.doingQuickDeath" ) + #endif + return false + } + + if ( EntityIsOutOfBounds( player ) ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: EntityIsOutOfBounds( player )" ) + #endif + return false + } + + + float range = 300 + if ( player.IsTitan() ) + { + range = 1000 + + // awkward to resume into a core-in-progress + if ( IsTitanCoreFiring( player ) ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: IsTitanCoreFiring( player )" ) + #endif + return false + } + } + else + { + entity weapon = player.GetActiveWeapon() + if ( IsValid( weapon ) ) + { + // cooking grenade? + if ( player.GetOffhandWeapon( OFFHAND_ORDNANCE ) == weapon ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: Cooking grenade" ) + #endif + return false + } + } + + if ( player.IsPhaseShifted() ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: player.IsPhaseShifted()" ) + #endif + return false + } + } + + array<entity> enemies = GetNPCArrayEx( "any", TEAM_ANY, TEAM_MILITIA, player.GetOrigin(), range ) + if ( enemies.len() > 0 ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: Enemy within " + range + " units" ) + #endif + return false + } + + if ( player.IsTitan() ) + { + + array<entity> titans = GetNPCArrayEx( "npc_titan", TEAM_ANY, TEAM_MILITIA, player.GetOrigin(), 3000 ) + foreach ( titan in titans ) + { + if ( titan.GetEnemy() == player ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: Enemy Titan within 3000 units" ) + #endif + return false + } + } + } + + if ( !IsPlayerSafeFromNPCs( player ) ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: !IsPlayerSafeFromNPCs( player )" ) + #endif + return false + } + + if ( !IsPlayerSafeFromProjectiles( player, player.GetOrigin() ) ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: !IsPlayerSafeFromProjectiles( player )" ) + #endif + return false + } + + if ( WasRecentlyHitByDamageSourceId( player, eDamageSourceId.toxic_sludge, 3.0 ) ) + { + #if SAVE_DEBUG + printt( "SaveGame: Failed: WasRecentlyHitByDamageSourceId( player, eDamageSourceId.toxic_sludge, 3.0 )" ) + #endif + return false + } + + return true +} + +void function BroadcastCheckpointMessage() +{ + if ( file.nextSaveSilent ) + { + file.nextSaveSilent = false + return + } + + // tell clients about the save + foreach ( player in GetPlayerArray() ) + { + if ( !IsAlive( player ) ) + continue + + Remote_CallFunction_NonReplay( player, "SCB_CheckPoint" ) + } +} + + +void function AddCallback_OnLoadSaveGame( void functionref( entity ) callbackFunc ) +{ + Assert( !svGlobalSP.onLoadSaveGameCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnLoadSaveGame" ) + svGlobalSP.onLoadSaveGameCallbacks.append( callbackFunc ) +} + +// Why does this need to be threaded?? I don't know! But it doesn't work if I don't wait a bit. +void function UpdateUI( bool isTitan, entity player ) +{ + if ( !IsValid( player ) ) + return + + player.EndSignal( "OnDeath" ) + + wait 0.1 + + UpdatePauseMenuMissionLog( player ) + + if ( isTitan ) + { + UI_NotifySPTitanLoadoutChange( player ) + NotifyUI_ShowTitanLoadout( player, null ) + } + else + { + NotifyUI_HideTitanLoadout( player, null ) + } +} + +float function LoadSaveTimeDamageMultiplier() +{ + return GraphCapped( Time() - file.loadSaveTime, 2.5, 4.0, 0.0, 1.0 ) +} + +float function GetLastCheckPointLoadTime() +{ + return file.loadSaveTime +} + +bool function JustLoadedFromCheckpoint() +{ + return Time() - file.loadSaveTime < 1.0 +} + +bool function LoadedFromSave() +{ + printt( "SAVEGAME: Trying to load saveName" ) + if ( !HasValidSaveGame() ) + { + printt( "SAVEGAME: !HasValidSaveGame" ) + return false + } + + string saveName = GetSaveName() + printt( "SAVEGAME: Did load " + saveName ) + + // set the correct loadscreen + string mapName = SaveGame_GetMapName( saveName ) + int startPointIndex = SaveGame_GetStartPoint( saveName ) + + array<string> clientCommands = GetLoadingClientCommands( mapName, startPointIndex, DETENT_FORCE_DISABLE, false ) + ExecuteClientCommands( clientCommands ) + + if ( GetBugReproNum() != 196356 ) + { + printt( "LOADPROGRESS" ) + ClientCommand( GetFirstPlayer(), "show_loading_progress" ) + WaitFrame() + } + + SaveGame_LoadWithStartPointFallback() + return true +} diff --git a/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_spoke02.nut b/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_spoke02.nut new file mode 100644 index 00000000..bdb291d7 --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_spoke02.nut @@ -0,0 +1,3891 @@ +untyped + +global function CodeCallback_MapInit +global function TransitionSpoke1 + +const DUMMY_MODEL = $"models/Robots/stalker/robot_stalker.mdl" +const TIMEZONE_DAY = 0 +const TIMEZONE_NIGHT = 1 +const TIMEZONE_ALL = 2 + +const ANDERSON_MODEL = $"models/humans/heroes/mlt_hero_anderson.mdl" +const IMC_CORPSE_MODEL_CIV = $"models/levels_terrain/sp_timeshift/civilian_eng_v2_corpse_static_20.mdl" + +//const FX_HUMAN_DOOR_OPEN = $"steam_leak_SM_CH_end" +const FX_ANDERSON_DEVICE_FX = $"P_timeshift_gauntlet_hld" +const FX_ARK_LAUNCH_GLOW = $"P_ts_core_hld_sm" +const FX_ARK_LAUNCH_IN_PLACE = $"P_ts_core_hld_sm_lock" + +struct +{ + int elevatorDudesDead + array <entity> elevatorAnimNodes + array <entity> elevatorDoors + +} file + + +///////////////////////////////////////////////////////////////////////////////////////// +// +// +// TIMESHIFT SPOKE 2 - MAP INIT +// +// +///////////////////////////////////////////////////////////////////////////////////////// +void function CodeCallback_MapInit() +{ + ShSpTimeshiftSpoke2CommonInit() + RegisterSignal( "AnimTimeshift" ) + RegisterSignal( "PlayerInsideCage" ) + RegisterSignal( "ReleaseLabRat") + + PrecacheModel( DUMMY_MODEL ) + PrecacheModel( ANDERSON_MODEL ) + PrecacheModel( IMC_CORPSE_MODEL_CIV ) + + //PrecacheParticleSystem( FX_HUMAN_DOOR_OPEN ) + PrecacheParticleSystem( FX_ARK_LAUNCH_GLOW ) + PrecacheParticleSystem( FX_ARK_LAUNCH_IN_PLACE ) + PrecacheParticleSystem( FX_ANDERSON_DEVICE_FX ) + + //PrecacheModel( LABRAT_MODEL ) + AddCallback_EntitiesDidLoad( EntitiesDidLoad ) + AddPlayerDidLoad( TimeShiftHub_PlayerDidLoad ) + AddSpawnCallback( "npc_soldier", OnSpawnedLevelNPC ) + AddSpawnCallback( "npc_prowler", OnSpawnedLevelNPC ) + AddSpawnCallback( "npc_spectre", OnSpawnedLevelNPC ) + AddSpawnCallback( "prop_dynamic", OnSpawnedPropDynamic ) + + AddDeathCallback( "npc_prowler", OnDeathProwlerAcheivement ) + + + SPTimeshiftUtilityInit() + + //------------------ + // Flags + //----------------- + FlagInit( "SwappedToFrozenWorld" ) + FlagInit( "DoneWithFanDropSequence") + FlagInit( "ProwlerAcheivementUnlocked" ) + FlagInit( "BrokeOutOfFanDropMusicLoop") + FlagInit( "ProwlerAmbushTriggered" ) + FlagInit( "AndersomHologram2AboutToStart" ) + FlagInit( "AcheivementUnlockedLabProwler" ) + FlagInit( "ForceFlyerTakeoff" ) + FlagInit( "RingVistaSequenceComplete" ) + FlagInit( "AllProwlersSpawnedInHumanControlRoom" ) + FlagInit( "IntelRoom1Finished" ) + FlagInit( "LabRatAcheivementUnlocked" ) + FlagInit( "AllElevatorProwlersSpawned" ) + FlagInit( "player_back_in_amenities_lobby" ) + FlagInit( "StartAndersonHologram1" ) + FlagInit( "StartAndersonHologram2" ) + FlagInit( "StartAndersonHologram3" ) + FlagInit( "StartSphereRoomGunship" ) + FlagInit( "LabBravoEnemiesDead" ) + FlagInit( "human_bridge_soldiers_dead" ) + FlagInit( "CampusReturnConversationFinished" ) + FlagInit( "PlayerPickingUpDevice" ) + FlagInit( "HidePlayerWeaponsDuringShifts") + FlagInit( "AllElevatorDudesDead" ) + FlagInit( "ElevatorDudesDead1" ) + FlagInit( "ElevatorDudesDead2" ) + FlagInit( "ElevatorDudesDead3" ) + FlagInit( "ElevatorDudesDead4" ) + FlagInit( "CinematicTimeshiftSequenceFinished") + FlagInit( "retract_bridge_human_01" ) + FlagInit( "retract_bridge_control_panel_pressed" ) + FlagInit( "door_open_amenities_lobby_return_pristine" ) + FlagInit( "finishedHumanVistaSequence" ) + FlagInit( "spawnHumanBridgeEnemies" ) + FlagInit( "player_looking_at_reactor_window" ) + FlagInit( "open_door_lobby_main_overgrown" ) + FlagInit( "PlayerLookingTowardsElevators" ) + FlagInit( "ConcoursePanelHacked01" ) + FlagInit( "ConcoursePanelHacked02" ) + FlagInit( "PlayerPickedUpTimeshiftDevice" ) + FlagInit( "FirstSpectreDeployedLobbyReduxPristine" ) + FlagInit( "FirstSpectreDeployedLobbyReduxOvergrown" ) + FlagInit( "SeveralElevatorDudesDead" ) + FlagInit( "ShowMobilityGhostTurretFirepit" ) + FlagInit( "ShowMobilityGhostElevatorShaft" ) + FlagInit( "ShowMobilityGhostTurretFlank" ) + FlagInit( "ShowMobilityGhostHumanLillypad01" ) + FlagInit( "ShowMobilityGhostHumanLillypad02" ) + FlagInit( "ShowMobilityGhostHumanWallrunChain" ) + FlagInit( "ShowMobilityGhostPowertech" ) + + //------------------ + // Start points + //------------------ + //startPoint, mainFunc, setupFunc skipFunc + AddStartPoint( "Timeshift Device", AA_TimeshiftDeviceThread, TimeshiftDeviceStartPointSetup, TimeshiftDeviceSkipped ) + AddStartPoint( "WILDLIFE RESEARCH", AA_WildlifeResearchThread, WildlifeResearchStartPointSetup, WildlifeResearchSkipped ) + AddStartPoint( "First Timeshift Fight", AA_FirstTimeshiftFightThread, FirstTimeshiftFightStartPointSetup, FirstTimeshiftFightSkipped ) + AddStartPoint( "Elevator Fight", AA_ElevatorFightThread, ElevatorFightStartPointSetup, ElevatorFightSkipped ) + AddStartPoint( "HUMAN RESEARCH", AA_ElevatorTopThread, ElevatorTopStartPointSetup, ElevatorTopSkipped ) + AddStartPoint( "Sphere Room", AA_SphereRoomThread, SphereRoomStartPointSetup, SphereRoomSkipped ) //checkpointIntelRoom2 + AddStartPoint( "Human Room", AA_HumanResearchThread, HumanResearchStartPointSetup, HumanResearchSkipped ) + AddStartPoint( "CAMPUS RETURN", AA_CampusReturnThread, CampusReturnStartPointSetup, CampusReturnSkipped ) + AddStartPoint( "Fan Drop", AA_FanDropThread, FanDropStartPointSetup, FanDropSkipped ) + AddStartPoint( "Fan Drop End", AA_FanDropEndThread, FanDropEndStartPointSetup, FanDropEndSkipped ) + + AddMobilityGhost( $"anim_recording/timeshift_turret_firepit_overgrown.rpak", "ShowMobilityGhostTurretFirepit" ) + AddMobilityGhost( $"anim_recording/timeshift_elevator_shaft_overgrown.rpak", "ShowMobilityGhostElevatorShaft" ) + AddMobilityGhost( $"anim_recording/timeshift_elevator_shaft_pristine.rpak", "ShowMobilityGhostElevatorShaft" ) + AddMobilityGhost( $"anim_recording/timeshift_turret_flank_overgrown.rpak", "ShowMobilityGhostTurretFlank" ) + AddMobilityGhost( $"anim_recording/timeshift_lillypad01_overgrown.rpak", "ShowMobilityGhostHumanLillypad01" ) + AddMobilityGhost( $"anim_recording/timeshift_lillypad01_pristine.rpak", "ShowMobilityGhostHumanLillypad01" ) + AddMobilityGhost( $"anim_recording/timeshift_lillypad02_overgrown.rpak", "ShowMobilityGhostHumanLillypad02" ) + AddMobilityGhost( $"anim_recording/timeshift_lillypad02_pristine.rpak", "ShowMobilityGhostHumanLillypad02" ) + AddMobilityGhost( $"anim_recording/timeshift_wallchain_overgrown.rpak", "ShowMobilityGhostHumanWallrunChain" ) + AddMobilityGhost( $"anim_recording/timeshift_wallchain_pristine.rpak", "ShowMobilityGhostHumanWallrunChain" ) + AddMobilityGhost( $"anim_recording/timeshift_powertech_overgrown.rpak", "ShowMobilityGhostPowertech" ) + AddMobilityGhost( $"anim_recording/timeshift_powertech_pristine.rpak", "ShowMobilityGhostPowertech" ) + +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function EntitiesDidLoad() +{ + FlagSet( "retract_bridge_human_01" ) + + HideStuff( "human_bridge_overgrown" ) + HideStuff( "elevator_doors_upper_overgrown" ) + HideStuff( "blocker_fandrop_pristine" ) + HideStuff( "blocker_fandrop_overgrown" ) + + + thread SkyboxStart() + thread RingsThink() + + array <entity> elevatorAnimNodesLocal = GetEntArrayByScriptName( "node_elevator_anim" ) + Assert( elevatorAnimNodesLocal.len() == 4 ) + file.elevatorAnimNodes = elevatorAnimNodesLocal + + array <entity> elevatorDoorsLocal = GetEntArrayByScriptName( "elevator_door_exec_housing_access" ) + Assert( elevatorDoorsLocal.len() == 4 ) + file.elevatorDoors = elevatorDoorsLocal + + + //navmesh sep to keep prowlers away from half-open elevators + entity navmesh_blocker_elevator_overgrown = GetEntByScriptName( "navmesh_blocker_elevator_overgrown" ) + navmesh_blocker_elevator_overgrown.NotSolid() //ok if it's notsolid, will still disconnect the navmesh + + + //CleanupEnts( "triggers_instadeath_humanroom" ) + //CleanupEnts( "triggers_quickdeath_humanroom" ) + //CleanupEnts( "trigger_quickdeath_checkpoint_humanroom" ) + + thread AndersonSetup() + + // coop code: fuck with entities so we can prevent some weird flags + //array< entity > doors = +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function TimeShiftHub_PlayerDidLoad( entity player ) +{ + /* + This will run before any start points run. + Useful for doing common player-specific setup, + saving you from having to put a common player + setup function in each of your start points + */ + + //--------------------- + // Timeshift thread + //---------------------- + thread TimeshiftPlayerThink( player ) + + +} + + +///////////////////////////////////////////////////////////////////////////////////////// +/* +████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗ ██████╗███████╗ +╚══██╔══╝██║████╗ ████║██╔════╝██╔════╝██║ ██║██║██╔════╝╚══██╔══╝ ██╔══██╗██╔════╝██║ ██║██║██╔════╝██╔════╝ + ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ ██║ ██║█████╗ ██║ ██║██║██║ █████╗ + ██║ ██║██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║██║██╔══╝ ██║ ██║ ██║██╔══╝ ╚██╗ ██╔╝██║██║ ██╔══╝ + ██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║██║██║ ██║ ██████╔╝███████╗ ╚████╔╝ ██║╚██████╗███████╗ + ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═══╝ ╚═╝ ╚═════╝╚══════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* +████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗ ██████╗███████╗ +╚══██╔══╝██║████╗ ████║██╔════╝██╔════╝██║ ██║██║██╔════╝╚══██╔══╝ ██╔══██╗██╔════╝██║ ██║██║██╔════╝██╔════╝ + ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ ██║ ██║█████╗ ██║ ██║██║██║ █████╗ + ██║ ██║██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║██║██╔══╝ ██║ ██║ ██║██╔══╝ ╚██╗ ██╔╝██║██║ ██╔══╝ + ██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║██║██║ ██║ ██████╔╝███████╗ ╚████╔╝ ██║╚██████╗███████╗ + ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═══╝ ╚═╝ ╚═════╝╚══════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* +████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗ ██████╗███████╗ +╚══██╔══╝██║████╗ ████║██╔════╝██╔════╝██║ ██║██║██╔════╝╚══██╔══╝ ██╔══██╗██╔════╝██║ ██║██║██╔════╝██╔════╝ + ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ ██║ ██║█████╗ ██║ ██║██║██║ █████╗ + ██║ ██║██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║██║██╔══╝ ██║ ██║ ██║██╔══╝ ╚██╗ ██╔╝██║██║ ██╔══╝ + ██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║██║██║ ██║ ██████╔╝███████╗ ╚████╔╝ ██║╚██████╗███████╗ + ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═══╝ ╚═╝ ╚═════╝╚══════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* +████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗ ██████╗███████╗ +╚══██╔══╝██║████╗ ████║██╔════╝██╔════╝██║ ██║██║██╔════╝╚══██╔══╝ ██╔══██╗██╔════╝██║ ██║██║██╔════╝██╔════╝ + ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ ██║ ██║█████╗ ██║ ██║██║██║ █████╗ + ██║ ██║██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║██║██╔══╝ ██║ ██║ ██║██╔══╝ ╚██╗ ██╔╝██║██║ ██╔══╝ + ██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║██║██║ ██║ ██████╔╝███████╗ ╚████╔╝ ██║╚██████╗███████╗ + ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═══╝ ╚═╝ ╚═════╝╚══════╝ +*/ +///////////////////////////////////////////////////////////////////////////////////////// +void function TimeshiftDeviceStartPointSetup( entity player ) +{ + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointTimeshiftDevice" ) ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function TimeshiftDeviceSkipped( entity player ) +{ + FlagSet( "open_creature_door_start_pristine" ) + FlagSet( "PlayerPickedUpTimeshiftDevice" ) + GiveTimeshiftAbility( player ) + FlagClear( "open_skybridge_door_end_pristine" ) + FlagClear( "open_skybridge_door_end_overgrown" ) + FlagSet( "CinematicTimeshiftSequenceFinished") +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_TimeshiftDeviceThread( entity player ) +{ + InitBoyleAudioLogs() + + //FlagSet( "open_skybridge_door_end_overgrown" ) + //FlagSet( "open_creature_door_start_pristine" ) + //----------------------------------------- + // Get timeshift device + //----------------------------------------- + wait 1 + + thread MusicGetTimeshiftDevice( player ) + thread DialogueCreatureLabAnderson( player ) + + thread PickupTimeShiftDevice( player ) + + + + FlagWait( "PlayerPickedUpTimeshiftDevice" ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function MusicGetTimeshiftDevice( entity player ) +{ + StopMusic() + PlayMusic( "music_timeshift_15_gettemporaldevice" ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function DialogueCreatureLabAnderson( entity player ) +{ + entity anderson = GetEntByScriptName( "anderson_other_half" ) + local attach_id = anderson.LookupAttachment( "L_HAND" ) + vector objectivePos = anderson.GetAttachmentOrigin( attach_id ) + + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_TAKE_TIME_DEVICE", objectivePos ) + +/* +██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗ +██╔══██╗██║██╔══██╗██║ ██╔═══██╗██╔════╝ ██║ ██║██╔════╝ +██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗ +██║ ██║██║██╔══██║██║ ██║ ██║██║ ██║██║ ██║██╔══╝ +██████╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ +*/ + + wait 10 + + if ( Flag( "PlayerPickingUpDevice" ) ) + return + + + Objective_Remind() + //timeshift device nags + string nagAlias + int nextNagNumber = 1 + + + while( !Flag( "PlayerPickingUpDevice") ) + { + wait( RandomFloatRange( 35, 45 ) ) + + if ( Flag( "PlayerPickingUpDevice" ) ) + break + + + if ( nextNagNumber == 1 ) + { + //BT Well done, Pilot. You located the wrist mounted device. SRS intel suggests if equipped you can withstand and manipulate the temporal shifts. + nagAlias = "diag_sp_wildlifeStudy_TS191_01_01_mcor_bt" + } + + if ( nextNagNumber == 2 ) + { + //BT Pilot, recommend you equip the device. + nagAlias = "diag_sp_wildlifeStudy_TS191_09_01_mcor_bt" + } + else if ( nextNagNumber == 3 ) + { + //BT The wrist mounted device may be useful. Recommend you equip the device. + nagAlias = "diag_sp_wildlifeStudy_TS191_10_01_mcor_bt" + } + else if ( nextNagNumber == 4 ) + { + //BT Advisory: Pilot, I recommend you equip the wrist mounted device. + nagAlias = "diag_sp_wildlifeStudy_TS191_11_01_mcor_bt" + } + + waitthread PlayBTDialogue( nagAlias ) + + if ( Flag( "PlayerPickingUpDevice" ) ) + break + Objective_Remind() + //Objective_Clear() + //TimeshiftSetObjective( player, "#TIMESHIFT_OBJECTIVE_TAKE_TIME_DEVICE", objectivePos ) + nextNagNumber++ + if ( nextNagNumber > 4 ) + nextNagNumber = 1 + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function AndersonSetup() +{ + entity node = GetEntByScriptName( "org_get_device_sequence" ) + entity anderson = GetEntByScriptName( "anderson_other_half" ) + anderson.SetModel( ANDERSON_MODEL ) + thread PlayAnimTeleport( anderson, "pt_timeshift_device_equip_corpse_sequence_idle", node ) + + entity AndersonDeviceFX = PlayLoopFXOnEntity( FX_ANDERSON_DEVICE_FX, anderson, "L_BACKHAND" ) + + + int bodyGroupIndex = anderson.FindBodyGroup( "watch" ) + anderson.SetBodygroup( bodyGroupIndex, 1 ) + + bodyGroupIndex = anderson.FindBodyGroup( "watch_ts" ) + anderson.SetBodygroup( bodyGroupIndex, 1 ) + + FlagWait( "PlayerPickingUpDevice") + + wait 1 + + if ( IsValid( AndersonDeviceFX ) ) + { + StopFX( AndersonDeviceFX ) + EntFireByHandle( AndersonDeviceFX, "Stop", "", 0, null, null ) + AndersonDeviceFX.Destroy() + } + +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function PickupTimeShiftDevice( entity player ) +{ + entity node = GetEntByScriptName( "org_get_device_sequence" ) + entity anderson = GetEntByScriptName( "anderson_other_half" ) + + wait 0.5 + + local attach_id = anderson.LookupAttachment( "L_HAND" ) + vector origin = anderson.GetAttachmentOrigin( attach_id ) + vector angles = anderson.GetAttachmentAngles( attach_id ) + + + //entity useDummy = CreatePropDynamic( TempModel, origin, angles, 6 ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only + entity useDummy = CreatePropDynamic( DUMMY_MODEL, origin, Vector( 0, 0, 0 ), 2 ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only + useDummy.SetOrigin( origin ) + + useDummy.SetUsable() + useDummy.Hide() + useDummy.SetUsableByGroup( "pilot" ) + useDummy.SetUsePrompts( "#TIMESHIFT_HINT_TAKE_DEVICE" , "#TIMESHIFT_HINT_TAKE_DEVICE_PC" ) + + local playerActivator + while( true ) + { + playerActivator = useDummy.WaitSignal( "OnPlayerUse" ).player + if ( IsValid( playerActivator ) && playerActivator.IsPlayer() && !playerActivator.IsTitan() ) + { + // set player to whoever actually picked up the device, not host + expect entity( playerActivator ) + player = playerActivator + break + } + } + + //Cooper: Sorry Anderson + FlagSet( "PlayerPickingUpDevice" ) + + Objective_Clear() + delaythread ( 1 ) PlayDialogue( "diag_sp_extra_GB101_65_01_mcor_player", player ) + + + useDummy.UnsetUsable() + useDummy.Destroy() + + player.DisableWeaponWithSlowHolster() + player.SetInvulnerable() + //player.FreezeControlsOnServer() + player.ContextAction_SetBusy() + + FlagSet( "DoingCinematicTimeshift" ) + + //---------------------------------- + // Player takes device off Anderson + //------------------------------------ + entity mover = CreateOwnedScriptMover( node ) //need a mover for first person sequence + + FirstPersonSequenceStruct sequenceTakeDevice + //sequenceTakeDevice.blendTime = 1 + sequenceTakeDevice.attachment = "ref" + sequenceTakeDevice.firstPersonAnim = "ptpov_timeshift_device_equip_sequence" + sequenceTakeDevice.thirdPersonAnim = "pt_timeshift_device_equip_sequence" + sequenceTakeDevice.viewConeFunction = ViewConeTight + + thread PlayAndersonCorpseAnims( anderson, node ) + waitthread FirstPersonSequence( sequenceTakeDevice, player, mover ) + + FlagSet( "PlayerPickedUpTimeshiftDevice" ) + //--------------------------- + // Player equips device + //---------------------------- + player.ClearParent() + mover.Destroy() + entity mover2 = CreateOwnedScriptMover( node ) //need a mover for first person sequence + + FirstPersonSequenceStruct sequenceEquipDevice + sequenceEquipDevice.blendTime = 0 + sequenceEquipDevice.attachment = "ref" + sequenceEquipDevice.firstPersonAnim = "ptpov_timeshift_device_equip_sequence_02" + sequenceEquipDevice.thirdPersonAnim = "pt_timeshift_device_equip_sequence_02" + sequenceEquipDevice.viewConeFunction = ViewConeTight + + //WaitFrame() + + GiveTimeshiftAbility( player ) + thread TimeshiftSequenceShifts( player, mover2 ) + + entity proxy = player.GetFirstPersonProxy() + int bodyGroupIndex = proxy.FindBodyGroup( "glove_default" ) + proxy.SetBodygroup( bodyGroupIndex, 1 ) // 0 = show, 1 = hide + + int bodyGroupIndex2 = proxy.FindBodyGroup( "glove_animated" ) + proxy.SetBodygroup( bodyGroupIndex2, 1 ) // 0 = show, 1 = hide + + waitthread FirstPersonSequence( sequenceEquipDevice, player, mover2 ) + + SetTimeshiftArmDeviceSkin( 1 ) + + //player.UnfreezeControlsOnServer() + player.ClearInvulnerable() + player.Anim_Stop() + player.ClearParent() + ClearPlayerAnimViewEntity( player ) + if ( player.ContextAction_IsBusy() ) + player.ContextAction_ClearBusy() + player.EnableWeaponWithSlowDeploy() + + FlagClear( "DoingCinematicTimeshift" ) + + FlagSet( "CinematicTimeshiftSequenceFinished") + + + proxy = player.GetFirstPersonProxy() + if ( proxy == null ) + return + + bodyGroupIndex = proxy.FindBodyGroup( "glove_default" ) + proxy.SetBodygroup( bodyGroupIndex, 0 ) // 0 = show, 1 = hide + + //bodyGroupIndex2= proxy.FindBodyGroup( "glove_animated" ) + //proxy.SetBodygroup( bodyGroupIndex2, 1 ) // 0 = show, 1 = hide + +} + +void function PlayAndersonCorpseAnims( entity anderson, entity node ) +{ + waitthread PlayAnim( anderson, "pt_timeshift_device_equip_corpse_sequence", node ) + + //watch + //int bodyGroupIndex = anderson.FindBodyGroup( "watch" ) + //anderson.SetBodygroup( bodyGroupIndex, 1 ) + + int bodyGroupIndex = anderson.FindBodyGroup( "watch_ts" ) + anderson.SetBodygroup( bodyGroupIndex, 0 ) + + thread PlayAnim( anderson, "pt_timeshift_device_equip_corpse_sequence_02", node ) + +} +void function TimeshiftSequenceShifts( entity player, entity node ) +{ + player.EndSignal( "OnDeath" ) + entity proxy = player.GetFirstPersonProxy() + vector nodeOrigin = node.GetOrigin() + vector playerOrigin = player.GetOrigin() + + + WaitSignal( proxy, "AnimTimeshift" ) + SwapTimelines( player, TIMEZONE_DAY ) + node.SetAbsOrigin( Vector( nodeOrigin.x, nodeOrigin.y, nodeOrigin.z + TIME_ZOFFSET ) ) + //player.SetAbsOrigin( Vector( playerOrigin.x, playerOrigin.y, playerOrigin.z + TIME_ZOFFSET ) ) + + wait 0.1 + //EmitSoundOnEntity( player, "Pilot_PhaseShift_End_3P" ) + + WaitSignal( proxy, "AnimTimeshift" ) + SwapTimelines( player, TIMEZONE_NIGHT ) + node.SetAbsOrigin( nodeOrigin ) + //player.SetAbsOrigin( playerOrigin ) + +} + +///////////////////////////////////////////////////////////////////////////////////////// +/* +██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███████╗███████╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗ +██║ ██║██║██║ ██╔══██╗██║ ██║██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██║ +██║ █╗ ██║██║██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔╝█████╗ ███████╗█████╗ ███████║██████╔╝██║ ███████║ +██║███╗██║██║██║ ██║ ██║██║ ██║██╔══╝ ██╔══╝ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██║██╔══██╗██║ ██╔══██║ +╚███╔███╔╝██║███████╗██████╔╝███████╗██║██║ ███████╗ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║ + ╚══╝╚══╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝╚═╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ + */ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███████╗███████╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗ +██║ ██║██║██║ ██╔══██╗██║ ██║██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██║ +██║ █╗ ██║██║██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔╝█████╗ ███████╗█████╗ ███████║██████╔╝██║ ███████║ +██║███╗██║██║██║ ██║ ██║██║ ██║██╔══╝ ██╔══╝ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██║██╔══██╗██║ ██╔══██║ +╚███╔███╔╝██║███████╗██████╔╝███████╗██║██║ ███████╗ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║ + ╚══╝╚══╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝╚═╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ + */ +///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// +/* +██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███████╗███████╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗ +██║ ██║██║██║ ██╔══██╗██║ ██║██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██║ +██║ █╗ ██║██║██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔╝█████╗ ███████╗█████╗ ███████║██████╔╝██║ ███████║ +██║███╗██║██║██║ ██║ ██║██║ ██║██╔══╝ ██╔══╝ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██║██╔══██╗██║ ██╔══██║ +╚███╔███╔╝██║███████╗██████╔╝███████╗██║██║ ███████╗ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║ + ╚══╝╚══╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝╚═╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ + */ +///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// +/* +██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███████╗███████╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗ +██║ ██║██║██║ ██╔══██╗██║ ██║██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██║ +██║ █╗ ██║██║██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔╝█████╗ ███████╗█████╗ ███████║██████╔╝██║ ███████║ +██║███╗██║██║██║ ██║ ██║██║ ██║██╔══╝ ██╔══╝ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██║██╔══██╗██║ ██╔══██║ +╚███╔███╔╝██║███████╗██████╔╝███████╗██║██║ ███████╗ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║ + ╚══╝╚══╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝╚═╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ + */ +///////////////////////////////////////////////////////////////////////////////////////// +void function WildlifeResearchStartPointSetup( entity player ) +{ + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointWildlifeResearch" ) ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function WildlifeResearchSkipped( entity player ) +{ + CleanupEnts( "flyer_lab" ) + CleanupEnts( "civilian_evac_firehall" ) + //thread MusicWildlifeResearch( player ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_WildlifeResearchThread( entity player ) +{ + FlagWait( "CinematicTimeshiftSequenceFinished") + + vector objectivePos = GetEntByScriptName( "obj_creature_labs_after_fire_hall" ).GetOrigin() + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos ) + + Remote_CallFunction_NonReplay( player, "ServerCallback_PlayGloveGlow", TIMEZONE_NIGHT ) + + CheckPoint_Forced() + + thread MusicWildlifeResearch( player ) + //CheckPoint() + + wait 1 + + thread DisplayOnscreenHint( player, "timeshift_hint_default", 3.0 ) + + //failsafeFlagToStart lookAtEnt = null + thread QuickSkit( player, GetEntByScriptName( "node_evac_firehall01" ), "headed_into_fire_hall" ) + thread QuickSkit( player, GetEntByScriptName( "node_evac_firehall02" ), "headed_into_fire_hall" ) + thread QuickSkit( player, GetEntByScriptName( "node_creature_soldiers_surprised" ), "open_door_creature_labs_part2" ) + + //------------------------------------------------- + // Use timeshift to get past electricity + //------------------------------------------------ + thread DialogueTimeShiftEquipped( player ) + thread DialogueCivilianEvacCreatureLabs( player ) + thread DialogueCreaturLabsIMC( player ) + + thread TimeshiftHint( player, TIMEZONE_NIGHT, "player_entered_creature_lab", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_first_ts_hazard" ) ) + + + FlagWait( "player_entered_creature_lab" ) + CleanupEnts( "civilian_walker_courtyard" ) + CleanupEnts( "civilian_actor_firehall01" ) + CleanupEnts( "civilian_actor_firehall02" ) + + + // remove this so we don't softlock other players lol + //FlagClear( "open_creature_door_start_pristine" ) + + objectivePos = GetEntByScriptName( "objective_spoke1_breadcrumb000" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + CheckPoint_Forced() + + thread TimeshiftHint( player, TIMEZONE_DAY, "player_past_creature_first_laser", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_creature_hazard" ) ) + + thread ObjectiveRemindUntilFlag( "player_past_creature_first_laser" ) + + FlagWait( "player_past_creature_first_laser" ) + + objectivePos = GetEntByScriptName( "objective_spoke1_breadcrumb00" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + thread TimeshiftHint( player, TIMEZONE_NIGHT, "player_past_creature_second_obstacle", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_creature_hazard2" ) ) + + CheckPoint_Forced() + + //lookAtEnt + thread QuickSkit( player, GetEntByScriptName( "node_evac_creaturelabs_2dudes" ), "player_past_creature_second_obstacle" ) + thread QuickSkit( player, GetEntByScriptName( "node_evac_creaturelabs_bench2" ), "player_past_creature_second_obstacle" ) + + thread ObjectiveRemindUntilFlag( "player_past_creature_second_obstacle" ) + + FlagWait( "player_past_creature_second_obstacle" ) + + + thread TimeshiftHint( player, TIMEZONE_DAY, "player_entered_creature_fans", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_creature_fan_hazard" ) ) + + objectivePos = GetEntByScriptName( "objective_spoke1_breadcrumb00aa" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + + //--------------------------------------- + // Get past laser door in pristine via fans + //--------------------------------------- + + thread ObjectiveRemindUntilFlag( "player_exited_creature_labs" ) + + FlagWait( "player_entered_creature_fans" ) + + CleanupEnts( "civilian_evac_firehall" ) + thread TimeshiftHint( player, TIMEZONE_NIGHT, "player_exited_creature_labs", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_creature_fan_blockage" ) ) + + FlagWait( "player_exited_creature_labs" ) + + +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function MusicWildlifeResearch( entity player ) +{ + if ( Flag( "entered_amenities_elevator_room" ) ) + return + + FlagEnd( "entered_amenities_elevator_room" ) + + + //waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + StopMusic() + SetGlobalNetBool( "music14LoopPausable", false ) + PlayMusicThatCantBeStopped( "music_timeshift_14_pastloop" ) + + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + //Until the elevator fight, whenever the player switches in the PRESENT - Play music_timeshift_16_explorepresent + thread PlayMusicInTimezoneUntilFlag( "music_timeshift_16_explorepresent", TIMEZONE_NIGHT, "entered_amenities_elevator_room" ) + +} + + + +///////////////////////////////////////////////////////////////////////////////////////// +void function OnDeathProwlerAcheivement( entity npc, var damageInfo ) +{ + if ( Flag( "ProwlerAcheivementUnlocked" ) ) + return + + if ( Flag( "crossed_elevator_fire_chasm" ) ) + return + + entity attacker = DamageInfo_GetAttacker( damageInfo ) + + if ( !IsValid ( attacker ) ) + return + + if ( !attacker.IsPlayer() ) + return + + entity player = attacker + + string classname = npc.GetClassName() + + if ( !npc.HasKey( "script_noteworthy") ) + return + + string scriptNoteworthy = expect string( npc.kv.script_noteworthy ) + + if ( scriptNoteworthy != "prowler_acheivement" ) + return + + FlagSet( "ProwlerAcheivementUnlocked" ) + UnlockAchievement( player, achievements.TIMESHIFT_PROWLER ) + +} + + + +///////////////////////////////////////////////////////////////////////////////////////// +void function DialogueTimeShiftEquipped( entity player ) +{ + FlagWait( "player_entered_creature_lab" ) + + wait 1 + + //BT Pilot Cooper, I have transferred some of my AI functions to the device, in order to permit communication across temporal shifts. + thread PlayBTDialogue( "diag_sp_wildlifeStudy_TS191_12_01_mcor_bt" ) + + FlagWait( "player_nearing_creature_fans" ) + + array <entity> grunts + entity gruntSpeaker + int gruntLines = 6 + int gruntLinesPlayed = 0 + string gruntAlias + + while( true ) + { + if ( gruntLinesPlayed >= gruntLines ) + break + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + gruntSpeaker = GetClosestGrunt( player, TIMEZONE_DAY ) + if ( !IsValid( gruntSpeaker ) ) + continue + if ( Distance( player.GetOrigin(), gruntSpeaker.GetOrigin() ) > 512 ) + continue + + switch( gruntLinesPlayed ) + { + case 0: + //IMC Grunt 2 (Radio) What the hell? He was just over there! + gruntAlias = "diag_sp_wildlifeStudy_TS191_17_01_imc_grunt2" + break + case 1: + //security3 (radio comms, agitated): Who has visual? + gruntAlias = "diag_sp_securityComs_TS104_01_01_imc_security3" + break + case 2: + //security1 (radio comms, agitated): Can't track him, he's bouncing all over the place. + gruntAlias = "diag_sp_securityComs_TS104_02_01_imc_security1" + break + case 3: + //security1 (radio comms, agitated): Someone give me a location on this guy! + gruntAlias = "diag_sp_securityComs_TS106_01_01_imc_security1" + break + case 4: + //security3 (radio comms, agitated): Where the hell did he go? + gruntAlias = "diag_sp_securityComs_TS106_03_01_imc_security3" + break + case 5: + //security2 (radio comms, agitated): Watch your back! Watch your back! + gruntAlias = "diag_sp_securityComs_TS106_02_01_imc_security2" + break + } + + waitthread PlayTimeShiftDialogue( player, gruntSpeaker, gruntAlias ) + gruntLinesPlayed++ + + //Don't do next line till you hit elevator fight + FlagWait( "crossed_elevator_fire_chasm" ) + + } + +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function DialogueCivilianEvacCreatureLabs( entity player ) +{ + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + //IMC Security 3 - PA ...ask that all non-combat personnel and research teams proceed directly to the nearest + //Emergency Shelter access point due to a minor security breach. Please remain calm and contact the nearest automated security personnel for assistance. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_13_01_imc_security3" ) + + wait 1 + + soundEnt.Destroy() + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function DialogueCreaturLabsIMC( entity player ) +{ + FlagWait( "player_past_creature_first_laser" ) + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + SetGlobalForcedDialogueOnly( true ) + + //IMC Grunt 1 (Radio) ….last spotted him headed towards Wildlife research. Get the rest of the eggheads evacuated and set up a choke point. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_14_01_imc_grunt1" ) + + SetGlobalForcedDialogueOnly( false ) + + FlagWait( "player_past_creature_second_obstacle" ) + //IMC Base (Radio) Additional laser meshes coming online. Let's box him in. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_15_01_imc_command" ) + + FlagWait( "player_entered_creature_fans" ) + + SetGlobalForcedDialogueOnly( true ) + + //IMC Grunt 3 (Radio) Control, we have initiated contact with the intruder but his movement is...erratic. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_18_01_imc_grunt3" ) + + SetGlobalForcedDialogueOnly( false ) + + soundEnt.Destroy() +} + + +void function CourtyardSoldiersThink( entity npc ) +{ + if ( Flag( "player_entered_creature_lab") ) + return + FlagEnd( "player_entered_creature_lab" ) + npc.EnableNPCFlag( NPC_IGNORE_ALL ) + npc.SetNoTarget( true ) + + OnThreadEnd( + function() : ( npc ) + { + if ( IsValid( npc ) ) + npc.Destroy() + } + ) + + WaitSignal( npc, "OnFinishedAssault" ) + +} + +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗██████╗ ███████╗████████╗ ████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗ +██╔════╝██║██╔══██╗██╔════╝╚══██╔══╝ ╚══██╔══╝██║████╗ ████║██╔════╝██╔════╝██║ ██║██║██╔════╝╚══██╔══╝ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝ +█████╗ ██║██████╔╝███████╗ ██║ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ █████╗ ██║██║ ███╗███████║ ██║ +██╔══╝ ██║██╔══██╗╚════██║ ██║ ██║ ██║██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║██║██╔══╝ ██║ ██╔══╝ ██║██║ ██║██╔══██║ ██║ +██║ ██║██║ ██║███████║ ██║ ██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║██║██║ ██║ ██║ ██║╚██████╔╝██║ ██║ ██║ +╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗██████╗ ███████╗████████╗ ████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗ +██╔════╝██║██╔══██╗██╔════╝╚══██╔══╝ ╚══██╔══╝██║████╗ ████║██╔════╝██╔════╝██║ ██║██║██╔════╝╚══██╔══╝ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝ +█████╗ ██║██████╔╝███████╗ ██║ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ █████╗ ██║██║ ███╗███████║ ██║ +██╔══╝ ██║██╔══██╗╚════██║ ██║ ██║ ██║██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║██║██╔══╝ ██║ ██╔══╝ ██║██║ ██║██╔══██║ ██║ +██║ ██║██║ ██║███████║ ██║ ██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║██║██║ ██║ ██║ ██║╚██████╔╝██║ ██║ ██║ +╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗██████╗ ███████╗████████╗ ████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗ +██╔════╝██║██╔══██╗██╔════╝╚══██╔══╝ ╚══██╔══╝██║████╗ ████║██╔════╝██╔════╝██║ ██║██║██╔════╝╚══██╔══╝ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝ +█████╗ ██║██████╔╝███████╗ ██║ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ █████╗ ██║██║ ███╗███████║ ██║ +██╔══╝ ██║██╔══██╗╚════██║ ██║ ██║ ██║██║╚██╔╝██║██╔══╝ ╚════██║██╔══██║██║██╔══╝ ██║ ██╔══╝ ██║██║ ██║██╔══██║ ██║ +██║ ██║██║ ██║███████║ ██║ ██║ ██║██║ ╚═╝ ██║███████╗███████║██║ ██║██║██║ ██║ ██║ ██║╚██████╔╝██║ ██║ ██║ +╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + + + +///////////////////////////////////////////////////////////////////////////////////////// +void function FirstTimeshiftFightStartPointSetup( entity player ) +{ + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointFirstTimeshiftFight" ) ) + vector objectivePos = GetEntByScriptName( "objective_amenities_elevator_fight" ).GetOrigin() + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function FirstTimeshiftFightSkipped( entity player ) +{ + FlagClear( "ShowMobilityGhostTurretFirepit" ) + CleanupEnts( "flyer_lab" ) + CleanupEnts( "lab_prowlers" ) + thread LoudspeakerThread( player ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_FirstTimeshiftFightThread( entity player ) +{ + FlagWait( "player_exited_creature_labs" ) + + vector objectivePos = GetEntByScriptName( "objective_spoke1_breadcrumb01" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + + thread LoudspeakerThread( player ) + FlagSet( "ShowMobilityGhostTurretFirepit" ) + + CheckPoint() + + thread DialogueToElevatorFight( player ) + thread SecurityRoom( player ) + //------------------------------- + // Hallway turret setup + //------------------------------- + + array <entity> turrets = GetEntArrayByScriptName( "turrets_hallway_to_elevators" ) + foreach( turret in turrets ) + { + //bool hasShield = true + //thread HACK_DisableTurret( turret, hasShield ) + turret.SetDumbFireMode( true ) + } + + FlagWait( "open_door_elevator_fight_hallway_both" ) + + CheckPoint() + + objectivePos = GetEntByScriptName( "objective_amenities_elevator_fight" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + //------------------------------- + // Hallway turret flank + //------------------------------- + + foreach( turret in turrets ) + { + //bool hasShield = true + //thread HACK_EnableTurret( turret ) + } + + FlagWait( "player_entered_turret_to_elevator_hallway" ) + + FlagWait( "crossed_elevator_fire_chasm" ) + + //FlagClear( "open_door_elevator_fight_hallway_both" ) + FlagClear( "ShowMobilityGhostTurretFirepit" ) + CleanupEnts( "flyer_lab" ) + CleanupEnts( "lab_prowlers" ) + + FlagWait( "entered_amenities_elevator_room" ) + +} + + +void function CreatureSurprisedSoldiersThink( entity npc ) +{ + npc.EndSignal( "OnDeath" ) + + FlagWait( "player_past_creature_second_obstacle" ) + + npc.Anim_Stop() +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function SecurityRoom( entity player ) +{ + player.EndSignal( "OnDeath" ) + + entity deadCivilian = CreatePropDynamic( IMC_CORPSE_MODEL_CIV, Vector( 3686, -3820, -764 ), Vector( 0, 29.0586, 0 ), 0 ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only + + deadCivilian.Hide() + int shiftCount = 0 + int shiftRequirement = 88 + entity triggerOvergrown = GetEntByScriptName( "trigger_security_underground_overgrown" ) + entity triggerPristine = GetEntByScriptName( "trigger_security_underground_pristine" ) + + while( true ) + { + wait 0.1 + if ( shiftCount >= shiftRequirement ) + break + FlagWait( "in_fire_pit" ) + + if ( level.timeZone == TIMEZONE_DAY ) + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + else + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + + if ( ( triggerOvergrown.IsTouching( player ) ) || ( triggerPristine.IsTouching( player ) ) ) + shiftCount++ + else + continue + } + + FlagSet( "open_door_security_underground" ) + + deadCivilian.Show() + + + +} +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗ +██╔════╝██║ ██╔════╝██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝ +█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╝ █████╗ ██║██║ ███╗███████║ ██║ +██╔══╝ ██║ ██╔══╝ ╚██╗ ██╔╝██╔══██║ ██║ ██║ ██║██╔══██╗ ██╔══╝ ██║██║ ██║██╔══██║ ██║ +███████╗███████╗███████╗ ╚████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ██║ ██║ +╚══════╝╚══════╝╚══════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗ +██╔════╝██║ ██╔════╝██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝ +█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╝ █████╗ ██║██║ ███╗███████║ ██║ +██╔══╝ ██║ ██╔══╝ ╚██╗ ██╔╝██╔══██║ ██║ ██║ ██║██╔══██╗ ██╔══╝ ██║██║ ██║██╔══██║ ██║ +███████╗███████╗███████╗ ╚████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ██║ ██║ +╚══════╝╚══════╝╚══════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗ +██╔════╝██║ ██╔════╝██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝ +█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╝ █████╗ ██║██║ ███╗███████║ ██║ +██╔══╝ ██║ ██╔══╝ ╚██╗ ██╔╝██╔══██║ ██║ ██║ ██║██╔══██╗ ██╔══╝ ██║██║ ██║██╔══██║ ██║ +███████╗███████╗███████╗ ╚████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ██║ ██║ +╚══════╝╚══════╝╚══════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗ +██╔════╝██║ ██╔════╝██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝ +█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╝ █████╗ ██║██║ ███╗███████║ ██║ +██╔══╝ ██║ ██╔══╝ ╚██╗ ██╔╝██╔══██║ ██║ ██║ ██║██╔══██╗ ██╔══╝ ██║██║ ██║██╔══██║ ██║ +███████╗███████╗███████╗ ╚████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ██║ ██║ +╚══════╝╚══════╝╚══════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +void function ElevatorFightStartPointSetup( entity player ) +{ + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointElevatorFight" ) ) + vector objectivePos = GetEntByScriptName( "objective_amenities_elevator_fight" ).GetOrigin() + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function ElevatorFightSkipped( entity player ) +{ + FlagClear( "ShowMobilityGhostElevatorShaft" ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_ElevatorFightThread( entity player ) +{ + + FlagWait( "entered_amenities_elevator_room" ) + + thread DialogueElevatorRoom( player ) + + FlagSet( "ShowMobilityGhostElevatorShaft" ) + + thread MusicElevatorFight( player ) + + //FlagClear( "open_door_elevator_fight_hallway_both" ) + + CheckPoint() + + vector objectivePos = GetEntByScriptName( "objective_amenities_elevator" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + + thread SetFlagWhenPlayerLookingAtEnt( player, "PlayerLookingTowardsElevators", GetEntByScriptName( "elevator_look_target"), GetEntByScriptName( "trigger_look_elevators" ) ) + + array< entity > propSpawners = GetEntArrayByScriptNameInInstance( "spectre_door_spawner", "spectre_spawner_amenities_elevator_overgrown_upper_02" ) + float delayMin = 0.5 + float delayMax = 0.6 + int maxToSpawn = 1 + string flagToAbort = "" + thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, "", delayMin, delayMax ) + + FlagWait( "time_shifted_to_elevator_room_past" ) + + //------------------------------- + // Elevator fight + //------------------------------- + thread OpenElevatorDoorsOvergrownThink() + thread TurretEnemiesComeIntoElevatorRoom() + FlagWait( "PlayerLookingTowardsElevators" ) + + thread AllElevatorDudesDead() + FlagSet( "DisplayTheDamageHint" ) + thread ElevatorObjectiveReminder( player ) + //------------------------------- + // Token prowlers elevator room + //------------------------------- + int difficulty = GetSpDifficulty() + if ( difficulty < DIFFICULTY_MASTER ) + maxToSpawn = 5 //max for this room is 10 + else + maxToSpawn = 8 //max for this room is 10 + + propSpawners = GetEntArrayByScriptName( "prowler_spawnvents_elevator_room" ) + delayMin = 3.5 + delayMax = 6 + maxToSpawn = 5 //max for this room is 10 + flagToAbort = "AllElevatorDudesDead" + string flagToSetWhenAllAreSpawned = "AllElevatorProwlersSpawned" + bool requiresLookAt = true + thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, flagToSetWhenAllAreSpawned, delayMin, delayMax, "", requiresLookAt ) + + //------------------------------------------ + // Player figures out elevator time puzzle + //------------------------------------------- + FlagWait( "entered_amenities_elevator" ) + + objectivePos = GetEntByScriptName( "objective_elevator_top" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + FlagClear( "DisplayTheDamageHint" ) + + FlagWait( "at_elevatorshaft_top" ) + + FlagClear( "ShowMobilityGhostElevatorShaft" ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function DialogueElevatorRoom( entity player ) +{ + + +/* +██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗ +██╔══██╗██║██╔══██╗██║ ██╔═══██╗██╔════╝ ██║ ██║██╔════╝ +██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗ +██║ ██║██║██╔══██║██║ ██║ ██║██║ ██║██║ ██║██╔══╝ +██████╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ +*/ + + FlagWait( "entered_amenities_elevator" ) + + entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + if( Flag( "exited_elevator_run") ) + return + FlagEnd( "exited_elevator_run" ) + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + + if ( Flag( "AllElevatorDudesDead" ) ) + { + + //IMC Base (Radio) Security, Beta 4, do you copy? Beta 4! What is your position? + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_16_01_imc_command" ) + } + else + { + //IMC Base (Radio) Has the intruder been neutralized? + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_22_01_imc_command" ) + + //IMC Grunt 6 (Radio) Standby, we can't get a lock on his position + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_23_01_imc_grunt6" ) + } + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function HallwayTurretDudesThink( entity npc ) +{ + npc.EndSignal( "OnDeath" ) + + //Kill these guys if player has finished elevator fight + FlagWait( "AllElevatorDudesDead" ) + + array <entity> players = GetPlayerArray() + if ( players.len() <= 0 ) + return + entity player = players[ 0 ] + thread DeleteNpcWhenOutOfSight( npc, player ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function ElevatorObjectiveReminder( entity player ) +{ + if( Flag( "at_elevatorshaft_top") ) + return + FlagEnd( "at_elevatorshaft_top" ) + + FlagWait( "AllElevatorDudesDead" ) + + wait 5 + + while( true ) + { + if ( level.timeZone == TIMEZONE_DAY ) + Objective_Remind() + else if ( ( level.timeZone == TIMEZONE_NIGHT ) && ( !Flag( "enemies_inside_elevator_room_trigger_overgrown") ) ) + Objective_Remind() + + wait ( RandomFloatRange( 30, 40 ) ) + } + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function GruntElevatorThink( entity npc ) +{ + if ( !IsValid( npc ) ) + return + npc.EndSignal( "OnDeath" ) + + npc.EnableNPCFlag( NPC_NO_MOVING_PLATFORM_DEATH ) + + OnThreadEnd( + function() : ( ) + { + file.elevatorDudesDead++ + if ( file.elevatorDudesDead >= 4 ) + FlagSet( "SeveralElevatorDudesDead" ) + } + ) + + if ( npc.HasKey( "script_noteworthy") ) + { + string anim = expect string( npc.kv.script_noteworthy ) + string animIdle = anim + "_idle" + entity node = GetClosest( file.elevatorAnimNodes, npc.GetOrigin() ) + Assert( IsValid( node ) ) + thread PlayAnimTeleport( npc, animIdle, node ) + + entity elevatorDoor = GetClosest( file.elevatorDoors, npc.GetOrigin() ) + Assert( IsValid( elevatorDoor ) ) + + string flagToWaitFor = expect string( elevatorDoor.kv.script_flag ) + + if ( !Flag( flagToWaitFor ) ) + { + FlagWait( flagToWaitFor ) + //wait 0.5 + } + + thread PlayAnimTeleport( npc, anim, node ) + + } + + WaitForever() +} +///////////////////////////////////////////////////////////////////////////////////////// +void function TurretEnemiesComeIntoElevatorRoom() +{ + /* + entity goal_lobby_middle = GetEntByScriptName( "goal_lobby_middle" ) + array <entity> grunts = GetNPCArrayBySquad( "kfiejfff" ) + foreach( grunt in grunts ) + { + if ( !IsAlive( grunt ) ) + continue + grunt.AssaultPoint( goal_lobby_middle.GetOrigin() ) + } + */ +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function OpenElevatorDoorsOvergrownThink() +{ + FlagWait( "AllElevatorDudesDead" ) + HideStuff( "doors_and_blockers_elevator_overgrown" ) + entity navmesh_blocker_elevator_overgrown = GetEntByScriptName( "navmesh_blocker_elevator_overgrown" ) + navmesh_blocker_elevator_overgrown.Hide() + navmesh_blocker_elevator_overgrown.NotSolid() + ToggleNPCPathsForEntity( navmesh_blocker_elevator_overgrown, true ) + + +//ToggleNPCPathsForEntity( GetEntByScriptName( "navmesh_blocker_elevator_overgrown" ), true ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function MusicElevatorFight( entity player ) +{ + FlagWait( "entered_amenities_elevator_room" ) + + wait 0.1 + + StopMusic() + PlayMusic( "music_timeshift_17_enterelevatorarea" ) + + FlagWait( "elevator_open_d" ) + + StopMusic() + PlayMusic( "music_timeshift_18_startelevatorfight" ) + SetGlobalNetBool( "music14LoopPausable", true ) + + if( Flag( "entered_amenities_elevator") ) + return + + FlagEnd( "entered_amenities_elevator" ) + + + //When you climb out of the roof of the elevator - Play music_timeshift_21b_climboutofelevator + //also need to stop the old music_timeshift_14_pastloop - Just play music_timeshift_21c_pastloop_stop + OnThreadEnd( + function() : ( player ) + { + StopMusic() + PlayMusicThatCantBeStopped( "music_timeshift_21c_pastloop_stop" ) + if ( IsValid( player ) ) + StopSoundOnEntity( player, "music_timeshift_21_combatpresentdone" ) + PlayMusic( "music_timeshift_21b_climboutofelevator" ) + if ( IsValid( player ) ) + { + StopSoundOnEntity( player, "music_timeshift_14_pastloop" ) + printl( "manually stopping music: music_timeshift_14_pastloop") + } + SetGlobalNetBool( "music14LoopPausable", false ) + } + ) + + + + + + //----------------------------------------------------------------------------------------------- + // Don't start playing different time period-specific tracks till the player starts time traveling + //----------------------------------------------------------------------------------------------- + if ( level.timeZone == TIMEZONE_NIGHT ) + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + else + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + + + while( true ) + { + + + if ( level.timeZone == TIMEZONE_NIGHT ) + { + //---------------------- + // Enemies still alive TIMEZONE_NIGHT + //---------------------- + if ( ( Flag( "enemies_inside_elevator_room_trigger_overgrown") ) || ( !Flag( "AllElevatorProwlersSpawned" ) ) ) + { + //During the combat, if the player switches to PRESENT - Play music_timeshift_20_combatpresent + //StopMusic() + StopMusicTrack( "music_timeshift_18_startelevatorfight" ) + PlayMusicThatCantBeStopped( "music_timeshift_20_combatpresent" ) + + //While in TIMEZONE_DAY, check to see when all are dead, so we can stop combat music + while( level.timeZone == TIMEZONE_NIGHT ) + { + wait 0.1 + if ( ( !Flag( "enemies_inside_elevator_room_trigger_overgrown" ) ) + && ( Flag( "AllElevatorProwlersSpawned" ) ) + ) + break + } + } + + //------------------------------------------------ + // If all enemies spawned and dead in TIMEZONE_NIGHT, play the "done" track + //------------------------------------------------ + if ( ( !Flag( "enemies_inside_elevator_room_trigger_overgrown") ) && ( Flag( "AllElevatorProwlersSpawned" ) ) && ( level.timeZone == TIMEZONE_NIGHT ) ) + { + //Each time the player switches to the PRESENT where there are no more enemies - Play music_timeshift_21_combatpresentdone + //StopMusic() + if ( IsValid( player ) ) + StopSoundOnEntity( player, "music_timeshift_21_combatpresentdone" ) + PlayMusicThatCantBeStopped( "music_timeshift_21_combatpresentdone" ) + } + + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + } + else //TIMEZONE_DAY + { + //---------------------- + // Enemies still alive TIMEZONE_DAY + //---------------------- + if ( ( !Flag( "AllElevatorDudesDead" ) ) && ( level.timeZone == TIMEZONE_DAY ) ) + { + //During the combat, if the player switches to PAST - Play music_timeshift_19_combatpast + PlayMusic( "music_timeshift_19_combatpast" ) + + //While in TIMEZONE_DAY, check to see when all are dead, so we can stop combat music + while( level.timeZone == TIMEZONE_DAY ) + { + wait 0.1 + if ( Flag( "AllElevatorDudesDead" ) ) + break + } + + } + + //------------------------------------------------ + //If all enemies dead in TIMEZONE_DAY, play the "done" track + //------------------------------------------------ + if ( ( Flag( "AllElevatorDudesDead" ) ) && ( level.timeZone == TIMEZONE_DAY ) ) + { + //Each time the player switches to the PAST where there are no more enemies - Play music_timeshift_21a_combatpastdone + //StopMusic() + PlayMusic( "music_timeshift_21a_combatpastdone" ) + } + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + } + } + + +} + + +///////////////////////////////////////////////////////////////////////////////////////// + +void function DialogueToElevatorFight( entity player ) +{ + player.EndSignal( "OnDeath" ) + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + FlagWait( "open_door_elevator_fight_hallway_both" ) + + SetGlobalForcedDialogueOnly( true ) + + //IMC Grunt 8 (Radio) In position. Activating turrets at south-east corridor. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_26_01_imc_grunt8" ) + + //IMC Base (Radio) We have an intruder, heavily armed and dangerous. All units in the area proceed to the south-east elevator banks on level 4 to intercept. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_19_01_imc_command" ) + + + if ( !Flag( "entered_amenities_elevator_room" ) ) + { + //IMC Grunt 4 (Radio) Copy that, control! + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_20_01_imc_grunt4" ) + + } + + if ( !Flag( "entered_amenities_elevator_room" ) ) + { + //IMC Grunt 5 (Radio) On our way, control! + thread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_21_01_imc_grunt5" ) + + } + + FlagWait( "SeveralElevatorDudesDead" ) + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + if ( Flag( "entered_amenities_elevator" ) || Flag( "AllElevatorDudesDead" ) ) + { + SetGlobalForcedDialogueOnly( false ) + return + } + + //IMC Base (Radio) Sitrep on the unknown target. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_24_01_imc_command" ) + + + if ( Flag( "entered_amenities_elevator" ) || Flag( "AllElevatorDudesDead" ) ) + { + SetGlobalForcedDialogueOnly( false ) + return + } + + entity soldier = GetClosestGrunt( player, TIMEZONE_DAY, "grunts_elevator" ) + + if ( IsValid( soldier ) ) + { + //IMC Grunt 7 (Radio) We're getting our asses kicked out here, that's the bloody SitRep! + waitthread PlayTimeShiftDialogue( player, soldier, "diag_sp_wildlifeStudy_TS191_25_01_imc_grunt7" ) + } + + SetGlobalForcedDialogueOnly( false ) + + soundEnt.Destroy() + +} + +void function AllElevatorDudesDead() +{ + FlagWaitAll( "ElevatorDudesDead1", "ElevatorDudesDead2", "ElevatorDudesDead3", "ElevatorDudesDead4" ) + FlagSet( "AllElevatorDudesDead" ) +} +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ████████╗ ██████╗ ██████╗ +██╔════╝██║ ██╔════╝██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ╚══██╔══╝██╔═══██╗██╔══██╗ +█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╝ ██║ ██║ ██║██████╔╝ +██╔══╝ ██║ ██╔══╝ ╚██╗ ██╔╝██╔══██║ ██║ ██║ ██║██╔══██╗ ██║ ██║ ██║██╔═══╝ +███████╗███████╗███████╗ ╚████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║ ██║ ╚██████╔╝██║ +╚══════╝╚══════╝╚══════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ████████╗ ██████╗ ██████╗ +██╔════╝██║ ██╔════╝██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ╚══██╔══╝██╔═══██╗██╔══██╗ +█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╝ ██║ ██║ ██║██████╔╝ +██╔══╝ ██║ ██╔══╝ ╚██╗ ██╔╝██╔══██║ ██║ ██║ ██║██╔══██╗ ██║ ██║ ██║██╔═══╝ +███████╗███████╗███████╗ ╚████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║ ██║ ╚██████╔╝██║ +╚══════╝╚══════╝╚══════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ████████╗ ██████╗ ██████╗ +██╔════╝██║ ██╔════╝██║ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ╚══██╔══╝██╔═══██╗██╔══██╗ +█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╝ ██║ ██║ ██║██████╔╝ +██╔══╝ ██║ ██╔══╝ ╚██╗ ██╔╝██╔══██║ ██║ ██║ ██║██╔══██╗ ██║ ██║ ██║██╔═══╝ +███████╗███████╗███████╗ ╚████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║ ██║ ╚██████╔╝██║ +╚══════╝╚══════╝╚══════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// + +void function ElevatorTopStartPointSetup( entity player ) +{ + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointElevatorTop" ) ) + vector objectivePos = GetEntByScriptName( "objective_intel_data_panel01" ).GetOrigin() + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function ElevatorTopSkipped( entity player ) +{ + FlagSet( "LabBravoEnemiesDead" ) + FlagSet( "entered_hallways_to_human_research" ) + FlagSet( "open_door_elevator_top_lab" ) + FlagSet( "StartAndersonHologram1" ) + FlagClear( "open_door_lab_civilian_escape_01" ) + +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_ElevatorTopThread( entity player ) +{ + FlagWait( "at_elevatorshaft_top" ) + FlagClear( "open_door_elevator_top_lab" ) + //failsafeFlagToStart + thread QuickSkit( player, GetEntByScriptName( "node_holo1_evac" ), "exited_elevator_run" ) + + thread LabSoldierA( player ) + + FlagWait( "exited_elevator_run" ) + + SetGlobalForcedDialogueOnly( true ) + + thread MusicElevatorTop( player ) + thread DialogueLabAlphaEvac( player ) + thread DialogueLabAlpha( player ) + thread ProwlerGagLabAlpha( player ) + thread AndersonHologramSequence( player, "node_hologram_lab1", "StartAndersonHologram1" ) + thread AchievementAndersonsFirstLog( player ) + + vector objectivePos = GetEntByScriptName( "objective_intel_data_panel01" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + CheckPoint() + + FlagWait( "open_door_elevator_top_lab" ) + + CheckPoint() + + CleanupEnts( "grunts_elevator" ) + + thread CleanupAI( player ) + + ShowStuff( "elevator_doors_upper_overgrown" ) + + wait 2 + + SetGlobalForcedDialogueOnly( false ) + + FlagClear( "open_door_lab_civilian_escape_01" ) + + FlagWait( "AndersonHologram1Finished" ) + + + FlagSet( "IntelRoom1Finished" ) + + if ( !Flag( "back_in_hall_after_anderson_first_log" ) ) + CheckPoint() + + objectivePos = GetEntByScriptName( "objective_sphere_room_breadcrumb01" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + thread ObjectiveRemindUntilFlag( "entered_hallways_to_human_research" ) + + FlagWait( "entered_hallways_to_human_research" ) + +} + +void function AchievementAndersonsFirstLog( entity player ) +{ + FlagWait( "AndersonHologram1Playing" ) + UnlockAchievement( player, achievements.VIEW_LOG ) +} + + +///////////////////////////////////////////////////////////////////////////////////////// + +void function LabSoldierA( entity player ) +{ + entity node = GetEntByScriptName( "node_sandtable_soldier_react" ) + Assert( IsValid( node ) ) + entity spawner = GetEntByScriptName( "labA_soldier" ) + Assert( IsValid( spawner ) ) + entity npc = spawner.SpawnEntity() + DispatchSpawn( npc ) + Assert( IsValid( npc ) ) + + npc.EndSignal( "OnDeath" ) + + if( Flag( "open_door_elevator_top_lab") ) + return + FlagEnd( "open_door_elevator_top_lab" ) + + OnThreadEnd( + function() : ( npc ) + { + if ( IsValid( npc ) ) + npc.Destroy() + } + ) + + npc.EnableNPCFlag( NPC_IGNORE_ALL ) + npc.SetNoTarget( true ) + thread PlayAnimTeleport( npc, "pt_cloak_react_app_far_point_timeshift_idle", node ) + + FlagWait( "exited_elevator_run" ) + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + waitthread PlayAnim( npc, "pt_cloak_react_app_far_point_timeshift", node ) + npc.AssaultPoint( Vector( 4804.74, -3668.88, 11744 ) ) + + WaitSignal( npc, "OnFinishedAssault" ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function MusicElevatorTop( entity player ) +{ + FlagWait( "exited_elevator_run" ) + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + StopMusic() + PlayMusicThatCantBeStopped( "music_timeshift_22_panicburst" ) + + FlagWait( "StartAndersonHologram1" ) + + StopMusic() + PlayMusic( "music_timeshift_23_andersonlog02" ) + + thread MusicHologram1Ambush() + + FlagWait( "AndersonHologram1Finished" ) + + + SetGlobalNetBool( "music14LoopPausable", false ) + PlayMusicThatCantBeStopped( "music_timeshift_14_pastloop" ) + //Following the ambush, when the player switches to the PRESENT - Play music_timeshift_16_explorepresent + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + thread PlayMusicInTimezoneUntilFlag( "music_timeshift_16_explorepresent", TIMEZONE_NIGHT, "StartAndersonHologram2" ) + + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function MusicHologram1Ambush() +{ + //Ambush could happen in either past of present + FlagWaitAny( "open_door_lab_reinforcements_pristine", "ProwlerAmbushTriggered" ) + + //StopMusic() + PlayMusicThatCantBeStopped( "music_timeshift_24_ambush" ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function ProwlerGagLabAlpha( entity player ) +{ + entity door = GetEntByScriptName( "door_prowler_lab_a" ) + entity spawner = GetEntByScriptName( "prowler_lab_a" ) + vector origin = door.GetOrigin() + vector angles = door.GetAngles() + + entity prowler = spawner.SpawnEntity() + DispatchSpawn( prowler ) + + prowler.EnableNPCFlag( NPC_NO_MOVING_PLATFORM_DEATH ) + prowler.Hide() + prowler.NotSolid() + MakeInvincible( prowler ) + + thread PlayAnimTeleport( prowler, "pr_timeshift_door_tease_01_idle", origin, angles ) + thread PlayAnimTeleport( door, "door_door_spawn_core_idle", origin, angles ) + + string animDoor + string animProwler + + FlagWait( "near_lab_alpha_prowler_skit" ) + + if ( !Flag( "IntelRoom1Finished" ) ) + { + prowler.Show() + prowler.Solid() + thread PlayAnimTeleport( door, "door_timeshift_prowler_tease_01", origin, angles ) + waitthread PlayAnimTeleport( prowler, "pr_timeshift_door_tease_01", origin, angles ) + prowler.Hide() + prowler.NotSolid() + thread PlayAnimTeleport( prowler, "pr_timeshift_door_tease_01_idle", origin, angles ) + thread PlayAnimTeleport( door, "door_door_spawn_core_idle", origin, angles ) + FlagWait( "AndersonHologram1Finished" ) + FlagWait( "near_lab_alpha_prowler_skit" ) + } + + FlagSet( "ProwlerAmbushTriggered" ) + prowler.Show() + prowler.Solid() + ClearInvincible( prowler ) + thread PlayAnimTeleport( prowler, "pr_timeshift_door_spawn_01", origin, angles ) + thread PlayAnimTeleport( door, "door_timeshift_prowler_spawn_01", origin, angles ) + DisableNavmeshSeperatorTargetedByEnt( door ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DialogueLabAlphaEvac( entity player ) +{ + if( Flag( "open_door_elevator_top_lab") ) + return + FlagEnd( "open_door_elevator_top_lab" ) + + + //entity panel_intel_room1 = GetEntByScriptName( "panel_intel_room1" ) + entity loudspeakerEnt1 = CreateLoudspeakerEnt( Vector( 5488,-3718, 11736 ) ) + entity loudspeakerEnt2 = CreateLoudspeakerEnt( Vector( 5488,-3718, 11736 ) ) + entity loudspeakerEnt3 = CreateLoudspeakerEnt( Vector( 5488,-3718, 11736 ) ) + + + OnThreadEnd( + function() : ( loudspeakerEnt1, loudspeakerEnt2, loudspeakerEnt3 ) + { + loudspeakerEnt1.Destroy() + loudspeakerEnt2.Destroy() + loudspeakerEnt3.Destroy() + } + ) + + +/* +██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗ +██╔══██╗██║██╔══██╗██║ ██╔═══██╗██╔════╝ ██║ ██║██╔════╝ +██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗ +██║ ██║██║██╔══██║██║ ██║ ██║██║ ██║██║ ██║██╔══╝ +██████╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ +*/ + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + //Scientist 1 What about the intruder?! + //thread PlayTimeShiftDialogue( player, loudspeakerEnt1, "diag_sp_humanStudy_TS201_03_01_imc_scientist1" ) + + wait 0.5 + + //He's here! + //thread PlayTimeShiftDialogue( player, loudspeakerEnt, "diag_sp_humanStudy_TS202_01_01_imc_scientist1" ) + + + //Grunt: Intruder spotted! Code 83! + thread PlayTimeShiftDialogue( player, loudspeakerEnt2, "diag_sp_humanStudy_TS201_14_01_imc_security1" ) + + wait 1.8 + + //Scientist 2 We have to leave! + thread PlayTimeShiftDialogue( player, loudspeakerEnt3, "diag_sp_humanStudy_TS201_04_01_imc_scientist2" ) + + wait 1 + + + //General Marder We're going forward with this. The test must be completed. + waitthread PlayTimeShiftDialogue( player, player, "diag_sp_humanStudy_TS201_01_01_imc_genMarder" ) + + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DialogueLabAlpha( entity player ) +{ + FlagWait( "open_door_elevator_top_lab" ) + + wait 1 + + + +/* +██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗ +██╔══██╗██║██╔══██╗██║ ██╔═══██╗██╔════╝ ██║ ██║██╔════╝ +██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗ +██║ ██║██║██╔══██║██║ ██║ ██║██║ ██║██║ ██║██╔══╝ +██████╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ +*/ + + FlagSet( "StartAndersonHologram1" ) + + //BT Pilot, a fragment of Anderson's damaged log may be relevant here. Activating log playback... + waitthread PlayBTDialogue( "diag_sp_humanStudy_TS201_11_01_imc_bt" ) + + + + FlagWaitAny( "AndersonHologram1Finished" ) + + wait 0.5 + + + FlagWait( "entered_hallways_to_human_research" ) + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + if ( !Flag( "player_inside_intel_room2" ) ) + { + entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + //General Marder If we don't test the Sculptor Core now we may never have another chance. Zulu Team, prep the Sculptor Core for delivery. + waitthread PlayTimeShiftDialogue( player, player, "diag_sp_humanStudy_TS201_02_01_imc_genMarder" ) + } + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function LabAlphaScientistThink( entity npc ) +{ + npc.EndSignal( "OnDeath" ) + thread DestroyNPCOnFlag( npc, "open_door_elevator_top_lab" ) + + WaitSignal( npc, "OnFinishedAssault" ) + + if ( IsValid( npc ) ) + npc.Destroy() + +} + + +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██████╗ ██╗ ██╗███████╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███╗ ███╗ +██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔══██╗██╔═══██╗██╔═══██╗████╗ ████║ +███████╗██████╔╝███████║█████╗ ██████╔╝█████╗ ██████╔╝██║ ██║██║ ██║██╔████╔██║ +╚════██║██╔═══╝ ██╔══██║██╔══╝ ██╔══██╗██╔══╝ ██╔══██╗██║ ██║██║ ██║██║╚██╔╝██║ +███████║██║ ██║ ██║███████╗██║ ██║███████╗ ██║ ██║╚██████╔╝╚██████╔╝██║ ╚═╝ ██║ +╚══════╝╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██████╗ ██╗ ██╗███████╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███╗ ███╗ +██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔══██╗██╔═══██╗██╔═══██╗████╗ ████║ +███████╗██████╔╝███████║█████╗ ██████╔╝█████╗ ██████╔╝██║ ██║██║ ██║██╔████╔██║ +╚════██║██╔═══╝ ██╔══██║██╔══╝ ██╔══██╗██╔══╝ ██╔══██╗██║ ██║██║ ██║██║╚██╔╝██║ +███████║██║ ██║ ██║███████╗██║ ██║███████╗ ██║ ██║╚██████╔╝╚██████╔╝██║ ╚═╝ ██║ +╚══════╝╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██████╗ ██╗ ██╗███████╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███╗ ███╗ +██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔══██╗██╔═══██╗██╔═══██╗████╗ ████║ +███████╗██████╔╝███████║█████╗ ██████╔╝█████╗ ██████╔╝██║ ██║██║ ██║██╔████╔██║ +╚════██║██╔═══╝ ██╔══██║██╔══╝ ██╔══██╗██╔══╝ ██╔══██╗██║ ██║██║ ██║██║╚██╔╝██║ +███████║██║ ██║ ██║███████╗██║ ██║███████╗ ██║ ██║╚██████╔╝╚██████╔╝██║ ╚═╝ ██║ +╚══════╝╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ +*/ +///////////////////////////////////////////////////////////////////////////////////////// + +void function SphereRoomStartPointSetup( entity player ) +{ + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointIntelRoom2" ) ) + vector objectivePos = GetEntByScriptName( "objective_intel_data_panel02" ).GetOrigin() + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function SphereRoomSkipped( entity player ) +{ + FlagSet( "RingsShouldBeSpinning" ) + FlagSet( "player_inside_intel_room2" ) + //thread HumanPodsThink( "biodoors_terminal01_pristine", "biodoors_terminal01_overgrown", player ) + //thread HumanPodsThinkNoHack( "biodoors_terminal01_pristine", "biodoors_terminal02_overgrown", player ) + //thread HumanPodsThinkNoHack( "biodoors_terminal02_pristine", "biodoors_terminal02_overgrown", player ) + + FlagClear( "ShowMobilityGhostTurretFlank" ) + thread QuickSkit( player, GetEntByScriptName( "node_reactor_flyers" ) ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_SphereRoomThread( entity player ) +{ + FlagWait( "entered_hallways_to_human_research" ) + + FlagSet( "ShowMobilityGhostTurretFlank" ) + + thread MusicSphereRoom( player ) + thread DialogueLabBravo( player ) + thread DialogueHumanAnteroom( player ) + thread DialogueGunshipDeploys( player ) + thread GunshipPadSequenceWait( player ) + thread GunshipSequence( "gunship_pad", player, "node_gunship_intel_room_2", "pad", "StartSphereRoomGunship" ) + thread AndersonHologramSequence( player, "node_hologram_lab2", "StartAndersonHologram2" ) + + CheckPoint() + + FlagWait( "dropped_into_fire_hallways" ) + vector objectivePos = GetEntByScriptName( "objective_intel_data_panel02" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + FlagWait( "player_inside_intel_room2" ) + + thread TurretNotargetHack( player ) + FlagWait( "AndersonHologram2Finished" ) + CheckPoint() + FlagSet( "open_door_diorama2_exit_pristine" ) + FlagSet( "open_door_diorama2_exit_overgrown" ) + + thread CheckpointSphereRoomOvergrown( player ) + thread CheckpointSphereRoomPristine( player ) + + objectivePos = GetEntByScriptName( "objective_human_research_breadcrumb_01a" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + thread ObjectiveRemindUntilFlag( "exited_intel_room2" ) + + FlagWait( "exited_intel_room2" ) + + objectivePos = GetEntByScriptName( "objective_human_research_breadcrumb_02" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + FlagWait( "entering_human_hallway_room" ) + + //thread HumanPodsThink( "biodoors_terminal01_pristine", "biodoors_terminal01_overgrown", player ) + //thread HumanPodsThinkNoHack( "biodoors_terminal01_pristine", "biodoors_terminal02_overgrown", player ) + //thread HumanPodsThinkNoHack( "biodoors_terminal02_pristine", "biodoors_terminal02_overgrown", player ) + + //------------------------------- + // Token stalkers + //------------------------------- + array< entity > propSpawners = GetEntArrayByScriptName( "stalker_spawnvents_human_hallway_room" ) + float delayMin = 1.3 + float delayMax = 3 + int maxToSpawn = 3 + string flagToAbort = "entering_human_main_room" + string flagToSetWhenAllAreSpawned = "" + bool requiresLookAt = false + thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, flagToSetWhenAllAreSpawned, delayMin, delayMax, "", requiresLookAt ) + + FlagWait( "past_human_hall_turrets" ) + thread QuickSkit( player, GetEntByScriptName( "node_reactor_flyers" ) ) + CheckPoint() + + FlagClear( "ShowMobilityGhostTurretFlank" ) + + + FlagWait( "entering_human_anteroom" ) + + //entity rings_pristine = GetEntByScriptName( "rings_pristine" ) + + //Spawn a sound dummy model to attach the sound to since doing it on an info_target + //or "AtPosition" is unreliable with Timeshift teleports (can get culled) + entity lookEnt = GetEntByScriptName( "lookent_rings" ) + EmitSoundAtPosition( TEAM_UNASSIGNED, lookEnt.GetOrigin(), "timeshift_scr_rings_spin_slow_lp" ) + + FlagSet( "RingsShouldBeSpinning" ) + + CheckPoint() + + + objectivePos = GetEntByScriptName( "objective_human_research_main_door" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + FlagWait( "entering_human_main_room" ) + +} + + + + + + +void function GruntsSphereRoomThink( entity npc ) +{ + //TODO: make these guys constanly get the player as an enemy + if ( !IsValid( npc ) ) + return + + npc.EndSignal( "OnDeath" ) + + while( true ) + { + AttackPlayer( npc ) + wait RandomFloatRange( 2, 5 ) + } + + + +} +void function CheckpointSphereRoomOvergrown( entity player ) +{ + FlagEnd( "entering_human_anteroom" ) + FlagWait( "sphere_room_stalkers_dead" ) + CheckPoint() +} + +void function CheckpointSphereRoomPristine( entity player ) +{ + FlagEnd( "entering_human_anteroom" ) + FlagWait( "sphere_room_pristine_enemies_dead" ) + CheckPoint() +} + +void function DialogueHumanAnteroom( entity player ) +{ + FlagWait( "entering_human_anteroom" ) + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + //General Marder - Radio Spin up the outer rings. Test sequence will commence once Sculptor Core is in place. + thread PlayTimeShiftDialogue( player, player, "diag_sp_miniSculptor_TS221_02_01_imc_genMarder" ) + + +} + +void function TurretNotargetHack( entity player ) +{ + if( Flag( "entering_human_main_room") ) + return + FlagEnd( "entering_human_main_room" ) + OnThreadEnd( + function() : ( player ) + { + if ( IsValid( player ) ) + player.SetNoTarget( false ) + } + ) + while( true ) + { + FlagWait( "player_no_target" ) + player.SetNoTarget( true ) + FlagWaitClear( "player_no_target" ) + player.SetNoTarget( false ) + } + +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function MusicSphereRoom( entity player ) +{ + FlagWait( "StartAndersonHologram2" ) + + //FlagWait( "AndersonHologram2Playing" ) + + // When the third Anderson log starts - Play music_timeshift_25_andersonlog03 + // At the same time - Play music_timeshift_21c_pastloop_stop + StopMusic() + PlayMusic( "music_timeshift_25_andersonlog03" ) + + if ( IsValid( player ) ) + { + StopSoundOnEntity( player, "music_timeshift_14_pastloop" ) + printl( "manually stopping music: music_timeshift_14_pastloop") + } + + PlayMusicThatCantBeStopped( "music_timeshift_21c_pastloop_stop" ) + + //When the door drops down revealing the dudes + FlagWaitAny( "open_door_diorama2_exit_pristine", "open_door_diorama2_exit_overgrown" ) + + StopMusic() + PlayMusicThatCantBeStopped( "music_timeshift_24_ambush" ) + SetGlobalNetBool( "music14LoopPausable", false ) + PlayMusicThatCantBeStopped( "music_timeshift_14_pastloop" ) + + // Following the ambush, when the player switches to the PRESENT - Play music_timeshift_16_explorepresent + FlagWait( "AndersonHologram2Finished" ) + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + + thread PlayMusicInTimezoneUntilFlag( "music_timeshift_16_explorepresent", TIMEZONE_NIGHT, "entering_human_anteroom" ) + + + FlagWait( "entering_human_anteroom" ) + + // When you enter human testing - Play music_timeshift_26_humanresearch + // At the same time - Play music_timeshift_21c_pastloop_stop + + wait 0.1 + + StopMusic() + if ( IsValid( player ) ) + { + StopSoundOnEntity( player, "music_timeshift_14_pastloop" ) + printl( "manually stopping music: music_timeshift_14_pastloop") + } + + PlayMusicThatCantBeStopped( "music_timeshift_21c_pastloop_stop" ) + + PlayMusic( "music_timeshift_26_humanresearch" ) + + + +} + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DialogueLabBravo( entity player ) +{ + FlagWaitAny( "LabBravoEnemiesDead", "player_near_intel_room_2_landing_pad" ) + + FlagWait( "player_inside_intel_room2" ) + + FlagWait( "player_inside_intel_room2_halfway" ) + + FlagClear( "open_doors_sphere_room_entrance_all" ) + + +/* +██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗ +██╔══██╗██║██╔══██╗██║ ██╔═══██╗██╔════╝ ██║ ██║██╔════╝ +██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗ +██║ ██║██║██╔══██║██║ ██║ ██║██║ ██║██║ ██║██╔══╝ +██████╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ +*/ + + FlagSet( "StartAndersonHologram2" ) + FlagSetDelayed( "AndersomHologram2AboutToStart", 3) + + + FlagWait( "AndersonHologram2Finished" ) + + wait 0.5 + + + +} + + +void function DialogueGunshipDeploys( entity player ) +{ + + /* + FlagWaitAny( "LabBravoEnemiesDead", "player_inside_intel_room2_halfway" ) + + + //FlagWait( "LookingAtPad" ) + + entity soundEnt = CreateLoudspeakerEnt( GetEntByScriptName( "lookent_pad" ).GetOrigin() ) + + FlagWait( "AndersonHologram2Finished" ) + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + //waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + if ( !Flag( "AndersomHologram2AboutToStart" ) ) + { + //Scientist 5 (Radio) Sculptor Core prepped for delivery and en route to the test chamber. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_miniSculptor_TS221_01_01_imc_scientist5" ) + } + + */ + + FlagWait( "StartSphereRoomGunship") + + wait 1 + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + entity soundEnt = CreateLoudspeakerEnt( GetEntByScriptName( "lookent_pad" ).GetOrigin() ) + + //Scientist 5 (Radio) Sculptor Core prepped for delivery and en route to the test chamber. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_miniSculptor_TS221_01_01_imc_scientist5" ) + +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function GunshipPadSequenceWait( entity player ) +{ + FlagEnd( "exited_intel_room2" ) + + FlagWait( "AndersonHologram2Finished" ) + + entity lookEnt = GetEntByScriptName( "lookent_pad" ) + //doTrace degrees minDist timeOut trigger, failsafeFlag ) + waitthread WaitTillLookingAt( player, lookEnt, false, 30, 0, 0, null, "player_near_intel_room_2_landing_pad" ) + + FlagSet( "StartSphereRoomGunship" ) +} + + +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +██╗ ██╗██╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗ +██║ ██║██║ ██║████╗ ████║██╔══██╗████╗ ██║ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██║ +███████║██║ ██║██╔████╔██║███████║██╔██╗ ██║ ██████╔╝█████╗ ███████╗█████╗ ███████║██████╔╝██║ ███████║ +██╔══██║██║ ██║██║╚██╔╝██║██╔══██║██║╚██╗██║ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██║██╔══██╗██║ ██╔══██║ +██║ ██║╚██████╔╝██║ ╚═╝ ██║██║ ██║██║ ╚████║ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║ +╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +██╗ ██╗██╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗ +██║ ██║██║ ██║████╗ ████║██╔══██╗████╗ ██║ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██║ +███████║██║ ██║██╔████╔██║███████║██╔██╗ ██║ ██████╔╝█████╗ ███████╗█████╗ ███████║██████╔╝██║ ███████║ +██╔══██║██║ ██║██║╚██╔╝██║██╔══██║██║╚██╗██║ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██║██╔══██╗██║ ██╔══██║ +██║ ██║╚██████╔╝██║ ╚═╝ ██║██║ ██║██║ ╚████║ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║ +╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// +/* +██╗ ██╗██╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗ +██║ ██║██║ ██║████╗ ████║██╔══██╗████╗ ██║ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██║ +███████║██║ ██║██╔████╔██║███████║██╔██╗ ██║ ██████╔╝█████╗ ███████╗█████╗ ███████║██████╔╝██║ ███████║ +██╔══██║██║ ██║██║╚██╔╝██║██╔══██║██║╚██╗██║ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██║██╔══██╗██║ ██╔══██║ +██║ ██║╚██████╔╝██║ ╚═╝ ██║██║ ██║██║ ╚████║ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║ +╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// +/* +██╗ ██╗██╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗ +██║ ██║██║ ██║████╗ ████║██╔══██╗████╗ ██║ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██║ +███████║██║ ██║██╔████╔██║███████║██╔██╗ ██║ ██████╔╝█████╗ ███████╗█████╗ ███████║██████╔╝██║ ███████║ +██╔══██║██║ ██║██║╚██╔╝██║██╔══██║██║╚██╗██║ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██║██╔══██╗██║ ██╔══██║ +██║ ██║╚██████╔╝██║ ╚═╝ ██║██║ ██║██║ ╚████║ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║ +╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +void function HumanResearchStartPointSetup( entity player ) +{ + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointHumanResearch" ) ) + vector objectivePos = GetEntByScriptName( "objective_human_research_vista" ).GetOrigin() + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function HumanResearchSkipped( entity player ) +{ + FlagClear( "ShowMobilityGhostHumanLillypad01" ) + FlagClear( "ShowMobilityGhostHumanLillypad02" ) + thread ElectricalScreenEffects( player, "inside_human_control_room" ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_HumanResearchThread( entity player ) +{ + + FlagWait( "entering_human_main_room" ) + FlagSet( "ShowMobilityGhostHumanLillypad01" ) + FlagSet( "ShowMobilityGhostHumanLillypad02" ) + + //thread DebugX( player ) + + thread FlyersHumanRoom() + thread MusicHumanRoom( player ) + thread DialogueHumanRoomStart( player ) + vector objectivePos = GetEntByScriptName( "objective_human_research_tower1" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + //thread HumanPanelHacks( player ) + CheckPoint() + + delaythread ( 60 ) TimeshiftHint( player, TIMEZONE_DAY, "reached_concourse_tower1", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_first_liilypad_puzzle_pristine" ) ) + + + FlagWait( "reached_concourse_tower1" ) + + objectivePos = GetEntByScriptName( "objective_human_research_fight" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + thread ElectricalScreenEffects( player, "inside_human_control_room" ) + + FlagClear( "ShowMobilityGhostHumanLillypad01" ) + + + CheckPoint() + + FlagWait( "inside_human_control_room" ) + + objectivePos = GetEntByScriptName( "objective_human_research_tower2" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + FlagSet( "DisplayTheDamageHint" ) + thread HumanRoomCheckpointSoldiers() + thread HumanRoomCheckpointProwlers() + + CheckPoint() + + //------------------------------- + // Token prowlers + //------------------------------- + array< entity > propSpawners = GetEntArrayByScriptName( "prowler_spawnvents_human_control_room" ) + float delayMin = 2.5 + float delayMax = 5 + int maxToSpawn = 1 + string flagToAbort = "reached_concourse_tower2" + string flagToSetWhenAllAreSpawned = "AllProwlersSpawnedInHumanControlRoom" + bool requiresLookAt = true + thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, flagToSetWhenAllAreSpawned, delayMin, delayMax, "", requiresLookAt ) + + FlagWait( "reached_concourse_tower2" ) + + FlagClear( "DisplayTheDamageHint" ) + FlagClear( "ShowMobilityGhostHumanLillypad02" ) +} + +void function FlyersHumanRoom() +{ + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + FlagSet( "ForceFlyerTakeoff") +} +///////////////////////////////////////////////////////////////////////////////////////// +void function HumanRoomCheckpointSoldiers() +{ + FlagWait( "inside_human_control_room" ) + if ( Flag( "reached_concourse_tower2") ) + return + FlagEnd( "reached_concourse_tower2" ) + FlagWait( "human_room_soldiers_dead" ) + CheckPoint() +} +///////////////////////////////////////////////////////////////////////////////////////// +void function HumanRoomCheckpointProwlers() +{ + FlagWait( "inside_human_control_room" ) + if ( Flag( "reached_concourse_tower2") ) + return + FlagEnd( "reached_concourse_tower2" ) + + FlagWait( "AllProwlersSpawnedInHumanControlRoom" ) + FlagWaitClear( "prowlers_alive_in_human_control_room") + + CheckPoint() +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function MusicHumanRoom( entity player ) +{ + + +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void function GunshipRingSequenceWait( entity player ) +{ + FlagWait( "crossed_wallrun_chain" ) + + wait 0.25 + + entity lookEnt = GetEntByScriptName( "lookent_rings" ) + //doTrace degrees minDist timeOut trigger, failsafeFlag ) + waitthread WaitTillLookingAt( player, lookEnt, false, 30, 0, 0, null, "player_crossing_human_bridge" ) + + FlagSet( "player_looking_at_reactor_window" ) + +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function DialogueHumanRoomStart( entity player ) +{ + +/* +██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗ +██╔══██╗██║██╔══██╗██║ ██╔═══██╗██╔════╝ ██║ ██║██╔════╝ +██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗ +██║ ██║██║██╔══██║██║ ██║ ██║██║ ██║██║ ██║██╔══╝ +██████╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ +*/ + + + FlagWait( "entering_human_main_room" ) + + //wait 1 + //Player Option A Were they doing experiments on these people? + //BT Option A SRS files indicated large-scale cryogenic stasis for testing on humans for unknown reasons. They are likely not volunteers. + + //Player Option B Who are these people? + //BT Option B Scanning... The genetic makeup matches those captured from the distant planet Colony led by former Militia leader MacAllan. + //waitthread PlayerConversation( "TsHumanExperiments", player ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +/* +void function HumanPanelHacks( entity player ) +{ + //-------------------------------------------- + // Override all security terminals (optional) + //---------------------------------------------- + array< entity > controlPanels + controlPanels.append( GetEntByScriptName( "security_panel_concourse_01" ) ) + //controlPanels.append( GetEntByScriptName( "security_panel_concourse_02" ) ) + foreach( panel in controlPanels ) + thread SecurityPanelConcourseThink( panel, player ) + + +} +*/ +///////////////////////////////////////////////////////////////////////////////////////// +void function SecurityPanelConcourseThink( entity panel, entity player ) +{ + panel.WaitSignal( "PanelReprogram_Success" ) + + string flagToSetWhenHacked + + string scriptName = panel.GetScriptName() + string laserMeshInstanceName + if ( scriptName == "security_panel_concourse_01" ) + { + laserMeshInstanceName = "laser_mesh_concourse_terminal_01" + flagToSetWhenHacked = "ConcoursePanelHacked01" + } + else if ( scriptName == "security_panel_concourse_02" ) + { + laserMeshInstanceName = "laser_mesh_concourse_terminal_02" + flagToSetWhenHacked = "ConcoursePanelHacked02" + } + else + Assert( 0, "Can't find lasermesh trigger associated with " + scriptName ) + + FlagSet( flagToSetWhenHacked ) + + if ( !Flag( "LabRatAcheivementUnlocked" ) ) + { + FlagSet( "LabRatAcheivementUnlocked" ) + Dev_PrintMessage( player, "#TIMESHIFT_ACHEIVEMENT_HEADING", "#TIMESHIFT_ACHEIVEMENT_TEXT_LAB_RATS", 5 ) + } + + wait 1.5 + + CheckPoint() + + LaserMeshDestroyByInstanceName( laserMeshInstanceName ) + Remote_CallFunction_NonReplay( player, "ServerCallback_LabRatLasers" ) + + +} + +///////////////////////////////////////////////////////////////////////////////////////// +/* +void function HumanPodsThink( string instanceNamePristine, string instanceNameOvergrown, entity player ) +{ + array< entity > doorsPristine = GetEntArrayByScriptNameInInstance( "bio_pod_door", instanceNamePristine ) + array< entity > doorsOvergrown = GetEntArrayByScriptNameInInstance( "bio_pod_door", instanceNameOvergrown ) + Assert( doorsPristine.len() == 4 ) + Assert( doorsOvergrown.len() == 4 ) + + string flagToReleasePrisoners = "security_concourse_01_hacked" + //Militia Prisoner 1 What? What where am I? + string prisonerDialogue = "diag_sp_humanStudy_TS202_03_01_mcor_prisoner1" + if ( instanceNamePristine == "biodoors_terminal02_pristine" ) + { + //Militia Prisoner 2 What's going on? This isn't Colony.... + string prisonerDialogue = "diag_sp_humanStudy_TS202_04_01_mcor_prisoner2" + flagToReleasePrisoners = "security_concourse_02_hacked" + + } + + entity dialogueEnt + + foreach( door in doorsPristine ) + { + if ( !IsValid( dialogueEnt ) ) + dialogueEnt = CreateLoudspeakerEnt( door.GetOrigin() ) + thread HumanPodThinkPristine( door ) + } + foreach( door in doorsOvergrown ) + thread HumanPodThinkOvergrown( door ) + + FlagWait( flagToReleasePrisoners ) + + foreach( door in doorsOvergrown ) + door.Signal( "ReleaseLabRat") + foreach( door in doorsPristine ) + door.Signal( "ReleaseLabRat") + + wait 5 + + thread PlayTimeShiftDialogue( player, dialogueEnt, prisonerDialogue ) +} + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* +void function HumanPodsThinkNoHack( string instanceNamePristine, string instanceNameOvergrown, entity player ) +{ + array< entity > spawners = GetEntArrayByScriptNameInInstance( "bio_pod_body", instanceNamePristine ) + foreach( spawner in spawners ) + thread HumanPodThinkPristine( null, spawner ) +} +&*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* +void function HumanPodThinkPristine( entity door = null, entity bodySpawner = null ) +{ + entity spawner + if ( !bodySpawner ) + spawner = door.GetLinkEnt() + else + spawner = bodySpawner + string animIdle + string animWakeUp + string animIdleEnd + entity labRat = spawner.SpawnEntity() + DispatchSpawn( labRat ) + Assert( IsValid( labRat) ) + entity node = CreateScriptRef( labRat.GetOrigin(), labRat.GetAngles() ) + vector originOffset = PositionOffsetFromEnt( node, -15, 0, 0 ) + node.SetOrigin( originOffset ) + + MakeInvincible( labRat ) + MakeCivilian( labRat ) + labRat.SetNoTarget( true ) + SetTeam( labRat, TEAM_MILITIA ) + labRat.SetModel( LABRAT_MODEL ) + Assert( labRat.HasKey( "script_noteworthy") ) + animIdle = labRat.kv.script_noteworthy + "_start_idle" + animWakeUp = labRat.kv.script_noteworthy + "_start" + animIdleEnd = labRat.kv.script_noteworthy + "_end_idle" + + thread PlayAnimTeleport( labRat, animIdle, node ) + + if ( !door ) + return + + door.WaitSignal( "ReleaseLabRat" ) + + waitthread OpenLabRatDoor( door, labRat ) + + labRat.EndSignal( "OnDeath" ) + wait 0.25 + + ClearInvincible( labRat ) + labRat.e.forceRagdollDeath = true + + waitthread PlayAnim( labRat, animWakeUp, node ) + thread PlayAnim( labRat, animIdleEnd, node ) +} +*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* +void function HumanPodThinkOvergrown( entity door ) +{ + entity body = door.GetLinkEnt() + + door.WaitSignal( "ReleaseLabRat" ) + + body.Destroy() + OpenLabRatDoor( door, body ) +} +*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* +void function OpenLabRatDoor( entity door, entity body ) +{ + entity soundDummy = CreateInfoTarget( body.GetOrigin() + Vector( 0, 0, 72 ), Vector( 0, 0, 0 ) ) + vector soundOrigin = soundDummy.GetOrigin() + entity lookEnt = CreateInfoTarget( body.GetOrigin() + Vector( 0, 0, 72 ), Vector( 0, 0, 0 ) ) + vector originOffset = PositionOffsetFromEnt( lookEnt, 15, 0, 0 ) + lookEnt.SetOrigin( originOffset ) + + vector fxOrigin = body.GetOrigin() + vector fxAngles = body.GetAngles() + + bool snapToOpen = false + if ( GetEntityTimelinePosition( door ) == TIMEZONE_NIGHT ) + snapToOpen = true + + vector startPos = door.GetOrigin() + vector endPos = startPos + Vector( 0, 0, -105 ) + + if ( snapToOpen == true ) + { + door.SetOrigin( endPos ) + return + } + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + array <entity> players = GetPlayerArray() + if ( players.len() <= 0 ) + return + entity player = players[ 0 ] + player.EndSignal( "OnDeath" ) + + //WaitTillLookingAt( entity player, entity ent, bool doTrace, float degrees, float minDist = 0, float timeOut = 0, entity trigger = null, string failsafeFlag = "" ) + waitthread WaitTillLookingAt( player, lookEnt, true, 45, 0, 3 ) + + wait( RandomFloatRange( 0, 0.75 ) ) + entity mover = CreateScriptMover( door.GetOrigin(), door.GetAngles() ) + door.SetParent( mover ) + float moveTime = 5 + + mover.NonPhysicsMoveTo( endPos, moveTime, moveTime*0.4, moveTime*0.4 ) + + + EmitSoundAtPosition( TEAM_UNASSIGNED, soundOrigin, "Timeshift_Scr_LabRatPodDoor_Open" ) + //EmitSoundOnEntity( soundDummy, "door_open_loop" ) + PlayFX( FX_HUMAN_DOOR_OPEN, fxOrigin, fxAngles ) + + wait moveTime + + //StopSoundOnEntity( soundDummy, "door_open_loop" ) + //EmitSoundAtPosition( TEAM_UNASSIGNED, soundOrigin, "door_stop" ) + +} +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* + ██████╗ █████╗ ███╗ ███╗██████╗ ██╗ ██╗███████╗ ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗ +██╔════╝██╔══██╗████╗ ████║██╔══██╗██║ ██║██╔════╝ ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔══██╗████╗ ██║ +██║ ███████║██╔████╔██║██████╔╝██║ ██║███████╗ ██████╔╝█████╗ ██║ ██║ ██║██████╔╝██╔██╗ ██║ +██║ ██╔══██║██║╚██╔╝██║██╔═══╝ ██║ ██║╚════██║ ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗██║╚██╗██║ +╚██████╗██║ ██║██║ ╚═╝ ██║██║ ╚██████╔╝███████║ ██║ ██║███████╗ ██║ ╚██████╔╝██║ ██║██║ ╚████║ + ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* + ██████╗ █████╗ ███╗ ███╗██████╗ ██╗ ██╗███████╗ ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗ +██╔════╝██╔══██╗████╗ ████║██╔══██╗██║ ██║██╔════╝ ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔══██╗████╗ ██║ +██║ ███████║██╔████╔██║██████╔╝██║ ██║███████╗ ██████╔╝█████╗ ██║ ██║ ██║██████╔╝██╔██╗ ██║ +██║ ██╔══██║██║╚██╔╝██║██╔═══╝ ██║ ██║╚════██║ ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗██║╚██╗██║ +╚██████╗██║ ██║██║ ╚═╝ ██║██║ ╚██████╔╝███████║ ██║ ██║███████╗ ██║ ╚██████╔╝██║ ██║██║ ╚████║ + ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +/* + ██████╗ █████╗ ███╗ ███╗██████╗ ██╗ ██╗███████╗ ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗ +██╔════╝██╔══██╗████╗ ████║██╔══██╗██║ ██║██╔════╝ ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔══██╗████╗ ██║ +██║ ███████║██╔████╔██║██████╔╝██║ ██║███████╗ ██████╔╝█████╗ ██║ ██║ ██║██████╔╝██╔██╗ ██║ +██║ ██╔══██║██║╚██╔╝██║██╔═══╝ ██║ ██║╚════██║ ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗██║╚██╗██║ +╚██████╗██║ ██║██║ ╚═╝ ██║██║ ╚██████╔╝███████║ ██║ ██║███████╗ ██║ ╚██████╔╝██║ ██║██║ ╚████║ + ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ + */ +///////////////////////////////////////////////////////////////////////////////////////// +/* + ██████╗ █████╗ ███╗ ███╗██████╗ ██╗ ██╗███████╗ ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗ +██╔════╝██╔══██╗████╗ ████║██╔══██╗██║ ██║██╔════╝ ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔══██╗████╗ ██║ +██║ ███████║██╔████╔██║██████╔╝██║ ██║███████╗ ██████╔╝█████╗ ██║ ██║ ██║██████╔╝██╔██╗ ██║ +██║ ██╔══██║██║╚██╔╝██║██╔═══╝ ██║ ██║╚════██║ ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗██║╚██╗██║ +╚██████╗██║ ██║██║ ╚═╝ ██║██║ ╚██████╔╝███████║ ██║ ██║███████╗ ██║ ╚██████╔╝██║ ██║██║ ╚████║ + ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ + */ +///////////////////////////////////////////////////////////////////////////////////////// + + +void function CampusReturnStartPointSetup( entity player ) +{ + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointCampusReturn" ) ) + vector objectivePos = GetEntByScriptName( "objective_human_research_vista" ).GetOrigin() + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function CampusReturnSkipped( entity player ) +{ + FlagSet( "CampusReturnConversationFinished" ) + FlagClear( "ShowMobilityGhostHumanWallrunChain" ) + FlagClear( "ShowMobilityGhostPowertech" ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_CampusReturnThread( entity player ) +{ + FlagWait( "reached_concourse_tower2" ) + + vector objectivePos = GetEntByScriptName( "objective_human_research_vista" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + thread ObjectiveRemindUntilFlag( "crossed_wallrun_chain" ) + + FlagSet( "ShowMobilityGhostHumanWallrunChain" ) + FlagSet( "ShowMobilityGhostPowertech" ) + + CheckPoint() + thread ArtifactLaunchThink( player ) + thread BridgeThink() + thread MusicCampusReturn( player ) + thread GunshipRingSequenceWait( player ) + thread GunshipSequence( "gunship_rings", player, "node_gunship_rings", "rings", "player_looking_at_reactor_window" ) + + FlagWait( "crossed_wallrun_chain" ) + + wait 0.25 + + //Checkpoint done with a trigger with safe spot here + + objectivePos = GetEntByScriptName( "objective_return_breadcrumb00" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + thread DialogueHumanRoomEnd( player ) + + FlagSet( "finishedHumanVistaSequence" ) + + + thread HumanBridgeEnemies( player ) + thread HumanBridgeOvergrownThink( player ) + + + FlagWait( "entered_powertech_return" ) + + objectivePos = GetEntByScriptName( "objective_return_breadcrumb01" ).GetOrigin() + TimeshiftSetObjective( player, "#TIMESHIFT_OBJECTIVE_RETURN", objectivePos ) + + CheckPoint() + + FlagWait( "approaching_fan_drop" ) +} + +void function BridgeThink() +{ + FlagWait( "bridge_control_panel_pressed" ) + FlagClear( "retract_bridge_human_01" ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function HumanBridgeOvergrownThink( entity player ) +{ + //Show it when enemies extend it in other time zone + FlagWaitClear( "retract_bridge_human_01" ) + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + ShowStuff( "human_bridge_overgrown" ) + + + //Hide it again if player manually retracts it + //FlagWait( "retract_bridge_human_01" ) + + //waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + //HideStuff( "human_bridge_overgrown" ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function MusicCampusReturn( entity player ) +{ + FlagWait( "crossing_wallrun_chain" ) + + StopMusic() + PlayMusic( "music_timeshift_27_towardthemachine" ) + + FlagWait( "spawnHumanBridgeEnemies" ) + + //Only play drone spawn music if player is actually in the trigger spawning the drones + + if ( !Flag( "player_touching_drone_spawn_area_bridge_pristine") ) + return + + StopMusic() + PlayMusic( "music_timeshift_28_bridge" ) + +} +///////////////////////////////////////////////////////////////////////////////////////// + +void function ArtifactLaunchThink( entity player ) +{ + entity arkShell = GetEntByScriptName( "core_model_pristine" ) + entity arkSphere = GetEntByScriptName( "core_glow_model_pristine" ) + vector startPos = arkShell.GetOrigin() + vector startAng = arkShell.GetAngles() + vector endPos = GetEntByScriptName( "artifact_ring_startpoint" ).GetOrigin() + + entity fxLaunchGlow = PlayLoopFXOnEntity( FX_ARK_LAUNCH_GLOW, arkShell ) + entity mover = CreateScriptMover( startPos, startAng ) + arkSphere.SetParent( arkShell ) + arkShell.SetParent( mover ) + float moveTime = 2 + + FlagWait( "crossed_wallrun_chain" ) + + wait 0.25 + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + wait 0.1 + + mover.NonPhysicsMoveTo( endPos, moveTime, moveTime*0.4, moveTime*0.4 ) + EmitSoundOnEntity( player, "timeshift_scr_core_rise_start" ) + + wait moveTime + + entity fxLaunchInPlace = PlayFXOnEntity( FX_ARK_LAUNCH_IN_PLACE, arkShell ) + + //StopSoundOnEntity( player, "timeshift_scr_core_rise_start" ) + EmitSoundAtPosition( TEAM_UNASSIGNED, endPos, "timeshift_scr_core_rise_end" ) + EmitSoundAtPosition( TEAM_UNASSIGNED, endPos, "timeshift_scr_core_pulse" ) + //CreateShake( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 ) + thread CreateAirShake( startPos, 32, 200, 1.5, 10000 ) + thread CreateShakeWhileFlagSet( 0.5, 0.5, 1, "player_near_rings", "DoneWithFanDropSequence" ) + + FlagSet( "RingVistaSequenceComplete" ) + +} + + + +///////////////////////////////////////////////////////////////////////////////////////// +void function DialogueHumanRoomEnd( entity player ) +{ + + +/* +██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗ +██╔══██╗██║██╔══██╗██║ ██╔═══██╗██╔════╝ ██║ ██║██╔════╝ +██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗ +██║ ██║██║██╔══██║██║ ██║ ██║██║ ██║██║ ██║██╔══╝ +██████╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ +*/ + + FlagWait( "crossed_wallrun_chain" ) + + wait 0.25 + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + //Scientist 5 PA Ark successfully delivered to fold weapon rings. Commencing test run. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_humanStudy_TS203_01_01_imc_sci" ) + + //FlagWaitAny( "human_bridge_soldiers_dead", "entered_powertech_return" ) + + FlagWaitAny( "RingVistaSequenceComplete", "player_crossing_human_bridge" ) + + + //BT: Pilot, the rings at my location contain a large amount of residual energy. This was the Ark's final destination. + waitthread PlayBTDialogue( "diag_sp_targeting_TS231_01_01_mcor_bt" ) + + //BT: Anderson's plan indicated a recon mission within close proximity to the center of the active rings. + waitthread PlayBTDialogue( "diag_sp_targeting_TS231_07_01_mcor_bt" ) + + if ( !Flag( "approaching_fan_drop" ) ) + { + // Player (Option A) You want me to do what? + // Player (Option B) Are you sure I'll be safe over there? (diag_sp_pristine_TS242_03_01_mcor_player) + + // BT (Option A) If we can obtain the Ark's energy signature, the Militia fleet will be able to track its current location in the present day. + // BT (Option B) No. But a true Militia Pilot takes risks for the welfare of others. As did Major Anderson and Captain Lastimosa before you. (diag_sp_pristine_TS242_04_01_mcor_bt) + + waitthread PlayerConversationStopOnFlag( "TsSeeingCoreFlownToRings", player, "entered_powertech_return" ) + } + + FlagSet( "CampusReturnConversationFinished" ) + + FlagSet( "spawnHumanBridgeEnemies" ) + + +} + + + +///////////////////////////////////////////////////////////////////////////////////////// +void function HumanBridgeEnemies( entity player ) +{ + FlagWait( "finishedHumanVistaSequence" ) + + entity lookEnt = GetEntByScriptName( "look_ent_human_bridge" ) + entity trigger = GetEntByScriptName( "trig_player_at_human_vista_pristine" ) + //doTrace degrees minDist timeOut trigger, failsafeFlag ) + waitthread WaitTillLookingAt( player, lookEnt, true, 30, 0, 0, trigger, "player_crossing_human_bridge" ) + + + //entity triggerBridgeVolume = GetEntByScriptName( "trigger_bridge_human_volume" ) + //entity bridge = GetEntByScriptName( "human_bridge" ) + //FlagSet( "spawnHumanBridgeEnemies" ) + + /* + //FlagClearDelayed( "retract_bridge_human_01", 3 ) + + FlagWait( "retract_bridge_control_panel_pressed" ) + FlagSet( "retract_bridge_human_01" ) + bridge.NotSolid() + + SetGlobalForcedDialogueOnly( true ) + bool screamPlayed = false + array<entity> ai = GetNPCArrayByClass( "npc_soldier" ) + entity tempNode + float delayTime = 0 + string floorDropAnim = "pt_react_bridgedrop_A" + foreach( guy in ai ) + { + if ( !triggerBridgeVolume.IsTouching( guy ) ) + continue + + if ( IsAlive( guy ) ) + { + //guy.NotSolid() + if ( screamPlayed == false ) + { + EmitSoundOnEntity( guy, "Grunt_DroppedByFlyer" ) + screamPlayed = true + } + if ( CoinFlip() ) + floorDropAnim = "pt_react_bridgedrop_B" + tempNode = CreateScriptMover( guy.GetOrigin(), guy.GetAngles() ) + guy.SetParent( tempNode ) + delayTime = RandomFloatRange( 0, 0.3 ) + delaythread ( delayTime ) PlayAnim( guy, floorDropAnim, tempNode ) + //guy.TakeDamage( guy.GetHealth() / 2, null, null, { damageSourceId=damagedef_suicide } ) + } + } + if ( screamPlayed == true ) + { + delaythread ( 2 ) Dev_PrintMessage( player, "#TIMESHIFT_ACHEIVEMENT_HEADING", "#TIMESHIFT_ACHEIVEMENT_TEXT_BRIDGE_FALL", 5 ) + } + + wait 1 + + SetGlobalForcedDialogueOnly( false ) + + DestroyIfValid( "human_bridge" ) + */ + +} + +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ +██╔════╝██╔══██╗████╗ ██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗ +█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔╝██║ ██║██████╔╝ +██╔══╝ ██╔══██║██║╚██╗██║ ██║ ██║██╔══██╗██║ ██║██╔═══╝ +██║ ██║ ██║██║ ╚████║ ██████╔╝██║ ██║╚██████╔╝██║ +╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ +██╔════╝██╔══██╗████╗ ██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗ +█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔╝██║ ██║██████╔╝ +██╔══╝ ██╔══██║██║╚██╗██║ ██║ ██║██╔══██╗██║ ██║██╔═══╝ +██║ ██║ ██║██║ ╚████║ ██████╔╝██║ ██║╚██████╔╝██║ +╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ +██╔════╝██╔══██╗████╗ ██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗ +█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔╝██║ ██║██████╔╝ +██╔══╝ ██╔══██║██║╚██╗██║ ██║ ██║██╔══██╗██║ ██║██╔═══╝ +██║ ██║ ██║██║ ╚████║ ██████╔╝██║ ██║╚██████╔╝██║ +╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ +██╔════╝██╔══██╗████╗ ██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗ +█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔╝██║ ██║██████╔╝ +██╔══╝ ██╔══██║██║╚██╗██║ ██║ ██║██╔══██╗██║ ██║██╔═══╝ +██║ ██║ ██║██║ ╚████║ ██████╔╝██║ ██║╚██████╔╝██║ +╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + +void function FanDropStartPointSetup( entity player ) +{ + //entity temp = CreateInfoTarget( Vector( 3536, -4641, -127), Vector( 35.5, -90, 0 ) ) + //TeleportPlayerToEnt( player, temp ) + + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointFanDrop" ) ) + vector objectivePos = GetEntByScriptName( "objective_concourse_panel" ).GetOrigin() + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_RETURN", objectivePos ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function FanDropSkipped( entity player ) +{ + CleanupEnts( "rings_pristine" ) + FlagSet( "approaching_fan_drop" ) + FlagSet( "DoneWithFanDropSequence" ) + thread QuickSkit( player, GetEntByScriptName( "node_labC_deathpose_overgrown" ), "approaching_fan_drop" ) + thread QuickSkit( player, GetEntByScriptName( "node_labC_deathpose_pristine" ), "approaching_fan_drop" ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_FanDropThread( entity player ) +{ + FlagWait( "approaching_fan_drop" ) + + thread MusicFanDrop( player ) + thread FanDropTeleport( player ) + thread QuickSkit( player, GetEntByScriptName( "node_labC_deathpose_overgrown" ), "approaching_fan_drop" ) + thread QuickSkit( player, GetEntByScriptName( "node_labC_deathpose_pristine" ), "approaching_fan_drop" ) + thread FanDropLanding( player ) + + vector objectivePos = GetEntByScriptName( "objective_return_breadcrumb01" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + + + FlagWait( "doing_fan_drop_pristine" ) + + + while( true ) + { + FlagWait( "exited_fan_drop" ) + + WaitEndFrame() //even if player hit the "exited_fan_drop", he might already be starting a quickDeath + + if ( player.p.doingQuickDeath ) + { + printl( "player doing quickDeath...waiting 2 secs") + wait 2 + printl( "Waiting to exit fan drop..." ) + continue + } + else + { + if ( IsAlive( player ) ) + thread FanDropQuickdeathFailsafe( player ) + break + } + + } + + + FlagSet( "DoneWithFanDropSequence" ) + +} + +void function FanDropQuickdeathFailsafe( entity player ) +{ + player.EndSignal( "OnDeath" ) + //If player gets into a state where the game thinks he has exited the fan sequence, but is in fact doing a quick death, restart from last checkpoint + wait 0.5 + if ( player.p.doingQuickDeath ) + player.TakeDamage( 9999, null, null, { damageSourceId=damagedef_suicide } ) +} + +void function FanDropLanding( entity player ) +{ + + entity node = GetEntByScriptName( "checkpointFanDropEnd" ) + entity nodePristine = CreateScriptRef( node.GetOrigin() + Vector( 0, 0, TIME_ZOFFSET ), node.GetAngles() ) + + FlagWait( "DoneWithFanDropSequence" ) + + + + printl( "Done with fan drop...playing land anim" ) + + player.FreezeControlsOnServer() + level.allowTimeTravel = false + ShowStuff( "blocker_fandrop_pristine" ) + ShowStuff( "blocker_fandrop_overgrown" ) + + player.DisableWeapon() + player.ForceStand() + + + waitthread AdjustPlayerTimelineHack( player, true ) + + WaitFrame() + if ( GetEntityTimelinePosition( player ) == TIMEZONE_NIGHT ) + waitthread PlayerDropLand( player, node, true ) + else + waitthread PlayerDropLand( player, nodePristine, true ) + + waitthread AdjustPlayerTimelineHack( player ) + + wait 0.5 + player.UnfreezeControlsOnServer() + level.allowTimeTravel = true +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AdjustPlayerTimelineHack( entity player, bool isBeforePlayerLandAnim = false ) +{ + vector newPos + var playerCurrentTimeline = GetEntityTimelinePosition( player ) + + if ( playerCurrentTimeline != level.timeZone ) + { + printl( "*********WARNING***************" ) + printl( "Rare bug: Player timeline and level timeline don't match....adjusting player pos" ) + printl( "Adjusting before player land animation: " + isBeforePlayerLandAnim ) + printl( "*******************************" ) + newPos = GetPosInOtherTimeline( player.GetOrigin() ) + player.SetOrigin( newPos ) + } +} +///////////////////////////////////////////////////////////////////////////////////////// +void function FanDropTeleport( entity player ) +{ + player.EndSignal( "OnDeath" ) + FlagEnd( "DoneWithFanDropSequence" ) + entity orgFanDropOvergrownMain = GetEntByScriptName( "fandrop_start_overgrown_main" ) + entity orgFanDropOvergrownAlt = GetEntByScriptName( "fandrop_start_overgrown_alt" ) + vector vectorOffset = orgFanDropOvergrownAlt.GetOrigin() - orgFanDropOvergrownMain.GetOrigin() + + + while( true ) + { + //wait till player timeshifts past first fire + FlagWait( "doing_fan_drop_pristine" ) + + //now wait till he switches back to dodge the spinning fan + FlagWait( "past_first_spinning_fan_and_back_in_overgrown" ) + //WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + + //if we haven't died yet, do the teleport while in overgrown + if ( CanDoFanTeleport( player, vectorOffset ) ) + player.SetOrigin( player.GetOrigin() + vectorOffset ) + + //otherwise, we died, wait for player to be in reset pos + else + FlagWait( "standing_at_top_of_fan" ) + + } + + +} + +///////////////////////////////////////////////////////////////////////////////////////// +bool function CanDoFanTeleport( entity player, vector vectorOffset ) +{ + //trying to teleport while in overgrown + + if ( !IsValid( player ) ) + return false + + player.EndSignal( "OnDeath" ) + + if ( Flag( "standing_at_top_of_fan" ) ) + return false + if ( !Flag( "dropping_down_fan" ) ) + return false + if ( player.p.doingQuickDeath ) + return false + if ( GetEntityTimelinePosition( player ) != TIMEZONE_NIGHT ) + return false + if ( GetTimelinePosition( player.GetOrigin() + vectorOffset ) != TIMEZONE_NIGHT ) + return false + + return true + +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function MusicFanDrop( entity player ) +{ + if ( Flag( "StartAndersonHologram3") ) + return + + FlagEnd( "StartAndersonHologram3") + + OnThreadEnd( + function() : () + { + FlagSet( "BrokeOutOfFanDropMusicLoop") + } + ) + while( true ) + { + FlagWait( "dropping_down_fan" ) + StopMusic() + PlayMusic( "music_timeshift_29_downtherabbithole" ) + wait 2 + FlagWait( "standing_at_top_of_fan" ) + StopMusic() + + } + +} + +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗███╗ ██╗██████╗ +██╔════╝██╔══██╗████╗ ██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗ ██╔════╝████╗ ██║██╔══██╗ +█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔╝██║ ██║██████╔╝ █████╗ ██╔██╗ ██║██║ ██║ +██╔══╝ ██╔══██║██║╚██╗██║ ██║ ██║██╔══██╗██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ +██║ ██║ ██║██║ ╚████║ ██████╔╝██║ ██║╚██████╔╝██║ ███████╗██║ ╚████║██████╔╝ +╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗███╗ ██╗██████╗ +██╔════╝██╔══██╗████╗ ██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗ ██╔════╝████╗ ██║██╔══██╗ +█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔╝██║ ██║██████╔╝ █████╗ ██╔██╗ ██║██║ ██║ +██╔══╝ ██╔══██║██║╚██╗██║ ██║ ██║██╔══██╗██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ +██║ ██║ ██║██║ ╚████║ ██████╔╝██║ ██║╚██████╔╝██║ ███████╗██║ ╚████║██████╔╝ +╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗███╗ ██╗██████╗ +██╔════╝██╔══██╗████╗ ██║ ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗ ██╔════╝████╗ ██║██╔══██╗ +█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔╝██║ ██║██████╔╝ █████╗ ██╔██╗ ██║██║ ██║ +██╔══╝ ██╔══██║██║╚██╗██║ ██║ ██║██╔══██╗██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ +██║ ██║ ██║██║ ╚████║ ██████╔╝██║ ██║╚██████╔╝██║ ███████╗██║ ╚████║██████╔╝ +╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + + +void function FanDropEndStartPointSetup( entity player ) +{ + TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointFanDropEnd" ) ) + vector objectivePos = GetEntByScriptName( "objective_concourse_panel" ).GetOrigin() + TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_RETURN", objectivePos ) + +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function FanDropEndSkipped( entity player ) +{ + + CleanupEnts( "triggers_instadeath_humanroom" ) + CleanupEnts( "triggers_quickdeath_humanroom" ) + CleanupEnts( "trigger_quickdeath_checkpoint_humanroom" ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function AA_FanDropEndThread( entity player ) +{ + FlagWait( "DoneWithFanDropSequence" ) + + + CleanupEnts( "rings_pristine" ) + CleanupEnts( "triggers_instadeath_humanroom" ) + CleanupEnts( "triggers_quickdeath_humanroom" ) + CleanupEnts( "trigger_quickdeath_checkpoint_humanroom" ) + + Remote_CallFunction_Replay( player, "ServerCallback_ShowHologramTitles" ) + thread MusicFanDropEnd( player) + thread StalkerDoorSequences( player ) + thread DialogueLabCharlie( player ) + thread DialogueTransitionHall( player ) + delaythread ( 1 ) AndersonHologramSequence( player, "node_hologram_lab3", "StartAndersonHologram3" ) + + vector objectivePos = GetEntByScriptName( "objective_concourse_panel_breadcrumb_01" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + + wait 2 + + CheckPoint() + + + FlagWait( "exited_intel_room3" ) + + objectivePos = GetEntByScriptName( "objective_concourse_panel" ).GetOrigin() + TimeshiftUpdateObjective( player, objectivePos ) + + //------------------------------- + // Token overgrown/pristine wall Spectres + //------------------------------- + array< entity > propSpawners = GetEntArrayByScriptNameInInstance( "spectre_door_spawner", "spectre_spawner_return_overgrown" ) + float delayMin = 0 + float delayMax = 0.4 + int maxToSpawn = 1 + string flagToAbort = "transition_hallway_return_finished" + string flagToSetWhenSpectreSpawned = "" + bool requiresLookAt = true + thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, "", delayMin, delayMax, flagToSetWhenSpectreSpawned, requiresLookAt ) + + propSpawners = GetEntArrayByScriptNameInInstance( "spectre_door_spawner", "spectre_spawner_return_pristine" ) + delayMin = 0 + delayMax = 0.4 + maxToSpawn = 1 + flagToAbort = "transition_hallway_return_finished" + flagToSetWhenSpectreSpawned = "" + requiresLookAt = true + thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, "", delayMin, delayMax, flagToSetWhenSpectreSpawned, requiresLookAt ) + + + FlagWait( "transition_hallway_return_finished" ) + + + + //ScreenFadeToBlack( player, 1.5, 5 ) + //wait 1.5 + //player.SetInvulnerable() + //player.FreezeControlsOnServer() + + thread TransitionSpoke1() +} + + +void function TransitionSpoke1() +{ + LevelTransitionStruct trans = SaveBoyleAudioLogs() + if ( level.timeZone == TIMEZONE_NIGHT ) + trans.timeshiftMostRecentTimeline = 1 + else + trans.timeshiftMostRecentTimeline = 0 + + PickStartPoint( "sp_hub_timeshift", "PRISTINE CAMPUS", trans ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function MusicFanDropEnd( entity player ) +{ + FlagWait( "StartAndersonHologram3" ) + FlagWait( "BrokeOutOfFanDropMusicLoop" ) + + //FlagWait( "AndersonHologram3Playing" ) + + //StopMusic() + PlayMusicThatCantBeStopped( "music_timeshift_30_andersonlog04") + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function StalkerDoorSequences( entity player ) +{ + + thread QuickSkit( player, GetEntByScriptName( "node_intel_room3_stalker_door_overgrown" ), "player_near_intel3_exit", GetEntByScriptName( "intel3_exit_overgrown_look_ent" ), GetEntByScriptName( "trig_approaching_lab3_exit_overgrown" ) ) + thread QuickSkit( player, GetEntByScriptName( "node_intel_room3_stalker_door_pristine" ), "player_near_intel3_exit", GetEntByScriptName( "intel3_exit_pristine_look_ent" ), GetEntByScriptName( "trig_approaching_lab3_exit_pristine" ) ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function DialogueLabCharlie( entity player ) +{ + + +/* +██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗ +██╔══██╗██║██╔══██╗██║ ██╔═══██╗██╔════╝ ██║ ██║██╔════╝ +██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗ +██║ ██║██║██╔══██║██║ ██║ ██║██║ ██║██║ ██║██╔══╝ +██████╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ +*/ + + FlagWait( "DoneWithFanDropSequence" ) + + wait 1.5 + + FlagSet( "StartAndersonHologram3" ) + + + FlagWaitAny( "AndersonHologram3Finished", "exited_intel_room3" ) + + wait 0.75 + + //That was Major Anderson's final recording. + waitthread PlayBTDialogue( "diag_sp_targeting_TS232_02_01_mcor_bt" ) + + while( true ) + { + wait 0.25 + if ( IsAudioLogPlaying( player ) ) + continue + + else + { + //Cooper, based on your recon of this facility, I may have a plan. Meet me outside. diag_sp_targeting_TS232_03_01_mcor_bt + waitthread PlayBTDialogue( "diag_sp_targeting_TS232_03_01_mcor_bt" ) + Objective_Remind() + break + } + } + + thread ObjectiveRemindUntilFlag( "transition_hallway_return_finished" ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function DialogueTransitionHall( entity player ) +{ + + +/* +██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗ +██╔══██╗██║██╔══██╗██║ ██╔═══██╗██╔════╝ ██║ ██║██╔════╝ +██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗ +██║ ██║██║██╔══██║██║ ██║ ██║██║ ██║██║ ██║██╔══╝ +██████╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗ +╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ +*/ + + + FlagWaitAny( "player_near_intel3_exit_overgrown", "player_near_intel3_exit_pristine" ) + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + wait 1.5 + + //Security - PA Attention. Automated security personnel have now been deployed in all non-combatant sectors. + //Please display security credentials clearly to avoid accidental termination. Thank you. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_targeting_TS231_12_01_imc_facilityPA" ) + + wait 3 + + soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + //Security -PA Automated security personnel: Please target all non-IMC military subjects. + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_targeting_TS231_11_01_imc_facilityPA" ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗ +██╔════╝██║ ██║██╔══██╗██╔══██╗██╔════╝██╔══██╗ +███████╗███████║███████║██████╔╝█████╗ ██║ ██║ +╚════██║██╔══██║██╔══██║██╔══██╗██╔══╝ ██║ ██║ +███████║██║ ██║██║ ██║██║ ██║███████╗██████╔╝ +╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗ +██╔════╝██║ ██║██╔══██╗██╔══██╗██╔════╝██╔══██╗ +███████╗███████║███████║██████╔╝█████╗ ██║ ██║ +╚════██║██╔══██║██╔══██║██╔══██╗██╔══╝ ██║ ██║ +███████║██║ ██║██║ ██║██║ ██║███████╗██████╔╝ +╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗ +██╔════╝██║ ██║██╔══██╗██╔══██╗██╔════╝██╔══██╗ +███████╗███████║███████║██████╔╝█████╗ ██║ ██║ +╚════██║██╔══██║██╔══██║██╔══██╗██╔══╝ ██║ ██║ +███████║██║ ██║██║ ██║██║ ██║███████╗██████╔╝ +╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////// +/* +███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗ +██╔════╝██║ ██║██╔══██╗██╔══██╗██╔════╝██╔══██╗ +███████╗███████║███████║██████╔╝█████╗ ██║ ██║ +╚════██║██╔══██║██╔══██║██╔══██╗██╔══╝ ██║ ██║ +███████║██║ ██║██║ ██║██║ ██║███████╗██████╔╝ +╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝ + +*/ +///////////////////////////////////////////////////////////////////////////////////////// + +void function OnSpawnedPropDynamic( entity propDynamic ) +{ + string scriptName = propDynamic.GetScriptName() + + if ( scriptName == "flyer_lab" ) + thread LabCreatureThink( propDynamic ) + + if ( scriptName == "labrats_idlers" ) + thread LabRatIdlerThink( propDynamic ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function LabRatIdlerThink( entity propDynamic ) +{ + entity node = CreateScriptRef( propDynamic.GetOrigin(), propDynamic.GetAngles() ) + vector originOffset = PositionOffsetFromEnt( node, -15, 0, 0 ) + node.SetOrigin( originOffset ) + thread PlayAnimTeleport( propDynamic, propDynamic.kv.script_noteworthy, node ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function OnSpawnedLevelNPC( entity npc ) +{ + string scriptName = npc.GetScriptName() + + if ( scriptName == "lab_prowlers" ) + { + thread LabCreatureThink( npc ) + + } + + if ( scriptName == "soldiers_human_room_fight_01" ) + AttackPlayer( npc ) + + + //if ( scriptName == "doctor_science_intel_room_01" ) + //thread LabAlphaDoctorScienceThink( npc ) + + if ( scriptName == "civilian_walker_evac_elevator_labs" ) + thread LabAlphaScientistThink( npc ) + + if ( scriptName == "labA_dudes" ) + thread LabAlphaScientistThink( npc ) + + if ( scriptName == "grunts_elevator" ) + thread GruntElevatorThink( npc ) + + if ( scriptName == "prowlers_courtyard" ) + thread ProwlersAmbientThink( npc ) + + if ( scriptName == "courtyard_responders" ) + thread CourtyardSoldiersThink( npc ) + + if ( scriptName == "civilian_walker_courtyard" ) + thread CourtyardSoldiersThink( npc ) + + if ( scriptName == "creature_surprised_dudes" ) + thread CreatureSurprisedSoldiersThink( npc ) + + if ( scriptName == "elevator_turret_dudes" ) + thread HallwayTurretDudesThink( npc ) + + if ( scriptName == "sphere_room_dudes" ) + thread GruntsSphereRoomThink( npc ) + + if ( scriptName == "first_timeshift_soldiers" ) + AttackPlayer( npc ) + + + +} + + +void function LabCreatureThink( entity creature ) +{ + creature.EndSignal( "PlayerInsideCage" ) + creature.EndSignal( "OnDeath" ) + creature.EndSignal( "OnDestroy" ) + + thread AnimalCrueltyAcheivement( creature ) + wait 1 //need to wait for player to spawn, otherwise it may early out + + if ( creature.IsNPC() ) + creature.SetNoTarget( true ) + + entity trigger + entity node + array <entity> linkedEnts = creature.GetLinkEntArray() + foreach ( entity ent in linkedEnts ) + { + if ( ent.GetClassName() == "script_ref" ) + { + node = ent + continue + } + if ( ent.GetClassName() == "trigger_multiple" ) + { + trigger = ent + continue + } + } + //if ( creature.IsNPC() ) + //Assert( IsValid( trigger ), "creature at " + creature.GetOrigin() + " is not linked to a trigger" ) + + Assert( IsValid( node ), "creature at " + creature.GetOrigin() + " is not linked to a node" ) + + + string animIdle = "" + string animBreakout = "" + string animAggroLoop = "" + + switch ( node.GetScriptName() ) + { + case "variant01": + animIdle = "pr_timeshift_caged_sleeping_01" + animBreakout = "pr_timeshift_caged_sleeping_01_interrupt" + animAggroLoop = "pr_timeshift_caged_small_aggro_01" + break + case "variant02": + animIdle = "pr_timeshift_caged_sleeping_02" + animBreakout = "pr_timeshift_caged_sleeping_02_interrupt" + animAggroLoop = "pr_timeshift_caged_small_aggro_02" + break + case "variant03": + animIdle = "pr_timeshift_caged_sleeping_03" + animBreakout = "pr_timeshift_caged_sleeping_03_interrupt" + animAggroLoop = "pr_timeshift_caged_small_aggro_03" + break + case "variant04": + animIdle = "pr_timeshift_caged_laying_01" + animBreakout = "pr_timeshift_caged_laying_01_interrupt" + animAggroLoop = "pr_timeshift_caged_small_aggro_04" + break + case "variant05": + animIdle = "pr_timeshift_caged_sitting_01" + animBreakout = "pr_timeshift_caged_sitting_01_interrupt" + animAggroLoop = "pr_timeshift_caged_small_aggro_05" + break + case "variant06": + animIdle = "pr_timeshift_caged_standing_01" + animBreakout = "pr_timeshift_caged_standing_01_interrupt" + animAggroLoop = "pr_timeshift_caged_small_aggro_03" + break + case "variant07": + animIdle = "pr_timeshift_caged_long_14" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterA1": + animIdle = "pr_timeshift_caged_long_06" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterA2": + animIdle = "pr_timeshift_caged_long_07" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterA3": + animIdle = "pr_timeshift_caged_long_08" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterA4": + animIdle = "pr_timeshift_caged_long_09" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterA5": + animIdle = "pr_timeshift_caged_long_10" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterA6": + animIdle = "pr_timeshift_caged_long_11" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterA7": + animIdle = "pr_timeshift_caged_long_12" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterA8": + animIdle = "pr_timeshift_caged_long_13" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterB1": + animIdle = "pr_timeshift_caged_long_01" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterB2": + animIdle = "pr_timeshift_caged_long_02" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterB3": + animIdle = "pr_timeshift_caged_long_03" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterB4": + animIdle = "pr_timeshift_caged_long_04" + animBreakout = "" + animAggroLoop = "" + break + case "variantCageTogeterB5": + animIdle = "pr_timeshift_caged_long_05" + animBreakout = "" + animAggroLoop = "" + break + case "variantLab01": + animIdle = "fl_timeshift_caged_tall_laying_01" + animBreakout = "fl_timeshift_caged_tall_interrupt_01" + animAggroLoop = "fl_timeshift_caged_tall_aggro_01" + break + case "variantLab02": + animIdle = "fl_timeshift_caged_tall_laying_02" + animBreakout = "fl_timeshift_caged_tall_interrupt_02" + animAggroLoop = "fl_timeshift_caged_tall_aggro_02" + break + case "variantLab03": + animIdle = "fl_timeshift_caged_tall_laying_03" + animBreakout = "fl_timeshift_caged_tall_interrupt_03" + animAggroLoop = "fl_timeshift_caged_tall_aggro_03" + break + case "variantLab04": + animIdle = "fl_timeshift_caged_tall_laying_04" + animBreakout = "fl_timeshift_caged_tall_interrupt_04" + animAggroLoop = "fl_timeshift_caged_tall_aggro_04" + break + case "variantCylinder01": + animIdle = "fl_timeshift_caged_cylinder_01" + animBreakout = "" + animAggroLoop = "" + thread PlayDeathAnimWhenShot( creature, "fl_timeshift_caged_cylinder_death_01" ) + break + case "variantCylinder02": + animIdle = "fl_timeshift_caged_cylinder_02" + animBreakout = "" + animAggroLoop = "" + thread PlayDeathAnimWhenShot( creature, "fl_timeshift_caged_cylinder_death_01" ) + break + default: + Assert( 0, "creatureNode at " + node.GetOrigin() + " has an unhandled script_name" ) + + } + + //------------------------------------------------------------- + // Go back into AI if this thread ends (player inside cage) + //-------------------------------------------------------------- + + OnThreadEnd( + function() : ( creature ) + { + if ( !IsAlive( creature ) ) + return + creature.Anim_Stop() + } + ) + + //------------------------------------------ + // Play chill idle + //------------------------------------------ + thread PlayAnimTeleport( creature, animIdle, node ) + + array <entity> players = GetPlayerArray() + if ( players.len() <= 0 ) + return + entity player = players[ 0 ] + + if ( ( creature.IsNPC() ) && ( trigger != null ) ) + thread LabProwlerPlayerInsideCage( creature, trigger, player ) + + while( !PlayerInRange( player.GetOrigin(), creature.GetOrigin(), 256 ) ) + wait 0.2 + + wait ( RandomFloatRange( 0, 0.8 ) ) + + //------------------------------------------ + // Player close, breakout into aggro idle + //------------------------------------------ + if ( animBreakout != "" ) + waitthread PlayAnim( creature, animBreakout, node ) + + if ( animAggroLoop != "" ) + thread PlayAnim( creature, animAggroLoop, node ) + + + WaitForever() +} + + +void function AnimalCrueltyAcheivement( creature ) +{ + if ( Flag( "AcheivementUnlockedLabProwler") ) + return + if ( !creature.IsNPC() ) + return + if ( creature.GetClassName() != "npc_prowler" ) + return + + if ( Flag( "AcheivementUnlockedLabProwler") ) + return + + FlagEnd( "AcheivementUnlockedLabProwler" ) + + + //To do: detect if killed by the player + //creature.WaitSignal( "OnDeath" ) + + +} +void function PlayDeathAnimWhenShot( entity propCreature, string anim ) +{ + + + + +} + +void function LabProwlerPlayerInsideCage( entity prowler, entity trigger, entity player ) +{ + prowler.EndSignal( "OnDeath" ) + player.EndSignal( "OnDeath" ) + + trigger.WaitSignal( "OnTrigger" ) + + prowler.Signal( "PlayerInsideCage" ) + +} + + +void function PlayMusicInTimezoneUntilFlag( string track, var timeZone, string flagToAbort ) +{ + if ( Flag( flagToAbort ) ) + return + + FlagEnd( flagToAbort ) + + var otherTimeZone + if ( timeZone == TIMEZONE_NIGHT ) + otherTimeZone = TIMEZONE_DAY + else + otherTimeZone = TIMEZONE_NIGHT + + while( true ) + { + + waitthread WaittillPlayerSwitchesTimezone( timeZone ) + + StopMusic() + PlayMusic( track ) + + waitthread WaittillPlayerSwitchesTimezone( otherTimeZone ) + } +} + + + + +void function DebugX( entity player ) +{ + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + + waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_18_01_imc_grunt3" ) +}
\ No newline at end of file diff --git a/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_utility.nut b/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_utility.nut new file mode 100644 index 00000000..29b2cfc3 --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_utility.nut @@ -0,0 +1,7253 @@ +untyped + +global function IsAudioLogPlaying +global function SaveBoyleAudioLogs +global function InitBoyleAudioLogs +global function SetFlagWhenPlayerWithinRangeOfEnt +global function DeleteUnnecessaryFlyers +global function GetPosInOtherTimeline +global function ObjectiveRemindUntilFlag +global function PlayAnimThenDelete +global function LoudspeakerThread +global function GivePropForAnim +global function ElectricalScreenEffects +global function GetTimelinePosition +global function DisableNavmeshSeperatorTargetedByEnt +global function DropshipSpawnAndRepeat +global function DeleteNpcWhenOutOfSight +global function TitanTimeshiftHint +global function CreateShakeWhileFlagSet +global function CreateShakeTimeshift +global function RingsLocalExplosionNormal +global function RingsLocalExplosionBig +global function PlayerDropLand +global function ProwlersAmbientThink +global function PlayerConversationStopOnFlagImmediate +global function FlagSetDelayed +global function FlagClearDelayed +global function PlayerConversationStopOnFlag +global function HideCritTimeshift +global function TitanRackSpawnersThink +global function TitanRackDeploy +global function SpawnAutoSecurityGroup +global function SpawnPristineStalkersWithDopplegangers +global function TitanTimeshiftLoadout +global function AndersonHologramSequence +global function GunshipSequence +global function GetTimeshiftPlayer +global function GetClosestGrunt +global function EmitSoundAtPositionHack +global function FreezeNPC +global function UnFreezeNPC +global function MakeCivilian +global function DestroyNPCOnFlag +global function AttackPlayer +global function HideStuff +global function ShowStuff +global function SwapTimelinesScripted +global function TS_WithinPlayerFOV +global function SetFlagWhenPlayerLookingAtEnt +global function PlayerInRange +global function HideWeaponsAndAmmoTillFlag +global function DataStab +//global function MakeSpectreOwnedByPlayer +global function SkyboxStart +global function TimeshiftSetObjective +global function TimeshiftSetObjectiveSilent +global function TimeshiftUpdateObjective +global function SPTimeshiftUtilityInit +global function GetSpectreDoorSwitchByDummyName +global function DropHangingOgre +global function DropHangingOgreOnFlag +global function DestroyArray +global function LaserMeshDeactivateByInstanceName +global function LaserMeshDestroyByInstanceName +global function LaserMeshActivateByInstanceName +global function SetCurrentObjectivePos +global function MoveEntityToOppositeTimePeriod +global function HackPlayLoopEffectOnEntity +//global function CreateSoundRefHack +global function DeleteFireHazards +global function DestroyFuncBrushBreakable +//global function SpawnWallSpectreGroupWhenInRange +global function SpawnShowcaseGroupWhenInRange +global function RemoveBlocker +global function RestoreBlocker +global function TempExplosion +global function DestroyEntByScriptName +global function SwapTimelines +global function WaittillSomeDudesAreDead +global function WaittillPlayerSwitchesTimezone +global function TimeshiftPlayerThink +global function TimeshiftHint +global function DestroyInstancesByScriptInstanceName +global function HACK_DisableTurret +global function HACK_EnableTurret +global function GetNpcByScriptName +global function CreateLoudspeakerEnt +global function GiveTimeshiftAbility +global function SetFlagWhenBreakablesDestroyed +global function DestroyIfValid +global function GetEntityTimelinePosition +global function SleepingSpectreFX +global function CleanupAI +global function CleanupEnts +global function CreateBestLoudspeakerEnt +global function GiveLowAmmo +//global function DeleteWaponsWithScriptname +global function QuickSkit +global function KillMyInterdimensionalBrother +global function RingsThink +global function SetLectureHallLineDuration + + + +global struct EntityLevelStruct +{ + bool npcMarkedForCleanup +} + +struct +{ + array< entity > flagSetEntities + array< entity > spectreDoorPanels + array< entity > spectreDoorTriggers, + array< entity > spawnProps + array< entity > spectreSpawnDoors + array< entity > loudspeakerEnts + vector currentObjectivePos + vector lastGoodTimeshiftPosOvergrown + vector lastGoodTimeshiftPosPristine + bool isDisplayingDamageText + float lectureHallTimeBeforePlayerInterrupts + array<entity> npcsPresent + array<entity> npcsPast + entity currentObjectiveEntity + entity titanCorpseOrg + entity stalkerCorpseOrg + array< entity > titanCorpsePieces + array< entity > stalkerCorpsePieces + array<string> deathPoses + bool isDisplayingTimeshiftHint + bool loudspeakerThreadRunning + array< entity > ambientDeletableFlyers + int[5] boyleAudioLogNumberAssignments = [ 0, 0, 0, 0, 0 ] + int boyleAudioLogsCollected = 0 + bool debugAudioLogs = false + array< entity > audioLogModels + +} file + +//--------------------------- +// GLOBALS +//--------------------------- +const FLYER_TIMESHIFT_HEALTH = 500 +const TIME_ZOFFSET_TOEXPLOSION_FROM_PRISTINE = -22144 +const TIME_ZOFFSET_TOEXPLOSION_FROM_OVERGROWN = -10624 +const TIMEZONE_DAY = 0 +const TIMEZONE_NIGHT = 1 +const TIMEZONE_ALL = 2 +const TIMEZONE_FROZEN = 3 + +const BREAKABLE_TYPE_SATCHEL_DEBRIS_MEDIUM = 0 +const BREAKABLE_TYPE_SATCHEL_DEBRIS_MEDIUM_WIDE = 1 +const BREAKABLE_TYPE_AQUARIUM = 2 + +const DIST_TO_NOT_CARE_ABOUT_AUDIOLOGS = 1500 +const MODEL_CIV01 = $"models/Humans/civilian/civilian_sci_v1.mdl" +const MODEL_CIV02 = $"models/Humans/civilian/civilian_sci_v2.mdl" +const MODEL_CIV03 = $"models/Humans/civilian/civilian_sci_v3.mdl" +const MODEL_CIV04 = $"models/Humans/civilian/civilian_sci_v4.mdl" +const MODEL_BUTTON = $"models/props/global_access_panel_button/global_access_panel_button_wall.mdl" +const MODEL_BUTTON_LARGE = $"models/props/global_access_panel_button/global_access_panel_button_console.mdl" +const IMC_CORPSE_MODEL_LMG = $"models/Humans/grunts/imc_grunt_lmg_corpse.mdl" +const IMC_CORPSE_MODEL_RIFLE = $"models/Humans/grunts/imc_grunt_rifle_corpse.mdl" +const IMC_CORPSE_MODEL_SHOTGUN = $"models/Humans/grunts/imc_grunt_shotgun_corpse.mdl" +const IMC_CORPSE_MODEL_SMG = $"models/Humans/grunts/imc_grunt_smg_corpse.mdl" +const IMC_CORPSE_MODEL_HEAVY = $"models/Humans/grunts/imc_grunt_lmg_corpse.mdl" +const HOLOGRAM_KNIFE_MODEL = $"models/weapons/combat_knife/w_combat_knife.mdl" +const ANDERSON_HOLOGRAM_MODEL = $"models/humans/heroes/mlt_hero_anderson.mdl" +const ANDERSON_MODEL = $"models/humans/heroes/mlt_hero_anderson.mdl" +const SARAH_HOLOGRAM_MODEL = $"models/humans/heroes/mlt_hero_jack.mdl" +const ENEMY_HOLOGRAM_MODEL = $"models/humans/grunts/imc_grunt_rifle.mdl" +const ANDERSON_PISTOL_MODEL = $"models/Weapons/b3wing/b3_wingman_ab_01.mdl" +const HOLOGRAM_ENEMY_GUN_MODEL = $"models/Weapons/b3wing/b3_wingman_ab_01.mdl" +const MODEL_IPAD = $"models/props/tablet/tablet.mdl" +const MODEL_COFFEE = $"models/domestic/mug_coffee_white.mdl" +const MARVIN_MODEL_OVERGROWN = $"models/robots/marvin/marvin_mossy.mdl" + +//--------------------------- +// FX +//--------------------------- +const FX_DLIGHT_LIGHT_FLICKER = $"interior_Dlight_blue_MED" +const FX_TIMESHIFT_ENTITY_MARKER = $"P_ts_entity" +const FX_GREEN_BLINKIE = $"runway_light_green" +const FX_FIRE_HYDRAULIC = $"P_fire_small_FULL" +const FX_DOOR_SCANNER = $"scan_laser_beam_mdl" //scan_laser_beam_mdl_sm +const FX_TIME_PORTAL = $"P_ts_portal" +const FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM = $"xo_exp_death" +const FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM_WIDE = $"xo_exp_death" +const FX_FIRE_MEDIUM = $"P_fire_rooftop" +const FX_FIRE_SMALL = $"P_fire_rooftop" +const FX_FIRE_HUGE = $"P_fire_512" +const FX_ELECTRICITY = $"P_elec_arc_LG_1" +//const FX_SPARKS = $"P_sparks_omni_SM_cheap" //this effect stopped working a few weeks ago and no one knows why +const FX_SPARKS = $"xo_sparks_large_trail_cheap" +const FX_LASER = $"P_security_laser" +const FX_RADIATION = $"env_ground_smoke_1024" +const FX_GENERATOR_LOOP_ACTIVE = $"P_drone_cloak_beam" +const FX_GENERATOR_LOOP_DORMANT = $"P_elec_arc_LG_1" +const FX_IMPACT_TABLE_TIMESHIFT = "timeshift_impact" +const FX_HOLOGRAM_FLASH_EFFECT = $"P_ar_holopilot_flash" +const FX_HOLOGRAM_HEX_EFFECT = $"P_ar_holopilot_hextrail" +const FX_HOLO_SCAN_ENVIRONMENT = $"P_ar_holopulse_CP" + + + + +//--------------------------- +// SOUND +//--------------------------- +const SOUND_HOLOGRAM_FLICKER = "Timeshift_Scr_AndersonHolo_FlickerIn" +const SOUND_HYDRAULIC_EXPLOSION = "Goblin_Dropship_Explode" +const SOUND_SCANNER_SPECTRE_DOOR = "SpectreDoorScanner" //LaserScanner.ScanSound_3P" +const SOUND_SCANNER_SPECTRE_DOOR_UNLOCK = "SpectreDoorUnlock" +const SOUND_SCANNER_SPECTRE_DOOR_FAIL = "SpectreDoorFail" +const SOUND_TIME_PORTAL_LOOP = "Time_Vortex_Loop" +const SOUND_BREAKABLE_SATCHEL_DEBRIS_MEDIUM = "Goblin_Dropship_Explode" +const SOUND_BREAKABLE_SATCHEL_DEBRIS_MEDIUM_WIDE = "Goblin_Dropship_Explode" +const SOUND_ELECTRICITY = "electricity_loop" +const SOUND_FIRE_MEDIUM = "amb_colony_fire_medium" +const SOUND_FIRE_HUGE = "amb_colony_fire_medium" +const SOUND_SPARKS = "Timeshift_Emit_Sparks" // +const SOUND_LASER_LOOP = "Timeshift_LaserMesh_Loop" +const SOUND_LASER_DAMAGE = "Timeshift_LaserMesh_Damage" +const SOUND_FAN_DAMAGE = "flesh_fanblade_damage_1p" +const SOUND_LIGHT_FLICKER = "marvin_weld_short" + +//--------------------------- +// STRINGS +//--------------------------- +const HINT_STRING_SPECTRE_REQUIRED = "Security Personnel Required" +const HINT_STRING_SATCHEL_REQUIRED = "Satchel Charge Required" + +//---------------------------- +// MODELS +//----------------------------- +const POV_MODEL_TIMESHIFT = $"models/weapons/arms/pov_mlt_hero_jack_ts.mdl" + + + +///////////////////////////////////////////////////////////////////////////////////////// +void function SPTimeshiftUtilityInit() +{ + FlyersShared_Init() + Temp_InitTimeshiftDialogue() + + Riff_ForceSetSpawnAsTitan( eSpawnAsTitan.Never ) + Riff_ForceTitanAvailability( eTitanAvailability.Never ) + AddCallback_EntitiesDidLoad( EntitiesDidLoad ) + AddCallback_OnPlayerRespawned( PlayerSpawned ) + AddDamageCallback( "player", OnPlayerDamage_TimeShift ) + + + PrecacheModel( MODEL_CIV01 ) + PrecacheModel( MODEL_CIV02 ) + PrecacheModel( MODEL_CIV03 ) + PrecacheModel( MODEL_CIV04 ) + PrecacheModel( MODEL_BUTTON ) + PrecacheModel( MODEL_BUTTON_LARGE ) + PrecacheModel( MODEL_IPAD ) + PrecacheModel( MODEL_COFFEE ) + PrecacheModel( POV_MODEL_TIMESHIFT ) + PrecacheModel( SARAH_HOLOGRAM_MODEL ) + PrecacheModel( ANDERSON_HOLOGRAM_MODEL ) + PrecacheModel( ENEMY_HOLOGRAM_MODEL ) + PrecacheModel( ANDERSON_PISTOL_MODEL ) + PrecacheModel( HOLOGRAM_ENEMY_GUN_MODEL ) + PrecacheModel( IMC_CORPSE_MODEL_LMG ) + PrecacheModel( IMC_CORPSE_MODEL_RIFLE ) + PrecacheModel( IMC_CORPSE_MODEL_SHOTGUN ) + PrecacheModel( IMC_CORPSE_MODEL_SMG ) + PrecacheModel( IMC_CORPSE_MODEL_HEAVY ) + PrecacheModel( HOLOGRAM_KNIFE_MODEL ) + PrecacheModel( ANDERSON_MODEL ) + PrecacheModel( MARVIN_MODEL_OVERGROWN ) + + + PrecacheImpactEffectTable( FX_IMPACT_TABLE_TIMESHIFT ) + + PrecacheParticleSystem( FX_HOLOGRAM_FLASH_EFFECT ) + PrecacheParticleSystem( FX_HOLOGRAM_HEX_EFFECT ) + PrecacheParticleSystem( FX_HOLO_SCAN_ENVIRONMENT ) + PrecacheParticleSystem( FX_DLIGHT_LIGHT_FLICKER ) + PrecacheParticleSystem( FX_FIRE_HYDRAULIC ) + PrecacheParticleSystem( FX_DOOR_SCANNER ) + PrecacheParticleSystem( FX_TIME_PORTAL ) + PrecacheParticleSystem( FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM ) + PrecacheParticleSystem( FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM_WIDE ) + PrecacheParticleSystem( FX_RADIATION ) + PrecacheParticleSystem( FX_FIRE_MEDIUM ) + PrecacheParticleSystem( FX_FIRE_SMALL ) + PrecacheParticleSystem( FX_ELECTRICITY ) + PrecacheParticleSystem( FX_SPARKS ) + PrecacheParticleSystem( FX_LASER ) + PrecacheParticleSystem( FX_TIMESHIFT_ENTITY_MARKER ) + PrecacheParticleSystem( FX_GENERATOR_LOOP_ACTIVE ) + PrecacheParticleSystem( FX_GENERATOR_LOOP_DORMANT ) + PrecacheParticleSystem( FX_GREEN_BLINKIE ) + + //spawn callbacks + AddSpectreRackCallback( RackSpawnCallback ) + AddSpawnCallback( "info_target", OnSpawnedInfoTarget ) + AddSpawnCallback( "func_brush", OnSpawnedFuncBrush ) + AddSpawnCallback( "trigger_multiple", OnSpawnedTrigger) + AddSpawnCallback( "trigger_once", OnSpawnedTrigger ) + AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_quickdeath", OnSpawnedTriggerQuickdeath ) + + + + AddSpawnCallback( "npc_turret_sentry", OnSpawnedNPC ) + AddSpawnCallback( "npc_drone", OnSpawnedNPC ) + AddSpawnCallback( "npc_soldier", OnSpawnedNPC ) + AddSpawnCallback( "npc_titan", OnSpawnedNPC ) + AddSpawnCallback( "npc_spectre", OnSpawnedNPC ) + AddSpawnCallback( "npc_stalker", OnSpawnedNPC ) + AddSpawnCallback( "npc_stalker_zombie", OnSpawnedNPC ) + AddSpawnCallback( "npc_stalker_zombie_mossy", OnSpawnedNPC ) + AddSpawnCallback( "npc_stalker_crawling_mossy", OnSpawnedNPC ) + AddSpawnCallback( "npc_prowler", OnSpawnedNPC ) + AddSpawnCallback( "npc_marvin", OnSpawnedNPC ) + AddSpawnCallback( "npc_frag_drone", OnSpawnedNPC ) + AddSpawnCallback( "npc_super_spectre", OnSpawnedNPC ) + //AddSpawnCallback( "env_fog_controller", OnSpawnedFogController ) + AddSpawnCallback( "info_spawnpoint_marvin", OnSpawnedMarvinSpawner ) + AddSpawnCallback( "prop_dynamic", OnSpawnedPropDynamic ) + AddSpawnCallback( "prop_dynamic_lightweight", OnSpawnedPropDynamic ) + AddSpawnCallbackEditorClass( "prop_dynamic", "script_switch", OnSpawnedScriptedSwitch ) + //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_weapon", WeaponPickupHack ) + + + //death callbacks + //AddDeathCallback( "player", TS_PlayerDeath ) + AddDeathCallback( "npc_soldier", TS_OnDeathNPC ) + AddDeathCallback( "npc_spectre", TS_OnDeathNPC ) + AddDeathCallback( "npc_prowler", TS_OnDeathNPC ) + AddDeathCallback( "npc_titan", TS_OnDeathNPC ) + AddDeathCallback( "npc_stalker", TS_OnDeathNPC ) + AddDeathCallback( "npc_marvin", TS_OnDeathNPC ) + + AddCallback_OnTimeShiftAbilityUsed( OnTimeShiftAbilityUsed ) + AddCallback_OnTimeShiftTitanAbilityUsed( OnTimeShiftAbilityUsed ) + AddCallback_OnSatchelPlanted( OnSatchelPlanted ) + + AddDamageCallback( "func_brush", OnDamagedFuncBrush ) + + FlagInit( "AudioLogPlaying" ) + FlagInit( "ShouldPlayGlobalLoudspeker" ) + FlagInit( "ForceFlyerTakeoff" ) + FlagInit( "AndersonHologram1Playing" ) + FlagInit( "AndersonHologram2Playing" ) + FlagInit( "AndersonHologram3Playing" ) + FlagInit( "PlayerInterruptedLecture" ) + FlagInit( "AndersonHologram1Finished" ) + FlagInit( "AndersonHologram2Finished" ) + FlagInit( "AndersonHologram3Finished" ) + FlagInit( "DisplayTheDamageHint" ) + FlagInit( "RingsShouldBeSpinning" ) + FlagSet( "DisableDropships" ) + FlagInit( "PlayerHasTimeTraveledInsideBT" ) + FlagInit( "DoingCinematicTimeshift" ) + FlagInit( "TurretsNearBunkerFenceActivated" ) + FlagInit( "PlayerObtainedC4" ) + FlagInit( "PlayerHasBioCreds") + FlagInit( "AtLeastOneBunkerTurretRestored" ) + FlagInit( "bunker_battery_teleport" ) + RegisterSignal( "BreakableDestroyed" ) + RegisterSignal( "PropSpawnerActivate" ) + RegisterSignal( "Frozen" ) + RegisterSignal( "UnFrozen" ) + RegisterSignal( "DisplayingSatchelHint" ) + RegisterSignal( "DisplayingDamageHint" ) + RegisterSignal( "PauseLasermesh" ) + RegisterSignal( "AndersonTimeshifts" ) + RegisterSignal( "AndersonEnemyShow" ) + RegisterSignal( "AndersonHideGun" ) + RegisterSignal( "AndersonEnemyShowKnife" ) + RegisterSignal( "FlyerTakeoffOverride" ) + RegisterSignal( "StopCoreEffects" ) + RegisterSignal( "AudioLogDebugDraw" ) + RegisterSignal( "StopAudioLog" ) + + + + file.isDisplayingDamageText = false + file.isDisplayingTimeshiftHint = false + + level.allowTimeTravel <- false + //level.isTimeTraveling <- false + level.timeZone <- TIMEZONE_NIGHT + level.fogController <- null + //level.playerSpawn <- null + + SetTimeshiftTimeOfDay_Night() + + if ( file.debugAudioLogs ) + thread AudioLogDebug() +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function AudioLogDebug() +{ + while ( true ) + { + wait 1 + + printl( "*******************************************************" ) + printt( "boyleAudioLogsCollected: ", file.boyleAudioLogsCollected ) + printt( "boyleAudioLogNumberAssignments:" ) + for ( int i = 0 ; i < file.boyleAudioLogNumberAssignments.len(); i++ ) + { + printt( i, " = ", file.boyleAudioLogNumberAssignments[ i ] ) + } + + } + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function EntitiesDidLoad() +{ + + array <entity> flyerSpawners = GetEntArrayByScriptName( "flyer_ambient" ) + array <entity> ambientFlyers + entity flyer + foreach( spawner in flyerSpawners ) + { + flyer = CreatePerchedFlyer( spawner.GetOrigin(), spawner.GetAngles() ) + //flyer.s.health = FLYER_TIMESHIFT_HEALTH + if ( spawner.HasKey( "script_noteworthy") ) + { + file.ambientDeletableFlyers.append( flyer ) + } + ambientFlyers.append( flyer ) + + spawner.Destroy() + thread FlyerAmbientThink( flyer ) + } + + + + array <entity> spawners = GetSpawnerArrayByClassName( "npc_prowler" ) + spawners.extend( GetSpawnerArrayByClassName( "npc_stalker" ) ) + spawners.extend( GetSpawnerArrayByClassName( "npc_stalker_zombie" ) ) + spawners.extend( GetSpawnerArrayByClassName( "npc_stalker_zombie_mossy" ) ) + spawners.extend( GetSpawnerArrayByClassName( "npc_stalker_crawling_mossy" ) ) + spawners.extend( GetSpawnerArrayByClassName( "npc_marvin" ) ) + Assert( spawners.len() > 0 ) + foreach( spawner in spawners ) + { + if ( IsSpawner( spawner ) ) + spawner.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID //setting this for all spawners because the error is retarded + } + + thread PlayerIndorsStatus() + + entity titanCorpseOrg = GetEntByScriptName( "titan_gibs_org" ) + entity stalkerCorpseOrg = GetEntByScriptName( "stalker_gibs_org" ) + Assert( IsValid( titanCorpseOrg ) ) + Assert( IsValid( stalkerCorpseOrg ) ) + thread CorpseSetup( titanCorpseOrg ) + thread CorpseSetup( stalkerCorpseOrg ) + + array <string> deathPosesLocal + + deathPosesLocal.append( "pt_timeshift_deathpose_back_01" ) + deathPosesLocal.append( "pt_timeshift_deathpose_back_02" ) + deathPosesLocal.append( "pt_timeshift_deathpose_back_03" ) + deathPosesLocal.append( "pt_timeshift_deathpose_front_01" ) + file.deathPoses = deathPosesLocal + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function DeleteUnnecessaryFlyers() +{ + wait 1 + foreach( flyer in file.ambientDeletableFlyers ) + { + if ( IsValid( flyer ) ) + flyer.Destroy() + } + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function CorpseSetup( entity corpseOrg ) +{ + array< entity > linkedEnts = corpseOrg.GetLinkEntArray() + Assert( linkedEnts.len() > 0 ) + string scriptName = corpseOrg.GetScriptName() + + foreach( entity ent in linkedEnts ) + { + ent.SetParent( corpseOrg ) + ent.Hide() + ent.NotSolid() + + if ( scriptName == "titan_gibs_org" ) + file.titanCorpsePieces.append( ent ) + if ( scriptName == "stalker_gibs_org" ) + file.stalkerCorpsePieces.append( ent ) + } + + if ( scriptName == "titan_gibs_org" ) + file.titanCorpseOrg = corpseOrg + else if ( scriptName == "stalker_gibs_org" ) + file.stalkerCorpseOrg = corpseOrg + else + Assert( 0, "Unhandled script_name: " + scriptName ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function TimeshiftSetObjectiveSilent( entity player, string objectiveString, vector objectivePos = < 0, 0, 0 >, entity objectiveEntity = null, silent = false ) +{ + TimeshiftSetObjective( player, objectiveString, objectivePos, objectiveEntity, true ) +} + +void function TimeshiftSetObjective( entity player, string objectiveString, vector objectivePos = < 0, 0, 0 >, entity objectiveEntity = null, silent = false ) +{ + if ( objectiveEntity == null ) + { + SetCurrentObjectivePos( objectivePos ) + if ( silent ) + Objective_SetSilent( objectiveString, objectivePos ) + else + Objective_Set( objectiveString, objectivePos ) + file.currentObjectiveEntity = null + } + else + { + if ( silent ) + Objective_SetSilent( objectiveString, < 0, 0, 0 >, objectiveEntity ) + else + Objective_Set( objectiveString, < 0, 0, 0 >, objectiveEntity ) + file.currentObjectiveEntity = objectiveEntity + } + + ObjectiveCompensate( player, objectiveEntity ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function TimeshiftUpdateObjective( entity player, vector objectivePos, entity objectiveEntity = null ) +{ + if ( objectiveEntity == null ) + { + SetCurrentObjectivePos( objectivePos ) + Objective_Update( objectivePos ) + file.currentObjectiveEntity = null + } + else + { + Objective_Update( < 0, 0, 0 >, objectiveEntity ) + file.currentObjectiveEntity = objectiveEntity + } + + ObjectiveCompensate( player, objectiveEntity ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function SetCurrentObjectivePos( vector pos ) +{ + Assert( pos.z != 0 ) + file.currentObjectivePos = pos +} +///////////////////////////////////////////////////////////////////////////////////////// +void function ObjectiveCompensate( entity player, entity objectiveEntity = null ) +{ + vector newPos + + if ( !IsValid( objectiveEntity ) ) + { + if ( GetTimelinePosition( file.currentObjectivePos ) == level.timeZone ) + newPos = file.currentObjectivePos + else + newPos = GetPosInOtherTimeline( file.currentObjectivePos ) + + Objective_Update( newPos ) + } + else + { + if ( GetEntityTimelinePosition( objectiveEntity ) == level.timeZone ) + Objective_Update( < 0, 0, 0 >, objectiveEntity ) + else + Objective_Update( GetPosInOtherTimeline( objectiveEntity.GetOrigin() ) ) + } +} +///////////////////////////////////////////////////////////////////////////////////////// +vector function GetPosInOtherTimeline( vector pos ) +{ + int zOffset + if ( GetTimelinePosition( pos ) == TIMEZONE_NIGHT ) + zOffset = TIME_ZOFFSET + else + zOffset = TIME_ZOFFSET * -1 + + return Vector( pos.x, pos.y, pos.z + ( zOffset ) ) + +} +///////////////////////////////////////////////////////////////////////////////////////// +/* +function DamagedBreakableHydraulic( func_brush, damageInfo ) +{ + int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo ) + entity attacker = DamageInfo_GetAttacker( damageInfo ) + local damageAmount = DamageInfo_GetDamage( damageInfo ) + local entName = func_brush.GetTargetName() + + if ( damageSourceID != eDamageSourceId.mp_weapon_satchel ) + { + Dev_PrintMessage( attacker, "#BLANK_TEXT", "#HINT_STRING_SATCHEL_REQUIRED", 3.0 ) + return + } + + Signal( func_brush, "BreakableDestroyed" ) +} +*/ +///////////////////////////////////////////////////////////////////////////////////////// +void function PlayerSpawned( entity player ) +{ + //Need to decide whether we are in past/present based on level.struct + Remote_CallFunction_NonReplay( player, "ServerCallback_TimeFlipped", TIMEZONE_NIGHT ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function GiveTimeshiftAbility( entity __player ) +{ + //give it to everyone, not just the player + foreach( entity player in GetPlayerArray() ) + { + player.SetPlayerSettings( "pilot_solo_timeshift" ) + if ( IsValid( player.GetOffhandWeapon( OFFHAND_SPECIAL ) ) ) + player.TakeOffhandWeapon( OFFHAND_SPECIAL ) + player.GiveOffhandWeapon( "mp_ability_timeshift", 1 ) + + entity viewModel = player.GetFirstPersonProxy() + viewModel.SetSkin( 1 ) + Remote_CallFunction_NonReplay( player, "ServerCallback_TimeDeviceAcquired" ) + } + level.allowTimeTravel = true +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function DataStab( entity player, entity ref ) +{ + vector playerStartPos = player.GetOrigin() + vector playerStartAng = player.GetAngles() + + vector origin = ref.GetOrigin() + vector angles = ref.GetAngles() + + + ref.SetAngles( ref.GetAngles() + Vector( 0, 0, 18 ) ) + + FirstPersonSequenceStruct sequenceStart + sequenceStart.blendTime = 1.0 + sequenceStart.attachment = "ref" + sequenceStart.firstPersonAnim = "ptpov_data_core_leech_start" + sequenceStart.thirdPersonAnim = "pt_core_console_leech_start" + sequenceStart.viewConeFunction = ViewConeZero + + FirstPersonSequenceStruct sequenceMid + sequenceMid.blendTime = 0.0 + sequenceMid.attachment = "ref" + sequenceMid.firstPersonAnim = "ptpov_data_knife_console_leech_idle" + sequenceMid.thirdPersonAnim = "pt_data_knife_console_leech_idle" + sequenceMid.viewConeFunction = ViewConeTight + + + FirstPersonSequenceStruct sequenceEnd + sequenceEnd.blendTime = 0.0 + sequenceEnd.attachment = "ref" + sequenceEnd.firstPersonAnim = "ptpov_core_scan_end" + sequenceEnd.thirdPersonAnim = "pt_core_scan_end" + sequenceEnd.viewConeFunction = ViewConeTight + + player.DisableWeaponWithSlowHolster() + player.ContextAction_SetBusy() + //player.FreezeControlsOnServer() + + //thread GiveDataknifeForDuration( player, 5 ) + + //entity fpProxy = player.GetFirstPersonProxy() + //int attachID = fpProxy.LookupAttachment( "KNIFE" ) + //entity weaponModel = CreatePropDynamic( DATA_KNIFE_MODEL ) + //weaponModel.SetParent( player.GetFirstPersonProxy(), "PROPGUN", false, 0.0 ) + + + entity viewModel = player.GetFirstPersonProxy() + viewModel.Hide() + Remote_CallFunction_NonReplay( player, "ServerCallback_StopGloveGlow" ) + + thread CoreHudHighlight( player ) + + + //delaythread ( 1 ) KnifePopOut( weaponModel ) + waitthread FirstPersonSequence( sequenceStart, player, ref ) + + + //EmitSoundOnEntity( player, "dataknife_loopable_beep" ) + + + FlagEnd( "PlayerAtLevelEnd" ) + + OnThreadEnd( + function() : ( player, viewModel ) + { + if ( !IsValid( player ) ) + return + player.Anim_Stop() + player.ClearParent() + ClearPlayerAnimViewEntity( player ) + if ( player.ContextAction_IsBusy() ) + player.ContextAction_ClearBusy() + player.EnableWeapon() + + if ( IsValid( viewModel ) ) + viewModel.Show() + } + ) + + thread FirstPersonSequence( sequenceMid, player, ref ) + wait 0.5 + //EmitSoundOnEntity( player, "Player.Hitbeep_headshot.Kill.Human_3P_vs_1P" ) + wait 0.5 + //EmitSoundOnEntity( player, "dataknife_ring1" ) + wait 1 + //EmitSoundOnEntity( player, "dataknife_ring2" ) + wait 1 + //EmitSoundOnEntity( player, "dataknife_ring1" ) + wait 15.5 + //EmitSoundOnEntity( player, "dataknife_complete" ) + //EmitSoundOnEntity( player, "Player.Hitbeep_headshot.Kill.Human_3P_vs_1P" ) + waitthread FirstPersonSequence( sequenceEnd, player, ref ) + + //StopSoundOnEntity( player, "dataknife_loopable_beep" ) + + + + +} + +////////////////////////////////////////////////////////////////////// +void function CoreHudHighlight( entity player ) +{ + entity core_dummy = GetEntByScriptName( "core_dummy" ) + + //core_dummy.Show() + //SetTeam( core_dummy, TEAM_IMC ) + //core_dummy.Highlight_ShowInside( 1.0 ) + //core_dummy.Highlight_ShowOutline( 1.0 ) + //Highlight_SetEnemyHighlight( core_dummy, "enemy_sonar" ) + + + + + +} +////////////////////////////////////////////////////////////////////// +function KnifePopOut( entity knife ) +{ + knife.Anim_Play( "data_knife_console_leech_start" ) +} + + +///////////////////////////////////////////////////////////////////////////////////////// +function GiveDataknifeForDuration( entity player, float time ) +{ + + entity fpProxy = player.GetFirstPersonProxy() + int attachID = fpProxy.LookupAttachment( "KNIFE" ) + entity weaponModel = CreatePropDynamic( DATA_KNIFE_MODEL, fpProxy.GetAttachmentOrigin( attachID ), fpProxy.GetAttachmentAngles( attachID ) ) + weaponModel.SetParent( player.GetFirstPersonProxy(), "KNIFE", false, 0.0 ) + + OnThreadEnd( + function() : ( weaponModel ) + { + if ( IsValid( weaponModel ) ) + weaponModel.Destroy() + } + ) + + player.EndSignal( "OnDeath" ) + + wait time +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function OnSpawnedInfoTarget( entity info_target ) +{ + string entName = info_target.GetTargetName() + string scriptName = info_target.GetScriptName() + + if ( scriptName == "loudspeaker_ent" ) + { + file.loudspeakerEnts.append( info_target ) + info_target.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT + } + + if ( ( entName.find( "flagSetEntity" ) != null ) || ( entName.find( "FlagSetEntity" ) != null ) ) + { + file.flagSetEntities.append( info_target ) + string flagToSet = info_target.GetScriptName() + FlagInit( flagToSet ) + } + + //breakables + /* + if ( entName.find( "breakable" ) != null ) + thread BreakableSetup( info_target ) + */ + + //generic sparks + if ( scriptName == "sparks" ) + thread GenericSparks( info_target ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function OnSpawnedFuncBrush( entity func_brush ) +{ + + + + +} + +/* +///////////////////////////////////////////////////////////////////////////////////////// +void function TempDoorThink( entity info_target ) +{ + entity doorController = info_target + vector origin = doorController.GetOrigin() + array< entity > linkedEnts = info_target.GetLinkEntArray() + Assert( linkedEnts.len() > 0, "Door controller at " + info_target.GetOrigin() + " has no linked ents" ) + string classname + array< entity > doors + foreach( entity ent in linkedEnts ) + { + classname = ent.GetClassName() + if ( classname == "func_brush" ) + doors.append( ent ) + } + Assert( doors.len() > 0 ) + + string entName + + foreach( door in doors ) + { + linkedEnts = door.GetLinkEntArray() + foreach( ent in linkedEnts ) + { + entName = ent.GetTargetName() + if ( entName.find( "start" ) != null ) + door.s.startEnt <- ent + if ( entName.find( "end" ) != null ) + door.s.endEnt <- ent + } + Assert( IsValid( door.s.startEnt ) ) + Assert( IsValid( door.s.endEnt ) ) + + entity mover = TSCreateScriptMoverLight( expect entity( door.s.startEnt ), door.s.startEnt.GetOrigin(), door.s.startEnt.GetAngles() ) + door.s.mover <- mover + door.SetParent( mover ) + door.s.openPos <- door.s.endEnt.GetOrigin() + } + + wait 1 + + entity flagEnt = GetClosest( file.flagSetEntities, doorController.GetOrigin() ) + Assert( IsValid( flagEnt ) ) + Assert( Distance( flagEnt.GetOrigin(), doorController.GetOrigin() ) < 50, "No flag entity within 50 units of doorController at " + doorController.GetOrigin() ) + string flagToWaitFor = flagEnt.GetScriptName() + + FlagWait( flagToWaitFor ) + + EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_stop" ) + EmitSoundOnEntity( doors[ 0 ], "door_open_loop" ) + foreach( door in doors ) + door.s.mover.NonPhysicsMoveTo( door.s.openPos, 2, 0.0, 0.0 ) + + wait 2 + + StopSoundOnEntity( doors[ 0 ], "door_open_loop" ) + EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_stop" ) + +} +*/ +///////////////////////////////////////////////////////////////////////////////////////// + +/* +void function OnSpawnedFogController( entity fogController ) +{ + if ( GetBugReproNum() != 007 ) + { + fogController.Destroy() + return + } + + level.fogController = fogController + ChangeFog( TIMEZONE_NIGHT ) +} +*/ + + +///////////////////////////////////////////////////////////////////////////////////////// +void function OnSpawnedPropDynamic( entity propDynamic ) +{ + int contextId = 0 + string entName = propDynamic.GetTargetName() + string scriptName = propDynamic.GetScriptName() + + if ( scriptName == "button_overgrown_large" ) + thread ButtonOvergrownThink( propDynamic, "large" ) + + if ( scriptName == "button_overgrown" ) + thread ButtonOvergrownThink( propDynamic, "small" ) + + if ( scriptName == "spectre_door_spawner" ) + thread SpectreDoorSpawnerThink( propDynamic ) + + if ( scriptName == "door_out_of_order" ) + thread DoorOutOfOrderThink( propDynamic ) + + + if ( scriptName == "light_flicker" ) + thread LightFlickerThink( propDynamic ) + + //---------------------------------------------------- + // Objective highlighting needs to be done onSpawn + //---------------------------------------------------- + if ( + ( scriptName.find( "helmet_dogtag" ) != null ) || + ( scriptName.find( "core_dummy" ) != null ) || + ( scriptName.find( "anderson_first_half" ) != null ) + + ) + { + Highlight_ClearEnemyHighlight( propDynamic ) + propDynamic.Highlight_SetFunctions( contextId, 0, true, HIGHLIGHT_OUTLINE_INTERACT_BUTTON, 1, 0, false ) + propDynamic.Highlight_SetParam( contextId, 0, HIGHLIGHT_COLOR_INTERACT ) + propDynamic.Highlight_SetCurrentContext( contextId ) + propDynamic.Highlight_ShowInside( 0 ) + propDynamic.Highlight_ShowOutline( 0 ) + + Objective_InitEntity( propDynamic ) + } + + if ( scriptName == "audio_log_model" ) + { + propDynamic.Highlight_SetFunctions( contextId, 0, true, HIGHLIGHT_OUTLINE_INTERACT_BUTTON, 1, 0, false ) + propDynamic.Highlight_SetParam( contextId, 0, HIGHLIGHT_COLOR_INTERACT ) + propDynamic.Highlight_SetCurrentContext( contextId ) + propDynamic.Highlight_ShowInside( 0 ) + propDynamic.Highlight_ShowOutline( 0 ) + thread AudioLogModelThink( propDynamic ) + } + + /* + asset modelName = propDynamic.GetModelName() + if ( + ( modelName == SARAH_HOLOGRAM_MODEL ) || + ( modelName == ANDERSON_HOLOGRAM_MODEL ) || + ( modelName == ENEMY_HOLOGRAM_MODEL ) + ) + { + propDynamic.SetSkin( 1 ) + } + */ + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function SpectreDoorSpawnerThink( entity spawnProp ) +{ + asset modelName = spawnProp.GetModelName() + file.spawnProps.append( spawnProp ) + entity spawnerRack + entity spawnerSpectre + array< entity > linkedEnts = spawnProp.GetLinkEntArray() + Assert( linkedEnts.len() > 0 ) + string editorClassname + string classname + + foreach( entity ent in linkedEnts ) + { + editorClassname = GetEditorClass( ent ) + classname = ent.GetClassName() + + if ( editorClassname == "npc_spectre_rack_wall" ) + spawnerRack = ent + if ( classname == "spawner" ) + spawnerSpectre = ent + } + + Assert( IsValid( spawnerRack ) ) + Assert( IsValid( spawnerSpectre ) ) + + entity mover + + vector origin = spawnProp.GetOrigin() + vector angles = spawnProp.GetAngles() + vector originOffset + vector reverseOriginOffset + + string sound + float doorOpenTime + float zHeight + entity ref = CreateEntity( "info_target" ) + ref.SetOrigin( origin ) + ref.SetAngles( angles ) + DispatchSpawn( ref ) + + var doorOpenDelayTime + float spawnDelayTime + if ( spawnProp.HasKey( "script_delay" ) ) + doorOpenDelayTime = spawnProp.kv.script_delay + else + doorOpenDelayTime = 0 + + mover = TSCreateScriptMoverLight( ref, origin, angles ) + spawnProp.SetParent( mover ) + + switch( modelName ) + { + + case $"models/levels_terrain/sp_timeshift/door_custom_timeshift_lobby_01.mdl": + file.spectreSpawnDoors.append( spawnProp ) + originOffset = PositionOffsetFromEnt( spawnProp, 0, 2, 0 ) + reverseOriginOffset = PositionOffsetFromEnt( spawnProp, 0, -2, 0 ) + sound = "Timeshift_Scr_StalkerPodOpen" + doorOpenTime = 0.5 + spawnDelayTime = 0.1 + zHeight = 72 + break + case $"models/levels_terrain/sp_timeshift/door_custom_timeshift_concourse_01.mdl": + file.spectreSpawnDoors.append( spawnProp ) + originOffset = PositionOffsetFromEnt( spawnProp, 4, 0, 0 ) + reverseOriginOffset = PositionOffsetFromEnt( spawnProp, -4, 0, 0 ) + sound = "Timeshift_Scr_StalkerPodOpen" + doorOpenTime = 2 + spawnDelayTime = 0.1 + zHeight = 85 + break + case $"models/timeshift/timeshift_column_panel_09_destroyed.mdl": + case $"models/timeshift/timeshift_column_panel_09.mdl": + case $"models/timeshift/timeshift_column_panel_10_destroyed.mdl": + case $"models/timeshift/timeshift_column_panel_10.mdl": + file.spectreSpawnDoors.append( spawnProp ) + originOffset = PositionOffsetFromEnt( spawnProp, 0, 2, 0 ) + reverseOriginOffset = PositionOffsetFromEnt( spawnProp, 0, -2, 0 ) + sound = "Timeshift_Scr_StalkerPodOpen" + doorOpenTime = 0.5 + spawnDelayTime = 0.1 + zHeight = 88 + break + default: + Assert( 0, "Unhandled spectre door type at " + spawnProp.GetOrigin() ) + break + } + + while( true ) + { + spawnProp.WaitSignal( "PropSpawnerActivate" ) + + wait doorOpenDelayTime + + EmitSoundAtPosition( TEAM_UNASSIGNED, origin, sound ) + mover.NonPhysicsMoveTo( originOffset, 0.5, 0.0, 0.0 ) + wait 0.5 + mover.NonPhysicsMoveTo( spawnProp.GetOrigin() + Vector( 0, 0, zHeight ), doorOpenTime, 0.0, 0.0 ) + delaythread ( spawnDelayTime ) SpawnFromStalkerRack( spawnerRack ) + + wait doorOpenTime + 3 + + mover.NonPhysicsMoveTo( spawnProp.GetOrigin() + Vector( 0, 0, -zHeight ), doorOpenTime, 0.0, 0.0 ) + //EmitSoundAtPosition( TEAM_UNASSIGNED, origin, sound ) + EmitSoundOnEntity( mover, "Timeshift_Scr_StalkerPodClose" ) + wait doorOpenTime + mover.NonPhysicsMoveTo( reverseOriginOffset, 0.5, 0.0, 0.0 ) + + } + +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function DoorControlPanelSpectreDisable( entity doorSwitch ) +{ + doorSwitch.s.enabled = false + doorSwitch.s.hintTrigger.kv.enabled = 0 +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function DoorControlPanelSpectreEnable( entity doorSwitch ) +{ + doorSwitch.s.enabled = true + doorSwitch.s.hintTrigger.kv.enabled = 1 +} + +///////////////////////////////////////////////////////////////////////////////////////// +bool function IsDoorHacking( playerSpectre ) +{ + if ( !( "doorHacking" in playerSpectre.s ) ) + return false + if ( playerSpectre.s.doorHacking ) + return true + return false +} + +///////////////////////////////////////////////////////////////////////////////////////// +/* +void function DoorControlPanelSpectreTryToHack( playerSpectre, entity doorSwitch ) +{ + playerSpectre.EndSignal( "OnDeath" ) + + if ( !( "doorHacking" in playerSpectre.s ) ) + playerSpectre.s.doorHacking <- null + playerSpectre.s.doorHacking = true + + //playerSpectre.SetTouchTriggers( false ) + playerSpectre.DisableBehavior( "Follow" ) + playerSpectre.EnableBehavior( "Assault" ) + playerSpectre.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_USE_SHOOTING_COVER ) + + var animEnt = doorSwitch.s.animEnt + animEnt.SetAngles( animEnt.GetAngles() + Vector( 0, 0, 0 ) ) + waitthread RunToAndPlayAnim( playerSpectre, "sp_casual_idle", animEnt.GetOrigin(), false, animEnt.GetAngles() ) + thread PlayAnim( playerSpectre, "sp_casual_idle", animEnt.GetOrigin(), animEnt.GetAngles() ) + + bool scanSuccess = true + waitthread DoorControlPanelSpectreScan( doorSwitch, scanSuccess ) + + //if we make it this far, unlock the door + doorSwitch.s.unlocked = true +} + +*/ + + +void function OnSpawnedTriggerQuickdeath( entity trigger ) +{ + + vector triggerOrigin = trigger.GetOrigin() + var timelinePositionTrigger = GetTimelinePosition( triggerOrigin ) + if ( timelinePositionTrigger == TIMEZONE_FROZEN ) + return + + var timelinePositionPlayer + trigger.EndSignal( "OnDestroy" ) + vector playerRespawnOrigin + var timelinePositionRespawnOrg + entity player + var result + + + while ( IsValid( trigger) ) + { + player = null + result = trigger.WaitSignal( "OnTrigger" ) + + if ( !IsValid( result.activator ) ) + continue + + if ( !result.activator.IsPlayer() ) + continue + + player = expect entity( result.activator ) + player.WaitSignal( "QuickDeathPlayerTeleported" ) + + timelinePositionPlayer = GetEntityTimelinePosition( player ) + playerRespawnOrigin = player.GetOrigin() + timelinePositionRespawnOrg = GetTimelinePosition( playerRespawnOrigin ) + + + // player respawn origin matches the timeZone he's supposed to be in + if ( timelinePositionRespawnOrg == level.timeZone ) + continue + + player.FreezeControlsOnServer() + if ( ( level.timeZone == TIMEZONE_NIGHT ) && ( timelinePositionRespawnOrg == TIMEZONE_DAY ) ) + { + player.SetOrigin( playerRespawnOrigin + Vector( 0, 0, TIME_ZOFFSET * -1 ) ) + } + else + { + player.SetOrigin( playerRespawnOrigin + Vector( 0, 0, TIME_ZOFFSET ) ) + } + player.UnfreezeControlsOnServer() + + + /* + if ( timelinePositionPlayer == timelinePositionTrigger ) + continue + + + if ( timelinePositionTrigger == TIMEZONE_DAY ) + player.SetOrigin( playerRespawnOrigin + Vector( 0, 0, TIME_ZOFFSET ) ) //player hit a pristine death trig but respawned in overgrown + else + player.SetOrigin( playerRespawnOrigin + Vector( 0, 0, ( TIME_ZOFFSET * -1 ) ) ) //player hit a overgrown death trig but respawned in pristine + */ + } + + + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function OnSpawnedTrigger( entity trigger ) +{ + local entName = trigger.GetTargetName() + string scriptName = trigger.GetScriptName() + + + //laser mesh + if ( scriptName == "trig_propspawner" ) + thread TriggerShowcaseSpawnInit( trigger ) + + + //spectre wall spawner + //if ( scriptName.find( "trig_spectre_wall_spawner" ) != null ) + //thread TriggerSpectreWallSpawnerThink( trigger ) + + //laser mesh + if ( scriptName == "laser_mesh" ) + thread TriggerPushbackDamageThink( trigger ) + + //laser mesh + if ( scriptName == "spinning_fan" ) + thread TriggerPushbackDamageThink( trigger ) + + //Dudes spawning in elevators + if ( scriptName == "trigger_elevator_npc" ) + thread trigger_elevator_npc_think( trigger ) + + /* + if ( scriptName == "trigger_time_hint" ) + thread TriggerTimehintThink( trigger ) + */ + + + //Spectre activated door panel triggers + if ( scriptName.find( "trigger_spectre_door_control" ) != null ) + file.spectreDoorTriggers.append( trigger ) + + + //HACK: need to switch over to script_name for these ones + + + //Spectre activated door panel triggers + if ( entName.find( "trigger_spectre_door_control" ) != null ) + file.spectreDoorTriggers.append( trigger ) + + + //Hazard triggers + if ( entName.find( "trigger_hazard" ) != null ) + thread TriggerHazardThink( trigger ) + + + + +} +///////////////////////////////////////////////////////////////////////////////////////// +void function trigger_elevator_npc_think( entity trigger ) +{ + trigger.EndSignal( "OnDestroy" ) + + vector soundOrigin + entity door + entity elevatorLightEntity + //entity button + string classname + string editorClassname + + array< entity > linkedEnts = trigger.GetLinkEntArray() + Assert( linkedEnts.len() > 0 ) + foreach( entity ent in linkedEnts ) + { + classname = ent.GetClassName() + editorClassname = GetEditorClass( ent ) + + if ( classname == "info_target" ) + { + elevatorLightEntity = ent + continue + } + if ( editorClassname == "script_door" ) + { + door = ent + continue + } + /*if ( editorClassname == "script_switch" ) + { + button = ent + continue + } + */ + } + Assert( IsValid( door ) ) + Assert( IsValid( elevatorLightEntity ) ) + //Assert( IsValid( button ) ) + + string flagToOpenDoor = expect string( door.kv.script_flag ) + Assert( flagToOpenDoor != "" ) + soundOrigin = elevatorLightEntity.GetOrigin() + //elevatorLightEntity.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT + + //--------------------------------------- + // Guys spawn inside elevator, bell dings + //--------------------------------------- + trigger.WaitSignal( "OnTrigger" ) + + EmitSoundAtPosition( TEAM_UNASSIGNED, soundOrigin, "Timeshift_ElevatorBell" ) + entity fxHandle = PlayFX( FX_GREEN_BLINKIE, elevatorLightEntity.GetOrigin() ) + + OnThreadEnd( + function() : ( fxHandle ) + { + if ( !IsValid( fxHandle) ) + return + fxHandle.Fire( "Stop" ) + fxHandle.Fire( "DestroyImmediately" ) + } + ) + + wait 1.25 + + //-------------- + // Door opens + //-------------- + FlagSet( flagToOpenDoor ) + + wait 5 +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function TriggerPushbackDamageThink( entity trigger ) +{ + trigger.EndSignal( "OnDestroy" ) + trigger.EndSignal( "PauseLasermesh" ) + + string scriptName = trigger.GetScriptName() + + vector triggerOrigin = trigger.GetOrigin() + vector soundOrigin = triggerOrigin + + array< entity > linkedEnts = trigger.GetLinkEntArray() + Assert( linkedEnts.len() > 0, "Laser mesh trigger at " + trigger.GetOrigin() + " not linked to anything" ) + entity centerEnt + entity blockerBrush + foreach( entity ent in linkedEnts ) + { + if ( ent.GetClassName() == "info_target" ) + centerEnt = ent + if ( ent.GetClassName() == "func_brush" ) + blockerBrush = ent + } + + Assert( IsValid( centerEnt ) ) + Assert( IsValid( blockerBrush ), "trigger at " + trigger.GetOrigin() + " does not link to a valid blockerbrush" ) + + //In case this was deactivated and we are re-activating it now + blockerBrush.Solid() + + + vector centerEntOrigin = centerEnt.GetOrigin() + + + //DebugDrawLine( topCorner, topCornerConnect, 255, 255, 0, true, 60.0 ) + //DebugDrawSphere( topCorner, 10.0, 255, 200, 0, true, 60.0 ) + //DebugDrawSphere( topCornerConnect, 10.0, 0, 255, 0, true, 60.0 ) + //DebugDrawSphere( botCorner, 10.0, 255, 200, 0, true, 60.0 ) + //DebugDrawBox( triggerOrigin, triggerMins, triggerMaxs, 255, 255, 0, 1, 60.0 ) + + string soundLoop + string soundDeactivate + string soundDamage + + int damageID + + switch ( scriptName ) + { + case "laser_mesh": + //soundLoop = SOUND_LASER_LOOP //doing this on the client + soundDamage = SOUND_LASER_DAMAGE + damageID = eDamageSourceId.lasergrid + break + case "spinning_fan": + soundDamage = SOUND_FAN_DAMAGE + damageID = eDamageSourceId.burn + break + } + + int statusEffectHandle + entity maxsCornerEnt + + //------------------- + // setup laser mesh + //------------------- + if ( scriptName == "laser_mesh" ) + { + // Get the trigger bounds + vector triggerMins = trigger.GetBoundingMins() + vector triggerMaxs = trigger.GetBoundingMaxs() + vector topCorner = PositionOffsetFromEnt( trigger, triggerMaxs.x, triggerMaxs.y, triggerMaxs.z ) + vector topCornerConnect = PositionOffsetFromEnt( trigger, triggerMins.x, triggerMins.y, triggerMaxs.z ) + vector botCorner = PositionOffsetFromEnt( trigger, triggerMaxs.x, triggerMaxs.y, triggerMins.z ) + + maxsCornerEnt = CreateEntity( "info_target" ) + maxsCornerEnt.SetOrigin( trigger.GetOrigin() + triggerMaxs ) + entity minsCornerEnt = CreateEntity( "info_target" ) + minsCornerEnt.SetOrigin( trigger.GetOrigin() + triggerMins ) + maxsCornerEnt.LinkToEnt( minsCornerEnt ) + + maxsCornerEnt.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT + minsCornerEnt.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT + maxsCornerEnt.EnableNetworkedEntityLinks() + DispatchSpawn( maxsCornerEnt ) + DispatchSpawn( minsCornerEnt ) + + statusEffectHandle = StatusEffect_AddEndless( maxsCornerEnt, eStatusEffect.laser_mesh, 1.0 ) + } + + + if ( soundLoop != "" ) + thread EmitSoundAtPositionHack( TEAM_UNASSIGNED, soundOrigin, soundLoop ) + + var scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN + entity player + var result + vector playerOriginXYonly + vector triggerOriginXYonly = Vector( centerEntOrigin.x, centerEntOrigin.y, 0 ) + vector vecToEnt + + //--------------------------- + // Cleanup when destroyed + //--------------------------- + OnThreadEnd( + function() : ( trigger, maxsCornerEnt, soundOrigin, blockerBrush, soundLoop, soundDeactivate, statusEffectHandle ) + { + //------------------------------------------------ + //Don't destroy blocker brush if we are just pausing + //------------------------------------------------ + if ( IsValid( trigger ) ) + { + blockerBrush.NotSolid() + blockerBrush.Hide() + blockerBrush.MakeInvisible() + } + else + blockerBrush.Destroy() + + if ( IsValid( maxsCornerEnt ) ) + StatusEffect_Stop( maxsCornerEnt, statusEffectHandle ) + + //-------------------------------------------------------------------------------- + // Stop and destroy all sounds and particles regardless of pausing or destroying + //-------------------------------------------------------------------------------- + if ( soundLoop != "" ) + StopSoundAtPosition( soundOrigin, soundLoop ) + + if ( soundDeactivate != "" ) + EmitSoundAtPosition( TEAM_UNASSIGNED, soundOrigin, soundDeactivate ) + } + ) + + + //--------------------------- + // Wait to do damage + //--------------------------- + while ( IsValid( trigger) ) + { + player = null + result = trigger.WaitSignal( "OnStartTouch" ) + + //if ( level.isTimeTraveling ) + // continue + + if ( !IsValid( result.activator ) ) + continue + + if ( result.activator.IsNPC() ) + { + thread LaserMeshDamageNPC( expect entity( result.activator ) ) + continue + } + if ( !result.activator.IsPlayer() ) + continue + + //Damage and pushback player + player = expect entity( result.activator ) + + if ( player.s.isTimeTraveling ) + continue + + player.TakeDamage( 50, svGlobal.worldspawn, svGlobal.worldspawn, { origin = player.GetOrigin(), scriptType = scriptTypeMask, damageSourceId = damageID } ) + CreateShakeRumbleOnly( player.GetOrigin(), 10, 105, 1 ) + + playerOriginXYonly = player.GetOrigin() + playerOriginXYonly = Vector( playerOriginXYonly.x, playerOriginXYonly.y, 0 ) + + vecToEnt = ( playerOriginXYonly - triggerOriginXYonly ) + vecToEnt = Normalize( vecToEnt ) + + printt( "vecToEnt: ", vecToEnt ) + player.SetVelocity( vecToEnt * 1000 ) + printt( "Velocity: ", vecToEnt * 1000 ) + + EmitSoundOnEntity( player, soundDamage ) + + + //DebugDrawSphere( playerOriginXYonly, 16, 255, 0, 0, true, 3 ) + //DebugDrawSphere( triggerOriginXYonly, 16, 255, 0, 0, true, 3 ) + + //SetVelocity( Normalize( triigerOrigin - playerOrigin ) * 1000 ) + + } + +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function LaserMeshDamageNPC( entity npc ) +{ + if ( !IsAlive( npc ) ) + return + + if ( npc.GetClassName() == "npc_marvin" ) + return + + if ( npc.GetClassName() == "npc_stalker" ) + return + + npc.Gib( < 0, 0, 100> ) +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function LaserMeshDeactivateByInstanceName( string instanceName ) +{ + array< entity > triggers = GetEntArrayByScriptNameInInstance( "laser_mesh", instanceName ) + foreach( trigger in triggers ) + { + if( IsValid( trigger ) ) + trigger.Signal( "PauseLasermesh" ) + + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function LaserMeshDestroyByInstanceName( string instanceName ) +{ + array< entity > triggers = GetEntArrayByScriptNameInInstance( "laser_mesh", instanceName ) + foreach( trigger in triggers ) + { + if( IsValid( trigger ) ) + trigger.Destroy() + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function LaserMeshActivateByInstanceName( string instanceName ) +{ + array< entity > lasermeshTriggers = GetEntArrayByScriptNameInInstance( "laser_mesh", instanceName ) + foreach( trigger in lasermeshTriggers ) + { + if( IsValid( trigger ) ) + thread TriggerPushbackDamageThink( trigger ) + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +var function CreateLaserMeshBeam( startPos, endPos ) +{ + entity cpoint = CreateEntity( "info_placement_helper" ) + cpoint.SetOrigin( endPos ) + SetTargetName( cpoint, UniqueString( "controlpoint" ) ) + DispatchSpawn( cpoint ) + + entity serverEffect = CreateEntity( "info_particle_system" ) + serverEffect.SetOrigin( startPos ) + serverEffect.SetValueForEffectNameKey( FX_LASER ) + serverEffect.kv.start_active = 1 + serverEffect.SetControlPointEnt( 1, cpoint ) + + DispatchSpawn( serverEffect ) + + return serverEffect +} + +///////////////////////////////////////////////////////////////////////////////////////// +/* +void function TriggerSpectreWallSpawnerThink( entity trigger ) +{ + trigger.EndSignal( "OnDeath" ) + + entity spectreSpawnDoor + array< entity > linkedEnts = trigger.GetLinkEntArray() + Assert( linkedEnts.len() > 0, "Trigger at " + trigger.GetOrigin() + " is not linked to any spectre spawn doors" ) + entity spectreRack + //entity spawner + + vector hackRackOrigin + + foreach( entity ent in linkedEnts ) + { + spectreSpawnDoor = ent + Assert( spectreSpawnDoor.GetClassName() == "prop_dynamic" ) + spectreRack = spectreSpawnDoor.GetLinkEnt() + Assert( IsValid( spectreRack ), "Spectre door at " + spectreSpawnDoor.GetOrigin() + " with angles: " + spectreSpawnDoor.GetAngles() + " needs to target a valid spectre rack." ) + //spawner = spectreRack.GetLinkEnt() + //Assert( IsValid( spawner ), "spectreRack at " + spectreRack.GetOrigin() + " needs to target a valid spectre spawner." ) + + //push racks back a bit so they don't clip thru doors...too difficult to do all in LevelEd...another reason we need instance_names! + hackRackOrigin = PositionOffsetFromEnt( spectreRack, -8, 0, 0 ) + spectreRack.SetOrigin( hackRackOrigin ) + + thread PropSpawnerThink( trigger, spectreSpawnDoor, spectreRack ) + } + +} +*/ + +///////////////////////////////////////////////////////////////////////////////////////// +int function GetPlayerSatchelCount( var player ) +{ + + return 0 + + /* + int numSatchels + + local weapon = player.GetOffhandWeapon( 0 ) + if ( !IsValid( weapon ) ) + + if ( weapon ) + local clipCount = player.GetWeaponAmmoMaxLoaded( weapon ) + + + return numSatchels + + */ +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function SwapTimelines( entity player, var timeZone ) +{ + //needs to be threaded off since need to do a + //WaitEndFrame()...player will take damage from random hazard triggers otherwise + thread SwapTimelinesThread( player, timeZone ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DeviceUsedInFrozenWorld( entity player ) +{ + const float EFFECT_DURATION_TOTAL = 0.5 + const float EFFECT_DURATION_EASE_OUT = 0.5 + StatusEffect_AddTimed( player, eStatusEffect.timeshift_visual_effect, 1.0, EFFECT_DURATION_TOTAL, EFFECT_DURATION_EASE_OUT ) + + //StopSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Past" ) + //EmitSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Present" ) + StopSoundOnEntity( player, "Timeshift_Scr_BrokenDeviceUse" ) + EmitSoundOnEntity( player, "Timeshift_Scr_BrokenDeviceUse" ) + + //entity timeShiftOffhand = player.GetFirstPersonProxy() + //timeShiftOffhand.SetSkin( 0 ) + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function SwapTimelinesThread( entity player, var timeZoneDestination ) +{ + if ( !CanTimeShift( player ) ) + return + + player.s.isTimeTraveling = true + + // no idea why respawn did this, but removing it doesn't seem to break anything + //entity player = GetPlayerArray()[0] + //if ( !player ) + // return + + player.ClearTraverse() + + player.EndSignal( "OnDeath" ) + + var skyCam + vector playerPos = player.GetOrigin() + vector newPlayerPos + int timeOffset + + player.SetCloakReactEndTime( Time() ) + + + if ( timeZoneDestination == TIMEZONE_NIGHT ) + { + //-------------------------- + // switch to night/overgrown + //-------------------------- + SetGlobalNetBool( "PlayerInOvergrownTimeline", true ) + skyCam = GetEnt( "skybox_cam_night" ) + + //level.timeZone = TIMEZONE_NIGHT + player.s.timeline = TIMEZONE_NIGHT + + timeOffset = TIME_ZOFFSET * -1 + + // make this not use global stuff + //FreezeNpcs( TIMEZONE_DAY ) + if ( GetPlayersInTimeline( TIMEZONE_DAY ).len() == 0 ) + FreezeNpcs( TIMEZONE_DAY ) + + player.Signal( "OnTimeFlippedTimezoneNight" ) + SetTimeshiftTimeOfDay_Night() + if ( Flag( "PlayerPickedUpTimeshiftDevice" ) ) + SetTimeshiftArmDeviceSkin( 1 ) + if ( Flag( "HidePlayerWeaponsDuringShifts") ) + player.EnableWeapon() + } + + else if ( timeZoneDestination == TIMEZONE_DAY ) + { + //------------------------ + //switch to day/pristine + //------------------------ + SetGlobalNetBool( "PlayerInOvergrownTimeline", false ) + skyCam = GetEnt( "skybox_cam_day" ) + + //level.timeZone = TIMEZONE_DAY + player.s.timeline = TIMEZONE_DAY + + timeOffset = TIME_ZOFFSET + FreezeNpcs( TIMEZONE_NIGHT ) + + // make this not use global stuff + //FreezeNpcs( TIMEZONE_NIGHT ) + if ( GetPlayersInTimeline( TIMEZONE_NIGHT ).len() == 0 ) + FreezeNpcs( TIMEZONE_NIGHT ) + + player.Signal( "OnTimeFlippedTimezoneDay" ) + + SetTimeshiftTimeOfDay_Day() + if ( Flag( "PlayerPickedUpTimeshiftDevice" ) ) + SetTimeshiftArmDeviceSkin( 0 ) + if ( Flag( "HidePlayerWeaponsDuringShifts") ) + player.DisableWeapon() + } + else + SetGlobalNetBool( "PlayerInOvergrownTimeline", false ) + + + SatchelManagement( player, timeZoneDestination ) + + if ( ( player.IsTitan() ) && ( !Flag( "PlayerHasTimeTraveledInsideBT" ) ) ) + { + FlagSet( "PlayerHasTimeTraveledInsideBT" ) + } + + TimeshiftUntrackLockedTargets( player ) + + vector positionOffset = Vector( 0, 0, timeOffset ); + newPlayerPos = playerPos + positionOffset; + + if ( PlayerPosInSolid( player, newPlayerPos ) ) + { + if ( timeZoneDestination == TIMEZONE_DAY ) + { + printt( "***WARNING: Destination TimeShiftPos " + newPlayerPos + " is in solid. Using file.lastGoodTimeshiftPosPristine instead" ) + //newPlayerPos = file.lastGoodTimeshiftPosPristine + // use player script var instead + // this won't compile if we don't manually do type safety stuff here + var pos = player.s.lastGoodTimeshiftPosPristine + newPlayerPos = expect vector( pos ) + } + else if ( timeZoneDestination == TIMEZONE_NIGHT ) + { + printt( "***WARNING: Destination TimeShiftPos " + newPlayerPos + " is in solid. Using file.lastGoodTimeshiftPosOvergrown instead" ) + //newPlayerPos = file.lastGoodTimeshiftPosOvergrown + // use player script var instead + // this won't compile if we don't manually do type safety stuff here + var pos = player.s.lastGoodTimeshiftPosOvergrown + newPlayerPos = expect vector( pos ) + } + } + else + { + //Not in solid, just use the offset + newPlayerPos = Vector( playerPos.x, playerPos.y, playerPos.z + timeOffset ) + } + + thread SwapTimelineEffectsAndSound( player, timeZoneDestination ) + + const float EFFECT_DURATION_TOTAL = 0.5 + const float EFFECT_DURATION_EASE_OUT = 0.5 + StatusEffect_AddTimed( player, eStatusEffect.timeshift_visual_effect, 1.0, EFFECT_DURATION_TOTAL, EFFECT_DURATION_EASE_OUT ) + + Remote_CallFunction_NonReplay( player, "ServerCallback_TimeFlipped", player.s.timeline ) + player.SetSkyCamera( skyCam ) + if ( !IsAlive( player ) ) + return + MakeInvincible( player ) + WaitEndFrame() //player will take damage from random hazard triggers otherwise + + if ( Flag( "DoingCinematicTimeshift" ) ) + player.SetAbsOrigin( newPlayerPos ) + else + player.SetOrigin( newPlayerPos ) + + EmitAISoundWithOwner( player, SOUND_PLAYER, 0, newPlayerPos, 150, 0.2 ) + + SetRapidShiftOffset( -positionOffset ); + ClearInvincible( player ) + + if ( timeZoneDestination == TIMEZONE_DAY ) + GruntChatter_TryEnemyTimeShifted( player ) + + ObjectiveCompensate( player, file.currentObjectiveEntity ) + player.s.isTimeTraveling = false + + //thread SonarColorCorrection( player, 0.5, null ) +} + + + +void function TimeshiftUntrackLockedTargets( entity player ) +{ + if ( !IsValid( player ) ) + return + + entity trackingWeapon + entity offhand + + if ( !player.IsTitan() ) + return + + + offhand = player.GetOffhandWeapon( OFFHAND_RIGHT ) + if ( !IsValid( offhand ) ) + return + if ( offhand.GetWeaponClassName() == "mp_titanweapon_tracker_rockets" ) + trackingWeapon = offhand + + + if ( !IsValid( trackingWeapon ) ) + return + + trackingWeapon.SmartAmmo_Clear( true, true ) + + /* + var allTargets = trackingWeapon.SmartAmmo_GetTargets() + foreach ( target in allTargets ) + { + if ( !IsValid( target.ent ) ) + continue + trackingWeapon.SmartAmmo_UntrackEntity( target.ent ) + + } + */ + + +} +///////////////////////////////////////////////////////////////////////////////////////////////////////// +void function PlayerIndorsStatus() +{ + while( GetPlayerArray().len() == 0 ) + wait 0.1 + + entity player = GetPlayerArray()[ 0 ] + Assert( IsValid( player ) ) + player.EndSignal( "OnDeath" ) + + + if ( GetMapName() == "sp_timeshift_spoke02" ) + { + Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerIndoorsChanged", 1 ) + return + } + + + Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerIndoorsChanged", 0 ) + + while( true ) + { + wait 0.1 + + FlagWait( "player_is_indoors" ) + Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerIndoorsChanged", 1 ) + + FlagWaitClear( "player_is_indoors" ) + Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerIndoorsChanged", 0 ) + + } +} + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +void function SatchelManagement( entity player, var timeZone ) +{ + if ( !IsValid( player ) ) + return + + array<entity> traps = GetScriptManagedEntArray( player.s.activeTrapArrayId ) + foreach ( index, satchel in traps ) + { + if ( !IsValid( satchel ) ) + continue + if ( GetEntityTimelinePosition( satchel ) == timeZone ) + DisableSatchel( satchel, false ) + else + DisableSatchel( satchel, true ) + } + +} +///////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DisableSatchel( entity satchel, bool isDisabled ) +{ + satchel.e.isDisabled = isDisabled +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +bool function CanTimeShift( entity player ) +{ + if ( Flag( "DoingCinematicTimeshift" ) ) + return true + + if ( player.GetParent() ) + { + printl( "Can't teleport...parented") + return false + } + + /* + if ( player.IsTraversing() ) + { + printl( "Can't teleport...traversing") + return false + } + */ + /* + if ( ( player.IsTitan() ) && ( level.titanCanTimeTravel == false ) ) + { + printl( "Can't teleport...Titan") + return false + } + */ + + if ( player.s.isTimeTraveling ) + return false + + return true +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +function OnTimeShiftAbilityUsed( player ) +{ + if ( level.allowTimeTravel == false ) + { + if ( Flag( "SwappedToFrozenWorld" ) ) + thread DeviceUsedInFrozenWorld( expect entity( player ) ) + return + + } + + //if ( level.timeZone == TIMEZONE_DAY ) + if ( player.s.timeline == TIMEZONE_DAY ) + SwapTimelines( expect entity( player ), TIMEZONE_NIGHT ) + else + SwapTimelines( expect entity( player ), TIMEZONE_DAY ) +} +///////////////////////////////////////////////////////////////////////////////////////////////////////// +function OnSatchelPlanted( player, collisionParams ) +{ + expect entity( player ) + if ( !IsValid( player ) ) + return + + expect table( collisionParams ) + + vector plantAngles = VectorToAngles( collisionParams.normal ) + vector plantPosition = expect vector( collisionParams.pos ) + + //thread SatchelHint( player ) + + //---------------------------------------------------------------- + //create a duplicate satchel in the present if planted in the past + //---------------------------------------------------------------- + if ( GetTimelinePosition( plantPosition ) == TIMEZONE_NIGHT ) + return + + if ( collisionParams.hitEnt != null ) //forget it if it's attached to an AI or anything moving + return + + //TO DO - plant a dupe satchel here + //bool result = PlantStickyEntity( weapon, collisionParams ) + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* +function SatchelHint( entity player ) +{ + player.EndSignal( "OnDeath" ) + player.Signal( "DisplayingSatchelHint" ) + player.EndSignal( "DisplayingSatchelHint" ) + + array<entity> traps + entity playerCurrentWeapon + string playerCurrentWeaponClassname + bool isDisplayingHint = false + + entity closestSatchel + entity offHandOrdinance + while ( true ) + { + wait 1.5 + + if ( isDisplayingHint == true ) + { + isDisplayingHint = false + ClearOnscreenHint( player ) + } + + offHandOrdinance = player.GetOffhandWeapon( 0 ) + if ( !offHandOrdinance ) + break + if ( offHandOrdinance.GetWeaponClassName() != "mp_weapon_satchel" ) + break + + traps = GetScriptManagedEntArray( player.s.activeTrapArrayId ) + if ( traps.len() < 1 ) + break + + playerCurrentWeapon = player.GetActiveWeapon() + + // No current weapon selected + if ( !IsValid( playerCurrentWeapon ) ) + continue + + playerCurrentWeaponClassname = playerCurrentWeapon.GetWeaponClassName() + + //Player is equipped with the clacker, clear message + if ( playerCurrentWeaponClassname == "mp_weapon_satchel" ) + continue + + closestSatchel = GetClosest( traps, player.GetOrigin() ) + if ( !IsValid( closestSatchel ) ) + continue + + if ( DistanceSqr( player.GetOrigin(), closestSatchel.GetOrigin() ) > ( 1024 * 1024 ) ) + continue + + + //player is not equipped with the clacker, display message + else + { + if ( file.isDisplayingTimeshiftHint) + continue + if ( file.isDisplayingDamageText ) + continue + + DisplayOnscreenHint( player, "satchel_hint_tap_twice", 3.0 ) + isDisplayingHint = true + continue + } + + } + + ClearOnscreenHint( player ) + + +} + +*/ + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool function PlayerHasSatchels( entity player ) +{ + if ( !IsValid( player ) ) + return false + + entity weapon = player.GetOffhandWeapon( 0 ) + if ( !IsValid( weapon ) ) + return false + + int satchelCount = player.GetWeaponAmmoMaxLoaded( weapon ) + if ( satchelCount == 0 ) + return false + + return true + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +function FreezeNpcs( timeZone ) +{ + array<entity> npcsToFreeze + array<entity> npcsToUnFreeze + ArrayRemoveDead( file.npcsPresent ) + ArrayRemoveDead( file.npcsPast ) + + if ( timeZone == TIMEZONE_NIGHT ) + { + npcsToFreeze = file.npcsPresent + npcsToUnFreeze = file.npcsPast + } + + + else if ( timeZone == TIMEZONE_DAY ) + { + npcsToFreeze = file.npcsPast + npcsToUnFreeze = file.npcsPresent + } + + + foreach( npc in npcsToFreeze ) + FreezeNPC( npc ) + + foreach( npc in npcsToUnFreeze ) + UnFreezeNPC( npc ) +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function FreezeNPC( entity npc ) +{ + if ( !IsValid( npc ) ) + return + + if ( IsFreezeProof( npc ) ) + return + + if ( IsFrozen( npc ) == true ) + return + + npc.Freeze() + npc.Signal( "Frozen" ) + + if ( !( "isFrozen" in npc.s ) ) + npc.s.isFrozen <- null + npc.s.isFrozen = true +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function UnFreezeNPC( entity npc ) +{ + if ( !IsValid( npc ) ) + return + + if ( IsFrozen( npc ) == false ) + return + + npc.Unfreeze() + npc.Signal( "UnFrozen" ) + if ( !( "isFrozen" in npc.s ) ) + npc.s.isFrozen <- null + npc.s.isFrozen = false +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function IsFrozen( npc ) +{ + if ( !( "isFrozen" in npc.s ) ) + return false + + return npc.s.isFrozen +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool function IsFreezeProof( entity npc ) +{ + if ( !IsValid( npc ) ) + return false + if ( "dontAllowFreeze" in npc.s ) + return true + + return false +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DontAllowFreeze( entity npc, bool state ) +{ + if ( !IsValid( npc ) ) + return + + if ( state == true ) + { + if ( !( "dontAllowFreeze" in npc.s ) ) + npc.s.dontAllowFreeze <- null + npc.s.dontAllowFreeze = true + } + else + { + if ( "dontAllowFreeze" in npc.s ) + delete npc.s.dontAllowFreeze + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function OnSpawnedNPC( entity npc ) +{ + string classname = npc.GetClassName() + string editorClassname = GetEditorClass( npc ) + + if ( editorClassname == "npc_marvin_drone" ) + return + + var timeZone = GetEntityTimelinePosition( npc ) + expect int(timeZone) + + int difficulty = GetSpDifficulty() + + + if ( timeZone == TIMEZONE_NIGHT ) + file.npcsPresent.append( npc ) + else if ( timeZone == TIMEZONE_DAY ) + file.npcsPast.append( npc ) + else + { + //Frozen world - do nothing + } + + //If I just spawned in the opposite time zone as the player, freeze me + //if ( ( level.timeZone != timeZone ) && ( timeZone != TIMEZONE_FROZEN ) ) + if ( GetPlayersInTimeline( timeZone ).len() == 0 && timeZone != TIMEZONE_FROZEN ) + FreezeNPC( npc ) + + if ( timeZone == TIMEZONE_FROZEN ) + return + + //Do blue afterglow if we have the device now + if ( level.allowTimeTravel ) + thread TimeshiftAfterglowThink( npc, timeZone ) + + + + + int maxHealth = npc.GetMaxHealth() + switch( classname ) + { + case "npc_marvin": + if ( timeZone == TIMEZONE_NIGHT ) + { + npc.SetModel( MARVIN_MODEL_OVERGROWN ) + npc.SetSkin( 1 ) //mossy + } + break + case "npc_stalker": + case "npc_stalker_zombie": + case "npc_stalker_zombie_mossy": + case "npc_stalker_crawling_mossy": + thread StalkerThink( npc, classname ) + + break + case "npc_drone": + break + case "npc_turret_sentry": + if ( difficulty < DIFFICULTY_MASTER ) + npc.kv.AccuracyMultiplier = 4 + else + npc.kv.AccuracyMultiplier = 3 + break + case "npc_titan": + thread TitanEnemyThink( npc ) + break + } + + string scriptName = npc.GetScriptName() + //-------------------------- + // Civilians + //-------------------------- + if ( scriptName.find( "civilian_walker_" ) != null ) + thread CivilianWalkerThink( npc ) + if ( scriptName.find( "civilian_actor_" ) != null ) + thread CivilianActorThink( npc ) +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TitanEnemyThink( entity npc ) +{ + if ( npc.GetTeam() != TEAM_IMC ) + return + + //npc.WaitSignal( "WeakTitanHealthInitialized" ) + //DeregisterBossTitan( npc ) + //npc.SetHealth( 100 ) + + var timeZone = GetEntityTimelinePosition( npc ) + + if ( timeZone == TIMEZONE_DAY ) + thread TitanEnemyThinkPristine( npc ) + else + thread TitanEnemyThinkOvergrown( npc ) +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TitanEnemyThinkOvergrown( npc ) +{ + npc.WaitSignal( "WeakTitanHealthInitialized" ) + npc.TakeDamage( npc.GetMaxHealth()/2, null, null, { damageSourceId=damagedef_suicide } ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TitanEnemyThinkPristine( npc ) +{ + npc.WaitSignal( "WeakTitanHealthInitialized" ) + printt( "Titan health: ", npc.GetHealth() ) + npc.SetMaxHealth( 10000 ) + npc.SetHealth( 10000 ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function StalkerThink( entity npc, string classname ) +{ + npc.SetSkin( 1 ) //mossy + TakeAllWeapons( npc ) + + if ( ( GetMapName() == "sp_hub_timeshift" ) && ( !Flag( "player_back_in_amenities_lobby") ) ) + return + + if ( ( GetMapName() == "sp_timeshift_spoke02" ) && ( !Flag( "StartAndersonHologram1" ) ) ) + return + + npc.GiveWeapon( "mp_weapon_mgl" ) + + npc.EndSignal( "OnDeath" ) + while( true ) + { + npc.kv.allowshoot = 0 + wait RandomFloatRange( 4, 6 ) + npc.kv.allowshoot = 1 + wait RandomFloatRange( 3, 5 ) + } +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TimeshiftAfterglowThink( entity npc, var timeZone ) +{ + + if( !IsValid( npc ) ) + return + + npc.EndSignal( "OnDestroy" ) + npc.EndSignal( "OnDeath" ) + + //don't do glows for friendlies + int team = npc.GetTeam() + if ( team == TEAM_MILITIA ) + return + + if ( team == TEAM_UNASSIGNED ) + return + + //don't do glow for certain scripted things + string scriptName = npc.GetScriptName() + if ( ( IsValid( scriptName ) ) && ( scriptName == "lab_prowlers" ) ) + return + + if ( ( IsValid( scriptName ) ) && ( scriptName == "bio_pod_body" ) ) + return + + + + string classname = npc.GetClassName() + string glowSound = "Timeshift_Scr_TimeResidue_Generic" + int zOffset + vector fxAfterglowAdditionalOffset + + switch( classname ) + { + case "npc_marvin": + return + case "npc_soldier": + glowSound = "Timeshift_Scr_TimeResidue_Grunt" + fxAfterglowAdditionalOffset = Vector( 0, 0, 32 ) + break + case "npc_spectre": + case "npc_stalker_zombie": + case "npc_stalker_zombie_mossy": + case "npc_stalker_crawling_mossy": + fxAfterglowAdditionalOffset = Vector( 0, 0, 32 ) + glowSound = "Timeshift_Scr_TimeResidue_ZombieStalker" + break + case "npc_stalker": + glowSound = "Timeshift_Scr_TimeResidue_Stalker" + fxAfterglowAdditionalOffset = Vector( 0, 0, 32 ) + break + case "npc_prowler": + glowSound = "Timeshift_Scr_TimeResidue_Prowler" + fxAfterglowAdditionalOffset = Vector( 0, 0, 32 ) + break + case "npc_drone": + glowSound = "Timeshift_Scr_TimeResidue_Drone" + fxAfterglowAdditionalOffset = Vector( 0, 0, 0 ) + break + case "npc_titan": + fxAfterglowAdditionalOffset = Vector( 0, 0, 80 ) + break + case "npc_super_spectre": + fxAfterglowAdditionalOffset = Vector( 0, 0, 64 ) + break + case "npc_frag_drone": + fxAfterglowAdditionalOffset = Vector( 0, 0, 32 ) + break + case "npc_turret_sentry": + fxAfterglowAdditionalOffset = Vector( 0, 0, 16 ) + break + default: + Assert( 0, "Unhandled npc: " + classname ) + break + } + + + if ( GetEditorClass( npc ) == "npc_specialist_imc" ) + glowSound = "Timeshift_Scr_TimeResidue_Generic" + + if ( timeZone == TIMEZONE_NIGHT ) + zOffset = TIME_ZOFFSET + else + zOffset = TIME_ZOFFSET * -1 + + vector fxAfterglowPos + vector fxAfterglowAng + + entity fx + while( true ) + { + npc.WaitSignal( "Frozen" ) + fxAfterglowPos = npc.GetOrigin() + fxAfterglowPos = fxAfterglowPos + fxAfterglowAdditionalOffset + fxAfterglowPos = Vector( fxAfterglowPos.x, fxAfterglowPos.y, fxAfterglowPos.z + zOffset ) + fxAfterglowAng = npc.GetAngles() + fx = PlayFX( FX_TIMESHIFT_ENTITY_MARKER, fxAfterglowPos, fxAfterglowAng ) + if ( glowSound != "" ) + EmitSoundAtPosition( TEAM_UNASSIGNED, fxAfterglowPos, glowSound ) + + OnThreadEnd( + function() : ( fx, glowSound, fxAfterglowPos ) + { + DestroyFxIfValid( fx ) + if ( glowSound != "" ) + StopSoundAtPosition( fxAfterglowPos, glowSound ) + } + ) + + npc.WaitSignal( "UnFrozen" ) + DestroyFxIfValid( fx ) + if ( glowSound != "" ) + StopSoundAtPosition( fxAfterglowPos, glowSound ) + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DestroyFxIfValid( entity fx ) +{ + if ( !IsValid( fx) ) + return + fx.Fire( "Stop" ) + fx.Fire( "DestroyImmediately" ) + fx.Destroy() +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TS_OnDeathNPC( entity npc, var damageInfo ) +{ + thread TS_OnDeathNPCThread( npc, damageInfo ) +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TS_OnDeathNPCThread( entity npc, var damageInfo ) +{ + if ( !IsValid( npc ) ) + return + + string editorClassname = GetEditorClass( npc ) + + if ( GetEntityTimelinePosition( npc ) == TIMEZONE_NIGHT ) + { + if ( editorClassname == "npc_marvin" ) + npc.SetSkin( 1 ) //bug where Marvins revert to base skin when killed + + //TryTimeshiftGibDeath( npc, damageInfo ) + + //get out of here because we don't care about npcs in the present + return + } + + int damageType = DamageInfo_GetCustomDamageType( damageInfo ) + + if ( !damageType ) + return + + printt( "explosive: " + ( damageType & DF_EXPLOSION ) ) + printt( "bullet: " + ( damageType & DF_BULLET ) ) + printt( "gib: " + ( damageType & DF_GIB ) ) + + if ( damageType & DF_GIB ) + { + return + } + + //if ( TryTimeshiftGibDeath( npc, damageInfo ) ) + //return + + + //vector deathPos = npc.GetOrigin() + vector deathPos = npc.GetOrigin() + Vector( 0, 0, ( TIME_ZOFFSET * -1 ) ) + //vector deathPos = npc.GetOrigin() + + + + + asset deathModel + entity corpseOrg + bool usesSingleDeathModel = true + int additionalZoffset = 0 + if ( editorClassname == "" ) + editorClassname = npc.GetClassName() + + switch( editorClassname ) + { + case "npc_prowler": + break + case "npc_marvin": + deathModel = MARVIN_MODEL_OVERGROWN + break + case "npc_stalker": + corpseOrg = file.stalkerCorpseOrg + usesSingleDeathModel = false + additionalZoffset = 16 + break + case "npc_titan_ogre_minigun": + case "npc_titan": + corpseOrg = file.titanCorpseOrg + usesSingleDeathModel = false + additionalZoffset = 64 + break + case "npc_soldier_imc_shotgun": + deathModel = IMC_CORPSE_MODEL_SHOTGUN + break + case "npc_soldier_imc_rifle": + case "npc_soldier": + deathModel = IMC_CORPSE_MODEL_RIFLE + break + case "npc_shield_captain_imc": + deathModel = IMC_CORPSE_MODEL_HEAVY + break + case "npc_soldier_imc_smg": + deathModel = IMC_CORPSE_MODEL_SMG + break + } + + + /* + int xAngle = 90 + if ( CoinFlip() ) + xAngle = -90 + vector deathAng = Vector( xAngle, RandomFloatRange( 0, 360 ), RandomFloatRange( 0, 360 ) ) + */ + + //randomly spin the body/gib collection on it's y axis + vector deathAng = Vector( 0, RandomFloatRange( 0, 360 ), 0 ) + + if ( ( deathModel == $"" ) && ( usesSingleDeathModel ) ) + { + printt( "Unhandled corpse type: '" + editorClassname + "'") + return + } + + + array<entity> corpseModels + + if ( usesSingleDeathModel ) + { + entity corpse = CreatePropDynamic( deathModel, deathPos + Vector( 0, 0, 5), deathAng, 6 ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only + corpse.Hide() + if ( deathModel == MARVIN_MODEL_OVERGROWN ) + corpse.SetSkin( 1 ) //mossy + corpseModels.append( corpse ) + string anim = GetrandomDeathPose( deathModel ) + vector newOrg = HackGetDeltaToRef( corpse.GetOrigin(), corpse.GetAngles(), corpse, anim ) + thread PlayAnimTeleport( corpse, anim, newOrg, corpse.GetAngles() ) + } + else + { + + corpseOrg.SetOrigin( deathPos ) + corpseOrg.SetAngles( deathAng ) + array<entity> bodyPartArray = file.titanCorpsePieces + if ( editorClassname == "npc_stalker" ) + bodyPartArray = file.stalkerCorpsePieces + + foreach( bodypart in bodyPartArray ) + { + asset modelName = bodypart.GetModelName() + vector angles = bodypart.GetAngles() + vector origin = bodypart.GetOrigin() + entity clonedModel = CreatePropPhysics( modelName, origin + Vector( 0, 0, 20 + additionalZoffset ), angles ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only + clonedModel.Hide() + corpseModels.append( clonedModel ) + } + + + } + + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT ) + + foreach( corpse in corpseModels ) + { + corpse.Show() + + if ( usesSingleDeathModel ) + corpse.BecomeRagdoll( Vector( 0,0,0 ), false ) + } + +} + +/* + array<string> idles = [ "pt_S2S_crew_A_idle", + "pt_S2S_crew_B_idle", + "pt_S2S_crew_C_idle", + "pt_S2S_crew_D_idle" ] +*/ + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//HACK: need a better global way to spawn marvins using levelEd ents +void function OnSpawnedMarvinSpawner( entity spawner ) +{ + SpawnMarvin( spawner ) +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool function TryTimeshiftGibDeath( entity npc, var damageInfo ) +{ + if ( !IsValid( npc ) ) + return false + + int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo ) + if ( !IsValid( damageSourceId ) ) + return false + if ( damageSourceId != eDamageSourceId.mp_weapon_satchel ) + return false + + + //string editorClassname = GetEditorClass( npc ) + string classname = npc.GetClassName() + + if ( !IsValidGibTarget( classname ) ) + return false + + + vector npcOrigin = npc.GetOrigin() + vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo ) + + if ( !IsValid( npcOrigin ) ) + return false + + if ( !IsValid( damageOrigin ) ) + return false + + float minDist = 100 + if ( Distance( npcOrigin, damageOrigin ) > minDist ) + return false + + EmitSoundAtPosition( TEAM_ANY, npcOrigin, "death.pinkmist" ) + npc.Dissolve( ENTITY_DISSOLVE_PINKMIST, Vector( 0, 0, 0 ), 500 ) + + return true + +} + +bool function IsValidGibTarget( string classname ) +{ + if ( classname == "npc_prowler" ) + return true + + if ( classname == "npc_soldier" ) + return true + + return false + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//HACK: need a better global way to spawn marvins using levelEd ents +function SpawnMarvin( spawner ) +{ + local origin = spawner.GetOrigin() + local angles = spawner.GetAngles() + entity npc_marvin = CreateEntity( "npc_marvin" ) + SetTargetName( npc_marvin, UniqueString( "mp_random_marvin") ) + npc_marvin.SetOrigin( origin ) + npc_marvin.SetAngles( angles ) + //npc_marvin.kv.rendercolor = "255 255 255" + npc_marvin.kv.health = -1 + npc_marvin.kv.max_health = -1 + npc_marvin.kv.spawnflags = 516 // Fall to ground, Fade Corpse + //npc_marvin.kv.FieldOfView = 0.5 + //npc_marvin.kv.FieldOfViewAlert = 0.2 + npc_marvin.kv.AccuracyMultiplier = 1.0 + npc_marvin.kv.physdamagescale = 1.0 + npc_marvin.kv.WeaponProficiency = eWeaponProficiency.GOOD + + npc_marvin.s.bodytype <- MARVIN_TYPE_WORKER + npc_marvin.SetValueForModelKey( $"models/robots/marvin/marvin.mdl" ) + + DispatchSpawn( npc_marvin ) + + SetTeam( npc_marvin, TEAM_UNASSIGNED ) + + return npc_marvin +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool function PlayerInRange( vector pos1, vector pos2, float minRange ) +{ + + if ( Distance( pos1 , pos2 ) > minRange ) + return false + + return true + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +entity function GetSpectreDoorSwitchByDummyName( string dummyName ) +{ + entity dummy = GetEntByScriptName( dummyName ) + entity doorSwitch = GetClosest( file.spectreDoorPanels, dummy.GetOrigin() ) + + Assert( IsValid( doorSwitch ) ) + Assert( Distance( doorSwitch.GetOrigin(), dummy.GetOrigin() ) < 50, "No doorSwitch within 50 units of dummy: " + dummyName ) + + return doorSwitch +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/* +void function TimeVortexThink( entity trigger ) +{ + trigger.EndSignal( "OnDestroy" ) + + local result + entity player + entity fxEnt = trigger.GetLinkEnt() + Assert( IsValid( fxEnt ), "Time vortex trig at " + trigger.GetOrigin() + " isn't linked to anything") + + var destinationTimeline = GetOppositeTimeline( GetEntityTimelinePosition( fxEnt ) ) + + //---------------- + // Play looping fx + //---------------- + entity FX = CreateEntity( "info_particle_system" ) + FX.SetValueForEffectNameKey( FX_TIME_PORTAL ) + FX.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE + FX.kv.start_active = 1 + FX.SetOrigin( fxEnt.GetOrigin() ) + FX.SetAngles( fxEnt.GetAngles() + Vector( 90, 0, 0 ) ) + DispatchSpawn( FX ) + EmitSoundAtPosition( TEAM_UNASSIGNED, fxEnt.GetOrigin(), SOUND_TIME_PORTAL_LOOP ) + + + OnThreadEnd( + function() : ( FX, fxEnt ) + { + StopSoundAtPosition( fxEnt.GetOrigin(), SOUND_TIME_PORTAL_LOOP ) + DestroyFxIfValid( FX ) + fxEnt.Destroy() + } + ) + + + //---------------- + // Think... + //---------------- + while( 1 ) + { + player = null + wait 0.1 + result = trigger.WaitSignal( "OnTrigger" ) + + if ( !IsValid( result.activator ) ) + continue + if ( result.activator.IsTitan() ) + continue + if ( !result.activator.IsPlayer() ) + continue + + player = expect entity( result.activator ) + player.EndSignal( "OnDeath" ) + thread TimeVortexSoundThread( player ) + + + SwapTimelines( player, destinationTimeline ) + + //thread TimeShiftSwapEffect( player ) + wait 4 + + EmitSoundOnEntity( player, "Pilot_Time_Vortex_Deactivate" ) + + wait 1 + float fadeTime = 0.3 + float holdTime = 0 + //(player, r, g, b, a, fadeTime, fadeHold, FFADE_ flags) + wait 0.75 + ScreenFade( player, 255, 255, 255, 254, fadeTime, holdTime, FFADE_OUT | FFADE_PURGE ) + wait 0.25 + + //HACK: can't time shift if player in the middle of hacking a panel + while ( !CanTimeShift( player ) ) + wait 0.1 + + StopSoundOnEntity( player, "Pilot_Time_Vortex_Loop" ) + SwapTimelines( player, GetOppositeTimeline( destinationTimeline ) ) + wait 1 + } + +} + +*/ + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +/* +void function TimeVortexSoundThread( player ) +{ + player.EndSignal( "OnDeath" ) + + //need to delay 0.1 otherwise won't hear the sound when he is teleported + wait 0.1 + EmitSoundOnEntity( player, "Pilot_Time_Vortex_Activate" ) + EmitSoundOnEntity( player, "Pilot_Time_Vortex_Loop" ) + +} + +*/ + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +void function SwapTimelineEffectsAndSound( entity player, timeZone ) +{ + + + + + //--------------------------------- + // Player has wrist mounted device + //--------------------------------- + if ( Flag( "PlayerPickedUpTimeshiftDevice" ) ) + { + //( amplitude frequency duration + CreateAirShake( player.GetOrigin(), 10, 50, 0.2, 10000 ) + + if ( player.IsTitan() ) + { + float fadeTime = 0.1 + float holdTime = 0.03 + ScreenFade( player, 255, 255, 255, 254, fadeTime, holdTime, FFADE_IN | FFADE_PURGE ) + CreateAirShake( player.GetOrigin(), 10, 50, 1, 20000 ) + } + + + if ( timeZone == TIMEZONE_NIGHT ) + { + //-------------------------- + // switch to night/overgrown + //-------------------------- + StopSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Past" ) + EmitSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Present" ) + } + else + { + //-------------------------- + // switch to day/pristine + //-------------------------- + StopSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Present" ) + EmitSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Past" ) + + } + + } + //--------------------------------- + // Scripted timeshift, no device + //--------------------------------- + else + { + float fadeTime = 0.1 + float holdTime = 0.05 + ScreenFade( player, 255, 255, 255, 254, fadeTime, holdTime, FFADE_IN | FFADE_PURGE ) + + //-------------- + // FX + //-------------- + EmitSoundOnEntity( player, "Timeshift_Scr_InvoluntaryShift" ) + } + + + + + + //----------------------------------- + // Impact table on ground + //----------------------------------- + TraceResults results = TraceLine( player.GetOrigin() + Vector( 0, 0, 32 ), player.GetOrigin() + Vector( 0, 0, -200 ), [ player ], TRACE_MASK_NPCSOLID_BRUSHONLY | TRACE_MASK_WATER, TRACE_COLLISION_GROUP_NONE ) + if ( !results.startSolid && !results.allSolid ) + PlayImpactFXTable( GetPosInOtherTimeline( results.endPos ), player, FX_IMPACT_TABLE_TIMESHIFT ) + + //----------------------------------- + // FX for viewmodel device + //----------------------------------- + if ( !Flag( "PlayerPickedUpTimeshiftDevice" ) ) + return + + entity timeShiftOffhand + + + + //------------------------------------- + // Skin change for cinematic device equip only + //--------------------------------------- + if ( !Flag( "DoingCinematicTimeshift" ) ) + return + + timeShiftOffhand = player.GetFirstPersonProxy() + + if ( !IsValid( timeShiftOffhand) ) + return + + if ( timeZone == TIMEZONE_DAY ) + { + timeShiftOffhand.SetSkin( 0 ) //orange 0 + } + + else if ( timeZone == TIMEZONE_NIGHT ) + { + timeShiftOffhand.SetSkin( 1 ) //blue 1 + } + + + //{ "hero_mil_jack_gauntlet_timeshift_skn_01"} // present amber + //{ "hero_mil_jack_gauntlet_timeshift_skn_01_v1"} // past blue + //{ "hero_mil_jack_gauntlet_timeshift_skn_01_v2"} // error red + //{ "hero_mil_jack_gauntlet_timeshift_skn_01_v3"} // off + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +var function GetOppositeTimeline( timeline ) +{ + var destinationTimeline + if ( timeline == TIMEZONE_NIGHT ) + destinationTimeline = TIMEZONE_DAY + else + destinationTimeline = TIMEZONE_NIGHT + + return destinationTimeline + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////// +var function GetEntityTimelinePosition( ent ) +{ + //What time period does this ent live in? + local z = ent.GetOrigin().z + if ( z < -6000 ) + return TIMEZONE_FROZEN + else if ( z < 5300 ) + return TIMEZONE_NIGHT + else + return TIMEZONE_DAY +} +/////////////////////////////////////////////////////////////////////////////////////////////////////// +var function GetTimelinePosition( vector pos ) +{ + //What time period does this pos live in? + local z = pos.z + if ( z < -6000 ) + return TIMEZONE_FROZEN + else if ( z < 5300 ) + return TIMEZONE_NIGHT + else + return TIMEZONE_DAY +} +/////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DebugDrawOnEnt( ent ) +{ + expect entity( ent ) + + while ( true ) + { + DebugDrawSphere( ent.GetOrigin(), 16, 255, 255, 255, true, 0.22 ) + wait 0.2 + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +void function WaitTillPlayerTouchesTrigger( trigger ) +{ + local result + while ( 1 ) + { + result = trigger.WaitSignal( "OnStartTouch" ) + + if ( !IsValid( result.activator ) ) + continue + if ( !result.activator.IsPlayer() ) + continue + break + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function SetFlagWhenBreakablesDestroyed( string flagToSet, string scriptName, string instanceName ) +{ + array < entity > breakableFuncBrushes = GetEntArrayByScriptNameInInstance( scriptName, instanceName ) + foreach ( funcBrush in breakableFuncBrushes ) + { + Assert( !( "flagToSetWhenDestroyed" in funcBrush.s ), "func brush '" + scriptName + "' instanceName '" + instanceName + "' has flag: " + flagToSet) + funcBrush.s.flagToSetWhenDestroyed <- flagToSet + } + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function OnDamagedFuncBrush( entity funcBrush, var damageInfo ) +{ + + //---------------------- + // Return if not player + //---------------------- + int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo ) + entity attacker = DamageInfo_GetAttacker( damageInfo ) + float damageAmount = DamageInfo_GetDamage( damageInfo ) + + if ( !damageSourceID ) + return + + if ( !damageAmount ) + return + + if ( !attacker ) + return + + if ( !attacker.IsPlayer() ) + return + + //----------------------------------- + // Do damage if correct damage type + //----------------------------------- + var health = funcBrush.s.health + var breakableType = funcBrush.s.breakableType + switch( breakableType ) + { + case BREAKABLE_TYPE_AQUARIUM: + case BREAKABLE_TYPE_SATCHEL_DEBRIS_MEDIUM_WIDE: + case BREAKABLE_TYPE_SATCHEL_DEBRIS_MEDIUM: + DamageSatchelDebris( funcBrush, damageSourceID, damageAmount ) + break + default: + Assert( 0, "Unhandled breakableType ' " + breakableType + "' at " + funcBrush.GetOrigin() ) + } + + + if ( funcBrush.s.health > 0 ) + return + + DestroyFuncBrushBreakable( funcBrush ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DestroyFuncBrushBreakable( funcBrush ) +{ + expect entity( funcBrush ) + + if ( !IsValid( funcBrush ) ) + return + + //---------------------- + // Debris blows up + //---------------------- + Signal( funcBrush, "BreakableDestroyed" ) + + entity hintTrigger + if ( ( "hintTrigger" in funcBrush.s ) ) + hintTrigger = expect entity( funcBrush.s.hintTrigger ) + + entity fxEnt = expect entity( funcBrush.s.fxEnt ) + asset explosionFx = expect asset( funcBrush.s.fxExplode ) + var explosionSound = funcBrush.s.soundExplode + vector origin = fxEnt.GetOrigin() + vector angles = fxEnt.GetAngles() + + PlayFX( explosionFx, origin, angles ) + EmitSoundAtPosition( TEAM_UNASSIGNED, origin, explosionSound ) + + //-------------------------------------- + // Swap out damaged/intact if applicable + //------------------------------------- + if ( ( "brokenState" in funcBrush.s ) && ( funcBrush.s.brokenState != null ) ) + funcBrush.s.brokenState.Show() + + //-------------------------------------- + // Delete intact version + //------------------------------------- + funcBrush.Destroy() + + if ( "flagToSetWhenDestroyed" in funcBrush.s ) + { + FlagSet( expect string( funcBrush.s.flagToSetWhenDestroyed ) ) + } + + //---------------------- + // Cleanup + //---------------------- + if ( IsValid( hintTrigger ) ) + hintTrigger.Destroy() + fxEnt.Destroy() +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DamageSatchelDebris( funcBrush, damageSourceID, damageAmount ) +{ + if ( damageSourceID != eDamageSourceId.mp_weapon_satchel ) + return + funcBrush.s.health = funcBrush.s.health - damageAmount +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TriggerHazardThink( entity trigger ) +{ + trigger.EndSignal( "OnDeath" ) + trigger.EndSignal( "OnDestroy" ) + + int damageAmt + float interval + string scriptName = trigger.GetScriptName() + asset fx + var sound + local scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN + + if ( scriptName.find( "fireSmall" ) != null ) + scriptName = "fireSmall" + + if ( scriptName.find( "fireHuge" ) != null ) + scriptName = "fireHuge" + + if ( scriptName.find( "fireMedium" ) != null ) + scriptName = "fireMedium" + + if ( scriptName.find( "radiation" ) != null ) + scriptName = "radiation" + + string soundDamage = "" + int damageID = eDamageSourceId.burn + + switch( scriptName ) + { + case "electricity": + fx = FX_ELECTRICITY + sound = SOUND_ELECTRICITY + scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN + damageAmt = 25 + interval = 0.75 + soundDamage = "flesh_electrical_damage_1p" + damageID = eDamageSourceId.electric_conduit + break + case "fireMedium": + fx = FX_FIRE_MEDIUM + sound = SOUND_FIRE_MEDIUM + scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN + damageAmt = 25 + interval = 1 + soundDamage = "flesh_fire_damage_1p" + damageID = eDamageSourceId.burn + break + case "fireSmall": + fx = FX_FIRE_SMALL + sound = SOUND_FIRE_MEDIUM + scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN + damageAmt = 25 + interval = 1 + soundDamage = "flesh_fire_damage_1p" + damageID = eDamageSourceId.burn + break + case "fireHuge": + fx = FX_FIRE_HUGE + sound = SOUND_FIRE_HUGE + scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN + damageAmt = 25 + interval = 1 + soundDamage = "flesh_fire_damage_1p" + damageID = eDamageSourceId.burn + break + case "radiation": + case "trigger_concourse_radiation": + fx = FX_RADIATION + scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN + damageAmt = 100 + interval = 1 + soundDamage = "flesh_fire_damage_1p" + damageID = eDamageSourceId.burn + break + default: + Assert( 0, "Unhandled hazard type ' " + scriptName + "' at " + trigger.GetOrigin() ) + } + + array< entity > linkedEnts = trigger.GetLinkEntArray() + array< var > fxHandles + //Assert( linkedEnts.len() > 0, "Hazard trigger at " + trigger.GetOrigin() + " has no linked ents to play fx on" ) + + //------------------- + // Hazard sound/FX + //-------------------- + foreach( ent in linkedEnts ) + thread HackPlayLoopEffectOnEntity( fx, ent ) + + if ( sound ) + thread EmitSoundAtPositionHack( TEAM_UNASSIGNED, trigger.GetOrigin(), sound ) + + OnThreadEnd( + function() : ( trigger, linkedEnts ) + { + if ( IsValid( trigger ) ) + trigger.Destroy() + foreach( ent in linkedEnts ) + { + if ( IsValid( ent ) ) + ent.Destroy() + } + } + ) + //------------------- + // Damage player + //-------------------- + + var player + local result + vector origin + while ( IsValid( trigger) ) + { + + wait 0.1 + + player = null + result = trigger.WaitSignal( "OnTrigger" ) + + //if ( level.isTimeTraveling ) + // continue + + if ( !IsValid( result.activator ) ) + continue + if ( result.activator.IsPlayer() ) + { + player = result.activator + + if ( player.s.isTimeTraveling ) + continue + + while ( trigger.IsTouching( player ) ) + { + player.TakeDamage( damageAmt, svGlobal.worldspawn, svGlobal.worldspawn, { origin = player.GetOrigin(), scriptType = scriptTypeMask, damageSourceId = damageID } ) + CreateShakeRumbleOnly( expect entity( player ).GetOrigin(), 10, 105, interval ) + if ( soundDamage != "" ) + EmitSoundOnEntity( player, soundDamage ) + printt( "Player taking damage: " + damageAmt ) + wait interval + } + } + } + +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function EmitSoundAtPositionHack( int team, vector origin, var sound ) +{ + //Need to delay 0.2 before playing at level start, Baker/Barb know this is a big but said will fix next game + wait 0.2 + EmitSoundAtPosition( team, origin, sound ) + +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// HACK - need better way to play fx on server +void function HackPlayLoopEffectOnEntity( asset fxName, entity ent ) +{ + ent.EndSignal( "OnDeath" ) + ent.EndSignal( "OnDestroy" ) + entity fxHandle + float waitTime + + + + fxHandle = CreateEntity( "info_particle_system" ) + fxHandle.SetValueForEffectNameKey( fxName ) + fxHandle.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE + fxHandle.kv.start_active = 1 + fxHandle.SetOrigin( ent.GetOrigin() ) + fxHandle.SetAngles( ent.GetAngles() ) + DispatchSpawn( fxHandle ) + + + OnThreadEnd( + function() : ( fxHandle ) + { + DestroyFxIfValid( fxHandle ) + } + ) + + + WaitForever() + +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DropHangingOgre( entity titan, entity player ) +{ + thread PlayAnim( titan.s.rack, "tr_AI_titanrack_bootup_heavy" ) + PlayAnimTeleport( titan, "ht_AI_titanrack_bootup_heavy", titan.s.rack ) + wait 0.5 + Embark_Allow( player ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DropHangingOgreOnFlag( entity titan, string flagToDrop, entity player ) +{ + Assert( IsValid( titan ) ) + FlagWait( flagToDrop ) + thread DropHangingOgre( titan, player ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function GenericSparks( entity ent ) +{ + vector origin = ent.GetOrigin() + vector angles = ent.GetAngles() + + entity fxHandle + wait ( RandomFloatRange( 0.1, 2.5 ) ) + + while( IsValid( ent ) ) + { + if ( GetBugReproNum() == 007 ) + { + //fails silently to play fx/sound when it's an info_target + fxHandle = PlayLoopFXOnEntity( FX_SPARKS, ent ) + EmitSoundOnEntity( ent, SOUND_SPARKS ) + } + else + { + fxHandle = PlayFX( FX_SPARKS, origin, angles ) + EmitSoundAtPosition( TEAM_UNASSIGNED, origin, SOUND_SPARKS ) + } + + wait ( RandomFloatRange( 1.7, 5.2 ) ) + + if ( IsValid( fxHandle ) ) + { + DestroyFxIfValid( fxHandle ) + } + wait ( RandomFloatRange( 0.1, 0.4 ) ) + } +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function ZombieSpectreSpawnAndDie( entity npc ) +{ + npc.EndSignal( "OnDeath" ) + + wait ( RandomFloatRange( 0.2, 1.75 ) ) + + npc.Die() +} +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function RackSpawnCallback( entity npc, entity activator ) +{ + string scriptName = npc.GetScriptName() + + if ( scriptName == "rusted_spectre" ) + thread ZombieSpectreSpawnAndDie( npc ) + +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DestroyArray( array entities ) +{ + foreach( ent in entities ) + { + if( IsValid( ent ) ) + ent.Destroy() + } +} +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function MoveEntityToOppositeTimePeriod( entity ent ) +{ + int zOffset = TIME_ZOFFSET + if ( GetEntityTimelinePosition( ent ) == TIMEZONE_DAY ) + zOffset = TIME_ZOFFSET * -1 + + ent.SetOrigin( ent.GetOrigin() + Vector( 0, 0, TIME_ZOFFSET ) ) +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DeleteFireHazards( string scriptName ) +{ +array< entity > fireTriggers = GetEntArrayByScriptName( scriptName ) + entity fxEnt + foreach( trigger in fireTriggers ) + { + fxEnt = trigger.GetLinkEnt() + fxEnt.Destroy() + trigger.Destroy() + } + +} +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* +void function SpawnWallSpectreGroupWhenInRange( entity player, array< entity > propSpawners, int maxToSpawn, string flagToAbort = "", string flagToSetWhenDone = "", float delayMin = 3, float delayMax = 5, string flagToSet = "", bool requireLookAt = false ) +{ + Assert( propSpawners.len() >= maxToSpawn, "Max to spawn( " + maxToSpawn + ") is greater than number of propSpawners (" + propSpawners.len() + ") " ) + int spectresSpawned = 0 + entity spawner + + var spawnerTimezone = GetEntityTimelinePosition( propSpawners[ 0 ] ) + + while( spectresSpawned < maxToSpawn ) + { + if ( ( flagToAbort != "") && ( Flag( flagToAbort ) ) ) + break + + + // Don't spawn if player not in the same timezone + if ( spawnerTimezone != GetEntityTimelinePosition( player ) ) + { + wait 0.1 + continue + + } + + spawner = GetBestPropSpawnerFromGroup( propSpawners, player, requireLookAt ) + if ( !IsValid( spawner ) ) + { + wait 0.1 + continue + } + + spawner.Signal( "PropSpawnerActivate" ) + spectresSpawned++ + + if ( ( flagToSet != "" ) && ( !Flag( flagToSet ) ) ) + FlagSet( flagToSet ) + + propSpawners.fastremovebyvalue( spawner ) + if ( ( spectresSpawned == maxToSpawn ) && ( flagToSetWhenDone != "" ) ) + { + FlagSet( flagToSetWhenDone ) + break + } + + wait( RandomFloatRange( delayMin, delayMax ) ) + } +} +*/ + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function SpawnShowcaseGroupWhenInRange( entity player, array< entity > propSpawners, int maxToSpawn, string flagToAbort = "", string flagToSetWhenDone = "", float delayMin = 3, float delayMax = 5, string flagToSet = "", bool requireLookAt = false ) +{ + Assert( propSpawners.len() >= maxToSpawn, "Max to spawn( " + maxToSpawn + ") is greater than number of propSpawners (" + propSpawners.len() + ") " ) + int dudesSpawned = 0 + entity spawnProp + + var spawnerTimezone = GetEntityTimelinePosition( propSpawners[ 0 ] ) + + while( dudesSpawned < maxToSpawn ) + { + if ( ( flagToAbort != "") && ( Flag( flagToAbort ) ) ) + break + + + // Don't spawn if player not in the same timezone + if ( spawnerTimezone != GetEntityTimelinePosition( player ) ) + { + wait 0.1 + continue + + } + + spawnProp = GetBestPropSpawnerFromGroup( propSpawners, player, requireLookAt ) + if ( !IsValid( spawnProp ) ) + { + wait 0.1 + continue + } + + if ( IsSpectreRackDoorSpawner( spawnProp ) ) + spawnProp.Signal( "PropSpawnerActivate" ) + else + thread ShowcaseSpawn( spawnProp ) + + dudesSpawned++ + + if ( ( flagToSet != "" ) && ( !Flag( flagToSet ) ) ) + FlagSet( flagToSet ) + + propSpawners.fastremovebyvalue( spawnProp ) + if ( ( dudesSpawned == maxToSpawn ) && ( flagToSetWhenDone != "" ) ) + { + FlagSet( flagToSetWhenDone ) + break + } + + wait( RandomFloatRange( delayMin, delayMax ) ) + } + + if ( FlagExists( flagToSetWhenDone ) ) + FlagSet( flagToSetWhenDone ) + +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +entity function GetBestPropSpawnerFromGroup( array< entity > propSpawners, entity player, bool requireLookAt = false ) +{ + entity bestSpawnerProp + + array< entity > propSpawnersOrderedClosest = ArrayClosest( propSpawners, player.GetOrigin() ) + + float minDistSqr = 128 * 128 + bool doTrace = true + float degrees = 90 + + foreach( spawnerProp in propSpawnersOrderedClosest ) + { + if ( DistanceSqr( player.GetOrigin(), spawnerProp.GetOrigin() ) < minDistSqr ) + continue + + if ( TS_WithinPlayerFOV( spawnerProp.GetOrigin() + Vector( 0, 0, 16) ) ) + { + bestSpawnerProp = spawnerProp + break + } + } + + if ( ( !IsValid( bestSpawnerProp ) ) && ( requireLookAt == false ) ) + bestSpawnerProp = GetClosest( propSpawners, player.GetOrigin() ) + + return bestSpawnerProp +} + +////////////////////////////////////////////////////////////// +// HACK: Move to utility? +bool function TS_WithinPlayerFOV( targetPos, MinDot = 0.8 ) +{ + expect vector( targetPos ) + entity player = GetPlayerArray()[0] + if ( !player ) + return false + + float dot = VectorDot_PlayerToOrigin( player, targetPos ) + if ( dot < MinDot ) + return false + + return true +} +////////////////////////////////////////////////////////////// +void function RemoveBlocker( string scriptName ) +{ + array <entity> blockers = GetEntArrayByScriptName( scriptName ) + Assert( blockers.len() > 0, "No blockers found with scriptName " + scriptName ) + foreach( blocker in blockers ) + { + blocker.Hide() //Does this even do anything anymore? + blocker.MakeInvisible() + blocker.NotSolid() + } + +} +////////////////////////////////////////////////////////////// +void function RestoreBlocker( string scriptName ) +{ + array <entity> blockers = GetEntArrayByScriptName( scriptName ) + Assert( blockers.len() > 0, "No blockers found with scriptName " + scriptName ) + foreach( blocker in blockers ) + { + blocker.Show() //Does this even do anything anymore? + blocker.MakeVisible() + blocker.Solid() + } +} + +////////////////////////////////////////////////////////////// +void function TempExplosion( vector origin ) +{ + CreateAirShake( origin, 10, 105, 1.25 ) + PlayFX( FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM, origin ) + EmitSoundAtPosition( TEAM_UNASSIGNED, origin, SOUND_BREAKABLE_SATCHEL_DEBRIS_MEDIUM ) +} +////////////////////////////////////////////////////////////// +void function DestroyEntByScriptName( string scriptName ) +{ + entity ent = GetEntByScriptName( scriptName ) + Assert( IsValid( ent ) ) + ent.Destroy() + +} + +////////////////////////////////////////////////////////////// +void function DestroyInstancesByScriptInstanceName( string scriptName, string instanceName ) +{ + array< entity > ents = GetEntArrayByScriptNameInInstance( scriptName, instanceName ) + Assert( ents.len() > 0, "No entities with script_name/instance_name combo: " + scriptName + " / " + instanceName ) + foreach( ent in ents ) + ent.Destroy() +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function WaittillSomeDudesAreDead( string scriptName, int numberToWaitToBeDead, string flagToSet = "" ) +{ + array< entity > entities = GetEntArrayByScriptName( scriptName ) + Assert( entities.len() > 0 ) + array< entity > dudes + foreach( ent in entities ) + { + if ( ent.IsNPC() ) + dudes.append( ent ) + } + if ( dudes.len() == 0 ) + { + printt( "Warning: WaittillSomeDudesAreDead returning. No npcs exist for scriptName " + scriptName ) + return + } + Assert( dudes.len() >= numberToWaitToBeDead ) + int dudesThatHaveDied = 0 + + while( dudesThatHaveDied < numberToWaitToBeDead ) + { + wait 0.1 + foreach( dude in dudes ) + { + if ( !IsAlive( dude ) ) + { + dudesThatHaveDied++ + dudes.fastremovebyvalue( dude ) + } + } + } + + if ( flagToSet != "" ) + FlagSet( flagToSet ) + +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function WaittillPlayerSwitchesTimezone( var timeZoneToWaitFor ) +{ + + if ( level.timeZone == timeZoneToWaitFor ) + return + + while( level.timeZone != timeZoneToWaitFor ) + wait 0.1 + + wait 0.1 +} + +///////////////////////////////////////////////////////////////////////////////////////// +void function TimeshiftPlayerThink( entity player ) +{ + player.EndSignal( "OnDeath" ) + + vector posInOtherTimeline + vector playerOrigin + var timeZoneCurrent + float minDist = 32 + float minDistSqr = minDist * minDist + vector idealPosInOvergrown + vector idealPosInPristine + + wait 0.1 + + while( true ) + { + WaitFrame() + + playerOrigin = player.GetOrigin() + timeZoneCurrent = GetTimelinePosition( playerOrigin ) + if ( timeZoneCurrent == TIMEZONE_FROZEN ) + return + + posInOtherTimeline = GetPosInOtherTimeline( playerOrigin ) + + if ( timeZoneCurrent == TIMEZONE_NIGHT ) + { + idealPosInOvergrown = playerOrigin + idealPosInPristine = posInOtherTimeline + } + else if ( timeZoneCurrent == TIMEZONE_DAY ) + { + idealPosInOvergrown = posInOtherTimeline + idealPosInPristine = playerOrigin + } + else + printl( "Player in frozen world, no need to run the TimeshiftPlayerThinkThread anymore" ) + + //------------------------------------------------------------- + // get last good pos in current timeline (player's pos, usually) + //------------------------------------------------------------- + if ( !PlayerPosInSolid( player, playerOrigin ) ) + { + if ( timeZoneCurrent == TIMEZONE_NIGHT ) + //file.lastGoodTimeshiftPosOvergrown = playerOrigin + player.s.lastGoodTimeshiftPosOvergrown = playerOrigin + else if ( timeZoneCurrent == TIMEZONE_DAY ) + //file.lastGoodTimeshiftPosPristine = playerOrigin + player.s.lastGoodTimeshiftPosPristine = playerOrigin + else + printl( "Player in frozen world, no need to run the TimeshiftPlayerThinkThread anymore" ) + } + else + { + if ( GetBugReproNum() == 88 ) + printt( "Player is stuck in solid in home timeline: " + playerOrigin ) + } + + //-------------------------------------- + // get last good pos in other timeline + //-------------------------------------- + if ( !PlayerPosInSolid( player, posInOtherTimeline ) ) + { + if ( timeZoneCurrent == TIMEZONE_NIGHT ) + { + //file.lastGoodTimeshiftPosPristine = posInOtherTimeline + player.s.lastGoodTimeshiftPosPristine = posInOtherTimeline + + } + else if ( timeZoneCurrent == TIMEZONE_DAY ) + { + //file.lastGoodTimeshiftPosOvergrown = posInOtherTimeline + player.s.lastGoodTimeshiftPosOvergrown = posInOtherTimeline + } + else + printl( "Player in frozen world, no need to run the TimeshiftPlayerThinkThread anymore" ) + } + else + { + if ( GetBugReproNum() == 88 ) + { + printt( "******WARNING: player would be in solid in other timeline: " + posInOtherTimeline ) + thread DebugDrawBadCollisionBox( posInOtherTimeline ) + } + + + } + + //------------------------------------------- + // Assert if vectors are in the wrong timeline + //------------------------------------------- + //Assert ( GetTimelinePosition( file.lastGoodTimeshiftPosPristine ) == TIMEZONE_DAY, "lastGoodTimeshiftPosPristine ( " + file.lastGoodTimeshiftPosPristine + " ) is not in the proper timeZone" ) + //Assert ( GetTimelinePosition( file.lastGoodTimeshiftPosOvergrown ) == TIMEZONE_NIGHT, "lastGoodTimeshiftPosPristine ( " + file.lastGoodTimeshiftPosPristine + " ) is not in the proper timeZone" ) + Assert ( GetTimelinePosition( player.s.lastGoodTimeshiftPosPristine ) == TIMEZONE_DAY, "lastGoodTimeshiftPosPristine ( " + player.s.lastGoodTimeshiftPosPristine + " ) is not in the proper timeZone" ) + Assert ( GetTimelinePosition( player.s.lastGoodTimeshiftPosOvergrown ) == TIMEZONE_NIGHT, "lastGoodTimeshiftPosPristine ( " + player.s.lastGoodTimeshiftPosPristine + " ) is not in the proper timeZone" ) + + //--------------------------------------------------------------- + // Check if disparity between ideal pos and proposed is too huge + //---------------------------------------------------------------- + //if ( ( DistanceSqr( file.lastGoodTimeshiftPosOvergrown, idealPosInOvergrown ) > minDistSqr ) && ( GetBugReproNum() == 88 ) ) + if ( ( DistanceSqr( player.s.lastGoodTimeshiftPosOvergrown, idealPosInOvergrown ) > minDistSqr ) && ( GetBugReproNum() == 88 ) ) + { + printl( "*****************WARNING:") + //printt( "lastGoodTimeshiftPosOvergrown( " + file.lastGoodTimeshiftPosOvergrown + " > " + minDist + " from ( " + idealPosInOvergrown + ")" ) + printt( "lastGoodTimeshiftPosOvergrown( " + player.s.lastGoodTimeshiftPosOvergrown + " > " + minDist + " from ( " + idealPosInOvergrown + ")" ) + //thread DebugDrawBadTeleportBoxes( file.lastGoodTimeshiftPosOvergrown, idealPosInOvergrown ) + var pos = player.s.lastGoodTimeshiftPosOvergrown + thread DebugDrawBadTeleportBoxes( expect vector( pos ), idealPosInOvergrown ) + } + //if ( ( DistanceSqr( file.lastGoodTimeshiftPosPristine, idealPosInPristine ) > minDistSqr ) && ( GetBugReproNum() == 88 ) ) + if ( ( DistanceSqr( player.s.lastGoodTimeshiftPosPristine, idealPosInPristine ) > minDistSqr ) && ( GetBugReproNum() == 88 ) ) + { + printl( "*****************WARNING:") + //printt( "lastGoodTimeshiftPosPristine( " + file.lastGoodTimeshiftPosPristine + " > " + minDist + " from ( " + idealPosInPristine + ")" ) + printt( "lastGoodTimeshiftPosPristine( " + player.s.lastGoodTimeshiftPosPristine + " > " + minDist + " from ( " + idealPosInPristine + ")" ) + //thread DebugDrawBadTeleportBoxes( file.lastGoodTimeshiftPosPristine, idealPosInPristine ) + var pos = player.s.lastGoodTimeshiftPosPristine + thread DebugDrawBadTeleportBoxes( expect vector( pos ), idealPosInPristine ) + } + //Assert( DistanceSqr( file.lastGoodTimeshiftPosOvergrown, idealPosInOvergrown ) < minDistSqr, "lastGoodTimeshiftPosOvergrown( " + file.lastGoodTimeshiftPosOvergrown + " > " + minDist + " from ( " + idealPosInOvergrown + ")" ) + //Assert( DistanceSqr( file.lastGoodTimeshiftPosPristine, idealPosInPristine ) < minDistSqr, "lastGoodTimeshiftPosPristine( " + file.lastGoodTimeshiftPosPristine + " > " + minDist + " from ( " + idealPosInPristine + ")" ) + + } +} + + +void function DebugDrawBadTeleportBoxes( vector badPos, vector goodPos ) +{ + + vector boxSize1 = Vector(-16,-16,0) + vector boxSize2 = Vector(16,16,72) + vector zOffset = Vector( 0, 0, 36) + + while( true ) + { + wait 0.15 + //r, g, b, a, drwTime + DebugDrawBox( badPos, boxSize1, boxSize2, 255, 0, 0, 1, 0.15 ) + DebugDrawBox( goodPos, boxSize1, boxSize2, 0, 255, 0, 1, 0.15 ) + DebugDrawLine( badPos + zOffset, goodPos + zOffset, 255, 0, 0, true, 0.15 ) + DebugDrawText( badPos + zOffset, "Bad TimeTravel Teleports", true, 0.15 ) + } + +} + + +void function DebugDrawAudioLogNumber( entity audioLogModel, string number ) +{ + audioLogModel.Signal( "AudioLogDebugDraw" ) + audioLogModel.EndSignal( "AudioLogDebugDraw" ) + + vector pos = audioLogModel.GetOrigin() + while( true ) + { + wait 0.15 + DebugDrawText( pos, number, true, 0.15 ) + } + + +} +void function DebugDrawBadCollisionBox( vector badPos ) +{ + + vector boxSize1 = Vector(-16,-16,0) + vector boxSize2 = Vector(16,16,72) + vector zOffset = Vector( 0, 0, 36) + + while( true ) + { + wait 0.15 + //r, g, b, a, drwTime + DebugDrawBox( badPos, boxSize1, boxSize2, 255, 255, 0, 1, 0.15 ) + DebugDrawText( badPos + zOffset, "Timeshift into solid", true, 0.15 ) + } + +} + + +////////////////////////////////////////////////////////////////////////////////////// +void function OnPlayerDamage_TimeShift( entity player, var damageInfo ) +{ + if ( level.allowTimeTravel == false ) + return + + int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo ) + entity attacker = DamageInfo_GetAttacker( damageInfo ) + local damageAmount = DamageInfo_GetDamage( damageInfo ) + + if ( !IsValid( attacker ) ) + return + + if ( !attacker.IsNPC() ) + return + + int playerHealth = player.GetHealth() + int playerMaxHealth = player.GetMaxHealth() + + if ( !Flag( "DisplayTheDamageHint" ) ) + return + + //if ( playerHealth > ( playerMaxHealth - 50 ) ) + //return + + //Show damage hint if we are at 60% or lower + printt( "Player health: ", playerHealth / playerMaxHealth.tofloat() ) + if ( playerHealth / playerMaxHealth.tofloat() <= 0.90 ) + thread DamageHintTillTimetravel( player ) + +} + +////////////////////////////////////////////////////////////////////////////////////// +function DamageHintTillTimetravel( entity player ) +{ + + if ( file.isDisplayingDamageText ) + return + + file.isDisplayingDamageText = true + file.isDisplayingTimeshiftHint = true + + player.EndSignal( "OnDeath" ) + player.Signal( "DisplayingDamageHint" ) + player.EndSignal( "DisplayingDamageHint" ) + EndSignal( player, "OnTimeFlippedTimezoneNight" ) + EndSignal( player, "OnTimeFlippedTimezoneDay" ) + + OnThreadEnd( + function() : ( player ) + { + ClearOnscreenHint( player ) + file.isDisplayingDamageText = false + file.isDisplayingTimeshiftHint = false + } + ) + + thread DisplayOnscreenHint( player, "timeshift_hint_combat", 3.0 ) + wait 3 + +} + + +///////////////////////////////////////////////////////////////////////////////////////// + +/* +void function TriggerTimehintThink( entity trigger ) +{ + wait 1 + + trigger.EndSignal( "OnDestroy" ) + string flagToAbort = trigger.GetTargetName() + Assert( IsValid( flagToAbort ) ) + if ( Flag( flagToAbort ) ) + return + FlagEnd( flagToAbort ) + + var timeZoneToShowHint = GetEntityTimelinePosition( trigger ) + + + local result + entity player + + while( true ) + { + result = trigger.WaitSignal( "OnTrigger" ) + + if ( !IsValid( result.activator ) ) + continue + if ( !result.activator.IsPlayer() ) + continue + if ( result.activator.IsTitan() ) + continue + + player = expect entity( result.activator ) + + break + } + + thread TimeshiftHint( player, timeZoneToShowHint, flagToAbort, "#BLANK_TEXT", trigger ) + +} +*/ + +///////////////////////////////////////////////////////////////////////////////////////// +void function TimeshiftHint( entity player, var timeZoneToShowHint, string flagToAbort, string message, entity trigger = null ) +{ + player.EndSignal( "OnDeath" ) + if( !IsValid( player ) ) + return + + if ( IsValid( trigger ) ) + trigger.EndSignal( "OnDestroy") + + Assert( FlagExists( flagToAbort ) ) + if ( Flag( flagToAbort ) ) + return + FlagEnd( flagToAbort ) + + //-------------------------------- + // Display in any timezone? + //-------------------------------- + bool hintIsTimezoneSpecific = true + if ( timeZoneToShowHint == TIMEZONE_ALL ) + hintIsTimezoneSpecific = false + + //--------------------------------` + // Custom hint message or default? + //-------------------------------- + string hintMessage + if ( message != "" ) + hintMessage = message + else + { + if ( timeZoneToShowHint == TIMEZONE_DAY ) + hintMessage = "timeshift_hint_present" + else if ( timeZoneToShowHint == TIMEZONE_NIGHT ) + hintMessage = "timeshift_hint_past" + else if ( timeZoneToShowHint == TIMEZONE_ALL ) + hintMessage = "timeshift_hint_default" + } + + //----------- + // Cleanup + //----------- + /* + OnThreadEnd( + function() : ( trigger ) + { + ClearOnscreenHint( player ) + if ( IsValid( trigger ) ) + trigger.Destroy() + } + ) + */ + + //---------------------------- + // Hint message display logic + //---------------------------- + + var hintTimezone = GetOppositeTimeline( timeZoneToShowHint ) + + bool isDisplayingMsg = false + file.isDisplayingTimeshiftHint = false + + float ticker = 0.0 + float displayTime = 5.0 + float increment = 0.25 + + //--------------------------------------------- + // Don't be condescending, before displaying + //------------------------------------------------ + wait 2 + + while( true ) + { + wait increment + + //---------------------------------------------------- + // No message if player not touching (optional) trigger + //---------------------------------------------------- + if ( IsValid( trigger ) ) + { + if ( !trigger.IsTouching( player ) ) + { + ClearOnscreenHint( player ) + continue + } + + } + + //---------------------------------------------------- + // No message if player has swapped to correct timeline + //---------------------------------------------------- + if ( ( GetEntityTimelinePosition( player ) != timeZoneToShowHint ) && ( hintIsTimezoneSpecific ) ) + { + ClearOnscreenHint( player ) + continue + } + + //---------------------------------------------------- + // Clear msg and wait if we've displayed it long enough + //---------------------------------------------------- + if ( ticker >= displayTime ) + { + ClearOnscreenHint( player ) + isDisplayingMsg = false + file.isDisplayingTimeshiftHint = false + wait 5 + } + + //---------------------------------------------------- + // Increment ticker if we are already displaying message + //---------------------------------------------------- + if ( isDisplayingMsg ) + { + ticker = ticker + increment + continue + } + //------------------------------------ + // Display the hint, reset the ticker + //------------------------------------ + else + { + ticker = 0.0 + DisplayOnscreenHint( player, hintMessage, displayTime ) + isDisplayingMsg = true + file.isDisplayingTimeshiftHint = true + } + + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +function HACK_DisableTurret( turret, shield = false ) +{ + //turret.EndSignal( "OnDestroy" ) + + + turret.EnableNPCFlag( NPC_IGNORE_ALL ) + turret.SetNoTarget( true ) + //turret.EnableNPCFlag( NPC_DISABLE_SENSING ) + turret.Anim_ScriptedPlay( "undeploy" ) + wait 1.0 + turret.DisableTurret() + turret.UnsetUsable() + turret.SetTitle( "" ) + + if ( !shield ) + turret.Signal( "TurretShieldWallRelease" ) + //turret.Anim_Stop() +} + +function HACK_EnableTurret( turret ) +{ + //turret.EndSignal( "OnDestroy" ) + + turret.Anim_ScriptedPlay( "deploy" ) + wait 1.0 + turret.EnableTurret() + turret.kv.AccuracyMultiplier = 100 + + //wait 1.0 + //turret.Anim_Stop() + turret.DisableNPCFlag( NPC_IGNORE_ALL ) + turret.SetNoTarget( false ) + //turret.DisableNPCFlag( NPC_DISABLE_SENSING ) +} + +void function OnSpawnedScriptedSwitch( entity button ) +{ + array< entity > linkedEnts = button.GetLinkEntArray() + array< entity > turrets + + foreach( entity ent in linkedEnts ) + { + if ( ent.GetClassName() == "npc_turret_sentry") + turrets.append( ent ) + } + + if ( turrets.len() > 0 ) + thread TurretButtonThink( button, turrets ) +} +////////////////////////////////////////////////////////////////////////////////////////// +void function TurretButtonThink( entity button, array< entity > turrets ) +{ + + foreach( turret in turrets ) + thread HACK_DisableTurret( turret ) + + var player //hack: have to use "var" when waiting on a usable signal or trigger + + string flagRequired = expect string( button.kv.scr_flagRequired ) + if ( flagRequired != "" ) + FlagWait( flagRequired ) + + button.SetUsePrompts( "#TIMESHIFT_HINT_TURRET_USE" , "#TIMESHIFT_HINT_TURRET_USE" ) + + while( true ) + { + button.WaitSignal( "OnActivate" ) + break + } + + if ( !Flag( "AtLeastOneBunkerTurretRestored" ) ) + FlagSet( "AtLeastOneBunkerTurretRestored" ) + + + string scriptName = button.GetScriptName() + if ( ( IsValid( scriptName ) ) && ( scriptName == "button_bunker_turrets_fence" ) ) + FlagSet( "TurretsNearBunkerFenceActivated" ) + + foreach( turret in turrets ) + { + thread HACK_EnableTurret( turret ) + } +} + + +///////////////////////////////////////////////////////////////////////////////////////// +entity function GetNpcByScriptName( string scriptName ) +{ + + array< entity > ents = GetEntArrayByScriptName( scriptName ) + array< entity > npcs + foreach( ent in ents ) + { + if ( ent.IsNPC() ) + npcs.append( ent ) + } + + Assert( npcs.len() > 0, "No NPCs found with script name: " + scriptName ) + Assert( npcs.len() < 2, "Multiple NPCs ( " + npcs.len() + " ) found with scriptName: " + scriptName ) + + return npcs[ 0 ] +} + + +entity function CreateLoudspeakerEnt( vector origin ) +{ + entity loudspeakerEnt = CreateEntity( "info_target" ) + loudspeakerEnt.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT + DispatchSpawn( loudspeakerEnt ) + loudspeakerEnt.SetOrigin( origin ) + + return loudspeakerEnt +} + +/* +void function BatteryMoveSounds( entity leftBattery, entity rightBattery ) +{ + entity soundEnt = leftBattery + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" ) + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" ) + wait 1.5 + StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" ) + soundEnt = rightBattery + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" ) +} +*/ + + +/* +void function BrokenBatteryMoveSounds( entity leftBattery ) +{ + FlagEnd( "bunker_move_battery" ) + + entity soundEnt = leftBattery + + while( !Flag( "bunker_move_battery" ) ) + { + + + OnThreadEnd( + function() : ( soundEnt ) + { + StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" ) + } + ) + + FlagWait( "broken_battery_moved_to_node0" ) + StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" ) + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" ) + wait 1 + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" ) + + FlagWait( "broken_battery_moved_to_node1" ) + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" ) + + FlagWait( "broken_battery_moved_to_node2" ) + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" ) + StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" ) + wait 0.75 + + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" ) + FlagWait( "broken_battery_moved_to_node3" ) + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" ) + StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" ) + wait 0.75 + + EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" ) + + } +} + +*/ + + +/* + +void function FuelPanelSounds( vector origin, string flagToStart, string flagToStop ) +{ + FlagWait( flagToStart ) + EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_stop" ) + + + EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_open_loop" ) + + FlagWait( flagToStop ) + StopSoundAtPosition( origin, "door_open_loop") + EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_stop" ) +} + +*/ + + +void function DestroyIfValid( string scriptName ) +{ + array< entity > ents = GetEntArrayByScriptName( scriptName ) + foreach( ent in ents ) + { + if ( IsValid( ent ) ) + ent.Destroy() + } +} + + +///////////////////////////////////////////////////////////////////////////////////////// +function SkyboxStart() +{ + wait 1.5 + local skyCam = GetEnt( "skybox_cam_night" ) + foreach ( player in GetPlayerArray() ) + { + player.SetSkyCamera( skyCam ) + } +} + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +void function SleepingSpectreFX( entity spectreModel, string signalToAbort ) +{ + spectreModel.EndSignal( "OnDestroy" ) + spectreModel.EndSignal( signalToAbort ) + + entity fxEyeSparksLeft + entity fxEyeSparksRight + + array< entity > fxArray + fxArray.append( fxEyeSparksLeft ) + fxArray.append( fxEyeSparksRight ) + + //fxFullBodyEffect = PlayFXOnEntity( <whatever>, spectreModel, "TAG" ) + + while( true ) + { + + OnThreadEnd( + function() : ( fxArray ) + { + foreach( fx in fxArray ) + DestroyFxIfValid( fx ) + } + ) + +// ModelFX_EnableGroup( spectreModel, "friend_lights" ) + + wait( RandomFloatRange( 2, 3.75 ) ) + +// ModelFX_DisableGroup( spectreModel, "friend_lights" ) + + fxEyeSparksLeft = PlayFXOnEntity( FX_SPARKS, spectreModel, "vent_left_out" ) + fxEyeSparksRight = PlayFXOnEntity( FX_SPARKS, spectreModel, "vent_right_out" ) + EmitSoundOnEntity( spectreModel, SOUND_SPARKS ) + wait( RandomFloatRange( 0.2, 0.25 ) ) + fxEyeSparksLeft.Fire( "Stop" ) + fxEyeSparksLeft.Fire( "DestroyImmediately" ) + fxEyeSparksLeft.Destroy() + fxEyeSparksRight.Fire( "Stop" ) + fxEyeSparksRight.Fire( "DestroyImmediately" ) + fxEyeSparksRight.Destroy() + + + fxEyeSparksLeft = PlayFXOnEntity( FX_SPARKS, spectreModel, "vent_left_out" ) + fxEyeSparksRight = PlayFXOnEntity( FX_SPARKS, spectreModel, "vent_right_out" ) + EmitSoundOnEntity( spectreModel, SOUND_SPARKS ) + wait( RandomFloatRange( 0.3, 0.5 ) ) + fxEyeSparksLeft.Fire( "Stop" ) + fxEyeSparksLeft.Fire( "DestroyImmediately" ) + fxEyeSparksLeft.Destroy() + fxEyeSparksRight.Fire( "Stop" ) + fxEyeSparksRight.Fire( "DestroyImmediately" ) + fxEyeSparksRight.Destroy() + + wait( RandomFloatRange( 0.1, 0.25 ) ) + + + } +} + +/////////////////////////////////////////////////////////////// +void function CleanupEnts( string scriptName ) +{ + array<entity> entArray = GetEntArrayByScriptName( scriptName ) + foreach( ent in entArray ) + { + if( IsValid( ent ) ) + ent.Destroy() + } + +} + +/////////////////////////////////////////////////////////////// +void function CleanupAI( entity player, string triggerScriptName = "", immediate = false ) +{ + if ( !IsValid( player ) ) + return + + array<entity> npcArray = GetNPCArray() + + if ( npcArray.len() == 0 ) + return + + //-------------------------------------------------- + // Optional trigger to specify which npcs to select + //-------------------------------------------------- + if ( triggerScriptName != "" ) + { + + entity trigger = GetEntByScriptName( triggerScriptName ) + vector triggerMins = trigger.GetBoundingMins() + vector triggerMaxs = trigger.GetBoundingMaxs() + bool entIsInsideTrigger + + for ( int i = npcArray.len() - 1; i >= 0; i-- ) //loop backward through array since we are removing entries + { + + + //HACK: Titans and marvins aren't registering IsTouching + if ( ( triggerScriptName == "trigger_ai_pristine" ) && ( GetEntityTimelinePosition( npcArray[ i ] ) == TIMEZONE_DAY ) ) + { + if ( !trigger.IsTouching( npcArray[ i ] ) ) + { + printl( "Ent " + ( npcArray[ i ] ).GetClassName() + " at " + npcArray[ i ].GetOrigin() + " is within trigger " + triggerScriptName + " but IsTouching returns false" ) + continue + } + } + + if ( !trigger.IsTouching( npcArray[ i ] ) ) + { + //Remove the npc + npcArray.fastremove( i ) + } + } + } + + + if ( npcArray.len() == 0 ) + return + + if ( immediate ) + { + foreach( npc in npcArray ) + { + if ( ( IsAlive( npc ) ) && ( npc.GetTeam() != TEAM_MILITIA ) ) + npc.Destroy() + } + + return + } + + //-------------------------------------------------- + // Delete all NPCs when out of sight + //-------------------------------------------------- + while ( npcArray.len() > 0 ) + { + ArrayRemoveDead( npcArray ) + for ( int i = npcArray.len() - 1; i >= 0; i-- ) //loop backward through array since we are removing entries + { + if ( npcArray[ i ].GetTeam() != TEAM_IMC ) + { + npcArray.fastremove( i ) + continue + } + thread DeleteNpcWhenOutOfSight( npcArray[ i ], player ) + npcArray.fastremove( i ) + } + wait 0.1 + } +} + +/////////////////////////////////////////////////////////////// +void function DeleteNpcWhenOutOfSight( entity npc, entity player ) +{ + if ( !IsValid( npc ) ) + return + + if ( !IsValid( player ) ) + return + + if ( !npc.IsNPC() ) + return + + npc.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDeath" ) + + //don't run this func twice on the same npc) + if ( npc.l.npcMarkedForCleanup == true ) + return + + npc.l.npcMarkedForCleanup = true + + float minDistSqr = 1024 * 1024 + bool doTrace = true + float degrees = 90 + + while( IsAlive( npc ) ) + { + WaitFrame() + + if ( DistanceSqr( player.GetOrigin(), npc.GetOrigin() ) < minDistSqr ) + continue + + if ( PlayerCanSee( player, npc, doTrace, degrees ) ) + continue + + npc.Destroy() + break + } + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/* +void function TimeFlux( entity player, string triggerNameToDoFlux, string flagToAbort = "", int maxSwaps = -1, float minFluxTime = 0.75, maxFluxTime = 2 ) +{ + if ( !IsValid( player ) ) + return + if ( ( flagToAbort != "" ) && ( Flag( flagToAbort ) ) ) + return + + entity triggerToDoFluxIn = GetEntByScriptName( triggerNameToDoFlux ) + entity lookTarget = triggerToDoFluxIn.GetLinkEnt() + var homeTimeZone = GetEntityTimelinePosition( triggerToDoFluxIn ) + var destTimeZone = GetOppositeTimeline( homeTimeZone ) + float fluxTime + int swapCount = 0 + + float fadeTime = 0.3 + float holdFadeTime = 0 + + + player.EndSignal( "OnDeath" ) + + if ( flagToAbort != "" ) + FlagEnd( flagToAbort ) + + + OnThreadEnd( + function() : ( player, homeTimeZone ) + { + if ( !IsValid( player ) ) + return + if ( GetEntityTimelinePosition( player ) != homeTimeZone ) + thread SwapTimelinesScripted( player, homeTimeZone ) + } + ) + + + + while( true ) + { + wait RandomFloatRange( 0.2, 0.5 ) + + //-------------------------------------------------- + // Player in home timezone with all requirements met + //-------------------------------------------------- + if ( ( GetEntityTimelinePosition( player ) == homeTimeZone ) && ( TS_WithinPlayerFOV( lookTarget.GetOrigin(), 0.7 ) ) && ( triggerToDoFluxIn.IsTouching( player ) ) ) + { + thread SwapTimelinesScripted( player, destTimeZone ) + fluxTime = RandomFloatRange( minFluxTime, maxFluxTime ) + wait fluxTime + + } + + //-------------------------------------------------- + // Player in dest timezone...swap back to home timezone + //-------------------------------------------------- + if ( GetEntityTimelinePosition( player ) == destTimeZone ) + { + thread SwapTimelinesScripted( player, homeTimeZone ) + fluxTime = RandomFloatRange( minFluxTime, maxFluxTime ) + wait fluxTime + swapCount++ + } + + //--------------------------------------- + // Early out if we've done enough swaps + //---------------------------------------- + if ( ( maxSwaps != -1 ) && ( swapCount >= maxSwaps ) ) + break + + } + +} + +*/ + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +void function SwapTimelinesScripted( entity player, var timeZone ) +{ + Remote_CallFunction_NonReplay( player, "ServerCallback_ScriptedTimeshiftStart", timeZone ) + + //------------------- + // crescendo sound + //------------------- + if ( timeZone == TIMEZONE_DAY ) + EmitSoundOnEntity( player, "Timeshift_Scr_InvoluntaryShift2Past_Start" ) + else + EmitSoundOnEntity( player, "Timeshift_Scr_InvoluntaryShift2Present_Start" ) + + wait 0.55 + //------------------- + // Scripted timeshift FX + //------------------- + + //Mostly done on the client + + wait 0.8 + //( amplitude frequency duration + CreateAirShake( player.GetOrigin(), 10, 50, 1.4, 20000 ) + wait 0.4 + + //------------------- + // Swap timelines + //------------------- + SwapTimelines( player, timeZone ) + + + //phaseshift_postfx_forceOn 1 +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + + + + +/* +void function MakeSpectreOwnedByPlayer( entity spectre, entity player ) +{ + spectre.EnableNPCFlag( NPC_IGNORE_ALL ) + spectre.SetNoTarget( true ) + //spectre.SetOwner( player ) + //spectre.SetOwnerPlayer( player ) + //spectre.SetBossPlayer( player ) + SetTeam( spectre, TEAM_MILITIA ) + + spectre.SetTitle( "" ) + SetTargetName( spectre, "" ) + + + int followBehavior = GetDefaultNPCFollowBehavior( spectre ) + spectre.InitFollowBehavior( player, followBehavior ) + spectre.DisableBehavior( "Assault" ) + spectre.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_USE_SHOOTING_COVER ) + spectre.EnableBehavior( "Follow" ) + +} +*/ + +///////////////////////////////////////////////////////////////////////////////////////// +entity function CreateBestLoudspeakerEnt( entity player, var timeZone, entity existingEnt = null ) +{ + entity soundEnt = existingEnt + if ( existingEnt == null) + soundEnt = CreateLoudspeakerEnt( player.GetOrigin() + Vector( 0, 0, 100 ) ) + if ( GetEntityTimelinePosition( soundEnt ) != timeZone ) + soundEnt.SetOrigin( GetPosInOtherTimeline( soundEnt.GetOrigin() ) ) + + //randomize position + vector baseOrigin = soundEnt.GetOrigin() + soundEnt.SetOrigin( baseOrigin + Vector( RandomIntRange( 0, 150 ), RandomIntRange( 0, 150 ), 0 ) ) + + return soundEnt +} +///////////////////////////////////////////////////////////////////////////////////////// +void function HideWeaponsAndAmmoTillFlag( string scriptName, string flagToRestore ) +{ + array <entity> weapons = GetWeaponArray( true ) + foreach ( weapon in weapons ) + { + if ( ( weapon.HasKey( "script_name" ) ) && ( weapon.kv.script_name == scriptName ) ) + { + weapon.Hide() + weapon.MakeInvisible() + weapon.UnsetUsable() + thread ShowEntityOnFlag( weapon, flagToRestore ) + } + + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +/* +void function DeleteWaponsWithScriptname( string scriptName ) +{ + array <entity> weapons = GetWeaponArray( true ) + foreach ( weapon in weapons ) + { + if ( ( weapon.HasKey( "script_name" ) ) && ( weapon.kv.script_name == scriptName ) ) + weapon.Destroy() + + } +} +*/ +///////////////////////////////////////////////////////////////////////////////////// +void function ShowEntityOnFlag( entity weapon, string flagToRestore ) +{ + FlagWait( flagToRestore ) + weapon.Show() + weapon.MakeVisible() + weapon.SetUsable() +} + +///////////////////////////////////////////////////////////////////////////////////// +void function SetFlagWhenPlayerLookingAtEnt( entity player, string flagToSet, entity ent, entity trigger = null ) +{ + if ( Flag( flagToSet ) ) + return + FlagEnd( flagToSet ) + + //WaitTillLookingAt( entity player, entity ent, bool doTrace, float degrees, float minDist = 0, float timeOut = 0, entity trigger = null ) + waitthread WaitTillLookingAt( player, ent, true, 45, 0, 0 ) + FlagSet( flagToSet ) +} + + +void function DoorOutOfOrderThink( entity propDynamic ) +{ + thread PlayAnim( propDynamic, "stutter01", propDynamic.GetOrigin(), propDynamic.GetAngles() ) +} + + + +entity function TSCreateScriptMoverLight( entity owner = null, origin = null, angles = null, solidType = 0 ) +{ + if ( owner == null ) + { + entity script_mover = CreateEntity( "script_mover_lightweight" ) + script_mover.kv.solid = solidType + script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" ) + script_mover.kv.SpawnAsPhysicsMover = 0 + if ( origin ) + script_mover.SetOrigin( origin) + if ( angles ) + script_mover.SetAngles( angles ) + + DispatchSpawn( script_mover ) + return script_mover + } + + entity script_mover = CreateEntity( "script_mover_lightweight" ) + script_mover.kv.solid = solidType + script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" ) + script_mover.kv.SpawnAsPhysicsMover = 0 + script_mover.SetOrigin( owner.GetOrigin() ) + script_mover.SetAngles( owner.GetAngles() ) + DispatchSpawn( script_mover ) + script_mover.Hide() + + script_mover.SetOwner( owner ) + return script_mover +} + +void function GiveLowAmmo( entity player ) +{ + //give player one clip of ammo + int clipBulletCapacity + int currentAmmo + + entity weapon = player.GetMainWeapons()[ 0 ] + clipBulletCapacity = player.GetWeaponAmmoMaxLoaded( weapon ) + currentAmmo = player.GetActiveWeaponPrimaryAmmoLoaded() + + //hack - just refill the current clips if ammo is picked up + weapon.SetWeaponPrimaryClipCount( clipBulletCapacity ) + player.SetActiveWeaponPrimaryAmmoTotal( 1 ) +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////// +void function CivilianSkitThink( entity civilian, entity player, string flagToReact = "" ) +{ + /* + //--------------------------------------------- + // Does civilian have props or escape nodes? + //--------------------------------------------- + array< entity > linkedEnts = civilian.GetLinkEntArray() + string classname + entity escapeOrg + entity animProp + foreach( entity ent in linkedEnts ) + { + classname = ent.GetClassName() + if ( classname == "prop_dynamic" ) + animProp = ent + if ( classname == "info_move_target" ) + escapeOrg = ent + } + + */ + + +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +void function CivilianWalkerThink( entity civilian ) +{ + if ( !civilian.HasKey( "script_noteworthy" ) ) + return + + string walkAnim = expect string( civilian.kv.script_noteworthy ) + Assert( IsValid( walkAnim ), "Ent at " + civilian.GetOrigin() + " needs a walk anim name in its script_noteworthy" ) + + thread GivePropForAnim( civilian, walkAnim ) + + /* + entity spawner = civilian.spawner + Assert( IsValid ( spawner ) ) + asset model = civilian.spawner.GetSpawnerModelName() + */ + + civilian.SetModel( GetRandomCivilianModel() ) + civilian.SetMoveAnim( walkAnim ) + MakeCivilian( civilian ) + + + + array< entity > linkedEnts = civilian.GetLinkEntArray() + string editorClassname + entity destinationOrg + foreach( entity ent in linkedEnts ) + { + editorClassname = GetEditorClass( ent ) + if ( editorClassname == "info_move_target" ) + destinationOrg = ent + + } + Assert( IsValid( destinationOrg ) ) + civilian.AssaultPoint( destinationOrg.GetOrigin() ) +} +///////////////////////////////////////////////////////////////////////////////////////////////////////// +void function CivilianActorThink( entity civilian ) +{ + civilian.SetModel( GetRandomCivilianModel() ) + MakeCivilian( civilian ) +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function QuickSkit( entity player, entity skitNode, string failsafeFlagToStart = "", entity lookAtEnt = null, entity lookAtTrigger = null ) +{ + array< entity > linkedEnts = skitNode.GetLinkEntArray() + Assert( linkedEnts.len() > 0, "skitNode at " + skitNode.GetOrigin() + " has no linked ents") + vector origin = skitNode.GetOrigin() + vector angles = skitNode.GetAngles() + skitNode.Destroy() + bool isLooping = false + bool showIdle = false + bool isSpawnSkit = false + bool isDeleteSkit = false + + + string scriptName + if ( ( skitNode.HasKey( "script_noteworthy") ) && ( skitNode.kv.script_noteworthy == "looping" ) ) + isLooping = true + if ( ( skitNode.HasKey( "script_noteworthy") ) && ( skitNode.kv.script_noteworthy == "show_idle" ) ) + showIdle = true + if ( ( skitNode.HasKey( "script_noteworthy") ) && ( skitNode.kv.script_noteworthy == "delete" ) ) + isDeleteSkit = true + + entity actor + array <entity> skitActors + bool isDoorSkit = false + asset modelName + + foreach( entity ent in linkedEnts ) + { + modelName = ent.GetModelName() + if ( modelName == $"models/door/door_imc_interior_03_128_animated.mdl" ) + isDoorSkit = true + + } + + + foreach( entity ent in linkedEnts ) + { + + if( IsSpawner( ent ) ) + { + actor = ent.SpawnEntity() + actor.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID + DispatchSpawn( actor ) + isSpawnSkit = true + actor.EnableNPCFlag( NPC_DISABLE_SENSING ) + actor.kv.alwaysAlert = 0 + actor.EnableNPCFlag( NPC_IGNORE_ALL ) + actor.SetNoTarget( true ) + actor.EnableNPCFlag( NPC_NO_MOVING_PLATFORM_DEATH ) + } + else + actor = ent + + + actor.EndSignal( "OnDestroy" ) + actor.EndSignal( "OnDeath" ) + + actor.s.anim <- expect string( actor.kv.script_noteworthy ) + Assert( IsValid( actor.s.anim ), "Ent at " + actor.GetOrigin() + " needs an anim name in its script_noteworthy" ) + Assert( actor.s.anim != "", "Ent at " + actor.GetOrigin() + " needs an anim name in its script_noteworthy" ) + + if ( isDoorSkit == true ) + DontAllowFreeze( actor, true ) + + + if ( ( actor.GetScriptName().find( "civilian_" ) != null ) && ( isSpawnSkit == true ) ) + { + actor.SetMoveAnim( GetRandomCivilianRunAnim() ) + MakeCivilian( actor ) + isDeleteSkit = true + } + + if ( !isLooping ) + { + actor.s.animIdle <- actor.s.anim + "_idle" + thread PlayAnimTeleport( actor, actor.s.animIdle, origin, angles ) + } + else if ( ( isLooping ) && ( actor.IsNPC() ) ) + actor.EnableNPCFlag( NPC_DISABLE_SENSING ) + + if ( !showIdle ) + actor.Hide() + + skitActors.append( actor ) + } + + if ( lookAtEnt ) + waitthread WaitTillLookingAt( player, lookAtEnt, true, 30, 0, 0, lookAtTrigger, failsafeFlagToStart ) + else if ( failsafeFlagToStart != "" ) + FlagWait( failsafeFlagToStart ) + + + float animLength + + + foreach( entity actor in skitActors ) + { + + + modelName = actor.GetModelName() + if ( modelName == $"models/door/door_imc_interior_03_128_animated.mdl" ) + { + //actor.NotSolid() + //actor.kv.solid = 0 + // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only + + animLength = actor.GetSequenceDuration( actor.s.anim ) + delaythread ( animLength ) DisableNavmeshSeperatorTargetedByEnt( actor ) + //ToggleNPCPathsForEntity( actor, true ) + DisableNavmeshSeperatorTargetedByEnt( actor ) + } + + actor.Show() + if ( ( isSpawnSkit ) && ( isDeleteSkit ) ) + thread PlayAnimThenDelete( actor, expect string( actor.s.anim ), origin, angles ) + else if ( isSpawnSkit ) + { + thread PlayAnim( actor, expect string( actor.s.anim ), origin, angles ) + animLength = actor.GetSequenceDuration( actor.s.anim ) + if ( actor.IsNPC() ) + { + actor.DisableNPCFlag( NPC_DISABLE_SENSING ) + actor.DisableNPCFlag( NPC_IGNORE_ALL ) + actor.SetNoTarget( false ) + + } + + if ( isDoorSkit == true ) + thread AllowFreezeWhenAnimDone( actor, animLength ) + } + else if ( isLooping ) + thread PlayAnim( actor, expect string( actor.s.anim ), origin, angles ) + else + thread PlayAnimThenDelete( actor, expect string( actor.s.anim ), origin, angles ) + } + +} + + +void function AllowFreezeWhenAnimDone( entity npc, float animLength ) +{ + if ( !IsValid( npc ) ) + return + npc.EndSignal( "OnDeath" ) + + wait animLength + + DontAllowFreeze( npc, false ) + +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +string function GetRandomCivilianRunAnim() +{ + array<string> anims + //anims.append( "pt_civ_walk_swagger" ) + anims.append( "pt_civ_fleeing_run_flail" ) + anims.append( "pt_civ_fleeing_run_hunched" ) + //anims.append( "pt_civ_fleeing_run_lookback" ) + //anims.append( "pt_civ_fleeing_run_pause" ) + //anims.append( "pt_civ_fleeing_run_spin" ) + anims.randomize() + return anims[ 0 ] +} +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void function MakeCivilian( entity npc ) +{ + + TakeAllWeapons( npc ) + npc.EnableNPCFlag( NPC_DISABLE_SENSING ) + npc.kv.alwaysAlert = 0 + npc.EnableNPCFlag( NPC_IGNORE_ALL ) + npc.EnableNPCMoveFlag( NPCMF_DISABLE_MOVE_TRANSITIONS ) + SetTeam( npc, TEAM_UNASSIGNED ) +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +asset function GetRandomCivilianModel() +{ + array<asset> models + models.append( MODEL_CIV01 ) + models.append( MODEL_CIV02 ) + models.append( MODEL_CIV03 ) + models.append( MODEL_CIV04 ) + models.randomize() + return models[ 0 ] +} +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function PlayAnimThenDelete( entity actor, string anim, origin, angles ) +{ + actor.EndSignal( "OnDeath" ) + actor.EndSignal( "OnDestroy" ) + entity node = actor.GetLinkEnt() + if ( ( actor.IsNPC ) && ( IsValid( node ) ) ) + actor.AssaultPoint( node.GetOrigin() ) + + float animLength = actor.GetSequenceDuration( anim ) + thread PlayAnimTeleport( actor, anim, origin, angles ) + wait animLength + + if ( ( actor.IsNPC ) && ( IsValid( node ) ) ) + actor.WaitSignal( "OnFinishedAssault" ) + + if ( IsValid( actor ) ) + actor.Destroy() +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TriggerShowcaseSpawnInit( entity trigger ) +{ + bool requiresLookAt = false + if ( ( trigger.HasKey( "script_noteworthy") ) && ( trigger.kv.script_noteworthy == "lookat" ) ) + requiresLookAt = true + + array< entity > linkedEnts = trigger.GetLinkEntArray() + Assert( linkedEnts.len() > 0, "Showcase spawn trigger at " + trigger.GetOrigin() + " has no linked ents" ) + + foreach( ent in linkedEnts ) + { + Assert( ent.GetClassName() == "prop_dynamic", "Showcase spawn trigger at " + trigger.GetOrigin() + " Should only link to prop_dynamics " + ( ent.GetClassName() ) ) + thread SpawnerShowcaseSpawnerThink( trigger, ent, requiresLookAt ) + } + +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function SpawnerShowcaseSpawnerThink( entity trigger, entity spawnProp, bool requiresLookAt ) +{ + FlagWait( "PlayerDidSpawn" ) + entity player = GetPlayerArray()[ 0 ] + if ( !IsValid( player ) ) + return + player.EndSignal( "OnDeath" ) + trigger.EndSignal( "OnDestroy" ) + + //--------------------------------------------- + // Get spawner and associated animated prop + //--------------------------------------------- + entity spawner = spawnProp.GetLinkEnt() + Assert( IsSpawner( spawner ), "Entity at " + spawner.GetOrigin() + " is not a spawner" ) + var spawnerKeyValues = spawner.GetSpawnEntityKeyValues() + expect table( spawnerKeyValues ) + int spawnCount = 1 + float spawnDelayTime = 0.0 + + if ( "script_delay" in spawnerKeyValues ) + spawnDelayTime = float( spawnerKeyValues.script_delay ) + + if ( "spawn_count" in spawnerKeyValues ) //spawn_count not implemented yet, but will add later + spawnCount = int( spawnerKeyValues.script_delay ) + + string spawnModel = expect string( spawnerKeyValues.model ) + + int npcsSpawned = 0 + + + //-------------------------- + // Wait for trigger to be hit + //-------------------------- + + entity npc + + while ( GetTriggerEnabled( trigger ) == false ) + wait 0.1 + + while( true ) + { + wait 0.1 + trigger.WaitSignal( "OnTrigger" ) + + wait spawnDelayTime + + if ( requiresLookAt ) + { + //player, entity, doTrace, degrees minDist, timeOut, trigger + waitthread WaitTillLookingAt( player, spawnProp, true, 45, 0, 0, trigger ) + } + + thread ShowcaseSpawn( spawnProp ) + + npcsSpawned++ + if ( npcsSpawned == spawnCount ) + break + + } +} + +////////////////////////////////////////////////////////////////////////// +void function ShowcaseSpawn( entity spawnProp ) +{ + vector origin = spawnProp.GetOrigin() + vector angles = spawnProp.GetAngles() + string spawnAnimNPC = "" + string spawnAnimProp = "" + entity spawner = spawnProp.GetLinkEnt() + Assert( IsSpawner( spawner ), "Entity at " + spawner.GetOrigin() + " is not a spawner" ) + var spawnerKeyValues = spawner.GetSpawnEntityKeyValues() + expect table( spawnerKeyValues ) + string spawnModel = expect string( spawnerKeyValues.model ).tolower() + + switch( spawnProp.GetModelName() ) + { + //-------------------------- + // Floor panel prop + //-------------------------- + case $"models/props/floor_panel_animated.mdl": + case $"models/props/floor_vent_d_animated.mdl": + if ( spawnModel == "models/creatures/prowler/r2_prowler.mdl" ) + { + //spawnAnimProp = "floor_timeshift_prowler_spawn_01" + //spawnAnimNPC = "pr_timeshift_floor_spawn_01" + spawnAnimProp = "floor_timeshift_prowler_spawn_01_long" + spawnAnimNPC = "pr_timeshift_floor_spawn_01_long" + + } + else if ( spawnModel == "models/robots/stalker/robot_stalker_mossy.mdl" ) + { + if ( CoinFlip() ) + { + //spawnAnimProp = "floor_breakout_spawn_floorpanel" + //spawnAnimNPC = "st_breakout_spawn_floorpanel" + spawnAnimProp = "floor_breakout_spawn_floorpanel_long" + spawnAnimNPC = "st_breakout_spawn_floorpanel_long" + } + else + { + //spawnAnimProp = "floor_breakout_spawn_floordeath" + //spawnAnimNPC = "st_breakout_spawn_floordeath" + spawnAnimProp = "floor_breakout_spawn_floordeath_long" + spawnAnimNPC = "st_breakout_spawn_floordeath_long" + } + } + else + Assert( 0, "Unhandled spawn prop: " + spawnProp.GetModelName() ) + break + //-------------------------- + // Wall panel prop + //-------------------------- + case $"models/props/wall_vent_animated.mdl": + if ( spawnModel == "models/creatures/prowler/r2_prowler.mdl" ) + { + if ( ( spawnProp.HasKey( "script_noteworthy") ) && ( spawnProp.kv.script_noteworthy == "ground_vent" ) ) + { + //spawnAnimProp = "vent_low_timeshift_prowler_spawn_01" + //spawnAnimNPC = "pr_timeshift_vent_low_spawn_01" + spawnAnimProp = "vent_low_timeshift_prowler_spawn_01_long" + spawnAnimNPC = "pr_timeshift_vent_low_spawn_01_long" + } + else + { + //spawnAnimProp = "vent_timeshift_prowler_spawn_01" + //spawnAnimNPC = "pr_timeshift_vent_spawn_01" + spawnAnimProp = "vent_timeshift_prowler_spawn_01_long" + spawnAnimNPC = "pr_timeshift_vent_spawn_01_long" + } + + } + else if ( spawnModel == "models/robots/stalker/robot_stalker_mossy.mdl" ) + { + //spawnAnimProp = "vent_vent_spawn_core" + //spawnAnimNPC = "st_vent_spawn_core" + spawnAnimProp = "vent_vent_spawn_damaged_longintro" + spawnAnimNPC = "st_vent_spawn_damaged_longintro" + } + else + { + Assert( 0, "Unhandled spawnModel: " + spawnModel ) + } + break + default: + Assert( 0, "Unhandled spawn prop: " + spawnProp.GetModelName() ) + + } + + //------------------ + //Spawn the npc + //------------------ + entity npc = spawner.SpawnEntity() + npc.Hide() + npc.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID + DispatchSpawn( npc ) + + //---------------------------------- + // Animate the spawned npc and prop + //---------------------------------- + thread PlayAnim( npc, spawnAnimNPC, origin, angles ) + npc.Show() + thread PlayAnim( spawnProp, spawnAnimProp, origin, angles ) + +} + +/////////////////////////////////////////////////////////////////// + +bool function IsSpectreRackDoorSpawner( entity spawnProp ) +{ + array validModels = [ + $"models/timeshift/timeshift_column_panel_09_destroyed.mdl", + $"models/timeshift/timeshift_column_panel_10_destroyed.mdl", + $"models/timeshift/timeshift_column_panel_09.mdl", + $"models/timeshift/timeshift_column_panel_10.mdl" + ] + + if ( validModels.contains( spawnProp.GetModelName() ) ) + return true + + return false +} + +/////////////////////////////////////////////////////////////////// +void function HideStuff( string scriptName ) +{ + array <entity> stuff = GetEntArrayByScriptName( scriptName ) + foreach( thing in stuff ) + { + thing.Hide() + thing.MakeInvisible() + thing.NotSolid() + } + +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function ShowStuff( string scriptName ) +{ + array <entity> stuff = GetEntArrayByScriptName( scriptName ) + foreach( thing in stuff ) + { + thing.Show() + thing.Solid() + thing.MakeVisible() + } + +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function AttackPlayer( entity npc ) +{ + if ( !IsValid( npc ) ) + return + + array<entity> players = GetPlayerArray() + if ( players.len() <= 0 ) + return + + npc.SetEnemy( players[ 0 ] ) + printt( "Attacking player..." ) +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* +void function ChangeIMCCorpse( entity model, string corpseType ) +{ + //disable all bodygroups + int stateIndex = 1 // 0 = show, 1 = hide + model.SetBodygroup( model.FindBodyGroup( "imc_corpse_rifle" ), 1 ) + model.SetBodygroup( model.FindBodyGroup( "imc_corpse_shotgun" ), 1 ) + model.SetBodygroup( model.FindBodyGroup( "imc_corpse_smg" ), 1 ) + model.SetBodygroup( model.FindBodyGroup( "imc_corpse_lmg" ), 1 ) + + //enable the right one + int bodyGroupIndex = model.FindBodyGroup( corpseType ) + stateIndex = 0 // 0 = show, 1 = hide + model.SetBodygroup( bodyGroupIndex, stateIndex ) +} +*/ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DestroyNPCOnFlag( entity npc, string flagToDestroy ) +{ + npc.EndSignal( "OnDeath" ) + FlagEnd( flagToDestroy ) + + OnThreadEnd( + function() : ( npc ) + { + if ( IsAlive( npc ) ) + npc.Destroy() + } + ) + WaitForever() +} + + +//////////////////////////////////////////////////////////////////////////////////////////////// +entity function GetClosestGrunt( entity player, var timeZone, string scriptName = "" ) +{ + if ( !IsValid( player ) ) + return + player.EndSignal( "OnDeath" ) + + vector playerOrigin = player.GetOrigin() + if ( level.timeZone != timeZone ) + playerOrigin = GetPosInOtherTimeline( player.GetOrigin() ) + + array <entity> npcs = GetNPCArray() + array <entity> grunts + string classname + foreach( npc in npcs ) + { + if ( !IsValid( npc ) ) + continue + if ( !npc.IsNPC() ) + continue + if ( !IsAlive( npc ) ) + continue + if ( ( scriptName != "" ) && ( npc.GetScriptName() != scriptName ) ) + continue + if ( !npc.IsHuman() ) + continue + + grunts.append( npc ) + } + + ArrayRemoveDead( grunts ) + if ( grunts.len() == 0 ) + return null + + entity closestDude = GetClosest( grunts, playerOrigin ) + + if ( IsValid( closestDude ) ) + return closestDude + else + return null + +} +///////////////////////////////////////////////////////////////////////////////////////// +entity function GetTimeshiftPlayer() +{ + array<entity> players = GetPlayerArray() + if ( players.len() <= 0 ) + return null + entity player = players[ 0 ] + return player +} + + + +///////////////////////////////////////////////////////////////////////////////////////// +void function GunshipSequence( string instanceName, entity player, string nodeName, string whichSequence, string flagToStart ) +{ + + entity gunship = GetEntByScriptNameInInstance( "gunship_artifact_hauler", instanceName ) + entity artifact = GetEntByScriptNameInInstance( "artifact_cargo", instanceName ) + entity harness = GetEntByScriptNameInInstance( "artifact_cargo_containment", instanceName ) + array <entity> ropeLinks = GetEntArrayByScriptNameInInstance( "harness_rope_points", instanceName ) + + foreach( ropeAttachEnt in ropeLinks ) + ropeAttachEnt.SetParent( harness ) + + gunship.SetFadeDistance( 1000000 ) + artifact.SetFadeDistance( 1000000 ) + harness.SetFadeDistance( 1000000 ) + + entity animEnt = GetEntByScriptName( nodeName ) + + + artifact.SetParent( gunship, "", true ) + harness.SetParent( gunship, "", true ) + + + string animIdle = "" + string animLeave = "" + + if ( whichSequence == "rings" ) + { + artifact.Destroy() + animIdle = "gunship_timeshift_fly_from_rings_idle" + animLeave = "gunship_timeshift_fly_from_rings" + + } + else if ( whichSequence == "pad" ) + { + animIdle = "st_AngelCity_IMC_Win_Idle" + //EmitSoundOnEntity( gunship, "Timeshift_Scr_DropshipTowingCore_Hover" ) + //animLeave = "st_AngelCity_IMC_Win_Leave" + } + else + Assert( 0, "Invalid sequence: " + whichSequence ) + + + + entity gunshipAttachEnt = CreateScriptRef( gunship.GetOrigin(), gunship.GetAngles() ) + gunshipAttachEnt.SetParent( gunship ) + local subdivisions = 15 // 25 + local slack = 25 // 25 + + wait 0.1 + + if ( whichSequence == "pad" ) + animEnt.SetOrigin( animEnt.GetOrigin() + Vector( 0, 0, -400 ) ) + + thread PlayAnimTeleport( gunship, animIdle, animEnt ) + + + wait 0.1 + + if ( whichSequence == "pad" ) + { + foreach( ropeAttachEnt in ropeLinks ) + { + + string startpointName = UniqueString( "rope_startpoint" ) + string endpointName = UniqueString( "rope_endpoint" ) + + entity rope_start = CreateEntity( "move_rope" ) + SetTargetName( rope_start, startpointName ) + rope_start.kv.NextKey = endpointName + rope_start.kv.MoveSpeed = 32 + rope_start.kv.Slack = slack + rope_start.kv.Subdiv = subdivisions + rope_start.kv.Width = "3" + rope_start.kv.TextureScale = "1" + rope_start.kv.RopeMaterial = "cable/cable.vmt" + rope_start.kv.PositionInterpolator = 2 + rope_start.SetOrigin( gunshipAttachEnt.GetOrigin() ) + + entity rope_end = CreateEntity( "keyframe_rope" ) + SetTargetName( rope_end, endpointName ) + rope_end.kv.MoveSpeed = 32 + rope_end.kv.Slack = slack + rope_end.kv.Subdiv = subdivisions + rope_end.kv.Width = "3" + rope_end.kv.TextureScale = "1" + rope_end.kv.RopeMaterial = "cable/cable.vmt" + rope_end.SetOrigin( ropeAttachEnt.GetOrigin() ) + + + DispatchSpawn( rope_start ) + DispatchSpawn( rope_end ) + + rope_start.SetParent( gunship ) + rope_end.SetParent( harness ) + } + // + + } + else + { + harness.Destroy() + + } + + + + wait 0.1 + + FlagWait( flagToStart ) + + if ( whichSequence == "rings" ) + { + EmitSoundAtPosition( TEAM_UNASSIGNED, GetEntByScriptName( "lookent_rings" ).GetOrigin(), "timeshift_scr_dropshipcoreflyover_long_muffled" ) + waitthread PlayAnim( gunship, animLeave, animEnt ) + + } + + else if ( whichSequence == "pad" ) + { + + int attachID = gunship.LookupAttachment( "REF" ) + vector attachOrg = gunship.GetAttachmentOrigin( attachID ) + vector attachAng = gunship.GetAttachmentAngles( attachID ) + entity mover = CreateScriptMover( attachOrg, attachAng ) + gunship.SetParent( mover, "", true ) + float duration = 30 + //StopSoundOnEntity( gunship, "Timeshift_Scr_DropshipTowingCore_Hover" ) + EmitSoundOnEntity( gunship, "Timeshift_Scr_DropshipTowingCore_Takeoff" ) + + mover.NonPhysicsMoveTo( gunship.GetOrigin() + Vector( 0, 0, 3200 ), duration, 0, 0 ) //, duration*0.4, duration*0.4 ) + wait duration + mover.Destroy() + } + + +} + +///////////////////////////////////////////////////////////////////////////// +void function AndersonHologramSequence( entity player, string nodeName, string flagToStart ) +{ + //Double the geo, double the fun + entity node = GetEntByScriptName( nodeName ) + entity node2 = CreateScriptRef( node.GetOrigin() + Vector( 0, 0, TIME_ZOFFSET ), node.GetAngles() ) + node2.kv.script_name = nodeName + + thread AndersonHologramSequenceThread( player, node, flagToStart ) + thread AndersonHologramSequenceThread( player, node2, flagToStart ) +} + +///////////////////////////////////////////////////////////////////////////// +void function AndersonHologramSequenceThread( entity player, entity node, string flagToStart ) +{ + player.EndSignal( "OnDeath" ) + vector origin = node.GetOrigin() + vector angles = node.GetAngles() + entity anderson = CreatePropDynamic( ANDERSON_HOLOGRAM_MODEL, origin, angles, 0 ) // 0 = no collision + anderson.kv.script_name = "anderson_holo" + entity andersonWeapon = CreatePropDynamic( ANDERSON_PISTOL_MODEL, origin, angles, 0 ) // 0 = no collision + andersonWeapon.SetSkin( 1 ) + anderson.SetSkin( 1 ) + string nodeName = node.GetScriptName() + Assert( nodeName != "" ) + + bool isOvergrownHolo = false + if ( GetEntityTimelinePosition( node ) == TIMEZONE_NIGHT ) + isOvergrownHolo = true + + anderson.Hide() + andersonWeapon.Hide() + + //string attachment = "PROPGUN" + //int attachIndex = anderson.LookupAttachment( attachment ) + //vector attachOrigin = anderson.GetAttachmentOrigin( attachIndex ) + andersonWeapon.SetParent( anderson, "PROPGUN", false, 0.0 ) + + string animAnderson + string animAndersonIdle + string animEnemy + string animEnemyIdle + entity andersonEnemy + entity enemyKnife + entity enemyGun + string flagToSetWhenDone + string flagToSetWhenPlaying + + switch( nodeName ) + { + case "node_hologram_lab1": + animAndersonIdle = "anderson_ghost_A_scene_idle" + animAnderson = "anderson_ghost_A_scene" + flagToSetWhenDone = "AndersonHologram1Finished" + flagToSetWhenPlaying = "AndersonHologram1Playing" + break + case "node_hologram_lab2": + animAndersonIdle = "anderson_ghost_B_scene_idle" + animAnderson = "anderson_ghost_B_scene" + flagToSetWhenDone = "AndersonHologram2Finished" + flagToSetWhenPlaying = "AndersonHologram2Playing" + break + case "node_hologram_lab3": + animAndersonIdle = "anderson_ghost_C_scene_idle" + animAnderson = "anderson_ghost_C_scene" + flagToSetWhenPlaying = "AndersonHologram3Playing" + andersonEnemy = CreatePropDynamic( ENEMY_HOLOGRAM_MODEL, origin, angles, 0 ) // 0 = no collision + andersonEnemy.SetSkin( 1 ) + enemyKnife = CreatePropDynamic( HOLOGRAM_KNIFE_MODEL, origin, angles, 0 ) // 0 = no collision + enemyKnife.SetSkin( 1 ) + enemyGun = CreatePropDynamic( HOLOGRAM_ENEMY_GUN_MODEL, origin, angles, 0 ) // 0 = no collision + enemyGun.SetSkin( 1 ) + enemyGun.SetParent( andersonEnemy, "PROPGUN", false, 0.0 ) + enemyKnife.SetParent( andersonEnemy, "KNIFE", false, 0.0 ) + andersonEnemy.Hide() + andersonEnemy.MakeInvisible() + enemyKnife.Hide() + enemyKnife.MakeInvisible() + animEnemy = "pt_ghost_C_scene" + animEnemyIdle = "pt_ghost_C_scene_idle" + flagToSetWhenDone = "AndersonHologram3Finished" + thread AndersonHologram3Think( anderson, andersonWeapon ) + thread AndersonEnemyHologram3Think( andersonEnemy, enemyKnife, enemyGun ) + break + default: + Assert( 0, "Unhandled nodeName: " + nodeName ) + } + + if ( IsValid( andersonEnemy) ) + { + Assert( animEnemyIdle != "" ) + thread PlayAnimTeleport( andersonEnemy, animEnemyIdle, node ) + } + Assert( animAndersonIdle != "" ) + thread PlayAnimTeleport( anderson, animAndersonIdle, node ) + + + FlagWait( flagToStart ) + + + + + + + + + //only display HUD stuff once + if ( isOvergrownHolo ) + thread DecodingLogsScreenPrint( player ) + + anderson.Show() + //spawn effects + int attachIndex = anderson.LookupAttachment( "CHESTFOCUS" ) + StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_FLASH_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex ) + StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_HEX_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex ) + + /* + int scanEffectIndex = GetParticleSystemIndex( FX_HOLO_SCAN_ENVIRONMENT ) + int particleIndex = StartParticleEffectInWorldWithHandle( scanEffectIndex, anderson.GetOrigin(), <0,0,0> ) + EffectSetControlPointVector( particleIndex, 1, <2.5,50,0> ) + */ + + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Show() + StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_FLASH_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex ) + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.25 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.5 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.25 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.01 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.25 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.03 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.2 + + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.3 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.2 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.05 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.5 + + anderson.Show() + StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_FLASH_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex ) + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.2 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.2 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Show() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + anderson.Hide() + EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER ) + wait 0.1 + + + FlagSet( flagToSetWhenPlaying ) + + EmitSoundOnEntity( anderson, "PathHologram_Materialized_3P" ) + //EmitSoundOnEntity( anderson, "PathHologram_Sustain_Loop_3P" ) + + if ( IsValid( andersonEnemy) ) + { + Assert( animEnemy != "" ) + thread PlayAnim( andersonEnemy, animEnemy, node ) + int attachIndexEnemy = andersonEnemy.LookupAttachment( "CHESTFOCUS" ) + StartParticleEffectOnEntity( andersonEnemy, GetParticleSystemIndex( FX_HOLOGRAM_HEX_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndexEnemy ) + } + anderson.Show() + andersonWeapon.Show() + Assert( animAnderson != "" ) + StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_FLASH_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex ) + waitthread PlayAnim( anderson, animAnderson, node ) + + vector andersonEndPos = anderson.GetAttachmentOrigin( attachIndex ) + + //hologram 3 we need to manually deal with Anderson disappearing at the end...all others he can just blink out + if ( nodeName != "node_hologram_lab3" ) + { + EmitSoundAtPosition( TEAM_UNASSIGNED, andersonEndPos, "AndersonHologram_Deactivate" ) + PlayFX( FX_HOLOGRAM_FLASH_EFFECT, andersonEndPos ) + } + + FlagSet( flagToSetWhenDone ) + anderson.Destroy() + if ( IsValid( andersonEnemy) ) + { + int attachIndexEnemy = andersonEnemy.LookupAttachment( "CHESTFOCUS" ) + vector andersonEnemyEndPos = andersonEnemy.GetAttachmentOrigin( attachIndexEnemy ) + PlayFX( FX_HOLOGRAM_FLASH_EFFECT, andersonEnemyEndPos ) + andersonEnemy.Destroy() + } + + + + if ( isOvergrownHolo ) + Remote_CallFunction_NonReplay( player, "ServerCallback_ClearScanningHudElem" ) + + +} +////////////////////////////////////////////////////////////////// +void function AndersonHologram3Think( entity anderson, entity andersonGun ) +{ + anderson.EndSignal( "OnDestroy" ) + + anderson.WaitSignal( "AndersonHideGun" ) + andersonGun.Destroy() + + anderson.WaitSignal( "AndersonTimeshifts" ) + + anderson.Dissolve( ENTITY_DISSOLVE_CHAR, Vector( 0, 0, 0 ), 0 ) + +} +////////////////////////////////////////////////////////////////// +void function AndersonEnemyHologram3Think( entity andersonEnemy, entity knife, entity andersonEnemyGun ) +{ + andersonEnemy.EndSignal( "OnDestroy" ) + + andersonEnemy.WaitSignal( "AndersonEnemyShow" ) + andersonEnemy.Show() + andersonEnemy.MakeVisible() + + andersonEnemy.WaitSignal( "AndersonEnemyShowKnife" ) + knife.Show() + knife.MakeVisible() +} + + +/* +////////////////////////////////////////////////////////////////// +void function TitanTimeshiftLoadoutOLD( entity player ) +{ + wait 1 + Assert( IsValid( player ) ) + entity bt = player.GetPetTitan() + Assert( IsValid( bt ) ) + //TitanLoadoutDef loadout = bt.ai.titanSpawnLoadout + //loadout.special = "mp_titanability_timeshift" + entity weapon = bt.GetOffhandWeapon( OFFHAND_SPECIAL ) + bt.TakeWeapon( weapon.GetWeaponClassName() ) + //entity offhand2weapon = bt.GetOffhandWeapon( OFFHAND_ANTIRODEO ) + //bt.TakeWeapon( offhand2weapon.GetWeaponClassName() ) + bt.GiveOffhandWeapon( "mp_titanability_timeshift", OFFHAND_SPECIAL, [] ) + LockOffhandSlot( player, OFFHAND_SPECIAL ) +} + +*/ + + + +////////////////////////////////////////////////////////////////// +void function TitanTimeshiftLoadout( entity player ) +{ + entity weapon = player.GetOffhandWeapon( OFFHAND_SPECIAL ) + if ( IsValid( weapon ) ) + { + string weaponName = weapon.GetWeaponClassName() + if ( weaponName == "mp_titanability_timeshift" ) + return + + player.TakeWeapon( weaponName ) + } + + player.GiveOffhandWeapon( "mp_titanability_timeshift", OFFHAND_SPECIAL, [] ) +// LockOffhandSlot( player, OFFHAND_SPECIAL ) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function KillMyInterdimensionalBrother( entity baseNpc, entity brotherNpc = null ) +{ + if ( !IsValid( baseNpc ) ) + return + + if ( baseNpc.IsTitan() ) + baseNpc.WaitSignal( "OnDeath" ) //may change to Doomed if not weak + else + baseNpc.WaitSignal( "OnDeath" ) + + entity brother = brotherNpc + + if ( !IsValid( brother ) ) + return + if ( !IsAlive( brother ) ) + return + if ( !brother.IsNPC() ) + return + + vector origin = brother.GetOrigin() + + TakeAllWeapons( brother ) + UnFreezeNPC( brother ) + + + if( level.timeZone == TIMEZONE_NIGHT ) + brother.TakeDamage( brother.GetMaxHealth() + 1, null, null, { damageSourceId=damagedef_suicide } ) //Kill npc manually if player can see him + else + brother.Destroy() //otherwise delete as if he never existed + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function SpawnPristineStalkersWithDopplegangers( string scriptName ) +{ + array <entity> spawners = GetEntArrayByScriptName( scriptName ) + Assert( spawners.len() > 0 ) + + + foreach( spawner in spawners ) + { + Assert( IsSpawner( spawner ) ) + entity baseStalker = spawner.SpawnEntity() + DispatchSpawn( baseStalker ) + baseStalker.kv.alwaysAlert = 1 + + + entity doppleganger = CreateZombieStalkerMossy( TEAM_IMC, baseStalker.GetOrigin() + Vector( 0, 0, ( TIME_ZOFFSET * -1 ) ), baseStalker.GetAngles() ) + DispatchSpawn( doppleganger ) + SetSquad( doppleganger, "bridge_room_overgrown" ) + doppleganger.kv.alwaysAlert = 1 + thread KillMyInterdimensionalBrother( baseStalker, doppleganger ) + } +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function SpawnAutoSecurityGroup( string scriptName ) +{ + array <entity> spawners = GetEntArrayByScriptName( scriptName ) + Assert( spawners.len() > 0 ) + array <entity> pristineRobots + array <entity> overgrownRobots + + foreach( spawner in spawners ) + { + Assert( IsSpawner( spawner ) ) + entity npc = spawner.SpawnEntity() + DispatchSpawn( npc ) + if ( GetEntityTimelinePosition( npc ) == TIMEZONE_DAY ) + pristineRobots.append( npc ) + else + overgrownRobots.append( npc ) + + } + + Assert( overgrownRobots.len() == pristineRobots.len(), "Arrays of pristine and overgrown robots are not equal: " + scriptName ) + + int i = 0 + foreach( robot in pristineRobots ) + { + thread KillMyInterdimensionalBrother( robot, overgrownRobots[ i ] ) + i++ + } +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TitanRackSpawnersThink( string scriptName ) +{ + array <entity> spawners = GetEntArrayByScriptName( scriptName ) + Assert( spawners.len() > 0 ) + entity pristineTitan + entity overgrownTitan + + foreach( spawner in spawners ) + { + Assert( IsSpawner( spawner ) ) + + array <entity> linkedEnts = spawner.GetLinkEntArray() + entity rack + foreach( ent in linkedEnts ) + { + if ( ent.GetClassName() == "info_target" ) + rack = ent + } + Assert( IsValid( rack ) ) + entity npc = spawner.SpawnEntity() + npc.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID + DispatchSpawn( npc ) + npc.EnableNPCFlag( NPC_IGNORE_ALL ) + npc.SetNoTarget( true ) + npc.SetTitle( "" ) + npc.SetValidHealthBarTarget( false ) + DisableTitanRodeo( npc ) + HideName( npc ) + //thread PlayAnimTeleport( npc, "bt_TDay_drop_titan1", rack ) + thread PlayAnimTeleport( npc, "at_titanrack_bootup_idle", rack ) //bt_TDay_drop_titan1 + //thread PlayAnim( rack, "tr_titanrack_bootup_idle" ) + + npc.s.rack <- rack + + if ( GetEntityTimelinePosition( npc ) == TIMEZONE_DAY ) + { + pristineTitan = npc + thread TitanRackHealthThink( pristineTitan, TIMEZONE_DAY ) + } + else + { + overgrownTitan = npc + thread TitanRackHealthThink( overgrownTitan, TIMEZONE_DAY ) + } + } + + thread KillMyInterdimensionalBrother( pristineTitan, overgrownTitan ) +} + + +void function TitanRackHealthThink( entity titan, var timeZone ) +{ + titan.EndSignal( "OnDeath" ) + if ( timeZone == TIMEZONE_DAY ) + FlagWait( "player_inside_bridge_room_pristine" ) + else + FlagWait( "player_inside_bridge_room_overgrown" ) + + titan.SetMaxHealth( 1000 ) + titan.SetHealth( 1000 ) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void function TitanRackDeploy( string scriptName, var timeZone ) +{ + array <entity> ents = GetEntArrayByScriptName( scriptName ) + foreach( ent in ents ) + { + if ( !IsValid( ent ) ) + continue + if ( ent.IsNPC() ) + thread TitanRackDeployThread( ent, timeZone ) + } +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function TitanRackDeployThread( entity titan, var timeZone ) +{ + if( !IsValid( titan ) ) + return + if( !IsAlive( titan ) ) + return + + titan.EndSignal( "OnDeath" ) + titan.EndSignal( "OnDestroy" ) + + if ( ( GetEntityTimelinePosition( titan ) == TIMEZONE_DAY ) && ( timeZone == TIMEZONE_NIGHT ) ) + return + if ( ( GetEntityTimelinePosition( titan ) == TIMEZONE_NIGHT ) && ( timeZone == TIMEZONE_DAY ) ) + return + + float animLength = titan.GetSequenceDuration( "at_titanrack_bootup" ) + entity rack = titan.GetLinkEnt() + Assert( IsValid( rack ) ) + thread PlayAnim( titan, "at_titanrack_bootup", rack ) + thread PlayAnim( rack, "tr_titanrack_bootup" ) + titan.SetNoTarget( false ) + + wait animLength + + titan.DisableNPCFlag( NPC_IGNORE_ALL ) +} + + +void function HideCritTimeshift( entity ent ) +{ + int bodyGroupIndex = ent.FindBodyGroup( "hitpoints" ) + + if ( bodyGroupIndex == -1 ) + { + return + } + + ent.SetBodygroup( bodyGroupIndex, 1 ) +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function RingsThink() +{ + entity rings = GetEntByScriptName( "rings_pristine" ) + if ( !IsValid( rings ) ) + return + + rings.EndSignal( "OnDestroy" ) + thread PlayAnim( rings, "idle" ) + + FlagWait( "RingsShouldBeSpinning" ) + + string spinAnim = "animated_slow" + if ( Flag( "player_back_in_amenities_lobby" ) ) + spinAnim = "animated" + + //rings.Anim_Stop() + thread PlayAnim( rings, spinAnim ) + +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function PlayerConversationStopOnFlagImmediate( string name, entity player, string flagToAbort ) +{ + Assert( flagToAbort != "" ) + waitthread PlayerConversationStopOnFlag( name, player, flagToAbort, true ) +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function PlayerConversationStopOnFlag( string name, entity player, string flagToAbort = "", bool immediate = false ) +{ + if ( flagToAbort != "" ) + { + if ( Flag( flagToAbort ) ) + return + + FlagEnd( flagToAbort ) + } + + OnThreadEnd( + function() : ( player, immediate, flagToAbort ) + { + if ( flagToAbort == "" ) + return + if ( !IsValid( player ) ) + return + if ( immediate == false ) + StopConversation( player ) + else if ( immediate == true ) + StopConversationNow( player ) + } + ) + + thread PlayerConversation( name, player ) + WaitSignal( player, "ConversationEnded" ) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function FlagSetDelayed( string flagToSet, float delay ) +{ + thread FlagSetDelayedThread( flagToSet, delay ) +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function FlagSetDelayedThread( string flagToSet, float delay ) +{ + wait delay + FlagSet( flagToSet ) +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function FlagClearDelayed( string flagToClear, float delay ) +{ + thread FlagClearDelayedThread( flagToClear, delay ) +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function FlagClearDelayedThread( string flagToClear, float delay ) +{ + wait delay + FlagClear( flagToClear ) +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function DecodingLogsScreenPrint( entity player ) +{ + Remote_CallFunction_NonReplay( player, "ServerCallback_ShowHoloDecoding" ) + +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void function AudioLogModelThink( entity audioLogModel ) +{ + wait 0.25 + + file.audioLogModels.append( audioLogModel ) + audioLogModel.SetFadeDistance( 2048 ) + EmitSoundAtPosition( TEAM_UNASSIGNED, audioLogModel.GetOrigin(), "LSTAR_Reloading_Beep_low" ) + audioLogModel.Highlight_ShowInside( 1.0 ) + audioLogModel.Highlight_ShowOutline( 1.0 ) + + string instanceName = audioLogModel.GetInstanceName() + string audioLogAlias + int logIndex + bool isTempAudioLog = false + bool isBoyleAudioLog = false + int boyleAudioLogNumber = 0 + int boyleLogInstanceNumber = 0 + + Assert( instanceName != "", "Audio log has no instance name: " + audioLogModel.GetOrigin() ) + + if ( instanceName == "audiolog_lobby_overgrown" ) + { + // Audio Log - Scientist 2 The trial-run of the Sculptor Core will continue as planned, but you have to get security to evacuate all Tier 1 personnel. + // General Marder and his key team members are transferring to remote observation. + audioLogAlias = "diag_sp_anderson_TS171_02_01_imc_scientist2" + logIndex = 1 + } + else if ( instanceName == "audiolog_security_overgrown" ) + { + // Dr. Alexander Darren log fourteen point six. The intruder has some kind of advanced tech and is slaughtering our response teams. + //Tyler in Wildlife Research said 2 teams were taken out at the elevator banks in a matter of seconds...by one guy! + audioLogAlias = "diag_sp_anderson_TS171_01_01_imc_scientist1" + logIndex = 2 + } + else if ( instanceName == "audiolog_lecture_overgrown" ) + { + + bool doLongSpeechAtLectern = false + + if ( doLongSpeechAtLectern == true ) + { + // Full ted talk, 1-28 + instanceName = "audiolog_ted_talk" + audioLogAlias = "tedTalk01" + logIndex = 9 + } + else + { + //General Marder + //But lest we lose sight of the bigger picture - remember those losses are ultimately + //replaceable by the inexorable march of human civilization. + audioLogAlias = "lectureLogA" + logIndex = 3 + } + + } + else if ( instanceName == "audiolog_upper_hub1_overgrown" ) + { + + //Audio log 4 This is Dr. Colby Marvin. I don't know how to explain it, but a Vanguard-Class Titan just appeared out of nowhere. The test is still underway. It will be completed. + audioLogAlias = "diag_sp_ambScience_TS551_09_01_imc_sci" + logIndex = 4 + + } + else if ( instanceName == "audiolog_upper_hub2_overgrown" ) + { + //Audio log 5 Dr. Altamirano log seven point six. General Marder is gone. He's making us stay to complete the test. I don't trust this thing. The Ark is unstable. + audioLogAlias = "diag_sp_ambScience_TS551_10_01_imc_sci" + logIndex = 5 + + } + else if ( IsAudioLogBoyle( instanceName ) ) + { + isBoyleAudioLog = true + logIndex = 6 + boyleLogInstanceNumber = GetBoyleLogInstanceNumber( instanceName ) + + if ( file.debugAudioLogs ) + thread DebugDrawAudioLogNumber( audioLogModel, file.boyleAudioLogNumberAssignments[ boyleLogInstanceNumber ].tostring() ) + } + + else if ( instanceName == "audiolog_humanroom" ) + { + + //Marders Log 21b - Human specimen 3 point 4. The experiments on the IMS Odyssey's colonists are underway. + //Soon we will discover the long lasting effects the Ark has on organic matter and brain function. + audioLogAlias = "diag_sp_audioLog_TS132_01_01_imc_genMarder" + logIndex = 7 + + } + else if ( instanceName == "audiolog_humanroom_tower" ) + { + //Audio log 7 Dr. Ehrenberg log eleven point four. Further research still leaves questions about the Fold Weapon and its intended purpose. + //I don't think we're using it right and that may cause a problem. Marder thinks it's worth it. Well I'm going on record - this is a bad idea. + audioLogAlias = "diag_sp_ambScience_TS551_12_01_imc_sci" + logIndex = 8 + } + + else if ( instanceName == "audiolog_ted_talk" ) + { + // Full ted talk, 1-28 + audioLogAlias = "tedTalk01" + logIndex = 9 + } + + + else + Assert( 0, "Unhandled audio log instance name: " + instanceName ) + + + audioLogModel.SetUsable() + audioLogModel.SetUsableByGroup( "pilot" ) + audioLogModel.SetUsePrompts( "#TIMESHIFT_HINT_AUDIO_LOG" , "#TIMESHIFT_HINT_AUDIO_LOG_PC" ) + local playerActivator + while( true ) + { + playerActivator = audioLogModel.WaitSignal( "OnPlayerUse" ).player + if ( IsValid( playerActivator ) && playerActivator.IsPlayer() ) + break + } + + FlagSet( "AudioLogPlaying" ) + + EmitSoundAtPosition( TEAM_UNASSIGNED, audioLogModel.GetOrigin(), "LSTAR_Reloading_Beep_low" ) + audioLogModel.UnsetUsable() + audioLogModel.Highlight_HideInside( 0 ) + audioLogModel.Highlight_HideOutline( 0 ) + + wait 0.5 + + entity player = GetPlayerArray()[ 0 ] + if ( !IsValid( player ) ) + return + + player.EndSignal( "OnDeath" ) + + + //Remote_CallFunction_NonReplay( player, "ServerCallback_ShowHoloDecoding", logIndex ) + + //wait 2 + + thread StopAudioLogWhenPlayerFarAway( audioLogModel ) + + audioLogModel.EndSignal( "StopAudioLog" ) + + OnThreadEnd( + function() : ( player, audioLogModel ) + { + Remote_CallFunction_NonReplay( player, "ServerCallback_ClearScanningHudElem" ) + FlagClear( "AudioLogPlaying" ) + if ( IsValid( audioLogModel ) ) + { + audioLogModel.Signal( "StopAudioLog" ) + EmitSoundAtPosition( TEAM_UNASSIGNED, audioLogModel.GetOrigin(), "LSTAR_Reloading_Beep_low" ) + delaythread( 2 ) AudioLogModelThink( audioLogModel ) + } + + + } + ) + + + if ( isBoyleAudioLog ) + { + boyleAudioLogNumber = GetBoyleLogNumber( boyleLogInstanceNumber, audioLogModel ) + file.boyleAudioLogNumberAssignments[ boyleLogInstanceNumber ] = boyleAudioLogNumber + } + else + waitthread PlayTimeShiftDialogue( player, audioLogModel, audioLogAlias ) + + + + + //------------------------------------------------ + // If this is the lecture hall, play the tail lines + //------------------------------------------------- + if ( instanceName == "audiolog_lecture_overgrown" ) + { + if ( !Flag( "PlayerInterruptedLecture") ) + { + + //General Marder By decisively neutralizing the Militia forces, + //we will in fact, safeguard the existence of the human race, extending its reach and power towards a prosperous, and bright future. + waitthread PlayTimeShiftDialogue( player, audioLogModel, "lectureLogB" ) + } + else + { + entity soundDummy = CreateLoudspeakerEnt( audioLogModel.GetOrigin() ) + + //General Marder By decisively neutralizing the Militia forces, + //we will in fact, safeguard the existence of the human race, extending its reach and power towards a prosperous, and bright future. + thread PlayTimeShiftDialogue( player, soundDummy, "lectureLogB" ) + wait file.lectureHallTimeBeforePlayerInterrupts + + //General Marder Yes, test Pilot? May I help you? + soundDummy.Destroy() + waitthread PlayTimeShiftDialogue( player, audioLogModel, "lectureLogC" ) + + } + } + + //------------------------------------------------ + // If this is a Boyle log, play all the aliases for the log number + //------------------------------------------------- + else if ( isBoyleAudioLog ) + { + array <string> aliases + if ( boyleAudioLogNumber == 1 ) + { + //--------------------------------------- + // Boyle audio logs: 1 + //--------------------------------------- + //Dr. Jefferson Boyle - Log one. Looks like they went forward with the Ark test despite my warnings to postpone, but what Marder wants - Marder gets. + aliases.append( "diag_sp_BoyleLog1_TS701_01_01_imc_boyle" ) + + //I don’t know how I survived, but I did...for now. I don’t know how I survived, but I did...for now. + aliases.append( "diag_sp_BoyleLog1_TS701_02_01_imc_boyle" ) + + //I’ve tried all exits but I’m trapped; damn place is locked down good. All I have is Hope. That’s what I get for picking a lab underground... What can say? I like archaeology. + aliases.append( "diag_sp_BoyleLog1_TS701_03_01_imc_boyle" ) + } + else if ( boyleAudioLogNumber == 2 ) + { + + //--------------------------------------- + // Boyle audio logs: 2 + //--------------------------------------- + //Dr. Jefferson Boyle Log 2 Dr. Jefferson Boyle - Log two. I found myself a standard IMC survival kit, which provides me with enough flavorless rations to keep me alive for a few days. Dr. Jefferson Boyle. Log two. I found myself a standard IMC survival kit, which provides me with enough flavorless rations to keep me alive for a few days. + aliases.append( "diag_sp_BoyleLog2_TS701_04_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 2 I’m hoping that’s all I need otherwise I’m going to have to get creative. I hate getting creative... I’m hoping that’s all I need otherwise I’m going to have to get creative. I hate getting creative... + aliases.append( "diag_sp_BoyleLog2_TS701_05_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 2 I hate getting creative... + //aliases.append( "diag_sp_BoyleLog2_TS701_06_01_imc_boyle" ) + + } + else if ( boyleAudioLogNumber == 3 ) + { + + //--------------------------------------- + // Boyle audio logs: 3 + //--------------------------------------- + //Dr. Jefferson Boyle Log 3 Dr. Jefferson Boyle - Log three. I had to get creative. Failed experiments on Typhon’s indigenous wildlife are unfortunately next door...in other words, I cooked a prowler. Dr. Jefferson Boyle. Log three. I had to get creative. Failed experiments on Typhon’s indigenous wildlife are unfortunately next door...in other words, I cooked a prowler. + aliases.append( "diag_sp_BoyleLog3_TS701_07_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 3 It tasted like chicken if chicken was a weird dinosaur-like creature injected with IMC meds and steroids. It tasted like chicken if chicken was a weird dinosaur-like creature injected with IMC meds and steroids. + aliases.append( "diag_sp_BoyleLog3_TS701_08_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 3 On a lighter note, I think I found a way to upload these logs to what's left of the IMC network here. Maybe someone's monitoring...here's to Hope. On a lighter note, I think I found a way to upload these logs to what's left of the IMC network here. Maybe someone's monitoring...here's to Hope. + aliases.append( "diag_sp_BoyleLog3_TS701_09_01_imc_boyle" ) + + } + else if ( boyleAudioLogNumber == 4 ) + { + //--------------------------------------- + // Boyle audio logs: 4 + //--------------------------------------- + //Dr. Jefferson Boyle Log 4 Dr. Jefferson Boyle - Log four. All right, my logs are on the network but unfortunately, I am out of Prowler so I'm pretty disappointed. Dr. Jefferson Boyle. Log four. All right, my logs are on the network but unfortunately, I am out of Prowler so I'm pretty disappointed. + aliases.append( "diag_sp_BoyleLog4_TS701_10_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 4 I’ve moved on to the vines and plants growing throughout this facility. I’ve moved on to the vines and plants growing throughout this facility. + aliases.append( "diag_sp_BoyleLog4_TS701_11_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 4 It’s raining non-stop so I have water, at least I think it’s water. It’s raining non-stop so I have water, at least I think it’s water. + aliases.append( "diag_sp_BoyleLog4_TS701_12_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 4 Yeah, it's water. It's definitely water....I think. Um, I gotta go run some tests... Yeah, it's water. It's definitely water...I think. Um, I got to go run some tests... + aliases.append( "diag_sp_BoyleLog4_TS701_13_01_imc_boyle" ) + + } + else if ( boyleAudioLogNumber == 5 ) + { + //--------------------------------------- + // Boyle audio logs: 5 + //--------------------------------------- + + //Dr. Jefferson Boyle Log 5 Dr. Jefferson Boyle - Log five. Okay, good news, tests came back negative...it is water. Bad news... Literally everything else. Nothing's good. Dr. Jefferson Boyle. Log five. Okay, good news, tests came back negative...it is water. Bad news... Literally everything else. Nothing's good. + aliases.append( "diag_sp_BoyleLog5_TS701_14_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 5 I think it's time I get out of this place. I've managed to breach a hole in the wall, it leads up to the main campus. I think it's time I get out of this place. I've managed to breach a hole in the wall, it leads up to the main campus. + aliases.append( "diag_sp_BoyleLog5_TS701_15_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 5 I'm about 100 meters underground. I have no idea how long it'll take me to get to the top because I'm horrible at math, but I am an archaeologist; I've done some climbing before. I'm about 100 meters underground. I have no idea how long it'll take me to get to the top because I'm horrible at math, but I am an archaeologist; I've done some climbing before. + aliases.append( "diag_sp_BoyleLog5_TS701_16_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 5 I just don't know what I'll find when I get up there but I have no choice. Okay, I have a choice but right now, it's not to die. I just don't know what I'll find when I get up there but I have no choice. Okay, I have a choice but right now, it's not to die. + aliases.append( "diag_sp_BoyleLog5_TS701_17_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 5 So, that's it. This is my final entry. Wish me luck. So, that's it. This is my final entry. Wish me luck. + aliases.append( "diag_sp_BoyleLog5_TS701_18_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 5 See you soon, Hope. See you soon, Hope. + aliases.append( "diag_sp_BoyleLog5_TS701_19_01_imc_boyle" ) + + //Dr. Jefferson Boyle Log 5 Dr. Jefferson Boyle - Signing Off. Err.. over and out. Is that how you say it? Whatever... bye. Dr. Jefferson Boyle. Signing Off. Err.. over and out. Is that how you say it? Whatever... bye. + aliases.append( "diag_sp_BoyleLog5_TS701_20_01_imc_boyle" ) + + } + else + Assert( 0, "Invalid Boyle audio log number " + boyleAudioLogNumber ) + + foreach( alias in aliases ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, alias ) + } + + + + + //------------------------------------------------------------- + // If this is the full ted talk, play the whole damn thing + //-------------------------------------------------------------- + else if ( instanceName == "audiolog_ted_talk" ) + { + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk02" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk03" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk04" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk05" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk06" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk07" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk08" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk09" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk10" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk11" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk12" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk13" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk14" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk15" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk16" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk17" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk18" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk19" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk20" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk21" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk22" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk23" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk24" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk25" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk26" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk27" ) + waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk28" ) + } +} + +/////////////////////////////////////////////////////////////////////////// +void function StopAudioLogWhenPlayerFarAway( entity audioLogModel ) +{ + if ( !IsValid( audioLogModel ) ) + return + + audioLogModel.EndSignal( "StopAudioLog" ) + + array<entity> players = GetPlayerArray() + if ( players.len() == 0 ) + return + + entity player = players[ 0 ] + if ( !IsValid( player ) ) + return + + var timeZoneAudioLog = GetEntityTimelinePosition( audioLogModel ) + player.EndSignal( "OnDeath" ) + + while( true ) + { + wait 0.25 + if ( timeZoneAudioLog != level.timeZone ) //don't do distance check if we are in other timezone + continue + if ( !PlayerInRange( player.GetOrigin(), audioLogModel.GetOrigin(), DIST_TO_NOT_CARE_ABOUT_AUDIOLOGS ) ) + break + } + + + audioLogModel.Signal( "StopAudioLog" ) +} +/////////////////////////////////////////////////////////////////////////// +bool function IsAudioLogBoyle( string instanceName ) +{ + if ( instanceName.find( "audiolog_boyle" ) == null ) + return false + return true + +} +/////////////////////////////////////////////////////////////////////////// +int function GetBoyleLogInstanceNumber( string instanceName ) +{ + int instanceNumber = -1 + + if ( instanceName == "audiolog_boyle_0" ) + instanceNumber = 0 + else if ( instanceName == "audiolog_boyle_1" ) + instanceNumber = 1 + else if ( instanceName == "audiolog_boyle_2" ) + instanceNumber = 2 + else if ( instanceName == "audiolog_boyle_3" ) + instanceNumber = 3 + else if ( instanceName == "audiolog_boyle_4" ) + instanceNumber = 4 + else + Assert( 0, "Unhandled instance name " + instanceName ) + + Assert( instanceNumber > -1 ) + + return instanceNumber +} +/////////////////////////////////////////////////////////////////////////// +int function GetBoyleLogNumber( int instanceNumber, entity audioLogModel ) +{ + int logNumber = file.boyleAudioLogNumberAssignments[ instanceNumber ] + if ( logNumber == 0 ) + logNumber = GetNextBoyleLogNumber( audioLogModel ) + + return logNumber + +} + +/////////////////////////////////////////////////////////////////////////// +int function GetNextBoyleLogNumber( entity audioLogModel ) +{ + int logNumber = file.boyleAudioLogsCollected + 1 + Assert( logNumber > 0 && logNumber < 6, "Boyle log at " + audioLogModel.GetOrigin() + " is trying to get assigned a number greater than 5: " + logNumber ) + file.boyleAudioLogsCollected++ + + return logNumber +} +/////////////////////////////////////////////////////////////////////////// +void function TempAudioLogDevMsg( entity player ) +{ + Dev_PrintMessage( player, "#BLANK_TEXT", "#AUDIOLLOG_TEMPTEXT_TIMESHIFT", 3.0 ) +} + +//////////////////////////////////////////////////////////////////////////// +void function InitBoyleAudioLogs() +{ + LevelTransitionStruct ornull trans = GetLevelTransitionStruct() + if ( trans == null ) + return + + expect LevelTransitionStruct( trans ) + file.boyleAudioLogsCollected = trans.boyleAudioLogsCollected + + for ( int i = 0 ; i < file.boyleAudioLogNumberAssignments.len(); i++ ) + { + file.boyleAudioLogNumberAssignments[ i ] = trans.boyleAudioLogNumberAssignments[ i ] + } +} + +//////////////////////////////////////////////////////////////////////////// +LevelTransitionStruct function SaveBoyleAudioLogs() +{ + LevelTransitionStruct trans + trans.boyleAudioLogsCollected = file.boyleAudioLogsCollected + + for ( int i = 0 ; i < trans.boyleAudioLogNumberAssignments.len(); i++ ) + { + trans.boyleAudioLogNumberAssignments[ i ] = file.boyleAudioLogNumberAssignments[ i ] + } + + return trans +} + + +/////////////////////////////////////////////////////////////////////////// +void function SetLectureHallLineDuration( float duration ) +{ + file. lectureHallTimeBeforePlayerInterrupts = duration +} +/////////////////////////////////////////////////////////////////////////// + +void function ProwlersAmbientThink( entity npc ) +{ + npc.EnableNPCFlag( NPC_IGNORE_ALL ) + npc.SetNoTarget( true ) +} +/////////////////////////////////////////////////////////////////////////// + +void function LightFlickerThink( entity lightModelOff ) +{ + entity lightModelOn = lightModelOff.GetLinkEnt() + Assert( IsValid( lightModelOn ), "Light model at " + lightModelOff.GetOrigin() + " needs to target a lit version of the same model" ) + + lightModelOn.Hide() + entity fx + + while( true ) + { + + wait RandomFloatRange( 0.5, 0.6 ) + lightModelOn.Show() + lightModelOff.Hide() + fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn ) + EmitSoundOnEntity( lightModelOn, SOUND_LIGHT_FLICKER ) + + wait RandomFloatRange( 0.3, 0.4 ) + lightModelOff.Show() + lightModelOn.Hide() + EntFireByHandle( fx, "Stop", "", 0, null, null ) + + wait RandomFloatRange( 0.5, 0.6 ) + lightModelOn.Show() + lightModelOff.Hide() + fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn ) + + wait RandomFloatRange( 0.5, 0.6 ) + lightModelOff.Show() + lightModelOn.Hide() + EntFireByHandle( fx, "Stop", "", 0, null, null ) + + wait RandomFloatRange( 0.5, 0.6 ) + lightModelOn.Show() + lightModelOff.Hide() + fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn ) + + + wait RandomFloatRange( 0.01, 0.02 ) + lightModelOff.Show() + lightModelOn.Hide() + EntFireByHandle( fx, "Stop", "", 0, null, null ) + + wait RandomFloatRange( 0.01, 0.02 ) + lightModelOn.Show() + lightModelOff.Hide() + fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn ) + EmitSoundOnEntity( lightModelOn, SOUND_LIGHT_FLICKER ) + + wait RandomFloatRange( 0.02, 0.03 ) + lightModelOff.Show() + lightModelOn.Hide() + EntFireByHandle( fx, "Stop", "", 0, null, null ) + + wait RandomFloatRange( 1, 1.1 ) + lightModelOn.Show() + lightModelOff.Hide() + fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn ) + + + wait RandomFloatRange( 0.2, 0.3 ) + lightModelOff.Show() + lightModelOn.Hide() + EntFireByHandle( fx, "Stop", "", 0, null, null ) + + wait RandomFloatRange( 1, 1.1 ) + lightModelOn.Show() + lightModelOff.Hide() + fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn ) + EmitSoundOnEntity( lightModelOn, SOUND_LIGHT_FLICKER ) + + wait RandomFloatRange( 0.2, 0.3 ) + lightModelOff.Show() + lightModelOn.Hide() + EntFireByHandle( fx, "Stop", "", 0, null, null ) + + wait RandomFloatRange( 0.2, 0.3 ) + lightModelOn.Show() + lightModelOff.Hide() + fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn ) + + + + wait RandomFloatRange( 0.01, 0.02 ) + lightModelOff.Show() + lightModelOn.Hide() + EntFireByHandle( fx, "Stop", "", 0, null, null ) + + wait RandomFloatRange( 0.01, 0.02 ) + lightModelOn.Show() + lightModelOff.Hide() + fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn ) + EmitSoundOnEntity( lightModelOn, SOUND_LIGHT_FLICKER ) + + wait RandomFloatRange( 0.01, 0.02 ) + lightModelOff.Show() + lightModelOn.Hide() + EntFireByHandle( fx, "Stop", "", 0, null, null ) + + wait RandomFloatRange( 0.01, 0.02 ) + lightModelOn.Show() + lightModelOff.Hide() + fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn ) + + + wait RandomFloatRange( 0.02, 0.03 ) + lightModelOff.Show() + lightModelOn.Hide() + EntFireByHandle( fx, "Stop", "", 0, null, null ) + + + } + +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +void function PlayerDropLand( entity player, entity node, bool doBlur = false ) +{ + entity moverNode = CreateScriptMover( node.GetOrigin(), node.GetAngles() ) + + float targetZpos = node.GetOrigin().z + 16 + + while( player.GetOrigin().z > targetZpos ) + WaitFrame() + + player.UnfreezeControlsOnServer() + player.ClearInvulnerable() + + string anim3rd= "pt_timeshift_fall_land" + string animPOV = "ptpov_timeshift_fall_land" + + player.SetAnimNearZ( 3 ) + + if ( doBlur ) + Remote_CallFunction_Replay( player, "ServerCallback_FanDropBlur" ) + + //PlayFPSAnimTeleportShowProxy( player, anim3rd, anim1st ref = null optionalTag, animView = null, float initialTime = 0.0 ) + waitthread PlayFPSAnimTeleportShowProxy( player, anim3rd, animPOV, moverNode, "REF", ViewConeTight ) + //wait 2 + player.ClearAnimNearZ() + player.ClearParent() + moverNode.Destroy() + player.UnforceStand() + player.Anim_Stop() + ClearPlayerAnimViewEntity( player ) + player.ClearParent() + player.EnableWeaponWithSlowDeploy() +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void function RingsLocalExplosionNormal( entity rings ) +{ + if ( level.timeZone == TIMEZONE_NIGHT ) + return + vector origin = rings.GetOrigin() + //CreateShake( org, amplitude = 16, frequency = 150, duration = 1.5, radius = 2048 ) + thread CreateAirShake( origin, 4, 10, 1, 32000 ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function RingsLocalExplosionBig( entity rings ) +{ + if ( level.timeZone == TIMEZONE_NIGHT ) + return + + vector origin = rings.GetOrigin() + //CreateShake( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 ) + thread CreateAirShake( origin, 4, 10, 1, 32000 ) +} + + + +///////////////////////////////////////////////////////////////////////////////////////// +void function CreateShakeTimeshift( float amplitude, float frequency, float duration ) +{ + array<entity> players = GetPlayerArray() + if ( players.len() == 0 ) + return + + entity player = players[ 0 ] + if ( !IsValid( player ) ) + return + + CreateAirShake( player.GetOrigin(), amplitude, frequency, duration ) + //Remote_CallFunction_Replay( player, "ServerCallback_ScreenShake", amplitude, frequency, duration ) +} +///////////////////////////////////////////////////////////////////////////////////////// +void function CreateShakeWhileFlagSet( float amplitude, float frequency, float duration, string flagToShake, string flagToAbort ) +{ + array<entity> players = GetPlayerArray() + if ( players.len() == 0 ) + return + + entity player = players[ 0 ] + if ( !IsValid( player ) ) + return + + player.EndSignal( "OnDeath" ) + FlagEnd( flagToAbort ) + + while ( true ) + { + wait 0.1 + if ( Flag( flagToShake ) ) + { + CreateAirShake( player.GetOrigin(), amplitude, frequency, duration ) + //Remote_CallFunction_Replay( player, "ServerCallback_ScreenShake", amplitude, frequency, duration ) + wait duration / 4 + } + } + +} + +void function TitanTimeshiftHint( entity player ) +{ + Remote_CallFunction_Replay( player, "ServerCallback_ShowTitanTimeshiftHint" ) +} + +///////////////////////////////////////////////////////////////////////////// +string function GetrandomDeathPose( asset deathModel ) +{ + if ( deathModel == MARVIN_MODEL_OVERGROWN ) + return "mv_timeshift_death_pose_01" + + array <string> deathPosesLocal = file.deathPoses + deathPosesLocal.randomize() + return deathPosesLocal[ 0 ] +} + +///////////////////////////////////////////////////////////////////////////// +void function FlyerAmbientThink( entity flyer ) +{ + if ( GetMapName() == "sp_hub_timeshift" ) + return + + flyer.EndSignal( "OnDeath" ) + flyer.EndSignal( "OnDestroy" ) + + FlagWait( "ForceFlyerTakeoff" ) + flyer.Signal( "FlyerStopThink" ) + //flyer.WaitSignal( "FlyerTakeoffOverride" ) + wait RandomFloatRange( 0, 6 ) + thread FlyerTakeOff( flyer ) +} + +void function DropshipSpawnAndRepeat( entity dropship ) +{ + dropship.EndSignal( "OnDeath" ) + dropship.EndSignal( "OnDestroy" ) + dropship.EndSignal( "OnAnimationInterrupted" ) + entity animNode = dropship.GetLinkEnt() + Assert( IsValid( animNode ) ) + string anim = expect string( animNode.kv.leveled_animation ) + float animLength = dropship.GetSequenceDuration( anim ) + Assert( IsValid( anim ) ) + + bool repeat = true + if ( dropship.GetScriptName() == "dropships_skybridge" ) + repeat = false + + OnThreadEnd( + function() : ( dropship ) + { + if ( IsValid( dropship ) ) + dropship.Destroy() + } + ) + + while( true ) + { + + wait animLength + + if ( repeat == false ) + break + + } + +} + + +void function DisableNavmeshSeperatorTargetedByEnt( entity doorModel ) +{ + array <entity> linkedEnts = doorModel.GetLinkEntArray() + Assert( linkedEnts.len() > 0 ) + string classname + entity navmeshBrush + + foreach( entity ent in linkedEnts ) + { + classname = GetEditorClass( ent ) + if ( classname == "func_brush_navmesh_separator" ) + { + navmeshBrush = ent + break + } + } + + Assert( IsValid( navmeshBrush ), "Entity at " + doorModel.GetOrigin() + " isn't targeting a func_brush_navmesh_separator" ) + + //navmeshBrush.Hide() + navmeshBrush.NotSolid() + ToggleNPCPathsForEntity( navmeshBrush, true ) + + +} + + + + +void function ElectricalScreenEffects( entity player, string enabledFlag = "" ) +{ + EndSignal( player, "OnDeath" ) + EndSignal( player, "StopCoreEffects" ) + OnThreadEnd( + function() : ( player ) + { + if ( IsValid ( player ) ) + StopSoundOnEntity( player, EMP_IMPARED_SOUND ) + } + ) + + if ( enabledFlag != "" ) + { + if ( !Flag( enabledFlag ) ) + FlagWait( enabledFlag ) + } + + array<entity> ents = GetEntArrayByScriptName( "BeaconScreenEffect" ) + array<vector> start + array<vector> end + array<float> radius + foreach( entity ent in ents ) + { + start.append( ent.GetOrigin() ) + end.append( ent.GetLinkEnt().GetOrigin() ) + radius.append( float( ent.kv.radius ) ) + } + + bool soundPlaying + float maxAmount + vector p + while( true ) + { + maxAmount = 0 + p = player.GetOrigin() + for ( int i = 0 ; i < ents.len() ; i++ ) + { + float d = GetDistanceFromLineSegment( start[i], end[i], p ) + float amount = GraphCapped( d, 0.0, radius[i], 1.0, 0.0 ) + maxAmount = max( amount, maxAmount ) + } + + if ( maxAmount > 0 ) + { + StatusEffect_AddTimed( player, eStatusEffect.emp, maxAmount, 0.25, 0.05 ) + if ( !soundPlaying ) + { + EmitSoundOnEntity( player, EMP_IMPARED_SOUND ) + soundPlaying = true + } + } + else if ( soundPlaying ) + { + StopSoundOnEntity( player, EMP_IMPARED_SOUND ) + soundPlaying = false + } + + wait 0.1 + } +} + + +void function GivePropForAnim( entity npc, string anim ) +{ + if ( !IsValid( npc ) ) + return + + string tagName + asset model + + if ( anim == "pt_lecture_student_1_idle" ) + { + tagName = "L_HAND" + model = MODEL_IPAD + } + + else if ( anim == "pt_lecture_student_2_idle" ) + { + tagName = "R_HAND" + model = MODEL_COFFEE + } + + else if ( anim == "pt_lecture_student_5_idle" ) + { + tagName = "L_HAND" + model = MODEL_IPAD + } + else if ( anim == "pt_civ_walk_tablet" ) + { + tagName = "R_HAND" + model = MODEL_IPAD + } + + else if ( anim == "pt_civ_walk_tablet_reading" ) + { + tagName = "R_HAND" + model = MODEL_IPAD + } + + else if ( anim == "pt_civ_walk_drink" ) + { + tagName = "R_HAND" + model = MODEL_COFFEE + } + + else + return + + entity prop = CreatePropDynamic( model ) + prop.SetParent( npc, tagName, false ) +} + + + +void function LoudspeakerThread( entity player ) +{ + + //if ( GetBugReproNum() != 202020 ) + //return + + if ( !IsValid( player ) ) + return + + if ( file.loudspeakerThreadRunning ) + return + + FlagSet( "ShouldPlayGlobalLoudspeker" ) + + file.loudspeakerThreadRunning = true + + player.EndSignal( "OnDeath" ) + + wait 3 + + array <string> arrayLoudspeakerLines + arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_01_01_imc_sci" ) + arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_02_01_imc_sci" ) + arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_03_01_imc_sci" ) + arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_04_01_imc_sci" ) + arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_05_01_imc_sci" ) + arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_06_01_imc_sci" ) + arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_07_01_imc_sci" ) + arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_08_01_imc_sci" ) + + int numberOfLoudspeakerLines = arrayLoudspeakerLines.len() -1 + + entity loudspeakerEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY ) + int loudspeakerLineCount = 0 + //bool playSpectreLine = false + + + while( true ) + { + waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY ) + + if ( !Flag( "ShouldPlayGlobalLoudspeker" ) ) + { + FlagWait( "ShouldPlayGlobalLoudspeker" ) + continue + } + + loudspeakerEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY, loudspeakerEnt ) + + waitthread PlayTimeShiftDialogue( player, loudspeakerEnt, "Timeshift_Scr_AnnouncementChime" ) + + //if ( playSpectreLine ) + //waitthread PlayTimeShiftDialogue( player, loudspeakerEnt, "Timeshift_Scr_SpectreAnnouncement" ) + + waitthread PlayTimeShiftDialogue( player, loudspeakerEnt, arrayLoudspeakerLines[ loudspeakerLineCount ] ) + loudspeakerLineCount++ + if ( loudspeakerLineCount > numberOfLoudspeakerLines ) + loudspeakerLineCount = 0 + + + wait RandomFloatRange( 60, 70 ) + + /* + if ( playSpectreLine == false ) + playSpectreLine = true + else if ( playSpectreLine == true ) + playSpectreLine = false + */ + } +} + + +void function ButtonOvergrownThink( entity propDynamic, string whichButton ) +{ + asset swapModelName + entity swapModel + + if ( whichButton == "small" ) + swapModelName = MODEL_BUTTON + else + swapModelName = MODEL_BUTTON_LARGE + + swapModel = CreatePropDynamic( swapModelName, propDynamic.GetOrigin(), propDynamic.GetAngles() ) + + while( true ) + { + swapModel.Hide() + propDynamic.Show() + + wait RandomFloatRange( 2, 3 ) + + swapModel.Show() + propDynamic.Hide() + + wait 0.2 + + swapModel.Hide() + propDynamic.Show() + + wait 0.1 + + swapModel.Show() + propDynamic.Hide() + + wait 0.2 + + swapModel.Hide() + propDynamic.Show() + + wait 0.05 + + swapModel.Show() + propDynamic.Hide() + + wait 0.05 + + swapModel.Hide() + propDynamic.Show() + + wait 0.05 + + swapModel.Show() + propDynamic.Hide() + + wait 0.4 + + swapModel.Hide() + propDynamic.Show() + + wait 0.1 + + swapModel.Show() + propDynamic.Hide() + + wait 0.2 + + swapModel.Hide() + propDynamic.Show() + + wait 0.05 + + swapModel.Show() + propDynamic.Hide() + + } +} + + +entity function CreateTimeshiftCinematicFlyer( entity flyerModel, entity victim = null ) +{ + entity newFlyer = CreateServerFlyer( flyerModel.GetOrigin(), flyerModel.GetAngles(), 100 ) + flyerModel.Destroy() + return newFlyer + +} + +/////////////////////////////////////////////////////////////////// +void function ObjectiveRemindUntilFlag( string flagToAbort ) +{ + Assert( IsNewThread(), "Must be threaded off" ) + + if ( Flag( flagToAbort ) ) + return + FlagEnd( flagToAbort ) + + while( true ) + { + wait RandomFloatRange( 45, 50 ) + if ( Flag( flagToAbort ) ) + break + Objective_Remind() + + } + + +} +/////////////////////////////////////////////////////////////////// +void function SetFlagWhenPlayerWithinRangeOfEnt( entity player, entity ent, float minDist, string flagToSet ) +{ + if ( Flag( flagToSet ) ) + return + + if ( !IsValid( ent ) ) + return + + if ( !IsValid( player ) ) + return + + FlagEnd( flagToSet ) + ent.EndSignal( "OnDestroy" ) + ent.EndSignal( "OnDeath" ) + player.EndSignal( "OnDeath" ) + + while( true ) + { + wait 0.25 + if ( Distance( player.GetOrigin(), ent.GetOrigin() ) < minDist ) + { + FlagSet( flagToSet ) + break + } + + } + +} +/////////////////////////////////////////////////////////////////// + +bool function IsAudioLogPlaying( entity player ) +{ + if ( !IsValid( player ) ) + return false + + if ( !Flag( "AudioLogPlaying" ) ) + return false + + if ( !PlayerInRangeOfAnyLaptopWhatsoever( player ) ) + return false + + return true +} +/////////////////////////////////////////////////////////////////// +bool function PlayerInRangeOfAnyLaptopWhatsoever( entity player ) +{ + //even if an audio log is "playing", we don't care if player is more than X units away from any laptop + foreach( model in file.audioLogModels ) + { + if ( PlayerInRange( player.GetOrigin(), model.GetOrigin(), DIST_TO_NOT_CARE_ABOUT_AUDIOLOGS ) ) + return true + } + + return false +}
\ No newline at end of file diff --git a/Northstar.Coop/scripts/vscripts/sp/sh_coop_sp_utils.gnut b/Northstar.Coop/scripts/vscripts/sp/sh_coop_sp_utils.gnut new file mode 100644 index 00000000..62f603e9 --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/sh_coop_sp_utils.gnut @@ -0,0 +1,34 @@ +untyped + +global function ClCoopSpUtils_Init +global function AreAllPlayersDead + +global function IsPlayingTimeshiftLevel + +void function ClCoopSpUtils_Init() +{ + +} + +bool function AreAllPlayersDead() +{ + foreach ( entity player in GetPlayerArray() ) + if ( IsAlive( player ) ) + return false + + return true +} + +// TIMESHIFT STUFF + +bool function IsPlayingTimeshiftLevel() +{ + bool allowed = false + try + { + allowed = expect bool( level.allowTimeTravel ) + } + catch ( exception ) {} + + return GetMapName().find( "timeshift" ) != null || allowed +} diff --git a/Northstar.Coop/scripts/vscripts/sp/sh_sp_objective.gnut b/Northstar.Coop/scripts/vscripts/sp/sh_sp_objective.gnut new file mode 100644 index 00000000..d98d4a6e --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/sh_sp_objective.gnut @@ -0,0 +1,537 @@ +global function SPObjectiveInit + +const OBJECTIVE_DISPLAY_TIME = 8.0 +const FADE_IN_TIME = 0.2 +const FADE_OUT_TIME = 0.5 +const OBJECTIVE_DISPLAY_TOTAL_TIME = FADE_IN_TIME + OBJECTIVE_DISPLAY_TIME + FADE_OUT_TIME + +#if SERVER + global function Objective_Set + global function Objective_SetSilent + global function Objective_Set_WithAltHighlight + global function Objective_SetSilent_WithAltHighlight + global function Objective_AddHighlightEntity + global function Objective_Clear + global function Objective_Hide + global function Objective_Update + global function Objective_InitEntity + global function Objective_Remind + global function Objective_AddKilometers + global function Objective_StaticModelHighlightOverrideEntity // if set it will highlight this entity instead, and also hide/show the entity when highlighting. This helps us get around not being able to do model highlight on a static model + global function Objective_StaticModelHighlightOverrideEntityArray + global function Objective_GetMarkerEntity // Dont use this if you're not working on S2S + global function Objective_SetFastball// Don't use this unless you grab the marker ent and clear it's parent after you're done + global function Objective_WayPointEneable + global function Objective_LastShownTime + global function Objective_SuppressCloseMenuShowsObjective + + struct + { + array<entity> objectiveEntities + entity objectiveMarkerEnt + array<entity> objectiveHighlightEntArray + array<entity> objectiveAltHighlightEntArray + bool showingObjective + float objectiveLastShownTime + bool suppressCloseMenuShowsObjective + bool objectiveAltHighlight = false + } file +#endif + +#if CLIENT + global function ShowObjectiveLineChanged + global function ObjectiveStringChanged + global function ShowObjectiveChanged + + const OBJECTIVE_MARKER_MODEL = $"models/dev/editor_ref.mdl" + const BLING_DURATION = 0.2 + + struct + { + bool onTimeshiftLevel + var objectiveRUI + } file +#endif + +void function SPObjectiveInit() +{ + #if SERVER + RegisterSignal( "ShowingObjective" ) + RegisterSignal( "HidingObjective" ) + AddClientCommandCallback( "ToggleObjective", ClientCommandToggleObjective ) + AddClientCommandCallback( "ShowObjective", ClientCommandShowObjective ) + #endif + + #if CLIENT + PrecacheModel( OBJECTIVE_MARKER_MODEL ) + RegisterSignal( "ShowingObjectiveRUI" ) + + file.onTimeshiftLevel = GetMapName().find( "sp_timeshift" ) == 0 + if ( file.onTimeshiftLevel ) + thread TimeshiftClientMarkerPositionThink() + #endif +} + +#if SERVER + void function Objective_Remind() + { + foreach( entity player in GetPlayerArray() ) + ClientCommand( player, "ShowObjective" ) + } + + void function Objective_InitEntity( entity ent ) + { + if ( ent.GetClassName() == "func_brush" ) + return + + // Add the entity to a valid list of highlightable objective entities + // (allows highlighting to be enabled without errors before everyone updates their script to call this function) + file.objectiveEntities.append( ent ) + + // Make the entity always render so the highlighting wont stop working + ent.DisableHibernation() + ent.EnableRenderAlways() + ent.kv.fadedist = 99999 + + // Highlight and unhighlight to precache the highlight ability on this entity (required by code) + Highlight_SetNeutralHighlight( ent, OBJECTIVE_HIGHLIGHT ) + Highlight_ClearNeutralHighlight( ent ) + } + + void function Objective_Set_WithAltHighlight( string objective, vector position = < 0, 0, 0 >, entity ent = null ) + { + file.objectiveAltHighlight = true + _ObjectiveSet( objective, position, ent, false ) + } + + void function Objective_SetSilent_WithAltHighlight( string objective, vector position = < 0, 0, 0 >, entity ent = null ) + { + file.objectiveAltHighlight = true + _ObjectiveSet( objective, position, ent, true ) + } + + void function Objective_Set( string objective, vector position = < 0, 0, 0 >, entity ent = null ) + { + file.objectiveAltHighlight = false + _ObjectiveSet( objective, position, ent, false ) + } + + void function Objective_SetSilent( string objective, vector position = < 0, 0, 0 >, entity ent = null ) + { + file.objectiveAltHighlight = false + _ObjectiveSet( objective, position, ent, true ) + } + + void function _ObjectiveSet( string objective, vector position, entity ent, bool silent ) + { + UpdateMarkerEnt( position, ent ) + SetGlobalNetInt( "objectiveStringIndex", GetObjectiveStringID( objective ) ) + + ClearObjectiveHighlight() + file.objectiveHighlightEntArray = [ ent ] + file.objectiveAltHighlightEntArray = [] + SetGlobalNetBool( "hilightingObjective", IsValid( ent ) ) + SetGlobalNetFloat( "additionalKilometers", 0.0 ) + + if ( !silent ) + thread ShowNewObjective() + + int stringIndex = GetGlobalNetInt( "objectiveStringIndex" ) + foreach( entity player in GetPlayerArray() ) + Remote_CallFunction_UI( player, "ServerCallback_UI_ObjectiveUpdated", stringIndex ) + } + + void function ShowNewObjective() + { + wait 0.1 // give time to sent position and entity to client + foreach( entity player in GetPlayerArray() ) + thread ShowObjectiveForDuration( player, OBJECTIVE_DISPLAY_TOTAL_TIME ) + } + + void function Objective_AddKilometers( float kilometers ) + { + Assert( kilometers >= 0.0 && kilometers <= 128.0 ) + SetGlobalNetFloat( "additionalKilometers", kilometers ) + } + + void function Objective_AddHighlightEntity( entity ent ) + { + file.objectiveHighlightEntArray.append( ent ) + } + + void function Objective_StaticModelHighlightOverrideEntity( entity ent ) + { + ent.kv.rendermode = 3 + ent.kv.renderamt = 1 + ent.Hide() + file.objectiveAltHighlightEntArray.append( ent ) + } + + void function Objective_StaticModelHighlightOverrideEntityArray( array<entity> ents ) + { + foreach ( ent in ents ) + Objective_StaticModelHighlightOverrideEntity( ent ) + } + + void function Objective_Clear() + { + file.objectiveAltHighlight = false + ClearObjectiveHighlight() + file.objectiveHighlightEntArray = [] + file.objectiveAltHighlightEntArray = [] + SetGlobalNetBool( "hilightingObjective", false ) + SetGlobalNetInt( "objectiveStringIndex", 0 ) + SetGlobalNetFloat( "additionalKilometers", 0.0 ) + + foreach( entity player in GetPlayerArray() ) + { + Objective_Hide( player ) + Remote_CallFunction_UI( player, "ServerCallback_UI_ObjectiveUpdated", -1 ) //-1 is no objective + } + } + + void function Objective_Update( vector position, entity ent = null ) + { + UpdateMarkerEnt( position, ent ) + ClearObjectiveHighlight() + file.objectiveHighlightEntArray = [ ent ] + file.objectiveAltHighlightEntArray = [] + SetGlobalNetBool( "hilightingObjective", IsValid( ent ) ) + } + + void function UpdateMarkerEnt( vector position, entity ent ) + { + vector newPos = position + bool showLine = IsValid( ent ) || (position != <0,0,0>) + + SetGlobalNetBool( "showObjectiveLine", showLine ) + + if ( IsValid( ent ) ) + { + newPos = ent.GetOrigin() + position + } + + // Create marker ent every time + RefreshMarkerEnt( newPos ) + + if ( IsValid( ent ) ) + { + if ( ent.GetClassName() != "info_target" ) + file.objectiveMarkerEnt.SetParent( ent, "", true ) + else + CodeWarning( "Tried to set objective entity to an info_target. This isn't supported because they aren't sent to the client. Try using a script_mover_lightweight or prop_dynamic." ) + } + } + + void function RefreshMarkerEnt( vector position ) + { + if ( !IsValid( file.objectiveMarkerEnt ) ) + { + file.objectiveMarkerEnt = CreateEntity( "info_target" ) + file.objectiveMarkerEnt.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT + DispatchSpawn( file.objectiveMarkerEnt ) + + SetGlobalNetEnt( "objectiveMarkerEntity", file.objectiveMarkerEnt ) + } + + file.objectiveMarkerEnt.ClearParent() + file.objectiveMarkerEnt.SetOrigin( position ) + } + + void function ShowObjectiveForDuration( entity player, float duration ) + { + EndSignal( player, "OnDeath" ) + EndSignal( player, "OnDestroy" ) + + // Make sure thread only runs once + Signal( player, "ShowingObjective" ) + EndSignal( player, "ShowingObjective" ) + EndSignal( player, "HidingObjective" ) + + player.SetPlayerNetBool( "showObjective", true ) + SetObjectiveHighlight() + file.objectiveLastShownTime = Time() + + wait duration + Objective_Hide( player ) + } + + void function Objective_Hide( entity player ) + { + player.SetPlayerNetBool( "showObjective", false ) + ClearObjectiveHighlight() + Signal( player, "HidingObjective" ) + } + + float function Objective_LastShownTime() + { + return file.objectiveLastShownTime + } + + bool function ClientCommandToggleObjective( entity player, array<string> args ) + { + bool isShowing = player.GetPlayerNetBool( "showObjective" ) + + if ( isShowing ) + thread Objective_Hide( player ) + else + thread ShowObjectiveForDuration( player, OBJECTIVE_DISPLAY_TOTAL_TIME ) + + return true + } + + bool function ClientCommandShowObjective( entity player, array<string> args ) + { + if ( args.len() > 0 ) + { + if ( args[0] == "closedSPMenu" && file.suppressCloseMenuShowsObjective ) + return true + } + + if ( IsAlive( player ) ) + thread ShowObjectiveForDuration( player, OBJECTIVE_DISPLAY_TOTAL_TIME ) + return true + } + + void function Objective_SuppressCloseMenuShowsObjective( bool enabled ) + { + file.suppressCloseMenuShowsObjective = enabled + } + + void function SetObjectiveHighlight() + { + // Alt highlight ent? + if ( file.objectiveAltHighlightEntArray.len() ) + { + foreach ( ent in file.objectiveAltHighlightEntArray ) + { + if ( !IsValid( ent ) ) + continue + Assert( file.objectiveEntities.contains( ent ), "Tried to use Objective_StaticModelHighlightOverrideEntity on entity that didn't call Objective_InitEntity" ) + ent.Show() + Highlight_SetNeutralHighlight( ent, OBJECTIVE_HIGHLIGHT ) + } + } + else if ( file.objectiveHighlightEntArray.len() ) + { + foreach ( ent in file.objectiveHighlightEntArray ) + { + if ( !IsValid( ent ) ) + continue + + if ( file.objectiveEntities.contains( ent ) ) + Highlight_SetNeutralHighlight( ent, OBJECTIVE_HIGHLIGHT ) + } + } + } + + void function ClearObjectiveHighlight() + { + // Alt highlight ent? + if ( file.objectiveAltHighlightEntArray.len() ) + { + foreach ( ent in file.objectiveAltHighlightEntArray ) + { + if ( !IsValid( ent ) ) + continue + Assert( file.objectiveEntities.contains( ent ), "Tried to use Objective_StaticModelHighlightOverrideEntity on entity that didn't call Objective_InitEntity" ) + ClearObjectiveHighlight_Internal( ent ) + ent.Hide() + } + } + else if ( file.objectiveHighlightEntArray.len() ) + { + foreach ( ent in file.objectiveHighlightEntArray ) + { + if ( !IsValid( ent ) ) + continue + if ( file.objectiveEntities.contains( ent ) ) + ClearObjectiveHighlight_Internal( ent ) + } + } + } + + void function ClearObjectiveHighlight_Internal( entity ent ) + { + if ( !file.objectiveAltHighlight ) + Highlight_ClearNeutralHighlight( ent ) + else + Highlight_SetNeutralHighlight( ent, OBJECTIVE_HIGHLIGHT_ALT ) + } + + entity function Objective_GetMarkerEntity() + { + return file.objectiveMarkerEnt + } + + entity function Objective_WayPointEneable( bool enable ) + { + SetGlobalNetBool( "objectiveMarkerIsWayPoint", enable ) + } + + // Don't use this unless you grab the marker ent and clear it's parent after you're done + void function Objective_SetFastball( entity bt ) + { + entity marker = Objective_GetMarkerEntity() + string tag = "HAND_R" + int attachID = bt.LookupAttachment( tag ) + vector angles = bt.GetAttachmentAngles( attachID ) + vector offset = AnglesToRight( angles ) * 20 + marker.SetOrigin( bt.GetAttachmentOrigin( attachID ) + offset ) + marker.SetParent( bt, tag, true, 0 ) + } + + +#endif + + + + + + + + + + +#if CLIENT + void function ObjectiveStringChanged( entity player, int oldString, int newString, bool actuallyChanged ) + { + if ( file.objectiveRUI == null ) + return + + if ( newString == 0 ) + return + + string objectiveText = GetObjectiveStringFromID( newString ) + RuiSetString( file.objectiveRUI, "objectiveText", objectiveText ) + } + + void function ShowObjectiveLineChanged( entity player, bool old, bool new, bool actuallyChanged ) + { + if ( file.objectiveRUI == null ) + return + + if ( !new ) + RuiSetBool( file.objectiveRUI, "showLine", false ) + else + RuiSetBool( file.objectiveRUI, "showLine", true ) + + if ( GetGlobalNetBool( "hilightingObjective" ) == true || !new ) + RuiSetBool( file.objectiveRUI, "showMarker", false ) + else + RuiSetBool( file.objectiveRUI, "showMarker", true ) + } + + void function ShowObjectiveChanged( entity player, bool wasShowing, bool isShowing, bool actuallyChanged ) + { + if ( !actuallyChanged ) + return + + if ( isShowing ) + thread ShowObjective( true ) + else + HideObjective() + } + + void function ShowObjective( bool newObjective ) + { + // Make sure only one RUI is displayed at a time + Signal( clGlobal.levelEnt, "ShowingObjectiveRUI" ) + EndSignal( clGlobal.levelEnt, "ShowingObjectiveRUI" ) + + entity marker = GetGlobalNetEnt( "objectiveMarkerEntity" ) + if ( !IsValid( marker ) ) + return + + OnThreadEnd( + function() : ( ) + { + HideObjective() + } + ) + + int stringIndex = GetGlobalNetInt( "objectiveStringIndex" ) + if ( stringIndex == 0 ) + return + + string titleText = newObjective ? "#OBJECTIVE_UPDATED" : "#OBJECTIVE_REMIND" + bool isWayPoint = GetGlobalNetBool( "objectiveMarkerIsWayPoint" ) + if( isWayPoint ) + titleText = "#OBJECTIVE_WAYPOINT" + + string objectiveText = GetObjectiveStringFromID( stringIndex ) + float additionalKilometers = GetGlobalNetFloat( "additionalKilometers" ) + + HideObjective() + file.objectiveRUI = CreatePermanentCockpitRui( $"ui/sp_objective.rpak", 0 ) + + RuiSetGameTime( file.objectiveRUI, "startTime", Time() ) + RuiSetGameTime( file.objectiveRUI, "endTime", Time() + OBJECTIVE_DISPLAY_TIME ) + RuiSetFloat( file.objectiveRUI, "fadeInDuration", FADE_IN_TIME ) + RuiSetFloat( file.objectiveRUI, "fadeOutDuration", FADE_OUT_TIME ) + RuiSetFloat( file.objectiveRUI, "blingDuration", newObjective ? BLING_DURATION : 0.0 ) + RuiSetString( file.objectiveRUI, "objectiveTitleText", titleText ) + RuiSetString( file.objectiveRUI, "objectiveText", objectiveText ) + RuiSetBool( file.objectiveRUI, "showButtonHint", newObjective ) + + // handle objective position in code on timeshift levels + if ( !file.onTimeshiftLevel ) + RuiTrackFloat3( file.objectiveRUI, "pos", marker, RUI_TRACK_ABSORIGIN_FOLLOW ) + + RuiSetFloat( file.objectiveRUI, "additionalKilometers", additionalKilometers ) + + if ( !GetGlobalNetBool( "showObjectiveLine" ) ) + RuiSetBool( file.objectiveRUI, "showLine", false ) + + if ( GetGlobalNetBool( "hilightingObjective" ) == true || !GetGlobalNetBool( "showObjectiveLine" ) ) + RuiSetBool( file.objectiveRUI, "showMarker", false ) + + EmitSoundOnEntity( GetLocalClientPlayer(), "ui_holotutorial_analyzingfinish" ) + + wait OBJECTIVE_DISPLAY_TOTAL_TIME + } + + void function HideObjective() + { + if ( file.objectiveRUI != null ) + { + RuiDestroyIfAlive( file.objectiveRUI ) + file.objectiveRUI = null + } + } + + void function TimeshiftClientMarkerPositionThink() + { + while ( true ) + { + WaitFrame() + + if ( IsValid( file.objectiveRUI ) ) + { + // marker is how the server tells us the objective pos + entity marker = GetGlobalNetEnt( "objectiveMarkerEntity" ) + if ( !IsValid( marker ) ) + continue // unsure if this can even ever be hit + + // get our current timeline and get zoffset for it + int zOffset = 0 + if ( marker.GetOrigin().z < -6000 ) // frozen time + { + RuiTrackFloat3( file.objectiveRUI, "pos", marker, RUI_TRACK_ABSORIGIN_FOLLOW ) + break // once we hit frozen time, all objectives from now will be on the same timeline as players so we can use normal objective pos logic + } + else if ( marker.GetOrigin().z < 5300 && GetLocalClientPlayer().GetOrigin().z > 5300 ) // marker in night, player in day + zOffset = TIME_ZOFFSET + else if ( marker.GetOrigin().z > 5300 && GetLocalClientPlayer().GetOrigin().z < 5300 ) // marker in day, player in night + zOffset = TIME_ZOFFSET * -1 + + // set objective pos + vector pos = marker.GetOrigin() + Vector( 0, 0, zOffset ) + //RuiSetString( file.objectiveRUI, "objectiveTitleText", "marker: " + marker.GetOrigin() + ", player: " + GetLocalClientPlayer().GetOrigin() + ", offset: " + zOffset ) + RuiSetFloat3( file.objectiveRUI, "pos", pos ) + } + } + } + +#endif + diff --git a/Northstar.Coop/scripts/vscripts/sp/sp_training.nut b/Northstar.Coop/scripts/vscripts/sp/sp_training.nut new file mode 100644 index 00000000..21a67a40 --- /dev/null +++ b/Northstar.Coop/scripts/vscripts/sp/sp_training.nut @@ -0,0 +1,7554 @@ +global function CodeCallback_MapInit + +#if DEV +global function skyboxchange +global function GetOGPilot +global function wallruntest +global function Record_ZenGarden_Wallrun +global function Record_ZenGarden_Slide +global function Record_ZenGarden_DoubleJump +global function shutdownscreentest +global function SendTrainingGauntletStats +global function FancyTeleport_EffectsAndSound +global function Training_WeaponRacks_SetSolidity +global function setfakeinstalldone + +global function Training_EnvArtColorCorrection_SetEnabled +global function SimpleScreenShake +global function GetSkitGuyInfo_ByName +global function NudgeSkitGuy +#endif + +const MARVIN_MODEL = $"models/robots/marvin/marvin.mdl" +const asset FX_POD_LASER = $"P_pod_scan_laser_FP" +const asset FX_POD_GLOWLIGHT = $"P_pod_door_glow_FP" +const asset FX_POD_SCREEN_IN = $"P_pod_screen_lasers_IN" +const asset FX_POD_SCREEN_OUT = $"P_pod_screen_lasers_OUT" +const asset FX_POD_DLIGHT_CONSOLE1 = $"P_pod_Dlight_console1" +const asset FX_POD_DLIGHT_CONSOLE2 = $"P_pod_Dlight_console2" +//const asset FX_POD_DLIGHT_BACKLIGHT_SIDE = $"P_pod_Dlight_backlight_side" +//const asset FX_POD_DLIGHT_BACKLIGHT_TOP = $"P_pod_Dlight_backlight_top" +const asset FX_FANCY_TELEPORT_ENV_PULSE = $"P_ar_holopulse_CP" +const asset FX_COCKPIT_LIGHT = $"xo_cockpit_dlight" +const asset OG_PILOT_HELMET_MODEL = $"models/Humans/heroes/mlt_hero_anderson_helmet.mdl" +const asset ANDERSON_PILOT_MODEL = $"models/humans/heroes/mlt_hero_anderson.mdl" +const asset PILOT_MODEL_BAY1 = $"models/humans/pilots/sp_medium_geist_f.mdl" +const asset PILOT_MODEL_BAY2 = $"models/humans/pilots/sp_medium_reaper_m.mdl" +const asset BUDDY_MODEL_POSED_NO_ANIMS = $"models/Titans/buddy/BT_posed.mdl" +const asset SAFETY_BATON_MODEL = $"models/industrial/safety_baton.mdl" + +const asset OG_PILOT_MODEL = $"models/humans/heroes/mlt_hero_lastimosa.mdl" +const int OG_PILOT_MODEL_HEAD_IDX_BARE = 0 +const int OG_PILOT_MODEL_HEAD_IDX_HELMET = 2 +const int OG_PILOT_MODEL_DECAL_IDX = 0 +const int OG_PILOT_MODEL_DECAL_IDX_BARE = 1 + +const string OG_WEAPON = "mp_weapon_rspn101" + +const string ANIM_OG_STANDING_IDLE = "OG_stand_upright_idle" +const string ANIM_OG_STANDING_TALK = "OG_stand_upright_talk" +const string ANIM_OG_SITTING_IDLE = "OG_sit_high_idle" +const string ANIM_OG_SITTING_TALK = "OG_sit_high_talk" +const string ANIM_OG_LEANING_IDLE = "OG_stand_lean_idle" +const string ANIM_OG_LEANING_TALK = "OG_stand_lean_talk" + +const int SCRIPTED_PATH_WALK = 0 +const int SCRIPTED_PATH_RUN = 1 + +const int MAX_RECREATED_OLD_WEAPONS = 16 + +const float TITANFALL_NAG_DURATION = 3.0 // extra time compensation for nag line playing when titanfall started + +const string TRAINING_PLAYER_SETTINGS = "pilot_solo_training" + +struct TrainingPod_dLightMapping +{ + string scriptAlias + asset fxName + string attachName + entity fxHandle +} + +struct TrainingPod_LaserEmitter +{ + entity ent + string attachName + vector ogAng + bool sweepDone = false + entity fxHandle +} + +struct TrainingPod_GlowLightRow +{ + array<string> fxSpotsL + array<string> fxSpotsR +} + +struct LoudspeakerVO_Info +{ + string scriptAlias + string soundAlias + float duration +} + +struct FiringRangeTarget +{ + entity ent + entity angleRefEnt + entity mover + bool wasDamaged + vector ogAngles +} + +struct SkitGuyInfo +{ + int id + string name + string skitAnim + entity guy + entity skitRef +} + +struct HangarTitanGroup +{ + entity ref + + entity titan + entity rack + entity marvin + entity pilot + + int titanSkin = -1 + + string titanAnim + string rackAnim + string marvinAnim + string pilotAnim + + asset pilotModel + + vector rack_ogPos + vector rack_ogAng + + float sequenceDuration + float animInitialTime = 0.0 + + bool isInited = false +} + +struct TrainingGauntletStats +{ + bool didBeatRequiredTime = false + int numRunsBeforeBeatRequiredTime = 0 + int numChallengeRuns = 0 + int numRestarts = 0 + float bestTime = -1.0 + int recommendedDifficulty = 0 +} + +struct +{ + #if DEV + bool fakeInstallDone = false + #endif + + bool gauntletMode = false + + entity player + int playerInputType + + entity ogPilot + entity ogTwin + entity anderson + entity titanTwin + entity ogHelmet + entity playerAnimWeapon + + entity ogPathMover + + entity animref_hangar + entity animref_leaveGauntlet + + entity trainingPod + array<TrainingPod_GlowLightRow> trainingPodGlowLightRows + array<entity> trainingPodGlowLightFXHandles + array<TrainingPod_dLightMapping> trainingPodDLightMappings + array<TrainingPod_LaserEmitter> trainingPodLaserEmitters + + float postWallrunVOEndTime = -1 + + float titanfallNagStartTime = -1 + vector playerTitanCallInPos + + entity envArt_colorCorrectionEnt + entity skycam_default + entity skycam_glitch + + table<string,LoudspeakerVO_Info> loudspeakerVO = {} + entity loudspeaker + + array<FiringRangeTarget> firingRangeTargets = [] + + //table<string,SkitGuyInfo> skitguys = {} + array<SkitGuyInfo> skitguys = [] + + TrainingGauntletStats trainingGauntletStats + + array<entity> scriptCreatedWeaponPickups = [] + bool weaponPickupsHaveAmmo = false + + bool displayWeaponHUD = true +} file + +void function CodeCallback_MapInit() +{ + FlagSet( "FlightPath_TitanDrop" ) + + PrecacheParticleSystem( FX_POD_LASER ) + PrecacheParticleSystem( FX_POD_GLOWLIGHT ) + PrecacheParticleSystem( FX_POD_SCREEN_IN ) + PrecacheParticleSystem( FX_POD_SCREEN_OUT ) + PrecacheParticleSystem( FX_POD_DLIGHT_CONSOLE1 ) + PrecacheParticleSystem( FX_POD_DLIGHT_CONSOLE2 ) + //PrecacheParticleSystem( FX_POD_DLIGHT_BACKLIGHT_SIDE ) + //PrecacheParticleSystem( FX_POD_DLIGHT_BACKLIGHT_TOP ) + PrecacheParticleSystem( FX_FANCY_TELEPORT_ENV_PULSE ) + PrecacheParticleSystem( FX_COCKPIT_LIGHT ) + + PrecacheModel( OG_PILOT_HELMET_MODEL ) + PrecacheModel( OG_PILOT_MODEL ) + PrecacheModel( ANDERSON_PILOT_MODEL ) + PrecacheModel( PILOT_MODEL_BAY1 ) + PrecacheModel( PILOT_MODEL_BAY2 ) + PrecacheModel( BUDDY_MODEL_POSED_NO_ANIMS ) + PrecacheModel( SAFETY_BATON_MODEL ) + PrecacheModel( MARVIN_MODEL ) + PrecacheModel( DATA_KNIFE_MODEL ) + + LoudspeakerVO_Setup() + Training_SharedInit() + + RegisterSignal( "ButtonPressedJump" ) + RegisterSignal( "ButtonPressedAttack" ) + RegisterSignal( "PodIntro_OG_StartPodAnim" ) + RegisterSignal( "PodInteriorSequenceDone" ) + RegisterSignal( "FancyTeleportStart" ) + RegisterSignal( "TargetRotate" ) + RegisterSignal( "TargetDamaged" ) + RegisterSignal( "Target_WaitForDamage_Start" ) + RegisterSignal( "StopRepeatingGhostRecorder" ) + RegisterSignal( "FiringRange_StopResettingTargets" ) + RegisterSignal( "Gauntlet_StopTeleportingPlayerAtFinishLine" ) + RegisterSignal( "FirstRun_OG_Creates_Ghost" ) + RegisterSignal( "GauntletChallenge_FirstGhostAppear" ) + RegisterSignal( "PlayerMadeSelection" ) + RegisterSignal( "TrainingPod_BeginInteriorShutdown" ) + RegisterSignal( "NPC_NewCommand" ) + RegisterSignal( "LoudspeakerVO_Stop" ) + RegisterSignal( "glitch_start" ) + + FlagInit( "PlayerPressedUse" ) + FlagInit( "PlayerReloaded" ) + FlagInit( "PodIntro_PodDoorsClosed" ) + FlagInit( "PlayerLookedAtTopTarget" ) + FlagInit( "PlayerLookedAtBottomTarget" ) + FlagInit( "PodIntro_InteriorBootSequence_Starting" ) + FlagInit( "OG_WhyWeFight_VO_Done" ) + FlagInit( "FiringRange_Approach_OG_Sequence_Done" ) + FlagInit( "ReloadTraining_PlayerPressedReload" ) + FlagInit( "PlayerSprinted" ) + FlagInit( "PlayerADSed" ) + FlagInit( "FiringRangeWeaponSwapped" ) + FlagInit( "FiringRange_AllTargetsKilled" ) + FlagInit( "OG_MovedTo_GauntletEntrance" ) + FlagInit( "Gauntlet_FirstRun_All_VO_Finished" ) + FlagInit( "Gauntlet_FirstRun_Done" ) + FlagInit( "ChallengeIntro_VO_Done" ) + FlagInit( "Gauntlet_PlayingFeedbackVO" ) + FlagInit( "PlayerUsedConversationInterface" ) + FlagInit( "GauntletExitConvo_FinishedResponse" ) + FlagInit( "TitanfallIntroConvo_FinishedResponse" ) + FlagInit( "PlayerConfirmedGauntletExit" ) + FlagInit( "PlayerLeavingGauntlet" ) + FlagInit( "PlayerStartedTitanfall" ) + FlagInit( "Titanfall_OG_FallingIn_VO_Start" ) + FlagInit( "TitanfallGlitchStart" ) + FlagInit( "PlayerWorldChangeThread" ) + FlagInit( "Glitch_WorldChanging_Zen" ) + FlagInit( "Glitch_WorldChanging_NonZen" ) + FlagInit( "PodOutroStarted" ) + FlagInit( "SimPodShutdown_LoudspeakerVO_Done" ) + FlagInit( "MeetOG_StartScene" ) + FlagInit( "CadillacMoment_MeetOG_Done" ) + FlagInit( "CadillacMoment_MeetOG_StartFadeOut" ) + FlagInit( "MeetOG_VO_Done" ) + + FlagClear( "AutomaticCheckpointsEnabled" ) + + AddClientCommandCallback( "Training_SetInputType", ClientCommand_Training_SetInputType ) + AddClientCommandCallback( "Training_PlayerPressedUse", ClientCommand_Training_PlayerPressedUse ) + AddClientCommandCallback( "Training_PlayerReloaded", ClientCommand_Training_PlayerReloaded ) + AddClientCommandCallback( "topTarget", ClientCommand_LookTarget_Top ) + AddClientCommandCallback( "bottomTarget", ClientCommand_LookTarget_Bottom ) + + AddCallback_EntitiesDidLoad( EntitiesDidLoad ) + AddPlayerDidLoad( Training_PlayerDidLoad ) + AddCallback_OnLoadSaveGame( Training_OnLoadSaveGame ) + + AddDamageCallback( "func_brush", Training_FuncBrush_OnDamaged ) + + TimerInit( "firingRangeNag", 15.0 ) + TimerInit( "installWaitComment", 60.0 ) + + AddStartPoint( "Pod Intro", Training_PodIntro, null, Training_Skipped_PodIntro ) + AddStartPoint( "Basic Movement", Training_BasicMovement, Training_Setup_BasicMovement, Training_Skipped_BasicMovement ) + AddStartPoint( "Zen Garden", Training_ZenGarden, Training_Setup_ZenGarden, Training_Skipped_ZenGarden ) + AddStartPoint( "Firing Range", Training_FiringRange, Training_Setup_FiringRange, Training_Skipped_FiringRange ) + AddStartPoint( "Gauntlet", Training_Gauntlet, Training_Setup_Gauntlet, Training_Skipped_Gauntlet ) + AddStartPoint( "Gauntlet Challenge", Training_GauntletChallenge, Training_Setup_GauntletChallenge, Training_Skipped_GauntletChallenge ) + AddStartPoint( "Titanfall", Training_Titanfall, Training_Setup_Titanfall, Training_Skipped_Titanfall ) + AddStartPoint( "Pod Outro", Training_PodOutro, Training_Setup_PodOutro, Training_Skipped_PodOutro ) + AddStartPoint( "Meet OG", Training_MeetOG, Training_Setup_MeetOG, Training_Skipped_MeetOG ) + AddStartPoint( "Gauntlet Mode", Training_GauntletModeStart, Training_Setup_GauntletMode, null ) + + #if DEV + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_FIRSTRUN", TrainingGauntlet_RecordGhostStart_FirstRun, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_WIP", TrainingGauntlet_RecordGhostStart_Challenge_WIP, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_01", TrainingGauntlet_RecordGhostStart_Challenge_01, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_02", TrainingGauntlet_RecordGhostStart_Challenge_02, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_03", TrainingGauntlet_RecordGhostStart_Challenge_03, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_04", TrainingGauntlet_RecordGhostStart_Challenge_04, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_05", TrainingGauntlet_RecordGhostStart_Challenge_05, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_06", TrainingGauntlet_RecordGhostStart_Challenge_06, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_07", TrainingGauntlet_RecordGhostStart_Challenge_07, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_08", TrainingGauntlet_RecordGhostStart_Challenge_08, null, null ) + AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_09", TrainingGauntlet_RecordGhostStart_Challenge_09, null, null ) + + AddStartPoint( "Pod Intro DEV", DEV_PodIntro, null, null ) + AddStartPoint( "Pod Outro DEV", DEV_PodOutro, null, null ) + #endif +} + + +void function EntitiesDidLoad() +{ + QuickDeathTrigger_SetIsPunitive( false ) + + SetupTrainingPod() + TrainingPod_GlowLightsArraySetup() + + FiringRangeTargets_Init() + + file.animref_hangar = GetEntByScriptName( "animref_hangar" ) + file.animref_hangar.DisableHibernation() + + file.skycam_default = GetEnt( "skybox_cam_level" ) + file.skycam_glitch = GetEnt( "skybox_cam_glitch" ) +} + + +void function Training_PlayerDidLoad( entity player ) +{ + player.SetNoTarget( true ) + SetGlobalForcedDialogueOnly( true ) + + AddButtonPressedPlayerInputCallback( player, IN_JUMP, Training_ButtonPressedJump ) + AddButtonPressedPlayerInputCallback( player, IN_ATTACK, Training_ButtonPressedAttack ) + + file.envArt_colorCorrectionEnt = GetEnt( "color_correction_1" ) + + file.player = player + + EnableDemigod( player ) + + player.ForceMPAimassist() // TODO doublecheck this + + player.SetSkyCamera( file.skycam_default ) + + Training_WeaponPickups_Init( player ) + player.PreventWeaponDestroyNoAmmo() // makes it so when player swaps empty weapon for another pickup, doesn't destroy empty weapon + + thread Training_PlayerQuickdeathSFX( player ) + + DisableFriendlyHighlight() +} + + +void function Training_OnLoadSaveGame( entity player ) +{ + thread Training_OnLoadSaveGame_Think( player ) +} + +void function Training_OnLoadSaveGame_Think( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + wait 0.5 // HACK have to wait otherwise it doesn't work + SetWeaponHUDEnabled( player, file.displayWeaponHUD ) +} + + +void function Training_ButtonPressedJump( entity player ) +{ + player.Signal( "ButtonPressedJump" ) +} + +void function Training_ButtonPressedAttack( entity player ) +{ + player.Signal( "ButtonPressedAttack" ) +} + +void function Training_FuncBrush_OnDamaged( entity ent, var damageInfo ) +{ + if( !IsValid( ent ) ) + return + + entity attacker = DamageInfo_GetAttacker( damageInfo ) + + if ( ent.GetScriptName() == "firingrange_target" ) + { + if ( IsValid( attacker ) && attacker.IsPlayer() ) + { + table<string,vector> resultTable = {} + resultTable["damagePos"] <- DamageInfo_GetDamagePosition( damageInfo ) + ent.Signal( "TargetDamaged", resultTable ) + } + } +} + + +// ============================== +// ========= POD INTRO ========== +// ============================== +void function Training_Skipped_PodIntro( entity player ) +{ + player.SetExtraWeaponMods( [ "low_ammo_disable" ] ) + SetWeaponHUDEnabled( player, false ) +} + +#if DEV +// bare bones start in pod +void function DEV_PodIntro( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + TakeAllWeapons( player ) + + player.SetExtraWeaponMods( [ "low_ammo_disable" ] ) + SetWeaponHUDEnabled( player, false ) + + Training_EnvArtColorCorrection_SetEnabled( false ) + SetDoF_Hangar( player ) + + thread PodIntro_BackgroundSkits( player ) + + entity pod = file.trainingPod + + TrainingPod_PlayerSequence_DoorsOpenIdle( player, false ) + + // anim starts at a slightly different spot + player.SetOrigin( < 10564, -10235, -6056.9 > ) + player.SetAngles( < -6, 90, 0 > ) + + WaitForever() +} +#endif //DEV + + +void function Training_PodIntro( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + player.SetExtraWeaponMods( [ "low_ammo_disable" ] ) + SetWeaponHUDEnabled( player, false ) + + Training_EnvArtColorCorrection_SetEnabled( false ) + + entity pod = file.trainingPod + + OnThreadEnd( + function() : ( player, pod ) + { + if ( IsValid( player ) ) + { + player.Anim_Stop() + ClearPlayerAnimViewEntity( player ) + player.ClearParent() + player.UnforceStand() + + Training_EnvArtColorCorrection_SetEnabled( true ) + } + + if ( IsValid ( pod ) ) + { + pod.Anim_Stop() + + thread TrainingPod_ResetLaserEmitterRotation( pod ) + thread TrainingPod_KillLasers( pod ) + thread TrainingPod_KillGlowFX( pod ) + TrainingPod_KillInteriorDLights() + } + } + ) + + // NORMAL LEVEL START + SetDoF_Hangar( player ) + + ShowIntroScreen( player ) + + thread PodIntro_MeetOG( player ) + + TrainingPod_PlayerSequence_DoorsOpenIdle( player ) + + FlagWait( "IntroScreenFading" ) + wait 1.2 + Remote_CallFunction_Replay( player, "ScriptCallback_LevelIntroText" ) + wait 4.2 // matches fade time in sp_introscreen data + + thread PodIntro_BackgroundSkits( player ) + + + player.Signal( "PodIntro_OG_StartPodAnim" ) + + // time for OG to animate before starting viewmodel anim + wait 11.8 + + FirstPersonSequenceStruct playerSequence + playerSequence.blendTime = 0.25 + playerSequence.attachment = "REF" + playerSequence.firstPersonAnim = "ptpov_trainingpod_doors_close" + playerSequence.firstPersonAnimIdle = "ptpov_trainingpod_idle" + playerSequence.thirdPersonAnim = "pt_trainingpod_doors_close" + playerSequence.thirdPersonAnimIdle = "pt_trainingpod_idle" + playerSequence.viewConeFunction = TrainingPod_ViewConeLock_SemiStrict + playerSequence.renderWithViewModels = true + + FirstPersonSequenceStruct podSequence + podSequence.blendTime = 0.25 + podSequence.thirdPersonAnim = "trainingpod_doors_close" + podSequence.thirdPersonAnimIdle = "trainingpod_doors_close_idle" + podSequence.renderWithViewModels = true + + entity viewmodel = player.GetFirstPersonProxy() + + if ( !HasAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut" ) ) + AddAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut", PlaySound_SimPod_DoorShut ) + + // HACK this should be based on an anim event + thread TrainingPod_KillInteriorDLights_Delayed( player, 2.65 ) + + thread FirstPersonSequence( podSequence, pod ) + waitthread FirstPersonSequence( playerSequence, player, pod ) + + FlagSet( "PodIntro_PodDoorsClosed" ) + + TrainingPod_ViewConeLock_PodClosed( player ) + + waitthread LookTraining( player ) + + // "Let's see how much you remember from last time." + waitthread PlayDialogue( "og_how_much_you_remember", player ) + + // "Setting the neural link." + waitthread PlayDialogue( "og_neural_link", player ) + + // "Not quite the same as a Titan link, but it's similar." + waitthread PlayDialogue( "og_neural_link_2", player ) + + thread TrainingPod_Interior_BootSequence( player ) + + // "To learn new skills, we need to be in the right state of mind." + thread PlayDialogue( "og_simulation_starting", player, 2.5 ) + + player.WaitSignal( "PodInteriorSequenceDone" ) + printt( "POD SEQUENCE DONE" ) + + wait 2.0 // timed to match the screen effect white screen flash + + SetDoF_Default( player ) +} + +void function PlaySound_SimPod_DoorShut( entity playerFirstPersonProxy ) //Hack, needed for wargames but has unfortunate side effect for Training. +{ + entity player = playerFirstPersonProxy.GetOwner() + if ( !IsValid( player ) ) + return + + EmitSoundOnEntityOnlyToPlayer( player, player, "NPE_Scr_SimPod_DoorShut" ) + +} + +void function PodIntro_MeetOG( entity player ) +{ + entity animref = file.animref_hangar + vector animrefOrigin = animref.GetOrigin() + vector animrefAngles = animref.GetAngles() + vector btSpawnOrg = <0,0,0> // to avoid red text errors about BT spawning in solid + + entity animEnt = CreateScriptMover( animrefOrigin, animrefAngles ) + + // Spawn scene actors + entity og = Training_SpawnOGPilot( animref ) + Training_OGPilot_SetHelmetOn( og, false ) + AddAnimEvent( file.ogPilot, "pod_slap", PodIntro_OG_Slaps_Pod ) + + TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap() + entity bt = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, btSpawnOrg, animrefAngles ) + SetSpawnOption_AISettings( bt, "npc_titan_buddy" ) + bt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID + DispatchSpawn( bt ) + FreeAutoTitan( bt ) // HACK this disables the worldspace BT locator icon + TakeAllWeapons( bt ) + + array<entity> actors = [ bt, og ] + + asset btWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( "mp_titanweapon_xo16_shorty", "playermodel" ) + Assert( btWeaponModel != $"" ) + entity btWeapon = CreatePropDynamic( btWeaponModel ) + btWeapon.SetParent( bt, "PROPGUN" ) + + asset ogWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( OG_WEAPON, "playermodel" ) + Assert( ogWeaponModel != $"" ) + entity ogWeapon = CreatePropDynamic( ogWeaponModel ) + ogWeapon.SetParent( og, "PROPGUN" ) + + entity ogHelmet = CreatePropDynamic( OG_PILOT_HELMET_MODEL ) + ogHelmet.DisableHibernation() + + array<entity> props = [ btWeapon, ogWeapon, ogHelmet ] + + OnThreadEnd( + function() : ( actors, props, animEnt ) + { + if ( IsValid( file.ogPilot ) ) + DeleteAnimEvent( file.ogPilot, "pod_slap" ) + + foreach ( weapon in props ) + { + if ( IsValid( weapon ) ) + { + weapon.ClearParent() + weapon.Destroy() + } + } + + foreach ( actor in actors ) + { + if ( !IsValid( actor ) ) + continue + + actor.ClearParent() + + if ( IsInvincible( actor ) ) + ClearInvincible( actor ) + + if ( !actor.IsPlayer() ) + actor.Destroy() + } + + if ( IsValid( animEnt ) ) + animEnt.Destroy() + } + ) + + foreach ( guy in actors ) + { + MakeInvincible( guy ) + guy.SetEfficientMode( true ) + Highlight_ClearFriendlyHighlight( guy ) + } + + string anim_og_idle = "pt_OG_training_rail_sit_idle" + string anim_og_helmet_idle = "helmet_intro_scene_OG_idle" + string anim_bt_idle = "BT_intro_scene_OG_idle" + + string anim_og = "pt_pod_setup_OG" + string anim_bt = "BT_pod_setup_OG" + + thread PlayAnimTeleport( og, anim_og_idle, animEnt ) + thread PlayAnimTeleport( ogHelmet, anim_og_helmet_idle, animEnt ) + thread PlayAnimTeleport( bt, anim_bt_idle, animEnt ) + + player.WaitSignal( "PodIntro_OG_StartPodAnim" ) + + if ( !IsValid( og ) ) + return + + og.Anim_Stop() + thread PlayAnim( og, anim_og, animEnt ) + + bt.Anim_Stop() + thread PlayAnim( bt, anim_bt, animEnt ) + + float animDuration = og.GetSequenceDuration( anim_og ) + wait animDuration - 0.1 + + if ( !IsValid( og ) ) + return + + og.Anim_Stop() + thread PlayAnimTeleport( og, anim_og_idle, animEnt ) + + bt.Anim_Stop() + thread PlayAnimTeleport( bt, anim_bt_idle, animEnt ) + + // wait for look training to get started before killing the scene + FlagWaitAny( "PlayerLookedAtTopTarget", "PlayerLookedAtBottomTarget" ) +} + +void function PodIntro_OG_Slaps_Pod( entity ogPilot ) +{ + array<entity> players = GetPlayerArray() + if ( !players.len() ) + return + + entity player = players[0] + if ( !IsValid( player ) ) + return + + float shakeDuration = 0.45 + float shakeAmplitude = 0.14 + float screenBlurFrac = 0 + SimpleScreenShake( player, shakeDuration, shakeAmplitude, screenBlurFrac ) +} + +void function LookTraining( entity player ) +{ + thread LookTraining_StartNag( player, "PlayerLookedAtTopTarget", "PlayerLookedAtBottomTarget" ) + + Remote_CallFunction_Replay( player, "ScriptCallback_ShowInvertCrosshair", true ) + + // LOOKAT SECTION + Remote_CallFunction_Replay( player, "ScriptCallback_SetupLookTargets" ) + wait 0.5 + Remote_CallFunction_Replay( player, "ScriptCallback_LookTargets_WaitForLookat" ) + + DialogueGroup invertConfirmVO = GetDialogueGroup( "ogInvertConfirm" ) + + int numInverts = 0 + int maxInverts = 2 + //while ( numInverts < maxInverts ) + while ( 1 ) + { + Remote_CallFunction_Replay( player, "ScriptCallback_SetupLookTargets" ) + wait 0.5 + Remote_CallFunction_Replay( player, "ScriptCallback_LookTargets_WaitForLookat" ) + + string hintAlias = "invert_look_at_lights" + if ( numInverts > 0 ) + hintAlias = "invert_look_at_lights_again" + + // only play this VO once + if ( numInverts == 1 ) + { + // "You sure?" + thread PlayDialogue( "og_invert_confirm_3", player ) + } + + DisplayOnscreenHint( player, hintAlias ) + + printt( "Waiting for player to look at either target" ) + + FlagWaitAny( "PlayerLookedAtTopTarget", "PlayerLookedAtBottomTarget" ) + + printt( "Player looked at one of the targets" ) + + hintAlias = "invert_look_at_lights_1_left" + if ( numInverts > 0 ) + hintAlias = "invert_look_at_lights_again_1_left" + + DisplayOnscreenHint( player, hintAlias ) + + FlagWait( "PlayerLookedAtTopTarget" ) + FlagWait( "PlayerLookedAtBottomTarget" ) + + printt( "Player looked at both targets" ) + + hintAlias = "invert_look_at_lights_0_left" + if ( numInverts > 0 ) + hintAlias = "invert_look_at_lights_again_0_left" + + DisplayOnscreenHint( player, hintAlias ) + + printt( "invert waiting for OG dialogue" ) + + // "Does that feel right to you?" + // "How about now? Feel alright?" + string askAlias = DialogueGroup_GetNextLine( invertConfirmVO ) + waitthread PlayDialogue( askAlias, player ) + + printt( "invert OG dialogue done" ) + + ClearOnscreenHint( player ) + + string invertConvar = file.playerInputType == 0 ? INVERT_CONVAR_GAMEPAD : INVERT_CONVAR_MOUSE + printt( "invertConvar:", invertConvar ) + bool invertSettingBeforeMenu = GetConVarBool( invertConvar ) + + printt( "Opening invert look dialog" ) + + // THIS PAUSES THE GAME UNTIL MENU IS CLOSED + Remote_CallFunction_UI( player, "ScriptCallback_OpenInvertLookDialog" ) + wait 0.5 // let the game come back after menu is closed + + // if player didn't change setting, don't repeat + invertConvar = file.playerInputType == 0 ? INVERT_CONVAR_GAMEPAD : INVERT_CONVAR_MOUSE + printt( "invertConvar:", invertConvar ) + if ( GetConVarBool( invertConvar ) == invertSettingBeforeMenu ) + { + printt( "player didn't change setting, not repeating" ) + break + } + + printt( "invert- player changed setting, repeating to confirm" ) + + numInverts++ + + // kill lights and reset flags + Remote_CallFunction_Replay( player, "ScriptCallback_LookTargets_KillLights" ) + + FlagClear( "PlayerLookedAtTopTarget" ) + FlagClear( "PlayerLookedAtBottomTarget" ) + } + + ClearOnscreenHint( player ) + Remote_CallFunction_Replay( player, "ScriptCallback_ShowInvertCrosshair", false ) + + // "Alright, we're good to go." + waitthread PlayDialogue( "og_invert_complete", player ) + + TrainingPod_ViewConeLock_PodClosed( player ) + + TrainingPod_ViewConeLock_SemiStrict( player ) // recenter player view + Remote_CallFunction_Replay( player, "ScriptCallback_LookTargets_KillLights" ) +} + +void function LookTraining_StartNag( entity player, string flag1, string flag2 ) +{ + EndSignal( player, "OnDestroy" ) + + if ( Flag( flag1 ) || Flag( flag2 ) ) + return + + FlagEnd( flag1 ) + FlagEnd( flag2 ) + + float nagInterval = 15.0 + float nextNagTime = Time() + nagInterval + + while( 1 ) + { + wait 1 + + if ( Time() < nextNagTime ) + continue + + // "We have to calibrate the pod. It won't boot up until you look at both of those lights." + waitthread PlayDialogue( "og_pod_calibrate", player ) + + nextNagTime = Time() + nagInterval + } +} + +void function TrainingPod_PlayerSequence_DoorsOpenIdle( entity player, bool doPlayerAnim = true ) +{ + entity pod = file.trainingPod + + // Have to do this first so the anim starts centered on the ref attachment angles + string podAttach = "REF" + int attachID = pod.LookupAttachment( podAttach ) + vector podRefOrg = pod.GetAttachmentOrigin( attachID ) + vector podRefAng = pod.GetAttachmentAngles( attachID ) + player.SetOrigin( podRefOrg ) + player.SetAngles( podRefAng ) + player.ForceStand() + + player.DisableWeapon() + + // default start anim starts open + void functionref( entity ) viewConeFunction_start = TrainingPod_ViewConeLock_PodOpen + string podAnim_start = "trainingpod_doors_open_idle" + + // start open idle + FirstPersonSequenceStruct playerSequence + playerSequence.blendTime = 0.0 + playerSequence.attachment = podAttach + playerSequence.firstPersonAnimIdle = "ptpov_trainingpod_idle" + playerSequence.thirdPersonAnimIdle = "pt_trainingpod_idle" + playerSequence.viewConeFunction = viewConeFunction_start + playerSequence.renderWithViewModels = true + + FirstPersonSequenceStruct podSequence + podSequence.blendTime = 0.0 + podSequence.thirdPersonAnimIdle = podAnim_start + podSequence.renderWithViewModels = true + + thread FirstPersonSequence( podSequence, pod ) + + if ( doPlayerAnim ) + thread FirstPersonSequence( playerSequence, player, pod ) + + TrainingPod_TurnOnInteriorDLight( "console1" ) + TrainingPod_TurnOnInteriorDLight( "console2" ) + //TrainingPod_TurnOnInteriorDLight( "backlight_side_L" ) + //TrainingPod_TurnOnInteriorDLight( "backlight_side_R" ) +} + + + +// ------ POD INTRO BACKGROUND SKITS ------ +void function PodIntro_BackgroundSkits( entity player ) +{ + string endFlag = "PodIntro_PodDoorsClosed" + FlagEnd( endFlag ) + + thread PodIntro_LoudspeakerVO( player, "PodIntro_InteriorBootSequence_Starting" ) + + thread PodIntro_TitanRacks( endFlag ) + + thread PodIntro_Background_WalkingGuys() + + // ---- ANIMATING SKIT GUYS --- + // script NudgeSkitGuy( "back_console_supervisor", 0, 5, 0 ) + + // guy sitting at console in back center of room + SkitGuyInfo backConsoleGuy1 = SpawnSkitGuy( "back_console_sitting", "pt_console_idle", < 10521.1, -9679.33, -6044.1 >, < 0, 88.3541, 0 >, TEAM_MILITIA, "npc_soldier" ) + SkitGuy_PlayAnim( backConsoleGuy1 ) + SkitGuyInfo backConsoleGuy2 = SpawnSkitGuy( "back_console_supervisor", "pt_bored_interface_leanback", < 10527.6, -9656.36, -6043.97 >, < 0, -26.564, 0 >, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_g2" ) + SkitGuy_PlayAnim( backConsoleGuy2 ) + + // extra marvins working on titans 1 and 2 + SkitGuyInfo titanBay1_marvin = SpawnSkitGuy( "bay1_marvin", "mv_idle_weld", < 10711.2, -9781.04, -6080.65 >, < 0, 153.715, 0 > ) + SkitGuy_PlayAnim( titanBay1_marvin, 3.0 ) // don't play same anim at same time + SkitGuyInfo titanBay2_marvin = SpawnSkitGuy( "bay2_marvin", "mv_idle_weld", < 10316.2, -9730.23, -6079.97 >, < 0, -69.965, 0 > ) + SkitGuy_PlayAnim( titanBay2_marvin ) + + // guys looking at console in back left + SkitGuyInfo leftConsoleGuy1 = SpawnSkitGuy( "console_lean", "pt_bored_interface_leanin", < 10257.6, -9846.63, -6079.97 >, < 0, 34.5175, 0 >, TEAM_MILITIA, "npc_soldier" ) + SkitGuy_PlayAnim( leftConsoleGuy1 ) + SkitGuyInfo leftConsoleGuy2 = SpawnSkitGuy( "console_supervisor", "pt_bored_interface_leanback", < 10260.5, -9874.78, -6079.97 >, < 0, -42.534, 0 >, TEAM_MILITIA, "npc_soldier", "mp_weapon_mastiff" ) + SkitGuy_PlayAnim( leftConsoleGuy2 ) + + OnThreadEnd( + function() : () + { + DeleteAllSkitGuys() + } + ) + + WaitForever() +} + +void function PodIntro_LoudspeakerVO( entity player, string endFlag ) +{ + EndSignal( player, "OnDestroy" ) + + array<string> aliases + // "Inbound to Planet Typhon. Subspace rendezvous in approximately 20 minutes." + aliases.append( "intro_0" ) + // "Reminder to dock personnel: Titan ordnance is a Type 3 Hazardous Material." + aliases.append( "intro_2" ) + // "Running Lifeboat diagnostic test two point one. All Mark Eight lifeboats are in the green." + aliases.append( "intro_4" ) + // "3rd Militia Grenadiers - prep dropship MacAllan 17." + aliases.append( "intro_5" ) + // "Captain Cole to communications." + aliases.append( "intro_3" ) + // "Major Anderson, please report to the briefing room." + aliases.append( "intro_1" ) + + thread LoopLoudspeakerVO( aliases, endFlag, 1.0, 2.0 ) +} + +void function PodIntro_Background_WalkingGuys() +{ + // group of guys walking left to right + array<Point> path1 = [] + ScriptedPath_AddPoint( path1, < 10327.9, -10000.28, -6079.97 >, < 0, 0.918, 0 > ) + ScriptedPath_AddPoint( path1, < 10373.5, -9987.14, -6079.97 >, < 0, 0.093, 0 > ) + ScriptedPath_AddPoint( path1, < 10688.3, -9973.76, -6079.97 >, < 0, 0, 0 > ) + ScriptedPath_AddPoint( path1, < 10856.4, -9973.58, -6079.97 >, < 0, 0, 0 > ) + SkitGuyInfo walkerInfo_1 = SpawnSkitGuy( "walker_1", "", path1[0].origin, path1[0].angles, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_hemlok_smg" ) + thread ScriptedPath_Walk( walkerInfo_1, path1, 0.75 ) + + array<Point> path2 = [] + ScriptedPath_AddPoint( path2, < 10248.6, -9925.06, -6079.97 >, < 0, 6.36392, 0 > ) + ScriptedPath_AddPoint( path2, < 10373.5, -9939.14, -6079.97 >, < 0, 0.093, 0 > ) + ScriptedPath_AddPoint( path2, < 10688.3, -9973.76, -6079.97 >, < 0, 0, 0 > ) + ScriptedPath_AddPoint( path2, < 10856.4, -9973.58, -6079.97 >, < 0, 0, 0 > ) + SkitGuyInfo walkerInfo_2 = SpawnSkitGuy( "walker_2", "", path2[0].origin, path2[0].angles, TEAM_MILITIA, "npc_soldier", "mp_weapon_hemlok" ) + thread ScriptedPath_Walk( walkerInfo_2, path2, 0.85 ) +} + +void function PodIntro_TitanRacks( string endFlag ) +{ + FlagEnd( endFlag ) + + array<HangarTitanGroup> titanGroups + + HangarTitanGroup bay1 + bay1.ref = GetEntByScriptName( "animref_drop_hangar_titan_1" ) + bay1.titan = GetEntByScriptName( "hangar_titan_1" ) + bay1.rack = GetEntByScriptName( "hangar_titan_rack_1" ) + bay1.titanAnim = "bt_TDay_drop_titan4" + bay1.rackAnim = "rack_TDay_drop_rack4" + bay1.marvinAnim = "mv_TDay_drop_marvin4" + HangarTitanGroup_Init( bay1 ) + titanGroups.append( bay1 ) + + // --- BAY 2 --- + HangarTitanGroup bay2 + bay2.ref = GetEntByScriptName( "animref_drop_hangar_titan_2" ) + bay2.titan = GetEntByScriptName( "hangar_titan_2" ) + bay2.rack = GetEntByScriptName( "hangar_titan_rack_2" ) + bay2.titanAnim = "bt_TDay_drop_titan3" + bay2.rackAnim = "rack_TDay_drop_rack3" + bay2.marvinAnim = "mv_TDay_drop_marvin3" + HangarTitanGroup_Init( bay2 ) + titanGroups.append( bay2 ) + + float wait_earlyEnd = 27.0 + thread HangarTitanGroup_Animate( bay1, endFlag, wait_earlyEnd ) + thread HangarTitanGroup_Animate( bay2, endFlag, wait_earlyEnd ) + + wait wait_earlyEnd + thread PodIntro_TitanRacks( endFlag ) +} + + + +// =================================== +// ========= BASIC MOVEMENT ========== +// =================================== +void function Training_Setup_BasicMovement( entity player ) +{ + player.DisableWeapon() +} + +void function Training_Skipped_BasicMovement( entity player ) +{ + player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, [ "disable_doublejump", "disable_wallrun" ] ) + + Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" ) +} + +void function Training_BasicMovement( entity player ) +{ + entity standNearJumpRef = GetEntByScriptName( "basic_movement_og_stand_near_jump" ) + standNearJumpRef.SetOrigin( OriginToGround( standNearJumpRef.GetOrigin() + <0,0,0.5> ) ) // HACK HACK the ref node is a tiny bit in the geo which causes OG to dip when blending between anims + entity ogStart = GetEntByScriptName( "basic_movement_og_start" ) + entity og = Training_SpawnOGPilot( ogStart ) + + player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, [ "disable_doublejump", "disable_wallrun" ] ) + player.ForceAutoSprintOff() + + entity playerStart = GetEntByScriptName( "startpoint_basic_movement" ) + waitthread PlayerAndOGTeleport_Fancy( player, playerStart.GetOrigin(), "basic_movement_og_start", playerStart.GetAngles() ) + Training_OG_Idles_Sitting( ogStart, "OG_base_move_A_idle" ) + + thread BasicMovement_DelayedWeaponDeploy( player, 1.5 ) + Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" ) + + // "Ah. Much better." + waitthread Training_OG_ScriptedAnim( ogStart, "OG_base_move_A" ) + thread Training_OG_Idles_Sitting( ogStart, "OG_base_move_B_idle" ) + + CheckPoint_Silent() + + thread OnscreenHint_DisplayUntilFlag( player, "move_hint", "BasicMovement_PlayerMovedForward", 10.0 ) + + FlagWait( "BasicMovement_PlayerMovedForward" ) + + if ( !Flag( "BasicMovement_PlayerJumped" ) ) + { + // "Technically, I'm not supposed to be training you. But in you, I see potential. + waitthread Training_OG_ScriptedAnim( ogStart, "OG_base_move_B1") + } + + if ( !Flag( "BasicMovement_PlayerJumped" ) ) + { + // "Besides, we're at war. Who's got time for classes, eh?" + waitthread Training_OG_ScriptedAnim( ogStart, "OG_base_move_B2") + thread Training_OG_Idles_Sitting( ogStart ) + } + + if ( !Flag( "BasicMovement_PlayerJumped" ) && player.IsOnGround() ) + { + DisplayOnscreenHint( player, "jump_hint" ) + + // "Here you go, up and over." + waitthread Training_OG_ScriptedAnim( ogStart, "OG_base_move_C" ) + waitthread Training_OG_Moves( standNearJumpRef ) + } + + if ( !Flag( "BasicMovement_PlayerJumped" ) ) + { + // "Cmon, schedule's tight today." + // "Here you go, up and over." + array<string> jumpNags = [ "og_jump_nag_2", "og_jump_nag" ] + + thread Training_OG_NagPlayerUntilFlag( player, jumpNags, 20.0, standNearJumpRef, "BasicMovement_PlayerJumped" ) + } + + FlagWait( "BasicMovement_PlayerJumped" ) + + ClearOnscreenHint( player ) + + waitthread BasicMovement_Sprint( player ) +} + +void function BasicMovement_Sprint( entity player ) +{ + thread BasicMovement_Sprint_OG_Moves( player ) + + //player.ForceAutoSprintOn() + + FlagClear( "PlayerSprinted" ) + thread BasicMovement_PlayerSprintDetection( player ) + + // "Let's pick up the pace. Enabling jumpkit assist." + float endVOTime = Time() + 3.5 + thread PlayDialogue( "og_autosprint_on", file.ogPilot ) + + wait 2.5 // let the VO play a bit before showing the hint + + player.UnforceAutoSprint() + thread BasicMovement_SprintHint_Think( player ) + + wait endVOTime - Time() + + // HACK- Create a fake VO speaker where OG will eventually move to/from so the lines emit in worldspace for audio + entity ref = GetEntByScriptName( "basic_movement_og_mid_hallway" ) + entity tempSpeaker = CreateScriptMover( ref.GetOrigin(), <0,0,0> ) + thread HACK_MoveTempSpeaker_WithOGPathMover( player, tempSpeaker ) + + // "Jumpkits operate on the principle of relaxed stability." + EmitSoundOnEntity( tempSpeaker, "diag_sp_addtional_TR411_53_mcor_og" ) // do this instead of PlayDialogue because it will follow the ent around + wait 3.4 // HACK + + // "Once your jumpkit calibrates to your movement style, enhanced mobility becomes second nature." + EmitSoundOnEntity( tempSpeaker, "diag_sp_movement_TR121_08_01_mcor_og" ) + wait 6.0 // HACK + + tempSpeaker.Destroy() +} + +void function HACK_MoveTempSpeaker_WithOGPathMover( entity player, entity tempSpeaker ) +{ + EndSignal( player, "OnDestroy" ) + EndSignal( tempSpeaker, "OnDestroy" ) + + vector prevOrg = < -1,-1,-1 > + + while ( 1 ) + { + WaitFrame() + + vector newOrg = < -1,-1,-1 > + + // if we are doing a path move, use the pathMover origin- otherwise use OG's origin + if ( IsValid( file.ogPathMover ) ) + newOrg = file.ogPathMover.GetOrigin() + else if ( IsValid( file.ogPilot ) ) + file.ogPilot.GetOrigin() + + if ( newOrg != < -1,-1,-1 > && newOrg != prevOrg ) + { + tempSpeaker.SetOrigin( newOrg ) + prevOrg = newOrg + } + } +} + +void function BasicMovement_SprintHint_Think( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + float autosprintHintTime = 10.0 + + // player with autosprint on will already be sprinting + if ( GetAutosprintEnabled() ) + { + thread DisplayOnscreenHint( player, "autosprint_hint", autosprintHintTime ) + return + } + + // Below this point assume that autosprint is NOT enabled + if ( !Flag( "PlayerSprinted" ) ) + { + thread OnscreenHint_DisplayUntilFlag( player, "sprint_button_hint", "PlayerSprinted", 0.0, true ) + FlagWait( "PlayerSprinted" ) + + wait 1.0 + } + + DisplayOnscreenHint( player, "autosprint_available", autosprintHintTime ) +} + +void function BasicMovement_Sprint_OG_Moves( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + entity ogMidHallwayRef = GetEntByScriptName( "basic_movement_og_mid_hallway" ) + waitthread Training_OG_Moves_ToSitting( ogMidHallwayRef, "", 0.8 ) + + FlagWait( "BasicMovement_PlayerReachedMidHallway" ) + + entity og_zenGarden_start = GetEntByScriptName( "basic_movement_og_zen_start" ) + thread Training_OG_Moves( og_zenGarden_start, "OG_Beautiful_idle" ) +} + +void function BasicMovement_PlayerSprintDetection( entity player ) +{ + EndSignal( player, "OnDestroy" ) + FlagEnd( "BasicMovement_PlayerReachedOpenArea" ) + + while ( 1 ) + { + WaitFrame() + + if ( player.IsSprinting() ) + break + } + + FlagSet( "PlayerSprinted" ) +} + +void function BasicMovement_DelayedWeaponDeploy( entity player, float delay ) +{ + player.EndSignal( "OnDestroy" ) + + wait delay + player.EnableWeaponWithSlowDeploy() + thread TakeAmmoFromPlayerASAP( player ) +} + + +// ================================================= +// ========= BASIC MOVEMENT 2: ZEN GARDEN ========== +// ================================================= +void function Training_Setup_ZenGarden( entity player ) +{ + entity ogStart = GetEntByScriptName( "basic_movement_og_zen_start" ) + entity og = Training_SpawnOGPilot( ogStart ) + Training_OG_Idles( ogStart, "OG_Beautiful_idle" ) + + thread TakeAmmoFromPlayerASAP( player ) + + TeleportPlayerAndBT( "startpoint_zen_garden" ) +} + +void function Training_Skipped_ZenGarden( entity player ) +{ + player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, [] ) + + OpenZenGardenExitDoor() +} + +void function Training_ZenGarden( entity player ) +{ + entity og = GetOGPilot() + Assert( IsValid( og ) ) + + entity ref_hillclimbFinish = GetEntByScriptName( "zengarden_hillclimb_finish_idle" ) + entity ref_exitSpot = GetEntByScriptName( "zengarden_og_exit_ref" ) + + OpenZenGardenExitDoor() + + FlagWait( "BasicMovement_PlayerReachedOpenArea" ) + + player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, ["disable_doublejump"] ) + + CheckPoint_Silent() + + thread ZenGarden_WhyWeFight_VO( player, og ) + + waitthread Training_ZenGarden_Wallrun( player ) + + waitthread Training_ZenGarden_Crouch( player ) + + player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, [] ) + + CheckPoint_Silent() + + waitthread Training_ZenGarden_DoubleJump( player ) + + waitthread Training_ZenGarden_HillClimb( player, ref_hillclimbFinish, ref_exitSpot ) + + if ( IsValid( level ) ) + Signal( level, "StopRepeatingGhostRecorder" ) +} + +// ZEN GARDEN SHARED BETWEEN PATHS +void function ZenGarden_WhyWeFight_VO( entity player, entity og ) +{ + entity ogStart = GetEntByScriptName( "basic_movement_og_zen_start" ) + entity firstRockRef = GetEntByScriptName( "zengarden_og_rock1_ref" ) + entity pathRef = GetEntByScriptName( "zengarden_og_path_ref" ) + entity treeRef = GetEntByScriptName( "zengarden_og_tree_ref" ) + + string stopFlag = "ZenGarden_PlayerReachedWallrunStart" + string setFlag = "OG_WhyWeFight_VO_Done" + + if ( !Flag( stopFlag ) ) + { + // "Beautiful, isn't it?" + waitthread Training_OG_ScriptedAnim( ogStart, "OG_Beautiful" ) + } + + if ( !Flag( stopFlag ) ) + { + waitthread Training_OG_Moves( firstRockRef, "", 0.25 ) + } + + if ( !Flag( stopFlag ) ) + { + // "It's inspired by my home planet of Harmony." + waitthread Training_OG_ScriptedAnim( firstRockRef, "OG_harmony_A" ) + } + + if ( !Flag( stopFlag ) ) + { + // "This is where I grew up." + waitthread Training_OG_ScriptedAnim( firstRockRef, "OG_harmony_B" ) + } + + if ( !Flag( stopFlag ) && !Flag( "ZenGarden_PlayerReachedFirstRock" ) ) + { + waitthread Training_OG_Moves( pathRef ) + } + + FlagWaitAny( stopFlag, "ZenGarden_PlayerReachedFirstRock" ) + + if ( !Flag( stopFlag ) ) + { + waitthread Training_OG_Moves_ToSitting( treeRef, "", 0.5 ) + } + + if ( !Flag( stopFlag ) ) + { + // "This is what we're fighting for, Cooper." + waitthread Training_OG_ScriptedAnim( treeRef, "OG_freedom_A" ) + } + + if ( !Flag( stopFlag ) ) + { + // "A world that's not metal and smoke." + waitthread Training_OG_ScriptedAnim( treeRef, "OG_freedom_B" ) + } + + if ( !Flag( stopFlag ) ) + { + // "The freedom to live in peace and prosperity." + waitthread Training_OG_ScriptedAnim( treeRef, "OG_freedom_C" ) + } + + FlagSet( setFlag ) +} + +void function Training_ZenGarden_Wallrun( entity player ) +{ + entity wallrunRef = GetEntByScriptName( "basic_movement_og_zen_wallrun_idle" ) + entity recorderRef = GetEntByScriptName( "basic_movement_wallrun_start_ref" ) + + FlagWaitAny( "ZenGarden_PlayerReachedWallrunStart", "OG_WhyWeFight_VO_Done" ) + + FlagWait( "OG_WhyWeFight_VO_Done" ) + + waitthread Training_OG_Moves_ToSitting( wallrunRef, "OG_primed_idle", 0.5 ) + + FlagWait( "ZenGarden_PlayerReachedWallrunStart" ) + + thread GhostRecorder_RepeatUntilFlag( player, "ZenGarden_PlayerFinishedWallrun", recorderRef, $"anim_recording/training_record_zengarden_wallrun.rpak" ) + + if ( !Flag( "ZenGarden_PlayerFinishedWallrun" ) ) + thread OnscreenHint_DisplayUntilFlag( player, "wallrun_hint", "ZenGarden_PlayerFinishedWallrun" ) + + wait 1.0 // wait to see if player starts wallrunning right away + + if ( !Flag( "ZenGarden_PlayerFinishedWallrun" ) && !Flag( "ZenGarden_PlayerTouchingWallrunPanel" ) ) + { + // "Let's make sure your jump kit is primed. Basic wallrun here, give it a try." + waitthread Training_OG_ScriptedAnim( wallrunRef, "OG_primed" ) + thread Training_OG_Idles_Sitting( wallrunRef, "OG_primed_idle" ) + } + + if ( !Flag( "ZenGarden_PlayerFinishedWallrun" ) && !Flag( "ZenGarden_PlayerTouchingWallrunPanel" ) ) + wait 1.0 + + if ( !Flag( "ZenGarden_PlayerFinishedWallrun" ) && !Flag( "ZenGarden_PlayerTouchingWallrunPanel" ) ) + { + // "Same routine as last time- watch the ghost pilot, and try to follow along." + thread PlayDialogue( "og_wallrun_follow_ghost", file.ogPilot ) + waitthread Training_OG_ScriptedAnim( wallrunRef, "OG_primed_generic" ) + thread Training_OG_Idles_Sitting( wallrunRef, "OG_primed_idle" ) + } + + FlagWait( "ZenGarden_PlayerFinishedWallrun" ) + + if ( !Flag( "ZenGarden_PlayerReachedCrouchArea" ) ) + { + // "Good! Now you're moving." + file.postWallrunVOEndTime = Time() + 3.0 + thread PlayDialogue( "og_wallrun_done", player ) + wait 0.3 // min wait after + } +} + +void function Training_ZenGarden_Crouch( entity player ) +{ + entity ogIdle_crouchSpot = GetEntByScriptName( "zengarden_og_slide_idle") + entity ref_slideStart = GetEntByScriptName( "zengarden_slide_ref" ) + + if ( Flag( "ZenGarden_PlayerCrouched" ) ) + return + + waitthread Training_OG_Moves_ToSitting( ogIdle_crouchSpot, "OG_low_idle", 0.8 ) + thread GhostRecorder_RepeatUntilFlag( player, "ZenGarden_PlayerCrouched", ref_slideStart, $"anim_recording/training_record_zengarden_slide.rpak", 1.0 ) + + FlagWait( "ZenGarden_PlayerReachedCrouchArea" ) + + DisplayOnscreenHint( player, "crouch_hint", 5.0 ) + + while ( file.postWallrunVOEndTime > 0 && file.postWallrunVOEndTime - Time() > 0 ) + wait 0.1 + + if ( Flag( "ZenGarden_PlayerCrouched" ) ) + return + + // "Under here. Stay low." + waitthread Training_OG_ScriptedAnim( ogIdle_crouchSpot, "OG_low" ) + thread Training_OG_Idles_Sitting( ogIdle_crouchSpot, "OG_low_idle", true ) + + thread Training_ZenGarden_CrouchHint_WithNags( player, 10.0, ogIdle_crouchSpot, "OG_low_idle" ) + + FlagWait( "ZenGarden_PlayerCrouched" ) + + ClearOnscreenHint( player ) +} + +void function Training_ZenGarden_CrouchHint_WithNags( entity player, float nagInterval, entity idleRef, string ogIdleAnim ) +{ + player.EndSignal( "OnDestroy" ) + + string endFlag = "ZenGarden_PlayerCrouched" + string activeFlag = "ZenGarden_PlayerAtCrouchStart" + + // "Crouch underneath, and we'll keep moving." + // "You need to get low here." + array<string> nags = [ "og_crouch_nag_1", "og_crouch_nag_2" ] + + int nagIdx = 0 + float nextNagTime = Time() + nagInterval + + bool showingHint = false + + while ( !Flag( endFlag ) ) + { + FlagWait( activeFlag ) + + while ( Flag( activeFlag ) ) + { + wait 0.1 + + if ( player.IsCrouched() ) + { + if ( showingHint ) + { + showingHint = false + ClearOnscreenHint( player ) + } + + continue + } + + if ( !showingHint ) + { + DisplayOnscreenHint( player, "crouch_hint" ) + showingHint = true + } + + if ( Time() - nextNagTime >= nagInterval ) + { + thread Training_OG_Talks_Sitting( nags[nagIdx], idleRef, "", ogIdleAnim ) + nextNagTime = Time() + nagInterval + + nagIdx++ + if ( nagIdx >= nags.len() ) + nagIdx = 0 + } + } + + if ( showingHint ) + { + showingHint = false + ClearOnscreenHint( player ) + } + } +} + +void function Training_ZenGarden_DoubleJump( entity player ) +{ + entity pathRef = GetEntByScriptName( "zengarden_og_postslide_idle" ) + entity ogIdleRef = GetEntByScriptName( "zengarden_og_doublejump_idle" ) + entity recordedAnimRef = GetEntByScriptName( "zengarden_doublejump_ref" ) + + waitthread Training_OG_Moves( pathRef, "", 0.5 ) + + // "Simple double jump. Follow the ghost." + waitthread Training_OG_Talks( "og_doublejump_hint", pathRef ) + + thread GhostRecorder_RepeatUntilFlag( player, "ZenGarden_PlayerDoubleJumped", recordedAnimRef, $"anim_recording/training_record_zengarden_doublejump.rpak", 1.0 ) + + waitthread Training_OG_Moves_ToSitting( ogIdleRef, "OG_doublejump_idle", 0.5 ) + + // "We've retaken over a quarter of Frontier space since the Battle of Demeter. The Militia's better organized now. More people join everyday to fight the IMC. People like you." + waitthread Training_OG_ScriptedAnim( ogIdleRef, "OG_doublejump_A" ) + Training_OG_Idles( ogIdleRef, "OG_doublejump_B_idle" ) + + FlagWait( "ZenGarden_PlayerReachedDoubleJumpArea" ) + + if ( !Flag( "ZenGarden_PlayerDoubleJumped" ) ) + { + DisplayOnscreenHint( player, "doublejump_hint", 5.0 ) + thread OnscreenHint_NagUntilFlag( player, "doublejump_hint", "ZenGarden_PlayerDoubleJumped", 10.0, 5.0 ) + + wait 0.8 + } + + FlagWait( "ZenGarden_PlayerDoubleJumped" ) +} + +void function Training_ZenGarden_HillClimb( entity player, entity ref_hillclimbFinish, entity ref_exitSpot ) +{ + // "We used to just run and hide from them. But now we chase them." + string nextAnim = "OG_doublejump_B" + + if ( !Flag( "ZenGarden_PlayerClimbedHill" ) ) + { + waitthread Training_OG_Moves( ref_hillclimbFinish, "", 0.25 ) + //waitthread Training_OG_Talks( nextLine, ref_hillclimbFinish ) + waitthread Training_OG_ScriptedAnim( ref_hillclimbFinish, nextAnim ) + FlagWait( "ZenGarden_PlayerClimbedHill" ) + waitthread Training_OG_Moves( ref_exitSpot, ANIM_OG_LEANING_IDLE, 0.5 ) + } + else if ( !Flag( "ZenGarden_PlayerReachedExitArea" ) ) + { + waitthread Training_OG_Moves( ref_exitSpot, ANIM_OG_LEANING_IDLE, 0.5 ) + + if ( !Flag( "ZenGarden_PlayerReachedExitArea" ) ) + waitthread Training_OG_ScriptedAnim( ref_exitSpot, nextAnim ) + + if ( !Flag( "ZenGarden_PlayerReachedExitArea" ) ) + waitthread Training_OG_Moves( ref_exitSpot, ANIM_OG_LEANING_IDLE, 0.5 ) + } + + FlagWait( "ZenGarden_PlayerReachedExitArea" ) +} + + +// ================================= +// ========= FIRING RANGE ========== +// ================================= +void function Training_Setup_FiringRange( entity player ) +{ + entity ogStart = GetEntByScriptName( "firingrange_og_needguns_start_sitting" ) + entity og = Training_SpawnOGPilot( ogStart ) + Training_OG_Idles_Sitting( ogStart ) + + thread TakeAmmoFromPlayerASAP( player ) + + TeleportPlayerAndBT( "startpoint_firing_range" ) +} + +void function Training_Skipped_FiringRange( entity player ) +{ + CloseZenGardenExitDoor() + OpenGauntletDoor() + + FlagSet( "ineedguns" ) + Training_WeaponRacks_SetSolidity( true ) + + player.SetExtraWeaponMods( [ "" ] ) // turns off low_ammo_disable + SetWeaponHUDEnabled( player, true ) + + Training_SetWeaponPickupsFullAmmo() +} + +void function Training_FiringRange( entity player ) +{ + entity ref_og_needGunsStart = GetEntByScriptName( "firingrange_og_needguns_start_sitting" ) + entity ref_og_firingRangeSpot = GetEntByScriptName( "firingrange_og_spot" ) + entity ref_og_firingRangeAttractSpot = GetEntByScriptName( "firingrange_og_attract_spot" ) + + float ogMoveTime = -1 + + Training_WeaponRacks_SetSolidity( false ) + Training_SetWeaponPickupsEmptyAmmo() + + thread FiringRange_CloseZenGardenDoor_WhenPlayerReachesRange( player ) + + waitthread Training_OG_Moves_ToSitting( ref_og_needGunsStart, "OG_Weapons_idle", ogMoveTime ) + + FlagWait( "PlayerApproachingFiringRange" ) + + thread FiringRange_DetectWeaponSwitch( player ) + + waitthread FiringRange_ApproachAndEntry( player, ref_og_needGunsStart, ref_og_firingRangeAttractSpot, ref_og_firingRangeSpot ) + + waitthread FiringRange_TrainReload( player, ref_og_firingRangeSpot ) + + Training_WeaponRacks_SetSolidity( true ) + thread FiringRange_InfiniteAmmo_WhenNearRange( player, "PodOutroStarted" ) + thread FiringRange_ResetTargets_Think( player ) + + thread Training_OG_Idles( ref_og_firingRangeSpot, "OG_firingrange_idle" ) + + waitthread FiringRange_TrainADS( player, ref_og_firingRangeSpot ) + + waitthread FiringRange_PlayerMustDamageAllTargets( player, ref_og_firingRangeSpot, true, false ) + + Training_SetWeaponPickupsFullAmmo() + + if ( !Flag( "FiringRangeWeaponSwapped" ) ) + { + waitthread FiringRange_TrainWeaponSwap( player, ref_og_firingRangeSpot ) + + FlagWait( "PlayerNearFiringRange" ) // if player moved away to get a weapon, wait for them to come back + wait 0.25 // extra wait for player to see the targets reset + + waitthread FiringRange_PlayerMustDamageAllTargets( player, ref_og_firingRangeSpot, false ) + } + + OpenGauntletDoor() + + CheckPoint_Silent() + + // "Good. Practice more if you want, then head to the Gauntlet." + waitthread Training_OG_ScriptedAnim( ref_og_firingRangeSpot, "OG_firingrange_ending" ) + + array<string> moveToGauntletNags = [ "og_moving_to_gauntlet_nag_1", "og_moving_to_gauntlet_nag_2", "og_moving_to_gauntlet_nag_3" ] + + if ( Flag( "PlayerNearFiringRange" ) ) + { + entity ref_og_midway2Gauntlet = GetEntByScriptName( "og_between_firingrange_and_gauntlet" ) + thread Training_OG_NagPlayerUntilFlag_Sitting( player, moveToGauntletNags, 45.0, ref_og_midway2Gauntlet, "OG_MovedTo_GauntletEntrance" ) + + waitthread Training_OG_Moves_ToSitting( ref_og_midway2Gauntlet ) + + FlagWaitClear( "PlayerNearFiringRange" ) + } + + FlagSet( "OG_MovedTo_GauntletEntrance" ) + + entity ref_og_gauntletEntrance = GetEntByScriptName( "og_gauntlet_entrance_attract_spot" ) + thread Training_OG_NagPlayerUntilFlag( player, moveToGauntletNags, 45.0, ref_og_gauntletEntrance, "PlayerInGauntletEntryway" ) + + waitthread Training_OG_Moves( ref_og_gauntletEntrance ) +} + +void function FiringRange_CloseZenGardenDoor_WhenPlayerReachesRange( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + vector doorFarEdge = < -6368, -952, 48 > // HACK this is the bottom edge of the door farthest from the firing range + + while ( 1 ) + { + WaitFrame() + + if ( !Flag( "PlayerNearFiringRange" ) ) + continue + + if ( PlayerCanSeePos( player, doorFarEdge, true, 90 ) ) + continue + + break + } + + CloseZenGardenExitDoor() +} + +void function FiringRange_ApproachAndEntry( entity player, entity ref_og_needGunsStart, entity ref_og_firingRangeAttractSpot, entity ref_og_firingRangeSpot ) +{ + thread FiringRange_Approach_OG_Sequence( player, ref_og_needGunsStart ) + + FlagWait( "FiringRange_Approach_OG_Sequence_Done" ) + + if ( !Flag( "PlayerNearFiringRange" ) ) + { + // "Time to hit the range." + waitthread Training_OG_ScriptedAnim( ref_og_needGunsStart, "OG_Weapons_D" ) + } + + if ( !Flag( "PlayerNearFiringRange" ) ) + { + // "I'm over here at the range, Cooper." + thread Training_OG_NagPlayerUntilFlag( player, [ "og_firingrange_attract_nag" ], 40.0, ref_og_firingRangeAttractSpot, "PlayerNearFiringRange" ) + + waitthread Training_OG_Moves( ref_og_firingRangeAttractSpot ) + } + + FlagWait( "PlayerNearFiringRange" ) + + waitthread Training_OG_Moves( ref_og_firingRangeSpot, "OG_firingrange_idle" ) +} + +void function FiringRange_Approach_OG_Sequence( entity player, entity ref_og_needGunsStart ) +{ + EndSignal( player, "OnDestroy" ) + + if ( !Flag( "PlayerNearFiringRange" ) ) + Training_OG_Idles_Sitting( ref_og_needGunsStart ) + + if ( Flag( "PlayerNearFiringRange" ) ) + { + // player is rushing forward + FlagSet( "ineedguns" ) + thread FiringRange_INeedGuns_SFX( player, 0.0 ) + } + else + { + FlagSetDelayed( "ineedguns", 2.0 ) + thread FiringRange_INeedGuns_SFX( player, 2.0 ) + } + + if ( !Flag( "PlayerNearFiringRange" ) ) + { + // "In combat, things never go as you expect." + waitthread Training_OG_ScriptedAnim( ref_og_needGunsStart, "OG_Weapons_A" ) + } + + if ( !Flag( "PlayerNearFiringRange" ) ) + { + // "You must be ready to use any weapon you can find on the field." + waitthread Training_OG_ScriptedAnim( ref_og_needGunsStart, "OG_Weapons_B" ) + } + + if ( !Flag( "PlayerNearFiringRange" ) ) + { + // "These are just a few of the weapons I've come across out there." + waitthread Training_OG_ScriptedAnim( ref_og_needGunsStart, "OG_Weapons_C" ) + } + + FlagSet( "FiringRange_Approach_OG_Sequence_Done" ) +} + +void function FiringRange_INeedGuns_SFX( entity player, float delayTime ) +{ + EndSignal( player, "OnDestroy" ) + + + entity centerEmitter = GetEntByScriptName( "ref_firingrange_rack_sfx_center" ) + entity backCenterEmitter = GetEntByScriptName( "ref_firingrange_rack_sfx_backcenter" ) + entity leftEmitter = GetEntByScriptName( "ref_firingrange_rack_sfx_left" ) + entity rightEmitter = GetEntByScriptName( "ref_firingrange_rack_sfx_right" ) + + entity moverCenter = CreateScriptMover( centerEmitter.GetOrigin(), centerEmitter.GetAngles() ) + entity moverBackCenter = CreateScriptMover( backCenterEmitter.GetOrigin(), backCenterEmitter.GetAngles() ) + entity moverLeft = CreateScriptMover( leftEmitter.GetOrigin(), leftEmitter.GetAngles() ) + entity moverRight = CreateScriptMover( rightEmitter.GetOrigin(), rightEmitter.GetAngles() ) + array<entity> movers = [ moverCenter, moverBackCenter, moverLeft, moverRight ] + + OnThreadEnd( + function() : ( movers ) + { + foreach ( mover in movers ) + { + if( !IsValid( mover ) ) + continue + + mover.Destroy() + } + } + ) + + if ( delayTime > 0.0 ) + wait delayTime + + EmitSoundOnEntity( moverCenter, "training_scr_center_racks" ) + EmitSoundOnEntity( moverBackCenter, "training_scr_back_racks" ) + EmitSoundOnEntity( moverLeft, "training_scr_left_racks" ) + EmitSoundOnEntity( moverRight, "training_scr_right_racks" ) + + wait 15.0 // wait before cleaning up movers +} + +void function FiringRange_DetectWeaponSwitch( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + entity ogWeapon = WaitForPlayerActiveWeapon( player ) + string ogWeaponName = ogWeapon.GetWeaponClassName() + + while ( 1 ) + { + entity weapon = WaitForPlayerActiveWeapon( player ) + string weaponName = weapon.GetWeaponClassName() + + if ( weaponName != "" && weaponName != ogWeaponName ) + { + FlagSet( "FiringRangeWeaponSwapped" ) + break + } + + wait 0.5 + } +} + +void function FiringRange_TrainReload( entity player, entity ogIdleSpot ) +{ + FlagClear( "PlayerReloaded" ) + + SetWeaponHUDEnabled( player, true ) + + player.SetExtraWeaponMods( [ "" ] ) // turns off low_ammo_disable + thread TakeAmmoFromPlayerASAP( player ) + + thread TrainReload_GivePlayerAmmoAfterButtonPressed( player ) + + // "Load your weapon." + // "Swap in a fresh mag." + array<string> reloadNags = [ "og_reload_hint", "og_reload_nag" ] + + wait 1.1 // let pro players reload before prompting + + int nagIdx = 0 + + if ( !Flag( "PlayerReloaded" ) ) + { + thread OnscreenHint_DisplayUntilFlag( player, "reload_hint", "PlayerReloaded" ) + + float nagInterval = 15 + float lastNagTime = -100 + + while ( !Flag( "PlayerReloaded" ) ) + { + wait 0.1 + + if ( Time() - lastNagTime >= nagInterval ) + { + waitthread Training_OG_Talks( reloadNags[nagIdx], ogIdleSpot, "OG_firingrange_talk", "OG_firingrange_idle", true ) + lastNagTime = Time() + + nagIdx++ + if ( nagIdx >= reloadNags.len() ) + nagIdx = 0 + } + } + } +} + +void function TrainReload_GivePlayerAmmoAfterButtonPressed( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + FlagWait( "PlayerReloaded" ) + + FlagSet( "ReloadTraining_PlayerPressedReload" ) + + player.SetActiveWeaponPrimaryAmmoTotal( 50 ) +} + +void function FiringRange_InfiniteAmmo_WhenNearRange( entity player, string endFlag ) +{ + player.EndSignal( "OnDestroy" ) + FlagEnd( endFlag ) + + while ( 1 ) + { + wait 0.5 + + if ( !Flag( "PlayerNearFiringRange" ) ) + continue + + entity weapon = player.GetActiveWeapon() + + if ( weapon == null ) + continue + + int currAmmo = player.GetWeaponAmmoStockpile( weapon ) + int magSize = weapon.GetWeaponSettingInt( eWeaponVar.ammo_clip_size ) + int maxAmmo = weapon.GetWeaponSettingInt( eWeaponVar.ammo_stockpile_max ) + + // let player reload before restocking + if ( currAmmo > (maxAmmo-magSize + 1) ) + continue + + printt( "firing range restock ammo" ) + + RestockPlayerAmmo_Silent( player ) + } +} + +void function FiringRange_TrainADS( entity player, entity ref_og_firingRangeSpot ) +{ + thread FlagSetWhenPlayerADS( player, "PlayerADSed" ) + + FlagWaitWithTimeout( "PlayerADSed", 2.5 ) // give player time to ADS before hinting + + if ( !Flag( "PlayerADSed" ) ) + { + // "Aim down the sights when engaging more distant targets." + // "You can focus more tightly on your targets if you aim down the sights." + array<string> adsNags = [ "og_ads_nag_1", "og_ads_nag_2" ] + thread Training_OG_NagPlayerUntilFlag( player, adsNags, 10.0, ref_og_firingRangeSpot, "PlayerADSed", "OG_firingrange_talk", "OG_firingrange_idle" ) + + thread OnscreenHint_DisplayUntilFlag( player, "ads_hint", "PlayerADSed" ) + + // "To get more precision, aim down the sights of your weapon." + waitthread Training_OG_Talks( "og_ads_hint", ref_og_firingRangeSpot, "OG_firingrange_talk", "OG_firingrange_idle", true ) + } + + FlagWait( "PlayerADSed" ) +} + +void function FiringRange_TrainWeaponSwap( entity player, entity ref_og_firingRangeSpot ) +{ + Signal( player, "FiringRange_StopResettingTargets" ) + + if ( !Flag( "FiringRangeWeaponSwapped" ) ) + { + DisplayOnscreenHint( player, "weapon_pickup_hint", 5.0 ) + thread OnscreenHint_NagUntilFlag( player, "weapon_pickup_hint", "FiringRangeWeaponSwapped", 10.0, 5.0 ) + + // "Use a different weapon this time. Grab another one off the rack." + thread Training_OG_Talks( "og_weaponswap_hint", ref_og_firingRangeSpot, "OG_firingrange_talk", "OG_firingrange_idle", true ) + + // "Switch to a different weapon." + array<string> weaponSwapNags = [ "og_weaponswap_nag" ] + waitthread Training_OG_NagPlayerUntilFlag( player, weaponSwapNags, 10.0, ref_og_firingRangeSpot, "FiringRangeWeaponSwapped", "OG_firingrange_talk", "OG_firingrange_idle" ) + } +} + +void function FiringRange_PlayerMustDamageAllTargets( entity player, entity ref_og_firingRangeSpot, bool firstTime, bool resetTargetsAtStart = true ) +{ + EndSignal( player, "OnDestroy" ) + + FlagClear( "FiringRange_AllTargetsKilled" ) + thread FiringRange_ResetTargets_Think( player, resetTargetsAtStart ) + + thread OnscreenHint_DisplayUntilFlag( player, "firingrange_dmg_targets_hint", "FiringRange_AllTargetsKilled" ) + + wait 2.0 + + // "Gotta take 'em all out before we can move on." + // "Just aim, take a breath, and squeeze the trigger." + // "In the real world, the targets don't just stand still. They shoot back." + DialogueGroup nags = GetDialogueGroup( "shootTargetsNag" ) + + int numTargetsKilled = FiringRange_GetNumDamagedTargets() + + while ( !Flag( "FiringRange_AllTargetsKilled" ) ) + { + wait 1.0 + + // don't nag player if they recently damaged a target + if ( FiringRange_GetNumDamagedTargets() > numTargetsKilled ) + { + numTargetsKilled = FiringRange_GetNumDamagedTargets() + TimerReset( "firingRangeNag" ) + + continue + } + + if ( !TimerCheck( "firingRangeNag" ) ) + continue + + string nagLine = DialogueGroup_GetNextLine( nags ) + waitthread Training_OG_Talks( nagLine, ref_og_firingRangeSpot, "OG_firingrange_talk", "OG_firingrange_idle", true ) + + TimerReset( "firingRangeNag" ) + } +} + +void function FiringRangeTargets_Init() +{ + string targetScriptName = "firingrange_target" + array<entity> targetEnts = GetEntArrayByScriptName( targetScriptName ) + Assert( targetEnts.len(), "Couldn't get firing range targets with script_name " + targetScriptName ) + + foreach ( ent in targetEnts ) + { + ent.SetTakeDamageType( DAMAGE_EVENTS_ONLY ) + ent.SetDamageNotifications( true ) + + entity angleRefEnt = ent.GetLinkEnt() + Assert( IsValid( angleRefEnt ), "Firing range target needs an angle reference entity linked" ) + angleRefEnt.SetParent( ent ) + + FiringRangeTarget target + target.ent = ent + target.angleRefEnt = angleRefEnt + target.ogAngles = ent.GetAngles() + target.mover = CreateScriptMover( ent.GetOrigin(), ent.GetAngles() ) + ent.SetParent( target.mover ) + + file.firingRangeTargets.append( target ) + } +} + +int function FiringRange_GetNumDamagedTargets() +{ + int numDamaged = 0 + + foreach ( target in file.firingRangeTargets ) + if ( target.wasDamaged ) + numDamaged++ + + return numDamaged +} + +void function FiringRange_ResetAllTargets() +{ + foreach ( target in file.firingRangeTargets ) + FiringRangeTarget_Reset( target ) +} + +void function FiringRangeTarget_Reset( FiringRangeTarget target ) +{ + if ( !IsValid( target.ent ) ) + return + + target.wasDamaged = false + + thread FiringRangeTarget_RotateBack( target ) +} + +void function FiringRange_ResetTargets_Think( entity player, bool resetTargetsAtStart = true ) +{ + Signal( player, "FiringRange_StopResettingTargets" ) + EndSignal( player, "FiringRange_StopResettingTargets" ) + EndSignal( player, "OnDestroy" ) + + FlagEnd( "PodOutroStarted" ) + + array<FiringRangeTarget> firingRangeTargets = file.firingRangeTargets + + bool firstLoop = true + float targetResetWait = 1.75 + + while ( 1 ) + { + bool resetTargets = true + if ( firstLoop && !resetTargetsAtStart ) + resetTargets = false + + if ( resetTargets ) + FiringRange_ResetAllTargets() + + foreach ( target in firingRangeTargets ) + thread FiringRangeTarget_WaitForDamage( target ) + + // wait for all targets to take damage + while ( 1 ) + { + wait 0.2 + + bool allDamaged = true + foreach ( target in firingRangeTargets ) + { + if ( !target.wasDamaged ) + { + allDamaged = false + break + } + } + + if ( allDamaged ) + break + } + + FlagSet( "FiringRange_AllTargetsKilled" ) + + wait targetResetWait + + if ( firstLoop ) + firstLoop = false + } +} + +void function FiringRangeTarget_WaitForDamage( FiringRangeTarget target ) +{ + Signal( target.ent, "Target_WaitForDamage_Start" ) + EndSignal( target.ent, "Target_WaitForDamage_Start" ) + EndSignal( target.ent, "OnDestroy" ) + + bool first = true + while ( 1 ) + { + table result = WaitSignal( target.ent, "TargetDamaged" ) + vector damagePos = expect vector( result.damagePos ) + + if ( first ) + { + thread FiringRangeTarget_FirstDamage( target, damagePos ) + first = false + } + } + + return +} + +string function FiringRangeTarget_GetDamageSide( FiringRangeTarget target, vector damagePos ) +{ + vector upEntPos = target.angleRefEnt.GetOrigin() + vector upEntAngles = target.angleRefEnt.GetAngles() + + vector facingVec = AnglesToForward( upEntAngles ) + vector vecToDamage = Normalize( damagePos - upEntPos ) + float dot2Damage = DotProduct( vecToDamage, facingVec ) + printt( "damage dot product:", dot2Damage ) + + #if DEV + //float debugDrawTime = 3.0 + //DebugDrawAngles( damagePos, upEntAngles, debugDrawTime ) + //DebugDrawLine( upEntPos, upEntPos + (facingVec * 200), 200, 200, 0, true, debugDrawTime ) + #endif + + vector frontFacingVec = AnglesToRight( upEntAngles ) + bool isOnLeft = IsPointInFrontofLine( damagePos, upEntPos, frontFacingVec ) + + string returnStr = "left" + if ( isOnLeft ) + { + printt( "LEFT SIDE" ) + } + else + { + printt( "RIGHT SIDE" ) + returnStr = "right" + } + + return returnStr +} + +void function FiringRangeTarget_FirstDamage( FiringRangeTarget target, vector damagePos ) +{ + //Assert( !target.wasDamaged, "Target not expected to be damaged yet" ) + if ( target.wasDamaged ) + return + + target.wasDamaged = true + + if ( IsAlive( file.player ) ) + EmitSoundOnEntity( file.player, "training_scr_hit_target" ) + + // rotate left by default + float rotateAngY = 179.9 + if ( FiringRangeTarget_GetDamageSide( target, damagePos ) == "right" ) + rotateAngY *= -1 + + vector newAngles = target.ogAngles + Vector( 0, rotateAngY, 0 ) + thread FiringRangeTarget_Rotate( target, newAngles ) +} + +void function FiringRangeTarget_Rotate( FiringRangeTarget target, vector targetAngles ) +{ + Signal( target.mover, "TargetRotate" ) + EndSignal( target.mover, "TargetRotate" ) + EndSignal( target.mover, "OnDestroy" ) + + float rotateTime = 0.14 + float accelTime = 0 + float decelTime = 0.1 + target.mover.NonPhysicsRotateTo( targetAngles, rotateTime, accelTime, decelTime ) + + wait rotateTime +} + +void function FiringRangeTarget_RotateBack( FiringRangeTarget target ) +{ + if ( target.ent.GetAngles() == target.ogAngles ) + return + + EmitSoundAtPosition( TEAM_UNASSIGNED, target.ent.GetOrigin(), "training_scr_range_target_spin" ) + + waitthread FiringRangeTarget_Rotate( target, target.ogAngles ) +} + + +void function FlagSetWhenPlayerADS( entity player, string setFlag ) +{ + player.EndSignal( "OnDestroy" ) + + while ( player.GetZoomFrac() < 0.9 ) + wait 0.1 + + FlagSet( setFlag ) +} + + + +// ============================= +// ========= GAUNTLET ========== +// ============================= +void function Training_Setup_Gauntlet( entity player ) +{ + entity ogStart = GetEntByScriptName( "og_gauntlet_entrance_attract_spot" ) + entity og = Training_SpawnOGPilot( ogStart ) + Training_OG_Idles( ogStart ) + + TeleportPlayerAndBT( "startpoint_gauntlet_entrance" ) +} + +void function Training_Skipped_Gauntlet( entity player ) +{ + FlagSet( "Gauntlet_FirstRun_Done" ) + thread TrainingGauntlet_RemindPlayerAboutMobility( player ) + thread TrainingGauntlet_CrouchHint( player ) + thread TrainingGauntlet_SetsDifficulty( player ) +} + +void function Training_Gauntlet( entity player ) +{ + entity og = GetOGPilot() + + entity ref_og_gauntletStartPos = GetEntByScriptName( "og_near_gauntlet_start_pos" ) + GauntletInfo trainingGauntlet = GetTrainingGauntlet() + + // HACK + vector resultsBoardCenterPos = < -4899.15, 666.269, 107.187 > + + if ( !Flag( "PlayerInGauntletEntryway" ) && !trainingGauntlet.isActive && !PlayerCanSeePos( player, resultsBoardCenterPos, true, 90 ) ) + DisableGauntlet( trainingGauntlet ) // let a rushing player start the gauntlet + + if ( !trainingGauntlet.isActive ) + FlagWait( "PlayerInGauntletEntryway" ) + + if ( !trainingGauntlet.isActive ) + CheckPoint_Silent() + + waitthread Training_OG_Moves( ref_og_gauntletStartPos, ANIM_OG_LEANING_IDLE ) + + thread Training_Gauntlet_FirstRunDialogue( player, trainingGauntlet, ref_og_gauntletStartPos ) + wait 1.0 + + EnableGauntlet( trainingGauntlet ) + thread Training_Gauntlet_FirstRunGhostPlaybackStart( player, trainingGauntlet ) + thread TrainingGauntlet_TeleportPlayerAtFinishLine( player ) + thread TrainingGauntlet_RemindPlayerAboutMobility( player, "Gauntlet_FirstRun_All_VO_Finished" ) + thread TrainingGauntlet_CrouchHint( player ) + thread TrainingGauntlet_SetsDifficulty( player ) + + thread Training_Gauntlet_WaitForRequiredTime( player, ref_og_gauntletStartPos ) + FlagWait( "Gauntlet_FirstRun_Done" ) + + if ( !trainingGauntlet.isActive ) + CheckPoint_Silent() + + Gauntlet_StopGhostPlayback( trainingGauntlet ) + + waitthread Training_Gauntlet_PostFirstRunDialogue( player, trainingGauntlet ) +} + + +void function Training_Gauntlet_FirstRunDialogue( entity player, GauntletInfo gauntlet, entity ogIdleSpot ) +{ + player.EndSignal( "OnDestroy" ) + + if ( !gauntlet.isActive ) + { + // "Alright. Got a new gauntlet for you to run today." + waitthread Training_OG_ScriptedAnim( ogIdleSpot, "OG_gauntlet_start_A" ) + } + + if ( !gauntlet.isActive ) + { + // "Par time is a minute-forty-five." + waitthread Training_OG_ScriptedAnim( ogIdleSpot, "OG_gauntlet_start_B" ) + } + + if ( !gauntlet.isActive ) + { + // "Gotta do better than that to continue." + waitthread Training_OG_ScriptedAnim( ogIdleSpot, "OG_gauntlet_start_C" ) + } + + Objective_Set( "#TRAINING_OBJ_GAUNTLET_FIRSTRUN" ) + + //if ( gauntlet.isActive ) + // Training_Gauntlet_OG_Creates_FirstRun_Ghost( file.ogPilot ) + + AddAnimEvent( file.ogPilot, "create_ghost", Training_Gauntlet_OG_Creates_FirstRun_Ghost ) + + // "Follow the ghost, or find your own path." + waitthread Training_OG_ScriptedAnim( ogIdleSpot, "OG_gauntlet_start_D" ) + thread Training_OG_Idles( ogIdleSpot, "OG_gauntlet_start_endidle" ) + + DeleteAnimEvent( file.ogPilot, "create_ghost" ) + + float minDelayEnd = 5.0 + Time() + DialogueGroup firstRunGauntletLore = GetDialogueGroup( "firstRunGauntletLore" ) + while ( !Flag( "Gauntlet_FirstRun_Done" ) && !firstRunGauntletLore.allPlayed ) + { + wait 1 + + if ( !gauntlet.isActive ) + continue + + if ( Time() < minDelayEnd ) + continue + + string line = DialogueGroup_GetNextLine( firstRunGauntletLore ) + PlayDialogue( line, player ) + } + + FlagSet( "Gauntlet_FirstRun_All_VO_Finished" ) +} + +void function Training_Gauntlet_OG_Creates_FirstRun_Ghost( entity og ) +{ + entity player = file.player + if ( !IsValid( player ) ) + return + + player.Signal( "FirstRun_OG_Creates_Ghost" ) +} + +void function TrainingGauntlet_RemindPlayerAboutMobility( entity player, string waitFlag = "" ) +{ + EndSignal( player, "OnDestroy" ) + FlagEnd( "PlayerLeavingGauntlet" ) + + GauntletInfo trainingGauntlet = GetTrainingGauntlet() + + //if ( waitFlag != "" && !Flag( waitFlag ) ) + // FlagWait( waitFlag ) + + float nagInterval = 300 // 5 minutes + float nextNagTime = -1 + + float minSampleTime = 15.0 + float lowWallrunningFrac = 0.05 + + bool hasSprinted = false + + FlagWait( "Gauntlet_FirstRun_All_VO_Finished" ) + + while ( !Flag( "PlayerLeavingGauntlet" ) ) + { + if ( !trainingGauntlet.isActive ) + WaitSignal( player, "Gauntlet_RunStarted" ) + + float startTime = Time() + float samplePoints = 0 + float samplesWallrunning = 0 + + while ( trainingGauntlet.isActive ) + { + wait 0.1 + + if ( player.IsSprinting() && !hasSprinted ) + hasSprinted = true + + samplePoints++ + if ( player.IsWallRunning() ) + samplesWallrunning++ + + if ( (Time() - startTime) < minSampleTime ) + continue + + float wallrunningFrac = samplesWallrunning / samplePoints + printt( "wallrunningFrac:", wallrunningFrac ) + + // Has player done all the stuff we would want to remind them about? + if ( wallrunningFrac >= lowWallrunningFrac && hasSprinted ) + break + + if ( Time() >= nextNagTime ) + { + if ( !hasSprinted ) + thread TrainingGauntlet_SprintNag( player ) + else if ( wallrunningFrac < lowWallrunningFrac ) + thread TrainingGauntlet_WallrunNag( player, trainingGauntlet ) + + nextNagTime = Time() + nagInterval + } + + break + } + + if ( trainingGauntlet.isActive ) + WaitSignal( player, "Gauntlet_RunStopped" ) + } +} + +void function TrainingGauntlet_SprintNag( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + float hintDuration = 10.0 + float hintEndTime = Time() + hintDuration + DisplayOnscreenHint( player, "sprint_button_hint", hintDuration ) + + EndSignal( player, "DisplayingOnscreenHint" ) + + while ( Time() < hintEndTime && !player.IsSprinting() ) + WaitFrame() + + ClearOnscreenHint( player ) +} + +void function TrainingGauntlet_WallrunNag( entity player, GauntletInfo trainingGauntlet ) +{ + EndSignal( player, "OnDestroy" ) + + //printt( "playing mobility nag" ) + + DisplayOnscreenHint( player, "gauntlet_wallrun_hint", 8.0 ) + + DialogueGroup gauntletHints_wallrun = GetDialogueGroup( "gauntletHints_wallrun" ) + string line = DialogueGroup_GetNextLine( gauntletHints_wallrun ) + waitthread PlayDialogue( line, player ) + + if ( !trainingGauntlet.isActive ) + return + + waitthread PlayDialogue( "og_gauntlet_hint_wallrun_capper", player ) +} + +void function TrainingGauntlet_CrouchHint( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + string checkFlag = "Gauntlet_PlayerInCrouchHintZone" + string nagTimer = "gauntlet_crouchHint" + + TimerInit( nagTimer, 2.5 ) + + float waitTime = 1.0 + bool hintShowing = false + + while ( 1 ) + { + wait waitTime + + if ( !Flag( checkFlag ) ) + continue + + TimerReset( nagTimer ) + + while ( Flag( checkFlag ) ) + { + wait waitTime + + if ( !TimerCheck( "gauntlet_crouchHint" ) ) + continue + + if ( !hintShowing ) + { + DisplayOnscreenHint( player, "crouch_hint" ) + hintShowing = true + } + } + + if ( hintShowing ) + { + ClearOnscreenHint( player ) + hintShowing = false + } + } +} + +void function Training_Gauntlet_PostFirstRunDialogue( entity player, GauntletInfo gauntlet ) +{ + player.EndSignal( "OnDestroy" ) + player.EndSignal( "Gauntlet_RunStarted" ) + + entity ref_og_gauntletResults = GetEntByScriptName( "og_ref_gauntlet_results_display" ) + + Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" ) + + waitthread Training_OG_Moves( ref_og_gauntletResults, "", 1.0 ) + + AddAnimEvent( file.ogPilot, "highlight_results_board_tip", Gauntlet_PostFirstRun_SetRandomTip ) + + // "Nice run! See the results board on the wall? You set a new Best Time." + // "Everyone has different strengths and weaknesses, so be sure to run this a few times with different weapons." + // "Look at the results board for more tips on how to improve." + waitthread Training_OG_ScriptedAnim( ref_og_gauntletResults, "OG_Gauntlet_return" ) + + DeleteAnimEvent( file.ogPilot, "highlight_results_board_tip" ) +} + +// update tip while OG is talking about the tips +void function Gauntlet_PostFirstRun_SetRandomTip( entity og ) +{ + entity player = file.player + if ( !IsValid( player ) ) + return + + GauntletInfo gauntlet = GetTrainingGauntlet() + Remote_CallFunction_Replay( player, "ScriptCallback_GauntletResultsDisplay_SetRandomTip", gauntlet.id ) +} + +void function Training_Gauntlet_FirstRunGhostPlaybackStart( entity player, GauntletInfo gauntlet ) +{ + if ( !gauntlet.isActive ) + WaitSignal( player, "Gauntlet_RunStarted", "FirstRun_OG_Creates_Ghost" ) + + thread Gauntlet_StartGhostPlayback( gauntlet, GHOST_NAME_FIRSTRUN, "#GAUNTLET_GHOST_NAME_FIRSTRUN" ) +} + +void function Training_Gauntlet_WaitForRequiredTime( entity player, entity ogIdleSpot ) +{ + player.EndSignal( "OnDestroy" ) + + GauntletInfo gauntlet = GetTrainingGauntlet() + + // "The first run of the day is always the toughest." + // "Too slow! I know you can do better. Give it another try." + DialogueGroup firstRunFailedGroup = GetDialogueGroup( "firstRunFailed" ) + + int failCount = 0 + while ( 1 ) + { + // keep setting this here because by default the gauntlet updates its random tip after each finished run + Remote_CallFunction_Replay( player, "ScriptCallback_TrainingGauntlet_ResultsDisplay_SetTip", gauntlet.id, 0 ) + + player.WaitSignal( "Gauntlet_RunStopped" ) + + wait 0.1 // HACK let the gauntlet struct get updated before checking it + + TrainingGauntletStats_PostRunUpdate( player, gauntlet, 0 ) + + if ( !gauntlet.runFinished ) + continue + + failCount++ + + if ( gauntlet.bestTime <= TRAINING_GAUNTLET_MAX_TIME_TO_PROGRESS || failCount == NUM_GAUNTLET_FAILS_BEFORE_FORCED_PROGRESS ) + { + if ( gauntlet.bestTime <= TRAINING_GAUNTLET_MAX_TIME_TO_PROGRESS ) + printt( "Gauntlet moving on: beat required time" ) + else if ( failCount == NUM_GAUNTLET_FAILS_BEFORE_FORCED_PROGRESS ) + printt( "Gauntlet moving on: forced progress after", NUM_GAUNTLET_FAILS_BEFORE_FORCED_PROGRESS, "failures" ) + + break + } + + wait 0.5 // let player get settled before VO starts + + EmitSoundOnEntityOnlyToPlayer( player, player, "training_scr_gaunlet_fail_01" ) + + string firstRunFailedLine = DialogueGroup_GetNextLine( firstRunFailedGroup ) + thread Training_OG_Talks_Leaning( firstRunFailedLine, ogIdleSpot, "", "", true ) + + Objective_Remind() + //DisplayOnscreenHint( player, "gauntlet_first_run_progression_hint", 9.0 ) + + if ( failCount == 2 ) + thread DoSprintEnableDialogForGauntletIfNeeded_Thread( player, 3.5 ) + } + + TrainingGauntletPostRun_TryAchievements( player, gauntlet ) + + FlagSet( "Gauntlet_FirstRun_Done" ) +} + +void function DoSprintEnableDialogForGauntletIfNeeded_Thread( entity player, float waitTime ) +{ + EndSignal( player, "OnDestroy" ) + wait waitTime + + if ( GetAutosprintEnabled() ) + return + + Remote_CallFunction_UI( player, "ScriptCallback_OpenAutosprintDialogForGauntlet" ) +} + +void function TrainingGauntlet_TeleportPlayerAtFinishLine( entity player ) +{ + Signal( player, "Gauntlet_StopTeleportingPlayerAtFinishLine" ) + EndSignal( player, "Gauntlet_StopTeleportingPlayerAtFinishLine" ) + EndSignal( player, "OnDestroy" ) + + entity startEnt = GetEntByScriptName( "results_room_teleport_refA" ) + entity endEnt = GetEntByScriptName( "results_room_teleport_refB" ) + vector startPos = startEnt.GetOrigin() + vector endPos = endEnt.GetOrigin() + + while ( 1 ) + { + player.WaitSignal( "Gauntlet_PlayerHitFinishTrig" ) + + vector currentPos = player.GetOrigin() + float offsetX = currentPos.x - startPos.x + float offsetY = currentPos.y - startPos.y + float offsetZ = currentPos.z - startPos.z + + float newPosX = endPos.x + offsetX + float newPosY = endPos.y + offsetY + float newPosZ = endPos.z + offsetZ + + vector newPos = < newPosX, newPosY, newPosZ > + + player.SetOrigin( newPos ) + } +} + + +void function TrainingGauntlet_SetsDifficulty( entity player ) +{ + if ( Flag( "PlayerLeavingGauntlet" ) ) + return + + FlagEnd( "PlayerLeavingGauntlet" ) + + player.EndSignal( "OnDestroy" ) + + GauntletInfo gauntlet = GetTrainingGauntlet() + + bool diffSetOnce = false + + while ( 1 ) + { + WaitSignal( player, "Gauntlet_RunStopped" ) + + if ( file.gauntletMode ) + return + + wait 0.2 // HACK let the gauntlet struct and file variables get set up after the run + + // extra wait for player to finish seeing their time, etc. + wait 2.0 + + if ( !gauntlet.runFinished ) + continue + + if ( !gauntlet.lastRunBestTime ) + continue + + int prevDiff = -1 + if ( diffSetOnce ) + prevDiff = GetConVarInt( "sp_difficulty" ) + + Training_SetDifficultyForGauntletTime( gauntlet.bestTime ) + if ( GetConVarInt( "sp_difficulty" ) > prevDiff ) + Training_AnnounceDifficultyForGauntletTime( player, 8.0 ) + + if ( !diffSetOnce ) + diffSetOnce = true + } +} + +void function Training_SetDifficultyForGauntletTime( float time ) +{ + float time = file.trainingGauntletStats.bestTime + if ( time == -1 ) + time = TRAINING_GAUNTLET_MAX_TIME_TO_PROGRESS + + int difficulty = DIFFICULTY_EASY + //if ( time <= REC_DIFF_GAUNTLET_TIME_MASTER ) + // difficulty = DIFFICULTY_MASTER + // else if + if ( time <= REC_DIFF_GAUNTLET_TIME_HARD ) + difficulty = DIFFICULTY_HARD + else if ( time <= REC_DIFF_GAUNTLET_TIME_NORMAL ) + difficulty = DIFFICULTY_NORMAL + + printt( "GetDifficultyForGauntletTime: diff", difficulty, "for time", time ) + + file.trainingGauntletStats.recommendedDifficulty = difficulty + + SetConVarInt( "sp_difficulty", difficulty ) +} + +void function Training_AnnounceDifficultyForGauntletTime( entity player, float displayTime = 5.0 ) +{ + string hintAlias = "hint_diff_" + hintAlias += file.trainingGauntletStats.recommendedDifficulty.tostring() + DisplayOnscreenHint( player, hintAlias, displayTime ) +} + + +// ====================================================== +// ========= GAUNTLET CHALLENGE (post 1st run) ========== +// ====================================================== +void function Training_Setup_GauntletChallenge( entity player ) +{ + entity ogStart = GetEntByScriptName( "og_ref_gauntlet_results_display" ) + entity og = Training_SpawnOGPilot( ogStart ) + Training_OG_Idles( ogStart ) + + TeleportPlayerAndBT( "playerstart_gauntlet_challenge" ) +} + +void function Training_Skipped_GauntletChallenge( entity player ) +{ +} + +void function Training_GauntletChallenge( entity player ) +{ + GauntletInfo gauntlet = GetTrainingGauntlet() + + thread TrainingGauntlet_TeleportPlayerAtFinishLine( player ) + + entity ref_og_gauntletLeaderboardPos = GetEntByScriptName( "og_near_leaderboard" ) + + // in gauntlet mode OG spawns here before player fade finishes, no need to move him + if ( !file.gauntletMode ) + waitthread Training_OG_Moves( ref_og_gauntletLeaderboardPos, "" ) + + Gauntlet_ShowLeaderboard( gauntlet ) + + thread GauntletChallenge_GhostsThink( player, gauntlet, "GauntletChallenge_FirstGhostAppear", "PlayerLeavingGauntlet" ) + thread GauntletChallenge_IntroDialogue( player, gauntlet, ref_og_gauntletLeaderboardPos ) + thread GauntletChallege_RestartGauntletHint( player, gauntlet ) + + FlagWait( "ChallengeIntro_VO_Done" ) + + bool installRuiStarted = false + if ( !Training_IsGameFullyInstalled() ) + Remote_CallFunction_Replay( player, "ScriptCallback_ShowInstallProgress", true ) + + entity ref_og_gauntletExitPos = GetEntByScriptName( "og_gauntlet_exit_pos" ) + thread Training_GauntletChallenge_DialogueThink( player, gauntlet, ref_og_gauntletExitPos ) + thread Training_OG_Moves( ref_og_gauntletExitPos, "OG_all_done_idle" ) + + wait 1.0 + thread Training_LeaveGauntletThink( player, ref_og_gauntletExitPos ) + + FlagWait( "PlayerConfirmedGauntletExit" ) + + Remote_CallFunction_Replay( player, "ScriptCallback_ShowInstallProgress", false ) + + Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" ) + + DisableGauntlet( gauntlet ) + + FlagWait( "PlayerLeavingGauntlet" ) + + Signal( player, "Gauntlet_StopTeleportingPlayerAtFinishLine" ) + + if ( file.gauntletMode ) + { + thread Training_PodOutro( player ) + WaitForever() // stall normal progression + } + else + { + thread DisableWeaponDelayed( player, 0.3 ) // disable weapon so crosshairs aren't visible during white screen + + entity destEnt = GetEntByScriptName( "startpoint_titanfall" ) + waitthread PlayerAndOGTeleport_Fancy( player, destEnt.GetOrigin(), "og_titanfall_start_pos", destEnt.GetAngles() ) + + CheckPoint_Silent() + } +} + +void function DisableWeaponDelayed( entity player, float delay ) +{ + EndSignal( player, "OnDestroy" ) + + if ( delay > 0 ) + wait delay + + player.DisableWeapon() +} + +void function GauntletChallege_RestartGauntletHint( entity player, GauntletInfo gauntlet ) +{ + EndSignal( player, "OnDestroy" ) + + float displayTime = 5.0 + float delayTime = 2.5 + + const int RUNS_BETWEEN_REMINDERS = 3 + int runsSinceLastReminder = 3 // do reminder on first run + + while ( 1 ) + { + if ( !gauntlet.isActive ) + WaitSignal( player, "Gauntlet_RunStarted" ) + + float displayEndTime = -1 + + // check if we should remind after run starts + if ( runsSinceLastReminder >= RUNS_BETWEEN_REMINDERS ) + { + thread OnscreenHint_DisplayAfterDelay( player, "gauntlet_restart_hint", displayTime, delayTime ) + displayEndTime = Time() + displayTime + delayTime + } + + // wait for run to stop + table result = WaitSignal( player, "Gauntlet_RunStopped", "Gauntlet_ForceRestart" ) + string signal = expect string( result.signal ) + + // clear initial hint if player cancelled the run before it would auto clear + if ( displayEndTime != -1 && Time() < displayEndTime ) + ClearOnscreenHint( player ) + + // player went back through the starting gate? + if ( signal == "Gauntlet_RunStopped" && !gauntlet.runFinished ) + { + // Don't care about how many runs between reminders here- if player does this it means they don't really get it yet + thread DisplayOnscreenHint( player, "gauntlet_restart_hint", displayTime ) + runsSinceLastReminder = 0 + } + // player used menu to restart? + else if ( signal == "Gauntlet_ForceRestart" ) + { + runsSinceLastReminder = 0 // reset the counter when force restarted + } + + if ( runsSinceLastReminder >= RUNS_BETWEEN_REMINDERS ) + runsSinceLastReminder = 0 + if ( gauntlet.runFinished ) // player restarted from menu or went back through the gate + runsSinceLastReminder++ + } +} + +void function GauntletChallenge_IntroDialogue( entity player, GauntletInfo gauntlet, entity ogRef ) +{ + if ( file.gauntletMode ) + { + thread GauntletChallengeModeOnly_IntroDialogue( player, gauntlet, ogRef ) + return + } + + player.EndSignal( "OnDestroy" ) + + if ( !gauntlet.isActive ) + { + // "Now that you're warmed up: if you want a REAL challenge, you can race against other Pilot ghosts." + waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_A" ) + } + + player.Signal( "GauntletChallenge_FirstGhostAppear" ) + + if ( !gauntlet.isActive ) + { + // "Word of warning, though- the Pilots who recorded these ghosts are the best in the SRS. + waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_B" ) + } + + if ( !gauntlet.isActive ) + { + // "If you can beat them, you'll be halfway to being a real Pilot." + waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_C" ) + } + + if ( !gauntlet.isActive ) + { + // "Go ahead and run the Gauntlet as much as you want." + waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_D" ) + } + + // "When you're done, I've got something special to show you." + waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_E" ) + + if ( !Training_IsGameFullyInstalled() ) + { + Objective_Set( "#TRAINING_OBJ_GAUNTLET_CHALLENGE_INSTALLING" ) + thread ChangeGauntletObjective_OnInstallComplete( player ) + } + else + { + Objective_Set( "#TRAINING_OBJ_GAUNTLET_CHALLENGE" ) + } + + FlagSet( "ChallengeIntro_VO_Done" ) +} + +void function GauntletChallengeModeOnly_IntroDialogue( entity player, GauntletInfo gauntlet, entity ogRef ) +{ + player.EndSignal( "OnDestroy" ) + + SetSignalDelayed( player, "GauntletChallenge_FirstGhostAppear", 0.25 ) + + wait 1.5 // let player teleport and fade in before starting to talk + + if ( !gauntlet.isActive ) + { + // "Go ahead and run the Gauntlet as much as you want." + waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_D", true ) + } + + // "When you're done, I've got something special to show you." + //waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_E" ) + + Objective_Set( "#TRAINING_OBJ_GAUNTLET_CHALLENGE" ) + + FlagSet( "ChallengeIntro_VO_Done" ) +} + +void function ChangeGauntletObjective_OnInstallComplete( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + while ( !Training_IsGameFullyInstalled() ) + wait 1.0 + + Objective_Set( "#TRAINING_OBJ_GAUNTLET_CHALLENGE" ) +} + +void function GauntletChallenge_GhostsThink( entity player, GauntletInfo gauntlet, string signalWait, string endFlag ) +{ + if ( Flag( endFlag ) ) + return + + player.EndSignal( "OnDestroy" ) + gauntlet.signalEnt.EndSignal( "OnDestroy" ) + + FlagEnd( endFlag ) + + if ( !gauntlet.isActive ) + WaitSignal( player, "Gauntlet_RunStarted", signalWait ) + + thread Gauntlet_ChallengeLeaderboardGhosts( player, gauntlet, "PlayerLeavingGauntlet" ) +} + +void function Training_GauntletChallenge_DialogueThink( entity player, GauntletInfo gauntlet, entity ogSpot ) +{ + player.EndSignal( "OnDestroy" ) + + FlagWait( "ChallengeIntro_VO_Done" ) + + DialogueGroup clearedLeaderboard = GetDialogueGroup( "clearedLeaderboard" ) + DialogueGroup defeatedGhost = GetDialogueGroup( "defeatedGhost" ) + DialogueGroup notBestTime = GetDialogueGroup( "notBestTime" ) + DialogueGroup newBestTime = GetDialogueGroup( "newBestTime" ) + + while ( 1 ) + { + FlagClear( "Gauntlet_PlayingFeedbackVO" ) + + WaitSignal( player, "Gauntlet_RunStopped" ) + + wait 0.1 // HACK let the gauntlet struct get updated before checking + + TrainingGauntletStats_PostRunUpdate( player, gauntlet, 1 ) + + if ( !gauntlet.runFinished ) + continue + + TrainingGauntletPostRun_TryAchievements( player, gauntlet ) + + FlagSet( "Gauntlet_PlayingFeedbackVO" ) + + string feedbackLine = "" + + // SPECIAL- defeated a Hero Ghost + if ( gauntlet.lastRunDefeatedGhost && !gauntlet.allGhostsDefeated ) + { + array<GauntletGhost> leaderboard = Gauntlet_GetLeaderboard( gauntlet ) + GauntletGhost playerGhost = Gauntlet_GetPlayerGhost( gauntlet ) + + array<string> heroFileNames = [ GHOST_NAME_ALIAS_LASTIMOSA, GHOST_NAME_ALIAS_ANDERSON, GHOST_NAME_ALIAS_BRIGGS ] + + GauntletGhost loserGhost + foreach ( ghost in leaderboard ) + { + if ( playerGhost.duration <= ghost.duration && heroFileNames.contains( ghost.fileName ) ) + { + loserGhost = ghost + break + } + } + + switch ( loserGhost.fileName ) + { + case GHOST_NAME_ALIAS_LASTIMOSA: + // "Hey - that was my best time! I must be getting slow." + feedbackLine = "og_gauntlet_unlocked_leaderboard_entry_og" + break + + case GHOST_NAME_ALIAS_ANDERSON: + // "Heh. Can't wait to tell Anderson about that. Son of a bitch..." + feedbackLine = "og_gauntlet_unlocked_leaderboard_entry_anderson" + break + + case GHOST_NAME_ALIAS_BRIGGS: + // "You just beat Commander Briggs. Might not stay that way for long though, she's very competitive." + feedbackLine = "og_gauntlet_unlocked_leaderboard_entry_briggs" + break + } + + if ( feedbackLine != "" ) + waitthread Training_OG_Talks( feedbackLine, ogSpot, "OG_all_done_talk", "OG_all_done_idle", true ) + } + + // SPECIAL - cleared leaderboard + if ( gauntlet.allGhostsDefeated && !clearedLeaderboard.allPlayed ) + { + while ( !clearedLeaderboard.allPlayed && !gauntlet.isActive ) + { + feedbackLine = DialogueGroup_GetNextLine( clearedLeaderboard ) + waitthread Training_OG_Talks( feedbackLine, ogSpot, "OG_all_done_talk", "OG_all_done_idle", true ) + } + } + + // If we played a line by now, it was Special, so we don't want the generic ones below. + if ( feedbackLine != "" ) + continue + + // LAST RUN: DEFEATED GHOST (GENERIC) + if ( gauntlet.lastRunDefeatedGhost && !gauntlet.allGhostsDefeated ) + { + feedbackLine = DialogueGroup_GetNextLine( defeatedGhost ) + } + // LAST RUN: BEAT BEST TIME + else if ( gauntlet.lastRunBestTime ) + { + feedbackLine = DialogueGroup_GetNextLine( newBestTime ) + } + // LAST RUN: FAILED TO BEAT BEST TIME + else + { + feedbackLine = DialogueGroup_GetNextLine( notBestTime ) + } + + Assert( feedbackLine != "" ) + waitthread Training_OG_Talks( feedbackLine, ogSpot, "OG_all_done_talk", "OG_all_done_idle", true ) + } +} + +void function TrainingGauntletPostRun_TryAchievements( entity player, GauntletInfo gauntlet ) +{ + Assert( gauntlet.runFinished ) + + GauntletGhost andersonGhost = Gauntlet_GetGhostByFileName( gauntlet, GHOST_NAME_ALIAS_ANDERSON ) + if ( gauntlet.lastRunTime < andersonGhost.duration ) + { + // Achievement - Beat Pilot Anderson's gauntlet ghost recorder time + UnlockAchievement( player, achievements.GAUNTLET_BEAT_ANDERSON ) + } + + GauntletGhost playerGhost = Gauntlet_GetPlayerGhost( gauntlet ) + int playerLeaderboardPos = Gauntlet_GetLeaderboardPosition_ForGhostID( gauntlet, playerGhost.id ) + if ( playerLeaderboardPos <= 2 ) + { + // Achievement - Get a top-3 spot on the Gauntlet scoreboard + UnlockAchievement( player, achievements.GAUNTLET_TOPTHREE ) + } +} + +void function Training_LeaveGauntletThink( entity player, entity ogSpot ) +{ + string gauntletExitConversation = "Gauntlet_Exit" + if ( file.gauntletMode ) + gauntletExitConversation = "Gauntlet_Exit_TechTest" + + GauntletInfo trainingGauntlet = GetTrainingGauntlet() + + // NOTE conversation callbacks need this defined + file.animref_leaveGauntlet = ogSpot + AddConversationCallback( gauntletExitConversation, ConvoCallback_TrainingExit ) + AddConversationCallback( "Titanfall_Intro", ConvoCallback_TitanfallIntro) + + int numTimesInitiated = 0 + + while ( !Flag( "PlayerConfirmedGauntletExit" ) ) + { + FlagWait( "Gauntlet_PlayerInExitZone" ) + + FlagWaitClear( "Gauntlet_PlayingFeedbackVO" ) + + if ( !Flag( "Gauntlet_PlayerInExitZone" ) ) + continue + + if ( !Training_IsGameFullyInstalled() ) + { + waitthread Gauntlet_GameNotFullyInstalled_Response( player, ogSpot ) + wait 0.1 // HACK make sure we always wait before continuing + continue + } + + numTimesInitiated++ + + // All done with the gauntlet? + waitthread Training_OG_ScriptedAnim( ogSpot, "OG_all_done", true ) + Training_OG_Idles( ogSpot, "OG_all_done_idle", true ) + + // if player rushed to the gauntlet during the anim, don't wait for conversation + if ( trainingGauntlet.isActive ) + continue + + // hint if player can't figure out how to conversate + bool displayedOnscreenHint = false + if ( numTimesInitiated > 1 && !Flag( "PlayerUsedConversationInterface" ) ) + { + thread OnscreenHint_DisplayAfterDelay( player, "conversation_hint", 5.0, 1.5 ) + displayedOnscreenHint = true + } + + // interactive dialogue: Gauntlet Exit + FlagClear( "GauntletExitConvo_FinishedResponse" ) + thread PlayerConversation( gauntletExitConversation, player, file.ogPilot ) + + table result = WaitSignal( player, "ConversationEnded", "PlayerMadeSelection", "Gauntlet_RunStarted" ) + string sig = expect string( result.signal ) + + // bug 202299: Also check flag for player confirmed gauntlet exit + // - player can confirm exit, but if player starts a gauntlet run just before the response line ends, convo can "cancel" causing a prog break here + if ( sig == "Gauntlet_RunStarted" ) + { + // HACK- wait a bit to make sure player didn't confirm exit before the conversation ended due to gauntlet run starting + wait 0.5 + + if ( !Flag( "PlayerConfirmedGauntletExit" ) ) + { + StopConversationNow( player ) + continue + } + } + + /* + #if DEV + if ( sig == "Gauntlet_RunStarted" && Flag( "PlayerConfirmedGauntletExit" ) ) + printt( "BUG CONDITION DEFEATED" ) + #endif + */ + + if ( sig == "PlayerMadeSelection" ) + FlagSet( "PlayerUsedConversationInterface" ) + + if ( displayedOnscreenHint ) + ClearOnscreenHint( player ) + + // wait for callback function to finish doing its thing + FlagWait( "GauntletExitConvo_FinishedResponse" ) + + if ( Flag( "PlayerConfirmedGauntletExit" ) ) + break + + FlagWaitClear( "Gauntlet_PlayerInExitZone" ) + + StopConversationNow( player ) + } + + if ( file.gauntletMode ) + { + GauntletMode_Finished( player ) + } + else + { + // interactive dialogue: Titanfall Intro + FlagClear( "TitanfallIntroConvo_FinishedResponse" ) + waitthread PlayerConversation( "Titanfall_Intro", player, file.ogPilot ) + + FlagWait( "TitanfallIntroConvo_FinishedResponse" ) + } + + wait 0.2 + + FlagSet( "PlayerLeavingGauntlet" ) +} + +void function Gauntlet_GameNotFullyInstalled_Response( entity player, entity ogRef ) +{ + EndSignal( player, "OnDestroy" ) + + float progress = GetGameFullyInstalledProgress() + printt( "Game is not fully installed- cannot continue past Gauntlet. Progress:", progress, "/ 1.0" ) + + DisplayOnscreenHint( player, "gauntlet_install_hint" ) + + OnThreadEnd( + function() : ( player ) + { + if ( IsValid( player ) ) + ClearOnscreenHint( player ) + } + ) + + if ( TimerCheck( "installWaitComment" ) ) + { + DialogueGroup installWait = GetDialogueGroup( "installWait" ) + string line = DialogueGroup_GetNextLine( installWait ) + waitthread Training_OG_Talks( line, ogRef, "OG_all_done_talk", "OG_all_done_idle", true ) + + TimerReset( "installWaitComment" ) + } + + while ( Flag( "Gauntlet_PlayerInExitZone" ) && !Training_IsGameFullyInstalled() ) + { + wait 0.25 + } +} + +void function ConvoCallback_TrainingExit( int choice ) +{ + EndSignal( file.player, "OnDestroy" ) + + OnThreadEnd( + function() : ( ) + { + if ( IsValid( file.player ) ) + FlagSet( "GauntletExitConvo_FinishedResponse" ) + } + ) + + printt( "TRAINING EXIT CALLBACK: player chose", choice ) + Assert( choice >= 0 && choice <= 2, "Nothing set up for Training Exit convo choice " + choice ) + + // prompt timed out + if ( choice == 0 ) + return + + Signal( file.player, "PlayerMadeSelection" ) + + // player chooses to stay + if ( choice == 2 ) + { + waitthread Training_OG_ScriptedAnim( file.animref_leaveGauntlet, "OG_all_done_respond", true ) + Training_OG_Idles( file.animref_leaveGauntlet, "OG_all_done_idle", true ) + return + } + + // player chooses to leave + FlagSet( "PlayerConfirmedGauntletExit" ) + printt( "PLAYER CONFIRMED GAUNTLET EXIT" ) + + // Good. You're gonna like this. + // It's time you learned the other half of being a Pilot: the Titan. Let's go call one in. + string anim = "OG_gonna_like_this" + if ( file.gauntletMode ) + { + // Good. You're gonna like this. + // Let's get back to the real world. + anim = "OG_gonna_like_this_tech_test_end" + } + + waitthread Training_OG_ScriptedAnim( file.animref_leaveGauntlet, anim, true ) + Training_OG_Idles( file.animref_leaveGauntlet, "OG_gonna_like_this_endidle", true ) +} + +void function ConvoCallback_TitanfallIntro( int choice ) +{ + EndSignal( file.player, "OnDestroy" ) + + OnThreadEnd( + function() : ( ) + { + if ( IsValid( file.player ) ) + FlagSet( "TitanfallIntroConvo_FinishedResponse" ) + } + ) + + printt( "TITANFALL INTRO CALLBACK: player chose", choice ) + Assert( choice >= 0 && choice <= 2, "Nothing set up for Titanfall Intro convo choice " + choice ) + + // It's only a simulation, Cooper. It's not the real thing. + // But first- We're gonna need a little more space + string responseAnim = "OG_gonna_like_this_respond_A" + if ( choice == 2 ) + { + // That's the spirit. + // But first- We're gonna need a little more space + responseAnim = "OG_gonna_like_this_respond_B" + } + + thread Training_OG_ScriptedAnim( file.animref_leaveGauntlet, responseAnim, true ) + float duration = GetOGPilot().GetSequenceDuration( responseAnim ) + wait duration - 1.0 + + //Training_OG_Idles( file.animref_leaveGauntlet, "OG_gonna_like_this_endidle", true ) +} + + +// runType: 0 = before beating required time; 1 = in challenge mode +void function TrainingGauntletStats_PostRunUpdate( entity player, GauntletInfo gauntlet, int runType ) +{ + Assert( !gauntlet.isActive, "Can't update gauntlet stats reliably while gauntlet is active." ) + + // restarted gauntlet from menu + if ( !gauntlet.runFinished ) + { + file.trainingGauntletStats.numRestarts++ + printt( "training gauntlet stats updated: numRestarts", file.trainingGauntletStats.numRestarts ) + + SendTrainingGauntletStats( player ) + return + } + + if ( gauntlet.lastRunBestTime ) + { + file.trainingGauntletStats.bestTime = gauntlet.bestTime + printt( "training gauntlet stats updated: bestTime", file.trainingGauntletStats.bestTime ) + } + + if ( runType == 0 ) + { + file.trainingGauntletStats.numRunsBeforeBeatRequiredTime++ + printt( "training gauntlet stats updated: numRunsBeforeBeatRequiredTime", file.trainingGauntletStats.numRunsBeforeBeatRequiredTime ) + + if ( gauntlet.bestTime > TRAINING_GAUNTLET_MAX_TIME_TO_PROGRESS ) + { + // failed to get required time + SendTrainingGauntletStats( player ) + return + } + else + { + file.trainingGauntletStats.didBeatRequiredTime = true + printt( "training gauntlet stats updated: didBeatRequiredTime", file.trainingGauntletStats.didBeatRequiredTime ) + } + } + else if ( runType == 1 ) + { + file.trainingGauntletStats.numChallengeRuns++ + printt( "training gauntlet stats updated: numChallengeRuns", file.trainingGauntletStats.numChallengeRuns ) + } + + SendTrainingGauntletStats( player ) +} + + +void function SendTrainingGauntletStats( entity player ) +{ + printt("======== TRAINING GAUNTLET STATS =========" ) + printt( "numRestarts:", file.trainingGauntletStats.numRestarts ) + printt( "numRunsBeforeBeatRequiredTime:", file.trainingGauntletStats.numRunsBeforeBeatRequiredTime ) + printt( "didBeatRequiredTime:", file.trainingGauntletStats.didBeatRequiredTime ) + printt( "numChallengeRuns:", file.trainingGauntletStats.numChallengeRuns ) + printt( "bestTime:", file.trainingGauntletStats.bestTime ) + printt("========= END TRAINING GAUNTLET STATS ==========" ) + SendTrainingGauntletStatsToBackend( + player, + file.trainingGauntletStats.didBeatRequiredTime ? file.trainingGauntletStats.numRunsBeforeBeatRequiredTime : 0, + file.trainingGauntletStats.numChallengeRuns, + file.trainingGauntletStats.bestTime + ) +} + + +bool function Training_IsGameFullyInstalled() +{ + #if DEV + if ( INSTALL_DELAY_TEST ) + return file.fakeInstallDone + #endif + + return IsGameFullyInstalled() +} + +#if DEV +void function setfakeinstalldone( bool isDone ) +{ + file.fakeInstallDone = isDone +} +#endif + + +// =================================== +// ============ TITANFALL ============ +// =================================== +void function Training_Setup_Titanfall( entity player ) +{ + entity ogRef_titanfall = GetEntByScriptName( "og_titanfall_start_pos" ) + entity og = Training_SpawnOGPilot( ogRef_titanfall ) + Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle" ) + + player.DisableWeapon() // player weapon is disabled before teleport to Titanfall area starts + + TeleportPlayerAndBT( "startpoint_titanfall" ) +} + +void function Training_Skipped_Titanfall( entity player ) +{ + player.SetExtraWeaponMods( ["training_low_ammo_disable"] ) + SetWeaponHUDEnabled( player, false ) +} + +void function Training_Titanfall( entity player ) +{ + thread Titanfall_EnableWeaponDelayed( player, 0.9 ) + + thread TakeAmmoFromPlayerASAP( player ) + thread Titanfall_SpecialWeaponRemove( player ) + + entity og = GetOGPilot() + entity ogRef_titanfall = GetEntByScriptName( "og_titanfall_start_pos" ) + Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle" ) + + vector twinRefSpawnOrg = TitanfallGlitch_WorldChange_GetOtherWorldPos( ogRef_titanfall.GetOrigin(), true ) + entity ogRef_titanfall_twin = CreateScriptMover( twinRefSpawnOrg, ogRef_titanfall.GetAngles() ) + entity ogTwin = Training_SpawnOGTwin( ogRef_titanfall_twin ) + Training_NPC_Idles_Sitting( ogTwin, ogRef_titanfall_twin, "OG_first_titan_idle" ) + + OnThreadEnd( + function() : ( player, ogTwin, ogRef_titanfall_twin ) + { + if ( IsValid( player ) ) + player.UnfreezeControlsOnServer() + + if ( IsValid( ogTwin ) ) + ogTwin.Destroy() + + if ( IsValid( ogRef_titanfall_twin ) ) + ogRef_titanfall_twin.Destroy() + } + ) + + //testglitch() + //wait 1000000 + + thread Titanfall_BT_Think( player, 6.5 ) + + wait 2.0 + + // "That's my partner, BT. He's a Vanguard-class." + // "Homegrown Militia technology." + // "The first Titan chassis we designed ourselves. One we didn't have to steal from the IMC." + waitthread Training_OG_ScriptedAnim( ogRef_titanfall, "OG_Partner_BT", true ) + Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle", true ) + + wait 1.1 // take a breath between lines + + thread Training_EnableTitanfallAndNag( player, ogRef_titanfall, 25.0 ) + + // "Go ahead Rifleman, call in your first Titan." + Titanfall_ResetHintVOTimer() // in case player calls in Titan during this line + waitthread Training_OG_ScriptedAnim( ogRef_titanfall, "OG_First_Titan", true ) + Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle", true ) + + FlagWait( "PlayerStartedTitanfall" ) + // Don't start OG's next line until the nag is done, to avoid overlap + wait Titanfall_GetHintVORemainingDuration() + + // "Look up, to the sky- there he is." + waitthread Training_OG_ScriptedAnim( ogRef_titanfall, "OG_Look_Up", true ) + Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle", true ) + + thread Titanfall_QuickdeathCustomResetPlayerPos( player, file.playerTitanCallInPos, "PodOutroStarted" ) + + // wait for titan to appear + while ( !GetPlayerTitanInMap( player ) ) + wait 0.1 + + entity titan = GetPlayerTitanInMap( player ) + + // "glitch" interrupts titan drop + FlagWait( "TitanfallGlitchStart" ) + + PlayMusic( "music_training_01_glitch" ) + + // Glitch ends when this function ends + float totalGlitchTime = 2.9 + float glitchEndTime = Time() + totalGlitchTime + + wait 1.0 + player.FreezeControlsOnServer() + + thread Training_OG_ScriptedAnim( ogRef_titanfall, "OG_Pulling_You_Out", true ) + + wait glitchEndTime - Time() + + if ( IsValid( titan ) ) + titan.Destroy() + + if ( IsValid( file.titanTwin ) ) + file.titanTwin.Destroy() +} + +void function Titanfall_EnableWeaponDelayed( entity player, float delay ) +{ + EndSignal( player, "OnDestroy" ) + + if ( delay > 0 ) + wait delay + + player.EnableWeapon() + player.SetExtraWeaponMods( ["training_low_ammo_disable"] ) + SetWeaponHUDEnabled( player, false ) +} + +void function Titanfall_SpecialWeaponRemove( entity player ) +{ + array<entity> weapons = player.GetMainWeapons() + int numWeaponsStart = weapons.len() + + foreach ( equippedWeapon in weapons ) + { + string equippedWeaponClassName = equippedWeapon.GetWeaponClassName() + if ( equippedWeaponClassName == "mp_weapon_lstar" ) // LSTAR display blinks annoyingly if ammo is gone + { + // give player a weapon if we are going to take their only weapon + if ( numWeaponsStart <= 1 ) + player.GiveWeapon( "mp_weapon_semipistol" ) + + player.TakeWeapon( equippedWeaponClassName ) + + break + } + } +} + +void function Titanfall_BT_Think( entity player, float getUpDelay = 0.0 ) +{ + EndSignal( player, "OnDestroy" ) + + entity spawnRef = GetEntByScriptName( "titanfall_bt_spawn_ref" ) + vector spawnOrg = spawnRef.GetOrigin() + vector spawnAng = spawnRef.GetAngles() + + // create BT + TitanLoadoutDef loadout = GetTitanLoadoutForPlayer( player ) + entity bt = CreateNPCTitan( loadout.setFile, player.GetTeam(), spawnOrg, spawnAng, loadout.setFileMods ) + bt.ai.titanSpawnLoadout = loadout + bt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID + DispatchSpawn( bt ) + + entity animref = CreateScriptMover( spawnOrg, spawnAng ) + + OnThreadEnd( + function() : ( bt, animref ) + { + if ( IsValid( bt ) ) + bt.Destroy() + + if ( IsValid( animref ) ) + animref.Destroy() + } + ) + + bt.EnableNPCFlag( NPC_IGNORE_FRIENDLY_SOUND ) + bt.SetEfficientMode( true ) + bt.SetTouchTriggers( false ) + bt.SetNoTarget( true ) + bt.UnsetUsable() + bt.SetInvulnerable() + bt.EnableRenderAlways() + + bt.SetTitle( "#NPC_BT_NAME" ) + ShowName( bt ) + + DisableTitanfallForLifetimeOfEntityNearOrigin( bt, bt.GetOrigin(), TITANHOTDROP_DISABLE_ENEMY_TITANFALL_RADIUS ) + + // HACK this anim pops if it's played off of the animref, + // but looks good if played off BT before using the animref for the rest + thread PlayAnim( bt, "bt_training_scripted_kneel_idle", bt ) + + if ( getUpDelay > 0 ) + wait getUpDelay + + waitthread PlayAnim( bt, "bt_training_scripted_kneel2stand", animref ) + thread PlayAnim( bt, "bt_training_scripted_stand_idle", animref ) + + FlagWait( "PodOutroStarted" ) +} + +void function Titanfall_QuickdeathCustomResetPlayerPos( entity player, vector titanCallInPos, string endFlag ) +{ + FlagEnd( endFlag ) + + entity resetEnt = GetEntByScriptName( "ref_titanfall_quickdeath_reset" ) + vector resetPos = resetEnt.GetOrigin() + + while ( 1 ) + { + WaitSignal( player, "QuickDeath" ) + + vector angToCallInPos = VectorToAngles( titanCallInPos - resetPos ) + float viewTiltUpAngX = -10.0 // goose the view up a little + vector viewAng = <viewTiltUpAngX, angToCallInPos.y, 0> + + player.p.quickDeathOrigin = resetPos + player.p.quickDeathAngles = viewAng + } +} + +void function Training_EnableTitanfallAndNag( entity player, entity ogRef, float nagDelay ) +{ + EndSignal( player, "OnDestroy" ) + + SetGlobalNetBool( "trainingTitanfallEnabled", true ) + AddClientCommandCallback( "ClientCommand_TrainingRequestedTitanfall", Training_RequestTitanfall ) + + float onscreenHintDisplayTime = nagDelay * 0.5 + thread OnscreenHint_DisplayAfterDelay( player, "titanfall_hint", onscreenHintDisplayTime, 3.5 ) // extra delay on first screen prompt so pros can call it in + + Objective_SetSilent( "#TRAINING_OBJ_CALL_TITAN" ) + + // "Titan's ready. Call it in." + // "Titan's ready to drop. On your mark." + array<string> titanfallNags = [ "og_titanfall_nag_1", "og_titanfall_nag_2" ] + + DialogueGroup callInTitan = GetDialogueGroup( "callInTitan" ) + while ( !Flag( "PlayerStartedTitanfall" ) ) + { + FlagWaitWithTimeout( "PlayerStartedTitanfall", nagDelay ) + if ( Flag( "PlayerStartedTitanfall" ) ) + break + + thread OnscreenHint_DisplayAfterDelay( player, "titanfall_hint", onscreenHintDisplayTime, 2.5 ) + + string line = DialogueGroup_GetNextLine( callInTitan ) + Titanfall_ResetHintVOTimer() + waitthread Training_OG_Talks_Sitting( line, ogRef, "OG_first_titan_talk", "OG_first_titan_idle" ) + } + + Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" ) + ClearOnscreenHint( player ) +} + + +// CUSTOM HOTDROP +bool function Training_RequestTitanfall( entity player, array<string> args ) +{ + Training_ReplacementTitan( player ) //Separate function because other functions will call ReplacementTitan + return true +} + +bool function Training_ReplacementTitan( entity player ) +{ + Assert( IsAlive( player ) ) + + Assert ( !GetPlayerTitanInMap( player ) ) + + Point spawnPoint = GetTitanReplacementPoint( player, false ) + vector origin = spawnPoint.origin + vector angles = spawnPoint.angles + + file.playerTitanCallInPos = origin + + FlagSet( "PlayerStartedTitanfall" ) + SetGlobalNetBool( "trainingTitanfallEnabled", false ) + + thread Training_CreateTitanForPlayerAndHotdrop( player, origin, angles ) + + return true +} + +#if DEV +void function testglitch() +{ + thread Training_CreateTitanForPlayerAndHotdrop( GetPlayerArray()[0], <2048, -2852, -349>, <0,0,0> ) +} +#endif + +void function Training_CreateTitanForPlayerAndHotdrop( entity player, vector spawnOrg, vector spawnAng ) +{ + Assert( IsValid( player ) ) + OnThreadEnd( + function() : ( player ) + { + if ( !IsValid( player ) ) + return + + player.ClearHotDropImpactTime() + } + ) + + player.EndSignal( "OnDestroy" ) + + vector origin = spawnOrg + vector angles + if ( spawnAng != < 0.0, 0.0, 0.0 > ) + angles = spawnAng + else + angles = VectorToAngles( FlattenVector( player.GetViewVector() ) * -1 ) // face the player + + printt( "Dropping replacement titan at " + origin + " with angles " + angles ) + + player.Signal( "CalledInReplacementTitan" ) + + int playerTeam = player.GetTeam() + + // spawn the Titan that will drop + TitanLoadoutDef loadout = GetTitanLoadoutForPlayer( player ) + entity titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, origin, angles ) + DispatchSpawn( titan ) + titan.EndSignal( "OnDeath" ) + + titan.SetSkin( 2 ) // "Sarah Briggs" Vanguard skin + titan.SetTitle( "#NPC_BT_SPARE_NAME" ) + HideName( titan ) + TakeAllWeapons( titan ) + titan.SetEfficientMode( true ) + titan.SetTouchTriggers( false ) + titan.SetNoTarget( true ) + titan.UnsetUsable() // Disable titan embark + + titan.Hide() + + OnThreadEnd( + function() : ( titan ) + { + if ( !IsValid( titan ) ) + return + + // removed so that model highlight always works for you autotitan + // titan.DisableRenderAlways() + } + ) + + // based on "at_hotdrop_drop_2knee_turbo" + string animation = "bt_hotdrop_glitch_descent" + string postButtonPressSFX = "training_scr_titan_glitch_button_press" + string hotdropToGlitchSFX = "training_anim_glitch_scene" + + EmitSoundOnEntity( player, postButtonPressSFX ) + + float hotDropAnimDuration = titan.GetSequenceDuration( animation ) + + float extraSpawnDelay = 1.5 // bit of extra padding time for "Look up, to the sky" VO to sink in + extraSpawnDelay += Titanfall_GetHintVORemainingDuration() + + // Glitched hotdrop anim created by trimming the end from normal hotdrop anim (glitch starts just before the Titan hits the ground) + // - add this little bit back so the timer looks "correct" (longer than actual anim duration) + float arrivalTimerDuration = 0.7 + arrivalTimerDuration += (hotDropAnimDuration + extraSpawnDelay) // now add the rest of the normal delays before arrival + thread Training_DrawReplacementTitanLocation( player, origin, arrivalTimerDuration ) + + wait extraSpawnDelay + + titan.Show() + ShowName( titan ) + + Attachment result = titan.Anim_GetAttachmentAtTime( animation, "OFFSET", (hotDropAnimDuration - 0.1) ) + vector maxs = titan.GetBoundingMaxs() + vector mins = titan.GetBoundingMins() + int mask = titan.GetPhysicsSolidMask() + origin = ModifyOriginForDrop( origin, mins, maxs, result.position, mask ) + + titan.SetInvulnerable() //Make Titan invulnerable until bubble shield is up. Cleared in OnTitanHotdropImpact + titan.EnableRenderAlways() + + int teamNum = TEAM_UNASSIGNED + if ( IsValid( player ) ) + teamNum = player.GetTeam() + + vector fwdToPlayer = player.GetOrigin() - origin + vector facingAngles = VectorToAngles( fwdToPlayer ) + vector facingAngles2D = <facingAngles.x, facingAngles.y, 0> + angles = facingAngles2D + + //FadeOutSoundOnEntity( player, postButtonPressSFX, 1.0 ) + EmitSoundOnEntity( player, hotdropToGlitchSFX ) + + thread PlayAnimTeleport( titan, animation, origin, angles ) + wait hotDropAnimDuration - 0.1 + + titan.SetTouchTriggers( true ) + + thread TitanfallGlitch( player, titan, origin, angles ) +} + +void function Titanfall_ResetHintVOTimer() +{ + file.titanfallNagStartTime = Time() +} + +float function Titanfall_GetHintVORemainingDuration() +{ + if ( file.titanfallNagStartTime == -1 ) + return 0 + + float duration = TITANFALL_NAG_DURATION - (Time() - file.titanfallNagStartTime) + if ( duration < 0 ) + duration = 0 + + return duration +} + +bool function Titanfall_IsHintVOStillPlaying() +{ + return Titanfall_GetHintVORemainingDuration() >= 0 +} + +void function TitanfallGlitch( entity player, entity titan, vector origin, vector angles ) +{ + if ( Flag( "PodOutroStarted" ) ) + return + FlagEnd( "PodOutroStarted" ) + + player.EndSignal( "OnDestroy" ) + titan.EndSignal( "OnDestroy" ) + + titan.Signal( "glitch_start" ) + FlagSet( "TitanfallGlitchStart" ) + + if ( IsValid( file.titanTwin ) ) + file.titanTwin.Destroy() + + entity titanTwin = CreatePropScript( BUDDY_MODEL, titan.GetOrigin(), titan.GetAngles() ) + file.titanTwin = titanTwin + vector twinOrigin = TitanfallGlitch_WorldChange_GetOtherWorldPos( origin, true ) + + array<entity> sceneTitans = [ titan, titanTwin ] + + // idle on last frame of descent anim + titanTwin.Anim_Stop() + titan.Anim_Stop() + string endIdleAnim = GetDefaultTitanfallGlitchAnim() + thread PlayAnimTeleport( titanTwin, endIdleAnim, twinOrigin, angles ) + thread PlayAnimTeleport( titan, endIdleAnim, origin, angles ) + + // DEPRECATED no more glitchy anims + // do this on the hotdropping titan to blend out of the hotdrop better (but makes the proxy on the client out of sync) + // - in the future, need to play all the hotdrop anims on the proxy as well, to let it blend the same + //thread PlayAnim( titan, endIdleAnim, origin, angles, 0.1 ) + + // START GLITCH SEQUENCE + + // setup for client extra flicker + titan.Hide() + titanTwin.Hide() + int eHandle_titan = titan.GetEncodedEHandle() + int eHandle_twin = titanTwin.GetEncodedEHandle() + Remote_CallFunction_Replay( player, "ScriptCallback_TitanfallGlitch_ExtraFlicker", eHandle_titan ) + Remote_CallFunction_Replay( player, "ScriptCallback_TitanfallGlitch_ExtraFlicker", eHandle_twin, true ) + SetGlobalNetBool( "titanGlitch_extraFlicker", true ) // makes him flicker until the world starts changing + + // DEPRECATED Not doing the huge anim moves might work better for the glitch moment, trying it out + //thread TitanfallGlitch_CycleTitanGlitchAnims_OnWorldChange( player, titan, titanTwin, origin, twinOrigin, angles ) + + float screenFXTime = 4.5 + float screenFXStartDelay = 0.1 + thread StartGlitchScreenFX_Delayed( player, screenFXTime, screenFXStartDelay ) + + // FIRST IMPACT + float firstShakeDuration = 3.0 + + float shakeAmplitude = 14.0 + float screenBlurFrac = 0.25 + SimpleScreenShake( player, firstShakeDuration, shakeAmplitude, screenBlurFrac ) + + // juice it with extra rumble + // note- this is not juicing it as much as I would expect... even cranking the amplitude doesn't really work that well + float rumbleAmplitude = 50.0 + float rumbleFrequency = 170 + float rumbleDuration = 4.5 + CreateShakeRumbleOnly( player.GetOrigin(), rumbleAmplitude, rumbleFrequency, rumbleDuration ) + + // let the titan idle for a moment + float postDescentIdleTime = 1.0 + wait postDescentIdleTime + + float firstShakeDuration_remaining = firstShakeDuration - postDescentIdleTime + // DEPRECATED trying without world changes + //thread TitanfallGlitch_PlayerWorldChange( player, firstShakeDuration_remaining * 0.9, 0.0 ) + + wait firstShakeDuration_remaining + + /* DEPRECATED don't need smaller shakes now that the scene only lasts a short time + printt( "FIRST IMPACT DONE") + + // LOOPING SMALLER IMPACTS + float shakeDuration_min = 2.5 + float shakeDuration_max = 3.0 + shakeAmplitude = 5.5 + screenBlurFrac = 0.6 + while ( 1 ) + { + float shakeDuration = RandomFloatRange( shakeDuration_min, shakeDuration_max ) + + SimpleScreenShake( player, shakeDuration, shakeAmplitude, screenBlurFrac ) + thread TitanfallGlitch_PlayerWorldChange( player, shakeDuration * 0.85, shakeDuration * 0.1 ) + + wait shakeDuration + } + */ +} + +void function StartGlitchScreenFX_Delayed( entity player, float screenFXTime, float delay ) +{ + if ( Flag( "PodOutroStarted" ) ) + return + FlagEnd( "PodOutroStarted" ) + + EndSignal( player, "OnDestroy" ) + + if ( delay > 0 ) + wait delay + + Remote_CallFunction_Replay( player, "ScriptCallback_PodGlitch_PlayerScreenFX", screenFXTime ) +} + +const float GLITCH_WORLD_CHANGE_WAIT_MIN = 0.1 +const float GLITCH_WORLD_CHANGE_WAIT_MAX = 0.35 +const float GLITCH_WORLD_CHANGE_MINWAIT_FOR_EXTRA_FLICKER = 0.25 + +void function TitanfallGlitch_CycleTitanGlitchAnims_OnWorldChange( entity player, entity mainTitan, entity titanTwin, vector origin, vector twinOrigin, vector angles, float delay = 0.0 ) +{ + player.EndSignal( "OnDestroy" ) + mainTitan.EndSignal( "OnDestroy" ) + titanTwin.EndSignal( "OnDestroy" ) + + array<entity> sceneTitans = [ mainTitan, titanTwin ] + + OnThreadEnd( + function() : ( player, sceneTitans ) + { + if ( IsValid( player ) ) + { + FlagClear( "PlayerWorldChangeThread" ) + SetGlobalNetBool( "titanGlitch_extraFlicker", false ) + } + + foreach ( titan in sceneTitans ) + if ( IsValid( titan ) ) + titan.Show() + } + ) + + if ( delay > 0 ) + wait delay + + array<string> titanAnims = GetTitanfallGlitchAnims() + array<string> twinAnims = GetTitanfallGlitchTwinAnims() + Assert( titanAnims.len() == twinAnims.len() ) + int animIdx = GetGlobalNetInt( "titanfallGlitchAnimIdx" ) + + while ( 1 ) + { + table result = WaitSignal( player, "Glitch_WorldChanging_Zen", "Glitch_WorldChanging_NonZen" ) + string signal = expect string( result.signal ) + + float worldChangeDuration = expect float( result.postChangeWait ) + + //printt( "titan anim:", titanAnims[animIdx] ) + + mainTitan.Anim_Stop() + titanTwin.Anim_Stop() + thread PlayAnimTeleport( mainTitan, titanAnims[animIdx], origin, angles ) + thread PlayAnimTeleport( titanTwin, twinAnims[animIdx], twinOrigin, angles ) + + animIdx++ + if ( animIdx >= titanAnims.len() ) + animIdx = 0 + + SetGlobalNetInt( "titanfallGlitchAnimIdx", animIdx ) + + // update flicker settings & show/hide server versions of the titans + if ( signal == "Glitch_WorldChanging_Zen" ) + { + // only do extra flicker if a longer world change pause is happening + if ( worldChangeDuration >= GLITCH_WORLD_CHANGE_MINWAIT_FOR_EXTRA_FLICKER ) + { + SetGlobalNetBool( "titanGlitch_extraFlicker", true ) + + foreach ( titan in sceneTitans ) + titan.Hide() + } + } + else if ( signal == "Glitch_WorldChanging_NonZen" ) + { + SetGlobalNetBool( "titanGlitch_extraFlicker", false ) + + foreach ( titan in sceneTitans ) + titan.Show() + } + else + { + Assert( false, "Couldn't find signal: " + signal ) + } + } +} + + +void function TitanfallGlitch_PlayerWorldChange( entity player, float duration, float delay = 0.0 ) +{ + if ( Flag( "PodOutroStarted" ) ) + return + FlagEnd( "PodOutroStarted" ) + + EndSignal( player, "OnDestroy" ) + + if ( delay > 0 ) + wait delay + + FlagWaitClear( "PlayerWorldChangeThread" ) + FlagSet( "PlayerWorldChangeThread" ) + + OnThreadEnd( + function() : ( player ) + { + if ( IsValid( player ) ) + FlagClear( "PlayerWorldChangeThread" ) + } + ) + + bool isInZenWorld = true + + float endTime = Time() + duration + + float minWait = GLITCH_WORLD_CHANGE_WAIT_MIN + float maxWait = GLITCH_WORLD_CHANGE_WAIT_MAX + + while ( 1 ) + { + // at end time, make sure player pos resets back to zen world before breaking + if ( Time() >= endTime && isInZenWorld ) + break + + vector newpos = TitanfallGlitch_WorldChange_GetOtherWorldPos( player.GetOrigin(), isInZenWorld ) + + float postChangeWait + string changeSignal + entity newSkyCam + + if ( isInZenWorld ) + { + // Switch to non zen world + changeSignal = "Glitch_WorldChanging_NonZen" + postChangeWait = minWait // always wait min time in non zen world + + newSkyCam = file.skycam_glitch + isInZenWorld = false + } + else + { + // Switch back to zen world + postChangeWait = RandomFloatRange( minWait, maxWait ) + changeSignal = "Glitch_WorldChanging_Zen" + + newSkyCam = file.skycam_default + isInZenWorld = true + } + + table<string,float> extraInfo = {} + extraInfo["postChangeWait"] <- postChangeWait + Signal( player, changeSignal, extraInfo ) + + player.SetSkyCamera( newSkyCam ) + player.SetOrigin( newpos ) + + wait postChangeWait + } +} + + +vector function TitanfallGlitch_WorldChange_GetOtherWorldPos( vector currentPos, bool startInZenWorld ) +{ + entity zenEnt = GetEntByScriptName( "titan_glitch_swap_ref1" ) + entity nonZenEnt = GetEntByScriptName( "titan_glitch_swap_ref2" ) + Assert( zenEnt && nonZenEnt ) + vector zenPos = zenEnt.GetOrigin() + vector nonZenPos = nonZenEnt.GetOrigin() + + float offsetX + float offsetY + float offsetZ + float newPosX + float newPosY + float newPosZ + + if ( startInZenWorld ) + { + offsetX = currentPos.x - zenPos.x + offsetY = currentPos.y - zenPos.y + offsetZ = currentPos.z - zenPos.z + + newPosX = nonZenPos.x + offsetX + newPosY = nonZenPos.y + offsetY + newPosZ = nonZenPos.z + offsetZ + } + else + { + offsetX = currentPos.x - nonZenPos.x + offsetY = currentPos.y - nonZenPos.y + offsetZ = currentPos.z - nonZenPos.z + + newPosX = zenPos.x + offsetX + newPosY = zenPos.y + offsetY + newPosZ = zenPos.z + offsetZ + } + + vector newPos = < newPosX, newPosY, newPosZ > + return newPos +} + + +void function Training_DrawReplacementTitanLocation( entity player, vector origin, float delay ) +{ + float endTime = Time() + delay + + player.SetHotDropImpactDelay( endTime ) + Remote_CallFunction_Replay( player, "ServerCallback_ReplacementTitanSpawnpoint", origin.x, origin.y, origin.z, endTime ) + player.WaitSignal( "OnDeath" ) +} + + +// =================================== +// ============ POD OUTRO ============ +// =================================== +void function Training_Setup_PodOutro( entity player ) +{ +} + +void function Training_Skipped_PodOutro( entity player ) +{ + Training_EnvArtColorCorrection_SetEnabled( false ) + SetDoF_Hangar( player ) + + Objective_Clear() + + FlagSet( "PodOutroStarted" ) + FlagSet( "SimPodShutdown_LoudspeakerVO_Done" ) +} + +#if DEV +// bare bones start in pod +void function DEV_PodOutro( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + TakeAllWeapons( player ) + + player.SetExtraWeaponMods( [ "low_ammo_disable" ] ) + SetWeaponHUDEnabled( player, false ) + + Training_EnvArtColorCorrection_SetEnabled( false ) + SetDoF_Hangar( player ) + + thread MeetOG_BackgroundSkits( player ) + + entity pod = file.trainingPod + + TrainingPod_PlayerSequence_DoorsOpenIdle( player, false ) + + // anim starts at a slightly different spot + player.SetOrigin( < 10564, -10235, -6056.9 > ) + player.SetAngles( < -6, 90, 0 > ) + + WaitForever() +} +#endif //DEV + +void function Training_PodOutro( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + Objective_Clear() + CheckPoint_Silent() + + entity pod = file.trainingPod + + FlagSet( "PodOutroStarted" ) + + OnThreadEnd( + function() : ( player, pod ) + { + if ( IsValid( player ) ) + { + //StopSoundOnEntity( player, "NPE_Scr_SimPod_End" ) + + // Not sure if we need any of this + //player.Anim_Stop() + //ClearAnimViewEntity( player ) + //player.ClearParent() + //player.UnforceStand() + } + + if ( IsValid ( pod ) ) + { + // Not sure if we need any of this + //pod.Anim_Stop() + //thread TrainingPod_ResetLaserEmitterRotation( pod ) + //thread TrainingPod_KillLasers( pod ) + //thread TrainingPod_KillGlowFX( pod ) + //TrainingPod_KillInteriorDLights() + } + } + ) + + // Have to do this first so the anim starts centered on the ref attachment angles + string podAttach = "REF" + int attachID = pod.LookupAttachment( podAttach ) + vector podRefOrg = pod.GetAttachmentOrigin( attachID ) + vector podRefAng = pod.GetAttachmentAngles( attachID ) + player.SetOrigin( podRefOrg ) + player.SetAngles( podRefAng ) + + player.ForceStand() + + TakeAllWeapons( player ) + + // start closed idle + FirstPersonSequenceStruct playerSequence + playerSequence.blendTime = 0.0 + playerSequence.attachment = podAttach + playerSequence.firstPersonAnimIdle = "ptpov_trainingpod_idle" + playerSequence.thirdPersonAnimIdle = "pt_trainingpod_idle" + playerSequence.viewConeFunction = TrainingPod_ViewConeLock_Strict // so player can't move camera under the pod shutdown screen + playerSequence.renderWithViewModels = true + + FirstPersonSequenceStruct podSequence + podSequence.blendTime = 0.0 + podSequence.thirdPersonAnimIdle = "trainingpod_doors_close_idle" + podSequence.renderWithViewModels = true + + thread FirstPersonSequence( playerSequence, player, pod ) + thread FirstPersonSequence( podSequence, pod ) + + thread CadillacMoment_MeetOG( player ) + + //thread PodOutro_ScreenShake( player ) + + Training_EnvArtColorCorrection_SetEnabled( false ) + SetDoF_Hangar( player ) + + // show the "pod shutdown screen" + float shutdownScreenWait = 7.0 + float screenFadeWait = 7.0 + ScreenFade( player, 0, 0, 0, 255, 0, screenFadeWait, FFADE_OUT | FFADE_STAYOUT ) + Remote_CallFunction_Replay( player, "ScriptCallback_SimPodShutdownScreen", shutdownScreenWait ) + + thread SimPodShutdown_Dialogue( player ) + + // HACK, need to wait a bit so player moves into pod before initing shutdown sequence + // - otherwise, interior emitter angle setup math vs player eye position will crash the game + float waitForPlayerToBeMoved = 1.0 + wait waitForPlayerToBeMoved + + float shutdownSequence_waitBeforeAnimStart = 10.0 + thread TrainingPod_Interior_ShutdownSequence( player, shutdownSequence_waitBeforeAnimStart ) + + wait screenFadeWait - waitForPlayerToBeMoved + + // HACK reparent the emitters so they look correct, I didn't expect to have to do this + TrainingPod_SnapLaserEmittersToAttachPoints() + + // Transition screen FX + thread PlayFXOnEntity( FX_POD_SCREEN_OUT, player ) + + float fadeInFromShutdownScreenTime = 0.2 + ScreenFade( player, 0, 0, 0, 255, fadeInFromShutdownScreenTime, 1, FFADE_IN | FFADE_PURGE ) + wait fadeInFromShutdownScreenTime + + TrainingPod_ViewConeLock_PodClosed( player ) + + // start shutdown sequence + // HACK- eventually one sound will cover the whole sequence + thread HACK_DelayedShutdownSequenceSFX( player, 3.0 ) + + player.Signal( "TrainingPod_BeginInteriorShutdown" ) + wait shutdownSequence_waitBeforeAnimStart + + FirstPersonSequenceStruct playerSequence_podOpens + playerSequence_podOpens.blendTime = 0.25 + playerSequence_podOpens.attachment = podAttach + playerSequence_podOpens.firstPersonAnim = "ptpov_trainingpod_doors_open" + playerSequence_podOpens.firstPersonAnimIdle = "ptpov_trainingpod_idle" + playerSequence_podOpens.thirdPersonAnim = "pt_trainingpod_doors_open" + playerSequence_podOpens.thirdPersonAnimIdle = "pt_trainingpod_idle" + playerSequence_podOpens.viewConeFunction = TrainingPod_ViewConeLock_SemiStrict + playerSequence_podOpens.renderWithViewModels = true + + FirstPersonSequenceStruct podSequence_podOpens + podSequence_podOpens.blendTime = 0.25 + podSequence_podOpens.thirdPersonAnim = "trainingpod_doors_open" + podSequence_podOpens.thirdPersonAnimIdle = "trainingpod_doors_open_idle" + podSequence_podOpens.renderWithViewModels = true + + thread TrainingPod_TurnOnInteriorDLights_Delayed( player, 1.5 ) + + thread FirstPersonSequence( podSequence_podOpens, pod ) + thread FirstPersonSequence( playerSequence_podOpens, player, pod ) + + wait 2.1 // wait until scene starts animating for Meet OG +} + +void function HACK_DelayedShutdownSequenceSFX( entity player, float delayTime ) +{ + EndSignal( player, "OnDestroy" ) + + wait delayTime + EmitSoundOnEntityOnlyToPlayerWithSeek( player, player, "NPE_Scr_SimPod_End", 0.7 ) +} + +void function PodOutro_ScreenShake( entity player ) +{ + if ( Flag( "MeetOG_StartScene" ) ) + return + FlagEnd( "MeetOG_StartScene" ) + + player.EndSignal( "OnDestroy" ) + + float shakeDuration = 2.0 + float shakeAmplitude = 0.2 + float screenBlurFrac = 0.0 + float shakeDelayMin = 1.75 + float shakeDelayMax = 2.25 + while ( 1 ) + { + SimpleScreenShake( player, shakeDuration, shakeAmplitude, screenBlurFrac ) + wait shakeDuration - (shakeDuration * 0.25) // HACK shake dies down + wait RandomFloatRange( shakeDelayMin, shakeDelayMax ) + } +} + +void function SimPodShutdown_LoudspeakerVO( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + wait 4.0 // wait for "I'm pulling you out" + + // "Powering down all non-essential systems." + waitthread PlayLoudspeakerVO( "outro_01_1" ) + + // "Prepare for Typhon atmospheric entry in less than three minutes." + //waitthread PlayLoudspeakerVO( "outro_01" ) + + wait 7.0 + + // "All personnel to battle stations." + waitthread PlayLoudspeakerVO( "outro_01_2" ) + + // "This is not a drill." + waitthread PlayLoudspeakerVO( "outro_01_3" ) + + FlagSet( "SimPodShutdown_LoudspeakerVO_Done" ) +} + +void function SimPodShutdown_Dialogue( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + thread SimPodShutdown_LoudspeakerVO( player ) + + Assert( IsValid( file.ogPilot ), "Can't find scene entity ogPilot" ) + + // "Alright Rifleman, sounds like it's about to hit the fan." + waitthread PlayDialogue( "og_hit_the_fan", file.ogPilot ) + + // "I'm pulling you out." + waitthread PlayDialogue( "og_pulling_you_out", file.ogPilot ) + + wait 2.0 + + // "Cooper! Ready up!" + waitthread PlayDialogue( "grunt_closed_pod_yell_at_player", file.ogPilot ) // HACK play it off OG until the animation idle is legit + + // "Easy Cole, he just left VR. Needs a minute to decompress. He'll be ready to go... trust me." + waitthread PlayDialogue( "og_closed_pod_respond_to_grunt", file.ogPilot ) + + // "Yes sir." + waitthread PlayDialogue( "grunt_yes_sir", file.ogPilot ) +} + +#if DEV +void function shutdownscreentest( float duration = 5.0 ) +{ + entity player = file.player + EndSignal( player, "OnDestroy" ) + + float fadeTime = 0.2 + + ScreenFade( player, 0, 0, 0, 255, fadeTime, duration, FFADE_OUT | FFADE_STAYOUT ) + Remote_CallFunction_Replay( player, "ScriptCallback_SimPodShutdownScreen", duration ) + wait duration + ScreenFade( player, 0,0,0, 255, fadeTime, fadeTime, FFADE_IN | FFADE_PURGE ) +} +#endif + + + +// ================================= +// ============ MEET OG ============ +// ================================= +void function Training_Setup_MeetOG( entity player ) +{ + TakeAllWeapons( player ) + + thread CadillacMoment_MeetOG( player ) + TrainingPod_PlayerSequence_DoorsOpenIdle( player ) + + wait 0.2 +} + +void function Training_Skipped_MeetOG( entity player ) +{ + // settings for checkpoints after the normal level progression + SetDoF_Default( player ) + player.SetExtraWeaponMods( [] ) // turn off low_ammo_disable + SetWeaponHUDEnabled( player, true ) +} + +void function Training_MeetOG( entity player ) +{ + thread MeetOG_BackgroundSkits( player ) + + FlagSet( "MeetOG_StartScene" ) + + thread MeetOG_ControllerRumble( player ) + + FlagWait( "CadillacMoment_MeetOG_StartFadeOut" ) + + UnlockAchievement( player, achievements.COMPLETE_TRAINING ) + + // Free SP trial? Load Beacon next. + if( Script_IsRunningTrialVersion() ) + { + thread FreeTrial_OutroPopup( player, 2.5 ) + PickStartPoint( "sp_beacon", "Level Start" ) + } + else + { + thread OutroDifficultyPopup( player, 2.5 ) + + // load next level + // NOTE this does a screen fade already + PickStartPoint( "sp_crashsite", "LevelStart" ) + } + + // don't ever progress from here to the dev functions beyond + WaitForever() +} + +void function FreeTrial_OutroPopup( entity player, float delay ) +{ + EndSignal( player, "OnDestroy" ) + wait delay + + Remote_CallFunction_UI( player, "ScriptCallback_Training_FreeTrialMessage" ) +} + +void function OutroDifficultyPopup( entity player, float delay ) +{ + EndSignal( player, "OnDestroy" ) + wait delay + + Remote_CallFunction_UI( player, "ScriptCallback_Training_SelectSPDifficulty" ) +} + +// can't do screen shake for this part because it looks bad (not connected) when player view is 1P animating while parented to the pod +void function MeetOG_ControllerRumble( entity player ) +{ + player.EndSignal( "OnDestroy" ) + FlagEnd( "CadillacMoment_MeetOG_StartFadeOut" ) + + float shakeDuration = 2.8 + float shakeAmplitudeMin = 13.5 + float shakeAmplitudeMax = 14.5 + float frequency = 155 + float shakeDelayMin = 4.0 + float shakeDelayMax = 7.0 + while ( 1 ) + { + float amplitude = RandomFloatRange( shakeAmplitudeMin, shakeAmplitudeMax ) + + //vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 + CreateAirShakeRumbleOnly( player.GetOrigin(), amplitude, frequency, shakeDuration ) + printt( "Shake Rumble:", amplitude ) + wait shakeDuration * 0.75 // HACK shake dies down pretty early + + wait RandomFloatRange( shakeDelayMin, shakeDelayMax ) + } +} + +void function CadillacMoment_MeetOG( entity player ) +{ + thread MeetOG_LoudspeakerVO( player ) + + int friendlyTeam = player.GetTeam() + + entity animref = file.animref_hangar + vector animrefOrigin = animref.GetOrigin() + vector animrefAngles = animref.GetAngles() + vector btSpawnOrg = <0,0,0> // to avoid red text errors about BT spawning in solid + + entity animEnt = CreateScriptMover( animrefOrigin, animrefAngles ) + + // Spawn scene actors + entity og = Training_SpawnOGPilot( animref ) + Training_OGPilot_SetHelmetOn( og, false ) + SetTeam( og, TEAM_SPECTATOR ) // turn off his glowy bits so they're accentuated when helmet turns on later + + entity anderson = CreateSoldier( friendlyTeam, animrefOrigin, animrefAngles ) + DispatchSpawn( anderson ) + anderson.SetModel( ANDERSON_PILOT_MODEL ) + anderson.SetTitle( "#TRAINING_ANDERSON_NAME" ) + //ShowName( anderson ) + HideName( anderson ) + + entity redshirt = CreateSoldier( friendlyTeam, animrefOrigin, animrefAngles ) + DispatchSpawn( redshirt ) + redshirt.SetModel( TEAM_MIL_GRUNT_MODEL_LMG ) + redshirt.SetTitle( "#CPT_COLE" ) + //ShowName( redshirt ) + HideName( redshirt ) + + TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap() + entity bt = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, btSpawnOrg, animrefAngles ) + SetSpawnOption_AISettings( bt, "npc_titan_buddy" ) + bt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID + DispatchSpawn( bt ) + FreeAutoTitan( bt ) // HACK this disables the worldspace BT locator icon + TakeAllWeapons( bt ) + + array<entity> actors = [ player, bt, og, anderson, redshirt ] + + // Setup scene props + + entity ogHelmet = CreatePropDynamic( OG_PILOT_HELMET_MODEL ) + ogHelmet.DisableHibernation() + file.ogHelmet = ogHelmet + + string knifeTag = "KNIFE" + int knifeTagID = og.LookupAttachment( knifeTag ) + entity dataKnife = CreatePropScript( DATA_KNIFE_MODEL, og.GetAttachmentOrigin( knifeTagID ), og.GetAttachmentAngles( knifeTagID ) ) + dataKnife.DisableHibernation() + dataKnife.SetParent( og, knifeTag, false, 0.0 ) + + asset btWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( "mp_titanweapon_xo16_shorty", "playermodel" ) + Assert( btWeaponModel != $"" ) + entity btWeapon = CreatePropDynamic( btWeaponModel ) + btWeapon.SetParent( bt, "PROPGUN" ) + + string gruntRifleName = "mp_weapon_rspn101" + string andersonWeaponName = "mp_weapon_car" + asset gruntRifleModel = GetWeaponInfoFileKeyFieldAsset_Global( gruntRifleName, "playermodel" ) + asset andersonWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( andersonWeaponName, "playermodel" ) + asset ogWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( OG_WEAPON, "playermodel" ) + Assert( gruntRifleModel != $"" ) + Assert( andersonWeaponModel != $"" ) + Assert( ogWeaponModel != $"" ) + + entity ogWeapon = CreatePropDynamic( ogWeaponModel ) + ogWeapon.DisableHibernation() + ogWeapon.SetParent( og, "PROPGUN" ) + + entity andersonWeapon = CreatePropDynamic( andersonWeaponModel ) + andersonWeapon.DisableHibernation() + andersonWeapon.SetParent( anderson, "PROPGUN" ) + + entity redshirtWeapon = CreatePropDynamic( gruntRifleModel ) + redshirtWeapon.DisableHibernation() + redshirtWeapon.SetParent( redshirt, "PROPGUN" ) + + array<entity> props = [ btWeapon, redshirtWeapon, andersonWeapon, ogWeapon, ogHelmet, dataKnife ] + + OnThreadEnd( + function() : ( actors, props, animEnt ) + { + foreach ( weapon in props ) + { + if ( IsValid( weapon ) ) + { + weapon.ClearParent() + weapon.Destroy() + } + } + + foreach ( actor in actors ) + { + if ( IsValid( actor ) ) + actor.ClearParent() + + if ( IsInvincible( actor ) ) + ClearInvincible( actor ) + + if ( !actor.IsPlayer() ) + actor.Destroy() + } + + if ( IsValid( animEnt ) ) + animEnt.Destroy() + } + ) + + foreach ( guy in actors ) + { + if ( guy.IsPlayer() ) + continue + + MakeInvincible( guy ) + guy.SetEfficientMode( true ) + Highlight_ClearFriendlyHighlight( guy ) + } + + string anim_og = "pt_intro_scene_OG" + string anim_og_helmet = "helmet_intro_scene_OG" + string anim_bt = "BT_intro_scene_OG" + string anim_anderson = "pt_intro_scene_Anderson" + string anim_redshirt = "pt_intro_scene_grunt" + + string anim_og_idle = "pt_intro_scene_OG_idle" + string anim_og_helmet_idle = "helmet_intro_scene_OG_idle" + string anim_bt_idle = "BT_intro_scene_OG_idle" + string anim_anderson_idle = "pt_intro_scene_Anderson_idle" + string anim_redshirt_idle = "pt_intro_scene_grunt_idle" + + thread PlayAnimTeleport( og, anim_og_idle, animEnt ) + thread PlayAnimTeleport( ogHelmet, anim_og_helmet_idle, animEnt ) + thread PlayAnimTeleport( bt, anim_bt_idle, animEnt ) + thread PlayAnimTeleport( anderson, anim_anderson_idle, animEnt ) + thread PlayAnimTeleport( redshirt, anim_redshirt_idle, animEnt ) + + FlagWait( "MeetOG_StartScene" ) + printt( "STARTING ANIMS: MEET OG" ) + + foreach ( guy in actors ) + guy.Anim_Stop() + + AddAnimEvent( og, "helmet_on", AnimEventCallback_MeetOG_HelmetTurnsOn ) + + thread PlayAnimTeleport( og, anim_og, animEnt ) + thread PlayAnimTeleport( ogHelmet, anim_og_helmet, animEnt ) + thread PlayAnimTeleport( bt, anim_bt, animEnt ) + thread PlayAnimTeleport( anderson, anim_anderson, animEnt ) + thread PlayAnimTeleport( redshirt, anim_redshirt, animEnt ) + + player.Anim_Stop() + + // give player weapon for the toss moment + entity fpProxy = player.GetFirstPersonProxy() + int attachID = fpProxy.LookupAttachment( "PROPGUN" ) + asset playerWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( "mp_weapon_vinson", "playermodel" ) + entity playerWeapon = CreatePropDynamic( playerWeaponModel, fpProxy.GetAttachmentOrigin( attachID ), fpProxy.GetAttachmentAngles( attachID ) ) + file.playerAnimWeapon = playerWeapon + playerWeapon.DisableHibernation() + playerWeapon.SetParent( fpProxy, "PROPGUN", false, 0.0 ) + + AddAnimEvent( player, "gun_catch", AnimEventCallback_MeetOG_PlayerCatchesWeapon ) + + FirstPersonSequenceStruct playerSequence_meetOG + + float viewAnimDelay = 4.0 + //playerSequence_meetOG.setInitialTime = viewAnimDelay // DEPRECATED animators will adjust so start position is good again} + playerSequence_meetOG.blendTime = 0.0 + playerSequence_meetOG.teleport = false + playerSequence_meetOG.attachment = "REF" + playerSequence_meetOG.firstPersonAnim = "pov_intro_scene_player" + playerSequence_meetOG.thirdPersonAnim = "pt_intro_scene_player" + playerSequence_meetOG.viewConeFunction = TrainingPod_ViewConeLock_SemiStrict + playerSequence_meetOG.renderWithViewModels = true + + // HACK delay sequence so it transitions better from previous anim + TrainingPod_ViewConeLock_SemiStrict( player ) // set this to cover any time between viewmodel anims + thread FirstPersonSequence_Delayed( viewAnimDelay, playerSequence_meetOG, player, animEnt ) + //thread FirstPersonSequence( playerSequence_meetOG, player, animEnt ) + + // gradual DOF racking during the scene + RackDoF_NearDepth( player, 0, 22, 12.0 ) + RackDoF_FarDepth( player, 350, 950, 20.0 ) + + // find longest anim duration + array<string> sceneAnims = [ anim_og, anim_bt, anim_anderson, anim_redshirt ]//, "pt_intro_scene_player" ] + float sceneDuration = -1 + foreach ( anim in sceneAnims ) + { + entity animActor = og + if ( anim.find( "BT_") != null ) + animActor = bt + else if ( anim.find( "_player") != null ) + animActor = player + + float duration = animActor.GetSequenceDuration( anim ) + if ( duration > sceneDuration ) + sceneDuration = duration + } + Assert( sceneDuration > 0 ) + + float fadeTime = SP_LEVEL_TRANSITION_FADETIME + + wait sceneDuration - fadeTime + FlagSet( "CadillacMoment_MeetOG_StartFadeOut" ) + + wait fadeTime + FlagSet( "CadillacMoment_MeetOG_Done" ) +} + +void function FirstPersonSequence_Delayed( float delay, FirstPersonSequenceStruct sequence, entity player, entity animEnt ) +{ + EndSignal( player, "OnDestroy" ) + + if ( delay > 0 ) + wait delay + + thread FirstPersonSequence( sequence, player, animEnt ) +} + +void function MeetOG_LoudspeakerVO( entity player ) +{ + EndSignal( player, "OnDestroy" ) + FlagWait( "SimPodShutdown_LoudspeakerVO_Done" ) + + array<string> aliases + // "Incoming hostile ship - Designation: IMS Malta. Battle stations." + aliases.append( "outro_08" ) + // "Caution - Fuel leak in Cargo Bay eighty - five. Activating airlock procedures." + aliases.append( "outro_12" ) + // "Titan bays two through five, drop clearance confirmed." + aliases.append( "outro_03" ) + // "All available medical personnel, report to Med Central for tasking." + aliases.append( "outro_05" ) + // "We need all Riflemen to docking bay four. Dropships standing by." + aliases.append( "outro_07" ) + // "Infantry teams Stork three, Elk four, Shark six, Badger one, prepare for emergency atmospheric drop sequence" + aliases.append( "outro_04" ) + // "Titan mech team - Rabbit six, prep the Vanguards." + aliases.append( "outro_06" ) + // "Infantry teams 2nd Militia Fusiliers, Raptor three, target the IMS Malta." + aliases.append( "outro_11" ) + // "Special Recon Squad deploy from Drop Bay thirty-seven - now." + aliases.append( "outro_10" ) + + thread LoopLoudspeakerVO( aliases ) +} + +void function AnimEventCallback_MeetOG_PlayerCatchesWeapon( entity player ) +{ + Assert( IsValid( file.playerAnimWeapon ) ) + file.playerAnimWeapon.RenderWithViewModels( true ) + + thread MeetOG_PlayerCatchesWeapon_RackNearDOF( player ) +} + +void function MeetOG_PlayerCatchesWeapon_RackNearDOF( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + wait 0.7 + + RackDOF_NearDepth_ToDefault( player, 1.5 ) + + wait 2.0 + + RackDoF_NearDepth( player, 0, 22, 1.25 ) +} + + +// ------ OG HELMET TURNING ON ------ +void function AnimEventCallback_MeetOG_HelmetTurnsOn( entity og ) +{ + thread MeetOG_HelmetTurnsOn( og ) +} + +// script MeetOG_HelmetTurnsOn( GetOGPilot() ) +void function MeetOG_HelmetTurnsOn( entity og ) +{ + entity og = file.ogPilot + entity ogHelmet = file.ogHelmet + EndSignal( og, "OnDestroy" ) + EndSignal( ogHelmet, "OnDestroy" ) + + // setting his team to friendly makes the helmet light up + if ( og.GetTeam() == TEAM_MILITIA ) + SetTeam( og, TEAM_SPECTATOR ) + + ogHelmet.Hide() + Training_OGPilot_SetHelmetOn( og, true ) + wait 0.2 + + SetTeam( og, TEAM_MILITIA ) + wait 0.1 + SetTeam( og, TEAM_SPECTATOR ) + wait 0.4 + SetTeam( og, TEAM_MILITIA ) + wait 0.1 + SetTeam( og, TEAM_SPECTATOR ) + wait 0.1 + SetTeam( og, TEAM_MILITIA ) +} + +#if DEV +void function MeetOG_HelmetTurnsOff( entity og ) +{ + entity og = file.ogPilot + entity ogHelmet = file.ogHelmet + + if ( og.GetTeam() == TEAM_SPECTATOR ) + SetTeam( og, TEAM_MILITIA ) + + ogHelmet.Show() + Training_OGPilot_SetHelmetOn( og, false ) +} +#endif + + +// ------ POD OUTRO BACKGROUND SKITS ------ +void function MeetOG_BackgroundSkits( entity player, float delay = 0.0 ) +{ + string endFlag = "CadillacMoment_MeetOG_Done" + FlagEnd( endFlag ) + + if ( delay > 0 ) + wait delay + + printt( "!!!! Pod Outro Background Skits START !!!!" ) + + thread PodOutro_TitanRacks( endFlag ) + + thread PodOutro_Background_Runners( player ) + thread PodOutro_Foreground_Runners( player ) + + thread PodOutro_Background_ATC_Marvins() + + OnThreadEnd( + function() : () + { + DeleteAllSkitGuys() + } + ) + + WaitForever() +} + +void function PodOutro_TitanRacks( string endFlag ) +{ + FlagEnd( endFlag ) + + array<HangarTitanGroup> hangarTitanGroups + + OnThreadEnd( + function() : ( hangarTitanGroups ) + { + foreach ( group in hangarTitanGroups ) + HangarTitanGroup_Cleanup( group ) + } + ) + + float wait_earlyEnd = 24.0 + bool cleanupAfterAnim = false + + // --- BAY 1 --- + HangarTitanGroup bay1 + entity rack_bay1 = GetEntByScriptName( "hangar_titan_rack_1" ) + bay1.ref = rack_bay1 + bay1.rack = rack_bay1 + bay1.titan = GetEntByScriptName( "hangar_titan_1" ) + bay1.titanAnim = "bt_rack_prep_titan2" + bay1.rackAnim = "rack_rack_prep_rack2" + bay1.marvinAnim = "mv_rack_prep_marvin2" + bay1.pilotAnim = "pt_rack_prep_pilot2" + bay1.pilotModel = PILOT_MODEL_BAY1 + HangarTitanGroup_Init( bay1 ) + hangarTitanGroups.append( bay1 ) + thread HangarTitanGroup_Animate( bay1, endFlag, -1, cleanupAfterAnim ) + + + // --- BAY 2 --- + //thread PodOutro_TitanBoot( endFlag, 15.0 ) + + HangarTitanGroup bay2 + entity rack_bay2 = GetEntByScriptName( "hangar_titan_rack_2" ) + bay2.ref = rack_bay2 + bay2.rack = rack_bay2 + bay2.titan = GetEntByScriptName( "hangar_titan_2" ) + bay2.titanAnim = "bt_rack_prep_titan1" + bay2.rackAnim = "rack_rack_prep_rack1" + bay2.marvinAnim = "mv_rack_prep_marvin1" + bay2.pilotAnim = "pt_rack_prep_pilot1" + bay2.pilotModel = PILOT_MODEL_BAY2 + HangarTitanGroup_Init( bay2 ) + hangarTitanGroups.append( bay2 ) + waitthread HangarTitanGroup_Animate( bay2, endFlag, -1, cleanupAfterAnim ) +} + +void function PodOutro_TitanBoot( string endFlag, float delay ) +{ + FlagEnd( endFlag ) + + entity refTitan = GetEntByScriptName( "hangar_titan_2" ) + refTitan.Hide() + + vector refOrg = refTitan.GetOrigin() + vector refAng = refTitan.GetAngles() + refOrg += < -20, 40, 0> // HACK + refOrg = OriginToGround( refOrg ) + + entity animref = CreateScriptRef( refOrg, refAng ) + + entity titan = CreatePropScript( BUDDY_MODEL, refOrg, refAng ) + titan.DisableHibernation() + + AddAnimEvent( titan, "hatch_closed", TitanBoot_HatchClosed ) + entity cockpitLightFX = PlayFXOnEntity( FX_COCKPIT_LIGHT, titan, "HIJACK" ) + + SkitGuyInfo titanInfo = AddSkitGuy_Manually( "bootup_titan", titan ) + + SkitGuyInfo titanPilotInfo = SpawnSkitGuy( "bootup_pilot", "", animref.GetOrigin(), animref.GetAngles(), TEAM_MILITIA, "npc_soldier" ) + entity titanPilot = titanPilotInfo.guy + titanPilot.SetParent( titan, "HIJACK", false, 0.0 ) + titanPilot.MarkAsNonMovingAttachment() + + SkitGuyInfo crewInfo = SpawnSkitGuy( "bootup_crew", "", animref.GetOrigin(), animref.GetAngles(), TEAM_MILITIA, "npc_soldier_bish" ) + entity crew = crewInfo.guy + + OnThreadEnd( + function() : ( titanPilotInfo, crewInfo, titanInfo, cockpitLightFX, animref, refTitan ) + { + if ( IsValid_ThisFrame( cockpitLightFX ) ) + { + EntFireByHandle( cockpitLightFX, "Stop", "", 0, null, null ) + cockpitLightFX.ClearParent() + cockpitLightFX.Destroy() + } + + if ( IsAlive( titanInfo.guy ) ) + StopSoundOnEntity( titanInfo.guy, "Wargames_MCOR_TitanActivate" ) + + if ( IsValid( animref ) ) + animref.Destroy() + + DeleteSkitGuy( titanPilotInfo ) + DeleteSkitGuy( crewInfo ) + DeleteSkitGuy( titanInfo ) + + if ( IsValid( refTitan ) ) + refTitan.Show() + } + ) + + EmitSoundOnEntity( titan, "Wargames_MCOR_TitanActivate" ) + + string pilotAnim = "pt_titan_activation_pilot" + string pilotAnim_idle = "pt_titan_activation_pilot_idle" + string titanAnim = "at_titan_activation_training_meetOG" + string titanAnim_idle = "at_titan_activation_idle" + string titanAnim_endIdle = "at_titan_activation_training_meetOG_end_idle" + string crewAnim = "pt_titan_activation_crew" + string crewAnim_idle = "pt_titan_activation_crew_idle" + + if ( delay > 0 ) + { + thread PlayAnim( crew, crewAnim_idle, animref, null, 0.0 ) + thread PlayAnim( titan, titanAnim_idle, animref, null, 0.0 ) + titanPilot.Anim_ScriptedPlay( pilotAnim_idle ) + titanPilot.Anim_EnableUseAnimatedRefAttachmentInsteadOfRootMotion() + + wait delay + + titanPilot.Anim_Stop() + crew.Anim_Stop() + titan.Anim_Stop() + } + + titanPilot.Anim_ScriptedPlay( pilotAnim ) + titanPilot.Anim_EnableUseAnimatedRefAttachmentInsteadOfRootMotion() + thread PlayAnim( crew, crewAnim, animref, null, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME ) + thread PlayAnim( titan, titanAnim, animref, null, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME ) + + // end it early so he doesn't make stomping sounds when the pod is closed + //wait 15.0 + float duration = titan.GetSequenceDuration( titanAnim ) + wait duration - 0.1 + + printt( "Ending titan boot anim" ) + + thread PlayAnim( titan, titanAnim_endIdle, animref, null, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME ) + crew.Freeze() + + WaitForever() +} + +void function TitanBoot_HatchClosed( entity titan ) +{ + SkitGuyInfo info = GetSkitGuyInfo_ByName( "bootup_pilot" ) + DeleteSkitGuy( info ) +} + +void function PodOutro_Background_Runners( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + // right to left, across the whole hangar + array<Point> path1 = [] + ScriptedPath_AddPoint( path1, < 11080.5, -10017.2, -6079.97 >, < 0, 177.587, 0 > ) + ScriptedPath_AddPoint( path1, < 9619.14, -10004.8, -6079.97 >, < 0, 183.021, 0 > ) + + // right to left, starts closer to player POV + array<Point> path2 = [] + ScriptedPath_AddPoint( path2, < 11000, -9946.33, -6079.97 >, < 0, 177.587, 0 > ) + ScriptedPath_AddPoint( path2, < 9619.14, -10004.8, -6079.97 >, < 0, 183.021, 0 > ) + + wait 2.0 // wait for OG's first line to be nearly over + + printt( "BACKGROUND RUNNER GROUP 1" ) + + thread SpawnSkitGuy_AndRun( "runner_1_1", path2, 0.85, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_hemlok_smg" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "runner_1_2", path2, 0.85, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + + wait 6.5 // wait until OG hands the eye to BT + + printt( "BACKGROUND RUNNER GROUP 2" ) + + thread SpawnSkitGuy_AndRun( "runner_2_1", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "runner_2_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" ) + + wait 4.5 // wait until OG is shaking Anderson's hand + + printt( "BACKGROUND RUNNER GROUP 3" ) + + thread SpawnSkitGuy_AndRun( "runner_3_1", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "runner_3_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "runner_3_3", path2, 0.8, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_hemlok_smg" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "runner_3_4", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + + wait 4.5 // wait until OG is mounting up + + printt( "BACKGROUND RUNNER GROUP 4" ) + + thread SpawnSkitGuy_AndRun( "runner_4_1", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "runner_4_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" ) + + wait 6.5 // wait until OG thumbs up + + printt( "BACKGROUND RUNNER GROUP 5" ) + + thread SpawnSkitGuy_AndRun( "runner_5_1", path2, 0.85, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_hemlok_smg" ) + wait 1.2 + thread SpawnSkitGuy_AndRun( "runner_5_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "runner_5_3", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" ) + wait 1.5 + thread SpawnSkitGuy_AndRun( "runner_5_4", path2, 0.85, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "runner_5_3", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" ) + wait 1.5 + thread SpawnSkitGuy_AndRun( "runner_5_4", path2, 0.85, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + + /* + wait 5.0 + + wait 4.0 + + thread SpawnSkitGuy_AndRun( "runner_2_1", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "runner_2_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" ) + */ +} + +void function PodOutro_Foreground_Runners( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + // right in front of pod + array<Point> pathClose = [] + ScriptedPath_AddPoint( pathClose, < 10728.2, -10194.2, -6055.97 >, < 0, 178.246, 0 > ) + ScriptedPath_AddPoint( pathClose, < 10236.8, -10180.3, -6055.97 >, < 0, 178.41, 0 > ) + + wait 11.0 // wait for OG to hand eyeball to BT + + thread SpawnSkitGuy_AndRun( "close_runner_1", pathClose, 0.7, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.2 + thread SpawnSkitGuy_AndRun( "close_runner_2", pathClose, 0.75, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + + wait 10.0 // wait for OG to start embarking BT + + thread SpawnSkitGuy_AndRun( "close_runner_4", pathClose, 0.75, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.3 + thread SpawnSkitGuy_AndRun( "close_runner_5", pathClose, 0.8, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_g2" ) + + wait 11 // wait for grunt to hand player the weapon + + thread SpawnSkitGuy_AndRun( "close_runner_10", pathClose, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.8 + thread SpawnSkitGuy_AndRun( "close_runner_11", pathClose, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) + wait 1.0 + thread SpawnSkitGuy_AndRun( "close_runner_12", pathClose, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" ) +} + +void function PodOutro_Background_ATC_Marvins() +{ + // 1, 2, 3- matches titan bay numbering (right to left) + // plain numbers = farther from camera + // "a" variants = closer to camera + //SkitGuyInfo marvin_1 = SpawnSkitGuy( "atc_marvin_1", "", < 10600, -9841.5, -6079.97 >, < 0, -20, 0 >, TEAM_MILITIA ) + //thread ATC_Marvin_Think( marvin_1, "mv_trafic_controller_A", 0.0 ) + //SkitGuyInfo marvin_1a = SpawnSkitGuy( "atc_marvin_1a", "", < 10683.8, -10077.7, -6094.65 >, < 0, 20, 0 >, TEAM_MILITIA ) + //thread ATC_Marvin_Think( marvin_1a, "mv_trafic_controller_B", 0.0 ) + SkitGuyInfo marvin_2 = SpawnSkitGuy( "atc_marvin_2", "", < 10460.6, -9861.5, -6079.97 >, < 0, -30, 0 >, TEAM_MILITIA ) + thread ATC_Marvin_Think( marvin_2, "mv_trafic_controller_B", 0.25 ) + //SkitGuyInfo marvin_2a = SpawnSkitGuy( "atc_marvin_2a", "", < 10410.6, -10075.6, -6079.97 >, < 0, 30, 0 >, TEAM_MILITIA ) + //thread ATC_Marvin_Think( marvin_2a, "mv_trafic_controller_A", 0.25 ) + SkitGuyInfo marvin_3 = SpawnSkitGuy( "atc_marvin_3", "", < 10214.8, -9891.5, -6079.97 >, < 0, -10, 0 >, TEAM_MILITIA ) + thread ATC_Marvin_Think( marvin_3, "mv_trafic_controller_A", 0.5 ) + SkitGuyInfo marvin_3a = SpawnSkitGuy( "atc_marvin_3a", "", < 10207.5, -10079.4, -6079.97 >, < 0, 10, 0 >, TEAM_MILITIA ) + thread ATC_Marvin_Think( marvin_3a, "mv_trafic_controller_B", 0.5 ) +} + +void function ATC_Marvin_Think( SkitGuyInfo marvinInfo, string anim, float skipAheadTime = 0.0 ) +{ + entity marvin = marvinInfo.guy + + EndSignal( marvin, "OnDestroy" ) + + entity batonRight = CreatePropDynamic( SAFETY_BATON_MODEL ) + batonRight.SetOrigin( marvin.GetAttachmentOrigin( marvin.LookupAttachment( "R_HAND" ) ) ) + // HACK adjust the angles since the tags are different + vector angs = marvin.GetAttachmentAngles( marvin.LookupAttachment( "R_HAND" ) ) + Vector( 0, 0, 180 ) + batonRight.SetAngles( angs ) + batonRight.SetParent( marvin, "R_HAND", true ) + + entity batonLeft = CreatePropDynamic( SAFETY_BATON_MODEL ) + batonLeft.SetParent( marvin, "L_HAND" ) + + marvin.NotSolid() // don't want other NPCs to path around the ATC marvins + + array<entity> cleanupEnts = [ batonRight, batonLeft ] + + OnThreadEnd( + function() : ( cleanupEnts ) + { + foreach ( ent in cleanupEnts ) + { + if ( IsValid( ent ) ) + { + ent.ClearParent() + ent.Destroy() + } + } + } + ) + + WaitFrame() + marvinInfo.skitAnim = anim + /* + array<string> anims = [ "mv_trafic_controller_A", "mv_trafic_controller_B" ] + while( 1 ) + { + marvin.Anim_Play( anims.getrandom() ) + marvin.SetPlaybackRate( RandomFloatRange( 0.9, 1.0 ) ) + waitthread WaittillAnimDone( marvin ) + } + */ + marvin.SetPlaybackRate( RandomFloatRange( 0.9, 1.0 ) ) + SkitGuy_PlayAnim( marvinInfo, skipAheadTime ) + + WaitForever() +} + + + +// ======================================= +// ============ GAUNTLET MODE ============ +// ======================================= +// Just runs the Gauntlet over and over. + +void function Training_Setup_GauntletMode( entity player ) +{ + Training_EnvArtColorCorrection_SetEnabled( true ) + + entity ogStart = GetEntByScriptName( "og_near_leaderboard" ) + entity og = Training_SpawnOGPilot( ogStart ) + Training_OG_Idles( ogStart, "OG_Leaderboard_D_idle" ) + + //TeleportPlayerAndBT( "playerstart_gauntlet_challenge" ) + // HACK better start position + player.SetOrigin( < -5179, 279.129, 32.0313 > ) + player.SetAngles( < 0, 80.7242, 0 > ) +} + +void function Training_GauntletModeStart( entity player ) +{ + file.gauntletMode = true + + waitthread Training_GauntletChallenge( player ) +} + +void function GauntletMode_Finished( entity player ) +{ + Assert( file.gauntletMode ) + + float fadeTime = 3.0 + ScreenFade( player, 0, 0, 0, 255, fadeTime, -1, FFADE_OUT | FFADE_STAYOUT ) + wait fadeTime + + // Dump player back to menu + ClientCommand( player, "disconnect" ) + + WaitForever() // defensive- don't want any extra stuff happening right before level transition +} + + + +#if DEV +// =============================================== +// ============ RECORD GAUNTLET GHOSTS =========== +// =============================================== +void function TrainingGauntlet_RecordGhostStart_FirstRun( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_FIRSTRUN ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_WIP( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_WIP ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_01( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_01 ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_02( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_02 ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_03( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_03 ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_04( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_04 ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_05( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_05 ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_06( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_06 ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_07( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_07 ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_08( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_08 ) +} + +void function TrainingGauntlet_RecordGhostStart_Challenge_09( entity player ) +{ + TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_09 ) +} + + +void function TrainingGauntlet_RecordGhost_CommonStart( entity player, GauntletInfo gauntlet, string ghostFileName ) +{ + thread TrainingGauntlet_TeleportPlayerAtFinishLine( player ) + + TeleportPlayerAndBT( "gauntlet_startpoint" ) + + Gauntlet_Player_GhostRecordOrPlayback( player, gauntlet, ghostFileName ) + + while ( 1 ) + wait 5 +} +#endif //DEV + + + +// ============================================ +// ============ TRAINING POD STUFF ============ +// ============================================ +void function SetupTrainingPod() +{ + file.trainingPod = GetEntByScriptName( "training_pod" ) + file.trainingPod.DisableHibernation() + + TrainingPod_SetupInteriorDLights() + + array<string> laserAttachNames = [ "fx_laser_L", "fx_laser_R" ] + + foreach ( attachName in laserAttachNames ) + { + entity emitterEnt = CreateScriptMover( file.trainingPod.GetOrigin() ) + int attachID = file.trainingPod.LookupAttachment( attachName ) + vector attachAng = file.trainingPod.GetAttachmentAngles( attachID ) + + TrainingPod_LaserEmitter emitter + emitter.ent = emitterEnt + emitter.attachName = attachName + emitter.ogAng = attachAng + + file.trainingPodLaserEmitters.append( emitter ) + } + + // HACK we do this later as well to reset the emitter positions, so it's a separate function + TrainingPod_SnapLaserEmittersToAttachPoints() + + //file.trainingPod.SetAngles( Vector( 0, 109, 0 ) ) // these angles are a little better for seeing the room +} + +void function TrainingPod_SetupInteriorDLights() +{ + entity pod = file.trainingPod + + TrainingPod_dLightMapping m1 + m1.scriptAlias = "console1" + m1.fxName = FX_POD_DLIGHT_CONSOLE1 + m1.attachName = "light_console1" + file.trainingPodDLightMappings.append( m1 ) + + TrainingPod_dLightMapping m2 + m2.scriptAlias = "console2" + m2.fxName = FX_POD_DLIGHT_CONSOLE2 + m2.attachName = "light_console2" + file.trainingPodDLightMappings.append( m2 ) + + //TrainingPod_dLightMapping m3 + //m3.scriptAlias = "backlight_side_L" + //m3.fxName = FX_POD_DLIGHT_BACKLIGHT_SIDE + //m3.attachName = "light_back1" + //file.trainingPodDLightMappings.append( m3 ) + + //TrainingPod_dLightMapping m4 + //m4.scriptAlias = "backlight_side_R" + //m4.fxName = FX_POD_DLIGHT_BACKLIGHT_SIDE + //m4.attachName = "light_back2" + //file.trainingPodDLightMappings.append( m4 ) + + //TrainingPod_dLightMapping m5 + //m5.scriptAlias = "backlight_top" + //m5.fxName = FX_POD_DLIGHT_BACKLIGHT_TOP + //m5.attachName = "light_backtop" + //file.trainingPodDLightMappings.append( m5 ) +} + +void function TrainingPod_TurnOnInteriorDLights_Delayed( entity player, float delay ) +{ + player.EndSignal( "OnDestroy" ) + + wait delay + + TrainingPod_TurnOnInteriorDLight( "console1" ) + TrainingPod_TurnOnInteriorDLight( "console2" ) +} + +void function TrainingPod_TurnOnInteriorDLight( string scriptAlias ) +{ + entity pod = file.trainingPod + + int idx + TrainingPod_dLightMapping thisMapping + foreach ( mappingIdx, mapping in file.trainingPodDLightMappings ) + { + if ( mapping.scriptAlias == scriptAlias ) + { + thisMapping = mapping + idx = mappingIdx + break + } + } + + Assert ( thisMapping.scriptAlias != "", "Couldn't find pod dlight mapping for alias " + scriptAlias ) + + entity fxHandle = PlayLoopFXOnEntity( thisMapping.fxName, pod, thisMapping.attachName ) + file.trainingPodDLightMappings[ idx ].fxHandle = fxHandle +} + +void function TrainingPod_KillInteriorDLights_Delayed( entity player, float delay ) +{ + player.EndSignal( "OnDestroy" ) + + wait delay + + TrainingPod_KillInteriorDLights() +} + +void function TrainingPod_KillInteriorDLights() +{ + foreach ( idx, mapping in file.trainingPodDLightMappings ) + { + if ( !IsValid_ThisFrame( mapping.fxHandle ) ) + continue + + KillFX( mapping.fxHandle ) + + file.trainingPodDLightMappings[ idx ].fxHandle = null + } +} + +void function TrainingPod_SnapLaserEmittersToAttachPoints() +{ + foreach ( TrainingPod_LaserEmitter emitter in file.trainingPodLaserEmitters ) + { + int attachID = file.trainingPod.LookupAttachment( emitter.attachName ) + vector attachOrg = file.trainingPod.GetAttachmentOrigin( attachID ) + vector attachAng = file.trainingPod.GetAttachmentAngles( attachID ) + + emitter.ent.ClearParent() + emitter.ent.SetOrigin( attachOrg ) // HACK set this to ANYTHING (even 0, 0, 0) and the position is correct, otherwise it's offset from the attachpoint when parented + emitter.ent.SetParent( file.trainingPod, emitter.attachName ) + } +} + +void function TrainingPod_Interior_BootSequence( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + entity pod = file.trainingPod + + TrainingPod_InteriorFX_CommonSetup( pod ) + + FlagSet( "PodIntro_InteriorBootSequence_Starting" ) + + EmitSoundOnEntity( player, "NPE_Scr_SimPod_PowerUp" ) + + // Transition screen FX + thread PlayFXOnEntity_Delayed( player, FX_POD_SCREEN_IN, player, 2.35 ) + + // GLOW LIGHTS + TrainingPod_GlowLightsTurnOn() + + // LASERS + float longestSweepTime = -1 + foreach ( emitter in file.trainingPodLaserEmitters ) + { + float sweepTime = RandomFloatRange( 2.9, 3.15 ) + if ( sweepTime > longestSweepTime ) + longestSweepTime = sweepTime + + thread LaserSweep( player, sweepTime, emitter, pod, "top" ) + } + + wait longestSweepTime + + player.Signal( "PodInteriorSequenceDone" ) +} + +void function TrainingPod_InteriorFX_CommonSetup( entity pod ) +{ + if ( file.trainingPodLaserEmitters.len() ) + { + TrainingPod_KillLasers( pod ) + TrainingPod_ResetLaserEmitterRotation( pod ) + } + + TrainingPod_KillGlowFX( pod ) +} + +// NOTE startPosition is actually inverted from what I think it should be. Tag orientation issue, maybe? +void function LaserSweep( entity player, float totalTime, TrainingPod_LaserEmitter emitter, entity pod, string startPosition = "bottom" ) +{ + //float startTime = Time() + + player.EndSignal( "OnDestroy" ) + emitter.ent.EndSignal( "OnDeath" ) + + emitter.sweepDone = false + + //printt( "emitter og angles:", emitter.GetAngles() ) + + vector vecToPlayerEye = ( player.EyePosition() + Vector( 0, 0, 7 ) ) - emitter.ent.GetOrigin() // eye position offset is a HACK, not sure why I need to do that here. + vector centerAng = VectorToAngles( vecToPlayerEye ) + vector topAng = centerAng + Vector( -270, 0, 0 ) + vector bottomAng = centerAng + Vector( -90, 0, 0 ) + + //vector topAng = emitter.GetAngles() + < 90, -8, 0 > + //vector bottomAng = emitter.GetAngles() + < -90, 8, 0 > + + //printt( "==== starting at:", startPosition ) + //printt( "topAng:", topAng ) + //printt( "bottomAng:", bottomAng ) + //printt( "centerAng:", centerAng ) + + vector lastBigSweepAng + + if ( startPosition == "bottom") + { + emitter.ent.SetAbsAngles( bottomAng ) + lastBigSweepAng = bottomAng + } + else + { + emitter.ent.SetAbsAngles( topAng ) + lastBigSweepAng = topAng + } + //printt( "setting start angles to:", lastBigSweepAng ) + + entity fxHandle = PlayLoopFXOnEntity( FX_POD_LASER, emitter.ent ) + emitter.fxHandle = fxHandle + + int numBigSweeps = 2 + float finalCenterTime = totalTime * 0.15 + float bigSweepTime = ( totalTime - finalCenterTime ) / numBigSweeps + + float bigSweep_AccelTime = 0 + float bigSweep_DecelTime = bigSweepTime * 0.2 + + // do the big sweeps + vector nextBigSweepAng + for ( int i = 0; i < numBigSweeps; i++ ) + { + nextBigSweepAng = topAng + if ( lastBigSweepAng == topAng ) + nextBigSweepAng = bottomAng + + //printt( "rotating to", nextBigSweepAng ) + + emitter.ent.NonPhysicsRotateTo( nextBigSweepAng, bigSweepTime, bigSweep_AccelTime, bigSweep_DecelTime ) + + float waitTime = bigSweepTime + if ( i < numBigSweeps - 1 ) + waitTime = bigSweepTime - 0.1 + + wait waitTime + + lastBigSweepAng = nextBigSweepAng + } + + // finish with centering move + //printt( "centering to", centerAng ) + + float finalCenter_AccelTime = 0 + float finalCenter_DecelTime = finalCenterTime * 0.2 + + emitter.ent.NonPhysicsRotateTo( centerAng, finalCenterTime, finalCenter_AccelTime, finalCenter_DecelTime ) + wait finalCenterTime + + emitter.sweepDone = true + //printt( "laser sweep done, total time", Time() - startTime, "should have been", totalTime ) +} + +void function TrainingPod_KillLasers( entity pod, bool doEndCap = false ) +{ + foreach ( emitter in file.trainingPodLaserEmitters ) + { + if ( IsValid_ThisFrame( emitter.fxHandle ) ) + { + if ( !doEndCap ) + { + //printt( "killing laser FX", emitter.fxHandle ) + KillFX( emitter.fxHandle ) + } + else + { + //printt( "killing laser FX with endcap", emitter.fxHandle ) + KillFXWithEndcap( emitter.fxHandle ) + } + } + + emitter.fxHandle = null + } +} + +void function TrainingPod_ResetLaserEmitterRotation( entity pod ) +{ + if ( !file.trainingPodLaserEmitters.len() ) + return + + foreach ( emitter in file.trainingPodLaserEmitters ) + { + //reset to start position + emitter.ent.NonPhysicsRotateTo( emitter.ogAng, 0.05, 0.0, 0.0 ) + } +} + +void function TrainingPod_GlowLightsArraySetup() +{ + array<TrainingPod_GlowLightRow> rows + + // rows are set up bottom to top + // lights are set up outside to in (in = door close seam; opposite for each side) + // process two rows per loop (one for each door side) + + TrainingPod_GlowLightRow row1 + row1.fxSpotsL = [ "fx_glow_L_door012", "fx_glow_L_door013" ] + row1.fxSpotsR = [ "fx_glow_R_door014", "fx_glow_R_door013" ] + rows.append( row1 ) + + TrainingPod_GlowLightRow row2 + row2.fxSpotsL = [ "fx_glow_L_door014", "fx_glow_L_door011" ] + row2.fxSpotsR = [ "fx_glow_R_door012", "fx_glow_R_door011" ] + rows.append( row2 ) + + TrainingPod_GlowLightRow row3 + row3.fxSpotsL = [ "fx_glow_L_door09", "fx_glow_L_door010" ] + row3.fxSpotsR = [ "fx_glow_R_door09", "fx_glow_R_door010" ] + rows.append( row3 ) + + TrainingPod_GlowLightRow row4 + row4.fxSpotsL = [ "fx_glow_L_door07", "fx_glow_L_door08" ] + row4.fxSpotsR = [ "fx_glow_R_door07", "fx_glow_R_door08" ] + rows.append( row4 ) + + TrainingPod_GlowLightRow row5 + row5.fxSpotsL = [ "fx_glow_L_door05", "fx_glow_L_door06" ] + row5.fxSpotsR = [ "fx_glow_R_door05", "fx_glow_R_door06" ] + rows.append( row5 ) + + TrainingPod_GlowLightRow row6 + row6.fxSpotsL = [ "fx_glow_L_door03", "fx_glow_L_door04" ] + row6.fxSpotsR = [ "fx_glow_R_door03", "fx_glow_R_door04" ] + rows.append( row6 ) + + TrainingPod_GlowLightRow row7 + row7.fxSpotsL = [ "fx_glow_L_door01", "fx_glow_L_door02" ] + row7.fxSpotsR = [ "fx_glow_R_door01", "fx_glow_R_door02" ] + rows.append( row7 ) + + file.trainingPodGlowLightRows = rows +} + +void function TrainingPod_GlowLightsTurnOn( bool instantOn = false ) +{ + //float startTime = Time() + + entity pod = file.trainingPod + + foreach ( TrainingPod_GlowLightRow row in file.trainingPodGlowLightRows ) + { + float loopTime = Time() + + array<string> group1 = [ row.fxSpotsL[0], row.fxSpotsR[0] ] + array<string> group2 = [ row.fxSpotsL[1], row.fxSpotsR[1] ] + table< int, array < string > > lightgroups + lightgroups[0] <- group1 + lightgroups[1] <- group2 + + foreach ( idx, group in lightgroups ) + { + foreach ( attachName in group ) + { + entity fxHandle = PlayLoopFXOnEntity( FX_POD_GLOWLIGHT, pod, attachName ) + file.trainingPodGlowLightFXHandles.append( fxHandle ) + } + + if ( !instantOn ) + wait 0.1 + } + + /* + // both sides have same number of lights + int numLights = 2 + for ( int i = 0; i < numLights; i++ ) + { + foreach ( var side in row ) + { + string attachName = side[ i ] + entity fxHandle = PlayLoopFXOnEntity( FX_POD_GLOWLIGHT, pod, attachName ) + file.trainingPodGlowLightFXHandles.append( fxHandle ) + } + + if ( lightWait > 0 ) + wait lightWait + } + + if ( rowWait > 0) + wait rowWait + */ + } + + //printt( "glow lights turn on took", Time() - startTime, "secs" ) +} + +void function TrainingPod_KillGlowFX( entity pod ) +{ + foreach ( fxHandle in file.trainingPodGlowLightFXHandles ) + { + if ( !IsValid_ThisFrame( fxHandle ) ) + continue + + KillFX( fxHandle ) + } + + file.trainingPodGlowLightFXHandles = [] +} + +void function TrainingPod_Interior_ShutdownSequence( entity player, float shutdownTime ) +{ + player.EndSignal( "OnDestroy" ) + + entity pod = file.trainingPod + + TrainingPod_InteriorFX_CommonSetup( pod ) + + // TURN ON GLOW LIGHTS + TrainingPod_GlowLightsTurnOn( true ) + + // TURN ON LASERS + TrainingPod_LasersInstantOn( player, pod ) + + player.WaitSignal( "TrainingPod_BeginInteriorShutdown" ) + + thread TrainingPod_LasersShutDown( player, pod, shutdownTime * 0.6 ) + thread TrainingPod_GlowLightsShutDown( player, pod, shutdownTime ) + + wait shutdownTime + printt( "interior shutdown done" ) +} + +void function TrainingPod_LasersInstantOn( entity player, entity pod ) +{ + foreach ( emitter in file.trainingPodLaserEmitters ) + { + float dist = Distance( player.EyePosition(), emitter.ent.GetOrigin() ) + Assert( dist <= 30, "player is usually about 20 units away when we try to set the laser angles. If very far away, the math will crash the game. Dist: " + dist ) + + vector vecToPlayerEye = ( player.EyePosition() + Vector( 0, 0, 7 ) ) - emitter.ent.GetOrigin() // eye position offset is a HACK, not sure why I need to do that here. + vector centerAng = VectorToAngles( vecToPlayerEye ) + emitter.ent.NonPhysicsRotateTo( centerAng, 0.1, 0.0, 0.0 ) // SETANGLES DOES NOT WORK! You have to rotate it for the FX to follow. + + emitter.fxHandle = PlayLoopFXOnEntity( FX_POD_LASER, emitter.ent ) + } +} + +void function TrainingPod_LasersShutDown( entity player, entity pod, float shutdownTime ) +{ + player.EndSignal( "OnDestroy" ) + + foreach ( emitter in file.trainingPodLaserEmitters ) + { + vector vecToPlayerEye = ( player.EyePosition() + Vector( 0, 0, 7 ) ) - emitter.ent.GetOrigin() // eye position offset is a HACK, not sure why I need to do that here. + vector centerAng = VectorToAngles( vecToPlayerEye ) + emitter.ent.NonPhysicsRotateTo( centerAng, 0.1, 0.0, 0.0 ) // SETANGLES DOES NOT WORK! You have to rotate it for the FX to follow. + } + + wait shutdownTime * 0.25 + + float moveDownTime = shutdownTime * 0.75 + float accelTime = moveDownTime * 0.25 + float decelTime = moveDownTime * 0.1 + + foreach ( TrainingPod_LaserEmitter emitter in file.trainingPodLaserEmitters ) + { + vector finalAng = emitter.ent.GetAngles() + Vector( 30, 0, 0 ) // not sure why adding pitch makes them appear to drop down + emitter.ent.NonPhysicsRotateTo( finalAng, moveDownTime, accelTime, decelTime ) + } + + wait moveDownTime + TrainingPod_KillLasers( pod, true ) +} + +void function TrainingPod_GlowLightsShutDown( entity player, entity pod, float shutdownTime ) +{ + player.EndSignal( "OnDestroy" ) + + float shutdownDelay = shutdownTime * 0.65 + float finishEarly = shutdownTime * 0.1 + float glowLightShutDownDuration = shutdownTime - shutdownDelay - finishEarly + + wait shutdownDelay + + Assert( glowLightShutDownDuration > 0.0 ) + + float timePerLight = glowLightShutDownDuration / file.trainingPodGlowLightFXHandles.len().tofloat() + + foreach ( entity fxHandle in file.trainingPodGlowLightFXHandles ) + { + if ( !IsValid_ThisFrame( fxHandle ) ) + continue + + thread KillFXWithEndcap( fxHandle ) + wait timePerLight + } + + file.trainingPodGlowLightFXHandles = [] +} + +void function TrainingPod_ViewConeLock_Shared( entity player ) +{ + player.PlayerCone_FromAnim() + player.PlayerCone_SetMinYaw( -25 ) + player.PlayerCone_SetMaxYaw( 25 ) + player.PlayerCone_SetMinPitch( -30 ) +} + +void function TrainingPod_ViewConeLock_PodOpen( entity player ) +{ + TrainingPod_ViewConeLock_Shared( player ) + player.PlayerCone_SetMaxPitch( 35 ) +} + +void function TrainingPod_ViewConeLock_PodClosed( entity player ) +{ + TrainingPod_ViewConeLock_Shared( player ) + player.PlayerCone_SetMaxPitch( 32 ) +} + +void function TrainingPod_ViewConeLock_SemiStrict( entity player ) +{ + player.PlayerCone_FromAnim() + player.PlayerCone_SetMinYaw( -10 ) + player.PlayerCone_SetMaxYaw( 10 ) + player.PlayerCone_SetMinPitch( -10 ) + player.PlayerCone_SetMaxPitch( 10 ) +} + +void function TrainingPod_ViewConeLock_Strict( entity player ) +{ + player.PlayerCone_FromAnim() + player.PlayerCone_SetMinYaw( 0 ) + player.PlayerCone_SetMaxYaw( 0 ) + player.PlayerCone_SetMinPitch( 0 ) + player.PlayerCone_SetMaxPitch( 0 ) +} + + +// ================================================== +// ============ CLIENT COMMAND CALLBACKS ============ +// ================================================== + +bool function ClientCommand_Training_SetInputType( entity player, array<string> args ) +{ + int inputType = args[0].tointeger() + + Assert( inputType == INPUT_TYPE_CONTROLLER || inputType == INPUT_TYPE_KBM ) + //printt( "Training- client input type updated:", inputType ) + file.playerInputType = inputType + return true +} + +bool function ClientCommand_Training_PlayerPressedUse( entity player, array<string> args ) +{ + FlagSet( "PlayerPressedUse" ) + return true +} + +bool function ClientCommand_Training_PlayerReloaded( entity player, array<string> args ) +{ + FlagSet( "PlayerReloaded" ) + return true +} + +bool function ClientCommand_LookTarget_Top( entity player, array<string> args ) +{ + player.ResetIdleTimer() + printt( "ClientCommand_LookTarget_Top" ) + FlagSet( "PlayerLookedAtTopTarget" ) + return true +} + +bool function ClientCommand_LookTarget_Bottom( entity player, array<string> args ) +{ + player.ResetIdleTimer() + printt( "ClientCommand_LookTarget_Bottom" ) + FlagSet( "PlayerLookedAtBottomTarget" ) + return true +} + + + +// =============================== +// ============ DOORS ============ +// =============================== +void function DoorOpenFast( string doorEntName ) +{ + entity door = GetEntByScriptName( doorEntName ) + door.Hide() + door.NotSolid() + + entity navBlocker = door.GetLinkEnt() + navBlocker.NotSolid() + ToggleNPCPathsForEntity( navBlocker, true ) +} + +void function DoorCloseFast( string doorEntName ) +{ + entity door = GetEntByScriptName( doorEntName ) + door.Show() + door.Solid() + + entity navBlocker = door.GetLinkEnt() + navBlocker.Solid() + ToggleNPCPathsForEntity( navBlocker, false ) +} + + +void function OpenZenGardenExitDoor() +{ + DoorOpenFast( "zengarden_door" ) +} + +void function CloseZenGardenExitDoor() +{ + DoorCloseFast( "zengarden_door" ) +} + +void function OpenGauntletDoor() +{ + DoorOpenFast( "gauntlet_door" ) +} + + + +// =================================== +// ========= LOUDSPEAKER VO ========== +// =================================== +void function LoudspeakerVO_Setup() +{ + vector loudspeakerPos = <10524, -9660, -5896> // HACK + file.loudspeaker = CreateScriptMover( loudspeakerPos, <0,0,0> ) + + // ======= PA ANNOUNCEMENTS: POD INTRO ======= + // "Inbound to Planet Typhon. Subspace rendezvous in approximately 20 minutes." + RegisterLoudspeakerVO( "intro_0", "diag_sp_addtional_TR411_01_mcor_shipPA", 8.0 ) + + // "Major Anderson, please report to the briefing room." + RegisterLoudspeakerVO( "intro_1", "diag_sp_addtional_TR411_02_mcor_grunt1", 3.0 ) + + // "Reminder to dock personnel: Titan ordnance is a Type 3 Hazardous Material." + RegisterLoudspeakerVO( "intro_2", "diag_sp_addtional_TR411_03_mcor_shipPA", 6.0 ) + + // "Captain Cole to communications." + RegisterLoudspeakerVO( "intro_3", "diag_sp_addtional_TR411_04_mcor_grunt1", 3.0 ) + + // "Running Lifeboat diagnostic test two point one. All Mark Eight lifeboats are in the green." + RegisterLoudspeakerVO( "intro_4", "diag_sp_addtional_TR411_05_mcor_shipPA", 6.0 ) + + // "3rd Militia Grenadiers - prep dropship MacAllan 17." + RegisterLoudspeakerVO( "intro_5", "diag_sp_addtional_TR411_06_mcor_grunt1", 4.0 ) + + + // ======= PA ANNOUNCEMENTS: MEET OG ======= + // "Prepare for Typhon atmospheric entry in less than three minutes." + RegisterLoudspeakerVO( "outro_01", "diag_sp_addtional_TR411_07_mcor_shipPA", 7.0 ) + + // "Powering down all non-essential systems." + RegisterLoudspeakerVO( "outro_01_1", "diag_sp_outro_TR171_01_01_mcor_shipPA", 4.0 ) + + // "All personnel to battle stations." + RegisterLoudspeakerVO( "outro_01_2", "diag_sp_outro_TR171_02_01_mcor_shipPA", 3.0 ) + + // "This is not a drill." + RegisterLoudspeakerVO( "outro_01_3", "diag_sp_outro_TR171_03_01_mcor_shipPA", 3.0 ) + + // "Titan bays two through five, drop clearance confirmed." + RegisterLoudspeakerVO( "outro_03", "diag_sp_addtional_TR411_09_mcor_shipPA", 5.0 ) + + // "Infantry teams Stork three, Elk four, Shark six, Badger one, prepare for emergency atmospheric drop sequence" + RegisterLoudspeakerVO( "outro_04", "diag_sp_addtional_TR411_10_mcor_grunt1", 7.0 ) + + // "All available medical personnel, report to Med Central for tasking." + RegisterLoudspeakerVO( "outro_05", "diag_sp_addtional_TR411_11_mcor_grunt2", 5.0 ) + + // "Titan mech team - Rabbit six, prep the Vanguards." + RegisterLoudspeakerVO( "outro_06", "diag_sp_addtional_TR411_12_mcor_grunt3", 4.0 ) + + // "We need all Riflemen to docking bay four. Dropships standing by." + RegisterLoudspeakerVO( "outro_07", "diag_sp_addtional_TR411_13_mcor_grunt1", 5.0 ) + + // "Incoming hostile ship - Designation: IMS Malta. Battle stations." + RegisterLoudspeakerVO( "outro_08", "diag_sp_addtional_TR411_14_mcor_shipPA", 6.0 ) + + // "Special Recon Squad deploy from Drop Bay thirty-seven - now." + RegisterLoudspeakerVO( "outro_10", "diag_sp_addtional_TR411_16_mcor_grunt2", 5.0 ) + + // "Infantry teams 2nd Militia Fusiliers, Raptor three, target the IMS Malta." + RegisterLoudspeakerVO( "outro_11", "diag_sp_addtional_TR411_17_mcor_grunt3", 6.0 ) + + // "Caution - Fuel leak in Cargo Bay eighty - five. Decompressing for fire suppression." + RegisterLoudspeakerVO( "outro_12", "diag_sp_addtional_TR411_18_mcor_shipPA", 6.0 ) +} + +void function RegisterLoudspeakerVO( string scriptAlias, string soundAlias, float duration = 6.0 ) +{ + Assert( !( scriptAlias in file.loudspeakerVO ), "duplicate scriptAlias " + scriptAlias ) + + LoudspeakerVO_Info voInfo + voInfo.scriptAlias = scriptAlias + voInfo.soundAlias = soundAlias + voInfo.duration = duration + + file.loudspeakerVO[ scriptAlias ] <- voInfo +} + +void function PlayLoudspeakerVO( string scriptAlias, float delay = 0.0 ) +{ + Assert( scriptAlias in file.loudspeakerVO ) + LoudspeakerVO_Info voInfo = file.loudspeakerVO[ scriptAlias ] + + string soundAlias = voInfo.soundAlias + + if ( delay > 0 ) + wait delay + + entity emitter = file.loudspeaker + emitter.Signal( "LoudspeakerVO_Stop" ) + emitter.EndSignal( "LoudspeakerVO_Stop" ) + + OnThreadEnd( + function() : ( emitter, soundAlias ) + { + if ( IsValid( emitter ) ) + FadeOutSoundOnEntity( emitter, soundAlias, 2.0 ) + } + ) + + //printt( "playing loudspeaker VO", scriptAlias, "/", soundAlias ) + EmitSoundOnEntity( emitter, soundAlias ) + wait voInfo.duration +} + +void function LoopLoudspeakerVO( array<string> scriptAliases, string endFlag = "", float minPause = -1, float maxPause = -1 ) +{ + if ( endFlag != "" ) + { + if ( Flag( endFlag ) ) + return + + FlagEnd( endFlag ) + } + + while ( 1 ) + { + foreach ( scriptAlias in scriptAliases ) + { + waitthread PlayLoudspeakerVO( scriptAlias ) + + if ( minPause > 0 && maxPause >= minPause ) + wait RandomFloatRange( minPause, maxPause ) + } + } +} + + + +// ============================== +// ============ MISC ============ +// ============================== + +entity function Training_SpawnAnOG( entity startSpot ) +{ + entity og = CreateSoldier( TEAM_MILITIA, startSpot.GetOrigin(), startSpot.GetAngles() ) + og.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID + DispatchSpawn( og ) + + TakeAllWeapons( og ) + + og.SetModel( OG_PILOT_MODEL ) + + og.SetTitle( "#TRAINING_OG_PILOT_NAME" ) + ShowName( og ) + + Training_OGPilot_SetHelmetOn( og, true ) + + og.DisableHibernation() + og.SetNoTarget( true ) + og.UseSequenceBounds( true ) + MakeInvincible( og ) + og.kv.scriptedAnimForceInterrupt = true + + og.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS ) + og.SetHologram() + + return og +} + +void function Training_OGPilot_SetHelmetOn( entity og, bool setOn ) +{ + int headIdx = og.FindBodyGroup( "head" ) + + int submodelIdx = OG_PILOT_MODEL_HEAD_IDX_BARE + if ( setOn ) + submodelIdx = OG_PILOT_MODEL_HEAD_IDX_HELMET + + og.SetBodygroup( headIdx, submodelIdx ) + + /* + int decalIdx = og.FindBodyGroup( "decal" ) + if ( decalIdx == -1 ) + return + + submodelIdx = OG_PILOT_MODEL_DECAL_IDX_BARE + if ( setOn ) + submodelIdx = OG_PILOT_MODEL_DECAL_IDX + + og.SetBodygroup( decalIdx, submodelIdx ) + */ +} + +entity function Training_SpawnOGPilot( entity startSpot ) +{ + if ( IsValid( file.ogPilot ) ) + file.ogPilot.Destroy() + + entity og = Training_SpawnAnOG( startSpot ) + file.ogPilot = og + + return og + } + +entity function Training_SpawnOGTwin( entity startSpot ) +{ + if ( IsValid( file.ogTwin ) ) + file.ogTwin.Destroy() + + entity ogTwin = Training_SpawnAnOG( startSpot ) + file.ogTwin = ogTwin + + return ogTwin +} + +entity function GetOGPilot() +{ + Assert( IsValid( file.ogPilot ) ) + return file.ogPilot +} + +entity function GetOGTwin() +{ + if ( !IsValid( file.ogTwin ) ) + return null + + return file.ogTwin +} + + +void function Training_OG_NagPlayerUntilFlag_Sitting( entity player, array<string> nagAliases, float nagInterval, entity idleRef, string endFlag, string talkAnim = "", string idleAnim = "" ) +{ + entity og = GetOGPilot() + Training_NPC_NagPlayerUntilFlag_Sitting( og, player, nagAliases, nagInterval, idleRef, endFlag, talkAnim, idleAnim ) +} + +void function Training_OG_NagPlayerUntilFlag( entity player, array<string> nagAliases, float nagInterval, entity idleRef, string endFlag, string talkAnim = "", string idleAnim = "" ) +{ + entity og = GetOGPilot() + Training_NPC_NagPlayerUntilFlag( og, player, nagAliases, nagInterval, idleRef, endFlag, talkAnim, idleAnim ) +} + +void function Training_NPC_NagPlayerUntilFlag_Sitting( entity npc, entity player, array<string> nagAliases, float nagInterval, entity idleRef, string endFlag, string talkAnim = "", string idleAnim = "" ) +{ + if ( talkAnim == "" ) + talkAnim = ANIM_OG_SITTING_TALK + + if ( idleAnim == "" ) + idleAnim = ANIM_OG_SITTING_IDLE + + Training_OG_NagPlayerUntilFlag( player, nagAliases, nagInterval, idleRef, endFlag, talkAnim, idleAnim ) +} + +void function Training_NPC_NagPlayerUntilFlag( entity npc, entity player, array<string> nagAliases, float nagInterval, entity idleRef, string endFlag, string talkAnim = "", string idleAnim = "" ) +{ + player.EndSignal( "OnDestroy" ) + + if ( talkAnim == "" ) + talkAnim = ANIM_OG_STANDING_TALK + + if ( idleAnim == "" ) + idleAnim = ANIM_OG_STANDING_IDLE + + int nagIdx = 0 + float nextNagTime = Time() + nagInterval + + while ( !Flag( endFlag ) ) + { + if ( Time() > nextNagTime ) + { + waitthread Training_OG_Talks( nagAliases[nagIdx], idleRef, talkAnim, idleAnim, true ) + nextNagTime = Time() + nagInterval + + nagIdx++ + if ( nagIdx >= nagAliases.len() ) + nagIdx = 0 + } + + wait 0.1 + } +} + +void function Training_OG_Talks_Sitting( string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false ) +{ + entity og = GetOGPilot() + Training_NPC_Talks_Sitting( og, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend ) +} + +void function Training_OG_Talks_Leaning( string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false ) +{ + entity og = GetOGPilot() + Training_NPC_Talks_Leaning( og, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend ) +} + +void function Training_OG_Talks( string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false ) +{ + entity og = GetOGPilot() + Training_NPC_Talks( og, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend ) +} + +void function Training_OG_Idles_Sitting( entity idleRef, string anim = "", bool useBlend = false ) +{ + entity og = GetOGPilot() + Training_NPC_Idles_Sitting( og, idleRef, anim, useBlend ) +} + +void function Training_OG_Idles_SittingAndTalking( entity idleRef, string anim = "", bool useBlend = false ) +{ + entity og = GetOGPilot() + Training_NPC_Idles_SittingAndTalking( og, idleRef, anim, useBlend ) +} + +void function Training_OG_Idles_Talking( entity idleRef, string anim = "", bool useBlend = false ) +{ + entity og = GetOGPilot() + Training_NPC_Idles_Talking( og, idleRef, anim, useBlend ) +} + +void function Training_OG_Idles( entity idleRef, string anim = "", bool useBlend = false ) +{ + entity og = GetOGPilot() + thread Training_NPC_Idles( og, idleRef, anim, useBlend ) +} + +void function Training_OG_ScriptedAnim( entity idleRef, string anim, bool useBlend = false ) +{ + entity og = GetOGPilot() + Training_NPC_ScriptedAnim( og, idleRef, anim, useBlend ) +} + + +void function Training_NPC_Talks_Sitting( entity npc, string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false ) +{ + if ( talkAnim == "" ) + talkAnim = ANIM_OG_SITTING_TALK + + if ( idleAnim == "" ) + idleAnim = ANIM_OG_SITTING_IDLE + + Training_NPC_Talks( npc, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend ) +} + +void function Training_NPC_Talks_Leaning( entity npc, string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false ) +{ + if ( talkAnim == "" ) + talkAnim = ANIM_OG_LEANING_TALK + + if ( idleAnim == "" ) + idleAnim = ANIM_OG_LEANING_IDLE + + Training_NPC_Talks( npc, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend ) +} + +void function Training_NPC_Talks( entity npc, string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false ) +{ + npc.EndSignal( "OnDestroy" ) + npc.EndSignal( "NPC_NewCommand" ) + + if ( talkAnim == "" ) + talkAnim = ANIM_OG_STANDING_TALK + + if ( idleAnim == "" ) + idleAnim = ANIM_OG_STANDING_IDLE + + npc.Anim_Stop() + if ( useBlend ) + thread PlayAnim( npc, talkAnim, idleRef ) + else + thread PlayAnim( npc, talkAnim, idleRef, null, 0.0 ) + + OnThreadEnd( + function() : ( npc, idleRef, idleAnim, useBlend ) + { + if ( IsValid( npc ) ) + Training_NPC_Idles( npc, idleRef, idleAnim, useBlend ) + } + ) + + waitthread PlayDialogue( voScriptAlias, npc ) +} + +void function Training_NPC_Idles_Sitting( entity npc, entity idleRef, string anim = "", bool useBlend = false ) +{ + if ( anim == "" ) + anim = ANIM_OG_SITTING_IDLE + + Training_NPC_Idles( npc, idleRef, anim, useBlend ) +} + +void function Training_NPC_Idles_SittingAndTalking( entity npc, entity idleRef, string anim = "", bool useBlend = false ) +{ + if ( anim == "" ) + anim = ANIM_OG_SITTING_TALK + + Training_NPC_Idles( npc, idleRef, anim, useBlend ) +} + +void function Training_NPC_Idles_Talking( entity npc, entity idleRef, string anim = "", bool useBlend = false ) +{ + if ( anim == "" ) + anim = ANIM_OG_STANDING_TALK + + thread Training_NPC_Idles( npc, idleRef, anim, useBlend ) +} + +void function Training_NPC_Idles( entity npc, entity idleRef, string anim = "", bool useBlend = false ) +{ + npc.Signal( "NPC_NewCommand" ) + + if ( anim == "" ) + anim = ANIM_OG_STANDING_IDLE + + npc.Anim_Stop() + + if ( useBlend ) + thread PlayAnim( npc, anim, idleRef ) + else + thread PlayAnim( npc, anim, idleRef, null, 0.0 ) +} + +void function Training_NPC_ScriptedAnim( entity npc, entity idleRef, string anim, bool useBlend = false ) +{ + npc.Signal( "NPC_NewCommand" ) + + npc.Anim_Stop() + + if ( useBlend ) + PlayAnim( npc, anim, idleRef ) + else + PlayAnim( npc, anim, idleRef, null, 0.0 ) +} + +void function Training_OG_Moves_ToSitting( entity moveToRef, string destAnim = "", float moveTimeOverride = -1 ) +{ + if ( destAnim == "" ) + destAnim = ANIM_OG_SITTING_IDLE + + Training_OG_Moves( moveToRef, destAnim, moveTimeOverride, true ) +} + +void function Training_OG_Moves( entity moveToRef, string destAnim = "", float moveTimeOverride = -1, bool destAnim_isSitting = false ) +{ + entity og = GetOGPilot() + og.Signal( "NPC_NewCommand" ) + + int ogAttachIdx = og.LookupAttachment( "CHESTFOCUS" ) + vector startOrigin = og.GetAttachmentOrigin( ogAttachIdx ) + + const vector standingOffset = <0, 0, 42> + const vector sittingOffset = <0, 0, 20> + vector destHeightOffset = destAnim_isSitting ? sittingOffset : standingOffset + vector endOrigin = moveToRef.GetOrigin() + destHeightOffset + + og.Freeze() + + file.ogPilot = null + + if ( IsValid( og ) ) + { + og.NotSolid() + DissolveGhost( og ) + } + + entity newOG = Training_SpawnOGPilot( moveToRef ) + newOG.Hide() + + entity mover = CreateScriptMover( startOrigin, <0,0,0> ) + int moverAttachIdx = mover.LookupAttachment( "REF" ) + EmitSoundOnEntity( mover, "og_dissolve_trail" ) + file.ogPathMover = mover + + newOG.EndSignal( "OnDestroy" ) + mover.EndSignal( "OnDestroy" ) + + OnThreadEnd( + function() : ( mover, newOG, moveToRef, destAnim_isSitting, destAnim, ogAttachIdx ) + { + if ( IsValid( mover ) ) + { + StopSoundOnEntity( mover, "og_dissolve_trail" ) + mover.Destroy() + file.ogPathMover = null + } + + if ( IsValid( newOG ) ) + { + StartParticleEffectOnEntity( newOG, GetParticleSystemIndex( GHOST_FLASH_EFFECT ), FX_PATTACH_POINT, ogAttachIdx ) + newOG.Show() + + if ( destAnim_isSitting ) + Training_OG_Idles_Sitting( moveToRef, destAnim ) + else + Training_OG_Idles( moveToRef, destAnim ) + } + } + ) + + StartParticleEffectOnEntity( mover, GetParticleSystemIndex( GHOST_TRAIL_EFFECT ), FX_PATTACH_POINT_FOLLOW, moverAttachIdx ) + StartParticleEffectOnEntity( mover, GetParticleSystemIndex( GHOST_FLASH_EFFECT ), FX_PATTACH_POINT, moverAttachIdx ) + wait 0.5 + + float moveSpeed = 1350.0 + float moveDist = Distance( startOrigin, endOrigin ) + float moveTime = moveDist / moveSpeed + if ( moveTimeOverride > 0 ) + moveTime = moveTimeOverride + + float accel = moveTime * 0.1 + float decel = moveTime * 0.1 + mover.NonPhysicsMoveTo( endOrigin, moveTime, accel, decel ) + wait moveTime - 0.1 + + EmitSoundAtPosition( TEAM_UNASSIGNED, endOrigin, "PathHologram_Materialized_training" ) + StartParticleEffectOnEntity( mover, GetParticleSystemIndex( GHOST_FLASH_EFFECT ), FX_PATTACH_POINT, moverAttachIdx ) + wait 0.1 +} + +entity function TeleportOG( string entName ) +{ + Assert( IsValid( file.ogPilot ) ) + + file.ogPilot.Signal( "NPC_NewCommand" ) + file.ogPilot.Anim_Stop() + + entity teleportSpot = GetEntByScriptName( entName ) + + vector org = teleportSpot.GetOrigin() + vector ang = teleportSpot.GetAngles() + file.ogPilot.SetOrigin( org ) + file.ogPilot.SetAngles( ang ) + + return teleportSpot +} + +void function NPC_DisableArrivals( entity npc ) +{ + printt( "disabling arrivals" ) + npc.EnableNPCMoveFlag( NPCMF_DISABLE_ARRIVALS ) +} + +void function NPC_EnableArrivals( entity npc ) +{ + printt( "enabling arrivals" ) + npc.DisableNPCMoveFlag( NPCMF_DISABLE_ARRIVALS ) +} + + +void function PlayerAndOGTeleport_Fancy( entity player, vector destPos, string ogTeleportSpotName, vector destAng = < -1, -1, -1 > ) +{ + EndSignal( player, "OnDeath" ) + thread FancyTeleport_EffectsAndSound( player, destPos ) + + player.WaitSignal( "FancyTeleportStart" ) + + entity ogTeleportSpot = TeleportOG( ogTeleportSpotName ) + Training_OG_Idles_Sitting( ogTeleportSpot ) + + MakeInvincible( player ) + WaitEndFrame() // player will take damage from random hazard triggers otherwise + + player.SetOrigin( destPos ) + if ( destAng != < -1, -1, -1 > ) + player.SetAngles( destAng ) + + ClearInvincible( player ) +} + +void function FancyTeleport_EffectsAndSound( entity player, vector teleportPos ) +{ + EndSignal( player, "OnDeath" ) + + float statusEffect_severity = 2.5 + float statusEffect_totalDuration = 0.8 + float statusEffect_easeOutTime = 0.1 + StatusEffect_AddTimed( player, eStatusEffect.timeshift_visual_effect, statusEffect_severity, statusEffect_totalDuration, statusEffect_easeOutTime ) + + wait 0.1 + + Remote_CallFunction_Replay( player, "ScriptCallback_PodTransition_PlayerScreenFX" ) + + EmitSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Present" ) + + wait 0.25 // let screen FX fade screen + + player.Signal( "FancyTeleportStart" ) + + //wait holdTime - 0.1 + + wait 0.5 // let white screen fade + + EmitSoundOnEntity( player, "training_scr_zen_player_fall" ) + + wait 0.2 // let screen clear before pulsing + + entity pulseFXHandle = PlayFX( FX_FANCY_TELEPORT_ENV_PULSE, teleportPos, <0,0,0> ) + EffectSetControlPointVector( pulseFXHandle, 1, <2.5,50,0> ) + thread KillFX_Delayed( pulseFXHandle, 0.5 ) +} + +entity function WaitForPlayerActiveWeapon( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + entity weapon = null + while ( !weapon ) + { + WaitFrame() + weapon = player.GetActiveWeapon() + } + + return weapon +} + + +void function GhostRecorder_RepeatUntilFlag( entity player, string endFlag, entity animRef, asset recordedAnim, float extraRepeatDelay = 0.0, bool silentDissolve = false ) +{ + EndSignal( player, "OnDestroy" ) + EndSignal( level, "StopRepeatingGhostRecorder" ) + + if ( Flag( endFlag ) ) + return + FlagEnd( endFlag ) + + string dissolveSFX = "object_dissolve_training" + if ( silentDissolve ) + dissolveSFX = "" + + table<int,entity> t = {} + t[0] <- null + + OnThreadEnd( + function() : ( t, dissolveSFX ) + { + if ( !t.len() ) + return + + entity ghost = t[0] + + if ( IsValid( ghost ) ) + { + StopSoundOnEntity( ghost, "PathHologram_Sustain_Loop_3P" ) + DissolveGhost( ghost, dissolveSFX ) + } + } + ) + + var rec = LoadRecordedAnimation( recordedAnim ) + float duration = GetRecordedAnimationDuration( rec ) + + const float ghostFadeTime = 1.2 + + while ( 1 ) + { + entity ghost = CreateGhost( animRef.GetOrigin() ) + t[0] = ghost + + EmitSoundOnEntity( ghost, "PathHologram_Sustain_Loop_3P" ) + + ghost.PlayRecordedAnimation( rec, <0,0,0>, <0,0,0>, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME, animRef ) + wait duration - ghostFadeTime + + DissolveGhost( ghost, dissolveSFX ) + + wait ghostFadeTime + wait extraRepeatDelay + } +} + +void function Training_TeleportEffect( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + player.MovementDisable() + + OnThreadEnd( + function() : ( player ) + { + if ( IsValid( player ) ) + player.MovementEnable() + } + ) + + float fadeTime = 0.3 + float holdTime = 0.5 + + ScreenFade( player, 255, 255, 255, 254, fadeTime, holdTime, FFADE_IN | FFADE_PURGE ) + EmitSoundOnEntity( player, "NPE_VisualImpair" ) + wait fadeTime + wait holdTime + FadeOutSoundOnEntity( player, "NPE_VisualImpair", fadeTime ) +} + +void function TakeAmmoFromPlayerASAP( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + entity weapon = WaitForPlayerActiveWeapon( player ) + + array<entity> weapons = player.GetMainWeapons() + + foreach ( weapon in weapons ) + { + weapon.SetWeaponPrimaryAmmoCount( 0 ) + weapon.SetWeaponPrimaryClipCount( 0 ) + } + + // take offhand weapons player may have collected + array<entity> offhands = player.GetOffhandWeapons() + foreach ( index, weapon in clone offhands ) + player.TakeOffhandWeapon( index ) +} + +void function Training_WeaponPickups_Init( entity player ) +{ + Assert( Flag( "EntitiesDidLoad" ) ) + + LeveledScriptedWeapons leveledScriptedWeapons = GetAllLeveledScriptWeapons() + + foreach ( ent in leveledScriptedWeapons.infoTargets ) + thread Training_RecreateWeaponPickup_Think( ent, player ) +} + +void function Training_SetWeaponPickupsEmptyAmmo() +{ + file.weaponPickupsHaveAmmo = false + + LeveledScriptedWeapons leveledScriptedWeapons = GetAllLeveledScriptWeapons() + foreach ( ent in leveledScriptedWeapons.infoTargets ) + { + // fix for player picking up a weapon right before calling this- attachedEnt is empty because it hasn't been recreated yet + // - actual fix is to thread, wait for attachedEnts to get the recreated weapon again, and timeout, but going with less risk for now + if ( !ent.e.attachedEnts.len() ) + continue + + entity weaponEnt = ent.e.attachedEnts[0] + if ( !IsValid( weaponEnt ) ) + continue + + weaponEnt.SetWeaponPrimaryAmmoCount( 0 ) + weaponEnt.SetWeaponPrimaryClipCount( 0 ) + } +} + +void function Training_SetWeaponPickupsFullAmmo() +{ + file.weaponPickupsHaveAmmo = true + + LeveledScriptedWeapons leveledScriptedWeapons = GetAllLeveledScriptWeapons() + foreach ( ent in leveledScriptedWeapons.infoTargets ) + { + // fix for player picking up a weapon right before calling this- attachedEnt is empty because it hasn't been recreated yet + // - actual fix is to thread, wait for attachedEnts to get the recreated weapon again, and timeout, but going with less risk for now + if ( !ent.e.attachedEnts.len() ) + continue + + entity weaponEnt = ent.e.attachedEnts[0] + if( !IsValid( weaponEnt ) ) + continue + + string weaponClass = weaponEnt.GetWeaponClassName() + int defaultTotal = GetWeaponInfoFileKeyField_GlobalInt( weaponClass, "ammo_default_total" ) + int defaultMag = GetWeaponInfoFileKeyField_GlobalInt( weaponClass, "ammo_clip_size" ) + + weaponEnt.SetWeaponPrimaryAmmoCount( defaultTotal ) + } +} + +void function Training_RecreateWeaponPickup_Think( entity ent, entity player ) +{ + EndSignal( player, "OnDestroy" ) + EndSignal( ent, "OnDestroy" ) + + const float MATCHING_PICKUP_DIST = 0.5 + const float NEARBY_SIMILAR_DIST = 200.0 + + string pickupEntWeaponClass = ent.GetValueForKey( "script_weapon" ) + + while ( ent.e.attachedEnts.len() && IsValid( ent.e.attachedEnts[0] ) ) + { + entity weaponEnt = ent.e.attachedEnts[0] + + while ( IsValid( weaponEnt ) && !weaponEnt.GetOwner() ) + wait 0.1 + + // this is the most reliable way to get a good push vector for if we need to kick another weapon out (pickup ent angles are often not optimal) + vector playerPos_onPickup = player.GetOrigin() + vector vecToPlayer_whenPickedUp = Normalize( playerPos_onPickup - ent.GetOrigin() ) + + ent.e.attachedEnts.remove( 0 ) + + wait 2.2 // don't respawn it right away + + bool oldWeapon_similarPickupNearby = false + bool pickupEnt_similarPickupNearby = false + + // previous player weapon may be here after swapping + array<entity> allPickups = GetWeaponArray( true ) + entity oldWeapon + foreach ( pickup in allPickups ) + { + float distToThisPickup = Distance( pickup.GetOrigin(), ent.GetOrigin() ) + if ( distToThisPickup <= MATCHING_PICKUP_DIST ) + { + oldWeapon = pickup + break + } + } + + if ( IsValid( oldWeapon ) ) + { + foreach ( pickup in allPickups ) + { + if ( oldWeapon == pickup ) + continue + + float distToThisPickup = Distance( pickup.GetOrigin(), ent.GetOrigin() ) + if ( distToThisPickup <= NEARBY_SIMILAR_DIST ) + { + string pickupWeaponClass = pickup.GetWeaponClassName() + + if ( pickupWeaponClass == oldWeapon.GetWeaponClassName() && !oldWeapon_similarPickupNearby ) + { + //printt( "found similar pickup nearby to one the player dropped:", pickupWeaponClass ) + oldWeapon_similarPickupNearby = true + } + + if ( pickupWeaponClass == pickupEntWeaponClass && !pickupEnt_similarPickupNearby ) + { + //printt( "found similar pickup nearby to one that would be recreated:", pickupWeaponClass ) + pickupEnt_similarPickupNearby = true + } + } + } + } + + bool recreatePickup = true + bool destroyOldWeapon = false + if ( IsValid( oldWeapon ) ) + { + if ( oldWeapon.GetWeaponClassName() == pickupEntWeaponClass ) + { + printt( "old weapon that is here is the same kind of weapon as we would spawn, so don't recreate:", pickupEntWeaponClass ) + recreatePickup = false // old weapon that is here is the same kind of weapon as we would spawn, so don't recreate + } + + if ( oldWeapon_similarPickupNearby ) + { + printt( "Old weapon can be destroyed, because a similar pickup is nearby:", oldWeapon.GetWeaponClassName() ) + destroyOldWeapon = true + } + + if ( !oldWeapon_similarPickupNearby && pickupEnt_similarPickupNearby ) + { + printt( "old weapon is unique to this area and pickup ent has a similar pickup nearby, so don't recreate pickup ent. Old weapon:", oldWeapon.GetWeaponClassName(), "/ pickup ent class:", pickupEntWeaponClass ) + recreatePickup = false + } + } + + if ( recreatePickup ) + { + if ( IsValid( oldWeapon ) ) + { + if ( destroyOldWeapon ) + { + printt( "destroying old weapon because similar pickup is nearby:", oldWeapon.GetWeaponClassName() ) + oldWeapon.Destroy() + } + else + { + MoveOldWeapon( ent, oldWeapon, vecToPlayer_whenPickedUp ) // kick the old weapon out of this spot + } + } + + // cover respawn with a flash effect + EmitSoundAtPosition( TEAM_UNASSIGNED, ent.GetOrigin(), "training_scr_rack_weapon_appear" ) + StartParticleEffectInWorld( GetParticleSystemIndex( GHOST_FLASH_EFFECT ), ent.GetOrigin(), ent.GetAngles() ) + CreateScriptWeapon( ent ) + + // defensive checks + if ( !ent.e.attachedEnts.len() || !IsValid( ent.e.attachedEnts[0] ) ) + { + printt( "WARNING! Recreated script pickup FAILED to recreate:", pickupEntWeaponClass, "on ent", ent ) + continue + } + + entity recreatedPickup = ent.e.attachedEnts[0] + printt( "training: recreated weapon pickup:", pickupEntWeaponClass, "by spawning:", recreatedPickup ) + + if ( !file.weaponPickupsHaveAmmo ) + { + recreatedPickup.SetWeaponPrimaryAmmoCount( 0 ) + recreatedPickup.SetWeaponPrimaryClipCount( 0 ) + } + } + else + { + if ( IsValid( oldWeapon ) ) + ent.e.attachedEnts.append( oldWeapon ) + } + } + + printt( "WARNING- Stopping think on pickupEntWeapon:", pickupEntWeaponClass ) +} + +void function MoveOldWeapon( entity pickupEnt, entity oldWeapon, vector pushVec = <0,0,0> ) +{ + // recreate weapon as unconstrained so we can physics push it + entity recreatedOldWeapon = Training_RecreatePlayerWeaponPickup( oldWeapon ) + string recreatedClassName = recreatedOldWeapon.GetWeaponClassName() + + float velocityScalar = 300.0 + + var hasSubClass = GetWeaponInfoFileKeyField_Global( recreatedClassName, "weaponSubClass" ) + if ( hasSubClass ) + { + string weaponSubClass = GetWeaponInfoFileKeyField_GlobalString( recreatedClassName, "weaponSubClass" ) + + switch ( weaponSubClass ) + { + case "offhand": + case "pistol": + velocityScalar = 200 + break + + case "smg": + velocityScalar = 300 + break + + case "rifle": + velocityScalar = 400 + break + + case "lmg": + case "at": + velocityScalar = 500 + break + } + } + + if ( pushVec == <0,0,0> ) + pushVec = AnglesToForward( pickupEnt.GetAngles() ) + + //vector pushAng = VectorToAngles( pushVec ) + //vector addVec = AnglesToUp( pushAng ) * (velocityScalar * 0.2) + //pushVec += addVec + pushVec += <0,0,1> + + printt( "moving old weapon:", oldWeapon.GetWeaponClassName(), "with velocity scalar:", velocityScalar ) + recreatedOldWeapon.SetVelocity( pushVec * velocityScalar ) +} + +entity function Training_RecreatePlayerWeaponPickup( entity oldWeapon ) +{ + if ( file.scriptCreatedWeaponPickups.len() >= MAX_RECREATED_OLD_WEAPONS ) + { + entity cleanupWeapon = file.scriptCreatedWeaponPickups[0] + + if ( IsValid( cleanupWeapon ) ) + cleanupWeapon.Destroy() + + file.scriptCreatedWeaponPickups.remove( 0 ) + } + + string oldWeaponClass = oldWeapon.GetWeaponClassName() + + entity weapon = CreateWeaponEntityByNameWithPhysics( oldWeaponClass, oldWeapon.GetOrigin(), oldWeapon.GetAngles() ) + weapon.SetVelocity( <0,0,0> ) + + SetTargetName( weapon, "_old_player_weapon_" + oldWeaponClass ) + weapon.kv.fadedist = -1 + + array<string> existingMods = oldWeapon.GetMods() + weapon.SetMods( existingMods ) + + bool doMarkAsLoadoutPickup = false + if ( doMarkAsLoadoutPickup ) + weapon.MarkAsLoadoutPickup() + + HighlightWeapon( weapon ) + + oldWeapon.Destroy() + + file.scriptCreatedWeaponPickups.append( weapon ) + + return weapon +} + + +void function Training_WeaponRacks_SetSolidity( bool doSolid ) +{ + array<entity> racks = GetEntArrayByScriptName( "ineedguns_racks" ) + + foreach ( rack in racks ) + { + if ( doSolid ) + rack.Solid() + else + rack.NotSolid() + } +} + + +bool function GetAutosprintEnabled() +{ + int autosprintSetting = GetConVarInt( AUTOSPRINT_CONVAR_NAME ) + bool autoSprintEnabled = autosprintSetting > 0 && autosprintSetting < 3 // 0 = none, 3 = titans only + return autoSprintEnabled +} + + +void function EmitSoundOnEntity_Delayed( entity ent, string alias, float delay ) +{ + ent.EndSignal( "OnDestroy" ) + + if ( delay > 0 ) + wait delay + + EmitSoundOnEntity( ent, alias ) +} + +void function PlayFXOnEntity_Delayed( entity player, asset fxAsset, entity ent, float delay ) +{ + player.EndSignal( "OnDestroy" ) + ent.EndSignal( "OnDeath" ) + + wait delay + + PlayFXOnEntity( fxAsset, ent ) +} + +void function KillFX_Delayed( entity fxHandle, float delay ) +{ + fxHandle.EndSignal( "OnDestroy" ) + + if ( delay > 0.0 ) + wait delay + + KillFX( fxHandle ) +} + +void function KillFX( entity fxHandle ) +{ + if ( !IsValid_ThisFrame( fxHandle ) ) + return + + fxHandle.SetStopType( "DestroyImmediately" ) + fxHandle.ClearParent() + fxHandle.Destroy() +} + +void function KillFXWithEndcap( entity fxHandle, float killDelay = 1.0 ) +{ + if ( !IsValid_ThisFrame( fxHandle ) ) + return + + EffectStop( fxHandle ) + wait killDelay + + if ( !IsValid_ThisFrame( fxHandle ) ) + return + + fxHandle.ClearParent() + fxHandle.Destroy() +} + + +void function FlagSetDelayed( string setFlag, float delay ) +{ + thread FlagSetDelayed_Think( setFlag, delay ) +} + +void function FlagSetDelayed_Think( string setFlag, float delay ) +{ + EndSignal( level, "OnDestroy" ) + + if ( delay > 0 ) + wait delay + + FlagSet( setFlag ) +} + + +void function Training_PlayerQuickdeathSFX( entity player ) +{ + EndSignal( player, "OnDestroy" ) + + while ( 1 ) + { + WaitSignal( player, "QuickDeath" ) + EmitSoundOnEntity( player, "training_scr_zen_player_fall" ) + } +} + + +void function Training_EnvArtColorCorrection_SetEnabled( bool isEnabled ) +{ + Assert( IsValid( file.envArt_colorCorrectionEnt ), "Called too early?" ) + + string setEnabledStr = "Disable" + if ( isEnabled) + setEnabledStr = "Enable" + + EntFireByHandle( file.envArt_colorCorrectionEnt, setEnabledStr, "", 0, null, null ) +} + + +void function SetDoF_Hangar( entity player ) +{ + Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetNearDepth", 0, 18 ) + Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetFarDepth", 450, 1250 ) +} + +void function SetDoF_Default( entity player ) +{ + Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetNearDepthToDefault" ) + Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetFarDepthToDefault" ) +} + +void function RackDoF_NearDepth( entity player, float nearDepthStart, float nearDepthEnd, float rackTime ) +{ + Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetNearDepth", nearDepthStart, nearDepthEnd, rackTime ) +} + +void function RackDOF_NearDepth_ToDefault( entity player, float duration ) +{ + Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetNearDepthToDefault", duration ) +} + +void function RackDoF_FarDepth( entity player, float farDepthStart, float farDepthEnd, float rackTime ) +{ + Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetFarDepth", farDepthStart, farDepthEnd, rackTime ) +} + +void function SimpleScreenShake( entity player, float duration, float amplitude, float blurMaxIntensity = 0.75 ) +{ + Remote_CallFunction_Replay( player, "ScriptCallback_SimpleScreenShake", duration, amplitude, blurMaxIntensity ) +} + +void function SetWeaponHUDEnabled( entity player, bool setEnabled ) +{ + file.displayWeaponHUD = setEnabled + Remote_CallFunction_Replay( player, "ScriptCallback_SetWeaponHUDEnabled", setEnabled ) +} + + + +// --------------------- +// ----- SKIT GUYS ----- +// --------------------- +SkitGuyInfo function AddSkitGuy_Manually( string name, entity guy ) +{ + if ( SkitGuyExists( name ) ) + DeleteSkitGuy( GetSkitGuyInfo_ByName( name ) ) + + SkitGuyInfo info + info.id = file.skitguys.len() + info.guy = guy + info.name = name + + file.skitguys.append( info ) + + return info +} + +SkitGuyInfo function SpawnSkitGuy( string name, string anim, vector origin, vector angles, int team = TEAM_IMC, string aiSettings = "", string weapon = "mp_weapon_semipistol", bool isRunner = false ) +{ + if ( SkitGuyExists( name ) ) + DeleteSkitGuy( GetSkitGuyInfo_ByName( name ) ) + + string guyType = "grunt" + if ( name.find( "marvin" ) != null ) + guyType = "marvin" + + // spawn the guy + entity guy + if ( guyType == "marvin" ) + { + Assert( !isRunner, "Marvins don't run!" ) + + guy = CreateEntity( "npc_marvin" ) + + DispatchSpawn( guy ) + + SetTeam( guy, TEAM_SPECTATOR ) + guy.SetNPCMoveSpeedScale( 0.6 ) + + //TakeAllJobs( guy ) + } + else + { + guy = CreateSoldier( team, <0,0,0>, <0,0,0> ) // spawn the guy at worldspawn to avoid "npc spawned in solid" red text` + SetSpawnOption_Weapon( guy, weapon ) + if ( aiSettings != "" ) + SetSpawnOption_AISettings( guy, aiSettings ) + + if ( isRunner ) + guy.kv.alwaysAlert = 1 + + DispatchSpawn( guy ) + } + + guy.SetTitle( "" ) + + entity ref = CreateOwnedScriptMover( guy ) + ref.SetOrigin( origin ) + ref.SetAngles( angles ) + + guy.SetOrigin( ref.GetOrigin() ) + guy.SetAngles( ref.GetAngles() ) + + MakeInvincible( guy ) + guy.SetEfficientMode( true ) + guy.EnableNPCFlag( NPC_IGNORE_ALL | NPC_DISABLE_SENSING ) + guy.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS ) + + SkitGuyInfo info + info.id = file.skitguys.len() + info.guy = guy + info.skitRef = ref + info.skitAnim = anim + info.name = name + + file.skitguys.append( info ) + + return info +} + +void function SpawnSkitGuy_AndRun( string name, array<Point> path, float moveSpeedScale, int team, string aiSettings = "", string weaponName = "" ) +{ + SkitGuyInfo runnerInfo + runnerInfo = SpawnSkitGuy( name, "", path[0].origin, path[0].angles, team, aiSettings, weaponName, true ) + + entity runner = runnerInfo.guy + EndSignal( runner, "OnDestroy" ) + + OnThreadEnd( + function() : ( runnerInfo ) + { + DeleteSkitGuy( runnerInfo ) + } + ) + + waitthread ScriptedPath_Run( runnerInfo, path, moveSpeedScale ) +} + +void function SpawnSkitGuy_AndRunForever( string name, array<Point> path, float moveSpeedScale, int team, string aiSettings = "", string weaponName = "" ) +{ + SkitGuyInfo runnerInfo + while ( 1 ) + { + runnerInfo = SpawnSkitGuy( name, "", path[0].origin, path[0].angles, team, aiSettings, weaponName ) + waitthread ScriptedPath_Run( runnerInfo, path, moveSpeedScale ) + } +} + +void function SkitGuy_PlayAnim( SkitGuyInfo info, float skipAheadTime = 0 ) +{ + Assert( info.skitAnim != "" ) + Assert( IsValid( info.skitRef ) ) + + entity guy = info.guy + + thread PlayAnim( guy, info.skitAnim, info.skitRef, null, 0.0, skipAheadTime ) +} + +bool function SkitGuyExists( string name ) +{ + foreach ( info in file.skitguys ) + { + if ( info.name == name ) + return true + } + + return false +} + +SkitGuyInfo function GetSkitGuyInfo_ByName( string guyName ) +{ + SkitGuyInfo thisInfo + foreach ( info in file.skitguys ) + { + if ( info.name == guyName ) + { + thisInfo = info + return thisInfo + } + } + + Assert( false, "couldn't find skit guy info by name: " + guyName ) + unreachable +} + +void function DeleteAllSkitGuys() +{ + array<string> deleteNames = [] + + foreach ( skitInfo in file.skitguys ) + deleteNames.append( skitInfo.name ) + + foreach ( name in deleteNames ) + { + if ( !SkitGuyExists( name ) ) + continue + + SkitGuyInfo deleteInfo = GetSkitGuyInfo_ByName( name ) + DeleteSkitGuy( deleteInfo ) + } +} + +void function DeleteSkitGuy( SkitGuyInfo info ) +{ + KillSkitGuy( info ) + + int removeIdx = -1 + foreach ( idx, guyInfo in file.skitguys ) + { + if ( guyInfo.id == info.id ) + { + removeIdx = idx + break + } + } + + if ( removeIdx == -1 ) + { + printt( "WARNING: SkitGuy was already deleted!" ) + return + } + + + file.skitguys.remove( removeIdx ) +} + +void function KillSkitGuy( SkitGuyInfo info ) +{ + entity guy = info.guy + entity skitRef = info.skitRef + + if ( IsValid( skitRef ) ) + skitRef.Destroy() + + info.skitRef = null + + if ( IsAlive( guy ) ) + { + guy.Anim_Stop() + ClearInvincible( guy ) + } + + if ( IsValid( guy ) ) + guy.Destroy() + + info.guy = null +} + +#if DEV +string function NudgeSkitGuy( string name, float offsetX, float offsetY = 0.0, float offsetZ = 0.0 ) +{ + if ( !SkitGuyExists( name ) ) + { + return "WARNING: SKIT GUY NAME NOT RECOGNIZED: " + name + } + + SkitGuyInfo info = GetSkitGuyInfo_ByName( name ) + entity guy = info.guy + entity skitRef = info.skitRef + string name = info.name + + vector offset = <offsetX, offsetY, offsetZ> + + if ( IsValid( skitRef ) ) + skitRef.SetOrigin( skitRef.GetOrigin() + offset ) + else + guy.SetOrigin( guy.GetOrigin() + offset ) + + if ( info.skitAnim != "" ) + SkitGuy_PlayAnim( info ) + + printt( "NUDGED:") + return PrintSkitGuy( info ) +} + +string function PrintSkitGuy( SkitGuyInfo info ) +{ + entity guy = info.guy + entity skitRef = info.skitRef + string name = info.name + + string returnStr = name + " origin/angles: " + CreateOriginAnglesString( guy.GetOrigin(), guy.GetAngles() ) + if ( IsValid( skitRef ) ) + returnStr = name + " ref origin/angles: " + CreateOriginAnglesString( skitRef.GetOrigin(), skitRef.GetAngles() ) + + return returnStr +} +#endif //DEV + + + +// ------------------------------ +// ----- SCRIPTED NPC PATHS ----- +// ------------------------------ +void function ScriptedPath_AddPoint( array<Point> pathpoints, vector origin, vector angles ) +{ + Point pathpoint + pathpoint.origin = origin + pathpoint.angles = angles + + pathpoints.append( pathpoint ) +} + +void function ScriptedPath_Walk( SkitGuyInfo info, array<Point> path, float moveSpeedScale = 0.8, string idleAnim = "" ) +{ + NPC_ScriptedPath( SCRIPTED_PATH_WALK, info, path, moveSpeedScale, idleAnim ) +} + +void function ScriptedPath_Run( SkitGuyInfo info, array<Point> path, float moveSpeedScale = 1.0, string idleAnim = "" ) +{ + NPC_ScriptedPath( SCRIPTED_PATH_RUN, info, path, moveSpeedScale, idleAnim ) +} + +void function NPC_ScriptedPath( int pathFollowType, SkitGuyInfo info, array<Point> path, float moveSpeedScale = 1.0, string idleAnim = "" ) +{ + entity guy = info.guy + + guy.EndSignal( "OnDestroy" ) + + guy.Anim_Stop() + + guy.EnableNPCMoveFlag( NPCMF_DISABLE_MOVE_TRANSITIONS ) + guy.EnableNPCMoveFlag( NPCMF_DISABLE_ARRIVALS ) + guy.SetNPCMoveSpeedScale( moveSpeedScale ) + + if ( pathFollowType == SCRIPTED_PATH_RUN ) + guy.SetAlert() // change his alert state so he will run + + if ( pathFollowType == SCRIPTED_PATH_WALK ) + guy.SetMoveAnim( "patrol_walk_bored" ) + + string waitSignal = "OnEnterGoalRadius" //"OnFinishedAssault" + float pathfindingFailTimeout = 20.0 + + guy.SetOrigin( path[0].origin ) + guy.SetAngles( path[0].angles ) + + for ( int i = 1; i < path.len(); i++ ) + { + Point pathpoint = path[i] + float goalradius = 64.0 // MINIMUM + //guy.DisableArrivalOnce( true ) // always want arrivals disabled because they are blended from run anim, not walking + + guy.AssaultPoint( pathpoint.origin ) + guy.AssaultSetGoalRadius( goalradius ) + + WaitSignalTimeout( guy, pathfindingFailTimeout, waitSignal ) + + if ( Distance( guy.GetOrigin(), pathpoint.origin ) >= goalradius ) + { + printt( guy, " scripted pathfinding stopped, quitting." ) + break + } + } + + if ( idleAnim != "" ) + { + guy.DisableBehavior( "Assault" ) + + while ( !guy.IsInterruptable() ) + wait 0.1 + + entity ref = CreateOwnedScriptMover( guy ) + thread PlayAnim( guy, idleAnim, ref, null, 0.4 ) + + WaitForever() + } + else + { + DeleteSkitGuy( info ) + } +} + + + +// ----------------------------- +// ----- TITAN GROUP SKITS ----- +// ----------------------------- +void function HangarTitanGroup_Init( HangarTitanGroup group ) +{ + group.rack_ogPos = group.rack.GetOrigin() + group.rack_ogAng = group.rack.GetAngles() + + if ( IsValid( group.titan ) ) + { + if ( group.titanSkin == -1 ) + group.titanSkin = 1 + } + + if ( group.marvinAnim != "" ) + { + group.marvin = CreatePropDynamic( MARVIN_MODEL ) + group.marvin.DisableHibernation() + } + + if ( group.pilotAnim != "" ) + { + group.pilot = CreatePropDynamic( group.pilotModel ) + group.pilot.DisableHibernation() + } + + HangarTitanGroup_SetMaxSequenceDuration( group ) + + group.isInited = true +} + +void function HangarTitanGroup_SetMaxSequenceDuration( HangarTitanGroup group ) +{ + table<string,entity> sceneActors = {} + sceneActors[ group.titanAnim ] <- group.titan + sceneActors[ group.rackAnim ] <- group.rack + sceneActors[ group.marvinAnim ] <- group.marvin + + if ( IsValid( group.titan ) ) + { + // set titan to use non posed model + group.titan.SetModel( BUDDY_MODEL ) + group.titan.SetSkin( group.titanSkin ) + } + + float maxDuration = 0 + foreach ( anim, actor in sceneActors ) + { + if ( !IsValid( actor ) ) + continue + + float animDuration = actor.GetSequenceDuration( anim ) + if ( animDuration > maxDuration ) + maxDuration = animDuration + } + + group.sequenceDuration = maxDuration + + if ( IsValid( group.titan ) ) + { + // set titan back to posed anim + group.titan.SetModel( BUDDY_MODEL_POSED_NO_ANIMS ) + //group.titan.SetSkin( group.titanSkin ) + } +} + +void function HangarTitanGroup_Animate( HangarTitanGroup group, string endFlag = "", float duration = -1, bool doCleanup = true ) +{ + if ( endFlag != "" ) + FlagEnd( endFlag ) + + Assert( group.isInited, "Need to call HangarTitanGroup_Init on this group before using" ) + + entity ref = group.ref + entity titan = group.titan + entity rack = group.rack + entity marvin = group.marvin + entity pilot = group.pilot + string titanAnim = group.titanAnim + string rackAnim = group.rackAnim + string marvinAnim = group.marvinAnim + string pilotAnim = group.pilotAnim + float animInitialTime = group.animInitialTime + + EndSignal( rack, "OnDestroy" ) + + if ( IsValid( marvin ) ) + EndSignal( marvin, "OnDestroy") + + if ( IsValid( titan ) ) + { + EndSignal( titan, "OnDestroy" ) + + // set titan to use non posed model + titan.SetModel( BUDDY_MODEL ) + titan.SetSkin( group.titanSkin ) + } + + OnThreadEnd( + function() : ( doCleanup, group ) + { + if ( doCleanup ) + HangarTitanGroup_Cleanup( group ) + } + ) + + if ( duration == -1 || group.sequenceDuration < duration ) + duration = group.sequenceDuration + + thread PlayAnim( rack, rackAnim, ref, null, 0.0, animInitialTime ) + + if ( IsValid( titan) ) + thread PlayAnim( titan, titanAnim, ref, null, 0.0, animInitialTime ) + + if ( IsValid( marvin ) ) + thread PlayAnim( marvin, marvinAnim, ref, null, 0.0, animInitialTime ) + + if ( IsValid( pilot ) ) + thread PlayAnim( pilot, pilotAnim, ref, null, 0.0, animInitialTime ) + + wait duration +} + +void function HangarTitanGroup_Cleanup( HangarTitanGroup group ) +{ + HangarTitanGroup_Reset( group ) + + if ( IsValid( group.marvin ) ) + group.marvin.Destroy() + + if ( IsValid( group.pilot ) ) + group.pilot.Destroy() +} + +void function HangarTitanGroup_Reset( HangarTitanGroup group ) +{ + entity titan = group.titan + entity rack = group.rack + + if ( IsValid( titan ) ) + { + titan.Anim_Stop() + titan.SetModel( BUDDY_MODEL_POSED_NO_ANIMS ) + //titan.SetSkin( group.titanSkin ) + } + + if ( IsValid( rack ) ) + { + rack.Anim_Stop() + rack.SetOrigin( group.rack_ogPos ) + rack.SetAngles( group.rack_ogAng ) + } +} + + + +#if DEV +void function skyboxchange( string tName ) +{ + entity cam = GetEnt( tName ) + GetPlayerArray()[0].SetSkyCamera( cam ) +} + + +// ====================================================== +// ============ GHOST RECORDER DEV FUNCTIONS ============ +// ====================================================== +void function wallruntest() +{ + entity preWallrunRef = GetEntByScriptName( "basic_movement_wallrun_start_ref" ) + var rec = LoadRecordedAnimation( $"anim_recording/training_record_zengarden_wallrun.rpak" ) + file.ogPilot.Anim_Stop() + file.ogPilot.PlayRecordedAnimation( rec, <0,0,0>, <0,0,0>, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME, preWallrunRef ) +} + +void function Record_ZenGarden_Wallrun() +{ + thread RecordAnimation_Think( "training_record_zengarden_wallrun", "basic_movement_wallrun_start_ref" ) +} + +void function Record_ZenGarden_Slide() +{ + thread RecordAnimation_Think( "training_record_zengarden_slide", "zengarden_slide_ref" ) +} + +void function Record_ZenGarden_DoubleJump() +{ + thread RecordAnimation_Think( "training_record_zengarden_doublejump", "zengarden_doublejump_ref" ) +} + +void function RecordAnimation_Think( string filename, string refName ) +{ + entity player = file.player + player.Signal( "RecordAnimation_Start" ) + player.EndSignal( "RecordAnimation_Start" ) + + TeleportPlayerAndBT( refName ) + + player.EndSignal( "OnDestroy" ) + + entity ref = GetEntByScriptName( refName ) + + printt( "READY TO RECORD: " + filename ) + + //start recording + player.WaitSignal( "ButtonPressedAttack" ) + printt( "RECORDING STARTED" ) + + player.StartRecordingAnimation( ref.GetOrigin(), ref.GetAngles() ) + + //stop + player.WaitSignal( "ButtonPressedAttack" ) + + var recording = player.StopRecordingAnimation() + +#if PC_PROG + SaveRecordedAnimation( recording, filename ) +#endif + printt( "STOP RECORD player org/ang:", player.GetOrigin(), player.GetAngles() ) +} +#endif //DEV |