diff options
Diffstat (limited to 'Northstar.CustomServers/mod/scripts')
13 files changed, 478 insertions, 306 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut index 2b7b90b3..58d38ce7 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut @@ -221,14 +221,12 @@ bool function ClientCommandCallback_SetFactionChoicePersistenceSlot( entity play bool function ClientCommandCallback_LoadoutMenuClosed( entity player, array<string> args ) { - SavePdataForEntityIndex( player.GetPlayerIndex() ) TryGivePilotLoadoutForGracePeriod( player ) return true } bool function ClientCommandCallback_InGameMPMenuClosed( entity player, array<string> args ) { - SavePdataForEntityIndex( player.GetPlayerIndex() ) //TryGivePilotLoadoutForGracePeriod( player ) return true } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut index c116ac33..d0820dd6 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut @@ -7,11 +7,18 @@ void function MenuCallbacks_Init() bool function ClientCommandCallback_LeaveMatch( entity player, array<string> args ) { - // todo: ideally, it'd be nice to get clients to return to lobby here, rather than just dcing them - // kind of a pain tho, since we'd have to get it to call script code without a remote func, since that'd break compatibility + thread WritePersistenceAndLeave( player ) + return true +} - ClientCommand( player, "disconnect" ) - //ClientCommand( player, "setplaylist tdm; map mp_lobby" ) +void function WritePersistenceAndLeave( entity player ) +{ + // write player persistence before we leave, since leaving player might load local lobby before server writes persistence, so they won't get newest + // not super essential, but a nice qol thing + NSEarlyWritePlayerPersistenceForLeave( player ) + while ( NSIsWritingPlayerPersistence() ) + WaitFrame() - return true + // this is a custom concommand which can be called on clients, it causes them to leave and doesn't have issues if they're host + ClientCommand( player, "ns_leave_to_lobby" ) }
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut index ba473cae..c0242cc1 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut @@ -1,315 +1,360 @@ untyped - global function Evac_Init -global function Evac_AddLocation -global function Evac_SetSpacePosition -global function Evac_SetEnabled -global function Evac_IsEnabled +global function AddEvacNode +global function SetEvacSpaceNode global function IsEvacDropship -global function EvacMain +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 -struct { - bool enabled = true +// 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" +] - array<Point> evacPoints - Point spacePosition +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 entity evacDropship - array<entity> evacPlayers + entity evacIcon } file void function Evac_Init() { EvacShared_Init() - - AddCallback_GameStateEnter( eGameState.Epilogue, Evac_OnEpilogue ) } -void function Evac_SetEnabled( bool enabled ) +void function AddEvacNode( entity evacNode ) { - file.enabled = enabled + file.evacNodes.append( evacNode ) } -bool function Evac_IsEnabled() +void function SetEvacSpaceNode( entity spaceNode ) { - return false // shit is busted rn lol - //return file.enabled && GetClassicMPMode() && !IsRoundBased() + file.spaceNode = spaceNode } -void function Evac_AddLocation( vector origin, vector angles ) +bool function IsEvacDropship( entity ent ) { - Point evacPoint - evacPoint.origin = origin - evacPoint.angles = angles - - file.evacPoints.append( evacPoint ) + return file.evacDropship == ent && IsValid( file.evacDropship ) } -void function Evac_SetSpacePosition( vector origin, vector angles ) +// evac epilogue +void function EvacEpilogueSetup() { - file.spacePosition.origin = origin - file.spacePosition.angles = angles + AddCallback_GameStateEnter( eGameState.Epilogue, EvacEpilogue ) } -bool function IsEvacDropship( entity ent ) +void function EvacEpilogue() { - return file.evacDropship == ent && IsValid( file.evacDropship ) + thread Evac( GetPlayerArray()[0].GetTeam(), EVAC_INITIAL_WAIT, EVAC_ARRIVAL_TIME, EVAC_WAIT_TIME, EvacEpiloguePlayerCanBoard, EvacEpilogueShouldLeaveEarly, EvacEpilogueCompleted ) } -void function Evac_OnEpilogue() +bool function EvacEpiloguePlayerCanBoard( entity dropship, entity player ) { - if ( Evac_IsEnabled() ) - thread EvacMain( GetOtherTeam( GameScore_GetWinningTeam() ) ) + // 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 } -void function EvacMain( int winningTeam ) +bool function EvacEpilogueShouldLeaveEarly( entity dropship ) { - if ( file.evacPoints.len() == 0 ) + int numEvacing + foreach ( entity player in dropship.s.evacSlots ) + if ( IsValid( player ) ) + numEvacing++ + + return GetPlayerArrayOfTeam( dropship.GetTeam() ).len() == numEvacing || numEvacing == dropship.s.evacSlots.len() +} + +void function EvacEpilogueCompleted( entity dropship ) +{ + wait 5.0 + print( dropship ) + + foreach ( entity player in dropship.s.evacSlots ) { - // automatically add evac locations if they aren't registered yet - int i = 1 - entity current = null - while ( true ) - { - current = GetEnt( "escape_node" + i ) - print( current ) - - if ( current != null ) - Evac_AddLocation( current.GetOrigin(), current.GetAngles() ) - else - break - - i++ - } + if ( !IsValid( player ) ) + continue - if ( file.evacPoints.len() == 0 ) - unreachable + ScreenFadeToBlackForever( player, 2.0 ) } - if ( file.spacePosition.origin == < 0, 0, 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 ) +{ + wait initialWait + + // setup evac nodes if not manually registered + if ( file.evacNodes.len() == 0 ) { - // automatically add a space node if not registered yet - entity defaultSpaceNode = GetEnt( "spaceNode" ) - if ( defaultSpaceNode == null ) - unreachable - - Evac_SetSpacePosition( defaultSpaceNode.GetOrigin(), defaultSpaceNode.GetAngles() ) + for ( int i = 1; ; i++ ) + { + entity newNode = GetEnt( "escape_node" + i ) + if ( !IsValid( newNode ) ) + break + + file.evacNodes.append( newNode ) + } } - Point evacPoint = file.evacPoints[ RandomInt( file.evacPoints.len() ) ] + // setup space node if not manually registered + if ( !IsValid( file.spaceNode ) ) + file.spaceNode = GetEnt( "spaceNode" ) + + entity evacNode = file.evacNodes.getrandom() + + // 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 ) - // create an entity for the evac point that clients will get - entity evacPointEntity = CreateEntity( MARKER_ENT_CLASSNAME ) - evacPointEntity.SetOrigin( evacPoint.origin ) - evacPointEntity.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT - DispatchSpawn( evacPointEntity ) - evacPointEntity.DisableHibernation() - - // set objectives - //SetTeamActiveObjective( winningTeam, "EG_DropshipExtract", Time() + EVAC_ARRIVAL_TIME, evacPointEntity ) - //SetTeamActiveObjective( GetOtherTeam( winningTeam ), "EG_StopExtract", Time() + EVAC_ARRIVAL_TIME, evacPointEntity ) - - // wanted to do this with an actual dropship to calculate embarkStartDelay but spawning it before it should exist ingame is weird - // could probably do it with a dummy entity but effort - wait EVAC_ARRIVAL_TIME - 4.33333//embarkStartDelay - - // create dropship - entity dropship = CreateDropship( winningTeam, evacPoint.origin, evacPoint.angles ) - file.evacDropship = dropship + // 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" ) DispatchSpawn( dropship ) - dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) // gotta do this after dispatch for some reason - vector startPos = dropship.Anim_GetStartForRefEntity( "cd_dropship_rescue_side_start", evacPointEntity, "origin" ).origin - dropship.SetOrigin( startPos ) // set origin so the dropship isn't in the map + dropship.s.evacSlots <- [ null, null, null, null, null, null, null, null ] + 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" ) - // calculate time until idle + 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 + 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" ) - float embarkStartDelay = sequenceDuration * cycleFrac + 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 ) || !canBoardCallback( dropship, player ) || PlayerInDropship( player, dropship ) ) + return - // play anim - thread PlayAnim( dropship, "cd_dropship_rescue_side_start", evacPointEntity ) - wait embarkStartDelay - - print( "evac flyin done! ready to load players" ) + thread AddPlayerToEvacDropship( dropship, player ) + }) - // set objectives again - SetTeamActiveObjective( winningTeam, "EG_DropshipExtract2", Time() + EVAC_WAIT_TIME, evacPointEntity ) - SetTeamActiveObjective( GetOtherTeam( winningTeam ), "EG_StopExtract2", Time() + EVAC_WAIT_TIME, evacPointEntity ) - - thread EvacShipThink( dropship ) // let people enter it + dropship.s.evacTrigger <- trigger + + float waitStartTime = Time() + while ( Time() - waitStartTime < waitTime ) + { + if ( shouldLeaveEarlyCallback( dropship ) ) + break + + WaitFrame() + } - wait EVAC_WAIT_TIME + // holster all weapons + foreach ( entity player in dropship.s.evacSlots ) + if ( IsValid( player ) ) + player.HolsterWeapon() // fly away - thread PlayAnim( dropship, "cd_dropship_rescue_side_end", evacPointEntity ) + thread PlayAnim( dropship, "cd_dropship_rescue_side_end", evacNode ) + + SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipFlyingAway" ) + SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtractDropshipFlyingAway" ) - // set objectives again - SetTeamActiveObjective( winningTeam, "EG_DropshipExtractDropshipFlyingAway" ) - SetTeamActiveObjective( GetOtherTeam( winningTeam ), "EG_StopExtractDropshipFlyingAway" ) + // todo: play the warpout effect somewhere here wait dropship.GetSequenceDuration( "cd_dropship_rescue_side_end" ) - WARPINFXTIME - foreach ( entity player in file.evacPlayers ) - { - Remote_CallFunction_Replay( player, "ServerCallback_PlayScreenFXWarpJump" ) - } + foreach ( entity player in dropship.s.evacSlots ) + if ( IsValid( player ) ) + Remote_CallFunction_NonReplay( player, "ServerCallback_PlayScreenFXWarpJump" ) - // todo screen effects and shit - //WaittillAnimDone( dropship ) wait WARPINFXTIME - // space - dropship.SetOrigin( file.spacePosition.origin ) - dropship.SetAngles( file.spacePosition.angles ) - thread PlayAnim( dropship, "ds_space_flyby_dropshipA" ) - - // display player [Evacuated] in killfeed - foreach ( entity player in GetPlayerArray() ) - { - foreach ( entity evacPlayer in file.evacPlayers ) - Remote_CallFunction_NonReplay( player, "ServerCallback_EvacObit", evacPlayer.GetEncodedEHandle() ) - } - - foreach ( entity player in file.evacPlayers ) - { - // set skybox to space for all evac players + // go to space + // hardcoded angles here are a hack, spacenode position doesn't face the planet in the skybox, for some reason + file.spaceNode.SetAngles( < 30, -75, 20 >) + dropship.SetOrigin( file.spaceNode.GetOrigin() ) + dropship.SetAngles( file.spaceNode.GetAngles() ) + dropship.SetInvulnerable() + 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" ) + + return + } + + SetPlayerActiveObjective( player, "EG_DropshipExtractSuccessfulEscape" ) + + // skybox player.SetSkyCamera( GetEnt( "skybox_cam_intro" ) ) 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() ) } - wait 5.0 - - foreach ( entity player in GetPlayerArray() ) - ScreenFadeToBlackForever( player, 2.0 ) - - wait 2.0 - - // end game lol - SetGameState( eGameState.Postmatch ) } -void function EvacShipThink( entity dropship ) +void function AddPlayerToEvacDropship( entity dropship, entity player ) { - dropship.EndSignal( "OnDestroy" ) - - // this is the easiest way i could figure out to get a bounding box that's parented to the dropship - entity mover1 = CreateScriptMover( dropship.GetOrigin(), dropship.GetAngles() ) - mover1.SetParent( dropship ) - mover1.SetLocalOrigin( dropship.GetBoundingMaxs() - < 0, 0, 100> ) - - entity mover2 = CreateScriptMover( dropship.GetOrigin(), dropship.GetAngles() ) - mover2.SetParent( dropship ) - mover2.SetLocalOrigin( dropship.GetBoundingMins() - < 0, 0, 100 > ) - - while ( true ) + int slot = RandomInt( dropship.s.evacSlots.len() ) + for ( int i = 0; i < dropship.s.evacSlots.len(); i++ ) { - foreach ( entity player in GetPlayerArrayOfTeam( dropship.GetTeam() ) ) + if ( !IsValid( dropship.s.evacSlots[ slot ] ) ) { - if ( file.evacPlayers.contains( player ) || !IsAlive( player ) ) - continue - - vector playerPos = player.GetOrigin() - - vector mover1Pos = mover1.GetOrigin() - vector mover2Pos = mover2.GetOrigin() - vector maxPos - maxPos.x = mover1Pos.x > mover2Pos.x ? mover1Pos.x : mover2Pos.x - maxPos.y = mover1Pos.y > mover2Pos.y ? mover1Pos.y : mover2Pos.y - maxPos.z = mover1Pos.z > mover2Pos.z ? mover1Pos.z : mover2Pos.z - - vector minPos - minPos.x = mover1Pos.x < mover2Pos.x ? mover1Pos.x : mover2Pos.x - minPos.y = mover1Pos.y < mover2Pos.y ? mover1Pos.y : mover2Pos.y - minPos.z = mover1Pos.z < mover2Pos.z ? mover1Pos.z : mover2Pos.z - - print( "\n" ) - print( player ) - print( playerPos ) - print( minPos ) - print( maxPos ) - - if ( playerPos.x > minPos.x && playerPos.y > minPos.y && playerPos.z > minPos.z && - playerPos.x < maxPos.x && playerPos.y < maxPos.y && playerPos.z < maxPos.z ) - { - print( player + " is evacuating!" ) - - file.evacPlayers.append( player ) - player.SetParent( dropship ) - - // super duper temp - player.SetLocalOrigin( dropship.GetOrigin() - < 0, 10, 80 > ) - } + dropship.s.evacSlots[ slot ] = player + break } - - WaitFrame() - } -} - -/*void function TestEvac() -{ - if ( file.evacShipSpawns.len() == 0 ) - Evac_AddLocation( GetEnt( "escape_node1" ).GetOrigin(), GetEnt( "escape_node1" ).GetAngles() ) - - Point shipSpawn = file.evacShipSpawns[ RandomInt( file.evacShipSpawns.len() ) ] - - entity dropship = CreateDropship( GetPlayerArray()[0].GetTeam(), shipSpawn.origin, shipSpawn.angles ) - file.evacDropship = dropship - DispatchSpawn( dropship ) - - dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) - - print( dropship.GetSequenceDuration( "cd_dropship_rescue_side_start" ) ) - print( dropship.GetScriptedAnimEventCycleFrac( "cd_dropship_rescue_side_start", "ReadyToLoad" ) ) - float embarkStart = dropship.GetSequenceDuration( "cd_dropship_rescue_side_start" ) * dropship.GetScriptedAnimEventCycleFrac( "cd_dropship_rescue_side_start", "ReadyToLoad" ) - print( embarkStart ) + slot = ( slot + 1 ) % expect int( dropship.s.evacSlots.len() ) + } - thread PlayAnim( dropship, "cd_dropship_rescue_side_start" ) - wait embarkStart - print( "evac start anim done" ) - thread TestEvacThink( dropship ) - SetTeamActiveObjective( GetPlayerArray()[0].GetTeam(), "EG_DropshipExtract2", Time() + 30, dropship ) + // no slots available + if ( !PlayerInDropship( player, dropship ) ) + return + + player.SetInvulnerable() + player.UnforceCrouch() + player.ForceStand() - thread PlayAnim( dropship, "cd_dropship_rescue_side_idle", GetEnt( "escape_node1" ) ) + 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 } -void function TestEvacThink( entity dropship ) +bool function PlayerInDropship( entity player, entity dropship ) { - dropship.EndSignal( "OnDestroy" ) - - // these numbers are probably innacurate but there's no real way of getting accurate ones and these are good enough - entity mover = CreateScriptMover( dropship.GetOrigin(), dropship.GetAngles() ) - mover.SetParent( dropship ) - mover.SetLocalOrigin( dropship.GetBoundingMaxs() - < 0, 0, 100> ) - - entity mover2 = CreateScriptMover( dropship.GetOrigin(), dropship.GetAngles() ) - mover2.SetParent( dropship ) - mover2.SetLocalOrigin( dropship.GetBoundingMins() - < 0, 0, 100> ) - - while ( true ) - { - foreach ( entity player in GetPlayerArrayOfTeam( dropship.GetTeam() ) ) - { - if ( !IsAlive( player ) ) - continue - - vector playerOrigin = player.GetOrigin() - - vector dropshipMax = mover.GetOrigin() - vector dropshipMin = mover2.GetOrigin() + // 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 - // temp, might be permenant but idk if box triggers are a thing in script - if ( playerOrigin.x > dropshipMin.x && playerOrigin.y > dropshipMin.y && playerOrigin.z > dropshipMin.z && - playerOrigin.x < dropshipMax.x && playerOrigin.y < dropshipMax.y && playerOrigin.z < dropshipMax.z ) - player.Die() - } - - WaitFrame() - } -}*/
\ No newline at end of file + return false +}
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut index 932f14b7..85b4aefb 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut @@ -2,7 +2,7 @@ global function FFA_Init void function FFA_Init() { - Evac_SetEnabled( false ) + ClassicMP_ForceDisableEpilogue( true ) AddCallback_OnPlayerKilled( OnPlayerKilled ) } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut index 60daa452..63b2c81a 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut @@ -13,6 +13,11 @@ void function PrivateLobby_Init() print( "PrivateLobby_Init()" ) //ClearPlaylistVarOverrides() + file.map = GetConVarString( "ns_private_match_last_map" ) + file.mode = GetConVarString( "ns_private_match_last_mode" ) + + thread SetupPrivateMatchUIVarsWhenReady() + AddClientCommandCallback( "PrivateMatchLaunch", ClientCommandCallback_PrivateMatchLaunch ) AddClientCommandCallback( "PrivateMatchSetMode", ClientCommandCallback_PrivateMatchSetMode ) AddClientCommandCallback( "SetCustomMap", ClientCommandCallback_SetCustomMap ) @@ -22,6 +27,14 @@ void function PrivateLobby_Init() AddClientCommandCallback( "ResetMatchSettingsToDefault", ClientCommandCallback_ResetMatchSettingsToDefault ) } +void function SetupPrivateMatchUIVarsWhenReady() +{ + // have to wait until end of first frame for SetUIVar to work + WaitEndFrame() + SetUIVar( level, "privatematch_map", GetPrivateMatchMapIndex( file.map ) ) + SetUIVar( level, "privatematch_mode", GetPrivateMatchModeIndex( file.mode ) ) +} + bool function ClientCommandCallback_PrivateMatchLaunch( entity player, array<string> args ) { if ( file.startState == ePrivateMatchStartState.STARTING ) @@ -127,6 +140,8 @@ void function StartMatch() RefreshPlayerTeams() + SetConVarString( "ns_private_match_last_map", file.map ) + SetConVarString( "ns_private_match_last_mode", file.mode ) SetConVarBool( "ns_should_return_to_lobby", true ) // potentially temp? // TEMP for now: start game diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_lobby.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_lobby.gnut index 2c02ebdc..605b23fd 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_lobby.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_lobby.gnut @@ -189,11 +189,32 @@ void function AddPrivateMatchModeSettingEnumEx( string category, string playlist #endif } -array< string > function GetPrivateMatchSettingCategories() +array< string > function GetPrivateMatchSettingCategories( bool uiAllowAllModeCategories = false ) { array< string > categories foreach ( string k, v in file.customMatchSettingsByCategory ) + { + // can only do this in ui because it relies on GetUIVar + #if UI + bool gamemode = k.find( "#GAMEMODE_" ) == 0 + if ( !uiAllowAllModeCategories && ( gamemode || k.find( "#PL_" ) == 0 ) ) + { + if ( gamemode ) + { + if ( k.slice( 10 ) != PrivateMatch_GetSelectedMode() ) + { + continue + } + } + else if ( k.slice( 4 ) != PrivateMatch_GetSelectedMode() ) + { + continue + } + } + #endif + categories.append( k ) + } return categories } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut index 8991ebb0..5cd4de50 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut @@ -4,6 +4,8 @@ void function PrivateMatchModesInit() { // match settings // super temp: do localisation strings later + AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_MATCH", "classic_mp", [ "Disabled", "Enabled" ], "1" ) + AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_MATCH", "run_epilogue", [ "Disabled", "Enabled" ], "1" ) AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_MATCH", "scorelimit", "5" ) //, "Score Limit" ) AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_MATCH", "roundscorelimit", "0" ) //, "Score Limit (round-based modes)" ) AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_MATCH", "timelimit", "12" ) //, "Time Limit" ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_ai_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_ai_mp.gnut index ac0c309b..4cbab84c 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_ai_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_ai_mp.gnut @@ -30,12 +30,12 @@ bool function IsAutoPopulateEnabled( var team = null ) return true } -void function SPMP_UpdateNPCProficiency(entity ent) +void function SPMP_UpdateNPCProficiency( entity ent ) { } -bool function SPMP_Callback_ForceAIMissPlayer(entity npc, entity player) +bool function SPMP_Callback_ForceAIMissPlayer( entity npc, entity player ) { return true }
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut index d7db601b..38803e04 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut @@ -219,6 +219,7 @@ void function CodeCallback_OnPlayerRespawned( entity player ) player.s.respawnTime = Time() Loadouts_TryGivePilotLoadout( player ) + SetHumanRagdollImpactTable( player ) foreach ( void functionref( entity ) callback in svGlobal.onPlayerRespawnedCallbacks ) callback( player ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp.nut index ac8a397f..66bb3d6a 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp.nut @@ -1,53 +1,80 @@ untyped global function ClassicMp_Init -global function ClassicMP_TryDefaultIntroSetup // called in mp_sh_init +global function ClassicMP_TryDefaultIntroSetup + +// intro setups +global function ClassicMP_SetLevelIntro global function ClassicMP_SetCustomIntro +global function ClassicMP_SetupIntro + +// intro funcs global function ClassicMP_OnIntroStarted global function ClassicMP_OnIntroFinished global function ClassicMP_GetIntroLength + +// epilogue setups +global function ClassicMP_ForceDisableEpilogue +global function ClassicMP_SetEpilogue +global function ClassicMP_SetupEpilogue +global function ClassicMP_ShouldRunEpilogue + global function GetClassicMPMode struct { - void functionref() introSetupFunc - float introLength + // level intros have a lower priority than custom intros + // level intros are used only if a custom intro was not specified + void functionref() levelIntroSetupFunc + float levelIntroLength + + void functionref() customIntroSetupFunc + float customIntroLength + + bool epilogueForceDisabled = false + void functionref() epilogueSetupFunc } file void function ClassicMp_Init() { - // literally nothing to do here atm lol + // default level intros + if ( IsFFAGame() ) + ClassicMP_SetLevelIntro( ClassicMP_DefaultNoIntro_Setup, ClassicMP_DefaultNoIntro_GetLength() ) + else + ClassicMP_SetLevelIntro( ClassicMP_DefaultDropshipIntro_Setup, DROPSHIP_INTRO_LENGTH ) } +// stub func, called in mp_sh_init void function ClassicMP_TryDefaultIntroSetup() { - if ( file.introSetupFunc == null ) - { - if ( IsFFAGame() ) - ClassicMP_SetCustomIntro( ClassicMP_DefaultNoIntro_Setup, ClassicMP_DefaultNoIntro_GetLength() ) - else - ClassicMP_SetCustomIntro( ClassicMP_DefaultDropshipIntro_Setup, DROPSHIP_INTRO_LENGTH ) - } - - thread DelayedDoDefaultIntroSetup() + } -void function DelayedDoDefaultIntroSetup() +void function ClassicMP_SetLevelIntro( void functionref() setupFunc, float introLength ) { - // wait a frame for CodeCallback_MapInit to run which generally sets custom intros - WaitFrame() - file.introSetupFunc() + file.levelIntroSetupFunc = setupFunc + file.levelIntroLength = introLength } void function ClassicMP_SetCustomIntro( void functionref() setupFunc, float introLength ) { - file.introSetupFunc = setupFunc - file.introLength = introLength + file.customIntroSetupFunc = setupFunc + file.customIntroLength = introLength +} + +void function ClassicMP_SetupIntro() +{ + if ( file.customIntroSetupFunc != null ) + file.customIntroSetupFunc() + else + file.levelIntroSetupFunc() } void function ClassicMP_OnIntroStarted() { print( "started intro!" ) - SetServerVar( "gameStartTime", Time() + file.introLength ) - SetServerVar( "roundStartTime", Time() + file.introLength ) + + float introLength = ClassicMP_GetIntroLength() + SetServerVar( "gameStartTime", Time() + introLength ) + SetServerVar( "roundStartTime", Time() + introLength ) } void function ClassicMP_OnIntroFinished() @@ -58,10 +85,37 @@ void function ClassicMP_OnIntroFinished() float function ClassicMP_GetIntroLength() { - return file.introLength + if ( file.customIntroSetupFunc != null ) + return file.customIntroLength + + return file.levelIntroLength +} + +void function ClassicMP_ForceDisableEpilogue( bool disabled ) +{ + file.epilogueForceDisabled = disabled +} + +void function ClassicMP_SetEpilogue( void functionref() setupFunc ) +{ + file.epilogueSetupFunc = setupFunc +} + +void function ClassicMP_SetupEpilogue() +{ + if ( file.epilogueSetupFunc == null ) // default is evac + ClassicMP_SetEpilogue( EvacEpilogueSetup ) + + file.epilogueSetupFunc() } bool function GetClassicMPMode() { return GetCurrentPlaylistVarInt( "classic_mp", 1 ) == 1 +} + +bool function ClassicMP_ShouldRunEpilogue() +{ + // note: there is a run_evac playlist var, but it's unused, and default 0, so use a new one + return !file.epilogueForceDisabled && GetClassicMPMode() && !IsRoundBased() && GetCurrentPlaylistVarInt( "run_epilogue", 1 ) == 1 }
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index 197ac5e9..e3f7e0b0 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -92,7 +92,8 @@ void function SetGameState( int newState ) void function GameState_EntitiesDidLoad() { - // nothing of importance to put here, this is referenced in _gamestate though so need it + if ( GetClassicMPMode() ) + ClassicMP_SetupIntro() } void function WaittillGameStateOrHigher( int gameState ) @@ -185,9 +186,29 @@ void function GameStateEnter_Prematch() int timeLimit = GameMode_GetTimeLimit( GAMETYPE ) * 60 if ( file.switchSidesBased ) timeLimit /= 2 // endtime is half of total per side - + SetServerVar( "gameEndTime", Time() + timeLimit + ClassicMP_GetIntroLength() ) SetServerVar( "roundEndTime", Time() + ClassicMP_GetIntroLength() + GameMode_GetRoundTimeLimit( GAMETYPE ) * 60 ) + + if ( !GetClassicMPMode() ) + thread StartGameWithoutClassicMP() +} + +void function StartGameWithoutClassicMP() +{ + WaitFrame() // wait for callbacks to finish + + // need these otherwise game will complain + SetServerVar( "gameStartTime", Time() ) + SetServerVar( "roundStartTime", Time() ) + + foreach ( entity player in GetPlayerArray() ) + { + RespawnAsPilot( player ) + ScreenFadeFromBlack( player, 0 ) + } + + SetGameState( eGameState.Playing ) } @@ -246,38 +267,53 @@ void function GameStateEnter_WinnerDetermined() void function GameStateEnter_WinnerDetermined_Threaded() { - bool killcamsWereEnabled = KillcamsEnabled() - if ( killcamsWereEnabled ) // dont want killcams to interrupt stuff - SetKillcamsEnabled( false ) - + // do win announcement + int winningTeam = GetWinningTeam() + + foreach ( entity player in GetPlayerArray() ) + { + int announcementSubstr + if ( winningTeam != TEAM_UNASSIGNED ) + announcementSubstr = player.GetTeam() == winningTeam ? file.announceRoundWinnerWinningSubstr : file.announceRoundWinnerLosingSubstr + + if ( IsRoundBased() ) + Remote_CallFunction_NonReplay( player, "ServerCallback_AnnounceRoundWinner", GetWinningTeam(), announcementSubstr, ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME, GameRules_GetTeamScore2( TEAM_MILITIA ), GameRules_GetTeamScore2( TEAM_IMC ) ) + else + Remote_CallFunction_NonReplay( player, "ServerCallback_AnnounceWinner", GetWinningTeam(), announcementSubstr, ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME ) + } + WaitFrame() // wait a frame so other scripts can setup killreplay stuff entity replayAttacker = file.roundWinningKillReplayAttacker - bool doReplay = Replay_IsEnabled() && !( !IsRoundBased() && Evac_IsEnabled() ) && IsRoundWinningKillReplayEnabled() && IsValid( replayAttacker ) + bool doReplay = Replay_IsEnabled() && !ClassicMP_ShouldRunEpilogue() && IsRoundWinningKillReplayEnabled() && IsValid( replayAttacker ) && Time() - file.roundWinningKillReplayTime <= ROUND_WINNING_KILL_REPLAY_LENGTH_OF_REPLAY float replayLength = 2.0 // extra delay if no replay if ( doReplay ) { + bool killcamsWereEnabled = KillcamsEnabled() + if ( killcamsWereEnabled ) // dont want killcams to interrupt stuff + SetKillcamsEnabled( false ) + replayLength = ROUND_WINNING_KILL_REPLAY_LENGTH_OF_REPLAY if ( "respawnTime" in replayAttacker.s && Time() - replayAttacker.s.respawnTime < replayLength ) replayLength += Time() - expect float ( replayAttacker.s.respawnTime ) SetServerVar( "roundWinningKillReplayEntHealthFrac", file.roundWinningKillReplayHealthFrac ) - } - - foreach ( entity player in GetPlayerArray() ) - thread PlayerWatchesRoundWinningKillReplay( player, doReplay, replayLength ) - - wait ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME - CleanUpEntitiesForRoundEnd() // fade should be done by this point, so cleanup stuff now when people won't see - wait replayLength - - WaitFrame() // prevent a race condition with PlayerWatchesRoundWinningKillReplay - file.roundWinningKillReplayAttacker = null // clear this + + foreach ( entity player in GetPlayerArray() ) + thread PlayerWatchesRoundWinningKillReplay( player, doReplay, replayLength ) - if ( killcamsWereEnabled ) - SetKillcamsEnabled( true ) + wait ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME + CleanUpEntitiesForRoundEnd() // fade should be done by this point, so cleanup stuff now when people won't see + wait replayLength + + WaitFrame() // prevent a race condition with PlayerWatchesRoundWinningKillReplay + file.roundWinningKillReplayAttacker = null // clear this + + if ( killcamsWereEnabled ) + SetKillcamsEnabled( true ) + } if ( IsRoundBased() ) { @@ -299,8 +335,11 @@ void function GameStateEnter_WinnerDetermined_Threaded() } else { - if ( Evac_IsEnabled() ) + if ( ClassicMP_ShouldRunEpilogue() ) + { + ClassicMP_SetupEpilogue() SetGameState( eGameState.Epilogue ) + } else SetGameState( eGameState.Postmatch ) } @@ -309,18 +348,8 @@ void function GameStateEnter_WinnerDetermined_Threaded() void function PlayerWatchesRoundWinningKillReplay( entity player, bool doReplay, float replayLength ) { player.FreezeControlsOnServer() - - int winningTeam = GetWinningTeam() - int announcementSubstr - if ( winningTeam != TEAM_UNASSIGNED ) - announcementSubstr = player.GetTeam() == winningTeam ? file.announceRoundWinnerWinningSubstr : file.announceRoundWinnerLosingSubstr - - if ( IsRoundBased() ) - Remote_CallFunction_NonReplay( player, "ServerCallback_AnnounceRoundWinner", winningTeam, announcementSubstr, ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME, GameRules_GetTeamScore2( TEAM_MILITIA ), GameRules_GetTeamScore2( TEAM_IMC ) ) - else - Remote_CallFunction_NonReplay( player, "ServerCallback_AnnounceWinner", winningTeam, announcementSubstr, ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME ) - if ( IsRoundBased() || !Evac_IsEnabled() ) // if we're doing evac, then no fades or killreplay + if ( IsRoundBased() || !ClassicMP_ShouldRunEpilogue() ) // if we're doing evac, then no fades or killreplay { ScreenFadeToBlackForever( player, ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME ) wait ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/_lf_maps_shared.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/_lf_maps_shared.gnut index d61d6baa..178b6560 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/_lf_maps_shared.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/_lf_maps_shared.gnut @@ -4,5 +4,5 @@ global function SetupLiveFireMaps void function SetupLiveFireMaps() { Riff_ForceTitanAvailability( eTitanAvailability.Never ) - ClassicMP_SetCustomIntro( ClassicMP_DefaultNoIntro_Setup, ClassicMP_DefaultNoIntro_GetLength() ) + ClassicMP_SetLevelIntro( ClassicMP_DefaultNoIntro_Setup, ClassicMP_DefaultNoIntro_GetLength() ) }
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_angel_city.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_angel_city.nut index 68b49ad5..87c9ea98 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_angel_city.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_angel_city.nut @@ -2,12 +2,12 @@ global function CodeCallback_MapInit void function CodeCallback_MapInit() { - Evac_AddLocation( < 2527.889893, -2865.360107, 753.002991 >, < 0, -80.54, 0 > ) - Evac_AddLocation( < 1253.530029, -554.075012, 811.125 >, < 0, 180, 0 > ) - Evac_AddLocation( < 2446.989990, 809.364014, 576.0 >, < 0, 90.253, 0 > ) - Evac_AddLocation( < -2027.430054, 960.395020, 609.007996 >, < 0, 179.604, 0 > ) + AddEvacNode( CreateScriptRef( < 2527.889893, -2865.360107, 753.002991 >, < 0, -80.54, 0 > ) ) + AddEvacNode( CreateScriptRef( < 1253.530029, -554.075012, 811.125 >, < 0, 180, 0 > ) ) + AddEvacNode( CreateScriptRef( < 2446.989990, 809.364014, 576.0 >, < 0, 90.253, 0 > ) ) + AddEvacNode( CreateScriptRef( < -2027.430054, 960.395020, 609.007996 >, < 0, 179.604, 0 > ) ) - Evac_SetSpacePosition( < -1700, -5500, -7600 >, < -3.620642, 270.307129, 0 > ) + SetEvacSpaceNode( CreateScriptRef( < -1700, -5500, -7600 >, < -3.620642, 270.307129, 0 > ) ) // todo: also we need to change the powerup spawns on this map, they use a version from an older patch |