diff options
Diffstat (limited to 'Northstar.CustomServers/mod/scripts/vscripts/mp')
6 files changed, 261 insertions, 136 deletions
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 |