From fd02b453d3ada42d4fd1774737fca9559d971956 Mon Sep 17 00:00:00 2001 From: Casper Talvio Date: Tue, 2 Jan 2024 23:22:08 +0200 Subject: Add validity check to `SaveStatsPeriodically_Threaded` (#769) --- Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut index bd64e4ca..f3180214 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut @@ -1035,7 +1035,10 @@ void function SaveStatsPeriodically_Threaded() while( true ) { foreach( entity player in GetPlayerArray() ) - Stats_SaveAllStats( player ) + { + if ( IsValid( player ) ) + Stats_SaveAllStats( player ) + } wait 5 } } -- cgit v1.2.3 From af84c42f714484e196cf0d82f9c296e3567b5f35 Mon Sep 17 00:00:00 2001 From: Casper Talvio Date: Tue, 2 Jan 2024 23:24:14 +0200 Subject: Add validity check to `HandleDistanceAndTimeStats_Threaded` to prevent crash (#767) --- Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut | 3 +++ 1 file changed, 3 insertions(+) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut index f3180214..101d5e4e 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut @@ -931,6 +931,9 @@ void function HandleDistanceAndTimeStats_Threaded() // track distance stats foreach ( entity player in GetPlayerArray() ) { + if ( !IsValid( player ) ) + continue + if ( player.p.lastPosForDistanceStatValid ) { // not 100% sure on using Distance2D over Distance tbh -- cgit v1.2.3 From 0e153649396355f8d726bad233992725633edea6 Mon Sep 17 00:00:00 2001 From: Aibek <35672535+L1ghtman2k@users.noreply.github.com> Date: Tue, 2 Jan 2024 17:18:18 -0500 Subject: Fix spectating for LTS (#774) Adds missing spectator initialisation Co-authored-by: Khalmee <87766895+Khalmee@users.noreply.github.com> --- Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index 4c52a9bf..e2bb36d2 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -201,6 +201,14 @@ void function GameStateEnter_Prematch() if ( !GetClassicMPMode() && !ClassicMP_ShouldTryIntroAndEpilogueWithoutClassicMP() ) thread StartGameWithoutClassicMP() + + // Initialise any spectators. Hopefully they are all initialised already in CodeCallback_OnClientConnectionCompleted + // (_base_gametype_mp.gnut) but for modes like LTS this doesn't seem to happen late enough to work properly. + foreach ( player in GetPlayerArray() ) + { + if ( IsPrivateMatchSpectator( player ) ) + InitialisePrivateMatchSpectatorPlayer( player ) + } } void function StartGameWithoutClassicMP() -- cgit v1.2.3 From 7edb0f2e726221d99db56c434ab6757f3428651f Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:20:33 +0000 Subject: Prevent client side crash in MFD when marked target dies out of bounds. (#705) --- .../mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut index 659dbb7a..d36045a6 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut @@ -188,10 +188,15 @@ void function UpdateMarksForKill( entity victim, entity attacker, var damageInfo { if ( victim == GetMarked( victim.GetTeam() ) ) { - MessageToAll( eEventNotifications.MarkedForDeathKill, null, victim, attacker.GetEncodedEHandle() ) + // handle suicides. Not sure what the actual message is that vanilla shows for this + // but this will prevent crashing for now + bool isSuicide = IsSuicide( victim, attacker, DamageInfo_GetDamageSourceIdentifier( damageInfo ) ) + entity actualAttacker = isSuicide ? victim : attacker + + MessageToAll( eEventNotifications.MarkedForDeathKill, null, victim, actualAttacker.GetEncodedEHandle() ) svGlobal.levelEnt.Signal( "MarkKilled", { mark = victim } ) - - if ( attacker.IsPlayer() ) + + if ( !isSuicide && attacker.IsPlayer() ) attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 ) } } \ No newline at end of file -- cgit v1.2.3 From 51ed7763f70273603a45dec1ca611ad3eba7f5f6 Mon Sep 17 00:00:00 2001 From: William Miller Date: Fri, 26 Jan 2024 18:44:31 -0300 Subject: Change Aegis Reset Functionality to use titan chassis ref instead of raw index (#788) The previous index passed to the method did not correspond properly with the persistent data index. Instead pass their class reference and then use that to grab the correct index. --- .../mod/scripts/vscripts/sh_progression.nut | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut index 2dc88d0d..5098dd32 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut @@ -88,7 +88,8 @@ bool function ClientCommand_SetProgression( entity player, array args ) /// Resets a specific Titan's Aegis rank back to `0` /// * `player` - The player entity to perform the action on -/// * `args` - The arguments passed from the client command. `args[0]` should be an integer corresponding to the index of the Titan to reset. +/// * `args` - The arguments passed from the client command. `args[0]` should be a string corresponding to the chassis name of the Titan to reset. +/// Valid chassis are: ion, tone, vanguard, northstar, ronin, legion, and scorch. /// /// Returns `true` on success and `false` on missing args. bool function ClientCommand_ResetTitanAegis( entity player, array args ) @@ -96,7 +97,12 @@ bool function ClientCommand_ResetTitanAegis( entity player, array args ) if ( !args.len() ) return false - int suitIndex = args[0].tointeger() + string titanRef = args[0].tolower() + if( !PersistenceEnumValueIsValid( "titanClasses", titanRef ) ) + return false + + int suitIndex = PersistenceGetEnumIndexForItemName( "titanClasses", titanRef ) + player.SetPersistentVar( "titanFDUnlockPoints[" + suitIndex + "]", 0 ) player.SetPersistentVar( "previousFDUnlockPoints[" + suitIndex + "]", 0 ) player.SetPersistentVar( "fdTitanXP[" + suitIndex + "]", 0 ) -- cgit v1.2.3 From 14561cf90f3f80ad844803bdb621c6bd720bdc55 Mon Sep 17 00:00:00 2001 From: William Miller Date: Fri, 26 Jan 2024 18:47:24 -0300 Subject: Unlock remaining DLC items while progression is enabled (#786) As they cannot be unlocked by levelling up and some players may still want to use them while having progression enabled, we simply keep them unlocked when progression is enabled. --- Northstar.CustomServers/mod/scripts/vscripts/_items.nut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut index a23a68b0..96623086 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut @@ -10094,7 +10094,7 @@ void function InitUnlockAsEntitlement( string itemRef, string parentRef, int ent unlock = file.entitlementUnlocks[fullRef] } - unlock.entitlementIds.append( entitlementId ) + unlock.entitlementIds.append( 1 ) // Using `1` here instead of the huge DLC check I did previously. Having the `1` seems to keep all paid cosmetics unlocked with progression enabled. } array function GetEntitlementIds( string itemRef, string parentRef = "" ) -- cgit v1.2.3 From c8193f64ff169441ef9a359af126a99121040224 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:12:10 +0000 Subject: Scale pilot kill core gain properly for mixed modes (#702) Closes #682 --- Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut index be20982d..aba1d540 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut @@ -304,7 +304,7 @@ void function ScoreEvent_SetupEarnMeterValuesForMixedModes() // mixed modes in t { // todo needs earn/overdrive values // player-controlled stuff - ScoreEvent_SetEarnMeterValues( "KillPilot", 0.07, 0.15 ) + ScoreEvent_SetEarnMeterValues( "KillPilot", 0.07, 0.15, 0.33 ) // 5% for titan cores ScoreEvent_SetEarnMeterValues( "KillTitan", 0.0, 0.15 ) ScoreEvent_SetEarnMeterValues( "TitanKillTitan", 0.0, 0.0 ) // unsure ScoreEvent_SetEarnMeterValues( "PilotBatteryStolen", 0.0, 0.35 ) // this actually just doesn't have overdrive in vanilla even -- cgit v1.2.3 From 51eb5eade9aedd69b64a293e95c008a8a8d1f908 Mon Sep 17 00:00:00 2001 From: NoCatt <86153630+NoCatt@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:37:57 +0100 Subject: Add weapon dropped callback Adds a callback for when a weapon is dropped. In the progress this also fixes weapons still dropping unintentionally in certain case. --- .../mod/scripts/vscripts/_loadouts_mp.gnut | 20 ++++------------- .../mod/scripts/vscripts/mp/_base_gametype_mp.gnut | 2 ++ .../mod/scripts/vscripts/mp/_codecallbacks.gnut | 25 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 16 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut index 63756fdc..141cfe15 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut @@ -51,8 +51,6 @@ void function SvLoadoutsMP_Init() AddClientCommandCallback( "InGameMPMenuClosed", ClientCommandCallback_InGameMPMenuClosed ) AddClientCommandCallback( "LoadoutMenuClosed", ClientCommandCallback_LoadoutMenuClosed ) } - - AddCallback_OnPlayerKilled( DestroyDroppedWeapon ) } void function SetLoadoutGracePeriodEnabled( bool enabled ) @@ -62,20 +60,10 @@ void function SetLoadoutGracePeriodEnabled( bool enabled ) void function SetWeaponDropsEnabled( bool enabled ) { - file.weaponDropsEnabled = enabled -} - -void function DestroyDroppedWeapon( entity victim, entity attacker, var damageInfo ) -{ - if ( !file.weaponDropsEnabled && IsValid( victim.GetActiveWeapon() ) ) - thread DelayDestroyDroppedWeapon( victim.GetActiveWeapon() ) -} - -void function DelayDestroyDroppedWeapon( entity weapon ) -{ - WaitEndFrame() - if ( IsValid( weapon ) ) - weapon.Destroy() + if( enabled ) + FlagSet( "WeaponDropsAllowed" ) + else + FlagClear( "WeaponDropsAllowed" ) } void function AddCallback_OnTryGetTitanLoadout( TryGetTitanLoadoutCallbackType callback ) 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 9288f75e..b77a37b2 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut @@ -43,6 +43,8 @@ void function BaseGametype_Init_MPSP() AddCallback_OnPlayerKilled( CheckForAutoTitanDeath ) RegisterSignal( "PlayerRespawnStarted" ) RegisterSignal( "KillCamOver" ) + + FlagInit( "WeaponDropsAllowed", true ) } void function SetIntermissionCamera( entity camera ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut index ff281d6e..8644296e 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut @@ -18,6 +18,8 @@ global function CodeCallback_OnProjectileGrappled global function DamageInfo_ScaleDamage global function CodeCallback_CheckPassThroughAddsMods global function SetTitanMeterGainScale +global function CodeCallback_WeaponDropped +global function AddCallback_OnWeaponDropped #if MP global function CodeCallback_OnServerAnimEvent @@ -43,6 +45,7 @@ struct ] table playerAccumulatedDamageData + array< void functionref( entity ) > weaponDroppedCallbacks } file void function CodeCallback_Init() @@ -1030,4 +1033,26 @@ void function CodeCallback_OnServerAnimEvent( entity ent, string eventName ) PerfEnd( PerfIndexServer.CB_OnServerAnimEvent ) } + +void function AddCallback_OnWeaponDropped( void functionref( entity ) callback ) +{ + file.weaponDroppedCallbacks.append( callback ) +} + +void function CodeCallback_WeaponDropped( entity weapon ) +{ + // shamelessly taken form SP + if ( !IsValid( weapon ) ) + return + + // Might look a bit hacky to put it here, but thats how SP does it + if ( !Flag( "WeaponDropsAllowed" ) ) + { + weapon.Destroy() + return + } + + foreach( callback in file.weaponDroppedCallbacks ) + callback( weapon ) +} #endif // #if MP \ No newline at end of file -- cgit v1.2.3 From a8c0653b25616a4b3b5cf224ae7eddc856fad7cc Mon Sep 17 00:00:00 2001 From: Cyn <70904206+itscynxx@users.noreply.github.com> Date: Tue, 26 Mar 2024 07:52:16 -0500 Subject: Fix weapon drop callback breaking SP (#797) Move weapon drop callback globalization to `#if MP` --- Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut index 8644296e..0d1b42b7 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut @@ -18,11 +18,11 @@ global function CodeCallback_OnProjectileGrappled global function DamageInfo_ScaleDamage global function CodeCallback_CheckPassThroughAddsMods global function SetTitanMeterGainScale -global function CodeCallback_WeaponDropped -global function AddCallback_OnWeaponDropped #if MP global function CodeCallback_OnServerAnimEvent +global function CodeCallback_WeaponDropped +global function AddCallback_OnWeaponDropped #endif struct AccumulatedDamageData -- cgit v1.2.3 From 11787b3896b8242ac8f7e583d72cabf68f9161a9 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:50:38 +0100 Subject: Validate selected Titan loadout index better (#762) Seemingly there are cases where mods can set an invalid Titan loadout index, which then causes the progression checks to attempt to set the player's Titan model to an invalid index. This commit adds a check to ensure that it is within the bounds of the titan loadout array. --- .../mod/scripts/vscripts/sh_progression.nut | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut index 5098dd32..3297643e 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut @@ -224,11 +224,12 @@ void function ValidateEquippedItems( entity player ) } // titan loadouts + int selectedTitanLoadoutIndex = player.GetPersistentVarAsInt( "titanSpawnLoadout.index" ) for ( int titanLoadoutIndex = 0; titanLoadoutIndex < NUM_PERSISTENT_TITAN_LOADOUTS; titanLoadoutIndex++ ) { printt( "- VALIDATING TITAN LOADOUT: " + titanLoadoutIndex ) - bool isSelected = titanLoadoutIndex == player.GetPersistentVarAsInt( "titanSpawnLoadout.index" ) + bool isSelected = titanLoadoutIndex == selectedTitanLoadoutIndex TitanLoadoutDef loadout = GetTitanLoadout( player, titanLoadoutIndex ) TitanLoadoutDef defaultLoadout = shGlobal.defaultTitanLoadouts[titanLoadoutIndex] @@ -446,11 +447,18 @@ void function ValidateEquippedItems( entity player ) { printt( " - SELECTED TITAN CLASS IS LOCKED, RESETTING" ) player.SetPersistentVar( "titanSpawnLoadout.index", 0 ) - Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateTitanModel", 0 ) + selectedTitanLoadoutIndex = 0 } } - Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateTitanModel", player.GetPersistentVarAsInt( "titanSpawnLoadout.index" ) ) + if ( selectedTitanLoadoutIndex < 0 || selectedTitanLoadoutIndex >= NUM_PERSISTENT_TITAN_LOADOUTS ) + { + printt( "- SELECTED TITAN CLASS IS INVALID, RESETTING" ) + player.SetPersistentVar( "titanSpawnLoadout.index", 0 ) + selectedTitanLoadoutIndex = 0 + } + + Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateTitanModel", selectedTitanLoadoutIndex ) // pilot loadouts for ( int pilotLoadoutIndex = 0; pilotLoadoutIndex < NUM_PERSISTENT_PILOT_LOADOUTS; pilotLoadoutIndex++ ) -- cgit v1.2.3 From 9661372712f47f3c5d3ef83f1c7776484eeab52f Mon Sep 17 00:00:00 2001 From: William Miller Date: Fri, 12 Apr 2024 20:54:55 -0300 Subject: Move all owned NPCs together with player on Team Switching (#789) Uses a call back that is triggered when the player switches team to update their own entities accordingly. --- .../mod/scripts/vscripts/mp/_gamestate_mp.nut | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index e2bb36d2..5cc096f2 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -96,6 +96,7 @@ void function PIN_GameStart() AddCallback_OnPlayerKilled( OnPlayerKilled ) AddDeathCallback( "npc_titan", OnTitanKilled ) + AddCallback_EntityChangedTeam( "player", OnPlayerChangedTeam ) RegisterSignal( "CleanUpEntitiesForRoundEnd" ) } @@ -1017,3 +1018,21 @@ void function DialoguePlayWinnerDetermined() PlayFactionDialogueToTeam( "scoring_lost", losingTeam ) } } + +/// This is to move all NPCs that a player owns from one team to the other during a match +/// Auto-Titans, Turrets, Ticks and Hacked Spectres will all move along together with the player to the new Team +/// Also possibly prevents mods that spawns other types of NPCs that players can own from breaking when switching (i.e Drones, Hacked Reapers) +void function OnPlayerChangedTeam( entity player ) +{ + if ( !player.hasConnected ) // Prevents players who just joined to trigger below code, as server always pre setups their teams + return + + NotifyClientsOfTeamChange( player, GetOtherTeam( player.GetTeam() ), player.GetTeam() ) + + foreach( npc in GetNPCArray() ) + { + entity bossPlayer = npc.GetBossPlayer() + if ( IsValidPlayer( bossPlayer ) && bossPlayer == player && IsAlive( npc ) ) + SetTeam( npc, player.GetTeam() ) + } +} -- cgit v1.2.3 From 6581c4e800e5cec0e4d6f5b3f87b80bd6989233c Mon Sep 17 00:00:00 2001 From: William Miller Date: Fri, 28 Jun 2024 15:10:45 -0300 Subject: Implement Match Goals menu and XP tracking (#756) for vanilla parity when it comes to the progression system. See PR description for full details --- .../mod/scripts/vscripts/_xp.gnut | 16 ++ .../mod/scripts/vscripts/evac/_evac.gnut | 10 + .../mod/scripts/vscripts/faction_xp.gnut | 9 + .../scripts/vscripts/gamemodes/_gamemode_aitdm.nut | 1 + .../scripts/vscripts/gamemodes/_gamemode_at.nut | 6 + .../scripts/vscripts/gamemodes/_gamemode_cp.nut | 41 +++- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 19 ++ .../scripts/vscripts/gamemodes/_gamemode_fra.nut | 1 + .../scripts/vscripts/gamemodes/_gamemode_lts.nut | 33 +++ .../scripts/vscripts/gamemodes/_gamemode_mfd.nut | 15 +- .../scripts/vscripts/gamemodes/_gamemode_ps.nut | 1 + .../vscripts/gamemodes/_gamemode_speedball.nut | 1 + .../scripts/vscripts/gamemodes/_gamemode_tdm.nut | 1 + .../scripts/vscripts/gamemodes/_gamemode_ttdm.nut | 31 +++ .../mod/scripts/vscripts/mp/_challenges.gnut | 268 +++++++++++++++++++++ .../mod/scripts/vscripts/mp/_gamestate_mp.nut | 38 ++- .../mod/scripts/vscripts/mp/_score.nut | 17 ++ .../mod/scripts/vscripts/titan_xp.gnut | 22 ++ .../mod/scripts/vscripts/weapon_xp.gnut | 7 + 19 files changed, 533 insertions(+), 4 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut index 2b95d1a8..97d993e6 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut @@ -16,7 +16,10 @@ void function SetupPlayerPreviousXPValues( entity player ) player.SetPersistentVar( "previousFactionXP[" + xpFaction + "]", FactionGetXP( player, xpFaction ) ) foreach ( string xpTitan in shTitanXP.titanClasses ) + { player.SetPersistentVar( "previousTitanXP[" + xpTitan + "]", TitanGetXP( player, xpTitan ) ) + player.SetPersistentVar( "fdPreviousTitanXP[" + xpTitan + "]", FD_TitanGetXP( player, xpTitan ) ) + } foreach ( string xpWeapon in shWeaponXP.weaponClassNames ) player.SetPersistentVar( GetItemPersistenceStruct( xpWeapon ) + ".previousWeaponXP", WeaponGetXP( player, xpWeapon ) ) @@ -35,6 +38,14 @@ void function HandleXPGainForScoreEvent( entity player, ScoreEvent event ) int weaponXp = ScoreEvent_GetXPValueWeapon( event ) int titanXp = ScoreEvent_GetXPValueTitan( event ) int factionXp = ScoreEvent_GetXPValueFaction( event ) + + if ( player.GetPlayerNetInt( "xpMultiplier" ) > 0 || GetCurrentPlaylistVarInt( "double_xp_enabled", 0 ) == 1 ) + { + xpValue *= 2 + weaponXp *= 2 + titanXp *= 2 + factionXp *= 2 + } entity weapon = player.GetActiveWeapon() if ( IsValid( weapon ) && ShouldTrackXPForWeapon( weapon.GetWeaponClassName() ) && weaponXp != 0 ) @@ -63,5 +74,10 @@ void function AddXP( entity player, int amount ) int newXp = player.GetPersistentVarAsInt( "xp" ) int newLevel = GetLevelForXP( newXp ) if ( newLevel != oldLevel ) + { Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerLeveledUp", player.GetPersistentVarAsInt( "gen" ), newLevel ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForPlayerLevels( player, player.GetPersistentVarAsInt( "gen" ), newLevel ) + } } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut index af074689..760daef0 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut @@ -246,6 +246,9 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa SetTeamActiveObjective( evacTeam, "EG_DropshipExtract", Time() + arrivalTime, file.evacIcon ) SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract", Time() + arrivalTime, file.evacIcon ) + foreach( entity player in GetPlayerArrayOfTeam( evacTeam ) ) //Show the Evac Match Goal for players of the team that lost the match + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 0 ) + // 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 @@ -280,6 +283,9 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa { SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipDestroyed" ) SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_DropshipExtractDropshipDestroyed" ) + + foreach( entity player in GetPlayerArrayOfTeam( evacTeam ) ) + SetPlayerChallengeEvacState( player, 0 ) } foreach ( entity player in dropship.s.evacSlots ) @@ -402,7 +408,10 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa if ( !PlayerInDropship( player, dropship ) ) { if ( player.GetTeam() == dropship.GetTeam() ) + { SetPlayerActiveObjective( player, "EG_DropshipExtractFailedEscape" ) + SetPlayerChallengeEvacState( player, 2 ) + } continue } @@ -417,6 +426,7 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa 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 ) + SetPlayerChallengeEvacState( player, 1 ) // display player [Evacuated] in killfeed foreach ( entity otherPlayer in GetPlayerArray() ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut index 6555c875..5aee1104 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut @@ -4,10 +4,19 @@ void function AddFactionXP( entity player, int amount ) { string faction = GetFactionChoice( player ) int oldLevel = FactionGetLevel( player, faction ) + int FactionXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.FACTION_LEVELED + "]" ) + // increment xp player.SetPersistentVar( "factionXP[" + faction + "]", min( FactionGetXP( player, faction ) + amount, FactionGetMaxXP( faction ) ) ) // note: no notif for faction level up if ( FactionGetLevel( player, faction ) != oldLevel ) + { AddPlayerScore( player, "FactionLevelUp" ) + IncrementPlayerChallengeFactionLeveledUp( player ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.FACTION_LEVELED + "]", FactionXPMatch + 1 ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForFactionLevels( player, faction, oldLevel, FactionGetLevel( player, faction ) ) + } } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut index f47ee90f..7d73c926 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut @@ -60,6 +60,7 @@ void function GamemodeAITdm_Init() } ScoreEvent_SetupEarnMeterValuesForMixedModes() + SetupGenericTDMChallenge() } // add settings diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut index 93a3aa16..9cf0021d 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut @@ -178,6 +178,12 @@ void function AT_PlayerTitleThink( entity player ) { // Set player money count player.SetTitle( "$" + string( AT_GetPlayerBonusPoints( player ) ) ) + + if( AT_GetPlayerBonusPoints( player ) >= 600 && !HasPlayerCompletedMeritScore( player ) ) //Challenge is: "Earn $600." + { + AddPlayerScore( player, "ChallengeATAssault" ) + SetPlayerChallengeMeritScore( player ) + } } else if ( GetGameState() >= eGameState.WinnerDetermined ) { diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut index c5765300..d8b0c9bd 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut @@ -29,6 +29,8 @@ struct { array hardpoints array players + table playerAssaultPoints + table playerDefensePoints } file void function GamemodeCP_Init() @@ -112,11 +114,13 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama { AddPlayerScore( attacker , "HardpointDefense", victim ) attacker.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_DEFENSE) + UpdatePlayerScoreForChallenge(attacker,0,POINTVALUE_HARDPOINT_DEFENSE) } else if((victimCP.hardpoint.GetTeam()==victim.GetTeam())||(GetHardpointCappingTeam(victimCP)==victim.GetTeam())) { AddPlayerScore( attacker, "HardpointAssault", victim ) attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_ASSAULT) + UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_ASSAULT,0) } } } @@ -127,10 +131,12 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama { AddPlayerScore( attacker , "HardpointSnipe", victim ) attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_SNIPE) + UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_SNIPE,0) } else{ AddPlayerScore( attacker , "HardpointSiege", victim ) attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_SIEGE) + UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_SIEGE,0) } } else if(attackerCP.hardpoint!=null)//Perimeter Defense @@ -138,6 +144,7 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama if(attackerCP.hardpoint.GetTeam()==attacker.GetTeam()) AddPlayerScore( attacker , "HardpointPerimeterDefense", victim) attacker.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_PERIMETER_DEFENSE) + UpdatePlayerScoreForChallenge(attacker,0,POINTVALUE_HARDPOINT_PERIMETER_DEFENSE) } foreach(CP_PlayerStruct player in file.players) //Reset Victim Holdtime Counter @@ -308,6 +315,7 @@ void function CapturePointForTeam(HardpointStruct hardpoint, int Team) if(player.IsPlayer()){ AddPlayerScore(player,"ControlPointCapture") player.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_CAPTURE) + UpdatePlayerScoreForChallenge(player,POINTVALUE_HARDPOINT_CAPTURE,0) } } } @@ -319,12 +327,17 @@ void function GamemodeCP_InitPlayer(entity player) playerStruct.timeOnPoints = [0.0,0.0,0.0] playerStruct.isOnHardpoint = false file.players.append(playerStruct) + file.playerAssaultPoints[player] <- 0 + file.playerDefensePoints[player] <- 0 thread PlayerThink(playerStruct) } void function GamemodeCP_RemovePlayer(entity player) { - + if(player in file.playerAssaultPoints) + delete file.playerAssaultPoints[player] + if(player in file.playerDefensePoints) + delete file.playerDefensePoints[player] foreach(index,CP_PlayerStruct playerStruct in file.players) { if(playerStruct.player==player) @@ -376,11 +389,13 @@ void function PlayerThink(CP_PlayerStruct player) { AddPlayerScore(player.player,"ControlPointAmpedHold") player.player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, POINTVALUE_HARDPOINT_AMPED_HOLD ) + UpdatePlayerScoreForChallenge(player.player,0,POINTVALUE_HARDPOINT_AMPED_HOLD) } else { AddPlayerScore(player.player,"ControlPointHold") player.player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, POINTVALUE_HARDPOINT_HOLD ) + UpdatePlayerScoreForChallenge(player.player,0,POINTVALUE_HARDPOINT_HOLD) } } break @@ -574,6 +589,7 @@ void function HardpointThink( HardpointStruct hardpoint ) { AddPlayerScore(player,"ControlPointAmped") player.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_AMPED) + UpdatePlayerScoreForChallenge(player,0,POINTVALUE_HARDPOINT_AMPED) } } } @@ -714,3 +730,26 @@ string function GetHardpointGroup(entity hardpoint) //Hardpoint Entity B on Home return string(hardpoint.kv.hardpointGroup) } + +void function UpdatePlayerScoreForChallenge(entity player,int assaultpoints = 0,int defensepoints = 0) +{ + if(player in file.playerAssaultPoints) + { + file.playerAssaultPoints[player] += assaultpoints + if( file.playerAssaultPoints[player] >= 1000 && !HasPlayerCompletedMeritScore(player) ) + { + AddPlayerScore(player,"ChallengeCPAssault") + SetPlayerChallengeMeritScore(player) + } + } + + if(player in file.playerDefensePoints) + { + file.playerDefensePoints[player] += defensepoints + if( file.playerDefensePoints[player] >= 500 && !HasPlayerCompletedMeritScore(player) ) + { + AddPlayerScore(player,"ChallengeCPDefense") + SetPlayerChallengeMeritScore(player) + } + } +} diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 9b05c3d4..fefdbcdd 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -421,8 +421,15 @@ void function CaptureFlag( entity player, entity flag ) assistList = file.militiaCaptureAssistList foreach( entity assistPlayer in assistList ) + { if ( player != assistPlayer ) AddPlayerScore( assistPlayer, "FlagCaptureAssist", player ) + if( !HasPlayerCompletedMeritScore( assistPlayer ) ) + { + AddPlayerScore( assistPlayer, "ChallengeCTFCapAssist" ) + SetPlayerChallengeMeritScore( assistPlayer ) + } + } assistList.clear() @@ -430,6 +437,12 @@ void function CaptureFlag( entity player, entity flag ) MessageToPlayer( player, eEventNotifications.YouCapturedTheEnemyFlag ) EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_PlayerScore" ) + if( !HasPlayerCompletedMeritScore( player ) ) + { + AddPlayerScore( player, "ChallengeCTFRetAssist" ) + SetPlayerChallengeMeritScore( player ) + } + MessageToTeam( team, eEventNotifications.PlayerCapturedEnemyFlag, player, player ) EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamScore", player.GetTeam(), player ) @@ -503,6 +516,12 @@ void function TryReturnFlag( entity player, entity flag ) AddPlayerScore( player, "FlagReturn", player ) player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, 1 ) + if( !HasPlayerCompletedMeritScore( player ) ) + { + AddPlayerScore( player, "ChallengeCTFRetAssist" ) + SetPlayerChallengeMeritScore( player ) + } + MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerReturnedFriendlyFlag, null, player ) EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_TeamReturnsFlag", flag.GetTeam() ) PlayFactionDialogueToTeam( "ctf_flagReturnedFriendly", flag.GetTeam() ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut index 9d8f84b5..6d0fd3c7 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut @@ -17,6 +17,7 @@ void function GamemodeFRA_Init() ScoreEvent_SetEarnMeterValues( "PilotBatteryPickup", 0.0, 0.34 ) EarnMeterMP_SetPassiveMeterGainEnabled( false ) PilotBattery_SetMaxCount( 3 ) + SetupGenericFFAChallenge() AddCallback_OnPlayerKilled( FRARemoveEarnMeter ) } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut index 31c85a57..8999231d 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut @@ -8,6 +8,8 @@ struct { float lastDamageInfoTime bool shouldDoHighlights + + table< entity, int > pilotstreak } file void function GamemodeLts_Init() @@ -34,6 +36,37 @@ void function GamemodeLts_Init() ClassicMP_SetCustomIntro( ClassicMP_DefaultNoIntro_Setup, ClassicMP_DefaultNoIntro_GetLength() ) ClassicMP_ForceDisableEpilogue( true ) AddCallback_GameStateEnter( eGameState.Playing, WaitForThirtySecondsLeft ) + + AddCallback_OnClientConnected( SetupPlayerLTSChallenges ) //Just to make up the Match Goals tracking + AddCallback_OnClientDisconnected( RemovePlayerLTSChallenges ) //Safety removal of data to prevent crashes + AddCallback_OnPlayerKilled( LTSChallengeForPlayerKilled ) +} + +void function SetupPlayerLTSChallenges( entity player ) +{ + file.pilotstreak[ player ] <- 0 +} + +void function RemovePlayerLTSChallenges( entity player ) +{ + if( player in file.pilotstreak ) + delete file.pilotstreak[ player ] +} + +void function LTSChallengeForPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing ) + return + + if ( victim.IsPlayer() && attacker in file.pilotstreak ) + { + file.pilotstreak[attacker]++ + if( file.pilotstreak[attacker] >= 2 && !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengeLTS" ) + SetPlayerChallengeMeritScore( attacker ) + } + } } void function WaitForThirtySecondsLeft() diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut index d36045a6..768bbde1 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut @@ -180,6 +180,12 @@ void function MarkPlayers( entity imcMark, entity militiaMark ) entity livingMark = GetMarked( GetOtherTeam( deadMark.GetTeam() ) ) livingMark.SetPlayerGameStat( PGS_DEFENSE_SCORE, livingMark.GetPlayerGameStat( PGS_DEFENSE_SCORE ) + 1 ) + if( !HasPlayerCompletedMeritScore( livingMark ) ) + { + AddPlayerScore( livingMark, "ChallengeMFD" ) + SetPlayerChallengeMeritScore( livingMark ) + } + // thread this so we don't kill our own thread thread AddTeamScore( livingMark.GetTeam(), 1 ) } @@ -195,8 +201,15 @@ void function UpdateMarksForKill( entity victim, entity attacker, var damageInfo MessageToAll( eEventNotifications.MarkedForDeathKill, null, victim, actualAttacker.GetEncodedEHandle() ) svGlobal.levelEnt.Signal( "MarkKilled", { mark = victim } ) - + if ( !isSuicide && attacker.IsPlayer() ) + { attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 ) + if( !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengeMFD" ) + SetPlayerChallengeMeritScore( attacker ) + } + } } } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut index 57355ad8..c91c27d1 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut @@ -19,6 +19,7 @@ void function GamemodePs_Init() AddCallback_OnPlayerKilled( GiveScoreForPlayerKill ) ScoreEvent_SetupEarnMeterValuesForMixedModes() SetTimeoutWinnerDecisionFunc( CheckScoreForDraw ) + SetupGenericFFAChallenge() // spawnzone stuff SetShouldCreateMinimapSpawnZones( true ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut index cb277b00..4617476e 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut @@ -18,6 +18,7 @@ void function GamemodeSpeedball_Init() Riff_ForceTitanAvailability( eTitanAvailability.Never ) Riff_ForceSetEliminationMode( eEliminationMode.Pilots ) ScoreEvent_SetupEarnMeterValuesForMixedModes() + SetupGenericFFAChallenge() AddSpawnCallbackEditorClass( "script_ref", "info_speedball_flag", CreateFlag ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut index 5c0e6fec..61ede2d4 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut @@ -6,6 +6,7 @@ void function GamemodeTdm_Init() AddCallback_OnPlayerKilled( GiveScoreForPlayerKill ) ScoreEvent_SetupEarnMeterValuesForMixedModes() SetTimeoutWinnerDecisionFunc( CheckScoreForDraw ) + SetupGenericTDMChallenge() } void function GiveScoreForPlayerKill( entity victim, entity attacker, var damageInfo ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut index 6b30a399..3ba84394 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut @@ -2,6 +2,11 @@ global function GamemodeTTDM_Init const float TTDMIntroLength = 15.0 +struct +{ + table< entity, int > challengeCount +} file + void function GamemodeTTDM_Init() { Riff_ForceSetSpawnAsTitan( eSpawnAsTitan.Always ) @@ -14,6 +19,8 @@ void function GamemodeTTDM_Init() ClassicMP_ForceDisableEpilogue( true ) SetTimeoutWinnerDecisionFunc( CheckScoreForDraw ) + AddCallback_OnClientConnected( SetupPlayerTTDMChallenges ) //Just to make up the Match Goals tracking + AddCallback_OnClientDisconnected( RemovePlayerTTDMChallenges ) //Safety removal of data to prevent crashes AddCallback_OnPlayerKilled( AddTeamScoreForPlayerKilled ) // dont have to track autotitan kills since you cant leave your titan in this mode // probably needs scoreevent earnmeter values @@ -56,6 +63,17 @@ void function TTDMIntroShowIntermissionCam( entity player ) thread PlayerWatchesTTDMIntroIntermissionCam( player ) } +void function SetupPlayerTTDMChallenges( entity player ) +{ + file.challengeCount[ player ] <- 0 +} + +void function RemovePlayerTTDMChallenges( entity player ) +{ + if( player in file.challengeCount ) + delete file.challengeCount[ player ] +} + void function PlayerWatchesTTDMIntroIntermissionCam( entity player ) { player.EndSignal( "OnDestroy" ) @@ -79,6 +97,19 @@ void function AddTeamScoreForPlayerKilled( entity victim, entity attacker, var d if ( victim == attacker || !victim.IsPlayer() || !attacker.IsPlayer() && GetGameState() == eGameState.Playing ) return + if( victim in file.challengeCount ) + file.challengeCount[victim] = 0 + + if( attacker in file.challengeCount ) + { + file.challengeCount[attacker]++ + if( file.challengeCount[attacker] >= 2 && !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengeTTDM" ) + SetPlayerChallengeMeritScore( attacker ) + } + } + AddTeamScore( GetOtherTeam( victim.GetTeam() ), 1 ) } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut index 466a5042..4b866a40 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut @@ -1,6 +1,274 @@ global function InitChallenges +global function SetPlayerChallengeEvacState //Hooked in _evac.gnut +global function SetPlayerChallengeMatchWon //Hooked in _score.nut +global function SetPlayerChallengeMatchComplete //Hooked in _score.nut +global function SetPlayerChallengeMeritScore //Up to gamemodes to use this directly if needed +global function IncrementPlayerChallengeTitanLeveledUp //Hooked in titan_xp.gnut +global function IncrementPlayerChallengeWeaponLeveledUp //Hooked in weapon_xp.gnut +global function IncrementPlayerChallengeFactionLeveledUp //Hooked in faction_xp.gnut (invisible but necessary for post-summary menu) +global function RegisterChallenges_OnMatchEnd //Hooked in _gamestate_mp.gnut + +global function HasPlayerCompletedMeritScore //Check from gamemodes to not reapply SetPlayerChallengeMeritScore +global function SetupGenericTDMChallenge //Used by gamemodes which simply adopts the: "Kill 3 Pilots without dying." Challenge +global function SetupGenericFFAChallenge //Used by gamemodes which simply adopts the: "Kill 5 Pilots." Challenge + +struct +{ + table< entity, int > playerTotalMeritCount + table< entity, bool > playerChallenge + table< entity, int > pilotstreak + bool isHappyHourActive +} file + + + + + + +/*============================================================================================================= + __ __ _ _ ____ _ _ _ + | \/ | __ _ | |_ ___ | |__ / ___|| |__ __ _ | || | ___ _ __ __ _ ___ ___ + | |\/| | / _` || __|/ __|| '_ \ | | | '_ \ / _` || || | / _ \| '_ \ / _` | / _ \/ __| + | | | || (_| || |_| (__ | | | | | |___ | | | || (_| || || || __/| | | || (_| || __/\__ \ + |_| |_| \__,_| \__|\___||_| |_| \____||_| |_| \__,_||_||_| \___||_| |_| \__, | \___||___/ + |___/ +=============================================================================================================*/ void function InitChallenges() { +#if (UI && CLIENT) + + SCB_SetCompleteMeritState( 4 ) + SCB_SetEvacMeritState( 4 ) + SCB_SetMeritCount( 4 ) + SCB_SetScoreMeritState( 4 ) + SCB_SetWinMeritState( 4 ) + SCB_SetWeaponMeritCount( -1 ) + SCB_SetTitanMeritCount( -1 ) + +#elseif (SERVER && MP) + + AddCallback_OnClientConnected( SetupPlayerMenuChallenges ) + AddCallback_OnClientDisconnected( RemovePlayerFromChallengePool ) + +#endif +} + +void function SetupPlayerMenuChallenges( entity player ) +{ + file.playerTotalMeritCount[ player ] <- 0 + file.pilotstreak[ player ] <- 0 + file.playerChallenge[ player ] <- false + + thread SetupChallenges_Threaded( player ) +} +void function SetupChallenges_Threaded( entity player ) +{ + WaitFrame() + + Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 0 ) + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 4 ) //4 tells RUI to hide it + Remote_CallFunction_UI( player, "SCB_SetMeritCount", 0 ) + Remote_CallFunction_UI( player, "SCB_SetScoreMeritState", 0 ) + Remote_CallFunction_UI( player, "SCB_SetWinMeritState", 0 ) + Remote_CallFunction_UI( player, "SCB_SetWeaponMeritCount", 0 ) + Remote_CallFunction_UI( player, "SCB_SetTitanMeritCount", 0 ) +} + +void function SetupGenericTDMChallenge() +{ + AddCallback_OnPlayerKilled( TDMChallenges_OnPlayerKilled ) +} + +void function SetupGenericFFAChallenge() +{ + AddCallback_OnPlayerKilled( FFAChallenges_OnPlayerKilled ) +} + +void function RemovePlayerFromChallengePool( entity player ) +{ + if( player in file.playerChallenge ) + delete file.playerChallenge[ player ] + if( player in file.playerTotalMeritCount ) + delete file.playerTotalMeritCount[ player ] + if( player in file.pilotstreak ) + delete file.pilotstreak[ player ] +} + +void function RegisterChallenges_OnMatchEnd() +{ + bool eliteWarpaintRNG = false + + if( RandomIntRange( 0, 100 ) <= 30 ) //30% Chance to trigger akin to vanilla, apply always since all players have paid cosmetics unlocked + eliteWarpaintRNG = true + + foreach( player in GetPlayerArray() ) + { + player.SetPersistentVar( "isPostGameScoreboardValid", true ) + player.SetPersistentVar( "isFDPostGameScoreboardValid", false ) //FD itself overrides this right after when match ends + SetUIVar( level, "showGameSummary", true ) + + if( eliteWarpaintRNG ) + SetPlayerChallengeSquadLeader( player ) + + if( ShouldAwardHappyHourBonus( player ) ) + { + AddPlayerScore( player, "HappyHourBonus" ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.HAPPY_HOUR + "]", 5 ) //The XP Given from Happy Hour Score is 5 merits + } + } +} + +void function TDMChallenges_OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing ) + return + + if ( victim.IsPlayer() ) + { + if( victim in file.pilotstreak ) + file.pilotstreak[victim] = 0 + if( attacker in file.pilotstreak ) + { + file.pilotstreak[attacker]++ + if( file.pilotstreak[attacker] >= 3 && !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengeTDM" ) + SetPlayerChallengeMeritScore( attacker ) + } + } + } +} + +void function FFAChallenges_OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing ) + return + + if ( victim.IsPlayer() && attacker in file.pilotstreak ) + { + file.pilotstreak[attacker]++ + if( file.pilotstreak[attacker] >= 5 && !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengePVPKillCount" ) + SetPlayerChallengeMeritScore( attacker ) + } + } +} + +bool function HasPlayerCompletedMeritScore( entity player ) +{ + Assert( player in file.playerChallenge, player + " is not registered in the challenge pool hooks." ) + return file.playerChallenge[ player ] +} + + + + + + + +/*============================================================================================================= + ____ _ _ _ _ + / ___| __ _ _ __ ___ ___ _ __ ___ ___ __| | ___ | | | | ___ ___ | | __ ___ + | | _ / _` || '_ ` _ \ / _ \| '_ ` _ \ / _ \ / _` | / _ \ | |_| | / _ \ / _ \ | |/ // __| + | |_| || (_| || | | | | || __/| | | | | || (_) || (_| || __/ | _ || (_) || (_) || < \__ \ + \____| \__,_||_| |_| |_| \___||_| |_| |_| \___/ \__,_| \___| |_| |_| \___/ \___/ |_|\_\|___/ + +=============================================================================================================*/ + +void function SetPlayerChallengeEvacState( entity player, int successEvac = 0 ) +{ + if( successEvac == 0 ) //Evac Ship destroyed + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 2 ) + + else if( successEvac == 1 ) //Player itself managed to evac + { + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 1 ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.EVAC + "]", 1 ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) + } + + else if( successEvac == 2 ) //Team managed to evac + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 3 ) +} + +void function SetPlayerChallengeMatchWon( entity player, bool playerWon ) +{ + if( playerWon ) + { + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetWinMeritState", 1 ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.MATCH_VICTORY + "]", 1 ) + player.SetPersistentVar( "matchWin", true ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) + } + else + Remote_CallFunction_UI( player, "SCB_SetWinMeritState", -1 ) +} + +void function SetPlayerChallengeMatchComplete( entity player ) +{ + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 1 ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.MATCH_COMPLETED + "]", 1 ) + player.SetPersistentVar( "matchComplete", true ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) +} + +void function SetPlayerChallengeSquadLeader( entity player ) +{ + if( !ProgressionEnabledForPlayer( player ) ) + return + + Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderDoubleXP" ) + Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderBonus", player.GetEncodedEHandle() ) + player.SetPersistentVar( "matchSquadBonus", true ) + Player_GiveDoubleXP( player, 1 ) + foreach( entity teamplayer in GetPlayerArrayOfTeam( player.GetTeam() ) ) + { + if( teamplayer == player ) + continue + + Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderBonus", teamplayer.GetEncodedEHandle() ) + } +} + +void function SetPlayerChallengeMeritScore( entity player ) +{ + if( !HasPlayerCompletedMeritScore( player ) ) + { + file.playerChallenge[ player ] = true + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetScoreMeritState", 1 ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.SCORE_MILESTONE + "]", 1 ) + player.SetPersistentVar( "matchScoreEvent", true ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) + } +} + +void function IncrementPlayerChallengeTitanLeveledUp( entity player ) +{ + player.p.meritData.titanMerits++ + file.playerTotalMeritCount[ player ]++ + + Remote_CallFunction_UI( player, "SCB_SetTitanMeritCount", player.p.meritData.titanMerits++ ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) +} + +void function IncrementPlayerChallengeWeaponLeveledUp( entity player ) +{ + player.p.meritData.weaponMerits++ + file.playerTotalMeritCount[ player ]++ + + Remote_CallFunction_UI( player, "SCB_SetWeaponMeritCount", player.p.meritData.weaponMerits ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) +} + +void function IncrementPlayerChallengeFactionLeveledUp( entity player ) +{ + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) } \ 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 5cc096f2..5eba24ac 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -396,6 +396,7 @@ void function GameStateEnter_WinnerDetermined_Threaded() } else { + RegisterChallenges_OnMatchEnd() if ( ClassicMP_ShouldRunEpilogue() ) { ClassicMP_SetupEpilogue() @@ -834,11 +835,44 @@ void function SetWinner( int team, string winningReason = "", string losingReaso } SetGameState( eGameState.WinnerDetermined ) + ScoreEvent_RoundComplete( team ) } else + { SetGameState( eGameState.WinnerDetermined ) - - ScoreEvent_MatchComplete( team ) + ScoreEvent_MatchComplete( team ) + + array players = GetPlayerArray() + int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( GAMETYPE ) + if ( compareFunc != null ) + { + players.sort( compareFunc ) + int playerCount = players.len() + int currentPlace = 1 + for ( int i = 0; i < 3; i++ ) + { + if ( i >= playerCount ) + continue + + if ( i > 0 && compareFunc( players[i - 1], players[i] ) != 0 ) + currentPlace += 1 + + switch( currentPlace ) + { + case 1: + UpdatePlayerStat( players[i], "game_stats", "mvp" ) + UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) + break + case 2: + UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) + break + case 3: + UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) + break + } + } + } + } } } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut index aba1d540..2a4c4282 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut @@ -8,6 +8,7 @@ global function ScoreEvent_TitanDoomed global function ScoreEvent_TitanKilled global function ScoreEvent_NPCKilled global function ScoreEvent_MatchComplete +global function ScoreEvent_RoundComplete global function ScoreEvent_SetEarnMeterValues global function ScoreEvent_SetupEarnMeterValuesForMixedModes @@ -287,8 +288,24 @@ void function ScoreEvent_MatchComplete( int winningTeam ) foreach( entity player in GetPlayerArray() ) { AddPlayerScore( player, "MatchComplete" ) + SetPlayerChallengeMatchComplete( player ) if ( player.GetTeam() == winningTeam ) + { AddPlayerScore( player, "MatchVictory" ) + SetPlayerChallengeMatchWon( player, true ) + } + else + SetPlayerChallengeMatchWon( player, false ) + } +} + +void function ScoreEvent_RoundComplete( int winningTeam ) +{ + foreach( entity player in GetPlayerArray() ) + { + AddPlayerScore( player, "RoundComplete" ) + if ( player.GetTeam() == winningTeam ) + AddPlayerScore( player, "RoundVictory" ) } } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut index 0436a393..847881b5 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut @@ -1,17 +1,39 @@ global function AddTitanXP +global function AddFDTitanXP void function AddTitanXP( entity player, int amount ) { string titan = GetActiveTitanLoadout( player ).titanClass int oldLevel = TitanGetLevel( player, titan ) + int TitanXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.TITAN_LEVELED + "]" ) // increment xp player.SetPersistentVar( "titanXP[" + titan + "]", min( TitanGetXP( player, titan ) + amount, TitanGetMaxXP( titan ) ) ) + Remote_CallFunction_NonReplay( player, "ServerCallback_TitanXPAdded", shTitanXP.titanClasses.find( titan ), TitanGetXP( player, titan ), amount ) // level up notif if ( TitanGetLevel( player, titan ) != oldLevel ) { Remote_CallFunction_NonReplay( player, "ServerCallback_TitanLeveledUp", shTitanXP.titanClasses.find( titan ), TitanGetGen( player, titan ), TitanGetLevel( player, titan ) ) AddPlayerScore( player, "TitanLevelUp" ) + IncrementPlayerChallengeTitanLeveledUp( player ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.TITAN_LEVELED + "]", TitanXPMatch + 1 ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForTitanLevels( player, titan, oldLevel, TitanGetLevel( player, titan ) ) } +} + +void function AddFDTitanXP( entity player, int fdXPamount ) +{ + string titanRef = GetActiveTitanLoadout( player ).titanClass + + player.SetPersistentVar( "fdTitanXP[" + titanRef + "]", FD_TitanGetPreviousXP( player, titanRef ) + fdXPamount ) + int startingLevel = FD_TitanGetLevelForXP( titanRef, FD_TitanGetPreviousXP( player, titanRef ) ) + int endingLevel = FD_TitanGetLevelForXP( titanRef, FD_TitanGetXP( player, titanRef ) ) + + Player_GiveFDUnlockPoints( player, endingLevel - startingLevel ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForFDTitanLevels( player, titanRef, startingLevel, endingLevel ) } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut index 4e25e301..0b0084b3 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut @@ -6,15 +6,22 @@ void function AddWeaponXP( entity player, int amount ) entity activeWeapon = player.GetActiveWeapon() string weaponClassname = activeWeapon.GetWeaponClassName() int oldLevel = WeaponGetLevel( player, weaponClassname ) + int WeaponXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.WEAPON_LEVELED + "]" ) // increment xp player.SetPersistentVar( GetItemPersistenceStruct( weaponClassname ) + ".weaponXP", min( WeaponGetXP( player, weaponClassname ) + amount, WeaponGetMaxXP( weaponClassname ) ) ) + Remote_CallFunction_NonReplay( player, "ServerCallback_WeaponXPAdded", shWeaponXP.weaponClassNames.find( weaponClassname ), WeaponGetXP( player, weaponClassname ), amount ) // level up notif if ( WeaponGetLevel( player, weaponClassname ) != oldLevel ) { Remote_CallFunction_NonReplay( player, "ServerCallback_WeaponLeveledUp", shWeaponXP.weaponClassNames.find( weaponClassname ), WeaponGetGen( player, weaponClassname ), WeaponGetLevel( player, weaponClassname ) ) AddPlayerScore( player, "WeaponLevelUp" ) + IncrementPlayerChallengeWeaponLeveledUp( player ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.WEAPON_LEVELED + "]", WeaponXPMatch + 1 ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForWeaponLevels( player, weaponClassname, oldLevel, WeaponGetLevel( player, weaponClassname ) ) } // proscreen -- cgit v1.2.3 From 0198d3f99c40ab641c85fff62b62276407e0dfc3 Mon Sep 17 00:00:00 2001 From: William Miller Date: Fri, 5 Jul 2024 19:02:11 -0300 Subject: Unfuck the Spawns (#808) 1. Get rid of the odd logic of limiting the algorithm to 3 spawn points 2. Changes the specific CTF algorithm to calculate the spawn points from the flag bases themselves rather than the initial spawn points which was causing severe inconsistencies. 3. Remove some checks in regards to map side swapping when matches reaches half-times due to odd behaviour in native code 4. Mitigate usage of `GetOtherTeam` for certain checks since that function might return Unreachable when it's a gamemode with more than 2 Teams and intentionally script crash the server. --- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 31 ++++++-------- .../mod/scripts/vscripts/mp/spawn.nut | 48 +++++++++------------- 2 files changed, 31 insertions(+), 48 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index fefdbcdd..97addc24 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -75,27 +75,20 @@ void function RateSpawnpoints_CTF( int checkClass, array spawnpoints, in bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) { // ensure spawnpoints aren't too close to enemy base + vector allyFlagSpot + vector enemyFlagSpot + foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) + { + if( spawn.GetTeam() == team ) + allyFlagSpot = spawn.GetOrigin() + else + enemyFlagSpot = spawn.GetOrigin() + } - if ( HasSwitchedSides() ) - team = GetOtherTeam( team ) - - array startSpawns = SpawnPoints_GetPilotStart( team ) - array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) ) - - vector averageFriendlySpawns - vector averageEnemySpawns - - foreach ( entity spawn in startSpawns ) - averageFriendlySpawns += spawn.GetOrigin() - - averageFriendlySpawns /= startSpawns.len() - - foreach ( entity spawn in enemyStartSpawns ) - averageEnemySpawns += spawn.GetOrigin() - - averageEnemySpawns /= startSpawns.len() + if( Distance2D( spawnpoint.GetOrigin(), allyFlagSpot ) > Distance2D( spawnpoint.GetOrigin(), enemyFlagSpot ) ) + return false - return Distance2D( spawnpoint.GetOrigin(), averageEnemySpawns ) / Distance2D( averageFriendlySpawns, averageEnemySpawns ) > 0.35 + return true } void function CTFInitPlayer( entity player ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut index 4956375b..d64e3a5b 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut @@ -130,7 +130,7 @@ entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnp { int team = player.GetTeam() if ( HasSwitchedSides() ) - team = GetOtherTeam( team ) + team = ( team == TEAM_MILITIA ) ? TEAM_IMC : TEAM_MILITIA array spawnpoints if ( useStartSpawnpoint ) @@ -181,29 +181,19 @@ entity function GetBestSpawnpoint( entity player, array spawnpoints ) foreach ( entity spawnpoint in spawnpoints ) { if ( IsSpawnpointValid( spawnpoint, player.GetTeam() ) ) - { validSpawns.append( spawnpoint ) - - if ( validSpawns.len() == 3 ) // arbitrary small sample size - break - } } - if ( validSpawns.len() == 0 ) + if ( !validSpawns.len() ) { // no valid spawns, very bad, so dont care about spawns being valid anymore print( "found no valid spawns! spawns may be subpar!" ) foreach ( entity spawnpoint in spawnpoints ) - { validSpawns.append( spawnpoint ) - - if ( validSpawns.len() == 3 ) // arbitrary small sample size - break - } } // last resort - if ( validSpawns.len() == 0 ) + if ( !validSpawns.len() ) { print( "map has literally 0 spawnpoints, as such everything is fucked probably, attempting to use info_player_start if present" ) entity start = GetEnt( "info_player_start" ) @@ -215,7 +205,7 @@ entity function GetBestSpawnpoint( entity player, array spawnpoints ) } } - return validSpawns[ RandomInt( validSpawns.len() ) ] // slightly randomize it + return validSpawns.getrandom() // slightly randomize it } bool function IsSpawnpointValid( entity spawnpoint, int team ) @@ -232,15 +222,18 @@ bool function IsSpawnpointValid( entity spawnpoint, int team ) return false } + if( IsFFAGame() && !spawnpoint.IsVisibleToEnemies( team ) ) + return true + int compareTeam = spawnpoint.GetTeam() - if ( HasSwitchedSides() && ( compareTeam == TEAM_MILITIA || compareTeam == TEAM_IMC ) ) - compareTeam = GetOtherTeam( compareTeam ) - + if ( HasSwitchedSides() ) + compareTeam = ( compareTeam == TEAM_MILITIA ) ? TEAM_IMC : TEAM_MILITIA + foreach ( bool functionref( entity, int ) customValidationRule in file.customSpawnpointValidationRules ) if ( !customValidationRule( spawnpoint, team ) ) return false - if ( spawnpoint.GetTeam() > 0 && compareTeam != team && !IsFFAGame() ) + if ( spawnpoint.GetTeam() > 0 && compareTeam != team ) return false if ( spawnpoint.IsOccupied() ) @@ -261,13 +254,12 @@ bool function IsSpawnpointValid( entity spawnpoint, int team ) return false } - const minEnemyDist = 1000.0 // about 20 meters? - // in rsquirrel extend returns null unlike in vanilla squirrel - array< entity > spawnBlockers = GetPlayerArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), minEnemyDist ) - spawnBlockers.extend( GetProjectileArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), minEnemyDist ) ) - foreach ( entity blocker in spawnBlockers ) - if ( blocker.GetTeam() != team ) - return false + const minEnemyDist = 1200.0 + array< entity > spawnBlockers = GetPlayerArrayEx( "any", TEAM_ANY, spawnpoint.GetTeam(), spawnpoint.GetOrigin(), minEnemyDist ) + spawnBlockers.extend( GetProjectileArrayEx( "any", TEAM_ANY, spawnpoint.GetTeam(), spawnpoint.GetOrigin(), minEnemyDist ) ) + spawnBlockers.extend( GetNPCArrayEx( "any", TEAM_ANY, spawnpoint.GetTeam(), spawnpoint.GetOrigin(), minEnemyDist ) ) + if ( spawnBlockers.len() ) + return false // los check return !spawnpoint.IsVisibleToEnemies( team ) @@ -399,11 +391,9 @@ void function InitPreferSpawnNodes() // frontline void function RateSpawnpoints_Frontline( int checkClass, array spawnpoints, int team, entity player ) { + float rating = RandomFloatRange( 0.0, 100.0 ) foreach ( entity spawnpoint in spawnpoints ) - { - float rating = spawnpoint.CalculateFrontlineRating() - spawnpoint.CalculateRating( checkClass, player.GetTeam(), rating, rating > 0 ? rating * 0.25 : rating ) - } + spawnpoint.CalculateRating( checkClass, player.GetTeam(), rating, rating ) } // spawnzones -- cgit v1.2.3 From 852f3bcb04f7dfbf8f41676d47c01d4d6fbf084e Mon Sep 17 00:00:00 2001 From: William Miller Date: Sat, 6 Jul 2024 19:02:53 -0300 Subject: Rework Intro Dropship Script to avoid player overlap (#809) This overhaul makes the Dropships behave consistently now like vanilla and players will only start overlapping each other when both Dropships are full, otherwise code will always attempt to populate them properly instead. --- .../vscripts/mp/_classic_mp_dropship_intro.gnut | 177 ++++++++++----------- 1 file changed, 86 insertions(+), 91 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') 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 23ae37a1..7b6c7e9f 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 @@ -26,18 +26,9 @@ const int MAX_DROPSHIP_PLAYERS = 4 global const float DROPSHIP_INTRO_LENGTH = 15.0 // TODO tweak this -struct IntroDropship -{ - entity dropship - - int playersInDropship - entity[MAX_DROPSHIP_PLAYERS] players -} - struct { - // these used to be IntroDropship[2]s but i wanted to be able to use array.getrandom so they have to be actual arrays - array militiaDropships - array imcDropships + table< entity, array > militiaDropships + table< entity, array > imcDropships float introStartTime } file @@ -52,7 +43,12 @@ void function ClassicMP_DefaultDropshipIntro_Setup() void function DropshipIntro_OnClientConnected( entity player ) { if ( GetGameState() == eGameState.Prematch ) - thread SpawnPlayerIntoDropship( player ) + { + if( PlayerCanSpawn( player ) ) + DoRespawnPlayer( player, null ) + + PutPlayerInDropship( player ) + } } void function OnPrematchStart() @@ -62,11 +58,11 @@ void function OnPrematchStart() print( "starting dropship intro!" ) file.introStartTime = Time() - // make 2 empty dropship structs per team - IntroDropship emptyDropship + // Clear Dropship arrays of Teams for Match Restarts (i.e Half-Times) file.militiaDropships.clear() file.imcDropships.clear() + // Try to gather all possible Dropship spawn points for Team array validDropshipSpawns array dropshipSpawns = GetEntArrayByClass_Expensive( "info_spawnpoint_dropship_start" ) foreach ( entity dropshipSpawn in dropshipSpawns ) @@ -78,47 +74,47 @@ void function OnPrematchStart() validDropshipSpawns.append( dropshipSpawn ) } - // if no dropship spawns for this mode, just allow any dropship spawns + // Use any spawn point if not enough valid for this Gamemode exists if ( validDropshipSpawns.len() < 2 ) validDropshipSpawns = dropshipSpawns // spawn dropships foreach ( entity dropshipSpawn in validDropshipSpawns ) { - // todo: possibly make this only spawn dropships if we've got enough players to need them int createTeam = HasSwitchedSides() ? GetOtherTeam( dropshipSpawn.GetTeam() ) : dropshipSpawn.GetTeam() - array teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships + table< entity, array > teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships if ( teamDropships.len() >= 2 ) - continue + break - // create entity entity dropship = CreateDropship( createTeam, dropshipSpawn.GetOrigin(), dropshipSpawn.GetAngles() ) - - teamDropships.append( clone emptyDropship ) - teamDropships[ teamDropships.len() - 1 ].dropship = dropship - AddAnimEvent( dropship, "dropship_warpout", WarpoutEffect ) + dropship.SetValueForModelKey( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) - dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) + if ( dropshipSpawn.GetTeam() == TEAM_IMC ) + dropship.SetValueForModelKey( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" ) DispatchSpawn( dropship ) - // have to do this after dispatch otherwise it won't work for some reason - // weirdly enough, tf2 actually does use different dropships for imc and militia, despite these concepts not really being a thing for players in tf2 - // probably was just missed by devs, but keeping it in for accuracy + dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) if ( dropshipSpawn.GetTeam() == TEAM_IMC ) dropship.SetModel( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" ) - else - dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) + + teamDropships[ dropship ] <- [ null, null, null, null ] thread PlayAnim( dropship, "dropship_classic_mp_flyin" ) } + // Populate Dropships foreach ( entity player in GetPlayerArray() ) { if ( !IsPrivateMatchSpectator( player ) ) - thread SpawnPlayerIntoDropship( player ) + { + if( PlayerCanSpawn( player ) ) + DoRespawnPlayer( player, null ) + + PutPlayerInDropship( player ) + } else RespawnPrivateMatchSpectator( player ) } @@ -128,68 +124,69 @@ void function OnPrematchStart() void function EndIntroWhenFinished() { - wait 15.0 + wait DROPSHIP_INTRO_LENGTH ClassicMP_OnIntroFinished() } -void function SpawnPlayerIntoDropship( entity player ) +void function PutPlayerInDropship( entity player ) { - player.EndSignal( "OnDestroy" ) + //Find the player's dropship and seat + table< entity, array > teamDropships + if ( player.GetTeam() == TEAM_MILITIA ) + teamDropships = file.militiaDropships + else + teamDropships = file.imcDropships + + entity playerDropship + array< int > availableShipSlots + array< entity > introDropships + int playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS ) + foreach( dropship, playerslot in teamDropships ) + { + introDropships.append( dropship ) + for ( int i = 0; i < MAX_DROPSHIP_PLAYERS; i++ ) + { + if ( !IsValidPlayer( playerslot[i] ) ) + availableShipSlots.append( i ) + } + + if( !availableShipSlots.len() ) + continue + + int slotPick = availableShipSlots.getrandom() + playerslot[slotPick] = player + playerDropship = dropship + playerDropshipIndex = slotPick + break + } + + if( !IsAlive( playerDropship ) ) //If we're at this point, we have more players than we do dropships, so just pick a random one + playerDropship = introDropships.getrandom() + + thread SpawnPlayerIntoDropship( player, playerDropshipIndex, playerDropship ) +} - if ( IsAlive( player ) ) - player.Die() // kill them so we don't have any issues respawning them later +void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, entity playerDropship ) +{ + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDeath" ) - player.s.dropshipIntroIsJumping <- false - OnThreadEnd( function() : ( player ) + OnThreadEnd( function() : ( player, playerDropshipIndex, playerDropship ) { if ( IsValid( player ) ) { player.ClearParent() ClearPlayerAnimViewEntity( player ) - - if ( !player.s.dropshipIntroIsJumping ) - { - player.MovementEnable() - player.EnableWeaponViewModel() - RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) - } + } + if( IsAlive( playerDropship ) ) + { + if ( playerDropship.GetTeam() == TEAM_MILITIA ) + file.militiaDropships[ playerDropship ][ playerDropshipIndex ] = null + else + file.imcDropships[ playerDropship ][ playerDropshipIndex ] = null } }) - WaitFrame() - - player.EndSignal( "OnDeath" ) - - // find the player's dropship and seat - array teamDropships - if ( player.GetTeam() == TEAM_MILITIA ) - teamDropships = file.militiaDropships - else - teamDropships = file.imcDropships - - IntroDropship playerDropship - int playerDropshipIndex = -1 - foreach ( IntroDropship dropship in teamDropships ) - for ( int i = 0; i < dropship.players.len(); i++ ) - if ( dropship.players[ i ] == null ) - { - playerDropship = dropship - playerDropshipIndex = i - - dropship.players[ i ] = player - break - } - - if ( playerDropship.dropship == null ) - { - // if we're at this point, we have more players than we do dropships, so just pick a random one - playerDropship = teamDropships.getrandom() - playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS ) - } - - // respawn player and holster their weapons so they aren't out - if ( !IsAlive( player ) ) - player.RespawnPlayer( null ) HolsterAndDisableWeapons(player) player.DisableWeaponViewModel() @@ -208,9 +205,7 @@ void function SpawnPlayerIntoDropship( entity player ) idleSequence.viewConeFunction = ViewConeRampFree idleSequence.hideProxy = true idleSequence.setInitialTime = Time() - file.introStartTime - thread FirstPersonSequence( idleSequence, player, playerDropship.dropship ) - WaittillAnimDone( player ) - + waitthread FirstPersonSequence( idleSequence, player, playerDropship ) // todo: possibly rework this to actually get the time the idle anim takes and start the starttime of the jump sequence for very late joiners using that // jump sequence @@ -218,13 +213,12 @@ void function SpawnPlayerIntoDropship( entity player ) jumpSequence.firstPersonAnim = DROPSHIP_JUMP_ANIMS_POV[ playerDropshipIndex ] jumpSequence.thirdPersonAnim = DROPSHIP_JUMP_ANIMS[ playerDropshipIndex ] jumpSequence.attachment = "ORIGIN" + jumpSequence.viewConeFunction = ViewConeFree jumpSequence.setInitialTime = max( 0.0, Time() - ( file.introStartTime + 11.0 ) ) // pretty sure you should do this with GetScriptedAnimEventCycleFrac? // idk unsure how to use that, all i know is getsequenceduration > the length it actually should be - thread FirstPersonSequence( jumpSequence, player, playerDropship.dropship ) - WaittillAnimDone( player ) // somehow this is better than just waiting for the blocking FirstPersonSequence call? + waitthread FirstPersonSequence( jumpSequence, player, playerDropship ) - player.s.dropshipIntroIsJumping <- true thread PlayerJumpsFromDropship( player ) } @@ -244,16 +238,17 @@ void function PlayerJumpsFromDropship( entity player ) RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) } }) - - // wait for intro timer to be fully done - wait ( file.introStartTime + DROPSHIP_INTRO_LENGTH ) - Time() - player.MovementDisable() // disable all movement but let them look around still - player.ConsumeDoubleJump() // movementdisable doesn't prevent double jumps // wait for player to hit the ground - wait 0.1 // assume players will never actually hit ground before this + player.ClearParent() + WaitFrame() + player.SetVelocity( < 0, 0, -100 > ) // Toss players a bit down so it makes a smoother transition when jumping off the Dropship + player.MovementDisable() // Disable all movement but let them look around still + player.ConsumeDoubleJump() // MovementDisable doesn't prevent double jumps + WaitFrame() while ( !player.IsOnGround() && !player.IsWallRunning() && !player.IsWallHanging() ) // todo this needs tweaking WaitFrame() - TryGameModeAnnouncement( player ) + if ( GetRoundsPlayed() == 0 ) //Intro is announced only for the first round in Vanilla as certain gamemodes have different announcements for rounds restarts + TryGameModeAnnouncement( player ) } -- cgit v1.2.3 From 2849218b47666c5611e3de591fc5f77a77a807eb Mon Sep 17 00:00:00 2001 From: Zanieon Date: Sun, 7 Jul 2024 00:06:51 +0200 Subject: Fix formatting in dropship intro taken from #809 --- .../mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') 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 7b6c7e9f..8b75a982 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 @@ -187,7 +187,7 @@ void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, e } }) - HolsterAndDisableWeapons(player) + HolsterAndDisableWeapons( player ) player.DisableWeaponViewModel() // hide hud and fade screen out from black @@ -234,7 +234,7 @@ void function PlayerJumpsFromDropship( entity player ) // show weapon viewmodel and hud and let them move again player.MovementEnable() player.EnableWeaponViewModel() - DeployAndEnableWeapons(player) + DeployAndEnableWeapons( player ) RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) } }) -- cgit v1.2.3 From dc0074d24923ec2e9cc3e4b9129c53730f338dd7 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Sun, 7 Jul 2024 00:12:30 +0200 Subject: Play battle chatter during dropship intro (#810) Co-authored-by: Zanieon --- .../mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'Northstar.CustomServers/mod/scripts') 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 8b75a982..22dc1e67 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 @@ -217,6 +217,11 @@ void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, e jumpSequence.setInitialTime = max( 0.0, Time() - ( file.introStartTime + 11.0 ) ) // pretty sure you should do this with GetScriptedAnimEventCycleFrac? // idk unsure how to use that, all i know is getsequenceduration > the length it actually should be + #if BATTLECHATTER_ENABLED + if( playerDropshipIndex == 0 ) + PlayBattleChatterLine( player, "bc_pIntroChat" ) + #endif + waitthread FirstPersonSequence( jumpSequence, player, playerDropship ) thread PlayerJumpsFromDropship( player ) -- cgit v1.2.3 From 34bd1b1ae6b9b232ce85e2ff3b7192c141a31853 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Sun, 7 Jul 2024 00:15:39 +0200 Subject: Mitigate muted audio during dropship intro (#811) Co-authored-by: Zanieon --- .../mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut | 1 + 1 file changed, 1 insertion(+) (limited to 'Northstar.CustomServers/mod/scripts') 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 22dc1e67..3d8abd9d 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 @@ -189,6 +189,7 @@ void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, e HolsterAndDisableWeapons( player ) player.DisableWeaponViewModel() + UnMuteAll( player ) // hide hud and fade screen out from black AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) -- cgit v1.2.3 From 1c54129e11b496f049764d0bc7feed53a6341a57 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Sun, 7 Jul 2024 00:19:21 +0200 Subject: Mitigate muted audio during dropship intro (#812) Co-authored-by: Zanieon --- .../mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut | 1 + 1 file changed, 1 insertion(+) (limited to 'Northstar.CustomServers/mod/scripts') 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 3d8abd9d..5ab14121 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 @@ -190,6 +190,7 @@ void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, e HolsterAndDisableWeapons( player ) player.DisableWeaponViewModel() UnMuteAll( player ) + StopSoundOnEntity( player, "Duck_For_FrontierDefenseTitanSelectScreen" ) // hide hud and fade screen out from black AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) -- cgit v1.2.3 From e898b0a17d817bf36189ed9c5499ded4cefdbb26 Mon Sep 17 00:00:00 2001 From: William Miller Date: Sat, 6 Jul 2024 19:49:01 -0300 Subject: Prevent suicides registering progression stats (#813) Prevent suicides registering stats --- Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut index 101d5e4e..674923e7 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut @@ -334,6 +334,10 @@ void function OnPlayerOrNPCKilled( entity victim, entity attacker, var damageInf thread SetLastPosForDistanceStatValid_Threaded( victim, false ) HandleDeathStats( victim, attacker, damageInfo ) + + if( victim == attacker ) //Suicides are registering stats, afaik vanilla ignores them + return + HandleKillStats( victim, attacker, damageInfo ) HandleWeaponKillStats( victim, attacker, damageInfo ) HandleTitanStats( victim, attacker, damageInfo ) -- cgit v1.2.3 From de01dc34c66a57eb77b68e1403f5503e84802df8 Mon Sep 17 00:00:00 2001 From: William Miller Date: Sat, 6 Jul 2024 19:53:51 -0300 Subject: Fix oversight in "Total MVP" not registering in regards to progression (#814) The game have an extra stat especifically to count the total amount of times a player has been MVP in all matches, while the other mvp stat which i previously thought to be absolutely is only registered per map, not globally. --- Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut | 1 + 1 file changed, 1 insertion(+) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index 5eba24ac..f7c398d9 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -861,6 +861,7 @@ void function SetWinner( int team, string winningReason = "", string losingReaso { case 1: UpdatePlayerStat( players[i], "game_stats", "mvp" ) + UpdatePlayerStat( players[i], "game_stats", "mvp_total" ) UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) break case 2: -- cgit v1.2.3 From e76789be99d43242bb06e148c1e32485217ce79a Mon Sep 17 00:00:00 2001 From: William Miller Date: Mon, 8 Jul 2024 05:52:50 -0300 Subject: Hotfix for Intro Dropship (#816) to fix script error --- .../mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Northstar.CustomServers/mod/scripts') 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 5ab14121..c3bdf01c 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 @@ -196,7 +196,7 @@ void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, e AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) ScreenFadeFromBlack( player, 0.5, 0.5 ) // faction leaders are done clientside, spawn them here - Remote_CallFunction_NonReplay( player, "ServerCallback_SpawnFactionCommanderInDropship", playerDropship.dropship.GetEncodedEHandle(), file.introStartTime ) + Remote_CallFunction_NonReplay( player, "ServerCallback_SpawnFactionCommanderInDropship", playerDropship.GetEncodedEHandle(), file.introStartTime ) // do firstperson sequence FirstPersonSequenceStruct idleSequence -- cgit v1.2.3 From 52e007ac0634ee5dd9376bd82ae76af6fd5f44d7 Mon Sep 17 00:00:00 2001 From: fvnkhead <98965760+fvnkhead@users.noreply.github.com> Date: Thu, 11 Jul 2024 20:31:29 +0300 Subject: Check player validity in `SetupChallenges_Threaded` (#819) --- Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut | 3 +++ 1 file changed, 3 insertions(+) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut index 4b866a40..ffefc3b4 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut @@ -66,6 +66,9 @@ void function SetupPlayerMenuChallenges( entity player ) void function SetupChallenges_Threaded( entity player ) { WaitFrame() + + if ( !IsValid( player ) ) + return Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 0 ) Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 4 ) //4 tells RUI to hide it -- cgit v1.2.3 From f586107e62dc0dfa09f1b08cbf418d31d0346886 Mon Sep 17 00:00:00 2001 From: William Miller Date: Fri, 12 Jul 2024 17:45:49 -0300 Subject: Change `IsValid()` check to `EndSignal()` (#820) --- Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut index ffefc3b4..016097f2 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut @@ -65,10 +65,9 @@ void function SetupPlayerMenuChallenges( entity player ) void function SetupChallenges_Threaded( entity player ) { + player.EndSignal( "OnDestroy" ) + WaitFrame() - - if ( !IsValid( player ) ) - return Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 0 ) Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 4 ) //4 tells RUI to hide it -- cgit v1.2.3 From 26d649fa7bf53e83758dd824297f402c8b9d174e Mon Sep 17 00:00:00 2001 From: William Miller Date: Wed, 17 Jul 2024 06:10:06 -0300 Subject: Fix "Kills by Auto-Titan" not registering at all (#817) This is a quick fix that will redirect the attacker entity to a proper NPC owned by the player. By default behavior of the game, attackers are always the final entity in the owning hiearchy, which is always a player if owned NPCs kills enemies. This change properly redirects the code to the correct NPC whenever they have an owning player. --- .../mod/scripts/vscripts/mp/_stats.nut | 27 ++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut index 674923e7..74a9088b 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut @@ -493,23 +493,32 @@ void function HandleKillStats( entity victim, entity attacker, var damageInfo ) // get the player and it's pet titan entity player entity playerPetTitan - if ( attacker.IsPlayer() ) + entity inflictor = DamageInfo_GetInflictor( damageInfo ) + + if ( IsValid( inflictor ) ) { - // the player is just the attacker - player = attacker - playerPetTitan = player.GetPetTitan() + if ( inflictor.IsProjectile() && IsValid( inflictor.GetOwner() ) ) // Attackers are always the final entity in the owning hierarchy, projectile owners though migh be a player's NPC minion (i.e Auto-Titans) + attacker = inflictor.GetOwner() + + else if ( inflictor.IsNPC() ) // NPCs are bypassed as Attackers if they are owned by players, instead they become just inflictors + attacker = inflictor } - else if ( attacker.IsTitan() && IsPetTitan( attacker ) ) + + if ( attacker.IsNPC() ) { - // the attacker is the player's auto titan + if ( !attacker.IsTitan() ) // Normal NPCs case + return + + if ( !IsPetTitan( attacker ) ) // NPC Titans case + return + player = attacker.GetTitanSoul().GetBossPlayer() playerPetTitan = attacker } + else if ( attacker.IsPlayer() ) // Still checks this because worldspawn might be the attacker + player = attacker else - { - // attacker could be something like an NPC, or worldspawn return - } // check things once, for performance int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo ) -- cgit v1.2.3 From 984a30c99842fd40769ddcf49070a99d10489727 Mon Sep 17 00:00:00 2001 From: Bobbyperson Date: Mon, 19 Aug 2024 05:33:06 -0400 Subject: Fix Attrition SquadHandler crash (#839) Adds an `IsAlive()` check to temporarily handle a rare crash --- .../mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut index 7d73c926..cacb54cf 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut @@ -435,9 +435,12 @@ void function SquadHandler( array guys ) // Setup AI, first assault point foreach ( guy in guys ) { - guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE ) - guy.AssaultPoint( point ) - guy.AssaultSetGoalRadius( 1600 ) // 1600 is minimum for npc_stalker, works fine for others + if ( IsAlive( guy ) ) + { + guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE ) + guy.AssaultPoint( point ) + guy.AssaultSetGoalRadius( 1600 ) // 1600 is minimum for npc_stalker, works fine for others + } //thread AITdm_CleanupBoredNPCThread( guy ) } -- cgit v1.2.3 From 6cd957e93f8eccf42d7cb08afc19fd4650ee0efd Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:52:21 +0200 Subject: Add missing valid check for militia flag spawn (#853) Co-authored-by: Zanieon --- .../mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 97addc24..6033940b 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -123,7 +123,9 @@ void function CreateFlags() file.imcFlagSpawn.Destroy() file.imcFlag.Destroy() file.imcFlagReturnTrigger.Destroy() - + } + if ( IsValid( file.militiaFlagSpawn ) ) + { file.militiaFlagSpawn.Destroy() file.militiaFlag.Destroy() file.militiaFlagReturnTrigger.Destroy() -- cgit v1.2.3 From 3eb15eef1123f71e09e773adb15f52b5fa63a179 Mon Sep 17 00:00:00 2001 From: Zanieon Date: Fri, 30 Aug 2024 14:59:47 +0200 Subject: Move `SetFlagStateForTeam` to end of file in an effort to have all related functions close to each other --- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 6033940b..81971e00 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -249,15 +249,6 @@ void function TrackFlagReturnTrigger( entity flag, entity returnTrigger ) } } -void function SetFlagStateForTeam( int team, int state ) -{ - if ( state == eFlagState.Away ) // we tell the client the flag is the player carrying it if they're carrying it - SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", ( team == TEAM_IMC ? file.imcFlag : file.militiaFlag ).GetParent() ) - else - SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", team == TEAM_IMC ? file.imcFlag : file.militiaFlag ) - - SetGlobalNetInt( team == TEAM_IMC ? "imcFlagState" : "milFlagState", state ) -} bool function OnFlagCollected( entity player, entity flag ) { @@ -524,4 +515,14 @@ void function TryReturnFlag( entity player, entity flag ) MessageToTeam( GetOtherTeam( flag.GetTeam() ), eEventNotifications.PlayerReturnedEnemyFlag, null, player ) EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyReturnsFlag", GetOtherTeam( flag.GetTeam() ) ) PlayFactionDialogueToTeam( "ctf_flagReturnedEnemy", GetOtherTeam( flag.GetTeam() ) ) -} \ No newline at end of file +} + +void function SetFlagStateForTeam( int team, int state ) +{ + if ( state == eFlagState.Away ) // we tell the client the flag is the player carrying it if they're carrying it + SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", ( team == TEAM_IMC ? file.imcFlag : file.militiaFlag ).GetParent() ) + else + SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", team == TEAM_IMC ? file.imcFlag : file.militiaFlag ) + + SetGlobalNetInt( team == TEAM_IMC ? "imcFlagState" : "milFlagState", state ) +} -- cgit v1.2.3 From c674e3c0583c5c8eca136a5ebeb5ed2ac6cae6c4 Mon Sep 17 00:00:00 2001 From: Zanieon Date: Fri, 30 Aug 2024 15:03:52 +0200 Subject: Move some drop flag related functions to end of file in an effort to have all related functions close to each other Part of the refactor in #830 --- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 58 +++++++++++----------- 1 file changed, 29 insertions(+), 29 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 81971e00..72f43e18 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -287,26 +287,6 @@ void function GiveFlag( entity player, entity flag ) SetFlagStateForTeam( flag.GetTeam(), eFlagState.Away ) // used for held } -void function DropFlagIfPhased( entity player, entity flag ) -{ - player.EndSignal( "StartPhaseShift" ) - player.EndSignal( "OnDestroy" ) - - OnThreadEnd( function() : ( player ) - { - if (GetGameState() == eGameState.Playing || GetGameState() == eGameState.SuddenDeath) - DropFlag( player, true ) - }) - // the IsValid check is purely to prevent a crash due to a destroyed flag (epilogue) - while( IsValid(flag) && flag.GetParent() == player ) - WaitFrame() -} - -void function DropFlagForBecomingTitan( entity pilot, entity titan ) -{ - DropFlag( pilot, true ) -} - void function DropFlag( entity player, bool realDrop = true ) { entity flag = GetFlagForTeam( GetOtherTeam( player.GetTeam() ) ) @@ -344,15 +324,6 @@ void function DropFlag( entity player, bool realDrop = true ) SetFlagStateForTeam( flag.GetTeam(), eFlagState.Home ) // used for return prompt } -void function TrackFlagDropTimeout( entity flag ) -{ - flag.EndSignal( "ResetDropTimeout" ) - - wait CTF_GetDropTimeout() - - ResetFlag( flag ) -} - void function ResetFlag( entity flag ) { // prevents crash when flag is reset after it's been destroyed due to epilogue @@ -526,3 +497,32 @@ void function SetFlagStateForTeam( int team, int state ) SetGlobalNetInt( team == TEAM_IMC ? "imcFlagState" : "milFlagState", state ) } + +void function DropFlagIfPhased( entity player, entity flag ) +{ + player.EndSignal( "StartPhaseShift" ) + player.EndSignal( "OnDestroy" ) + + OnThreadEnd( function() : ( player ) + { + if (GetGameState() == eGameState.Playing || GetGameState() == eGameState.SuddenDeath) + DropFlag( player, true ) + }) + // the IsValid check is purely to prevent a crash due to a destroyed flag (epilogue) + while( IsValid(flag) && flag.GetParent() == player ) + WaitFrame() +} + +void function DropFlagForBecomingTitan( entity pilot, entity titan ) +{ + DropFlag( pilot, true ) +} + +void function TrackFlagDropTimeout( entity flag ) +{ + flag.EndSignal( "ResetDropTimeout" ) + + wait CTF_GetDropTimeout() + + ResetFlag( flag ) +} -- cgit v1.2.3 From dafc1d1e9729093b00fd27bc3083133f6af4d4b7 Mon Sep 17 00:00:00 2001 From: Zanieon Date: Fri, 30 Aug 2024 15:08:38 +0200 Subject: Move `CaptureFlag` function as part of the refactoring effort in #830 --- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 123 +++++++++++---------- 1 file changed, 62 insertions(+), 61 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 72f43e18..7375d199 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -287,6 +287,68 @@ void function GiveFlag( entity player, entity flag ) SetFlagStateForTeam( flag.GetTeam(), eFlagState.Away ) // used for held } +void function CaptureFlag( entity player, entity flag ) +{ + // can only capture flags during normal play or sudden death + if (GetGameState() != eGameState.Playing && GetGameState() != eGameState.SuddenDeath) + { + printt( player + " tried to capture the flag, but the game state was " + GetGameState() + " not " + eGameState.Playing + " or " + eGameState.SuddenDeath) + return + } + // reset flag + ResetFlag( flag ) + + print( player + " captured the flag!" ) + + // score + int team = player.GetTeam() + AddTeamScore( team, 1 ) + AddPlayerScore( player, "FlagCapture", player ) + player.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 ) // add 1 to captures on scoreboard + SetRoundWinningKillReplayAttacker( player ) // set attacker for last cap replay + + array assistList + if ( player.GetTeam() == TEAM_IMC ) + assistList = file.imcCaptureAssistList + else + assistList = file.militiaCaptureAssistList + + foreach( entity assistPlayer in assistList ) + { + if ( player != assistPlayer ) + AddPlayerScore( assistPlayer, "FlagCaptureAssist", player ) + if( !HasPlayerCompletedMeritScore( assistPlayer ) ) + { + AddPlayerScore( assistPlayer, "ChallengeCTFCapAssist" ) + SetPlayerChallengeMeritScore( assistPlayer ) + } + } + + assistList.clear() + + // notifs + MessageToPlayer( player, eEventNotifications.YouCapturedTheEnemyFlag ) + EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_PlayerScore" ) + + if( !HasPlayerCompletedMeritScore( player ) ) + { + AddPlayerScore( player, "ChallengeCTFRetAssist" ) + SetPlayerChallengeMeritScore( player ) + } + + MessageToTeam( team, eEventNotifications.PlayerCapturedEnemyFlag, player, player ) + EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamScore", player.GetTeam(), player ) + + MessageToTeam( GetOtherTeam( team ), eEventNotifications.PlayerCapturedFriendlyFlag, player, player ) + EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyScores", flag.GetTeam() ) + + if ( GameRules_GetTeamScore( team ) == GameMode_GetRoundScoreLimit( GAMETYPE ) - 1 ) + { + PlayFactionDialogueToTeam( "ctf_notifyWin1more", team ) + PlayFactionDialogueToTeam( "ctf_notifyLose1more", GetOtherTeam( team ) ) + } +} + void function DropFlag( entity player, bool realDrop = true ) { entity flag = GetFlagForTeam( GetOtherTeam( player.GetTeam() ) ) @@ -351,67 +413,6 @@ void function ResetFlag( entity flag ) flag.Signal( "ResetDropTimeout" ) } -void function CaptureFlag( entity player, entity flag ) -{ - // can only capture flags during normal play or sudden death - if (GetGameState() != eGameState.Playing && GetGameState() != eGameState.SuddenDeath) - { - printt( player + " tried to capture the flag, but the game state was " + GetGameState() + " not " + eGameState.Playing + " or " + eGameState.SuddenDeath) - return - } - // reset flag - ResetFlag( flag ) - - print( player + " captured the flag!" ) - - // score - int team = player.GetTeam() - AddTeamScore( team, 1 ) - AddPlayerScore( player, "FlagCapture", player ) - player.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 ) // add 1 to captures on scoreboard - SetRoundWinningKillReplayAttacker( player ) // set attacker for last cap replay - - array assistList - if ( player.GetTeam() == TEAM_IMC ) - assistList = file.imcCaptureAssistList - else - assistList = file.militiaCaptureAssistList - - foreach( entity assistPlayer in assistList ) - { - if ( player != assistPlayer ) - AddPlayerScore( assistPlayer, "FlagCaptureAssist", player ) - if( !HasPlayerCompletedMeritScore( assistPlayer ) ) - { - AddPlayerScore( assistPlayer, "ChallengeCTFCapAssist" ) - SetPlayerChallengeMeritScore( assistPlayer ) - } - } - - assistList.clear() - - // notifs - MessageToPlayer( player, eEventNotifications.YouCapturedTheEnemyFlag ) - EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_PlayerScore" ) - - if( !HasPlayerCompletedMeritScore( player ) ) - { - AddPlayerScore( player, "ChallengeCTFRetAssist" ) - SetPlayerChallengeMeritScore( player ) - } - - MessageToTeam( team, eEventNotifications.PlayerCapturedEnemyFlag, player, player ) - EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamScore", player.GetTeam(), player ) - - MessageToTeam( GetOtherTeam( team ), eEventNotifications.PlayerCapturedFriendlyFlag, player, player ) - EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyScores", flag.GetTeam() ) - - if ( GameRules_GetTeamScore( team ) == GameMode_GetRoundScoreLimit( GAMETYPE ) - 1 ) - { - PlayFactionDialogueToTeam( "ctf_notifyWin1more", team ) - PlayFactionDialogueToTeam( "ctf_notifyLose1more", GetOtherTeam( team ) ) - } -} void function OnPlayerEntersFlagReturnTrigger( entity trigger, entity player ) { -- cgit v1.2.3 From bd77a936a90f4b30c5e88b5fa26e4d995930d9ff Mon Sep 17 00:00:00 2001 From: Zanieon Date: Fri, 30 Aug 2024 15:11:51 +0200 Subject: Move `CTFInitPlayer` function as part of the refactoring effort in #830 --- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 7375d199..e1c179b5 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -91,18 +91,6 @@ bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) return true } -void function CTFInitPlayer( entity player ) -{ - if ( !IsValid( file.imcFlagSpawn ) ) - return - - vector imcSpawn = file.imcFlagSpawn.GetOrigin() - Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z ) - - vector militiaSpawn = file.militiaFlagSpawn.GetOrigin() - Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z ) -} - void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) { if ( !IsValid( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ) ) ) // getting a crash idk @@ -236,6 +224,18 @@ void function RemoveFlags() SetFlagStateForTeam( TEAM_IMC, eFlagState.None ) } +void function CTFInitPlayer( entity player ) +{ + if ( !IsValid( file.imcFlagSpawn ) ) + return + + vector imcSpawn = file.imcFlagSpawn.GetOrigin() + Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z ) + + vector militiaSpawn = file.militiaFlagSpawn.GetOrigin() + Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z ) +} + void function TrackFlagReturnTrigger( entity flag, entity returnTrigger ) { // this is a bit of a hack, it seems parenting the return trigger to the flag actually sets the pickup radius of the flag to be the same as the trigger -- cgit v1.2.3 From 8b34689dca9f2392ad74c57a19e64ec58abda69e Mon Sep 17 00:00:00 2001 From: Zanieon Date: Fri, 30 Aug 2024 15:12:42 +0200 Subject: Move `OnPlayerKilled` function as part of the refactoring effort in #830 --- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index e1c179b5..752aa907 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -91,19 +91,6 @@ bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) return true } -void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) -{ - if ( !IsValid( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ) ) ) // getting a crash idk - return - if ( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ).GetParent() == victim ) - { - if ( victim != attacker && attacker.IsPlayer() ) - AddPlayerScore( attacker, "FlagCarrierKill", victim ) - - DropFlag( victim ) - } -} - void function CreateFlags() { if ( IsValid( file.imcFlagSpawn ) ) @@ -236,6 +223,19 @@ void function CTFInitPlayer( entity player ) Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z ) } +void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( !IsValid( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ) ) ) // getting a crash idk + return + if ( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ).GetParent() == victim ) + { + if ( victim != attacker && attacker.IsPlayer() ) + AddPlayerScore( attacker, "FlagCarrierKill", victim ) + + DropFlag( victim ) + } +} + void function TrackFlagReturnTrigger( entity flag, entity returnTrigger ) { // this is a bit of a hack, it seems parenting the return trigger to the flag actually sets the pickup radius of the flag to be the same as the trigger -- cgit v1.2.3 From 9e9d08ff8ba0c85b90590f17cb9be38cba4f6450 Mon Sep 17 00:00:00 2001 From: Zanieon Date: Fri, 30 Aug 2024 15:16:35 +0200 Subject: Move CTF spawn point related functions as part of the refactoring effort in #830 --- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 48 +++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 752aa907..3c5eb12c 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -67,30 +67,6 @@ void function CaptureTheFlag_Init() ScoreEvent_SetEarnMeterValues( "FlagReturn", 0.0, 0.20 ) } -void function RateSpawnpoints_CTF( int checkClass, array spawnpoints, int team, entity player ) -{ - RateSpawnpoints_SpawnZones( checkClass, spawnpoints, team, player ) -} - -bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) -{ - // ensure spawnpoints aren't too close to enemy base - vector allyFlagSpot - vector enemyFlagSpot - foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) - { - if( spawn.GetTeam() == team ) - allyFlagSpot = spawn.GetOrigin() - else - enemyFlagSpot = spawn.GetOrigin() - } - - if( Distance2D( spawnpoint.GetOrigin(), allyFlagSpot ) > Distance2D( spawnpoint.GetOrigin(), enemyFlagSpot ) ) - return false - - return true -} - void function CreateFlags() { if ( IsValid( file.imcFlagSpawn ) ) @@ -211,6 +187,30 @@ void function RemoveFlags() SetFlagStateForTeam( TEAM_IMC, eFlagState.None ) } +void function RateSpawnpoints_CTF( int checkClass, array spawnpoints, int team, entity player ) +{ + RateSpawnpoints_SpawnZones( checkClass, spawnpoints, team, player ) +} + +bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) +{ + // ensure spawnpoints aren't too close to enemy base + vector allyFlagSpot + vector enemyFlagSpot + foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) + { + if( spawn.GetTeam() == team ) + allyFlagSpot = spawn.GetOrigin() + else + enemyFlagSpot = spawn.GetOrigin() + } + + if( Distance2D( spawnpoint.GetOrigin(), allyFlagSpot ) > Distance2D( spawnpoint.GetOrigin(), enemyFlagSpot ) ) + return false + + return true +} + void function CTFInitPlayer( entity player ) { if ( !IsValid( file.imcFlagSpawn ) ) -- cgit v1.2.3 From ebbf9df5134d75c7d46bca0826cfee47090d8215 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:25:54 +0200 Subject: Add missing `IsValidPlayer()` check for CTF capture assists (#854) Co-authored-by: Zanieon --- .../mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 3c5eb12c..31c05eb3 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -315,12 +315,15 @@ void function CaptureFlag( entity player, entity flag ) foreach( entity assistPlayer in assistList ) { - if ( player != assistPlayer ) - AddPlayerScore( assistPlayer, "FlagCaptureAssist", player ) - if( !HasPlayerCompletedMeritScore( assistPlayer ) ) + if ( IsValidPlayer( assistPlayer ) ) { - AddPlayerScore( assistPlayer, "ChallengeCTFCapAssist" ) - SetPlayerChallengeMeritScore( assistPlayer ) + if ( player != assistPlayer ) + AddPlayerScore( assistPlayer, "FlagCaptureAssist", player ) + if( !HasPlayerCompletedMeritScore( assistPlayer ) ) + { + AddPlayerScore( assistPlayer, "ChallengeCTFCapAssist" ) + SetPlayerChallengeMeritScore( assistPlayer ) + } } } -- cgit v1.2.3 From 331828005244bd9da0f5a0213ecfff9c957c1453 Mon Sep 17 00:00:00 2001 From: GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:21:30 +0200 Subject: Add is `IsValidPlayer()` check (#855) to prevent potential script crash Co-authored-by: Zanieon --- .../mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 31c05eb3..1f06ff4a 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -509,8 +509,11 @@ void function DropFlagIfPhased( entity player, entity flag ) OnThreadEnd( function() : ( player ) { - if (GetGameState() == eGameState.Playing || GetGameState() == eGameState.SuddenDeath) - DropFlag( player, true ) + if ( IsValidPlayer( player ) ) + { + if ( GetGameState() == eGameState.Playing || GetGameState() == eGameState.SuddenDeath ) + DropFlag( player, true ) + } }) // the IsValid check is purely to prevent a crash due to a destroyed flag (epilogue) while( IsValid(flag) && flag.GetParent() == player ) -- cgit v1.2.3 From e253357946fd7287c31d1e2a439bac4762c3ba3d Mon Sep 17 00:00:00 2001 From: x3Karma Date: Sat, 31 Aug 2024 20:39:12 +0800 Subject: Fix `PlayerInventory_TakeAllInventoryItems` not being threaded (#852) This needs to be threaded as there's a `waitthread` further below on that function, which will cause an error if not threaded. --- .../mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut index 9057f7d8..3814126b 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut @@ -32,7 +32,7 @@ void function PrematchClearInventory() // vanilla behavior { foreach( entity player in GetPlayerArray() ) { - PlayerInventory_TakeAllInventoryItems( player ) + thread PlayerInventory_TakeAllInventoryItems( player ) } } -- cgit v1.2.3 From b664005c5dc0142531176258db53a3bf47392faf Mon Sep 17 00:00:00 2001 From: William Miller Date: Mon, 2 Sep 2024 19:40:47 -0300 Subject: Avoid duplication of challenge award (#858) --- .../mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut | 3 --- 1 file changed, 3 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 1f06ff4a..90b20a8a 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -334,10 +334,7 @@ void function CaptureFlag( entity player, entity flag ) EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_PlayerScore" ) if( !HasPlayerCompletedMeritScore( player ) ) - { - AddPlayerScore( player, "ChallengeCTFRetAssist" ) SetPlayerChallengeMeritScore( player ) - } MessageToTeam( team, eEventNotifications.PlayerCapturedEnemyFlag, player, player ) EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamScore", player.GetTeam(), player ) -- cgit v1.2.3 From 0aa2d0adb1812ed78a9dc3b4452f4ced01bed8f2 Mon Sep 17 00:00:00 2001 From: William Miller Date: Mon, 2 Sep 2024 20:23:10 -0300 Subject: Add `IsValidPlayer()` before flag return cleanup (#859) Add validity check to player validity as the thread of that function might end on via OnDestroy signal by disconnection, and by that point, the player is no longer valid. --- .../mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 90b20a8a..e52b0de5 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -453,9 +453,11 @@ void function TryReturnFlag( entity player, entity flag ) OnThreadEnd( function() : ( player ) { - // cleanup - Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StopReturnFlagProgressBar" ) - StopSoundOnEntity( player, "UI_CTF_1P_FlagReturnMeter" ) + if ( IsValidPlayer( player ) ) + { + Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StopReturnFlagProgressBar" ) + StopSoundOnEntity( player, "UI_CTF_1P_FlagReturnMeter" ) + } }) player.EndSignal( "FlagReturnEnded" ) -- cgit v1.2.3 From 628e2b39fb53f2b10fa45f58a19b23c21b0439ba Mon Sep 17 00:00:00 2001 From: William Miller Date: Mon, 2 Sep 2024 20:26:57 -0300 Subject: Play effect on flag match over (#860) Actually not even vanilla does this, but this is just a fancied effect for when the match is over, the flags simply vanish together with their bases. So this adds some effects on top of it to not look so bland. --- .../mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index e52b0de5..c82dcf8f 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -171,12 +171,14 @@ void function RemoveFlags() // destroy all the flag related things if ( IsValid( file.imcFlagSpawn ) ) { + PlayFX( $"P_phase_shift_main", file.imcFlagSpawn.GetOrigin() ) file.imcFlagSpawn.Destroy() file.imcFlag.Destroy() file.imcFlagReturnTrigger.Destroy() } if ( IsValid( file.militiaFlagSpawn ) ) { + PlayFX( $"P_phase_shift_main", file.militiaFlagSpawn.GetOrigin() ) file.militiaFlagSpawn.Destroy() file.militiaFlag.Destroy() file.militiaFlagReturnTrigger.Destroy() -- cgit v1.2.3 From 957f6a3ac6ff0cca3168e15a362cee80c8e9dcf0 Mon Sep 17 00:00:00 2001 From: William Miller Date: Mon, 2 Sep 2024 20:33:36 -0300 Subject: Complete overhaul for the CTF script (#830) Refactors CTF script that was dearly needed for a while --- .../scripts/vscripts/gamemodes/_gamemode_ctf.nut | 358 +++++++++++++-------- 1 file changed, 220 insertions(+), 138 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index c82dcf8f..85b80d74 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -1,5 +1,4 @@ untyped -// this needs a refactor lol global function CaptureTheFlag_Init global function RateSpawnpoints_CTF @@ -12,16 +11,31 @@ const array SWAP_FLAG_MAPS = [ struct { entity imcFlagSpawn entity imcFlag - entity imcFlagReturnTrigger entity militiaFlagSpawn entity militiaFlag - entity militiaFlagReturnTrigger array imcCaptureAssistList array militiaCaptureAssistList } file + + + + + + + + + +/* + ██████ █████ ██████ ████████ ██ ██ ██████ ███████ ████████ ██ ██ ███████ ███████ ██ █████ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███████ ██████ ██ ██ ██ ██████ █████ ██ ███████ █████ █████ ██ ███████ ██ ███ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██████ ██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ ██ ███████ ██ ███████ ██ ██ ██████ +*/ + void function CaptureTheFlag_Init() { PrecacheModel( CTF_FLAG_MODEL ) @@ -30,30 +44,31 @@ void function CaptureTheFlag_Init() PrecacheParticleSystem( FLAG_FX_ENEMY ) CaptureTheFlagShared_Init() + SetSwitchSidesBased( true ) SetSuddenDeathBased( true ) + SetShouldUseRoundWinningKillReplay( true ) - SetRoundWinningKillReplayKillClasses( false, false ) // make these fully manual + SetRoundWinningKillReplayKillClasses( false, false ) AddCallback_OnClientConnected( CTFInitPlayer ) - + AddCallback_OnClientDisconnected( CTFPlayerDisconnected ) + AddCallback_GameStateEnter( eGameState.Prematch, CreateFlags ) AddCallback_GameStateEnter( eGameState.Epilogue, RemoveFlags ) + AddCallback_GameStateEnter( eGameState.Playing, OnPlaying ) + AddCallback_OnTouchHealthKit( "item_flag", OnFlagCollected ) + AddCallback_OnPlayerKilled( OnPlayerKilled ) AddCallback_OnPilotBecomesTitan( DropFlagForBecomingTitan ) - SetSpawnZoneRatingFunc( DecideSpawnZone_CTF ) AddSpawnpointValidationRule( VerifyCTFSpawnpoint ) - RegisterSignal( "FlagReturnEnded" ) RegisterSignal( "ResetDropTimeout" ) - // setup stuff for the functions in sh_gamemode_ctf - // don't really like using level for stuff but just how it be level.teamFlags <- {} - // setup score event earnmeter values ScoreEvent_SetEarnMeterValues( "KillPilot", 0.05, 0.20 ) ScoreEvent_SetEarnMeterValues( "Headshot", 0.0, 0.02 ) ScoreEvent_SetEarnMeterValues( "FirstStrike", 0.0, 0.05 ) @@ -67,33 +82,42 @@ void function CaptureTheFlag_Init() ScoreEvent_SetEarnMeterValues( "FlagReturn", 0.0, 0.20 ) } + + + + + + + + +/* +███████ ██████ █████ ██ ██ ███ ██ ██ ██████ ██████ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ +███████ ██████ ███████ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ + ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██ ██ ██ ███ ███ ██ ████ ███████ ██████ ██████ ██ ██████ +*/ + void function CreateFlags() { if ( IsValid( file.imcFlagSpawn ) ) { file.imcFlagSpawn.Destroy() file.imcFlag.Destroy() - file.imcFlagReturnTrigger.Destroy() } if ( IsValid( file.militiaFlagSpawn ) ) { file.militiaFlagSpawn.Destroy() file.militiaFlag.Destroy() - file.militiaFlagReturnTrigger.Destroy() } foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) { - // on some maps flags are on the opposite side from what they should be - // likely this is because respawn uses distance checks from spawns to check this in official - // but i don't like doing that so just using a list of maps to swap them on lol bool switchedSides = HasSwitchedSides() == 1 - - // i dont know why this works and whatever we had before didn't, but yeah + bool shouldSwap = switchedSides - if (!shouldSwap && SWAP_FLAG_MAPS.contains( GetMapName() )) + if ( !shouldSwap && SWAP_FLAG_MAPS.contains( GetMapName() ) ) shouldSwap = !shouldSwap - int flagTeam = spawn.GetTeam() if ( shouldSwap ) @@ -101,48 +125,33 @@ void function CreateFlags() flagTeam = GetOtherTeam( flagTeam ) SetTeam( spawn, flagTeam ) } - - // create flag base + entity base = CreatePropDynamic( CTF_FLAG_BASE_MODEL, spawn.GetOrigin(), spawn.GetAngles(), 0 ) SetTeam( base, spawn.GetTeam() ) svGlobal.flagSpawnPoints[ flagTeam ] = base - // create flag entity flag = CreateEntity( "item_flag" ) flag.SetValueForModelKey( CTF_FLAG_MODEL ) SetTeam( flag, flagTeam ) flag.MarkAsNonMovingAttachment() - flag.Minimap_AlwaysShow( TEAM_IMC, null ) // show flag icon on minimap + flag.Minimap_AlwaysShow( TEAM_IMC, null ) flag.Minimap_AlwaysShow( TEAM_MILITIA, null ) flag.Minimap_SetAlignUpright( true ) DispatchSpawn( flag ) flag.SetModel( CTF_FLAG_MODEL ) - flag.SetOrigin( spawn.GetOrigin() + < 0, 0, base.GetBoundingMaxs().z * 2 > ) // ensure flag doesn't spawn clipped into geometry + flag.SetOrigin( spawn.GetOrigin() + < 0, 0, base.GetBoundingMaxs().z * 2 > ) flag.SetVelocity( < 0, 0, 1 > ) flag.s.canTake <- true - flag.s.playersReturning <- [] level.teamFlags[ flag.GetTeam() ] <- flag - - entity returnTrigger = CreateEntity( "trigger_cylinder" ) - SetTeam( returnTrigger, flagTeam ) - returnTrigger.SetRadius( CTF_GetFlagReturnRadius() ) - returnTrigger.SetAboveHeight( CTF_GetFlagReturnRadius() ) - returnTrigger.SetBelowHeight( CTF_GetFlagReturnRadius() ) - - returnTrigger.SetEnterCallback( OnPlayerEntersFlagReturnTrigger ) - returnTrigger.SetLeaveCallback( OnPlayerExitsFlagReturnTrigger ) - DispatchSpawn( returnTrigger ) - - thread TrackFlagReturnTrigger( flag, returnTrigger ) + thread FlagProximityTracker( flag ) if ( flagTeam == TEAM_IMC ) { file.imcFlagSpawn = base file.imcFlag = flag - file.imcFlagReturnTrigger = returnTrigger SetGlobalNetEnt( "imcFlag", file.imcFlag ) SetGlobalNetEnt( "imcFlagHome", file.imcFlagSpawn ) @@ -151,52 +160,76 @@ void function CreateFlags() { file.militiaFlagSpawn = base file.militiaFlag = flag - file.militiaFlagReturnTrigger = returnTrigger SetGlobalNetEnt( "milFlag", file.militiaFlag ) SetGlobalNetEnt( "milFlagHome", file.militiaFlagSpawn ) } } - // reset the flag states, prevents issues where flag is home but doesnt think it's home when halftime goes SetFlagStateForTeam( TEAM_MILITIA, eFlagState.None ) SetFlagStateForTeam( TEAM_IMC, eFlagState.None ) - - foreach ( entity player in GetPlayerArray() ) - CTFInitPlayer( player ) } void function RemoveFlags() { - // destroy all the flag related things if ( IsValid( file.imcFlagSpawn ) ) { PlayFX( $"P_phase_shift_main", file.imcFlagSpawn.GetOrigin() ) file.imcFlagSpawn.Destroy() file.imcFlag.Destroy() - file.imcFlagReturnTrigger.Destroy() } + if ( IsValid( file.militiaFlagSpawn ) ) { PlayFX( $"P_phase_shift_main", file.militiaFlagSpawn.GetOrigin() ) file.militiaFlagSpawn.Destroy() file.militiaFlag.Destroy() - file.militiaFlagReturnTrigger.Destroy() } - - // unsure if this is needed, since the flags are destroyed? idk + SetFlagStateForTeam( TEAM_MILITIA, eFlagState.None ) SetFlagStateForTeam( TEAM_IMC, eFlagState.None ) } void function RateSpawnpoints_CTF( int checkClass, array spawnpoints, int team, entity player ) { - RateSpawnpoints_SpawnZones( checkClass, spawnpoints, team, player ) + vector allyFlagSpot + vector enemyFlagSpot + foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) + { + if( spawn.GetTeam() == team ) + allyFlagSpot = spawn.GetOrigin() + else + enemyFlagSpot = spawn.GetOrigin() + } + + foreach ( entity spawn in spawnpoints ) + { + float rating = 0.0 + float allyFlagDistance = Distance2D( spawn.GetOrigin(), allyFlagSpot ) + float enemyFlagDistance = Distance2D( spawn.GetOrigin(), enemyFlagSpot ) + + if( enemyFlagDistance > allyFlagDistance ) + { + rating += spawn.NearbyAllyScore( team, "ai" ) + rating += spawn.NearbyAllyScore( team, "titan" ) + rating += spawn.NearbyAllyScore( team, "pilot" ) + + rating += spawn.NearbyEnemyScore( team, "ai" ) + rating += spawn.NearbyEnemyScore( team, "titan" ) + rating += spawn.NearbyEnemyScore( team, "pilot" ) + + rating = rating / allyFlagDistance + } + + if ( spawn == player.p.lastSpawnPoint ) + rating += GetConVarFloat( "spawnpoint_last_spawn_rating" ) + + spawn.CalculateRating( checkClass, team, rating, rating * 0.25 ) + } } bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) { - // ensure spawnpoints aren't too close to enemy base vector allyFlagSpot vector enemyFlagSpot foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) @@ -213,23 +246,62 @@ bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) return true } + + + + + + + + +/* +██████ ██ █████ ██ ██ ███████ ██████ ██ ██████ ██████ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██████ ██ ███████ ████ █████ ██████ ██ ██ ██ ██ ███ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███████ ██ ██ ██ ███████ ██ ██ ███████ ██████ ██████ ██ ██████ +*/ + +void function OnPlaying() +{ + foreach ( entity player in GetPlayerArray() ) + CTFInitPlayer( player ) +} + void function CTFInitPlayer( entity player ) { - if ( !IsValid( file.imcFlagSpawn ) ) + if ( !GamePlaying() ) return - vector imcSpawn = file.imcFlagSpawn.GetOrigin() - Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z ) + if ( IsValid( file.imcFlagSpawn ) ) + { + vector imcSpawn = file.imcFlagSpawn.GetOrigin() + Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z ) + } + + if ( IsValid( file.militiaFlagSpawn ) ) + { + vector militiaSpawn = file.militiaFlagSpawn.GetOrigin() + Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z ) + } +} + +void function CTFPlayerDisconnected( entity player ) +{ + // This has no validity checks on the player because the disconnection callback happens in the exact last frame the player entity still exists + if( !GamePlaying() ) + return - vector militiaSpawn = file.militiaFlagSpawn.GetOrigin() - Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z ) + if ( PlayerHasEnemyFlag( player ) ) + DropFlag( player, false ) } void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) { if ( !IsValid( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ) ) ) // getting a crash idk return - if ( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ).GetParent() == victim ) + + if ( PlayerHasEnemyFlag( victim ) ) { if ( victim != attacker && attacker.IsPlayer() ) AddPlayerScore( attacker, "FlagCarrierKill", victim ) @@ -238,31 +310,35 @@ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) } } -void function TrackFlagReturnTrigger( entity flag, entity returnTrigger ) -{ - // this is a bit of a hack, it seems parenting the return trigger to the flag actually sets the pickup radius of the flag to be the same as the trigger - // this isn't wanted since only pickups should use that additional radius - flag.EndSignal( "OnDestroy" ) - - while ( true ) - { - returnTrigger.SetOrigin( flag.GetOrigin() ) - WaitFrame() - } -} + + + + + + + + +/* +███████ ██ █████ ██████ ██ ██████ ██████ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +█████ ██ ███████ ██ ███ ██ ██ ██ ██ ███ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███████ ██ ██ ██████ ███████ ██████ ██████ ██ ██████ +*/ + bool function OnFlagCollected( entity player, entity flag ) { if ( !IsAlive( player ) || flag.GetParent() != null || player.IsTitan() || player.IsPhaseShifted() ) return false if ( player.GetTeam() != flag.GetTeam() && flag.s.canTake ) - GiveFlag( player, flag ) // pickup enemy flag + GiveFlag( player, flag ) else if ( player.GetTeam() == flag.GetTeam() && IsFlagHome( flag ) && PlayerHasEnemyFlag( player ) ) - CaptureFlag( player, GetFlagForTeam( GetOtherTeam( flag.GetTeam() ) ) ) // cap the flag + CaptureFlag( player, GetFlagForTeam( GetOtherTeam( flag.GetTeam() ) ) ) - return false // don't wanna delete the flag entity + return false // Don't delete the flag } void function GiveFlag( entity player, entity flag ) @@ -271,7 +347,8 @@ void function GiveFlag( entity player, entity flag ) flag.Signal( "ResetDropTimeout" ) flag.SetParent( player, "FLAG" ) - thread DropFlagIfPhased( player, flag ) + if ( GetCurrentPlaylistVarInt( "phase_shift_drop_flag", 0 ) == 1 ) + thread DropFlagIfPhased( player, flag ) // do notifications MessageToPlayer( player, eEventNotifications.YouHaveTheEnemyFlag ) @@ -292,7 +369,7 @@ void function GiveFlag( entity player, entity flag ) void function CaptureFlag( entity player, entity flag ) { // can only capture flags during normal play or sudden death - if (GetGameState() != eGameState.Playing && GetGameState() != eGameState.SuddenDeath) + if ( GetGameState() != eGameState.Playing && GetGameState() != eGameState.SuddenDeath ) { printt( player + " tried to capture the flag, but the game state was " + GetGameState() + " not " + eGameState.Playing + " or " + eGameState.SuddenDeath) return @@ -344,10 +421,12 @@ void function CaptureFlag( entity player, entity flag ) MessageToTeam( GetOtherTeam( team ), eEventNotifications.PlayerCapturedFriendlyFlag, player, player ) EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyScores", flag.GetTeam() ) - if ( GameRules_GetTeamScore( team ) == GameMode_GetRoundScoreLimit( GAMETYPE ) - 1 ) + if ( GameRules_GetTeamScore( team ) == GetScoreLimit_FromPlaylist() - 1 ) { PlayFactionDialogueToTeam( "ctf_notifyWin1more", team ) PlayFactionDialogueToTeam( "ctf_notifyLose1more", GetOtherTeam( team ) ) + foreach( entity otherPlayer in GetPlayerArray() ) + Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_CTF_PlayMatchNearEndMusic" ) } } @@ -355,7 +434,7 @@ void function DropFlag( entity player, bool realDrop = true ) { entity flag = GetFlagForTeam( GetOtherTeam( player.GetTeam() ) ) - if ( flag.GetParent() != player ) + if( !IsValid( flag ) || flag.GetParent() != player ) return print( player + " dropped the flag!" ) @@ -366,94 +445,92 @@ void function DropFlag( entity player, bool realDrop = true ) if ( realDrop ) { - // start drop timeout countdown - thread TrackFlagDropTimeout( flag ) - - // add to capture assists - if ( player.GetTeam() == TEAM_IMC ) + if ( player.GetTeam() == TEAM_IMC && !file.imcCaptureAssistList.contains( player ) ) file.imcCaptureAssistList.append( player ) - else + + else if( !file.militiaCaptureAssistList.contains( player ) ) file.militiaCaptureAssistList.append( player ) - // do notifications MessageToPlayer( player, eEventNotifications.YouDroppedTheEnemyFlag ) EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagDrop" ) MessageToTeam( player.GetTeam(), eEventNotifications.PlayerDroppedEnemyFlag, player, player ) - // todo need a sound here maybe MessageToTeam( GetOtherTeam( player.GetTeam() ), eEventNotifications.PlayerDroppedFriendlyFlag, player, player ) - // todo need a sound here maybe } - SetFlagStateForTeam( flag.GetTeam(), eFlagState.Home ) // used for return prompt + thread TrackFlagDropTimeout( flag ) + SetFlagStateForTeam( flag.GetTeam(), eFlagState.Home ) } void function ResetFlag( entity flag ) { - // prevents crash when flag is reset after it's been destroyed due to epilogue - if (!IsValid(flag)) - return - // ensure we can't pickup the flag after it's been dropped but before it's been reset flag.s.canTake = false if ( flag.GetParent() != null ) DropFlag( flag.GetParent(), false ) - entity spawn + entity flagBase if ( flag.GetTeam() == TEAM_IMC ) - spawn = file.imcFlagSpawn + flagBase = file.imcFlagSpawn else - spawn = file.militiaFlagSpawn + flagBase = file.militiaFlagSpawn - flag.SetOrigin( spawn.GetOrigin() + < 0, 0, spawn.GetBoundingMaxs().z + 1 > ) + flag.SetOrigin( flagBase.GetOrigin() + < 0, 0, flagBase.GetBoundingMaxs().z + 1 > ) - // we can take it again now flag.s.canTake = true - SetFlagStateForTeam( flag.GetTeam(), eFlagState.None ) // used for home + SetFlagStateForTeam( flag.GetTeam(), eFlagState.None ) flag.Signal( "ResetDropTimeout" ) } - -void function OnPlayerEntersFlagReturnTrigger( entity trigger, entity player ) +//----------------------------------------------------------------------------- +// Purpose: Check proximity for flag returns +// Input : flag - The flag entity +//----------------------------------------------------------------------------- +void function FlagProximityTracker( entity flag ) { - entity flag - if ( trigger.GetTeam() == TEAM_IMC ) - flag = file.imcFlag - else - flag = file.militiaFlag - - if( !IsValid( flag ) || !IsValid( player ) ) - return + flag.EndSignal( "OnDestroy" ) - if ( !player.IsPlayer() || player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null ) - return + array < entity > playerInsidePerimeter + while( true ) + { + if( !playerInsidePerimeter.len() ) + ArrayRemoveDead( playerInsidePerimeter ) - thread TryReturnFlag( player, flag ) -} - -void function OnPlayerExitsFlagReturnTrigger( entity trigger, entity player ) -{ - entity flag - if ( trigger.GetTeam() == TEAM_IMC ) - flag = file.imcFlag - else - flag = file.militiaFlag + foreach ( player in GetPlayerArrayOfTeam_Alive( flag.GetTeam() ) ) + { + if ( Distance( player.GetOrigin(), flag.GetOrigin() ) < CTF_GetFlagReturnRadius() ) + { + if ( player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null ) + continue + + if( playerInsidePerimeter.contains( player ) ) + continue + + playerInsidePerimeter.append( player ) + thread TryReturnFlag( player, flag ) + } + else + { + if( playerInsidePerimeter.contains( player ) ) + { + player.Signal( "CTF_LeftReturnTriggerArea" ) // Cut the progress if outside range + playerInsidePerimeter.removebyvalue( player ) + } + } + } - if ( !player.IsPlayer() || player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null ) - return - - player.Signal( "FlagReturnEnded" ) -} + WaitFrame() + } +} void function TryReturnFlag( entity player, entity flag ) { - // start return progress bar Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StartReturnFlagProgressBar", Time() + CTF_GetFlagReturnTime() ) EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagReturnMeter" ) - OnThreadEnd( function() : ( player ) + OnThreadEnd( function() : ( flag, player ) { if ( IsValidPlayer( player ) ) { @@ -462,35 +539,37 @@ void function TryReturnFlag( entity player, entity flag ) } }) - player.EndSignal( "FlagReturnEnded" ) - flag.EndSignal( "FlagReturnEnded" ) // avoid multiple players to return one flag at once + flag.EndSignal( "CTF_ReturnedFlag" ) + flag.EndSignal( "OnDestroy" ) + + player.EndSignal( "CTF_LeftReturnTriggerArea" ) player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) wait CTF_GetFlagReturnTime() - // flag return succeeded - // return flag ResetFlag( flag ) - flag.Signal( "FlagReturnEnded" ) - - // do notifications for return + + MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerReturnedFriendlyFlag, null, player ) + EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_TeamReturnsFlag", flag.GetTeam() ) + PlayFactionDialogueToTeam( "ctf_flagReturnedFriendly", flag.GetTeam() ) + MessageToPlayer( player, eEventNotifications.YouReturnedFriendlyFlag ) AddPlayerScore( player, "FlagReturn", player ) player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, 1 ) - if( !HasPlayerCompletedMeritScore( player ) ) + if ( !HasPlayerCompletedMeritScore( player ) ) { AddPlayerScore( player, "ChallengeCTFRetAssist" ) SetPlayerChallengeMeritScore( player ) } - MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerReturnedFriendlyFlag, null, player ) - EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_TeamReturnsFlag", flag.GetTeam() ) - PlayFactionDialogueToTeam( "ctf_flagReturnedFriendly", flag.GetTeam() ) - MessageToTeam( GetOtherTeam( flag.GetTeam() ), eEventNotifications.PlayerReturnedEnemyFlag, null, player ) EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyReturnsFlag", GetOtherTeam( flag.GetTeam() ) ) + EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_ReturnsFlag" ) PlayFactionDialogueToTeam( "ctf_flagReturnedEnemy", GetOtherTeam( flag.GetTeam() ) ) + + flag.Signal( "CTF_ReturnedFlag" ) } void function SetFlagStateForTeam( int team, int state ) @@ -507,6 +586,7 @@ void function DropFlagIfPhased( entity player, entity flag ) { player.EndSignal( "StartPhaseShift" ) player.EndSignal( "OnDestroy" ) + flag.EndSignal( "OnDestroy" ) OnThreadEnd( function() : ( player ) { @@ -516,8 +596,8 @@ void function DropFlagIfPhased( entity player, entity flag ) DropFlag( player, true ) } }) - // the IsValid check is purely to prevent a crash due to a destroyed flag (epilogue) - while( IsValid(flag) && flag.GetParent() == player ) + + while( flag.GetParent() == player ) WaitFrame() } @@ -528,7 +608,9 @@ void function DropFlagForBecomingTitan( entity pilot, entity titan ) void function TrackFlagDropTimeout( entity flag ) { + flag.EndSignal( "CTF_ReturnedFlag" ) flag.EndSignal( "ResetDropTimeout" ) + flag.EndSignal( "OnDestroy" ) wait CTF_GetDropTimeout() -- cgit v1.2.3 From 34e5b52282a7d3619e032a928d16058cc7044678 Mon Sep 17 00:00:00 2001 From: LightBlueCube <115393812+LightBlueCube@users.noreply.github.com> Date: Tue, 3 Sep 2024 07:49:15 +0800 Subject: Prevent rare crash when player disconnects during War Games intro (#856) --- .../mod/scripts/vscripts/mp/levels/mp_wargames.nut | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') 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 8d859ba6..376c5b7c 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut @@ -231,6 +231,8 @@ void function OnPrematchStart() void function PlayerWatchesWargamesIntro( entity player ) { + player.EndSignal( "OnDestroy" ) + if ( IsAlive( player ) ) player.Die() @@ -253,8 +255,6 @@ void function PlayerWatchesWargamesIntro( entity player ) // we need to wait a frame if we killed ourselves to spawn into this, so just easier to do it all the time to remove any weirdness WaitFrame() - - player.EndSignal( "OnDestroy" ) player.EndSignal( "OnDeath" ) int factionTeam = ConvertPlayerFactionToIMCOrMilitiaTeam( player ) -- cgit v1.2.3 From 23abddd7019a186230f794c483cd5372a3b89c66 Mon Sep 17 00:00:00 2001 From: William Miller Date: Mon, 2 Sep 2024 20:55:10 -0300 Subject: Remove `SetShouldCreateMinimapSpawnZones()` from `_gamemode_ps.nut` (#861) Function is not necessary anymore, the reworked spawning logic at #829 uses playlistvars to control spawnzones creation on minimap. --- .../mod/scripts/vscripts/gamemodes/_gamemode_ps.nut | 6 ------ 1 file changed, 6 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut index c91c27d1..fb84cc82 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut @@ -20,12 +20,6 @@ void function GamemodePs_Init() ScoreEvent_SetupEarnMeterValuesForMixedModes() SetTimeoutWinnerDecisionFunc( CheckScoreForDraw ) SetupGenericFFAChallenge() - - // spawnzone stuff - SetShouldCreateMinimapSpawnZones( true ) - - //AddCallback_OnPlayerKilled( CheckSpawnzoneSuspiciousDeaths ) - //AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", SpawnzoneTriggerInit ) file.militiaPreviousSpawnZones = [ null, null, null ] file.imcPreviousSpawnZones = [ null, null, null ] -- cgit v1.2.3 From 6b839def7fa1aff2519dccb7cf7d9dcee661a6ef Mon Sep 17 00:00:00 2001 From: William Miller Date: Sat, 7 Sep 2024 17:07:52 -0300 Subject: Refactor and improve spawns (#829) Overhauls the spawning logic to primarily use native functions and with Squirrel only doing refining on the selected spawns. --- .../mod/scripts/vscripts/mp/spawn.nut | 763 ++++++++------------- 1 file changed, 302 insertions(+), 461 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut index d64e3a5b..c47552b3 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut @@ -1,27 +1,30 @@ untyped -global function InitRatings // temp for testing - global function Spawn_Init -global function SetRespawnsEnabled -global function RespawnsEnabled +global function FindSpawnPoint + global function SetSpawnpointGamemodeOverride global function GetSpawnpointGamemodeOverride global function AddSpawnpointValidationRule + +global function SetRespawnsEnabled +global function RespawnsEnabled global function CreateNoSpawnArea global function DeleteNoSpawnArea - -global function FindSpawnPoint +global function SpawnPointInNoSpawnArea global function RateSpawnpoints_Generic global function RateSpawnpoints_Frontline - -global function SetSpawnZoneRatingFunc -global function SetShouldCreateMinimapSpawnZones -global function CreateTeamSpawnZoneEntity global function RateSpawnpoints_SpawnZones global function DecideSpawnZone_Generic -global function DecideSpawnZone_CTF + +global struct spawnZoneProperties{ + int controllingTeam = TEAM_UNASSIGNED + entity minimapEnt = null + float zoneRating = 0.0 +} + +global table< entity, spawnZoneProperties > mapSpawnZones // Global so other scripts can access this for custom ratings if needed struct NoSpawnArea { @@ -35,30 +38,61 @@ struct NoSpawnArea struct { bool respawnsEnabled = true + array noSpawnAreas string spawnpointGamemodeOverride array< bool functionref( entity, int ) > customSpawnpointValidationRules - - table noSpawnAreas + bool shouldCreateMinimapSpawnzones } file + + + + + + + + + + +/* +██████ █████ ███████ ███████ ███████ ██ ██ ███ ██ ██████ ████████ ██ ██████ ███ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██ +██████ ███████ ███████ █████ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██████ ██ ██ ███████ ███████ ██ ██████ ██ ████ ██████ ██ ██ ██████ ██ ████ ███████ +*/ + void function Spawn_Init() -{ +{ + // callbacks for generic spawns AddSpawnCallback( "info_spawnpoint_human", InitSpawnpoint ) - AddSpawnCallback( "info_spawnpoint_human_start", InitSpawnpoint ) AddSpawnCallback( "info_spawnpoint_titan", InitSpawnpoint ) + AddSpawnCallback( "info_spawnpoint_droppod", InitSpawnpoint ) + AddSpawnCallback( "info_spawnpoint_dropship", InitSpawnpoint ) + AddSpawnCallback( "info_spawnpoint_human_start", InitSpawnpoint ) AddSpawnCallback( "info_spawnpoint_titan_start", InitSpawnpoint ) - - // callbacks for generic spawns - AddCallback_EntitiesDidLoad( InitPreferSpawnNodes ) + AddSpawnCallback( "info_spawnpoint_droppod_start", InitSpawnpoint ) + AddSpawnCallback( "info_spawnpoint_dropship_start", InitSpawnpoint ) // callbacks for spawnzone spawns AddCallback_GameStateEnter( eGameState.Prematch, ResetSpawnzones ) AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", AddSpawnZoneTrigger ) -} - -void function InitSpawnpoint( entity spawnpoint ) -{ - spawnpoint.s.lastUsedTime <- -999 + + float friendlyAIValue = 1.75 + if ( GameModeHasCapturePoints() ) + friendlyAIValue = 0.75 + + SpawnPoints_SetRatingMultipliers_Enemy( TD_TITAN, -10.0, -6.0, -1.0 ) + SpawnPoints_SetRatingMultipliers_Enemy( TD_PILOT, -10.0, -6.0, -1.0 ) + SpawnPoints_SetRatingMultipliers_Enemy( TD_AI, -2.0, -0.25, 0.0 ) + + SpawnPoints_SetRatingMultipliers_Friendly( TD_TITAN, 0.25, 1.75, friendlyAIValue ) + SpawnPoints_SetRatingMultipliers_Friendly( TD_PILOT, 0.25, 1.75, friendlyAIValue ) + SpawnPoints_SetRatingMultipliers_Friendly( TD_AI, 0.5, 0.25, 0.0 ) + + SpawnPoints_SetRatingMultiplier_PetTitan( 2.0 ) + + file.shouldCreateMinimapSpawnzones = GetCurrentPlaylistVarInt( "spawn_zone_enabled", 1 ) != 0 } void function SetRespawnsEnabled( bool enabled ) @@ -71,9 +105,10 @@ bool function RespawnsEnabled() return file.respawnsEnabled } -void function AddSpawnpointValidationRule( bool functionref( entity spawn, int team ) rule ) +void function InitSpawnpoint( entity spawnpoint ) { - file.customSpawnpointValidationRules.append( rule ) + spawnpoint.s.lastUsedTime <- -999 + spawnpoint.s.inUse <- false } string function CreateNoSpawnArea( int blockSpecificTeam, int blockEnemiesOfTeam, vector position, float lifetime, float radius ) @@ -85,11 +120,12 @@ string function CreateNoSpawnArea( int blockSpecificTeam, int blockEnemiesOfTeam noSpawnArea.lifetime = lifetime noSpawnArea.radius = radius - // generate an id noSpawnArea.id = UniqueString( "noSpawnArea" ) - thread NoSpawnAreaLifetime( noSpawnArea ) + if ( lifetime > 0 ) + thread NoSpawnAreaLifetime( noSpawnArea ) + file.noSpawnAreas.append( noSpawnArea ) return noSpawnArea.id } @@ -101,8 +137,41 @@ void function NoSpawnAreaLifetime( NoSpawnArea noSpawnArea ) void function DeleteNoSpawnArea( string noSpawnIdx ) { - if ( noSpawnIdx in file.noSpawnAreas ) - delete file.noSpawnAreas[ noSpawnIdx ] + foreach ( noSpawnArea in file.noSpawnAreas ) + { + if ( noSpawnArea.id == noSpawnIdx ) + file.noSpawnAreas.removebyvalue( noSpawnArea ) + } +} + +bool function SpawnPointInNoSpawnArea( vector vec, int team ) +{ + foreach ( noSpawnArea in file.noSpawnAreas ) + { + if ( Distance( noSpawnArea.position, vec ) < noSpawnArea.radius ) + { + if ( noSpawnArea.blockedTeam != TEAM_INVALID && noSpawnArea.blockedTeam == team ) + return true + + if ( noSpawnArea.blockOtherTeams != TEAM_INVALID && noSpawnArea.blockOtherTeams != team ) + return true + } + } + + return false +} + +bool function IsSpawnpointValidDrop( entity spawnpoint, int team ) +{ + if ( spawnpoint.IsOccupied() || spawnpoint.s.inUse ) + return false + + return true +} + +void function AddSpawnpointValidationRule( bool functionref( entity spawn, int team ) rule ) +{ + file.customSpawnpointValidationRules.append( rule ) } void function SetSpawnpointGamemodeOverride( string gamemode ) @@ -114,35 +183,38 @@ string function GetSpawnpointGamemodeOverride() { if ( file.spawnpointGamemodeOverride != "" ) return file.spawnpointGamemodeOverride - else - return GAMETYPE - unreachable + return GAMETYPE } -void function InitRatings( entity player, int team ) -{ - if ( player != null ) - SpawnPoints_InitRatings( player, team ) // no idea what the second arg supposed to be lol -} + + + + + + + + + +/* +███████ ██████ █████ ██ ██ ███ ██ ██████ ██████ ██████ ███████ ██████ ██ ███ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ +███████ ██████ ███████ ██ █ ██ ██ ██ ██ ██ ██ ██████ ██ ██ █████ ██████ ██ ██ ██ ██ ██ ███ + ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██ ██ ██ ███ ███ ██ ████ ██████ ██ ██ ██████ ███████ ██ ██ ██ ██ ████ ██████ +*/ entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnpoint ) { int team = player.GetTeam() - if ( HasSwitchedSides() ) - team = ( team == TEAM_MILITIA ) ? TEAM_IMC : TEAM_MILITIA - + array spawnpoints if ( useStartSpawnpoint ) spawnpoints = isTitan ? SpawnPoints_GetTitanStart( team ) : SpawnPoints_GetPilotStart( team ) else spawnpoints = isTitan ? SpawnPoints_GetTitan() : SpawnPoints_GetPilot() - InitRatings( player, player.GetTeam() ) - - // don't think this is necessary since we call discardratings - //foreach ( entity spawnpoint in spawnpoints ) - // spawnpoint.CalculateRating( isTitan ? TD_TITAN : TD_PILOT, team, 0.0, 0.0 ) + SpawnPoints_InitRatings( player, team ) void functionref( int, array, int, entity ) ratingFunc = isTitan ? GameMode_GetTitanSpawnpointsRatingFunc( GAMETYPE ) : GameMode_GetPilotSpawnpointsRatingFunc( GAMETYPE ) ratingFunc( isTitan ? TD_TITAN : TD_PILOT, spawnpoints, team, player ) @@ -166,36 +238,46 @@ entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnp spawnpoints = useStartSpawnpoint ? SpawnPoints_GetPilotStart( team ) : SpawnPoints_GetPilot() } - entity spawnpoint = GetBestSpawnpoint( player, spawnpoints ) + entity spawnpoint = GetBestSpawnpoint( player, spawnpoints, isTitan ) spawnpoint.s.lastUsedTime = Time() player.SetLastSpawnPoint( spawnpoint ) + + //SpawnPoints_DiscardRatings() return spawnpoint } -entity function GetBestSpawnpoint( entity player, array spawnpoints ) +entity function GetBestSpawnpoint( entity player, array spawnpoints, bool isTitan ) { - // not really 100% sure on this randomisation, needs some thought array validSpawns + + // I know this looks hacky but the native funcs to get the spawns is returning null arrays for FFA idk why. + if ( IsFFAGame() ) + { + spawnpoints.clear() + if ( isTitan ) + spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_titan" ) + else + spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_human" ) + } + foreach ( entity spawnpoint in spawnpoints ) { if ( IsSpawnpointValid( spawnpoint, player.GetTeam() ) ) validSpawns.append( spawnpoint ) } - if ( !validSpawns.len() ) + if ( !validSpawns.len() ) // First validity check { - // no valid spawns, very bad, so dont care about spawns being valid anymore - print( "found no valid spawns! spawns may be subpar!" ) + CodeWarning( "Map has no valid spawn points for " + GAMETYPE + " gamemode, attempting any other possible spawn point" ) foreach ( entity spawnpoint in spawnpoints ) validSpawns.append( spawnpoint ) } - // last resort - if ( !validSpawns.len() ) + if ( !validSpawns.len() ) // On all validity check, just gather the most basic spawn { - print( "map has literally 0 spawnpoints, as such everything is fucked probably, attempting to use info_player_start if present" ) + CodeWarning( "Map has no proper spawn points, falling back to info_player_start" ) entity start = GetEnt( "info_player_start" ) if ( IsValid( start ) ) @@ -203,14 +285,19 @@ entity function GetBestSpawnpoint( entity player, array spawnpoints ) start.s.lastUsedTime <- -999 validSpawns.append( start ) } + else + throw( "Map has no player spawns at all" ) } - return validSpawns.getrandom() // slightly randomize it + if ( IsFFAGame() ) + return validSpawns.getrandom() + + return validSpawns[0] // Return first entry in the array because native have already sorted everything through the ratings, so first one is the best one } bool function IsSpawnpointValid( entity spawnpoint, int team ) { - if ( !spawnpoint.HasKey( "ignoreGamemode" ) || ( spawnpoint.HasKey( "ignoreGamemode" ) && spawnpoint.kv.ignoreGamemode == "0" ) ) // used by script-spawned spawnpoints + if ( !spawnpoint.HasKey( "ignoreGamemode" ) || spawnpoint.HasKey( "ignoreGamemode" ) && spawnpoint.kv.ignoreGamemode == "0" ) // used by script-spawned spawnpoints { if ( file.spawnpointGamemodeOverride != "" ) { @@ -222,223 +309,149 @@ bool function IsSpawnpointValid( entity spawnpoint, int team ) return false } - if( IsFFAGame() && !spawnpoint.IsVisibleToEnemies( team ) ) - return true - - int compareTeam = spawnpoint.GetTeam() - if ( HasSwitchedSides() ) - compareTeam = ( compareTeam == TEAM_MILITIA ) ? TEAM_IMC : TEAM_MILITIA - foreach ( bool functionref( entity, int ) customValidationRule in file.customSpawnpointValidationRules ) if ( !customValidationRule( spawnpoint, team ) ) return false - if ( spawnpoint.GetTeam() > 0 && compareTeam != team ) + if ( !IsSpawnpointValidDrop( spawnpoint, team ) || Time() - spawnpoint.s.lastUsedTime <= 10.0 ) return false - if ( spawnpoint.IsOccupied() ) + if ( SpawnPointInNoSpawnArea( spawnpoint.GetOrigin(), team ) ) return false - - if ( Time() - spawnpoint.s.lastUsedTime <= 10.0 ) - return false - - foreach ( k, NoSpawnArea noSpawnArea in file.noSpawnAreas ) + + // Line of Sight Check, could use IsVisibleToEnemies but apparently that considers only players, not NPCs + array< entity > enemyTitans = GetTitanArrayOfEnemies( team ) + if ( GetConVarBool( "spawnpoint_avoid_npc_titan_sight" ) ) { - if ( Distance( noSpawnArea.position, spawnpoint.GetOrigin() ) > noSpawnArea.radius ) - continue - - if ( noSpawnArea.blockedTeam != TEAM_INVALID && noSpawnArea.blockedTeam == team ) - return false - - if ( noSpawnArea.blockOtherTeams != TEAM_INVALID && noSpawnArea.blockOtherTeams != team ) - return false + foreach ( titan in enemyTitans ) + { + if ( IsAlive( titan ) && titan.IsNPC() && titan.CanSee( spawnpoint ) ) + return false + } } - - const minEnemyDist = 1200.0 - array< entity > spawnBlockers = GetPlayerArrayEx( "any", TEAM_ANY, spawnpoint.GetTeam(), spawnpoint.GetOrigin(), minEnemyDist ) - spawnBlockers.extend( GetProjectileArrayEx( "any", TEAM_ANY, spawnpoint.GetTeam(), spawnpoint.GetOrigin(), minEnemyDist ) ) - spawnBlockers.extend( GetNPCArrayEx( "any", TEAM_ANY, spawnpoint.GetTeam(), spawnpoint.GetOrigin(), minEnemyDist ) ) - if ( spawnBlockers.len() ) - return false - // los check return !spawnpoint.IsVisibleToEnemies( team ) } -// SPAWNPOINT RATING FUNCS BELOW -// generic -struct { - array preferSpawnNodes -} spawnStateGeneric -void function RateSpawnpoints_Generic( int checkClass, array spawnpoints, int team, entity player ) -{ - if ( !IsFFAGame() ) - { - // use frontline spawns in 2-team modes - RateSpawnpoints_Frontline( checkClass, spawnpoints, team, player ) - return - } - else - { - // todo: ffa spawns :terror: - } - // old algo: keeping until we have a better ffa spawn algo - // i'm not a fan of this func, but i really don't have a better way to do this rn, and it's surprisingly good with los checks implemented now - - // calculate ratings for preferred nodes - // this tries to prefer nodes with more teammates, then activity on them - // todo: in the future it might be good to have this prefer nodes with enemies up to a limit of some sort - // especially in ffa modes i could deffo see this falling apart a bit rn - // perhaps dead players could be used to calculate some sort of activity rating? so high-activity points with an even balance of friendly/unfriendly players are preferred - array preferSpawnNodeRatings - foreach ( vector preferSpawnNode in spawnStateGeneric.preferSpawnNodes ) + + + + +/* +██████ ██████ ██ ███ ██ ████████ ██████ █████ ████████ ██ ███ ██ ██████ +██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ +██████ ██ ██ ██ ██ ██ ██ ██ ██████ ███████ ██ ██ ██ ██ ██ ██ ███ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██████ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██████ +*/ + +void function RateSpawnpoints_Generic( int checkClass, array spawnpoints, int team, entity player ) +{ + foreach ( entity spawnpoint in spawnpoints ) { - float currentRating + float currentRating = 0.0 - // this seems weird, not using rn - //Frontline currentFrontline = GetCurrentFrontline( team ) - //if ( !IsFFAGame() || currentFrontline.friendlyCenter != < 0, 0, 0 > ) - // currentRating += max( 0.0, ( 1000.0 - Distance2D( currentFrontline.origin, preferSpawnNode ) ) / 200 ) + // Gather friendly scoring first to give positive rating first + currentRating += spawnpoint.NearbyAllyScore( team, "ai" ) + currentRating += spawnpoint.NearbyAllyScore( team, "titan" ) + currentRating += spawnpoint.NearbyAllyScore( team, "pilot" ) - foreach ( entity nodePlayer in GetPlayerArray() ) - { - float currentChange = 0.0 - - // the closer a player is to a node the more they matter - float dist = Distance2D( preferSpawnNode, nodePlayer.GetOrigin() ) - if ( dist > 600.0 ) - continue - - currentChange = ( 600.0 - dist ) / 5 - if ( player == nodePlayer ) - currentChange *= -3 // always try to stay away from places we've already spawned - else if ( !IsAlive( nodePlayer ) ) // dead players mean activity which is good, but they're also dead so they don't matter as much as living ones - currentChange *= 0.6 - if ( nodePlayer.GetTeam() != player.GetTeam() ) // if someone isn't on our team and alive they're probably bad - { - if ( IsFFAGame() ) // in ffa everyone is on different teams, so this isn't such a big deal - currentChange *= -0.2 - else - currentChange *= -0.6 - } - - currentRating += currentChange - } + // Enemies then subtract that rating ( Values already returns negative, so no need to apply subtract again ) + currentRating += spawnpoint.NearbyEnemyScore( team, "ai" ) + currentRating += spawnpoint.NearbyEnemyScore( team, "titan" ) + currentRating += spawnpoint.NearbyEnemyScore( team, "pilot" ) - preferSpawnNodeRatings.append( currentRating ) - } - - foreach ( entity spawnpoint in spawnpoints ) - { - float currentRating - float petTitanModifier - // scale how much a given spawnpoint matters to us based on how far it is from each node - bool spawnHasRecievedInitialBonus = false - for ( int i = 0; i < spawnStateGeneric.preferSpawnNodes.len(); i++ ) - { - // bonus if autotitan is nearish - if ( IsAlive( player.GetPetTitan() ) && Distance( player.GetPetTitan().GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] ) < 1200.0 ) - petTitanModifier += 10.0 - - float dist = Distance2D( spawnpoint.GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] ) - if ( dist > 750.0 ) - continue - - if ( dist < 600.0 && !spawnHasRecievedInitialBonus ) - { - currentRating += 10.0 - spawnHasRecievedInitialBonus = true // should only get a bonus for simply being by a node once to avoid over-rating - } + if ( spawnpoint == player.p.lastSpawnPoint ) // Reduce the rating of the spawn point used previously + currentRating += GetConVarFloat( "spawnpoint_last_spawn_rating" ) - currentRating += ( preferSpawnNodeRatings[ i ] * ( ( 750.0 - dist ) / 75 ) ) + max( RandomFloat( 1.25 ), 0.9 ) - if ( dist < 250.0 ) // shouldn't get TOO close to an active node - currentRating *= 0.7 - - if ( spawnpoint.s.lastUsedTime < 10.0 ) - currentRating *= 0.7 - } - - float rating = spawnpoint.CalculateRating( checkClass, team, currentRating, currentRating + petTitanModifier ) - //print( "spawnpoint at " + spawnpoint.GetOrigin() + " has rating: " + ) - - if ( rating != 0.0 || currentRating != 0.0 ) - print( "rating = " + rating + ", internal rating = " + currentRating ) + spawnpoint.CalculateRating( checkClass, team, currentRating, currentRating * 0.25 ) } } -void function InitPreferSpawnNodes() +void function RateSpawnpoints_Frontline( int checkClass, array spawnpoints, int team, entity player ) { - foreach ( entity hardpoint in GetEntArrayByClass_Expensive( "info_hardpoint" ) ) + Frontline currentFrontline = GetFrontline( team ) + + vector inverseFrontlineDir = currentFrontline.combatDir * -1 + vector adjustedPosition = currentFrontline.origin + currentFrontline.combatDir * 8000 + + SpawnPoints_InitFrontlineData( adjustedPosition, currentFrontline.combatDir, currentFrontline.origin, currentFrontline.friendlyCenter, 4000 ) + + foreach ( entity spawnpoint in spawnpoints ) { - if ( !hardpoint.HasKey( "hardpointGroup" ) ) - continue - - if ( hardpoint.kv.hardpointGroup != "A" && hardpoint.kv.hardpointGroup != "B" && hardpoint.kv.hardpointGroup != "C" ) - continue - - spawnStateGeneric.preferSpawnNodes.append( hardpoint.GetOrigin() ) + float frontlineRating = spawnpoint.CalculateFrontlineRating() + + spawnpoint.CalculateRating( checkClass, team, frontlineRating, frontlineRating * 0.25 ) } - - //foreach ( entity frontline in GetEntArrayByClass_Expensive( "info_frontline" ) ) - // spawnStateGeneric.preferSpawnNodes.append( frontline.GetOrigin() ) } -// frontline -void function RateSpawnpoints_Frontline( int checkClass, array spawnpoints, int team, entity player ) -{ - float rating = RandomFloatRange( 0.0, 100.0 ) - foreach ( entity spawnpoint in spawnpoints ) - spawnpoint.CalculateRating( checkClass, player.GetTeam(), rating, rating ) -} -// spawnzones -struct { - array mapSpawnzoneTriggers - entity functionref( array, int ) spawnzoneRatingFunc - bool shouldCreateMinimapSpawnzones = false - - // for DecideSpawnZone_Generic - table activeTeamSpawnzones - table activeTeamSpawnzoneMinimapEnts -} spawnStateSpawnzones + + + + + + + + +/* +███████ ██████ █████ ██ ██ ███ ██ ███████ ██████ ███ ██ ███████ ███████ +██ ██ ██ ██ ██ ██ ██ ████ ██ ███ ██ ██ ████ ██ ██ ██ +███████ ██████ ███████ ██ █ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ █████ ███████ + ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ +███████ ██ ██ ██ ███ ███ ██ ████ ███████ ██████ ██ ████ ███████ ███████ +*/ void function ResetSpawnzones() { - spawnStateSpawnzones.activeTeamSpawnzones.clear() - - foreach ( int team, entity minimapEnt in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts ) - if ( IsValid( minimapEnt ) ) - minimapEnt.Destroy() - - spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts.clear() + foreach ( zone, zoneProperties in mapSpawnZones ) + { + if ( IsValid( zoneProperties.minimapEnt ) ) + zoneProperties.minimapEnt.Destroy() + + zoneProperties.controllingTeam = TEAM_UNASSIGNED + zoneProperties.zoneRating = 0.0 + } } void function AddSpawnZoneTrigger( entity trigger ) { - trigger.s.spawnzoneRating <- 0.0 - spawnStateSpawnzones.mapSpawnzoneTriggers.append( trigger ) + spawnZoneProperties zoneProperties + mapSpawnZones[trigger] <- zoneProperties } -void function SetSpawnZoneRatingFunc( entity functionref( array, int ) ratingFunc ) +bool function TeamHasDirtySpawnzone( int team ) { - spawnStateSpawnzones.spawnzoneRatingFunc = ratingFunc -} - -void function SetShouldCreateMinimapSpawnZones( bool shouldCreateMinimapSpawnzones ) -{ - spawnStateSpawnzones.shouldCreateMinimapSpawnzones = shouldCreateMinimapSpawnzones + foreach ( zone, zoneProperties in mapSpawnZones ) + { + if ( zoneProperties.controllingTeam == team ) + { + int numDeadInZone = 0 + array teamPlayers = GetPlayerArrayOfTeam( team ) + foreach ( entity player in teamPlayers ) + { + if ( Time() - player.p.postDeathThreadStartTime < 20.0 && zone.ContainsPoint( player.p.deathOrigin ) ) + numDeadInZone++ + } + + if ( numDeadInZone < teamPlayers.len() ) + return false + } + } + + return true } -entity function CreateTeamSpawnZoneEntity( entity spawnzone, int team ) +void function CreateTeamSpawnZoneEntity( entity spawnzone, int team ) { entity minimapObj = CreatePropScript( $"models/dev/empty_model.mdl", spawnzone.GetOrigin() ) SetTeam( minimapObj, team ) - minimapObj.Minimap_SetObjectScale( 100.0 / Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) ) + minimapObj.Minimap_SetObjectScale( Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) / 16000 ) // 16000 cuz thats the total space Minimap uses minimapObj.Minimap_SetAlignUpright( true ) minimapObj.Minimap_AlwaysShow( TEAM_IMC, null ) minimapObj.Minimap_AlwaysShow( TEAM_MILITIA, null ) @@ -451,67 +464,58 @@ entity function CreateTeamSpawnZoneEntity( entity spawnzone, int team ) minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_MIL ) minimapObj.DisableHibernation() - return minimapObj + mapSpawnZones[spawnzone].minimapEnt = minimapObj } void function RateSpawnpoints_SpawnZones( int checkClass, array spawnpoints, int team, entity player ) { - if ( spawnStateSpawnzones.spawnzoneRatingFunc == null ) - spawnStateSpawnzones.spawnzoneRatingFunc = DecideSpawnZone_Generic - - // don't use spawnzones if we're using start spawns if ( ShouldStartSpawn( player ) ) { RateSpawnpoints_Generic( checkClass, spawnpoints, team, player ) return } - - entity spawnzone = spawnStateSpawnzones.spawnzoneRatingFunc( spawnStateSpawnzones.mapSpawnzoneTriggers, player.GetTeam() ) - if ( !IsValid( spawnzone ) ) // no spawn zone, use generic algo + + array< entity > zoneTriggers + foreach ( zone, zoneProperties in mapSpawnZones ) + zoneTriggers.append( zone ) + + entity spawnzone = DecideSpawnZone_Generic( zoneTriggers, player.GetTeam() ) + if ( !IsValid( spawnzone ) ) { RateSpawnpoints_Generic( checkClass, spawnpoints, team, player ) return } - // rate spawnpoints foreach ( entity spawn in spawnpoints ) { float rating = 0.0 float distance = Distance2D( spawn.GetOrigin(), spawnzone.GetOrigin() ) if ( distance < Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) ) - rating = 100.0 - else // max 35 rating if not in zone, rate by closest - rating = 35.0 * ( 1 - ( distance / 5000.0 ) ) + rating = 10.0 + else + rating = 2.0 * ( 1 - ( distance / 3000.0 ) ) - spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating ) + spawn.CalculateRating( checkClass, team, rating, rating * 0.25 ) } } entity function DecideSpawnZone_Generic( array spawnzones, int team ) { - if ( spawnzones.len() == 0 ) + if ( !spawnzones.len() ) return null - // get average team startspawn positions - int spawnCompareTeam = team - if ( HasSwitchedSides() ) - spawnCompareTeam = GetOtherTeam( team ) - - array startSpawns = SpawnPoints_GetPilotStart( spawnCompareTeam ) - array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( spawnCompareTeam ) ) + array startSpawns = SpawnPoints_GetPilotStart( team ) + array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) ) - if ( startSpawns.len() == 0 || enemyStartSpawns.len() == 0 ) // ensure we don't crash + if ( !startSpawns.len() || !enemyStartSpawns.len() ) return null - - // get average startspawn position and max dist between spawns - // could probably cache this, tbh, not like it should change outside of halftimes - vector averageFriendlySpawns + + vector averageFriendlySpawns foreach ( entity spawn in startSpawns ) averageFriendlySpawns += spawn.GetOrigin() averageFriendlySpawns /= startSpawns.len() - // get average enemy startspawn position vector averageEnemySpawns foreach ( entity spawn in enemyStartSpawns ) averageEnemySpawns += spawn.GetOrigin() @@ -520,250 +524,87 @@ entity function DecideSpawnZone_Generic( array spawnzones, int team ) float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns ) - bool needNewZone = true - if ( team in spawnStateSpawnzones.activeTeamSpawnzones ) + if ( TeamHasDirtySpawnzone( team ) ) { - foreach ( entity player in GetPlayerArray() ) - { - // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this - if ( player.GetTeam() != team && spawnStateSpawnzones.activeTeamSpawnzones[ team ].ContainsPoint( player.GetOrigin() ) ) - break - } - - int numDeadInZone = 0 - array teamPlayers = GetPlayerArrayOfTeam( team ) - foreach ( entity player in teamPlayers ) - { - // check if they died in the zone recently, get a new zone if too many died - if ( Time() - player.p.postDeathThreadStartTime < 15.0 && spawnStateSpawnzones.activeTeamSpawnzones[ team ].ContainsPoint( player.p.deathOrigin ) ) - numDeadInZone++ - } - - // cast to float so result is float - if ( float( numDeadInZone ) / teamPlayers.len() <= 0.1 ) - needNewZone = false - } - - if ( needNewZone ) - { - // find new zone array possibleZones - foreach ( entity spawnzone in spawnStateSpawnzones.mapSpawnzoneTriggers ) + foreach ( zone, zoneProperties in mapSpawnZones ) { - // don't remember if you can do a "value in table.values" sorta thing in squirrel so doing manual lookup - bool spawnzoneTaken = false - foreach ( int otherTeam, entity otherSpawnzone in spawnStateSpawnzones.activeTeamSpawnzones ) - { - if ( otherSpawnzone == spawnzone ) - { - spawnzoneTaken = true - break - } - } - - if ( spawnzoneTaken ) + if ( zoneProperties.controllingTeam == GetOtherTeam( team ) ) continue - // check zone validity - bool spawnzoneEvil = false - foreach ( entity player in GetPlayerArray() ) + bool spawnzoneHasEnemies = false + foreach ( entity enemy in GetPlayerArrayOfEnemies_Alive( team ) ) { - // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this - if ( player.GetTeam() != team && spawnzone.ContainsPoint( player.GetOrigin() ) ) + if ( zone.ContainsPoint( enemy.GetOrigin() ) ) { - spawnzoneEvil = true + spawnzoneHasEnemies = true break } } - // don't choose spawnzones that are closer to enemy base than friendly base - // note: vanilla spawns might not necessarily require this, worth checking - if ( !spawnzoneEvil && Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns ) > Distance2D( spawnzone.GetOrigin(), averageEnemySpawns ) ) - spawnzoneEvil = true + if ( !spawnzoneHasEnemies && Distance2D( zone.GetOrigin(), averageFriendlySpawns ) > Distance2D( zone.GetOrigin(), averageEnemySpawns ) ) + spawnzoneHasEnemies = true - if ( spawnzoneEvil ) + if ( spawnzoneHasEnemies ) continue - // rate spawnzone based on distance to frontline Frontline frontline = GetFrontline( team ) - - // prefer spawns close to base pos - float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawnzone.GetOrigin() ) / baseDistance ) + float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, zone.GetOrigin() ) / baseDistance ) if ( frontline.friendlyCenter != < 0, 0, 0 > ) { - // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir - rating += rating * ( 1.0 - ( Distance2D( spawnzone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) - rating *= fabs( frontline.combatDir.y - Normalize( spawnzone.GetOrigin() - averageFriendlySpawns ).y ) + rating += rating * ( 1.0 - ( Distance2D( zone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) + rating *= fabs( frontline.combatDir.y - Normalize( zone.GetOrigin() - averageFriendlySpawns ).y ) } - spawnzone.s.spawnzoneRating = rating - possibleZones.append( spawnzone ) + zoneProperties.zoneRating = rating + possibleZones.append( zone ) } - if ( possibleZones.len() == 0 ) + if ( !possibleZones.len() ) return null - possibleZones.sort( int function( entity a, entity b ) - { - if ( a.s.spawnzoneRating > b.s.spawnzoneRating ) - return -1 - - if ( b.s.spawnzoneRating > a.s.spawnzoneRating ) - return 1 - - return 0 - } ) - entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ] - - if ( spawnStateSpawnzones.shouldCreateMinimapSpawnzones ) - { - entity oldEnt - if ( team in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts ) - oldEnt = spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] - - spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] <- CreateTeamSpawnZoneEntity( chosenZone, team ) - if ( IsValid( oldEnt ) ) - oldEnt.Destroy() - } + possibleZones.sort( SortPossibleZones ) - spawnStateSpawnzones.activeTeamSpawnzones[ team ] <- chosenZone - } - - return spawnStateSpawnzones.activeTeamSpawnzones[ team ] -} - -// ideally this should be in the gamemode_ctf file, but would need refactors to expose more stuff that's not available there rn -entity function DecideSpawnZone_CTF( array spawnzones, int team ) -{ - if ( spawnzones.len() == 0 ) - return null - - int otherTeam = GetOtherTeam( team ) - array enemyPlayers = GetPlayerArrayOfTeam( otherTeam ) - - // get average team startspawn positions - int spawnCompareTeam = team - if ( HasSwitchedSides() ) - spawnCompareTeam = GetOtherTeam( team ) - - array startSpawns = SpawnPoints_GetPilotStart( spawnCompareTeam ) - array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( spawnCompareTeam ) ) - - if ( startSpawns.len() == 0 || enemyStartSpawns.len() == 0 ) // ensure we don't crash - return null - - // get average startspawn position and max dist between spawns - // could probably cache this, tbh, not like it should change outside of halftimes - vector averageFriendlySpawns - foreach ( entity spawn in startSpawns ) - averageFriendlySpawns += spawn.GetOrigin() - - averageFriendlySpawns /= startSpawns.len() - - // get average enemy startspawn position - vector averageEnemySpawns - foreach ( entity spawn in enemyStartSpawns ) - averageEnemySpawns += spawn.GetOrigin() - - averageEnemySpawns /= enemyStartSpawns.len() - - float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns ) - - // find new zone - array possibleZones - foreach ( entity spawnzone in spawnStateSpawnzones.mapSpawnzoneTriggers ) - { - // can't choose zone if another team has it - if ( otherTeam in spawnStateSpawnzones.activeTeamSpawnzones && spawnStateSpawnzones.activeTeamSpawnzones[ otherTeam ] == spawnzone ) - continue + entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ] - // check zone validity - bool spawnzoneEvil = false - foreach ( entity player in enemyPlayers ) + if ( file.shouldCreateMinimapSpawnzones ) { - // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this - if ( spawnzone.ContainsPoint( player.GetOrigin() ) ) + foreach ( zone, zoneProperties in mapSpawnZones ) { - spawnzoneEvil = true - break + if ( chosenZone == zone ) + continue + + if ( IsValid( zoneProperties.minimapEnt ) && zoneProperties.controllingTeam == team ) + zoneProperties.minimapEnt.Destroy() } + + CreateTeamSpawnZoneEntity( chosenZone, team ) } - // don't choose spawnzones that are closer to enemy base than friendly base - if ( !spawnzoneEvil && Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns ) > Distance2D( spawnzone.GetOrigin(), averageEnemySpawns ) ) - spawnzoneEvil = true - - if ( spawnzoneEvil ) - continue - - // rate spawnzone based on distance to frontline - Frontline frontline = GetFrontline( team ) - - // prefer spawns close to base pos - float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawnzone.GetOrigin() ) / baseDistance ) - - if ( frontline.friendlyCenter != < 0, 0, 0 > ) + foreach ( zone, zoneProperties in mapSpawnZones ) { - // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir - rating += rating * ( 1.0 - ( Distance2D( spawnzone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) - rating *= fabs( frontline.combatDir.y - Normalize( spawnzone.GetOrigin() - averageFriendlySpawns ).y ) - - // reduce rating based on players that can currently see the zone - bool hasAppliedInitialLoss = false - foreach ( entity player in enemyPlayers ) - { - // don't trace here, just do an angle check - if ( PlayerCanSee( player, spawnzone, false, 65 ) && Distance2D( player.GetOrigin(), spawnzone.GetOrigin() ) <= 2000.0 ) - { - float distFrac = TraceLineSimple( player.GetOrigin(), spawnzone.GetOrigin(), player ) - - if ( distFrac >= 0.65 ) - { - // give a fairly large loss if literally anyone can see it - if ( !hasAppliedInitialLoss ) - { - rating *= 0.8 - hasAppliedInitialLoss = true - } - - rating *= ( 1.0 / enemyPlayers.len() ) * distFrac - } - } - } + if ( chosenZone == zone ) + continue + + if ( zoneProperties.controllingTeam == team ) + zoneProperties.controllingTeam = TEAM_UNASSIGNED } - spawnzone.s.spawnzoneRating = rating - possibleZones.append( spawnzone ) + mapSpawnZones[chosenZone].controllingTeam = team + return chosenZone } - if ( possibleZones.len() == 0 ) - return null - - possibleZones.sort( int function( entity a, entity b ) - { - if ( a.s.spawnzoneRating > b.s.spawnzoneRating ) - return -1 + return null +} + +int function SortPossibleZones( entity a, entity b ) +{ + if ( mapSpawnZones[a].zoneRating > mapSpawnZones[b].zoneRating ) + return -1 - if ( b.s.spawnzoneRating > a.s.spawnzoneRating ) - return 1 + if ( mapSpawnZones[b].zoneRating > mapSpawnZones[a].zoneRating ) + return 1 - return 0 - } ) - entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ] - - if ( spawnStateSpawnzones.shouldCreateMinimapSpawnzones ) - { - entity oldEnt - if ( team in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts ) - oldEnt = spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] - - spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] <- CreateTeamSpawnZoneEntity( chosenZone, team ) - if ( IsValid( oldEnt ) ) - oldEnt.Destroy() - } - - spawnStateSpawnzones.activeTeamSpawnzones[ team ] <- chosenZone - - return spawnStateSpawnzones.activeTeamSpawnzones[ team ] + return 0 } \ No newline at end of file -- cgit v1.2.3 From ee33fd31e7559630260541bef3492ef895647b8c Mon Sep 17 00:00:00 2001 From: LightBlueCube <115393812+LightBlueCube@users.noreply.github.com> Date: Tue, 10 Sep 2024 06:26:23 +0800 Subject: Fix crash when player is in a special team (#866) on player team switch. Adds a check whether player is in a default team (IMC/Milita) before calling `GetOtherTeam()` --- Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index f7c398d9..5d3777b4 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -1062,7 +1062,9 @@ void function OnPlayerChangedTeam( entity player ) if ( !player.hasConnected ) // Prevents players who just joined to trigger below code, as server always pre setups their teams return - NotifyClientsOfTeamChange( player, GetOtherTeam( player.GetTeam() ), player.GetTeam() ) + // only TEAM_IMC and TEAM_MILITIA can use function GetOtherTeam(), doesnt need to notify them when player got a special team + if( IsIMCOrMilitiaTeam( player.GetTeam() ) ) + NotifyClientsOfTeamChange( player, GetOtherTeam( player.GetTeam() ), player.GetTeam() ) foreach( npc in GetNPCArray() ) { -- cgit v1.2.3 From 3dfd2091b4e72aad14b5d9cfd2332e7ec643c7fd Mon Sep 17 00:00:00 2001 From: William Miller Date: Tue, 10 Sep 2024 11:52:40 -0300 Subject: Reorder code of `_gamestate_mp.nut` (#875) Reorder functions to prepare for refactor and add big ASCII text comments for wayfinding using common IDE minimap --- .../mod/scripts/vscripts/mp/_gamestate_mp.nut | 574 ++++++++++++++------- 1 file changed, 380 insertions(+), 194 deletions(-) (limited to 'Northstar.CustomServers/mod/scripts') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index 5d3777b4..0c66f5a9 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -59,18 +59,21 @@ struct { bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) shouldTryUseProjectileReplayCallback } file -void function SetCallback_TryUseProjectileReplay( bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) callback ) -{ - file.shouldTryUseProjectileReplayCallback = callback -} -bool function ShouldTryUseProjectileReplay( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) -{ - if ( file.shouldTryUseProjectileReplayCallback != null ) - return file.shouldTryUseProjectileReplayCallback( victim, attacker, damageInfo, isRoundEnd ) - // default to true (vanilla behaviour) - return true -} + + + + + + + +/* + ██████ █████ ███ ███ ███████ ███████ ████████ █████ ████████ ███████ ███████ +██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███ ███████ ██ ████ ██ █████ ███████ ██ ███████ ██ █████ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██████ ██ ██ ██ ██ ███████ ███████ ██ ██ ██ ██ ███████ ███████ +*/ void function PIN_GameStart() { @@ -101,6 +104,61 @@ void function PIN_GameStart() RegisterSignal( "CleanUpEntitiesForRoundEnd" ) } +void function GameState_EntitiesDidLoad() +{ + if ( GetClassicMPMode() || ClassicMP_ShouldTryIntroAndEpilogueWithoutClassicMP() ) + ClassicMP_SetupIntro() +} + +void function WaittillGameStateOrHigher( int gameState ) +{ + while ( GetGameState() < gameState ) + svGlobal.levelEnt.WaitSignal( "GameStateChanged" ) +} + +bool function ShouldTryUseProjectileReplay( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) +{ + if ( file.shouldTryUseProjectileReplayCallback != null ) + return file.shouldTryUseProjectileReplayCallback( victim, attacker, damageInfo, isRoundEnd ) + // default to true (vanilla behaviour) + return true +} + +/// This is to move all NPCs that a player owns from one team to the other during a match +/// Auto-Titans, Turrets, Ticks and Hacked Spectres will all move along together with the player to the new Team +/// Also possibly prevents mods that spawns other types of NPCs that players can own from breaking when switching (i.e Drones, Hacked Reapers) +void function OnPlayerChangedTeam( entity player ) +{ + if ( !player.hasConnected ) // Prevents players who just joined to trigger below code, as server always pre setups their teams + return + + if( IsIMCOrMilitiaTeam( player.GetTeam() ) ) + NotifyClientsOfTeamChange( player, GetOtherTeam( player.GetTeam() ), player.GetTeam() ) + + foreach( npc in GetNPCArray() ) + { + entity bossPlayer = npc.GetBossPlayer() + if ( IsValidPlayer( bossPlayer ) && bossPlayer == player && IsAlive( npc ) ) + SetTeam( npc, player.GetTeam() ) + } +} + + + + + + + + + +/* + ██████ █████ ███ ███ ███████ ███████ ███████ ████████ ██ ██ ██████ +██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███ ███████ ██ ████ ██ █████ ███████ █████ ██ ██ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██████ ██ ██ ██ ██ ███████ ███████ ███████ ██ ██████ ██ +*/ + void function SetGameState( int newState ) { if ( newState == GetGameState() ) @@ -115,23 +173,166 @@ void function SetGameState( int newState ) callbackFunc() } -void function GameState_EntitiesDidLoad() +void function AddTeamScore( int team, int amount ) { - if ( GetClassicMPMode() || ClassicMP_ShouldTryIntroAndEpilogueWithoutClassicMP() ) - ClassicMP_SetupIntro() + GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + amount ) + GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + amount ) + + int scoreLimit + if ( IsRoundBased() ) + scoreLimit = GameMode_GetRoundScoreLimit( GAMETYPE ) + else + scoreLimit = GameMode_GetScoreLimit( GAMETYPE ) + + int score = GameRules_GetTeamScore( team ) + if ( score >= scoreLimit || GetGameState() == eGameState.SuddenDeath ) + SetWinner( team ) + else if ( ( file.switchSidesBased && !file.hasSwitchedSides ) && score >= ( scoreLimit.tofloat() / 2.0 ) ) + SetGameState( eGameState.SwitchingSides ) } -void function WaittillGameStateOrHigher( int gameState ) +void function SetWinner( int team, string winningReason = "", string losingReason = "" ) +{ + SetServerVar( "winningTeam", team ) + + file.gameWonThisFrame = true + thread UpdateGameWonThisFrameNextFrame() + + if ( winningReason.len() == 0 ) + file.announceRoundWinnerWinningSubstr = 0 + else + file.announceRoundWinnerWinningSubstr = GetStringID( winningReason ) + + if ( losingReason.len() == 0 ) + file.announceRoundWinnerLosingSubstr = 0 + else + file.announceRoundWinnerLosingSubstr = GetStringID( losingReason ) + + if ( GamePlayingOrSuddenDeath() ) + { + if ( IsRoundBased() ) + { + if ( team != TEAM_UNASSIGNED ) + { + GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + 1 ) + GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + 1 ) + } + + SetGameState( eGameState.WinnerDetermined ) + ScoreEvent_RoundComplete( team ) + } + else + { + SetGameState( eGameState.WinnerDetermined ) + ScoreEvent_MatchComplete( team ) + + array players = GetPlayerArray() + int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( GAMETYPE ) + if ( compareFunc != null ) + { + players.sort( compareFunc ) + int playerCount = players.len() + int currentPlace = 1 + for ( int i = 0; i < 3; i++ ) + { + if ( i >= playerCount ) + continue + + if ( i > 0 && compareFunc( players[i - 1], players[i] ) != 0 ) + currentPlace += 1 + + switch( currentPlace ) + { + case 1: + UpdatePlayerStat( players[i], "game_stats", "mvp" ) + UpdatePlayerStat( players[i], "game_stats", "mvp_total" ) + UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) + break + case 2: + UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) + break + case 3: + UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) + break + } + } + } + } + } +} + +void function SetTimeoutWinnerDecisionFunc( int functionref() callback ) { - while ( GetGameState() < gameState ) - svGlobal.levelEnt.WaitSignal( "GameStateChanged" ) + file.timeoutWinnerDecisionFunc = callback } +void function SetCallback_TryUseProjectileReplay( bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) callback ) +{ + file.shouldTryUseProjectileReplayCallback = callback +} -// logic for individual gamestates: +void function AddCallback_OnRoundEndCleanup( void functionref() callback ) +{ + file.roundEndCleanupCallbacks.append( callback ) +} + +void function SetShouldUsePickLoadoutScreen( bool shouldUse ) +{ + file.usePickLoadoutScreen = shouldUse +} +void function SetSwitchSidesBased( bool switchSides ) +{ + file.switchSidesBased = switchSides +} + +void function SetSuddenDeathBased( bool suddenDeathBased ) +{ + file.suddenDeathBased = suddenDeathBased +} + +void function SetTimerBased( bool timerBased ) +{ + file.timerBased = timerBased +} + +void function SetShouldUseRoundWinningKillReplay( bool shouldUse ) +{ + SetServerVar( "roundWinningKillReplayEnabled", shouldUse ) +} + +void function SetRoundWinningKillReplayKillClasses( bool pilot, bool titan ) +{ + file.roundWinningKillReplayTrackPilotKills = pilot + file.roundWinningKillReplayTrackTitanKills = titan // player kills in titans should get tracked anyway, might be worth renaming this +} + +void function SetRoundWinningKillReplayAttacker( entity attacker, int inflictorEHandle = -1 ) +{ + file.roundWinningKillReplayTime = Time() + file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker ) + file.roundWinningKillReplayAttacker = attacker + file.roundWinningKillReplayInflictorEHandle = inflictorEHandle == -1 ? attacker.GetEncodedEHandle() : inflictorEHandle + file.roundWinningKillReplayTimeOfDeath = Time() +} + + + + + + + + + + +/* + ██████ ██ ██ ███████ ████████ ██████ ███ ███ ███████ ████████ █████ ██████ ████████ +██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ███████ ██ ██ ██ ██ ████ ██ ███████ ██ ███████ ██████ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██████ ██████ ███████ ██ ██████ ██ ██ ███████ ██ ██ ██ ██ ██ ██ +*/ -// eGameState.WaitingForCustomStart void function GameStateEnter_WaitingForCustomStart() { // unused in release, comments indicate this was supposed to be used for an e3 demo @@ -139,7 +340,22 @@ void function GameStateEnter_WaitingForCustomStart() } -// eGameState.WaitingForPlayers + + + + + + + + +/* +██ ██ █████ ██ ████████ ██ ███ ██ ██████ ███████ ██████ ██████ ██████ ██ █████ ██ ██ ███████ ██████ ███████ +██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ █ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ███ █████ ██ ██ ██████ ██████ ██ ███████ ████ █████ ██████ ███████ +██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ███ ███ ██ ██ ██ ██ ██ ██ ████ ██████ ██ ██████ ██ ██ ██ ███████ ██ ██ ██ ███████ ██ ██ ███████ +*/ + void function GameStateEnter_WaitingForPlayers() { foreach ( entity player in GetPlayerArray() ) @@ -171,7 +387,22 @@ void function WaitingForPlayers_ClientConnected( entity player ) ScreenFadeToBlackForever( player, 0.0 ) } -// eGameState.PickLoadout + + + + + + + + +/* +██████ ██ ██████ ██ ██ ██ ██████ █████ ██████ ██████ ██ ██ ████████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██████ ██ ██ █████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██████ ██ ██ ███████ ██████ ██ ██ ██████ ██████ ██████ ██ +*/ + void function GameStateEnter_PickLoadout() { thread GameStateEnter_PickLoadout_Threaded() @@ -190,7 +421,22 @@ void function GameStateEnter_PickLoadout_Threaded() } -// eGameState.Prematch + + + + + + + + +/* +██████ ██████ ███████ ███ ███ █████ ████████ ██████ ██ ██ +██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ +██████ ██████ █████ ██ ████ ██ ███████ ██ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ███████ ██ ██ ██ ██ ██ ██████ ██ ██ +*/ + void function GameStateEnter_Prematch() { int timeLimit = GameMode_GetTimeLimit( GAMETYPE ) * 60 @@ -236,7 +482,22 @@ void function StartGameWithoutClassicMP() } -// eGameState.Playing + + + + + + + + +/* +██████ ██ █████ ██ ██ ██ ███ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ +██████ ██ ███████ ████ ██ ██ ██ ██ ██ ███ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███████ ██ ██ ██ ██ ██ ████ ██████ +*/ + void function GameStateEnter_Playing() { thread GameStateEnter_Playing_Threaded() @@ -279,7 +540,22 @@ void function GameStateEnter_Playing_Threaded() } -// eGameState.WinnerDetermined + + + + + + + + +/* +██ ██ ██ ███ ██ ███ ██ ███████ ██████ ██████ ███████ ████████ ███████ ██████ ███ ███ ██ ███ ██ ███████ ██████ +██ ██ ██ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ +██ █ ██ ██ ██ ██ ██ ██ ██ ██ █████ ██████ ██ ██ █████ ██ █████ ██████ ██ ████ ██ ██ ██ ██ ██ █████ ██ ██ +██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ███ ███ ██ ██ ████ ██ ████ ███████ ██ ██ ██████ ███████ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ███████ ██████ +*/ + // these are likely innacurate const float ROUND_END_FADE_KILLREPLAY = 1.0 const float ROUND_END_DELAY_KILLREPLAY = 3.0 @@ -446,7 +722,22 @@ void function PlayerWatchesRoundWinningKillReplay( entity player, float replayLe } -// eGameState.SwitchingSides + + + + + + + + +/* +███████ ██ ██ ██ ████████ ██████ ██ ██ ██ ███ ██ ██████ ███████ ██ ██████ ███████ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██ █ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ███ ███████ ██ ██ ██ █████ ███████ + ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ███ ███ ██ ██ ██████ ██ ██ ██ ██ ████ ██████ ███████ ██ ██████ ███████ ███████ +*/ + void function GameStateEnter_SwitchingSides() { thread GameStateEnter_SwitchingSides_Threaded() @@ -537,7 +828,22 @@ void function PlayerWatchesSwitchingSidesKillReplay( entity player, bool doRepla } -// eGameState.SuddenDeath + + + + + + + + +/* +███████ ██ ██ ██████ ██████ ███████ ███ ██ ██████ ███████ █████ ████████ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██ ██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ██ █████ ███████ ██ ███████ + ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██████ ██████ ██████ ███████ ██ ████ ██████ ███████ ██ ██ ██ ██ ██ +*/ + void function GameStateEnter_SuddenDeath() { // disable respawns, suddendeath calling is done on a kill callback @@ -559,7 +865,22 @@ void function GameStateEnter_SuddenDeath() } -// eGameState.Postmatch + + + + + + + + +/* +██████ ██████ ███████ ████████ ███ ███ █████ ████████ ██████ ██ ██ +██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ +██████ ██ ██ ███████ ██ ██ ████ ██ ███████ ██ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██████ ███████ ██ ██ ██ ██ ██ ██ ██████ ██ ██ +*/ + void function GameStateEnter_Postmatch() { foreach ( entity player in GetPlayerArray() ) @@ -592,7 +913,21 @@ void function ForceFadeToBlack( entity player ) } -// shared across multiple gamestates + + + + + + + + +/* +██ ██ ██ ██ ██ ██████ █████ ██ ██ ██████ █████ ██████ ██ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +█████ ██ ██ ██ ██ ███████ ██ ██ ██████ ███████ ██ █████ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ███████ ███████ ██████ ██ ██ ███████ ███████ ██████ ██ ██ ██████ ██ ██ ███████ +*/ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) { @@ -722,10 +1057,22 @@ void function OnTitanKilled( entity victim, var damageInfo ) } } -void function AddCallback_OnRoundEndCleanup( void functionref() callback ) -{ - file.roundEndCleanupCallbacks.append( callback ) -} + + + + + + + + + +/* +████████ ██████ ██████ ██ ███████ ██ ██ ███ ██ ██████ ████████ ██ ██████ ███ ██ ███████ + ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██ + ██ ██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ + ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██ ██████ ██████ ███████ ██ ██████ ██ ████ ██████ ██ ██ ██████ ██ ████ ███████ +*/ void function CleanUpEntitiesForRoundEnd() { @@ -763,120 +1110,6 @@ void function CleanUpEntitiesForRoundEnd() SetPlayerDeathsHidden( false ) } - - -// stuff for gamemodes to call - -void function SetShouldUsePickLoadoutScreen( bool shouldUse ) -{ - file.usePickLoadoutScreen = shouldUse -} - -void function SetSwitchSidesBased( bool switchSides ) -{ - file.switchSidesBased = switchSides -} - -void function SetSuddenDeathBased( bool suddenDeathBased ) -{ - file.suddenDeathBased = suddenDeathBased -} - -void function SetTimerBased( bool timerBased ) -{ - file.timerBased = timerBased -} - -void function SetShouldUseRoundWinningKillReplay( bool shouldUse ) -{ - SetServerVar( "roundWinningKillReplayEnabled", shouldUse ) -} - -void function SetRoundWinningKillReplayKillClasses( bool pilot, bool titan ) -{ - file.roundWinningKillReplayTrackPilotKills = pilot - file.roundWinningKillReplayTrackTitanKills = titan // player kills in titans should get tracked anyway, might be worth renaming this -} - -void function SetRoundWinningKillReplayAttacker( entity attacker, int inflictorEHandle = -1 ) -{ - file.roundWinningKillReplayTime = Time() - file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker ) - file.roundWinningKillReplayAttacker = attacker - file.roundWinningKillReplayInflictorEHandle = inflictorEHandle == -1 ? attacker.GetEncodedEHandle() : inflictorEHandle - file.roundWinningKillReplayTimeOfDeath = Time() -} - -void function SetWinner( int team, string winningReason = "", string losingReason = "" ) -{ - SetServerVar( "winningTeam", team ) - - file.gameWonThisFrame = true - thread UpdateGameWonThisFrameNextFrame() - - if ( winningReason.len() == 0 ) - file.announceRoundWinnerWinningSubstr = 0 - else - file.announceRoundWinnerWinningSubstr = GetStringID( winningReason ) - - if ( losingReason.len() == 0 ) - file.announceRoundWinnerLosingSubstr = 0 - else - file.announceRoundWinnerLosingSubstr = GetStringID( losingReason ) - - if ( GamePlayingOrSuddenDeath() ) - { - if ( IsRoundBased() ) - { - if ( team != TEAM_UNASSIGNED ) - { - GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + 1 ) - GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + 1 ) - } - - SetGameState( eGameState.WinnerDetermined ) - ScoreEvent_RoundComplete( team ) - } - else - { - SetGameState( eGameState.WinnerDetermined ) - ScoreEvent_MatchComplete( team ) - - array players = GetPlayerArray() - int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( GAMETYPE ) - if ( compareFunc != null ) - { - players.sort( compareFunc ) - int playerCount = players.len() - int currentPlace = 1 - for ( int i = 0; i < 3; i++ ) - { - if ( i >= playerCount ) - continue - - if ( i > 0 && compareFunc( players[i - 1], players[i] ) != 0 ) - currentPlace += 1 - - switch( currentPlace ) - { - case 1: - UpdatePlayerStat( players[i], "game_stats", "mvp" ) - UpdatePlayerStat( players[i], "game_stats", "mvp_total" ) - UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) - break - case 2: - UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) - break - case 3: - UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) - break - } - } - } - } - } -} - void function UpdateGameWonThisFrameNextFrame() { WaitFrame() @@ -884,29 +1117,6 @@ void function UpdateGameWonThisFrameNextFrame() file.hasKillForGameWonThisFrame = false } -void function AddTeamScore( int team, int amount ) -{ - GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + amount ) - GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + amount ) - - int scoreLimit - if ( IsRoundBased() ) - scoreLimit = GameMode_GetRoundScoreLimit( GAMETYPE ) - else - scoreLimit = GameMode_GetScoreLimit( GAMETYPE ) - - int score = GameRules_GetTeamScore( team ) - if ( score >= scoreLimit || GetGameState() == eGameState.SuddenDeath ) - SetWinner( team ) - else if ( ( file.switchSidesBased && !file.hasSwitchedSides ) && score >= ( scoreLimit.tofloat() / 2.0 ) ) - SetGameState( eGameState.SwitchingSides ) -} - -void function SetTimeoutWinnerDecisionFunc( int functionref() callback ) -{ - file.timeoutWinnerDecisionFunc = callback -} - int function GetWinningTeamWithFFASupport() { if ( !IsFFAGame() ) @@ -936,8 +1146,6 @@ int function GetWinningTeamWithFFASupport() unreachable } -// idk - float function GameState_GetTimeLimitOverride() { return 100 @@ -967,8 +1175,6 @@ float function GetTimeLimit_ForGameMode() return GetCurrentPlaylistVarFloat( playlistString, 10 ) } -// faction dialogue - void function DialoguePlayNormal() { int totalScore = GameMode_GetScoreLimit( GameRules_GetGameMode() ) @@ -1052,24 +1258,4 @@ void function DialoguePlayWinnerDetermined() PlayFactionDialogueToTeam( "scoring_won", winningTeam ) PlayFactionDialogueToTeam( "scoring_lost", losingTeam ) } -} - -/// This is to move all NPCs that a player owns from one team to the other during a match -/// Auto-Titans, Turrets, Ticks and Hacked Spectres will all move along together with the player to the new Team -/// Also possibly prevents mods that spawns other types of NPCs that players can own from breaking when switching (i.e Drones, Hacked Reapers) -void function OnPlayerChangedTeam( entity player ) -{ - if ( !player.hasConnected ) // Prevents players who just joined to trigger below code, as server always pre setups their teams - return - - // only TEAM_IMC and TEAM_MILITIA can use function GetOtherTeam(), doesnt need to notify them when player got a special team - if( IsIMCOrMilitiaTeam( player.GetTeam() ) ) - NotifyClientsOfTeamChange( player, GetOtherTeam( player.GetTeam() ), player.GetTeam() ) - - foreach( npc in GetNPCArray() ) - { - entity bossPlayer = npc.GetBossPlayer() - if ( IsValidPlayer( bossPlayer ) && bossPlayer == player && IsAlive( npc ) ) - SetTeam( npc, player.GetTeam() ) - } -} +} \ No newline at end of file -- cgit v1.2.3