diff options
author | BobTheBob9 <for.oliver.kirkham@gmail.com> | 2022-07-15 21:19:14 +0100 |
---|---|---|
committer | BobTheBob9 <for.oliver.kirkham@gmail.com> | 2022-07-15 21:19:14 +0100 |
commit | e158a247522230d97c5b16ed62af1969c626fd73 (patch) | |
tree | 111fcf6d7e8077d6c9fc9a8eb501c1d07cda1bac | |
parent | 85eb27362bd295a9e1560982a3369a688e13ded0 (diff) | |
download | NorthstarMods-e158a247522230d97c5b16ed62af1969c626fd73.tar.gz NorthstarMods-e158a247522230d97c5b16ed62af1969c626fd73.zip |
fix race condition crash when boarding dropship as it explodesevac-crash-fix-pr
-rw-r--r-- | Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut | 858 |
1 files changed, 430 insertions, 428 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut index bce8b4c7..da623dbf 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut @@ -1,428 +1,430 @@ -untyped -global function Evac_Init -global function AddEvacNode -global function SetEvacSpaceNode -global function IsEvacDropship - -global function EvacEpilogueSetup -global function Evac - -const float EVAC_INITIAL_WAIT = 5.0 -const float EVAC_ARRIVAL_TIME = 40.0 -const float EVAC_WAIT_TIME = 18.0 - -const int EVAC_SHIP_HEALTH = 25000 -const int EVAC_SHIP_SHIELDS = 2000 - -// we don't use these because they're busted, just keeping them -const array<string> EVAC_EMBARK_ANIMS_1P = [ - "ptpov_e3_rescue_side_embark_A", - "ptpov_e3_rescue_side_embark_B", - "ptpov_e3_rescue_side_embark_C", - "ptpov_e3_rescue_side_embark_D", - "ptpov_e3_rescue_side_embark_E", - "ptpov_e3_rescue_side_embark_F", - "ptpov_e3_rescue_side_embark_G", - "ptpov_e3_rescue_side_embark_H" -] - -const array<string> EVAC_EMBARK_ANIMS_3P = [ - "pt_e3_rescue_side_embark_A", - "pt_e3_rescue_side_embark_B", - "pt_e3_rescue_side_embark_C", - "pt_e3_rescue_side_embark_D", - "pt_e3_rescue_side_embark_E", - "pt_e3_rescue_side_embark_F", - "pt_e3_rescue_side_embark_G", - "pt_e3_rescue_side_embark_H" -] - -const array<string> EVAC_IDLE_ANIMS_1P = [ - "ptpov_e3_rescue_side_embark_A_idle", - "ptpov_e3_rescue_side_embark_B_idle", - "ptpov_e3_rescue_side_embark_C_idle", - "ptpov_e3_rescue_side_embark_D_idle", - "ptpov_e3_rescue_side_embark_E_idle", - "ptpov_e3_rescue_side_embark_F_idle", - "ptpov_e3_rescue_side_embark_G_idle", - "ptpov_e3_rescue_side_embark_H_idle" -] - -const array<string> EVAC_IDLE_ANIMS_3P = [ - "pt_e3_rescue_side_idle_A", - "pt_e3_rescue_side_idle_B", - "pt_e3_rescue_side_idle_C", - "pt_e3_rescue_side_idle_D", - "pt_e3_rescue_side_idle_E", - "pt_e3_rescue_side_idle_F", - "pt_e3_rescue_side_idle_G", - "pt_e3_rescue_side_idle_H" -] - -struct { - array<entity> evacNodes - entity spaceNode - - // this only supports 1 evac at once atm, is this ideal? - entity currentEvacNode - entity evacDropship - entity evacIcon -} file - -void function Evac_Init() -{ - EvacShared_Init() - RegisterSignal( "EvacShipLeaves" ) - RegisterSignal( "EvacOver" ) -} - -void function AddEvacNode( entity evacNode ) -{ - file.evacNodes.append( evacNode ) -} - -void function SetEvacSpaceNode( entity spaceNode ) -{ - file.spaceNode = spaceNode -} - -bool function IsEvacDropship( entity ent ) -{ - return file.evacDropship == ent && IsValid( file.evacDropship ) -} - -// evac epilogue -void function EvacEpilogueSetup() -{ - // don't do much here because other scripts should be able to call evac stuff themselves - AddCallback_GameStateEnter( eGameState.Epilogue, EvacEpilogue ) -} - -void function EvacEpilogue() -{ - int winner = GetWinningTeam() - - // make sure we don't run evac if it won't be supported for current map/gamestate - bool canRunEvac = GetCurrentPlaylistVarInt( "max_teams", 2 ) == 2 && - ( winner == TEAM_MILITIA || winner == TEAM_IMC ) && - ( file.evacNodes.len() > 0 || IsValid( GetEnt( "escape_node1" ) ) ) // code assumes escape_node1 should be first node if automatically registering - - if ( canRunEvac ) - { - thread SetRespawnAndWait( false ) - thread Evac( GetOtherTeam( winner ), EVAC_INITIAL_WAIT, EVAC_ARRIVAL_TIME, EVAC_WAIT_TIME, EvacEpiloguePlayerCanBoard, EvacEpilogueShouldLeaveEarly, EvacEpilogueCompleted ) - } - else - { - thread SetRespawnAndWait( false ) //prevent respawns during the fade to black, should only be an issue if the match is a draw - thread EvacEpilogueCompleted( null ) //this is hacky but like, this also shouldn't really be hit in normal gameplay - } -} - -void function EvacSpectatorFunc( entity player ) -{ - svGlobal.levelEnt.EndSignal( "GameStateChanged" ) - file.evacDropship.EndSignal( "OnDestroy" ) - - entity cam = GetEnt( expect string( file.currentEvacNode.kv.target ) ) - if ( !IsValid( cam ) ) - return - - player.SetObserverModeStaticPosition( cam.GetOrigin() ) - player.SetObserverModeStaticAngles( cam.GetAngles() ) - player.StartObserverMode( OBS_MODE_STATIC ) - - file.evacDropship.WaitSignal( "EvacOver" ) -} - -void function SetRespawnAndWait( bool mode ) -{ - wait GAME_EPILOGUE_PLAYER_RESPAWN_LEEWAY - SetRespawnsEnabled( mode ) -} - -bool function EvacEpiloguePlayerCanBoard( entity dropship, entity player ) -{ - // can't board a dropship on a different team - if ( dropship.GetTeam() != player.GetTeam() ) - return false - - // check if there are any free slots on the dropship, if there are then they can board - foreach ( entity player in dropship.s.evacSlots ) - if ( !IsValid( player ) ) - return true - - // no empty slots - return false -} - -bool function EvacEpilogueShouldLeaveEarly( entity dropship ) -{ - int numEvacing - foreach ( entity player in dropship.s.evacSlots ) - if ( IsValid( player ) ) - numEvacing++ - - return GetPlayerArrayOfTeam_Alive( dropship.GetTeam() ).len() == numEvacing || numEvacing == dropship.s.evacSlots.len() -} - -void function EvacEpilogueCompleted( entity dropship ) -{ - wait 5.0 - - foreach ( entity player in GetPlayerArray() ) - ScreenFadeToBlackForever( player, 2.0 ) - - wait 2.0 - SetGameState( eGameState.Postmatch ) -} - -// global evac func, anything can call this, it's not necessarily an epilogue thing -void function Evac( int evacTeam, float initialWait, float arrivalTime, float waitTime, bool functionref( entity, entity ) canBoardCallback, bool functionref( entity ) shouldLeaveEarlyCallback, void functionref( entity ) completionCallback, entity customEvacNode = null ) -{ - wait initialWait - - // setup evac nodes if not manually registered - if ( file.evacNodes.len() == 0 && !IsValid( customEvacNode ) ) - { - for ( int i = 1; ; i++ ) - { - entity newNode = GetEnt( "escape_node" + i ) - if ( !IsValid( newNode ) ) - break - - file.evacNodes.append( newNode ) - } - } - - // setup space node if not manually registered - if ( !IsValid( file.spaceNode ) ) - file.spaceNode = GetEnt( "spaceNode" ) - - entity evacNode = customEvacNode - if ( !IsValid( customEvacNode ) ) - evacNode = file.evacNodes.getrandom() - - file.currentEvacNode = evacNode - - // setup client evac position - file.evacIcon = CreateEntity( "info_target" ) - file.evacIcon.SetOrigin( evacNode.GetOrigin() ) - file.evacIcon.kv.spawnFlags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT - DispatchSpawn( file.evacIcon ) - file.evacIcon.DisableHibernation() - - wait 0.5 // need to wait here, or the target won't appear on clients for some reason - // eta until arrive - SetTeamActiveObjective( evacTeam, "EG_DropshipExtract", Time() + arrivalTime, file.evacIcon ) - SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract", Time() + arrivalTime, file.evacIcon ) - - // would've liked to use cd_dropship_rescue_side_start length here, but can't since this is done before dropship spawn, can't - wait arrivalTime - 4.33333 - - entity dropship = CreateDropship( evacTeam, evacNode.GetOrigin(), evacNode.GetAngles() ) - dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) - dropship.SetValueForModelKey( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) - dropship.SetMaxHealth( EVAC_SHIP_HEALTH ) - dropship.SetHealth( EVAC_SHIP_HEALTH ) - dropship.SetShieldHealth( EVAC_SHIP_SHIELDS ) - SetTargetName( dropship, "#NPC_EVAC_DROPSHIP" ) - DispatchSpawn( dropship ) - dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) - AddEntityCallback_OnKilled( dropship, EvacDropshipKilled ) - - dropship.s.evacSlots <- [ null, null, null, null, null, null, null, null ] - file.evacDropship = dropship - - dropship.EndSignal( "OnDestroy" ) - OnThreadEnd( function() : ( evacTeam, completionCallback, dropship ) - { - if ( "evacTrigger" in dropship.s ) - dropship.s.evacTrigger.Destroy() - - // this should be for both teams - SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipDestroyed" ) - SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_DropshipExtractDropshipDestroyed" ) - - foreach ( entity player in dropship.s.evacSlots ) - { - if ( !IsValid( player ) ) - continue - - player.ClearInvulnerable() - } - - // this is called whether dropship is destroyed or evac finishes, callback can handle this itself - thread completionCallback( dropship ) - }) - - // flyin - Spectator_SetCustomSpectatorFunc( EvacSpectatorFunc ) - thread PlayAnim( dropship, "cd_dropship_rescue_side_start", evacNode ) - - // calculate time until idle start - float sequenceDuration = dropship.GetSequenceDuration( "cd_dropship_rescue_side_start" ) - float cycleFrac = dropship.GetScriptedAnimEventCycleFrac( "cd_dropship_rescue_side_start", "ReadyToLoad" ) - wait sequenceDuration * cycleFrac - - thread PlayAnim( dropship, "cd_dropship_rescue_side_idle", evacNode ) - - // eta until leave - SetTeamActiveObjective( evacTeam, "EG_DropshipExtract2", Time() + EVAC_WAIT_TIME, file.evacIcon ) - SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract2", Time() + EVAC_WAIT_TIME, file.evacIcon ) - - // setup evac trigger - entity trigger = CreateEntity( "trigger_cylinder" ) - trigger.SetRadius( 150 ) - trigger.SetAboveHeight( 100 ) - trigger.SetBelowHeight( 100 ) - trigger.SetOrigin( dropship.GetOrigin() ) - trigger.SetParent( dropship, "ORIGIN" ) - DispatchSpawn( trigger ) - // have to do this inline since we capture the completionCallback - trigger.SetEnterCallback( void function( entity trigger, entity player ) : ( canBoardCallback, dropship ) - { - if ( !player.IsPlayer() || !IsAlive( player ) || player.IsTitan() || player.ContextAction_IsBusy() || !canBoardCallback( dropship, player ) || PlayerInDropship( player, dropship ) ) - return - - thread AddPlayerToEvacDropship( dropship, player ) - }) - - dropship.s.evacTrigger <- trigger - - float waitStartTime = Time() - while ( Time() - waitStartTime < waitTime ) - { - if ( shouldLeaveEarlyCallback( dropship ) ) - break - - WaitFrame() - } - - // holster all weapons - foreach ( entity player in dropship.s.evacSlots ) - if ( IsValid( player ) ) - player.HolsterWeapon() - - // fly away - dropship.Signal( "EvacShipLeaves" ) - thread PlayAnim( dropship, "cd_dropship_rescue_side_end", evacNode ) - - SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipFlyingAway" ) - SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtractDropshipFlyingAway" ) - - // todo: play the warpout effect somewhere here - - wait dropship.GetSequenceDuration( "cd_dropship_rescue_side_end" ) - WARPINFXTIME - - foreach ( entity player in dropship.s.evacSlots ) - if ( IsValid( player ) ) - Remote_CallFunction_NonReplay( player, "ServerCallback_PlayScreenFXWarpJump" ) - - wait WARPINFXTIME - - // go to space - - // hardcoded angles here are a hack, spacenode position doesn't face the planet in the skybox, for some reason - // nvm removing for now - //file.spaceNode.SetAngles( < 30, -75, 20 > ) - - dropship.SetOrigin( file.spaceNode.GetOrigin() ) - dropship.SetAngles( file.spaceNode.GetAngles() ) - dropship.SetInvulnerable() - dropship.Signal( "EvacOver" ) - thread PlayAnim( dropship, "ds_space_flyby_dropshipA", file.spaceNode ) - - foreach( entity player in GetPlayerArray() ) - { - // evac-ed players only beyond this point - if ( !PlayerInDropship( player, dropship ) ) - { - if ( player.GetTeam() == dropship.GetTeam() ) - SetPlayerActiveObjective( player, "EG_DropshipExtractFailedEscape" ) - - continue - } - - SetPlayerActiveObjective( player, "EG_DropshipExtractSuccessfulEscape" ) - - // skybox - player.SetSkyCamera( GetEnt( SKYBOXSPACE ) ) - Remote_CallFunction_NonReplay( player, "ServerCallback_DisableHudForEvac" ) - Remote_CallFunction_NonReplay( player, "ServerCallback_SetClassicSkyScale", dropship.GetEncodedEHandle(), 0.7 ) - Remote_CallFunction_NonReplay( player, "ServerCallback_SetMapSettings", 4.0, false, 0.4, 0.125 ) - - // display player [Evacuated] in killfeed - foreach ( entity otherPlayer in GetPlayerArray() ) - Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_EvacObit", player.GetEncodedEHandle() ) - } - -} - -void function AddPlayerToEvacDropship( entity dropship, entity player ) -{ - int slot = RandomInt( dropship.s.evacSlots.len() ) - for ( int i = 0; i < dropship.s.evacSlots.len(); i++ ) - { - if ( !IsValid( dropship.s.evacSlots[ slot ] ) ) - { - dropship.s.evacSlots[ slot ] = player - break - } - - slot = ( slot + 1 ) % expect int( dropship.s.evacSlots.len() ) - } - - // no slots available - if ( !PlayerInDropship( player, dropship ) ) - return - - player.SetInvulnerable() - player.UnforceCrouch() - player.ForceStand() - - FirstPersonSequenceStruct fp - //fp.firstPersonAnim = EVAC_EMBARK_ANIMS_1P[ slot ] - fp.thirdPersonAnim = EVAC_EMBARK_ANIMS_3P[ slot ] - fp.attachment = "RESCUE" - fp.teleport = true - fp.thirdPersonCameraAttachments = [ "VDU" ] // this seems wrong, firstperson anim has better angles, but no head - - EmitSoundOnEntityOnlyToPlayer( player, player, SHIFTER_START_SOUND_3P ) - // should play SHIFTER_START_SOUND_1P when they actually arrive in the ship i think, unsure how this is supposed to be done - PlayPhaseShiftDisappearFX( player ) - waitthread FirstPersonSequence( fp, player, dropship ) - - FirstPersonSequenceStruct idleFp - idleFp.firstPersonAnimIdle = EVAC_IDLE_ANIMS_1P[ slot ] - idleFp.thirdPersonAnimIdle = EVAC_IDLE_ANIMS_3P[ slot ] - idleFp.attachment = "RESCUE" - idleFp.teleport = true - idleFp.hideProxy = true - idleFp.viewConeFunction = ViewConeWide - - thread FirstPersonSequence( idleFp, player, dropship ) - ViewConeWide( player ) // gotta do this after for some reason, adding it to idleFp does not work for some reason -} - -bool function PlayerInDropship( entity player, entity dropship ) -{ - // couldn't get "player in dropship.s.evacSlots" to work for some reason, likely due to static typing? - foreach ( entity dropshipPlayer in dropship.s.evacSlots ) - if ( dropshipPlayer == player ) - return true - - return false -} - -void function EvacDropshipKilled( entity dropship, var damageInfo ) -{ - foreach ( entity player in dropship.s.evacSlots ) - { - if ( IsValid( player ) ) - { - player.ClearParent() - player.Die( DamageInfo_GetAttacker( damageInfo ), DamageInfo_GetWeapon( damageInfo ), { damageSourceId = eDamageSourceId.evac_dropship_explosion, scriptType = DF_GIB } ) - } - } -} +untyped
+global function Evac_Init
+global function AddEvacNode
+global function SetEvacSpaceNode
+global function IsEvacDropship
+
+global function EvacEpilogueSetup
+global function Evac
+
+const float EVAC_INITIAL_WAIT = 5.0
+const float EVAC_ARRIVAL_TIME = 40.0
+const float EVAC_WAIT_TIME = 18.0
+
+const int EVAC_SHIP_HEALTH = 25000
+const int EVAC_SHIP_SHIELDS = 2000
+
+// we don't use these because they're busted, just keeping them
+const array<string> EVAC_EMBARK_ANIMS_1P = [
+ "ptpov_e3_rescue_side_embark_A",
+ "ptpov_e3_rescue_side_embark_B",
+ "ptpov_e3_rescue_side_embark_C",
+ "ptpov_e3_rescue_side_embark_D",
+ "ptpov_e3_rescue_side_embark_E",
+ "ptpov_e3_rescue_side_embark_F",
+ "ptpov_e3_rescue_side_embark_G",
+ "ptpov_e3_rescue_side_embark_H"
+]
+
+const array<string> EVAC_EMBARK_ANIMS_3P = [
+ "pt_e3_rescue_side_embark_A",
+ "pt_e3_rescue_side_embark_B",
+ "pt_e3_rescue_side_embark_C",
+ "pt_e3_rescue_side_embark_D",
+ "pt_e3_rescue_side_embark_E",
+ "pt_e3_rescue_side_embark_F",
+ "pt_e3_rescue_side_embark_G",
+ "pt_e3_rescue_side_embark_H"
+]
+
+const array<string> EVAC_IDLE_ANIMS_1P = [
+ "ptpov_e3_rescue_side_embark_A_idle",
+ "ptpov_e3_rescue_side_embark_B_idle",
+ "ptpov_e3_rescue_side_embark_C_idle",
+ "ptpov_e3_rescue_side_embark_D_idle",
+ "ptpov_e3_rescue_side_embark_E_idle",
+ "ptpov_e3_rescue_side_embark_F_idle",
+ "ptpov_e3_rescue_side_embark_G_idle",
+ "ptpov_e3_rescue_side_embark_H_idle"
+]
+
+const array<string> EVAC_IDLE_ANIMS_3P = [
+ "pt_e3_rescue_side_idle_A",
+ "pt_e3_rescue_side_idle_B",
+ "pt_e3_rescue_side_idle_C",
+ "pt_e3_rescue_side_idle_D",
+ "pt_e3_rescue_side_idle_E",
+ "pt_e3_rescue_side_idle_F",
+ "pt_e3_rescue_side_idle_G",
+ "pt_e3_rescue_side_idle_H"
+]
+
+struct {
+ array<entity> evacNodes
+ entity spaceNode
+
+ // this only supports 1 evac at once atm, is this ideal?
+ entity currentEvacNode
+ entity evacDropship
+ entity evacIcon
+} file
+
+void function Evac_Init()
+{
+ EvacShared_Init()
+ RegisterSignal( "EvacShipLeaves" )
+ RegisterSignal( "EvacOver" )
+}
+
+void function AddEvacNode( entity evacNode )
+{
+ file.evacNodes.append( evacNode )
+}
+
+void function SetEvacSpaceNode( entity spaceNode )
+{
+ file.spaceNode = spaceNode
+}
+
+bool function IsEvacDropship( entity ent )
+{
+ return file.evacDropship == ent && IsValid( file.evacDropship )
+}
+
+// evac epilogue
+void function EvacEpilogueSetup()
+{
+ // don't do much here because other scripts should be able to call evac stuff themselves
+ AddCallback_GameStateEnter( eGameState.Epilogue, EvacEpilogue )
+}
+
+void function EvacEpilogue()
+{
+ int winner = GetWinningTeam()
+
+ // make sure we don't run evac if it won't be supported for current map/gamestate
+ bool canRunEvac = GetCurrentPlaylistVarInt( "max_teams", 2 ) == 2 &&
+ ( winner == TEAM_MILITIA || winner == TEAM_IMC ) &&
+ ( file.evacNodes.len() > 0 || IsValid( GetEnt( "escape_node1" ) ) ) // code assumes escape_node1 should be first node if automatically registering
+
+ if ( canRunEvac )
+ {
+ thread SetRespawnAndWait( false )
+ thread Evac( GetOtherTeam( winner ), EVAC_INITIAL_WAIT, EVAC_ARRIVAL_TIME, EVAC_WAIT_TIME, EvacEpiloguePlayerCanBoard, EvacEpilogueShouldLeaveEarly, EvacEpilogueCompleted )
+ }
+ else
+ {
+ thread SetRespawnAndWait( false ) //prevent respawns during the fade to black, should only be an issue if the match is a draw
+ thread EvacEpilogueCompleted( null ) //this is hacky but like, this also shouldn't really be hit in normal gameplay
+ }
+}
+
+void function EvacSpectatorFunc( entity player )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+ file.evacDropship.EndSignal( "OnDestroy" )
+
+ entity cam = GetEnt( expect string( file.currentEvacNode.kv.target ) )
+ if ( !IsValid( cam ) )
+ return
+
+ player.SetObserverModeStaticPosition( cam.GetOrigin() )
+ player.SetObserverModeStaticAngles( cam.GetAngles() )
+ player.StartObserverMode( OBS_MODE_STATIC )
+
+ file.evacDropship.WaitSignal( "EvacOver" )
+}
+
+void function SetRespawnAndWait( bool mode )
+{
+ wait GAME_EPILOGUE_PLAYER_RESPAWN_LEEWAY
+ SetRespawnsEnabled( mode )
+}
+
+bool function EvacEpiloguePlayerCanBoard( entity dropship, entity player )
+{
+ // can't board a dropship on a different team
+ if ( dropship.GetTeam() != player.GetTeam() )
+ return false
+
+ // check if there are any free slots on the dropship, if there are then they can board
+ foreach ( entity player in dropship.s.evacSlots )
+ if ( !IsValid( player ) )
+ return true
+
+ // no empty slots
+ return false
+}
+
+bool function EvacEpilogueShouldLeaveEarly( entity dropship )
+{
+ int numEvacing
+ foreach ( entity player in dropship.s.evacSlots )
+ if ( IsValid( player ) )
+ numEvacing++
+
+ return GetPlayerArrayOfTeam_Alive( dropship.GetTeam() ).len() == numEvacing || numEvacing == dropship.s.evacSlots.len()
+}
+
+void function EvacEpilogueCompleted( entity dropship )
+{
+ wait 5.0
+
+ foreach ( entity player in GetPlayerArray() )
+ ScreenFadeToBlackForever( player, 2.0 )
+
+ wait 2.0
+ SetGameState( eGameState.Postmatch )
+}
+
+// global evac func, anything can call this, it's not necessarily an epilogue thing
+void function Evac( int evacTeam, float initialWait, float arrivalTime, float waitTime, bool functionref( entity, entity ) canBoardCallback, bool functionref( entity ) shouldLeaveEarlyCallback, void functionref( entity ) completionCallback, entity customEvacNode = null )
+{
+ wait initialWait
+
+ // setup evac nodes if not manually registered
+ if ( file.evacNodes.len() == 0 && !IsValid( customEvacNode ) )
+ {
+ for ( int i = 1; ; i++ )
+ {
+ entity newNode = GetEnt( "escape_node" + i )
+ if ( !IsValid( newNode ) )
+ break
+
+ file.evacNodes.append( newNode )
+ }
+ }
+
+ // setup space node if not manually registered
+ if ( !IsValid( file.spaceNode ) )
+ file.spaceNode = GetEnt( "spaceNode" )
+
+ entity evacNode = customEvacNode
+ if ( !IsValid( customEvacNode ) )
+ evacNode = file.evacNodes.getrandom()
+
+ file.currentEvacNode = evacNode
+
+ // setup client evac position
+ file.evacIcon = CreateEntity( "info_target" )
+ file.evacIcon.SetOrigin( evacNode.GetOrigin() )
+ file.evacIcon.kv.spawnFlags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ DispatchSpawn( file.evacIcon )
+ file.evacIcon.DisableHibernation()
+
+ wait 0.5 // need to wait here, or the target won't appear on clients for some reason
+ // eta until arrive
+ SetTeamActiveObjective( evacTeam, "EG_DropshipExtract", Time() + arrivalTime, file.evacIcon )
+ SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract", Time() + arrivalTime, file.evacIcon )
+
+ // would've liked to use cd_dropship_rescue_side_start length here, but can't since this is done before dropship spawn, can't
+ wait arrivalTime - 4.33333
+
+ entity dropship = CreateDropship( evacTeam, evacNode.GetOrigin(), evacNode.GetAngles() )
+ dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+ dropship.SetValueForModelKey( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+ dropship.SetMaxHealth( EVAC_SHIP_HEALTH )
+ dropship.SetHealth( EVAC_SHIP_HEALTH )
+ dropship.SetShieldHealth( EVAC_SHIP_SHIELDS )
+ SetTargetName( dropship, "#NPC_EVAC_DROPSHIP" )
+ DispatchSpawn( dropship )
+ dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+ AddEntityCallback_OnKilled( dropship, EvacDropshipKilled )
+
+ dropship.s.evacSlots <- [ null, null, null, null, null, null, null, null ]
+ file.evacDropship = dropship
+
+ dropship.EndSignal( "OnDestroy" )
+ OnThreadEnd( function() : ( evacTeam, completionCallback, dropship )
+ {
+ if ( "evacTrigger" in dropship.s )
+ dropship.s.evacTrigger.Destroy()
+
+ // this should be for both teams
+ SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipDestroyed" )
+ SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_DropshipExtractDropshipDestroyed" )
+
+ foreach ( entity player in dropship.s.evacSlots )
+ {
+ if ( !IsValid( player ) )
+ continue
+
+ player.ClearInvulnerable()
+ }
+
+ // this is called whether dropship is destroyed or evac finishes, callback can handle this itself
+ thread completionCallback( dropship )
+ })
+
+ // flyin
+ Spectator_SetCustomSpectatorFunc( EvacSpectatorFunc )
+ thread PlayAnim( dropship, "cd_dropship_rescue_side_start", evacNode )
+
+ // calculate time until idle start
+ float sequenceDuration = dropship.GetSequenceDuration( "cd_dropship_rescue_side_start" )
+ float cycleFrac = dropship.GetScriptedAnimEventCycleFrac( "cd_dropship_rescue_side_start", "ReadyToLoad" )
+ wait sequenceDuration * cycleFrac
+
+ thread PlayAnim( dropship, "cd_dropship_rescue_side_idle", evacNode )
+
+ // eta until leave
+ SetTeamActiveObjective( evacTeam, "EG_DropshipExtract2", Time() + EVAC_WAIT_TIME, file.evacIcon )
+ SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract2", Time() + EVAC_WAIT_TIME, file.evacIcon )
+
+ // setup evac trigger
+ entity trigger = CreateEntity( "trigger_cylinder" )
+ trigger.SetRadius( 150 )
+ trigger.SetAboveHeight( 100 )
+ trigger.SetBelowHeight( 100 )
+ trigger.SetOrigin( dropship.GetOrigin() )
+ trigger.SetParent( dropship, "ORIGIN" )
+ DispatchSpawn( trigger )
+ // have to do this inline since we capture the completionCallback
+ trigger.SetEnterCallback( void function( entity trigger, entity player ) : ( canBoardCallback, dropship )
+ {
+ if ( !player.IsPlayer() || !IsAlive( player ) || player.IsTitan() || player.ContextAction_IsBusy() || !canBoardCallback( dropship, player ) || PlayerInDropship( player, dropship ) )
+ return
+
+ thread AddPlayerToEvacDropship( dropship, player )
+ })
+
+ dropship.s.evacTrigger <- trigger
+
+ float waitStartTime = Time()
+ while ( Time() - waitStartTime < waitTime )
+ {
+ if ( shouldLeaveEarlyCallback( dropship ) )
+ break
+
+ WaitFrame()
+ }
+
+ // holster all weapons
+ foreach ( entity player in dropship.s.evacSlots )
+ if ( IsValid( player ) )
+ player.HolsterWeapon()
+
+ // fly away
+ dropship.Signal( "EvacShipLeaves" )
+ thread PlayAnim( dropship, "cd_dropship_rescue_side_end", evacNode )
+
+ SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipFlyingAway" )
+ SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtractDropshipFlyingAway" )
+
+ // todo: play the warpout effect somewhere here
+
+ wait dropship.GetSequenceDuration( "cd_dropship_rescue_side_end" ) - WARPINFXTIME
+
+ foreach ( entity player in dropship.s.evacSlots )
+ if ( IsValid( player ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayScreenFXWarpJump" )
+
+ wait WARPINFXTIME
+
+ // go to space
+
+ // hardcoded angles here are a hack, spacenode position doesn't face the planet in the skybox, for some reason
+ // nvm removing for now
+ //file.spaceNode.SetAngles( < 30, -75, 20 > )
+
+ dropship.SetOrigin( file.spaceNode.GetOrigin() )
+ dropship.SetAngles( file.spaceNode.GetAngles() )
+ dropship.SetInvulnerable()
+ dropship.Signal( "EvacOver" )
+ thread PlayAnim( dropship, "ds_space_flyby_dropshipA", file.spaceNode )
+
+ foreach( entity player in GetPlayerArray() )
+ {
+ // evac-ed players only beyond this point
+ if ( !PlayerInDropship( player, dropship ) )
+ {
+ if ( player.GetTeam() == dropship.GetTeam() )
+ SetPlayerActiveObjective( player, "EG_DropshipExtractFailedEscape" )
+
+ continue
+ }
+
+ SetPlayerActiveObjective( player, "EG_DropshipExtractSuccessfulEscape" )
+
+ // skybox
+ player.SetSkyCamera( GetEnt( SKYBOXSPACE ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_DisableHudForEvac" )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetClassicSkyScale", dropship.GetEncodedEHandle(), 0.7 )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetMapSettings", 4.0, false, 0.4, 0.125 )
+
+ // display player [Evacuated] in killfeed
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_EvacObit", player.GetEncodedEHandle() )
+ }
+}
+
+void function AddPlayerToEvacDropship( entity dropship, entity player )
+{
+ int slot = RandomInt( dropship.s.evacSlots.len() )
+ for ( int i = 0; i < dropship.s.evacSlots.len(); i++ )
+ {
+ if ( !IsValid( dropship.s.evacSlots[ slot ] ) )
+ {
+ dropship.s.evacSlots[ slot ] = player
+ break
+ }
+
+ slot = ( slot + 1 ) % expect int( dropship.s.evacSlots.len() )
+ }
+
+ // no slots available
+ if ( !PlayerInDropship( player, dropship ) )
+ return
+
+ // need to cancel if the dropship dies
+ dropship.EndSignal( "OnDeath", "OnDestroy" )
+
+ player.SetInvulnerable()
+ player.UnforceCrouch()
+ player.ForceStand()
+
+ FirstPersonSequenceStruct fp
+ //fp.firstPersonAnim = EVAC_EMBARK_ANIMS_1P[ slot ]
+ fp.thirdPersonAnim = EVAC_EMBARK_ANIMS_3P[ slot ]
+ fp.attachment = "RESCUE"
+ fp.teleport = true
+ fp.thirdPersonCameraAttachments = [ "VDU" ] // this seems wrong, firstperson anim has better angles, but no head
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, SHIFTER_START_SOUND_3P )
+ // should play SHIFTER_START_SOUND_1P when they actually arrive in the ship i think, unsure how this is supposed to be done
+ PlayPhaseShiftDisappearFX( player )
+ FirstPersonSequence( fp, player, dropship )
+
+ FirstPersonSequenceStruct idleFp
+ idleFp.firstPersonAnimIdle = EVAC_IDLE_ANIMS_1P[ slot ]
+ idleFp.thirdPersonAnimIdle = EVAC_IDLE_ANIMS_3P[ slot ]
+ idleFp.attachment = "RESCUE"
+ idleFp.teleport = true
+ idleFp.hideProxy = true
+ idleFp.viewConeFunction = ViewConeWide
+
+ thread FirstPersonSequence( idleFp, player, dropship )
+ ViewConeWide( player ) // gotta do this after for some reason, adding it to idleFp does not work for some reason
+}
+
+bool function PlayerInDropship( entity player, entity dropship )
+{
+ // couldn't get "player in dropship.s.evacSlots" to work for some reason, likely due to static typing?
+ foreach ( entity dropshipPlayer in dropship.s.evacSlots )
+ if ( dropshipPlayer == player )
+ return true
+
+ return false
+}
+
+void function EvacDropshipKilled( entity dropship, var damageInfo )
+{
+ foreach ( entity player in dropship.s.evacSlots )
+ {
+ if ( IsValid( player ) )
+ {
+ player.ClearParent()
+ player.Die( DamageInfo_GetAttacker( damageInfo ), DamageInfo_GetWeapon( damageInfo ), { damageSourceId = eDamageSourceId.evac_dropship_explosion, scriptType = DF_GIB } )
+ }
+ }
+}
|