diff options
12 files changed, 458 insertions, 143 deletions
diff --git a/Northstar.CustomServers/mod.json b/Northstar.CustomServers/mod.json index 09d8fc2d..00dd06bc 100644 --- a/Northstar.CustomServers/mod.json +++ b/Northstar.CustomServers/mod.json @@ -18,6 +18,11 @@ "DefaultValue": "1" }, { + "Name": "ns_allow_spectators", + "DefaultValue": "0", + "Flags": 8192 + }, + { "Name": "ns_private_match_last_mode", "DefaultValue": "tdm" }, @@ -102,6 +107,13 @@ "RunOn": "SERVER && MP" }, { + "Path": "mp/_spectator.gnut", + "RunOn": "SERVER && MP", + "ServerCallback": { + "After": "Spectator_Init" + } + }, + { "Path": "_loadouts_mp.gnut", "RunOn": "SERVER && MP", "ServerCallback": { diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut index 9fe95445..bce8b4c7 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut @@ -63,6 +63,8 @@ struct { array<entity> evacNodes entity spaceNode + // this only supports 1 evac at once atm, is this ideal? + entity currentEvacNode entity evacDropship entity evacIcon } file @@ -71,6 +73,7 @@ void function Evac_Init() { EvacShared_Init() RegisterSignal( "EvacShipLeaves" ) + RegisterSignal( "EvacOver" ) } void function AddEvacNode( entity evacNode ) @@ -116,7 +119,23 @@ void function EvacEpilogue() } } -void function SetRespawnAndWait(bool mode) +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 ) @@ -180,9 +199,11 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa if ( !IsValid( file.spaceNode ) ) file.spaceNode = GetEnt( "spaceNode" ) - entity evacNode + entity evacNode = customEvacNode if ( !IsValid( customEvacNode ) ) evacNode = file.evacNodes.getrandom() + + file.currentEvacNode = evacNode // setup client evac position file.evacIcon = CreateEntity( "info_target" ) @@ -236,6 +257,7 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa }) // flyin + Spectator_SetCustomSpectatorFunc( EvacSpectatorFunc ) thread PlayAnim( dropship, "cd_dropship_rescue_side_start", evacNode ) // calculate time until idle start @@ -247,7 +269,7 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa // eta until leave SetTeamActiveObjective( evacTeam, "EG_DropshipExtract2", Time() + EVAC_WAIT_TIME, file.evacIcon ) - SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract2", 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" ) @@ -308,6 +330,7 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa 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() ) @@ -324,7 +347,7 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa SetPlayerActiveObjective( player, "EG_DropshipExtractSuccessfulEscape" ) // skybox - player.SetSkyCamera( GetEnt( "skybox_cam_intro" ) ) + 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 ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut index 3102326c..6b30a399 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut @@ -36,7 +36,12 @@ void function TTDMIntroStartThreaded() ClassicMP_OnIntroStarted()
foreach ( entity player in GetPlayerArray() )
- TTDMIntroShowIntermissionCam( player )
+ {
+ if ( !IsPrivateMatchSpectator( player ) )
+ TTDMIntroShowIntermissionCam( player )
+ else
+ RespawnPrivateMatchSpectator( player )
+ }
wait TTDMIntroLength
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut index 0f37251d..33c0b8e9 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut @@ -19,7 +19,8 @@ void function Lobby_Init() void function Lobby_OnClientConnectionStarted( entity player ) { - + if ( !( IsPrivateMatch() || GetCurrentPlaylistName() == "private_match" ) || !GetConVarBool( "ns_allow_spectators" ) ) + player.SetPersistentVar( "privateMatchState", 0 ) // disable spectator } void function Lobby_OnClientConnectionCompleted( entity player ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut index c56e537a..c410869e 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut @@ -123,7 +123,10 @@ bool function ClientCommandCallback_PrivateMatchSwitchTeams( entity player, arra bool function ClientCommandCallback_PrivateMatchToggleSpectate( entity player, array<string> args ) { - // not currently working, gotta figure it out at some point + if ( file.startState == ePrivateMatchStartState.STARTING || !GetConVarBool( "ns_allow_spectators" ) ) + return true + + player.SetPersistentVar( "privateMatchState", player.GetPersistentVarAsInt( "privateMatchState" ) == 0 ? 1 : 0 ) return true } 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 453e35ec..d22f2627 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut @@ -31,12 +31,6 @@ struct { void function BaseGametype_Init_MPSP() { AddSpawnCallback( "info_intermission", SetIntermissionCamera ) - AddCallback_EntitiesDidLoad( SetSpecCams ) - - RegisterSignal( "ObserverTargetChanged" ) - AddClientCommandCallback( "spec_next", ClientCommandCallback_spec_next ) - AddClientCommandCallback( "spec_prev", ClientCommandCallback_spec_prev ) - AddClientCommandCallback( "spec_mode", ClientCommandCallback_spec_mode ) AddPostDamageCallback( "player", AddToTitanDamageStat ) AddPostDamageCallback( "npc_titan", AddToTitanDamageStat ) @@ -50,19 +44,6 @@ void function SetIntermissionCamera( entity camera ) file.intermissionCamera = camera } -void function SetSpecCams() -{ - // spec cams are called spec_cam1,2,3 etc by default, so this is the easiest way to get them imo - int camNum = 1 - entity lastCam = null - do { - lastCam = GetEnt( "spec_cam" + camNum++ ) - - if ( lastCam != null ) - file.specCams.append( lastCam ) - } while ( lastCam != null ) -} - void function CodeCallback_OnClientConnectionStarted( entity player ) { // not a real player? @@ -173,6 +154,17 @@ void function CodeCallback_OnClientConnectionCompleted( entity player ) svGlobal.levelEnt.Signal( "PlayerDidSpawn", { player = player } ) + if ( GetConVarBool( "ns_allow_spectators" ) ) + { + if ( IsPrivateMatchSpectator( player ) ) + { + InitialisePrivateMatchSpectatorPlayer( player ) + return + } + } + else + player.SetPersistentVar( "privateMatchState", 0 ) + // handle spawning late joiners if ( GetGameState() == eGameState.Playing ) { @@ -492,118 +484,6 @@ void function RespawnAsTitan( entity player, bool manualPosition = false ) } -// spectator stuff - -void function PlayerBecomesSpectator( entity player ) -{ - player.StartObserverMode( OBS_MODE_CHASE ) - player.StopPhysics() - - player.EndSignal( "OnRespawned" ) - player.EndSignal( "OnDestroy" ) - player.EndSignal( "PlayerRespawnStarted" ) - - int targetIndex = 0 - - OnThreadEnd( function() : ( player ) - { - if ( IsValid( player ) ) - player.StopObserverMode() - }) - - while ( true ) - { - table result = player.WaitSignal( "ObserverTargetChanged" ) - - array<entity> targets - - targets.append( file.intermissionCamera ) - foreach( entity cam in file.specCams ) - targets.append( cam ) - - array<entity> targetPlayers - if ( IsFFAGame() ) - targetPlayers = GetPlayerArray_Alive() - else - targetPlayers = GetPlayerArrayOfTeam_Alive( player.GetTeam() ) - - foreach( entity player in targetPlayers ) - targets.append( player ) - - if ( result.next ) - targetIndex = ( targetIndex + 1 ) % targets.len() - else - { - if ( targetIndex == 0 ) - targetIndex = ( targets.len() - 1 ) - else - targetIndex-- - } - - if ( targetIndex >= targets.len() ) - targetIndex = 0 - - entity target = targets[ targetIndex ] - - player.StopObserverMode() - if ( player.IsWatchingSpecReplay() ) - player.SetSpecReplayDelay( 0.0 ) // clear spectator replay - - if ( target.IsPlayer() ) - { - try - { - player.SetObserverTarget( target ) - player.StartObserverMode( OBS_MODE_CHASE ) - } - catch ( ex ) {} - } - else - { - player.SetObserverModeStaticPosition( target.GetOrigin() ) - player.SetObserverModeStaticAngles( target.GetAngles() ) - player.StartObserverMode( OBS_MODE_STATIC ) - } - player.StopPhysics() - } -} - -bool function ClientCommandCallback_spec_next( entity player, array<string> args ) -{ - if ( player.GetObserverMode() == OBS_MODE_CHASE || player.GetObserverMode() == OBS_MODE_STATIC || player.GetObserverMode() == OBS_MODE_IN_EYE ) - player.Signal( "ObserverTargetChanged", { next = true } ) - - return true -} - -bool function ClientCommandCallback_spec_prev( entity player, array<string> args ) -{ - if ( player.GetObserverMode() == OBS_MODE_CHASE || player.GetObserverMode() == OBS_MODE_STATIC || player.GetObserverMode() == OBS_MODE_IN_EYE ) - player.Signal( "ObserverTargetChanged", { next = false } ) - - return true -} - -bool function ClientCommandCallback_spec_mode( entity player, array<string> args ) -{ - // currently unsure how this actually gets called on client, works through console and has references in client.dll tho - if ( player.GetObserverMode() == OBS_MODE_CHASE ) - { - // set to first person spectate - player.SetSpecReplayDelay( FIRST_PERSON_SPECTATOR_DELAY ) - player.SetViewEntity( player.GetObserverTarget(), true ) - player.StartObserverMode( OBS_MODE_IN_EYE ) - } - else if ( player.GetObserverMode() == OBS_MODE_IN_EYE ) - { - // set to third person spectate - player.SetSpecReplayDelay( 0.0 ) - player.StartObserverMode( OBS_MODE_CHASE ) - } - - return true -} - void function TryGameModeAnnouncement( entity player ) // only putting this here because it's here in gametype_sp lol { diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut index 1e19abd3..80fae331 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut @@ -112,7 +112,12 @@ void function OnPrematchStart() } foreach ( entity player in GetPlayerArray() ) - thread SpawnPlayerIntoDropship( player ) + { + if ( !IsPrivateMatchSpectator( player ) ) + thread SpawnPlayerIntoDropship( player ) + else + RespawnPrivateMatchSpectator( player ) + } thread EndIntroWhenFinished() } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_no_intro.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_no_intro.gnut index 106f867b..7901e3a2 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_no_intro.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_no_intro.gnut @@ -38,8 +38,12 @@ void function ClassicMP_DefaultNoIntro_Start() foreach ( entity player in GetPlayerArray() ) { - player.UnfreezeControlsOnServer() - RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) + if ( !IsPrivateMatchSpectator( player ) ) + { + player.UnfreezeControlsOnServer() + RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) + } + TryGameModeAnnouncement( player ) } } @@ -52,6 +56,12 @@ void function ClassicMP_DefaultNoIntro_SpawnPlayer( entity player ) if ( GetGameState() != eGameState.Prematch ) return + if ( IsPrivateMatchSpectator( player ) ) // private match spectators use custom spawn logic + { + RespawnPrivateMatchSpectator( player ) + return + } + if ( IsAlive( player ) ) player.Die() diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index aa14477a..7e9943c3 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -198,7 +198,9 @@ void function StartGameWithoutClassicMP() foreach ( entity player in GetPlayerArray() ) { - RespawnAsPilot( player ) + if ( !IsPrivateMatchSpectator( player ) ) + RespawnAsPilot( player ) + ScreenFadeFromBlack( player, 0 ) } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut new file mode 100644 index 00000000..aa2fc108 --- /dev/null +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut @@ -0,0 +1,223 @@ +global function Spectator_Init + +// stuff called by _base_gametype_mp and such +global function InitialisePrivateMatchSpectatorPlayer +global function PlayerBecomesSpectator +global function RespawnPrivateMatchSpectator + +// custom spectator state functions +// yes, GM_SetSpectatorFunc does exist in vanilla and serves roughly the same purpose, but using custom funcs here seemed better +global function Spectator_SetDefaultSpectatorFunc +global function Spectator_SetCustomSpectatorFunc +global function Spectator_ClearCustomSpectatorFunc + +// helper funcs +global function HACKCleanupStaticObserverStuff + +global typedef SpectatorFunc void functionref( entity player ) + +struct { + array<entity> staticSpecCams + SpectatorFunc defaultSpectatorFunc + SpectatorFunc nextSpectatorFunc = null + + int newestFuncIndex = 0 // used to track which players have finished the most recent spectator func +} file + +void function Spectator_Init() +{ + Spectator_SetDefaultSpectatorFunc( SpectatorFunc_Default ) + + AddCallback_EntitiesDidLoad( SetStaticSpecCams ) + + RegisterSignal( "ObserverTargetChanged" ) + RegisterSignal( "SpectatorFuncChanged" ) + AddClientCommandCallback( "spec_next", ClientCommandCallback_spec_next ) + AddClientCommandCallback( "spec_prev", ClientCommandCallback_spec_prev ) + AddClientCommandCallback( "spec_mode", ClientCommandCallback_spec_mode ) +} + +void function SetStaticSpecCams() +{ + // spec cams are called spec_cam1,2,3 etc by default, so this is the easiest way to get them imo + int camNum = 1 + entity lastCam = null + do { + lastCam = GetEnt( "spec_cam" + camNum++ ) + + if ( IsValid( lastCam ) ) + file.staticSpecCams.append( lastCam ) + } while ( IsValid( lastCam ) ) +} + +void function Spectator_SetDefaultSpectatorFunc( SpectatorFunc func ) +{ + file.defaultSpectatorFunc = func +} + +// sets the current spectator func, stopping any currently running spectator funcs to start this one +void function Spectator_SetCustomSpectatorFunc( SpectatorFunc func ) +{ + file.nextSpectatorFunc = func + svGlobal.levelEnt.Signal( "SpectatorFuncChanged" ) // spectator funcs need to listen to this manually + file.newestFuncIndex++ +} + +void function Spectator_ClearCustomSpectatorFunc() +{ + Spectator_SetCustomSpectatorFunc( null ) +} + +void function HACKCleanupStaticObserverStuff( entity player ) +{ + // this may look like horrible awful pointless code at first glance, and while it is horrible and awful, it's not pointless + // 3.402823466E38 is 0xFFFF7F7F in memory, which is the value the game uses to determine whether the current static observer pos/angles are valid ( i.e. 0xFFFF7F7F = invalid/not initialised ) + // in my experience, not cleaning this up after setting static angles will break OBS_MODE_CHASE-ing non-player entities which is bad for custom spectator funcs + // this is 100% way lower level than what script stuff should usually be doing, but it's needed here + // i sure do hope this won't break in normal use :clueless: + player.SetObserverModeStaticPosition( < 3.402823466e38, 3.402823466e38, 3.402823466e38 > ) + player.SetObserverModeStaticAngles( < 3.402823466e38, 3.402823466e38, 3.402823466e38 > ) +} + +void function InitialisePrivateMatchSpectatorPlayer( entity player ) +{ + thread PlayerBecomesSpectator( player ) +} + +// this should be called when intros respawn players normally to handle fades and stuff +void function RespawnPrivateMatchSpectator( entity player ) +{ + ScreenFadeFromBlack( player, 0.5, 0.5 ) +} + +void function PlayerBecomesSpectator( entity player ) +{ + player.StopPhysics() + + player.EndSignal( "OnRespawned" ) + player.EndSignal( "OnDestroy" ) + player.EndSignal( "PlayerRespawnStarted" ) + + OnThreadEnd( function() : ( player ) + { + if ( IsValid( player ) ) + player.StopObserverMode() + }) + + // keeps track of the most recent func this player has completed + // this is to ensure that custom spectator funcs are only run once per player even before being cleared + int funcIndex = 0 + + while ( true ) + { + SpectatorFunc nextSpectatorFunc = file.defaultSpectatorFunc + if ( file.nextSpectatorFunc != null && funcIndex != file.newestFuncIndex ) + nextSpectatorFunc = file.nextSpectatorFunc + + waitthread nextSpectatorFunc( player ) + funcIndex = file.newestFuncIndex // assuming this will be set before file.newestFuncIndex increments when the spectator func is ended by SpectatorFuncChanged + // surely this will not end up being false in practice :clueless: + + // cleanup + player.StopObserverMode() + HACKCleanupStaticObserverStuff( player ) // un-initialise static observer positions/angles + + WaitFrame() // always wait at least a frame in case an observer func exits immediately to prevent stuff locking up + } +} + +void function SpectatorFunc_Default( entity player ) +{ + svGlobal.levelEnt.EndSignal( "SpectatorFuncChanged" ) + int targetIndex + + table result = { next = false } + + while ( true ) + { + array<entity> targets + targets.extend( file.staticSpecCams ) + + if ( IsFFAGame() ) + targets.extend( GetPlayerArray_Alive() ) + else + targets.extend( GetPlayerArrayOfTeam_Alive( player.GetTeam() ) ) + + if ( targets.len() > 0 ) + { + if ( result.next ) + targetIndex = ( targetIndex + 1 ) % targets.len() + else + { + if ( targetIndex == 0 ) + targetIndex = ( targets.len() - 1 ) + else + targetIndex-- + } + + if ( targetIndex >= targets.len() ) + targetIndex = 0 + + entity target = targets[ targetIndex ] + + player.StopObserverMode() + if ( player.IsWatchingSpecReplay() ) + player.SetSpecReplayDelay( 0.0 ) // clear spectator replay + + if ( target.IsPlayer() ) + { + try + { + player.SetObserverTarget( target ) + player.StartObserverMode( OBS_MODE_CHASE ) + } + catch ( ex ) { } + } + else + { + player.SetObserverModeStaticPosition( target.GetOrigin() ) + player.SetObserverModeStaticAngles( target.GetAngles() ) + player.StartObserverMode( OBS_MODE_STATIC ) + } + } + + player.StopPhysics() + result = player.WaitSignal( "ObserverTargetChanged" ) + } +} + +bool function ClientCommandCallback_spec_next( entity player, array<string> args ) +{ + if ( player.GetObserverMode() == OBS_MODE_CHASE || player.GetObserverMode() == OBS_MODE_STATIC || player.GetObserverMode() == OBS_MODE_IN_EYE ) + player.Signal( "ObserverTargetChanged", { next = true } ) + + return true +} + +bool function ClientCommandCallback_spec_prev( entity player, array<string> args ) +{ + if ( player.GetObserverMode() == OBS_MODE_CHASE || player.GetObserverMode() == OBS_MODE_STATIC || player.GetObserverMode() == OBS_MODE_IN_EYE ) + player.Signal( "ObserverTargetChanged", { next = false } ) + + return true +} + +bool function ClientCommandCallback_spec_mode( entity player, array<string> args ) +{ + // currently unsure how this actually gets called on client, works through console and has references in client.dll tho + if ( player.GetObserverMode() == OBS_MODE_CHASE ) + { + // set to first person spectate + player.SetSpecReplayDelay( FIRST_PERSON_SPECTATOR_DELAY ) + player.SetViewEntity( player.GetObserverTarget(), true ) + player.StartObserverMode( OBS_MODE_IN_EYE ) + } + else if ( player.GetObserverMode() == OBS_MODE_IN_EYE ) + { + // set to third person spectate + player.SetSpecReplayDelay( 0.0 ) + player.StartObserverMode( OBS_MODE_CHASE ) + } + + return true +}
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut index bd0f2d62..5af01346 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut @@ -182,7 +182,12 @@ void function OnPrematchStart() // launch players into intro foreach ( entity player in GetPlayerArray() ) - thread PlayerWatchesWargamesIntro( player ) + { + if ( !IsPrivateMatchSpectator( player ) ) + thread PlayerWatchesWargamesIntro( player ) + else + RespawnPrivateMatchSpectator( player ) + } // 7 seconds of nothing until we start the pod sequence wait 7.0 diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_mp_utility.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_mp_utility.gnut new file mode 100644 index 00000000..2e6e04f4 --- /dev/null +++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_mp_utility.gnut @@ -0,0 +1,146 @@ +untyped + +globalize_all_functions + +struct +{ + table<string,table<string,int> > mapModeScoreLimits +} file + +int function GetRoundScoreLimit_FromPlaylist() +{ + if ( !GameMode_IsDefined( GAMETYPE ) ) + return GetCurrentPlaylistVarInt( "roundscorelimit", 10 ) + + return GameMode_GetRoundScoreLimit( GAMETYPE ) +} + +int function GetScoreLimit_FromPlaylist() +{ + if ( GameMode_HasMapSpecificScoreLimits( GAMETYPE ) ) + return GameMode_GetMapSpecificScoreLimit( GAMETYPE ) + + if ( !GameMode_IsDefined( GAMETYPE ) ) + return GetCurrentPlaylistVarInt( "scorelimit", 10 ) + + return GameMode_GetScoreLimit( GAMETYPE ) +} + +bool function GameMode_HasMapSpecificScoreLimits( string gameType ) +{ + if ( gameType in file.mapModeScoreLimits ) + { + if ( GetMapName() in file.mapModeScoreLimits[gameType] ) + return true + } + return false +} + +int function GameMode_GetMapSpecificScoreLimit( string gameType ) +{ + return file.mapModeScoreLimits[gameType][GetMapName()] +} + +void function GameMode_SetMapSpecificScoreLimit( table<string,int> mapModeScoreTable, string gameType ) +{ + Assert( !( gameType in file.mapModeScoreLimits ), "GAMETYPE has already been added to mapModeScoreLimits" ) + file.mapModeScoreLimits[gameType] <- mapModeScoreTable +} + +bool function IsSuddenDeathGameMode() +{ + return GameMode_GetSuddenDeathEnabled( GameRules_GetGameMode() ) +} + +bool function IsCaptureMode() +{ + return GameRules_GetGameMode() == CAPTURE_POINT +} + +bool function GameModeWantsToSkipBoostsAndTitanEarning() +{ + if ( Riff_TitanAvailability() == eTitanAvailability.Never ) + return true + if ( Riff_BoostAvailability() == eBoostAvailability.Disabled ) + return true + + return false +} + +IntFromEntityCompare function GetScoreboardCompareFunc() +{ + return ScoreboardCompareFuncForGamemode( GameRules_GetGameMode() ) +} + +IntFromEntityCompare function ScoreboardCompareFuncForGamemode( string gamemode ) +{ + IntFromEntityCompare func = GameMode_GetScoreCompareFunc( gamemode ) + if ( func != null ) + return func + + return CompareScore +} + + +bool function IsRoundWinningKillReplayEnabled() +{ + return expect bool ( level.nv.roundWinningKillReplayEnabled ) +} + +bool function IsRoundWinningKillReplayPlaying() +{ + return expect bool ( level.nv.roundWinningKillReplayPlaying ) +} + +bool function HasRoundScoreLimitBeenReached() //Different from RoundScoreLimit_Complete in that it only checks to see if the score required has been reached. Allows us to use it on the client to cover 90% of the cases we want +{ + if ( !IsRoundBased() ) + return false + + int roundLimit = GetRoundScoreLimit_FromPlaylist() + + if ( !roundLimit ) + return false + + int militiaScore = GameRules_GetTeamScore2( TEAM_MILITIA ) + int imcScore = GameRules_GetTeamScore2( TEAM_IMC ) + + if ( ( militiaScore >= roundLimit ) || ( imcScore >= roundLimit ) ) + return true + + return false +} + + +bool function IsTitanAvailable( entity player ) +{ + var shiftIndex = player.GetEntIndex() - 1 + var elimMask = (1 << shiftIndex) + + return (level.nv.titanAvailableBits & elimMask) != 0 +} + + + +bool function IsRespawnAvailable( entity player ) +{ + var shiftIndex = player.GetEntIndex() - 1 + var elimMask = (1 << shiftIndex) + + return (level.nv.respawnAvailableBits & elimMask) != 0 +} + +bool function IsPrivateMatchSpectator( entity player ) +{ + // JFS + #if SERVER + if ( !player.p.clientScriptInitialized ) + return false + #endif + + // NS: allow spectators on non-private_match playlists + if ( ( IsPrivateMatch() || GetConVarBool( "ns_allow_spectators" ) ) && player.GetPersistentVarAsInt( "privateMatchState" ) == 1 ) + return true + + return false +}
\ No newline at end of file |