aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack <66967891+ASpoonPlaysGames@users.noreply.github.com>2023-09-02 17:52:06 +0100
committerGitHub <noreply@github.com>2023-09-02 18:52:06 +0200
commit1d95b7d5f3a4b7176456c94b147f0382de04f18e (patch)
tree1678c273b68985fe54cde59154a37a47b89bf419
parentc7e50b28fe31e20b064f812758d8a0f5e889ea74 (diff)
downloadNorthstarMods-1d95b7d5f3a4b7176456c94b147f0382de04f18e.tar.gz
NorthstarMods-1d95b7d5f3a4b7176456c94b147f0382de04f18e.zip
Progression system (#655)
This also can't hurt right? --------- Co-authored-by: uniboi <64006268+uniboi@users.noreply.github.com>
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_english.txt18
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut53
-rw-r--r--Northstar.CustomServers/mod.json18
-rw-r--r--Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg5
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_items.nut8
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut39
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut45
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut3
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut12
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut9
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut4
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut11
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut1043
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut4
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut1091
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut6
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut3
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut5
21 files changed, 2337 insertions, 46 deletions
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt
index b2608331d..8c7bab3a7 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt
@@ -343,5 +343,23 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a
"MOD_SETTINGS_RESET_ALL" "Reset All"
"NO_RESULTS" "No results."
"NO_MODS" "No settings available! Install more mods at ^5588FF00northstar.thunderstore.io^0."
+
+ // Toggleable progression
+ "TOGGLE_PROGRESSION" "Toggle Progression"
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON]% Toggle Progression"
+
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "Disable Progression?"
+ "PROGRESSION_TOGGLE_ENABLED_BODY" "Titans, Weapons, Factions, Skins, etc. will all be unlocked and usable at any time.\n\nThis can be changed at any time in the multiplayer lobby."
+
+ "PROGRESSION_TOGGLE_DISABLED_HEADER" "Enable Progression?"
+ "PROGRESSION_TOGGLE_DISABLED_BODY" "Titans, Weapons, Factions, Skins, etc. will need to be unlocked by levelling up, or bought with Merits.\n\nThis can be changed at any time in the multiplayer lobby.\n\n^CC000000Warning: if you have currently equipped any items that you do not have unlocked, they will be reset!"
+
+ "PROGRESSION_ENABLED_HEADER" "Progression Enabled!"
+ "PROGRESSION_ENABLED_BODY" "^CCCC0000Progression has been enabled.^\n\nTitans, Weapons, Factions, Skins, etc. will need to be unlocked by levelling up, or bought with Merits.\n\nThis can be changed at any time in the multiplayer lobby."
+
+ "PROGRESSION_DISABLED_HEADER" "Progression Disabled!"
+ "PROGRESSION_DISABLED_BODY" "^CCCC0000Progression has been disabled.^\n\nTitans, Weapons, Factions, Skins, etc. will all be unlocked and usable at any time.\n\nThis can be changed at any time in the multiplayer lobby."
+
+ "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000Progression can now be enabled!^\n\nNorthstar now supports vanilla progression, meaning you can choose to unlock Weapons, Skins, Titans, etc. through levelling up and completing challenges.\n\nYou can enable progression using the button at the bottom of the lobby screen.\n\nThis can be changed at any time."
}
}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut
index 2bef0e205..e4cc56874 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut
@@ -178,6 +178,8 @@ void function InitLobbyMenu()
AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
AddMenuFooterOption( menu, BUTTON_BACK, "#BACK_BUTTON_POSTGAME_REPORT", "#POSTGAME_REPORT", OpenPostGameMenu, IsPostGameMenuValid )
AddMenuFooterOption( menu, BUTTON_TRIGGER_RIGHT, "#R_TRIGGER_CHAT", "", null, IsVoiceChatPushToTalk )
+ // Client side progression toggle
+ AddMenuFooterOption( menu, BUTTON_Y, "#Y_BUTTON_TOGGLE_PROGRESSION", "#TOGGLE_PROGRESSION", ShowToggleProgressionDialog )
InitChatroom( menu )
@@ -226,6 +228,57 @@ void function InitLobbyMenu()
RegisterSignal( "LeaveParty" )
}
+void function ShowToggleProgressionDialog( var button )
+{
+ bool enabled = Progression_GetPreference()
+
+ DialogData dialogData
+ dialogData.menu = GetMenu( "AnnouncementDialog" )
+ dialogData.header = enabled ? "#PROGRESSION_TOGGLE_ENABLED_HEADER" : "#PROGRESSION_TOGGLE_DISABLED_HEADER"
+ dialogData.message = enabled ? "#PROGRESSION_TOGGLE_ENABLED_BODY" : "#PROGRESSION_TOGGLE_DISABLED_BODY"
+ dialogData.image = $"ui/menu/common/dialog_announcement_1"
+
+ AddDialogButton( dialogData, "#NO" )
+ AddDialogButton( dialogData, "#YES", enabled ? DisableProgression : EnableProgression )
+
+ OpenDialog( dialogData )
+}
+
+void function EnableProgression()
+{
+ Progression_SetPreference( true )
+
+ // update the cache just in case something changed
+ UpdateCachedLoadouts_Delayed()
+
+ DialogData dialogData
+ dialogData.menu = GetMenu( "AnnouncementDialog" )
+ dialogData.header = "#PROGRESSION_ENABLED_HEADER"
+ dialogData.message = "#PROGRESSION_ENABLED_BODY"
+ dialogData.image = $"ui/menu/common/dialog_announcement_1"
+
+ AddDialogButton( dialogData, "#OK" )
+
+ EmitUISound( "UI_Menu_Item_Purchased_Stinger" )
+
+ OpenDialog( dialogData )
+}
+
+void function DisableProgression()
+{
+ Progression_SetPreference( false )
+
+ DialogData dialogData
+ dialogData.menu = GetMenu( "AnnouncementDialog" )
+ dialogData.header = "#PROGRESSION_DISABLED_HEADER"
+ dialogData.message = "#PROGRESSION_DISABLED_BODY"
+ dialogData.image = $"ui/menu/common/dialog_announcement_1"
+
+ AddDialogButton( dialogData, "#OK" )
+
+ OpenDialog( dialogData )
+}
+
void function SetupComboButtonTest( var menu )
{
ComboStruct comboStruct = ComboButtons_Create( menu )
diff --git a/Northstar.CustomServers/mod.json b/Northstar.CustomServers/mod.json
index 4f19307d2..8f5508c82 100644
--- a/Northstar.CustomServers/mod.json
+++ b/Northstar.CustomServers/mod.json
@@ -53,6 +53,11 @@
{
"Name": "ns_allow_kill_commands",
"DefaultValue": "0"
+ },
+ {
+ "Name": "ns_progression_enabled",
+ "DefaultValue": "0",
+ "Flags": "ARCHIVE_PLAYERPROFILE"
}
],
"Scripts": [
@@ -150,6 +155,19 @@
"ServerCallback": {
"After": "AiTurretSentry_Init"
}
+ },
+ {
+ "Path": "sh_progression.nut",
+ "RunOn": "UI || SERVER || CLIENT",
+ "ServerCallback": {
+ "Before": "Progression_Init"
+ },
+ "ClientCallback": {
+ "Before": "Progression_Init"
+ },
+ "UICallback": {
+ "Before": "Progression_Init"
+ }
}
]
}
diff --git a/Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg b/Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg
index f28f601b2..bd4227835 100644
--- a/Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg
+++ b/Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg
@@ -21,4 +21,7 @@ sv_updaterate_mp 20 // default updaterate: 20 tick
sv_minupdaterate 20 // unsure if this actually works, but if it does, should set minimum client updaterate
sv_max_snapshots_multiplayer 300 // this needs to be updaterate * 15, or clients will dc in killreplay
net_data_block_enabled 0 // not really sure on this, have heard datablock could have security issues? doesn't seem to have any adverse effects being disabled
-host_skip_client_dll_crc 1 // allow people to run modded client dlls, this is mainly so people running pilot visor colour mods can keep those, since they use a client.dll edit \ No newline at end of file
+host_skip_client_dll_crc 1 // allow people to run modded client dlls, this is mainly so people running pilot visor colour mods can keep those, since they use a client.dll edit
+
+announcementVersion 1
+announcement #PROGRESSION_ANNOUNCEMENT_BODY \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
index 539b72bc2..5878da134 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
@@ -5698,7 +5698,7 @@ bool function IsUnlockValid( string ref, string parentRef = "" )
bool function IsSubItemLocked( entity player, string ref, string parentRef )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return false
if ( IsItemInEntitlementUnlock( ref, parentRef ) )
@@ -5817,7 +5817,7 @@ bool function IsSubItemLocked( entity player, string ref, string parentRef )
bool function IsItemLocked( entity player, string ref )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return false
if ( IsItemInEntitlementUnlock( ref ) )
@@ -5906,7 +5906,7 @@ bool function IsItemLockedForEntitlement( entity player, string ref, string pare
bool function IsSubItemOwned( entity player, string ref, string parentRef )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return false
Assert( IsValid( player ) )
@@ -5990,7 +5990,7 @@ bool function IsSubItemOwned( entity player, string ref, string parentRef )
bool function IsItemOwned( entity player, string ref )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return false
Assert( IsValid( player ) )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut
index 1092bf2d8..6499faa2e 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut
@@ -65,6 +65,8 @@ bool function ClientCommandCallback_GenUp( entity player, array<string> args )
player.GenChanged()
player.XPChanged()
}
+
+ RegenPersistentLoadouts(player)
return true
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
index 6f044b7ac..2b95d1a8c 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
@@ -1,6 +1,7 @@
global function SvXP_Init
global function PlayerProgressionAllowed
global function HandleXPGainForScoreEvent
+global function AddXP
void function SvXP_Init()
{
@@ -29,46 +30,38 @@ bool function PlayerProgressionAllowed( entity player )
void function HandleXPGainForScoreEvent( entity player, ScoreEvent event )
{
// note: obviously all xp stuff can be cheated in if people want to on customs, this is mainly just here for fun for those who want it and feature completeness
- // most score events don't have this, so we'll set this to the xp value of other categories later if needed
+
int xpValue = ScoreEvent_GetXPValue( event )
int weaponXp = ScoreEvent_GetXPValueWeapon( event )
int titanXp = ScoreEvent_GetXPValueTitan( event )
-
- if ( xpValue < weaponXp )
- xpValue = weaponXp
- else if ( xpValue < titanXp )
- xpValue = titanXp
+ int factionXp = ScoreEvent_GetXPValueFaction( event )
entity weapon = player.GetActiveWeapon()
- if ( IsValid( weapon ) && ShouldTrackXPForWeapon( weapon.GetWeaponClassName() ) )
- AddWeaponXP( player, xpValue )
+ if ( IsValid( weapon ) && ShouldTrackXPForWeapon( weapon.GetWeaponClassName() ) && weaponXp != 0 )
+ AddWeaponXP( player, weaponXp )
// if we specifically gain titan xp, then give titan xp no matter what, otherwise only give it when we're in a titan
- if ( titanXp != 0 || player.IsTitan() )
- AddTitanXP( player, xpValue )
-
- // most events don't have faction xp but almost everything should give it
- int factionXp = ScoreEvent_GetXPValueFaction( event )
- if ( xpValue > factionXp )
- factionXp = xpValue
- else if ( xpValue < factionXp )
- xpValue = factionXp
+ if ( titanXp != 0 )
+ AddTitanXP( player, titanXp )
if ( factionXp != 0 )
AddFactionXP( player, factionXp )
- if ( xpValue == 0 )
- return
-
// global xp
+ if ( xpValue != 0 )
+ AddXP( player, xpValue )
+}
+
+void function AddXP( entity player, int amount )
+{
int oldXp = player.GetPersistentVarAsInt( "xp" )
- if(oldXp<0) oldXp = 0
+ if( oldXp < 0 ) oldXp = 0
int oldLevel = GetLevelForXP( oldXp )
- player.SetPersistentVar( "xp", min( oldXp + xpValue, PlayerGetMaxXPPerGen() ) )
+ player.SetPersistentVar( "xp", min( oldXp + amount, PlayerGetMaxXPPerGen() ) )
player.XPChanged() // network xp change to client, gen can't change here
int newXp = player.GetPersistentVarAsInt( "xp" )
int newLevel = GetLevelForXP( newXp )
if ( newLevel != oldLevel )
Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerLeveledUp", player.GetPersistentVarAsInt( "gen" ), newLevel )
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
index 5821d0150..56750352f 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
@@ -265,6 +265,8 @@ void function UseBurnCardWeapon( entity weapon, entity player )
PlayerEarnMeter_SetRewardUsed( player )
thread PlayerInventory_PopInventoryItem( player )
+
+ UpdatePlayerStat( player, "misc_stats", "boostsActivated" )
}
void function UseBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
index f23c841db..af074689c 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
@@ -422,6 +422,40 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
foreach ( entity otherPlayer in GetPlayerArray() )
Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_EvacObit", player.GetEncodedEHandle() )
}
+
+ // award player score to evacing team
+ int evacCount = 0
+ array<entity> evacingPlayers = GetPlayerArrayOfTeam( dropship.GetTeam() ) // all players that are supposed to evac in the dropship
+
+ // count how many players are in the dropship
+ foreach ( entity player in evacingPlayers )
+ {
+ if ( !PlayerInDropship( player, dropship ) )
+ continue
+
+ evacCount++
+ }
+
+ bool allEvac = evacCount == evacingPlayers.len()
+
+ foreach(entity player in evacingPlayers)
+ {
+ if ( !PlayerInDropship( player, dropship ) )
+ continue
+
+ AddPlayerScore( player, "HotZoneExtract" )
+ UpdatePlayerStat( player, "misc_stats", "evacsSurvived" )
+
+ if ( allEvac )
+ AddPlayerScore( player, "TeamBonusFullEvac" )
+ }
+
+ // sole survivor (but not the only one on the team)
+ if ( evacCount == 1 && !allEvac )
+ {
+ // we can assume there is one player in the array because otherwise evacCount wouldn't be 1
+ AddPlayerScore( evacingPlayers[0], "SoleSurvivor" )
+ }
}
void function AddPlayerToEvacDropship( entity dropship, entity player )
@@ -442,6 +476,8 @@ void function AddPlayerToEvacDropship( entity dropship, entity player )
if ( !PlayerInDropship( player, dropship ) )
return
+ UpdatePlayerStat( player, "misc_stats", "evacsAttempted" )
+
// need to cancel if the dropship dies
dropship.EndSignal( "OnDeath", "OnDestroy" )
@@ -507,6 +543,15 @@ void function CheckIfAnyPlayerLeft( int evacTeam )
SetTeamActiveObjective( evacTeam, "EG_DropshipExtractEvacPlayersKilled" )
SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtractEvacPlayersKilled" )
thread EvacEpilogueCompleted( null )
+
+ // score for killing the entire evacing team
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( player.GetTeam() == evacTeam )
+ continue
+
+ AddPlayerScore( player, "TeamBonusKilledAll")
+ }
}
)
while( true )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut
index 5fd7d1014..6555c875b 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut
@@ -3,8 +3,11 @@ global function AddFactionXP
void function AddFactionXP( entity player, int amount )
{
string faction = GetFactionChoice( player )
+ int oldLevel = FactionGetLevel( player, faction )
// 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" )
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
index ae933b713..8b65ec935 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
@@ -14,6 +14,7 @@ void function Lobby_Init()
{
// non-private lobby clientcommands
AddClientCommandCallback( "StartPrivateMatchSearch", ClientCommandCallback_StartPrivateMatchSearch )
+ AddClientCommandCallback( "SetAnnouncementVersionSeen", ClientCommandCallback_SetAnnouncementVersionSeen )
}
}
@@ -37,3 +38,14 @@ bool function ClientCommandCallback_StartPrivateMatchSearch( entity player, arra
return true
}
+
+bool function ClientCommandCallback_SetAnnouncementVersionSeen( entity player, array<string> args )
+{
+ if ( args.len() < 1 )
+ return false
+
+ int version = int( args[0] )
+
+ player.SetPersistentVar( "announcementVersionSeen", version )
+ return true
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
index 3bd0cd690..bab7eaede 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
@@ -134,6 +134,15 @@ void function CodeCallback_OnClientConnectionCompleted( entity player )
Lobby_OnClientConnectionCompleted( player )
return
}
+ else if ( !IsFDMode( GAMETYPE ) )
+ {
+ // reset this for non-fd modes
+ // for some reason the postgame scoreboard uses this to
+ // determine if it should show the FD aegis rank one
+ // FD should either set this in their own mode, or add an else
+ // to this if statement when it releases
+ player.SetPersistentVar( "lastFDTitanRef", "" )
+ }
player.hasConnected = true
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut
index 16a3ce922..0ababfc71 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut
@@ -58,7 +58,9 @@ void function CodeCallback_MatchIsOver()
#if MP
void function PopulatePostgameData()
{
- // something's busted here because this isn't showing automatically on match end, ag
+ // show the postgame scoreboard summary
+ SetUIVar( level, "showGameSummary", true )
+
foreach ( entity player in GetPlayerArray() )
{
int teams = GetCurrentPlaylistVarInt( "max_teams", 2 )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
index 46b39ebc1..3426cec52 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
@@ -800,6 +800,8 @@ void function SetWinner( int team, string winningReason = "", string losingReaso
}
else
SetGameState( eGameState.WinnerDetermined )
+
+ ScoreEvent_MatchComplete( team )
}
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut
index dacd43b0f..0b55e9ffd 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut
@@ -7,6 +7,7 @@ global function ScoreEvent_PlayerKilled
global function ScoreEvent_TitanDoomed
global function ScoreEvent_TitanKilled
global function ScoreEvent_NPCKilled
+global function ScoreEvent_MatchComplete
global function ScoreEvent_SetEarnMeterValues
global function ScoreEvent_SetupEarnMeterValuesForMixedModes
@@ -230,7 +231,15 @@ void function ScoreEvent_NPCKilled( entity victim, entity attacker, var damageIn
catch ( ex ) {}
}
-
+void function ScoreEvent_MatchComplete( int winningTeam )
+{
+ foreach( entity player in GetPlayerArray() )
+ {
+ AddPlayerScore( player, "MatchComplete" )
+ if ( player.GetTeam() == winningTeam )
+ AddPlayerScore( player, "MatchVictory" )
+ }
+}
void function ScoreEvent_SetEarnMeterValues( string eventName, float earned, float owned, float coreScale = 1.0 )
{
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut
index 0e8b58f45..92c0f4014 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut
@@ -1,3 +1,5 @@
+untyped // because entity.s
+
global function Stats_Init
global function AddStatCallback
global function Stats_SaveStatDelayed
@@ -12,67 +14,1084 @@ global function PreScoreEventUpdateStats
global function PostScoreEventUpdateStats
global function Stats_OnPlayerDidDamage
+struct {
+ table< string, array<string> > refs
+ table< string, array< void functionref( entity, float, string ) > > callbacks
+
+ table< entity, table< string, int > > cachedIntStatChanges
+ table< table< string, float > > cachedFloatStatChanges
+
+ table< entity, float > playerKills
+ table< entity, float > playerKillsPvp
+ table< entity, float > playerDeaths
+ table< entity, float > playerDeathsPvp
+
+ bool isFirstStrike = true
+} file
+
void function Stats_Init()
{
+ AddCallback_OnPlayerKilled( OnPlayerOrNPCKilled )
+ AddCallback_OnNPCKilled( OnPlayerOrNPCKilled )
+ AddCallback_OnPlayerRespawned( OnPlayerRespawned )
+ AddCallback_OnClientConnected( OnClientConnected )
+ AddCallback_OnClientDisconnected( OnClientDisconnected )
+ AddCallback_GameStateEnter( eGameState.Epilogue, OnEpilogueStarted )
+ thread HandleDistanceAndTimeStats_Threaded()
+ thread SaveStatsPeriodically_Threaded()
}
-void function AddStatCallback(string statCategory, string statAlias, string statSubAlias, void functionref(entity, float, string) callback, string subRef)
+void function AddStatCallback( string statCategory, string statAlias, string statSubAlias, void functionref( entity, float, string ) callback, string subRef )
{
+ if ( !IsValidStat( statCategory, statAlias, statSubAlias ) )
+ throw format( "INVALID STAT: %s : %s : %s", statCategory, statAlias, statSubAlias )
+
+
+ string statVar = GetStatVar( statCategory, statAlias, statSubAlias )
+ if ( statVar in file.refs )
+ {
+ file.refs[ statVar ].append( subRef )
+ file.callbacks[ statVar ].append( callback )
+ }
+ else
+ {
+ file.refs[ statVar ] <- [ subRef ]
+ file.callbacks[ statVar ] <- [ callback ]
+ }
}
-void function Stats_SaveStatDelayed(entity player, string statCategory, string statAlias, string statSubAlias)
+// a lot of this file seems to be doing caching of stats in some way
+void function Stats_SaveStatDelayed( entity player, string statCategory, string statAlias, string statSubAlias, float delay = 0.1 )
{
+ // idk how long the delay is meant to be but whatever
+ wait delay
+
+ if ( !IsValid( player ) )
+ return
+
+ Stats_SaveStat( player, statCategory, statAlias, statSubAlias )
+}
+
+void function Stats_SaveAllStats( entity player )
+{
+ if ( player in file.cachedIntStatChanges )
+ {
+ foreach( string key, int val in file.cachedIntStatChanges[ player ] )
+ {
+ player.SetPersistentVar( key, player.GetPersistentVarAsInt( key ) + val )
+ }
+
+ delete file.cachedIntStatChanges[ player ]
+ }
+ // save cached float stat change
+ if ( player in file.cachedFloatStatChanges )
+ {
+ foreach( string key, float val in file.cachedFloatStatChanges[ player ] )
+ {
+ player.SetPersistentVar( key, expect float( player.GetPersistentVar( key ) ) + val )
+ }
+ delete file.cachedFloatStatChanges[ player ]
+ }
}
-int function PlayerStat_GetCurrentInt(entity player, string statCategory, string statAlias, string statSubAlias)
+void function Stats_SaveStat( entity player, string statCategory, string statAlias, string statSubAlias )
{
+ string stat = GetStatVar( statCategory, statAlias, statSubAlias )
+ // save cached int stat change
+ if ( player in file.cachedIntStatChanges && stat in file.cachedIntStatChanges[ player ] )
+ {
+ player.SetPersistentVar( stat, player.GetPersistentVarAsInt( stat ) + file.cachedIntStatChanges[ player ][ stat ] )
+ delete file.cachedIntStatChanges[ player ][ stat ]
+ return
+ }
+ // save cached float stat change
+ if ( player in file.cachedFloatStatChanges && stat in file.cachedFloatStatChanges[ player ] )
+ {
+ player.SetPersistentVar( stat, expect float( player.GetPersistentVar( stat ) ) + file.cachedFloatStatChanges[ player ][ stat ] )
+ delete file.cachedFloatStatChanges[ player ][ stat ]
+ return
+ }
+}
+
+// this gets the cached change, not the actual value
+int function PlayerStat_GetCurrentInt( entity player, string statCategory, string statAlias, string statSubAlias )
+{
+ string str = GetStatVar( statCategory, statAlias, statSubAlias )
+
+ if ( player in file.cachedIntStatChanges && str in file.cachedIntStatChanges[ player ] )
+ return file.cachedIntStatChanges[ player ][ str ]
return 0
}
-float function PlayerStat_GetCurrentFloat(entity player, string statCategory, string statAlias, string statSubAlias)
+// this gets the cached change, not the actual value
+float function PlayerStat_GetCurrentFloat( entity player, string statCategory, string statAlias, string statSubAlias )
{
+ string str = GetStatVar( statCategory, statAlias, statSubAlias )
+
+ if ( player in file.cachedFloatStatChanges && str in file.cachedFloatStatChanges[ player ] )
+ return file.cachedFloatStatChanges[ player ][ str ]
return 0
}
-void function UpdatePlayerStat(entity player, string statCategory, string subStat, int count = 0)
+void function UpdatePlayerStat( entity player, string statCategory, string subStat, int count = 1 )
{
+ if ( !IsValid( player ) )
+ return
+
+ Stats_IncrementStat( player, statCategory, subStat, "", count.tofloat() )
+}
+
+void function IncrementPlayerDidPilotExecutionWhileCloaked( entity player )
+{
+ UpdatePlayerStat( player, "kills_stats", "pilotExecutePilotWhileCloaked" )
+}
+
+void function UpdateTitanDamageStat( entity attacker, float savedDamage, var damageInfo )
+{
+ if ( !IsValid( attacker ) )
+ return
+
+ Stats_IncrementStat( attacker, "titan_stats", "titanDamage", GetTitanCharacterName( attacker ), savedDamage )
+}
+
+void function UpdateTitanWeaponDamageStat( entity attacker, float savedDamage, var damageInfo )
+{
+ if ( !IsValid( attacker ) )
+ return
+
+ string weaponName = GetPersistenceRefFromDamageInfo( damageInfo )
+ if ( weaponName == "" )
+ return
+
+ Stats_IncrementStat( attacker, "weapon_stats", "titanDamage", weaponName, savedDamage )
+}
+
+void function UpdateTitanCoreEarnedStat( entity player, entity titan, int count = 1 )
+{
+ if ( !IsValid( player ) )
+ return
+
+ if ( !IsValid( titan ) )
+ return
+
+ Stats_IncrementStat( player, "titan_stats", "coresEarned", GetTitanCharacterName( titan ), count.tofloat() )
+}
+
+void function PreScoreEventUpdateStats( entity attacker, entity ent )
+{
+ // used to track kill streaks ending i think ( that stuff gets reset during score event update )
+}
+
+void function PostScoreEventUpdateStats( entity attacker, entity ent )
+{
+ if ( !attacker.IsPlayer() )
+ return
+ // used to track kill streaks starting maybe
+ if ( attacker.s.currentKillstreak == KILLINGSPREE_KILL_REQUIREMENT )
+ {
+ // killingSpressAs_<chassis>
+ if ( attacker.IsTitan() )
+ Stats_IncrementStat( attacker, "kills_stats", "killingSpressAs_" + GetTitanCharacterName( attacker ), "", 1.0 )
+
+ entity weapon = attacker.GetActiveWeapon()
+ // I guess if you dont have a valid active weapon when you get awarded a killing spree
+ // you dont get the stat. Too bad!
+ if ( !IsValid( weapon ) )
+ return
+ Stats_IncrementStat( attacker, "weapon_kill_stats", "killingSprees", weapon.GetWeaponClassName(), 1.0 )
+ }
+}
+
+void function Stats_OnPlayerDidDamage( entity victim, var damageInfo )
+{
+ // try and get the player
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ // get the player from their titan
+ if ( attacker.IsTitan() && IsPetTitan( attacker ) )
+ attacker = attacker.GetTitanSoul().GetBossPlayer()
+
+ if ( !attacker.IsPlayer() )
+ return
+
+ string weaponName = GetPersistenceRefFromDamageInfo( damageInfo )
+ if ( weaponName == "" )
+ return
+
+ Stats_IncrementStat( attacker, "weapon_stats", "shotsHit", weaponName, 1.0 )
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_CRITICAL )
+ Stats_IncrementStat( attacker, "weapon_stats", "critHits", weaponName, 1.0 )
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_HEADSHOT )
+ Stats_IncrementStat( attacker, "weapon_stats", "headshots", weaponName, 1.0 )
+}
+
+void function Stats_IncrementStat( entity player, string statCategory, string statAlias, string statSubAlias, float amount )
+{
+ if ( !IsValidStat( statCategory, statAlias, statSubAlias ) )
+ {
+ printt( "invalid stat: " + statCategory + " : " + statAlias + " : " + statSubAlias )
+ return
+ }
+
+ string str = GetStatVar( statCategory, statAlias, statSubAlias )
+ int type = GetStatVarType( statCategory, statAlias, statSubAlias )
+
+ // stupid exception because respawn set this up as an int in script
+ // but it is actually a float, so the game will crash if we don't fix it somewhere
+ // i dont feel like committing all of sh_stats.gnut so im doing this instead
+ if ( str == "mapStats[%mapname%].hoursPlayed[%gamemode%]" )
+ type = ePlayerStatType.FLOAT
+
+ // this is rather hacky
+ string mode = GAMETYPE
+ int difficulty = GetDifficultyLevel()
+ if ( difficulty >= 5 )
+ return
+
+ string saveVar = Stats_GetFixedSaveVar( str, GetMapName(), mode, difficulty.tostring() )
+ // check if the map and mode exist in persistence
+ try
+ {
+ PersistenceGetEnumIndexForItemName( "gamemodes", mode )
+ PersistenceGetEnumIndexForItemName( "maps", GetMapName() )
+ }
+ catch( ex )
+ {
+ // if we have an invalid mode or map for persistence, and it is used in the
+ // persistence string, we can't save the persistence so we have to just return
+ if ( str != saveVar )
+ {
+ printt( ex )
+ return
+ }
+ }
+ str = saveVar
+
+ switch ( type )
+ {
+ case ePlayerStatType.INT:
+ // populate table if needed
+ if ( !( player in file.cachedIntStatChanges ) )
+ file.cachedIntStatChanges[ player ] <- {}
+ if ( !( str in file.cachedIntStatChanges[ player ] ) )
+ file.cachedIntStatChanges[ player ][ str ] <- 0
+
+ file.cachedIntStatChanges[ player ][ str ] += amount.tointeger()
+ break
+ case ePlayerStatType.FLOAT:
+ // populate table if needed
+ if ( !( player in file.cachedFloatStatChanges ) )
+ file.cachedFloatStatChanges[ player ] <- {}
+ if ( !( str in file.cachedFloatStatChanges[ player ] ) )
+ file.cachedFloatStatChanges[ player ][ str ] <- 0.0
+
+ file.cachedFloatStatChanges[ player ][ str ] += amount
+ break
+ default:
+ throw "UNIMPLEMENTED STAT TYPE: " + type
+ }
+
+ // amount here is never used
+ Stats_RunCallbacks( str, player, amount )
+}
+
+void function Stats_RunCallbacks( string statVar, entity player, float change )
+{
+ if ( !( statVar in file.refs ) )
+ return
+
+ for( int i = 0; i < file.refs[ statVar ].len(); i++ )
+ {
+ string ref = file.refs[ statVar ][ i ]
+ void functionref( entity, float, string ) callback = file.callbacks[ statVar ][ i ]
+
+ callback( player, change, ref )
+ }
+}
+
+void function OnClientConnected( entity player )
+{
+ Stats_IncrementStat( player, "game_stats", "game_joined", "", 1.0 )
+}
+
+void function OnClientDisconnected( entity player )
+{
+ Stats_SaveAllStats( player )
+ // maybe we can save this stuff, but idk if we can access persistence in this callback
+ if ( player in file.cachedIntStatChanges )
+ delete file.cachedIntStatChanges[ player ]
+
+ if ( player in file.cachedFloatStatChanges )
+ delete file.cachedFloatStatChanges[ player ]
+}
+
+void function OnPlayerOrNPCKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim.IsPlayer() )
+ thread SetLastPosForDistanceStatValid_Threaded( victim, false )
+
+ HandleDeathStats( victim, attacker, damageInfo )
+ HandleKillStats( victim, attacker, damageInfo )
+ HandleWeaponKillStats( victim, attacker, damageInfo )
+ HandleTitanStats( victim, attacker, damageInfo )
+}
+
+void function HandleDeathStats( entity player, entity attacker, var damageInfo )
+{
+ if ( !IsValid( player ) || !player.IsPlayer() )
+ return
+
+ if ( player in file.playerDeaths )
+ file.playerDeaths[ player ]++
+ else
+ file.playerDeaths[ player ] <- 1.0
+ // total
+ Stats_IncrementStat( player, "deaths_stats", "total", "", 1.0 )
+
+ // these all rely on the attacker being valid
+ if ( IsValid( attacker ) )
+ {
+ // totalPVP
+ // note: I'm not sure if owned entities count towards totalPVP
+ // such as auto-titans, turrets, etc.
+ if ( attacker.IsPlayer() || attacker.GetBossPlayer() )
+ {
+ if ( player in file.playerDeathsPvp )
+ file.playerDeathsPvp[ player ]++
+ else
+ file.playerDeathsPvp[ player ] <- 1.0
+ Stats_IncrementStat( player, "deaths_stats", "totalPVP", "", 1.0 )
+ }
+
+ // byPilots
+ if ( IsPilot( attacker ) )
+ Stats_IncrementStat( player, "deaths_stats", "byPilots", "", 1.0 )
+
+ // byTitan_<chassis>
+ if ( attacker.IsTitan() && attacker.IsPlayer() )
+ Stats_IncrementStat( player, "deaths_stats", "byTitan_" + GetTitanCharacterName( attacker ), "", 1.0 )
+
+ // bySpectres
+ if ( IsSpectre( attacker ) )
+ Stats_IncrementStat( player, "deaths_stats", "bySpectres", "", 1.0 )
+
+ // byGrunts
+ if ( IsGrunt( attacker ) )
+ Stats_IncrementStat( player, "deaths_stats", "byGrunts", "", 1.0 )
+
+ // byNPCTitans_<chassis>
+ if ( attacker.IsTitan() && attacker.IsNPC() )
+ Stats_IncrementStat( player, "deaths_stats", "byNPCTitans_" + GetTitanCharacterName( attacker ), "", 1.0 )
+ }
+
+ // asPilot
+ if ( IsPilot( player ) )
+ Stats_IncrementStat( player, "deaths_stats", "asPilot", "", 1.0 )
+
+ // asTitan_<chassis>
+ if ( player.IsTitan() )
+ Stats_IncrementStat( player, "deaths_stats", "asTitan_" + GetTitanCharacterName( player ), "", 1.0 )
+
+ // suicides
+ if ( IsSuicide( attacker, player, DamageInfo_GetDamageSourceIdentifier( damageInfo ) ) )
+ Stats_IncrementStat( player, "deaths_stats", "suicides", "", 1.0 )
+
+ // whileEjecting
+ if ( player.p.pilotEjecting )
+ Stats_IncrementStat( player, "deaths_stats", "whileEjecting", "", 1.0 )
+}
+
+void function HandleWeaponKillStats( entity victim, entity attacker, var damageInfo )
+{
+ if ( !IsValid( attacker ) )
+ return
+
+ // get the player and it's pet titan
+ entity player
+ entity playerPetTitan
+ if ( attacker.IsPlayer() )
+ {
+ // the player is just the attacker
+ player = attacker
+ playerPetTitan = player.GetPetTitan()
+ }
+ else if ( attacker.IsTitan() && IsPetTitan( attacker ) )
+ {
+ // the attacker is the player's auto titan
+ player = attacker.GetTitanSoul().GetBossPlayer()
+ playerPetTitan = attacker
+ }
+ else
+ {
+ // attacker could be something like an NPC, or worldspawn
+ return
+ }
+
+ string damageSourceStr = GetPersistenceRefFromDamageInfo( damageInfo )
+ // cant do weapon stats for no weapon
+ if ( damageSourceStr == "" )
+ return
+
+ // check things once, for performance
+ int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ bool victimIsPlayer = victim.IsPlayer()
+ bool victimIsNPC = victim.IsNPC()
+ bool victimIsPilot = IsPilot( victim )
+ bool victimIsTitan = victim.IsTitan()
+
+ // total
+ Stats_IncrementStat( player, "weapon_kill_stats", "total", damageSourceStr, 1.0 )
+
+ // pilots
+ if ( victimIsPilot )
+ Stats_IncrementStat( player, "weapon_kill_stats", "pilots", damageSourceStr, 1.0 )
+
+ // ejecting_pilots
+ if ( victimIsPilot && victim.p.pilotEjecting )
+ Stats_IncrementStat( player, "weapon_kill_stats", "ejecting_pilots", damageSourceStr, 1.0 )
+
+ // titansTotal
+ if ( victimIsTitan )
+ Stats_IncrementStat( player, "weapon_kill_stats", "titansTotal", damageSourceStr, 1.0 )
+ // spectres
+ if ( IsSpectre( victim ) )
+ Stats_IncrementStat( player, "weapon_kill_stats", "spectres", damageSourceStr, 1.0 )
+
+ // marvins
+ if ( IsMarvin( victim ) )
+ Stats_IncrementStat( player, "weapon_kill_stats", "marvins", damageSourceStr, 1.0 )
+
+ // grunts
+ if ( IsGrunt( victim ) )
+ Stats_IncrementStat( player, "weapon_kill_stats", "grunts", damageSourceStr, 1.0 )
+
+ // ai
+ if ( victimIsNPC )
+ Stats_IncrementStat( player, "weapon_kill_stats", "ai", damageSourceStr, 1.0 )
+
+ // titans_<chassis>
+ if ( victimIsPlayer && victimIsTitan )
+ Stats_IncrementStat( player, "weapon_kill_stats", "titans_" + GetTitanCharacterName( victim ), damageSourceStr, 1.0 )
+
+ // npcTitans_<chassis>
+ if ( victimIsNPC && victimIsTitan )
+ Stats_IncrementStat( player, "weapon_kill_stats", "npcTitans_" + GetTitanCharacterName( victim ), damageSourceStr, 1.0 )
}
-void function IncrementPlayerDidPilotExecutionWhileCloaked(entity player)
+void function HandleKillStats( entity victim, entity attacker, var damageInfo )
{
+ if ( !IsValid( attacker ) )
+ return
+ // get the player and it's pet titan
+ entity player
+ entity playerPetTitan
+ if ( attacker.IsPlayer() )
+ {
+ // the player is just the attacker
+ player = attacker
+ playerPetTitan = player.GetPetTitan()
+ }
+ else if ( attacker.IsTitan() && IsPetTitan( attacker ) )
+ {
+ // the attacker is the player's auto titan
+ player = attacker.GetTitanSoul().GetBossPlayer()
+ playerPetTitan = attacker
+ }
+ else
+ {
+ // attacker could be something like an NPC, or worldspawn
+ return
+ }
+
+ // check things once, for performance
+ int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ bool victimIsPlayer = victim.IsPlayer()
+ bool victimIsNPC = victim.IsNPC()
+ bool victimIsPilot = IsPilot( victim )
+ bool victimIsTitan = victim.IsTitan()
+
+ if ( player in file.playerKills )
+ file.playerKills[ player ]++
+ else
+ file.playerKills[ player ] <- 1.0
+ // total
+ Stats_IncrementStat( player, "kills_stats", "total", "", 1.0 )
+
+ // totalPVP
+ if ( victimIsPlayer )
+ {
+ if ( player in file.playerKillsPvp )
+ file.playerKillsPvp[ player ]++
+ else
+ file.playerKillsPvp[ player ] <- 1.0
+ Stats_IncrementStat( player, "kills_stats", "totalPVP", "", 1.0 )
+ }
+
+ // pilots
+ if ( victimIsPilot )
+ Stats_IncrementStat( player, "kills_stats", "pilots", "", 1.0 )
+
+ // spectres
+ if ( IsSpectre( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "spectres", "", 1.0 )
+
+ // marvins
+ if ( IsMarvin( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "marvins", "", 1.0 )
+
+ // grunts
+ if ( IsGrunt( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "grunts", "", 1.0 )
+
+ // totalTitans
+ if ( victimIsTitan )
+ Stats_IncrementStat( player, "kills_stats", "totalTitans", "", 1.0 )
+
+ // totalPilots
+ if ( victimIsPilot )
+ Stats_IncrementStat( player, "kills_stats", "totalPilots", "", 1.0 )
+
+ // totalNPC
+ if ( victimIsNPC )
+ Stats_IncrementStat( player, "kills_stats", "totalNPC", "", 1.0 )
+
+ // totalTitansWhileDoomed
+ if ( victimIsTitan && attacker.IsTitan() && GetDoomedState( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "totalTitansWhileDoomed", "", 1.0 )
+
+ // asPilot
+ if ( IsPilot( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "asPilot", "", 1.0 )
+
+ // totalAssists
+ // assistsTotal ( weapon_kill_stats )
+ // note: eww
+ table<int, bool> alreadyAssisted
+ foreach( DamageHistoryStruct attackerInfo in victim.e.recentDamageHistory )
+ {
+ if ( !IsValid( attackerInfo.attacker ) || !attackerInfo.attacker.IsPlayer() || attackerInfo.attacker == victim )
+ continue
+
+ bool exists = attackerInfo.attacker.GetEncodedEHandle() in alreadyAssisted ? true : false
+ if( attackerInfo.attacker != attacker && !exists )
+ {
+ alreadyAssisted[ attackerInfo.attacker.GetEncodedEHandle() ] <- true
+ Stats_IncrementStat( attackerInfo.attacker, "kills_stats", "totalAssists", "", 1.0 )
+
+ string source = DamageSourceIDToString( attackerInfo.damageSourceId )
+
+ if ( IsValidStatItemString( source ) )
+ Stats_IncrementStat( attacker, "weapon_kill_stats", "assistsTotal", source, 1.0 )
+ }
+ }
+
+ // asTitan_<chassis>
+ if ( player.IsTitan() )
+ Stats_IncrementStat( player, "kills_stats", "asTitan_" + GetTitanCharacterName( player ), "", 1.0 )
+
+ // firstStrikes
+ if ( file.isFirstStrike && attacker.IsPlayer() && victimIsPlayer )
+ {
+ Stats_IncrementStat( player, "kills_stats", "firstStrikes", "", 1.0 )
+ file.isFirstStrike = false
+ }
+
+ // ejectingPilots
+ if ( victimIsPilot && victim.p.pilotEjecting )
+ Stats_IncrementStat( player, "kills_stats", "ejectingPilots", "", 1.0 )
+
+ // whileEjecting
+ if ( attacker.IsPlayer() && attacker.p.pilotEjecting )
+ Stats_IncrementStat( player, "kills_stats", "whileEjecting", "", 1.0 )
+
+ // cloakedPilots
+ if ( victimIsPilot && IsCloaked( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "cloakedPilots", "", 1.0 )
+
+ // whileCloaked
+ if ( attacker == player && IsCloaked( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "whileCloaked", "", 1.0 )
+
+ // wallrunningPilots
+ if ( victimIsPilot && victim.IsWallRunning() )
+ Stats_IncrementStat( player, "kills_stats", "wallrunningPilots", "", 1.0 )
+
+ // whileWallrunning
+ if ( attacker == player && attacker.IsWallRunning() )
+ Stats_IncrementStat( player, "kills_stats", "whileWallrunning", "", 1.0 )
+
+ // wallhangingPilots
+ if ( victimIsPilot && victim.IsWallHanging() )
+ Stats_IncrementStat( player, "kills_stats", "wallhangingPilots", "", 1.0 )
+
+ // whileWallhanging
+ if ( attacker == player && attacker.IsWallHanging() )
+ Stats_IncrementStat( player, "kills_stats", "whileWallhanging", "", 1.0 )
+
+ // pilotExecution
+ if ( damageSource == eDamageSourceId.human_execution )
+ Stats_IncrementStat( player, "kills_stats", "pilotExecution", "", 1.0 )
+
+ // pilotExecutePilot
+ if ( victimIsPilot && damageSource == eDamageSourceId.human_execution )
+ Stats_IncrementStat( player, "kills_stats", "pilotExecutePilot", "", 1.0 )
+
+ // pilotKillsWithHoloPilotActive
+ if ( victimIsPilot && GetDecoyActiveCountForPlayer( player ) > 0 )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsWithHoloPilotActive", "", 1.0 )
+
+ // pilotKillsWithAmpedWallActive
+ if ( victimIsPilot && GetAmpedWallsActiveCountForPlayer( player ) > 0 )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsWithAmpedWallActive", "", 1.0 )
+
+ // pilotExecutePilotUsing_<execution>
+ if ( victimIsPilot && damageSource == eDamageSourceId.human_execution )
+ Stats_IncrementStat( player, "kills_stats", "pilotExecutePilotUsing_" + player.p.lastExecutionUsed, "", 1.0 )
+
+ // pilotKickMelee
+ if ( damageSource == eDamageSourceId.human_melee )
+ Stats_IncrementStat( player, "kills_stats", "pilotKickMelee", "", 1.0 )
+
+ // pilotKickMeleePilot
+ if ( victimIsPilot && damageSource == eDamageSourceId.human_melee )
+ Stats_IncrementStat( player, "kills_stats", "pilotKickMeleePilot", "", 1.0 )
+
+ // titanMelee
+ if ( DamageIsTitanMelee( damageSource ) )
+ Stats_IncrementStat( player, "kills_stats", "titanMelee", "", 1.0 )
+
+ // titanMeleePilot
+ if ( victimIsPilot && DamageIsTitanMelee( damageSource ) )
+ Stats_IncrementStat( player, "kills_stats", "titanMeleePilot", "", 1.0 )
+
+ // titanStepCrush
+ if ( IsTitanCrushDamage( damageInfo ) )
+ Stats_IncrementStat( player, "kills_stats", "titanStepCrush", "", 1.0 )
+
+ // titanStepCrushPilot
+ if ( victimIsPilot && IsTitanCrushDamage( damageInfo ) )
+ Stats_IncrementStat( player, "kills_stats", "titanStepCrushPilot", "", 1.0 )
+
+ // titanExocution<capitalisedChassis>
+ // note: RESPAWN WHY? EXPLAIN
+ if ( damageSource == eDamageSourceId.titan_execution )
+ {
+ string titanName = GetTitanCharacterName( player )
+ titanName = titanName.slice( 0, 1 ).toupper() + titanName.slice( 1, titanName.len() )
+ Stats_IncrementStat( player, "kills_stats", "titanExocution" + titanName, "", 1.0 )
+ }
+
+ // titanFallKill
+ if ( damageSource == eDamageSourceId.damagedef_titan_fall )
+ Stats_IncrementStat( player, "kills_stats", "titanFallKill", "", 1.0 )
+
+ // petTitanKillsFollowMode
+ if ( attacker == playerPetTitan && player.GetPetTitanMode() == eNPCTitanMode.FOLLOW )
+ Stats_IncrementStat( player, "kills_stats", "petTitanKillsFollowMode", "", 1.0 )
+
+ // petTitanKillsGuardMode
+ if ( attacker == playerPetTitan && player.GetPetTitanMode() == eNPCTitanMode.STAY )
+ Stats_IncrementStat( player, "kills_stats", "petTitanKillsGuardMode", "", 1.0 )
+
+ // rodeo_total
+ if ( damageSource == eDamageSourceId.rodeo_battery_removal )
+ Stats_IncrementStat( player, "kills_stats", "rodeo_total", "", 1.0 )
+
+ // pilot_headshots_total
+ if ( victimIsPilot && DamageInfo_GetCustomDamageType( damageInfo ) & DF_HEADSHOT )
+ Stats_IncrementStat( player, "kills_stats", "pilot_headshots_total", "", 1.0 )
+
+ // evacShips
+ if ( IsEvacDropship( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "evacShips", "", 1.0 )
+
+ // nuclearCore
+ if ( damageSource == eDamageSourceId.damagedef_nuclear_core )
+ Stats_IncrementStat( player, "kills_stats", "nuclearCore", "", 1.0 )
+
+ // meleeWhileCloaked
+ if ( IsCloaked( attacker ) && damageSource == eDamageSourceId.human_melee )
+ Stats_IncrementStat( player, "kills_stats", "meleeWhileCloaked", "", 1.0 )
+
+ // titanKillsAsPilot
+ if ( victimIsTitan && IsPilot( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "titanKillsAsPilot", "", 1.0 )
+
+ // pilotKillsWhileStimActive
+ if ( victimIsPilot && StatusEffect_Get( attacker, eStatusEffect.stim_visual_effect ) <= 0 )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsWhileStimActive", "", 1.0 )
+
+ // pilotKillsAsTitan
+ if ( victimIsPilot && attacker.IsTitan() )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsAsTitan", "", 1.0 )
+
+ // pilotKillsAsPilot
+ if ( victimIsPilot && IsPilot( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsAsPilot", "", 1.0 )
+
+ // titanKillsAsTitan
+ if ( victimIsTitan && attacker.IsTitan() )
+ Stats_IncrementStat( player, "kills_stats", "titanKillsAsTitan", "", 1.0 )
}
-void function UpdateTitanDamageStat(entity attacker, float savedDamage, var damageInfo)
+void function HandleTitanStats( entity victim, entity attacker, var damageInfo )
{
+ if ( !IsValid( attacker ) )
+ return
+
+ // get the player and it's pet titan
+ entity player
+ entity playerPetTitan
+ if ( attacker.IsPlayer() )
+ {
+ // the player is just the attacker
+ player = attacker
+ playerPetTitan = player.GetPetTitan()
+ }
+ else if ( attacker.IsTitan() && IsPetTitan( attacker ) )
+ {
+ // the attacker is the player's auto titan
+ player = attacker.GetTitanSoul().GetBossPlayer()
+ playerPetTitan = attacker
+ }
+ else
+ {
+ // attacker could be something like an NPC, or worldspawn
+ return
+ }
+
+ int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ bool victimIsPlayer = victim.IsPlayer()
+ bool victimIsNPC = victim.IsNPC()
+ bool victimIsPilot = IsPilot( victim )
+ bool victimIsTitan = victim.IsTitan()
+ bool titanIsPrime = IsTitanPrimeTitan( player )
+ // pilots
+ if ( victimIsPilot && attacker.IsTitan() )
+ Stats_IncrementStat( player, "titan_stats", "pilots", GetTitanCharacterName( attacker ), 1.0 )
+
+ // titansTotal
+ if ( victimIsTitan && attacker.IsTitan() )
+ Stats_IncrementStat( player, "titan_stats", "titansTotal", GetTitanCharacterName( attacker ), 1.0 )
+
+ // pilotsAsPrime
+ if ( victimIsPilot && attacker.IsTitan() && titanIsPrime )
+ Stats_IncrementStat( player, "titan_stats", "pilotsAsPrime", GetTitanCharacterName( attacker ), 1.0 )
+
+ // titansAsPrime
+ if ( victimIsTitan && attacker.IsTitan() && titanIsPrime )
+ Stats_IncrementStat( player, "titan_stats", "titansAsPrime", GetTitanCharacterName( attacker ), 1.0 )
+
+ // executionsAsPrime
+ if ( damageSource == eDamageSourceId.titan_execution && attacker.IsTitan() && titanIsPrime )
+ Stats_IncrementStat( player, "titan_stats", "executionsAsPrime", GetTitanCharacterName( attacker ), 1.0 )
}
-void function UpdateTitanWeaponDamageStat(entity attacker, float savedDamage, var damageInfo)
+void function OnPlayerRespawned( entity player )
{
+ thread SetLastPosForDistanceStatValid_Threaded( player, true )
+}
+
+void function OnEpilogueStarted()
+{
+ // award players for match completed, wins, and losses
+ foreach ( entity player in GetPlayerArray() )
+ {
+ Stats_IncrementStat( player, "game_stats", "game_completed", "", 1.0 )
+
+ if ( player.GetTeam() == GetWinningTeam() )
+ Stats_IncrementStat( player, "game_stats", "game_won", "", 1.0 )
+ else
+ Stats_IncrementStat( player, "game_stats", "game_lost", "", 1.0 )
+ }
+
+ if ( IsValidGamemodeString( GAMETYPE ) )
+ {
+ // award players with matches played on the mode
+ foreach ( entity player in GetPlayerArray() )
+ {
+ Stats_IncrementStat( player, "game_stats", "mode_played", GAMETYPE, 1.0 )
+
+ if ( player.GetTeam() == GetWinningTeam() )
+ Stats_IncrementStat( player, "game_stats", "mode_won", GAMETYPE, 1.0 )
+ }
+ }
+
+ // update player's KD
+ foreach ( entity player in GetPlayerArray() )
+ {
+ // kd stats
+ // index 0 is most recent game
+ // index 9 is least recent game
+ float playerKills = ( player in file.playerKills ) ? file.playerKills[ player ] : 0.0
+ float playerDeaths = ( player in file.playerDeaths ) ? file.playerDeaths[ player ] : 0.0
+ float kdratio_match
+ if ( playerDeaths == 0.0 )
+ kdratio_match = playerKills
+ else
+ kdratio_match = playerKills / playerDeaths
+
+ float playerKillsPvp = ( player in file.playerKillsPvp ) ? file.playerKillsPvp[ player ] : 0.0
+ float playerDeathsPvp = ( player in file.playerDeathsPvp ) ? file.playerDeathsPvp[ player ] : 0.0
+ float kdratiopvp_match
+ if ( playerDeathsPvp == 0.0 )
+ kdratiopvp_match = playerKillsPvp
+ else
+ kdratiopvp_match = playerKillsPvp / playerDeathsPvp
+
+ float totalDeaths = player.GetPersistentVarAsInt( "deathStats.total" ).tofloat()
+ float totalKills = player.GetPersistentVarAsInt( "killStats.total" ).tofloat()
+ float totalDeathsPvp = player.GetPersistentVarAsInt( "deathStats.totalPVP" ).tofloat()
+ float totalKillsPvp = player.GetPersistentVarAsInt( "killStats.totalPVP" ).tofloat()
+ float kdratio_lifetime
+ if ( totalDeaths == 0.0 )
+ kdratio_lifetime = totalKills
+ else
+ kdratio_lifetime = totalKills / totalDeaths
+ float kdratio_lifetimepvp
+ if ( totalDeathsPvp == 0.0 )
+ kdratio_lifetimepvp = totalKillsPvp
+ else
+ kdratio_lifetimepvp = totalKillsPvp / totalDeathsPvp
+
+ // shift stats by 1 to make room for new game data
+ for ( int i = NUM_GAMES_TRACK_KDRATIO - 2; i >= 0; --i )
+ {
+ player.SetPersistentVar( format( "kdratio_match[%i]", ( i + 1 ) ), player.GetPersistentVar( format("kdratio_match[%i]", i ) ) )
+ player.SetPersistentVar( format( "kdratiopvp_match[%i]", ( i + 1 ) ), player.GetPersistentVar( format( "kdratiopvp_match[%i]", i ) ) )
+ }
+ // add new game data
+ player.SetPersistentVar( "kdratio_match[0]", kdratio_match )
+ player.SetPersistentVar( "kdratiopvp_match[0]", kdratiopvp_match )
+ player.SetPersistentVar( "kdratio_lifetime", kdratio_lifetime )
+ player.SetPersistentVar( "kdratio_lifetime_pvp", kdratio_lifetimepvp )
+ }
+
+ // award mvp and top 3 in each team
+ if ( !IsFFAGame() )
+ {
+ string gamemode = GameRules_GetGameMode()
+ int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( gamemode )
+
+ for( int team = 0; team < MAX_TEAMS; team++ )
+ {
+ array<entity> players = GetPlayerArrayOfTeam( team )
+ if ( compareFunc == null )
+ {
+ printt( "gamemode doesn't have a compare func to get the top 3" )
+ return
+ }
+ players.sort( compareFunc )
+ int maxAwards = int( min( players.len(), 3 ) )
+ for ( int i = 0; i < maxAwards; i++ )
+ {
+ if ( i == 0 )
+ Stats_IncrementStat( players[ i ], "game_stats", "mvp", "", 1.0 )
+ Stats_IncrementStat( players[ i ], "game_stats", "top3OnTeam", "", 1.0 )
+ }
+ }
+
+ }
+}
+void function SetLastPosForDistanceStatValid_Threaded( entity player, bool val )
+{
+ WaitFrame()
+ if ( !IsValid( player ) )
+ return
+ player.p.lastPosForDistanceStatValid = val
}
-void function UpdateTitanCoreEarnedStat( entity player, entity titan )
+// Respawn did this through stuff found in _entitystructs.gnut (stuff like stats_wallrunTime)
+// but their implementation seems kinda bad. The advantage it has over this method is not polling
+// every 0.25 seconds, and using movement callbacks and stuff instead. However, since i can't find
+// callbacks for things like changing weapon, i would have to poll for that *anyway* and thus,
+// there is no point in doing things Respawn's way here
+void function HandleDistanceAndTimeStats_Threaded()
{
+ // just to be safe
+ if ( IsLobby() )
+ return
+
+ while ( GetGameState() < eGameState.Playing )
+ WaitFrame()
+
+ float lastTickTime = Time()
+
+ while( true )
+ {
+ // track distance stats
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( player.p.lastPosForDistanceStatValid )
+ {
+ // not 100% sure on using Distance2D over Distance tbh
+ float distInches = Distance2D( player.p.lastPosForDistanceStat, player.GetOrigin() )
+ float distMiles = distInches / 63360.0
+
+ // more generic distance stats
+ Stats_IncrementStat( player, "distance_stats", "total", "", distMiles )
+ if ( player.IsTitan() )
+ {
+ Stats_IncrementStat( player, "distance_stats", "asTitan_" + GetTitanCharacterName( player ), "", distMiles )
+ Stats_IncrementStat( player, "distance_stats", "asTitan", "", distMiles )
+ }
+ else
+ Stats_IncrementStat( player, "distance_stats", "asPilot", "", distMiles )
+
+
+ string state = ""
+ // specific distance stats
+ if ( player.IsWallRunning() )
+ state = "wallrunning"
+ else if ( PlayerIsRodeoingTitan( player ) )
+ {
+ if ( player.GetTitanSoulBeingRodeoed().GetTeam() == player.GetTeam() )
+ state = "onFriendlyTitan"
+ else
+ state = "onEnemyTitan"
+ }
+ else if ( player.IsZiplining() )
+ state = "ziplining"
+ else if ( !player.IsOnGround() )
+ state = "inAir"
+
+ if ( state != "" )
+ Stats_IncrementStat( player, "distance_stats", state, "", distMiles )
+ }
+
+ player.p.lastPosForDistanceStat = player.GetOrigin()
+ }
+
+ float timeSeconds = Time() - lastTickTime
+ float timeHours = timeSeconds / 3600.0
+
+ // track time stats
+ foreach ( entity player in GetPlayerArray() )
+ {
+ // first tick i dont count
+ if ( timeSeconds == 0 )
+ break
+
+ // more generic time stats
+ Stats_IncrementStat( player, "time_stats", "hours_total", "", timeHours )
+ if ( player.IsTitan() )
+ {
+ Stats_IncrementStat( player, "time_stats", "hours_as_titan_" + GetTitanCharacterName( player ), "", timeHours )
+ Stats_IncrementStat( player, "time_stats", "hours_as_titan", "", timeHours )
+ }
+ else
+ Stats_IncrementStat( player, "time_stats", "hours_as_pilot", "", timeHours )
+
+ string state = ""
+ // specific time stats
+ if ( !IsAlive( player ) )
+ state = "hours_dead"
+ else if ( player.IsWallHanging() )
+ state = "hours_wallhanging"
+ else if ( player.IsWallRunning() )
+ state = "hours_wallrunning"
+ else if ( !player.IsOnGround() )
+ state = "hours_inAir"
+ if ( state != "" )
+ Stats_IncrementStat( player, "time_stats", state, "", timeHours )
+
+ // weapon time stats
+ entity activeWeapon = player.GetActiveWeapon()
+ if ( IsValid( activeWeapon ) )
+ {
+ if ( IsValidStatItemString( activeWeapon.GetWeaponClassName() ) )
+ Stats_IncrementStat( player, "weapon_stats", "hoursUsed", activeWeapon.GetWeaponClassName(), timeHours )
+
+ foreach( entity weapon in player.GetMainWeapons() )
+ {
+ if ( IsValidStatItemString( weapon.GetWeaponClassName() ) )
+ Stats_IncrementStat( player, "weapon_stats", "hoursEquipped", weapon.GetWeaponClassName(), timeHours )
+ }
+ }
+
+ // map time stats
+ Stats_IncrementStat( player, "game_stats", "hoursPlayed", "", timeHours )
+ }
+
+ lastTickTime = Time()
+ // not rly worth doing this every frame, just a couple of times per second should be fine
+ wait 0.25
+ }
+}
+
+// this is kinda shit
+void function SaveStatsPeriodically_Threaded()
+{
+ while( true )
+ {
+ foreach( entity player in GetPlayerArray() )
+ Stats_SaveAllStats( player )
+ wait 5
+ }
}
-void function PreScoreEventUpdateStats(entity attacker, entity ent)
+bool function IsValidGamemodeString( string mode )
{
+ int gameModeCount = PersistenceGetEnumCount( "gameModes" )
+ for ( int modeIndex = 0; modeIndex < gameModeCount; modeIndex++ )
+ {
+ string gameModeName = PersistenceGetEnumItemNameForIndex( "gameModes", modeIndex )
+
+ if ( gameModeName == mode )
+ return true
+ }
+ return false
}
-void function PostScoreEventUpdateStats(entity attacker, entity ent)
+bool function IsValidStatItemString( string item )
{
+ foreach( str in shGlobalMP.statsItemsList )
+ {
+ if ( str == item )
+ return true
+ }
+ return false
}
-void function Stats_OnPlayerDidDamage(entity player, var damageInfo)
+string function GetPersistenceRefFromDamageInfo( var damageInfo )
{
+ string damageSourceString = DamageSourceIDToString( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+
+ foreach( str in shGlobalMP.statsItemsList )
+ {
+ if ( str == damageSourceString )
+ return damageSourceString
+ }
+ return ""
+}
+
+bool function DamageIsTitanMelee( int damageSourceId )
+{
+ switch( damageSourceId )
+ {
+ case eDamageSourceId.melee_titan_punch:
+ case eDamageSourceId.melee_titan_punch_ion:
+ case eDamageSourceId.melee_titan_punch_legion:
+ case eDamageSourceId.melee_titan_punch_tone:
+ case eDamageSourceId.melee_titan_punch_scorch:
+ case eDamageSourceId.melee_titan_punch_northstar:
+ case eDamageSourceId.melee_titan_punch_fighter:
+ case eDamageSourceId.melee_titan_sword:
+ case eDamageSourceId.melee_titan_sword_aoe:
+ return true
+ default:
+ return false
+ }
+ unreachable
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut
index 85f5aa05e..7a7498b8c 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut
@@ -785,7 +785,7 @@ bool function IsSettingPrimeTitanWithoutSetFile( entity player, string loadoutTy
bool function SkipItemLockedCheck( entity player, string ref, string parentRef, string loadoutProperty ) //Hack: Skip entitlement related unlock checks for now. Can fail.
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return true
//if ( IsItemInEntitlementUnlock( ref ) && IsLobby() ) //TODO: Look into restricting this to lobby only? But entitlement checks can fail randomly...
@@ -3993,7 +3993,7 @@ bool function IsValidTitanLoadoutIndex( int loadoutIndex )
bool function HasPrimeToMatchExecutionType( entity player, int itemType )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return true
switch( itemType )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut
new file mode 100644
index 000000000..00bdff60c
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut
@@ -0,0 +1,1091 @@
+global function Progression_Init
+global function ProgressionEnabledForPlayer
+#if CLIENT || UI
+global function Progression_SetPreference
+global function Progression_GetPreference
+global function UpdateCachedLoadouts_Delayed
+#endif
+
+// SO FOR SOME GOD DAMN REASON, PUTTING THESE INTO ONE STRUCT
+// AND PUTTING THE #if STUFF AROUND THE VARS CAUSES A COMPILE
+// ERROR, SO I HAVE TO DO THIS AWFULNESS
+
+#if SERVER
+struct {
+ table<entity, bool> progressionEnabled
+} file
+#else // UI || CLIENT
+struct {
+ bool isUpdatingCachedLoadouts = false
+} file
+#endif
+
+
+void function Progression_Init()
+{
+ #if SERVER
+ AddCallback_OnClientDisconnected( OnClientDisconnected )
+ AddClientCommandCallback( "ns_progression", ClientCommand_SetProgression )
+ AddCallback_GameStateEnter( eGameState.Playing, OnPlaying )
+ #elseif CLIENT
+ AddCallback_OnClientScriptInit( OnClientScriptInit )
+ #endif
+}
+
+bool function ProgressionEnabledForPlayer( entity player )
+{
+ #if SERVER
+ if ( player in file.progressionEnabled )
+ return file.progressionEnabled[player]
+
+ return false
+ #else // CLIENT || UI
+ return GetConVarBool( "ns_progression_enabled" )
+ #endif
+}
+
+#if SERVER
+void function OnPlaying()
+{
+ SetUIVar( level, "penalizeDisconnect", false ) // dont show the "you will lose merits thing"
+}
+
+void function OnClientDisconnected( entity player )
+{
+ // cleanup table when player leaves
+ if ( player in file.progressionEnabled )
+ delete file.progressionEnabled[player]
+}
+
+bool function ClientCommand_SetProgression( entity player, array<string> args )
+{
+ if ( args.len() != 1 )
+ return false
+ if ( args[0] != "0" && args[0] != "1" )
+ return false
+
+ file.progressionEnabled[player] <- args[0] == "1"
+
+ // loadout validation when progression is turned on
+ if ( file.progressionEnabled[player] )
+ ValidateEquippedItems( player )
+
+ return true
+}
+#endif
+
+#if CLIENT
+void function OnClientScriptInit( entity player )
+{
+ // unsure if this is needed, just being safe
+ if ( player != GetLocalClientPlayer() )
+ return
+
+ Progression_SetPreference( GetConVarBool( "ns_progression_enabled" ) )
+ UpdateCachedLoadouts_Delayed()
+}
+#endif
+
+#if CLIENT || UI
+void function Progression_SetPreference( bool enabled )
+{
+ SetConVarBool( "ns_progression_enabled", enabled )
+
+ #if CLIENT
+ GetLocalClientPlayer().ClientCommand( "ns_progression " + enabled.tointeger() )
+ #else // UI
+ ClientCommand( "ns_progression " + enabled.tointeger() )
+ #endif
+}
+
+bool function Progression_GetPreference()
+{
+ return GetConVarBool( "ns_progression_enabled" )
+}
+
+void function UpdateCachedLoadouts_Delayed()
+{
+ if ( file.isUpdatingCachedLoadouts )
+ return
+
+ file.isUpdatingCachedLoadouts = true
+
+ #if UI
+ RunClientScript( "UpdateCachedLoadouts_Delayed" ) // keep client and UI synced
+ #else // CLIENT
+ RunUIScript( "UpdateCachedLoadouts_Delayed" ) // keep client and UI synced
+ #endif
+
+ thread UpdateCachedLoadouts_Threaded()
+}
+
+void function UpdateCachedLoadouts_Threaded()
+{
+ wait 1.0 // give the server time to network our new persistence
+
+ UpdateCachedLoadouts()
+
+ // below here is just making all the menu models update properly and such
+
+ #if UI
+ uiGlobal.pilotSpawnLoadoutIndex = GetPersistentSpawnLoadoutIndex( GetUIPlayer(), "pilot" )
+ uiGlobal.titanSpawnLoadoutIndex = GetPersistentSpawnLoadoutIndex( GetUIPlayer(), "titan" )
+ #endif
+
+ #if CLIENT
+ entity player = GetLocalClientPlayer()
+ ClearAllTitanPreview( player )
+ ClearAllPilotPreview( player )
+ UpdateTitanModel( player, GetPersistentSpawnLoadoutIndex( player, "titan" ) )
+ UpdatePilotModel( player, GetPersistentSpawnLoadoutIndex( player, "pilot" ) )
+ #endif
+
+ file.isUpdatingCachedLoadouts = false
+}
+#endif
+
+#if SERVER
+void function ValidateEquippedItems( entity player )
+{
+ printt( "VALIDATING EQUIPPED ITEMS FOR PLAYER: " + player.GetPlayerName() )
+
+ // banner
+ CallingCard card = PlayerCallingCard_GetActive( player )
+ if ( IsItemLocked( player, card.ref ) )
+ {
+ printt( "- BANNER CARD IS LOCKED, RESETTING" )
+ PlayerCallingCard_SetActiveByRef( player, "callsign_16_col" ) // copied from _persistentdata.gnut
+ }
+
+ // patch
+ CallsignIcon icon = PlayerCallsignIcon_GetActive( player )
+ if ( IsItemLocked( player, icon.ref ) )
+ {
+ printt( "- BANNER PATCH IS LOCKED, RESETTING" )
+ PlayerCallsignIcon_SetActiveByRef( player, "gc_icon_titanfall" ) // copied from _persistentdata.gnut
+ }
+
+ // faction
+ int factionIndex = player.GetPersistentVarAsInt( "factionChoice" )
+ string factionRef = PersistenceGetEnumItemNameForIndex( "faction", factionIndex )
+ if ( IsItemLocked( player, factionRef ) )
+ {
+ printt( "- FACTION IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "factionChoice", "faction_marauder" ) // im so sorry that i am setting you to use sarah, you don't deserve this
+ }
+
+ // boost
+ BurnReward reward = BurnReward_GetById( player.GetPersistentVarAsInt( "burnmeterSlot" ) )
+ if ( IsItemLocked( player, reward.ref ) )
+ {
+ printt( "- BOOST IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "burnmeterSlot", BurnReward_GetByRef( "burnmeter_amped_weapons" ).id )
+ }
+
+ // titan loadouts
+ for ( int titanLoadoutIndex = 0; titanLoadoutIndex < NUM_PERSISTENT_TITAN_LOADOUTS; titanLoadoutIndex++ )
+ {
+ printt( "- VALIDATING TITAN LOADOUT: " + titanLoadoutIndex )
+
+ bool isSelected = titanLoadoutIndex == player.GetPersistentVarAsInt( "titanSpawnLoadout.index" )
+ TitanLoadoutDef loadout = GetTitanLoadout( player, titanLoadoutIndex )
+ TitanLoadoutDef defaultLoadout = shGlobal.defaultTitanLoadouts[titanLoadoutIndex]
+
+ printt( " - CHASSIS: " + loadout.titanClass )
+
+ // passive1 - "Titan Kit" (things like overcore)
+ if ( loadout.passive1 != defaultLoadout.passive1 && IsSubItemLocked( player, loadout.passive1, loadout.titanClass ) )
+ {
+ printt( " - TITAN KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive1", defaultLoadout.passive1 )
+ }
+
+ // passive2 - "<chassis> Kit" (things like zero point tripwire)
+ if ( loadout.passive2 != defaultLoadout.passive2 && IsSubItemLocked( player, loadout.passive2, loadout.titanClass ) )
+ {
+ printt( " - CHASSIS KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive2", defaultLoadout.passive2 )
+ }
+
+ // passive3 - "Titanfall Kit" (warpfall/dome shield)
+ if ( loadout.passive3 != defaultLoadout.passive3 && IsSubItemLocked( player, loadout.passive3, loadout.titanClass ) )
+ {
+ printt( " - TITANFALL KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive3", defaultLoadout.passive3 )
+ }
+
+ // passive4 - monarch core 1
+ if ( loadout.passive4 != defaultLoadout.passive4 && IsSubItemLocked( player, loadout.passive4, loadout.titanClass ) )
+ {
+ printt( " - MONARCH CORE 1 KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive4", defaultLoadout.passive4 )
+ }
+
+ // passive5 - monarch core 2
+ if ( loadout.passive5 != defaultLoadout.passive5 && IsSubItemLocked( player, loadout.passive5, loadout.titanClass ) )
+ {
+ printt( " - MONARCH CORE 2 KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive5", defaultLoadout.passive5 )
+ }
+
+ // passive6 - monarch core 3
+ if ( loadout.passive6 != defaultLoadout.passive6 && IsSubItemLocked( player, loadout.passive6, loadout.titanClass ) )
+ {
+ printt( " - MONARCH CORE 3 KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive6", defaultLoadout.passive6 )
+ }
+
+ // titanExecution
+ if ( !IsRefValid( loadout.titanExecution ) || !IsValidTitanExecution( titanLoadoutIndex, "titanExecution", "", loadout.titanExecution ) )
+ {
+ printt( " - TITAN EXECUTION IS INVALID FOR CHASSIS, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].titanExecution", defaultLoadout.titanExecution )
+ }
+ else if ( IsItemLocked( player, loadout.titanExecution ) )
+ {
+ printt( " - TITAN EXECUTION EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].titanExecution", defaultLoadout.titanExecution )
+ }
+ else if ( GetItemData( loadout.titanExecution ).reqPrime && IsItemLocked( player, loadout.primeTitanRef ) )
+ {
+ printt( " - PRIME TITAN EXECUTION EQUIPPED WHEN PRIME TITAN IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].titanExecution", defaultLoadout.titanExecution )
+ }
+
+ // skinIndex
+ // camoIndex
+ if ( loadout.skinIndex == TITAN_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.camoIndex >= camoSkins.len() || loadout.camoIndex < 0 )
+ {
+ printt( " - INVALID TITAN CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.camoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.titanClass ) )
+ {
+ printt( " - TITAN CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+ }
+ else if ( loadout.skinIndex == 0 )
+ {
+ if ( loadout.camoIndex != 0 )
+ {
+ printt( " - INVALID TITAN CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+ else
+ {
+ string ref = GetSkinRefFromTitanClassAndPersistenceValue( loadout.titanClass, loadout.skinIndex )
+ if ( ref == INVALID_REF )
+ {
+ printt( " - INVALID TITAN WARPAINT, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ else if ( IsSubItemLocked( player, ref, loadout.titanClass ) )
+ {
+ printt( " - TITAN WARPAINT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+
+ // decalIndex
+ string noseArtRef = GetNoseArtRefFromTitanClassAndPersistenceValue( loadout.titanClass, loadout.decalIndex )
+ if ( loadout.decalIndex != defaultLoadout.decalIndex && IsSubItemLocked( player, noseArtRef, loadout.titanClass ) )
+ {
+ printt( " - NOSE ART EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].decalIndex", defaultLoadout.decalIndex )
+ }
+
+ // primarySkinIndex
+ // primaryCamoIndex
+ if ( loadout.primarySkinIndex == WEAPON_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.primaryCamoIndex >= camoSkins.len() || loadout.primaryCamoIndex < 0 )
+ {
+ printt( " - INVALID WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.primaryCamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.titanClass ) )
+ {
+ printt( " - WEAPON CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ }
+ }
+ else if ( loadout.primarySkinIndex == 0 && loadout.primaryCamoIndex != 0 )
+ {
+ // titan weapons do not have skins, if we ever do add them lots of stuff will
+ //need a refactor outside of here so with that being said, i cannot be bothered
+ printt( " - INVALID WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+
+
+ // isPrime
+ if ( loadout.isPrime == "titan_is_prime" && IsItemLocked( player, loadout.primeTitanRef ) )
+ {
+ printt( " - PRIME TITAN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].isPrime", defaultLoadout.isPrime )
+ }
+
+ // primeSkinIndex
+ // primeCamoIndex
+ if ( loadout.primeSkinIndex == TITAN_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.primeCamoIndex >= camoSkins.len() || loadout.primeCamoIndex < 0 )
+ {
+ printt( " - INVALID TITAN CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeSkinIndex", defaultLoadout.primeSkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeCamoIndex", defaultLoadout.primeCamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.primeCamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.titanClass ) )
+ {
+ printt( " - TITAN CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeSkinIndex", defaultLoadout.primeSkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeCamoIndex", defaultLoadout.primeCamoIndex )
+ }
+ }
+ }
+ else if ( loadout.primeSkinIndex == 0 )
+ {
+ if ( loadout.primeCamoIndex != 0 )
+ {
+ printt( " - INVALID TITAN CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeSkinIndex", defaultLoadout.primeSkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeCamoIndex", defaultLoadout.primeCamoIndex )
+ }
+ }
+ else
+ {
+ printt( " - INVALID PRIME TITAN SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeSkinIndex", defaultLoadout.primeSkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeCamoIndex", defaultLoadout.primeCamoIndex )
+ }
+
+ // primeDecalIndex
+ string primeNoseArtRef = GetNoseArtRefFromTitanClassAndPersistenceValue( loadout.titanClass, loadout.primeDecalIndex )
+ if ( loadout.primeDecalIndex != defaultLoadout.primeDecalIndex && IsSubItemLocked( player, primeNoseArtRef, loadout.titanClass ) )
+ {
+ printt( " - PRIME NOSE ART EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeDecalIndex", defaultLoadout.primeDecalIndex )
+ }
+
+ // showArmBadge - equipped and shouldnt be able to
+ if ( loadout.showArmBadge && !CanEquipArmBadge( player, loadout.titanClass ) )
+ {
+ printt( " - ARM BADGE EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].showArmBadge", defaultLoadout.showArmBadge )
+ }
+
+ // equipped titan loadout - equipped titan class is locked
+ if ( isSelected && IsItemLocked( player, loadout.titanClass ) )
+ {
+ printt( " - SELECTED TITAN CLASS IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanSpawnLoadout.index", 0 )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateTitanModel", 0 )
+ }
+ }
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateTitanModel", player.GetPersistentVarAsInt( "titanSpawnLoadout.index" ) )
+
+ // pilot loadouts
+ for ( int pilotLoadoutIndex = 0; pilotLoadoutIndex < NUM_PERSISTENT_PILOT_LOADOUTS; pilotLoadoutIndex++ )
+ {
+ printt( "- VALIDATING PILOT LOADOUT: " + pilotLoadoutIndex )
+
+ bool isSelected = pilotLoadoutIndex == player.GetPersistentVarAsInt( "pilotSpawnLoadout.index" )
+ PilotLoadoutDef loadout = GetPilotLoadout( player, pilotLoadoutIndex )
+ PilotLoadoutDef defaultLoadout = shGlobal.defaultPilotLoadouts[pilotLoadoutIndex]
+
+ // note: for readability, I have added {} around the different items,
+ // so that you can collapse them in visual studio code (and other good IDEs)
+
+ // tactical
+ {
+ if ( !IsRefValid( loadout.suit ) )
+ {
+ printt( " - TACTICAL IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].suit", defaultLoadout.suit )
+ }
+ else if ( IsItemLocked( player, loadout.suit ) )
+ {
+ printt( " - TACTICAL IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].suit", defaultLoadout.suit )
+ }
+ }
+
+ // ordnance
+ {
+ if ( !IsRefValid( loadout.ordnance ) )
+ {
+ printt( " - ORDNANCE IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].ordnance", defaultLoadout.ordnance )
+ }
+ else if ( IsItemLocked( player, loadout.ordnance ) )
+ {
+ printt( " - ORDNANCE IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].ordnance", defaultLoadout.ordnance )
+ }
+ }
+
+ // race ( gender )
+ {
+ if ( !IsRefValid( loadout.race ) )
+ {
+ printt( " - GENDER IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].race", defaultLoadout.race )
+ }
+ else if ( IsItemLocked( player, loadout.race ) )
+ {
+ printt( " - GENDER IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].race", defaultLoadout.race )
+ }
+ }
+
+ // camoIndex
+ // skinIndex
+ {
+ if ( loadout.skinIndex == PILOT_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN_PILOT )
+ if ( loadout.camoIndex >= camoSkins.len() || loadout.camoIndex < 0 )
+ {
+ printt( " - INVALID PILOT CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.camoIndex]
+ if ( IsItemLocked( player, camoSkin.ref ) )
+ {
+ printt( " - PILOT CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+ }
+ else if ( loadout.skinIndex == 0 )
+ {
+ if ( loadout.camoIndex != 0 )
+ {
+ printt( " - INVALID PILOT CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+ else
+ {
+ // pilots can't have skins other than 0 and 1 right?
+ printt( " - INVALID PILOT SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+
+ // primary weapon
+ {
+ if ( !IsRefValid( loadout.primary ) || GetItemType( loadout.primary ) != eItemTypes.PILOT_PRIMARY )
+ {
+ printt( " - PRIMARY WEAPON IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primary", defaultLoadout.primary )
+ }
+ else if ( IsItemLocked( player, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primary", defaultLoadout.primary )
+ }
+ }
+
+ // primary weapon mods
+ {
+ // mod1
+ if ( loadout.primaryMod1 == "" )
+ {
+ // do nothing
+ }
+ else if ( !HasSubitem( loadout.primary, loadout.primaryMod1 ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 1 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod1", defaultLoadout.primaryMod1 )
+ }
+ else if ( IsSubItemLocked( player, loadout.primaryMod1, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 1 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod1", defaultLoadout.primaryMod1 )
+ }
+ // mod2
+ if ( loadout.primaryMod2 == "" )
+ {
+ // do nothing
+ }
+ else if ( IsSubItemLocked( player, "primarymod2", loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 SLOT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ else if ( !HasSubitem( loadout.primary, loadout.primaryMod2 ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ else if ( IsSubItemLocked( player, loadout.primaryMod2, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ else if ( loadout.primaryMod2 == loadout.primaryMod1 && loadout.primaryMod2 != "" )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 IS DUPLICATE, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ else if ( loadout.primaryAttachment == "threat_scope" )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 IS SET WITH THREAT SCOPE, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ // attachment
+ if ( loadout.primaryAttachment == "" )
+ {
+ // do nothing
+ }
+ else if ( !HasSubitem( loadout.primary, loadout.primaryAttachment ) )
+ {
+ printt( " - PRIMARY WEAPON ATTACHMENT IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryAttachment", defaultLoadout.primaryAttachment )
+ }
+ else if ( IsSubItemLocked( player, loadout.primaryAttachment, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON ATTACHMENT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryAttachment", defaultLoadout.primaryAttachment )
+ }
+ // mod3 (pro screen)
+ if ( loadout.primaryMod3 == "" )
+ {
+ // do nothing
+ }
+ else if ( loadout.primaryMod3 == "pro_screen" )
+ {
+ // fuck you and your three mod slot stuff
+ printt( " - PRIMARY WEAPON PRO SCREEN IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod3", defaultLoadout.primaryMod3 )
+ }
+ else if ( IsSubItemLocked( player, loadout.primaryMod3, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON PRO SCREEN IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod3", defaultLoadout.primaryMod3 )
+ }
+ }
+
+ // primary weapon camoIndex
+ // primary weapon skinIndex
+ {
+ if ( loadout.primarySkinIndex == WEAPON_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.primaryCamoIndex >= camoSkins.len() || loadout.primaryCamoIndex < 0 )
+ {
+ printt( " - INVALID PRIMARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.primaryCamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ }
+ }
+ else if ( loadout.primarySkinIndex == 0 )
+ {
+ if ( loadout.primaryCamoIndex != 0 )
+ {
+ printt( " - INVALID PRIMARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ }
+ else if ( IsSubItemLocked( player, GetWeaponWarpaintRefByIndex( loadout.primarySkinIndex, loadout.primary ), loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON SKIN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ }
+
+ // secondary weapon
+ {
+ if ( !IsRefValid( loadout.secondary ) || GetItemType( loadout.secondary ) != eItemTypes.PILOT_SECONDARY )
+ {
+ printt( " - SECONDARY WEAPON IS LOCKED, RESETTING" )
+ string ref = defaultLoadout.secondary
+ if ( loadout.secondary == ref ) // item dupes swap
+ {
+ ref = defaultLoadout.weapon3
+ }
+ else if ( ItemsInSameMenuCategory( loadout.secondary, ref ) ) // category dupes assign value to other slot and swap
+ {
+ ref = defaultLoadout.weapon3
+ }
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondary", ref )
+ }
+ else if ( IsItemLocked( player, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON IS LOCKED, RESETTING" )
+ string ref = defaultLoadout.secondary
+ if ( loadout.weapon3 == ref ) // item dupes swap
+ {
+ ref = defaultLoadout.weapon3
+ }
+ else if ( ItemsInSameMenuCategory( loadout.weapon3, ref ) ) // category dupes assign value to other slot and swap
+ {
+ ref = defaultLoadout.weapon3
+ }
+
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondary", ref )
+ }
+ }
+
+ // secondary weapon mods
+ {
+ // mod1
+ if ( loadout.secondaryMod1 == "" )
+ {
+ // do nothing
+ }
+ else if ( !HasSubitem( loadout.secondary, loadout.secondaryMod1 ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 1 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod1", defaultLoadout.secondaryMod1 )
+ }
+ else if ( IsSubItemLocked( player, loadout.secondaryMod1, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 1 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod1", defaultLoadout.secondaryMod1 )
+ }
+ // mod2
+ if ( loadout.secondaryMod2 == "" )
+ {
+ // do nothing
+ }
+ else if ( IsSubItemLocked( player, "secondarymod2", loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 2 SLOT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod2", defaultLoadout.secondaryMod2 )
+ }
+ else if ( !HasSubitem( loadout.secondary, loadout.secondaryMod2 ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 2 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod2", defaultLoadout.secondaryMod2 )
+ }
+ else if ( IsSubItemLocked( player, loadout.secondaryMod2, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 2 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod2", defaultLoadout.secondaryMod2 )
+ }
+ else if ( loadout.secondaryMod2 == loadout.secondaryMod1 && loadout.secondaryMod2 != "" )
+ {
+ printt( " - SECONDARY WEAPON MOD 2 IS DUPLICATE, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod2", defaultLoadout.secondaryMod2 )
+ }
+ // mod3 (pro screen)
+ if ( loadout.secondaryMod3 == "" )
+ {
+ // do nothing
+ }
+ else if ( loadout.secondaryMod3 == "pro_screen" )
+ {
+ // fuck you and your three mod slot stuff
+ printt( " - SECONDARY WEAPON PRO SCREEN IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod3", defaultLoadout.secondaryMod3 )
+ }
+ else if ( IsSubItemLocked( player, "secondarymod3", loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON PRO SCREEN IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod3", defaultLoadout.secondaryMod3 )
+ }
+ }
+
+ // secondary weapon camoIndex
+ // secondary weapon skinIndex
+ {
+ if ( loadout.secondarySkinIndex == WEAPON_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.secondaryCamoIndex >= camoSkins.len() || loadout.secondaryCamoIndex < 0 )
+ {
+ printt( " - INVALID SECONDARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondarySkinIndex", defaultLoadout.secondarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryCamoIndex", defaultLoadout.secondaryCamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.secondaryCamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondarySkinIndex", defaultLoadout.secondarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryCamoIndex", defaultLoadout.secondaryCamoIndex )
+ }
+ }
+ }
+ else if ( loadout.secondarySkinIndex == 0 )
+ {
+ if ( loadout.secondaryCamoIndex != 0 )
+ {
+ printt( " - INVALID SECONDARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondarySkinIndex", defaultLoadout.secondarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryCamoIndex", defaultLoadout.secondaryCamoIndex )
+ }
+ }
+ else if ( IsSubItemLocked( player, GetWeaponWarpaintRefByIndex( loadout.secondarySkinIndex, loadout.secondary ), loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON SKIN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondarySkinIndex", defaultLoadout.secondarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryCamoIndex", defaultLoadout.secondaryCamoIndex )
+ }
+ }
+
+ // weapon3
+ // note: these are always eItemTypes.PILOT_SECONDARY
+ {
+ if ( !IsRefValid( loadout.weapon3 ) || GetItemType( loadout.weapon3 ) != eItemTypes.PILOT_SECONDARY )
+ {
+ printt( " - WEAPON3 WEAPON IS LOCKED, RESETTING" )
+ string ref = defaultLoadout.weapon3
+ if ( loadout.weapon3 == ref ) // item dupes swap
+ {
+ ref = defaultLoadout.secondary
+ }
+ else if ( ItemsInSameMenuCategory( loadout.weapon3, ref ) ) // category dupes assign value to other slot and swap
+ {
+ ref = defaultLoadout.secondary
+ }
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3", ref )
+ }
+ else if ( IsItemLocked( player, loadout.weapon3 ) )
+ {
+ printt( " - TERTIARY WEAPON IS LOCKED, RESETTING" )
+ string ref = defaultLoadout.weapon3
+ if ( loadout.secondary == ref ) // item dupes swap
+ {
+ ref = defaultLoadout.secondary
+ }
+ else if ( ItemsInSameMenuCategory( loadout.secondary, ref ) ) // category dupes assign value to other slot and swap
+ {
+ ref = defaultLoadout.secondary
+ }
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3", ref )
+ }
+ }
+
+ // weapon3 mods
+ {
+ // mod1
+ if ( loadout.weapon3Mod1 == "" )
+ {
+ // do nothing
+ }
+ else if ( !HasSubitem( loadout.weapon3, loadout.weapon3Mod1 ) )
+ {
+ printt( " - WEAPON3 MOD 1 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod1", defaultLoadout.weapon3Mod1 )
+ }
+ else if ( IsSubItemLocked( player, loadout.weapon3Mod1, loadout.weapon3 ) )
+ {
+ printt( " - WEAPON3 MOD 1 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod1", defaultLoadout.weapon3Mod1 )
+ }
+ // mod2
+ if ( loadout.weapon3Mod2 == "" )
+ {
+ // do nothing
+ }
+ else if ( IsSubItemLocked( player, "secondarymod2", loadout.weapon3 ) )
+ {
+ printt( " - WEAPON3 MOD 2 SLOT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod2", defaultLoadout.weapon3Mod2 )
+ }
+ else if ( !HasSubitem( loadout.weapon3, loadout.weapon3Mod2 ) )
+ {
+ printt( " - WEAPON3 MOD 2 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod2", defaultLoadout.weapon3Mod2 )
+ }
+ else if ( IsSubItemLocked( player, loadout.weapon3Mod2, loadout.weapon3 ) )
+ {
+ printt( " - WEAPON3 MOD 2 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod2", defaultLoadout.weapon3Mod2 )
+ }
+ else if ( loadout.weapon3Mod2 == loadout.weapon3Mod1 && loadout.weapon3Mod2 != "" )
+ {
+ printt( " - WEAPON3 MOD 2 IS DUPLICATE, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod2", defaultLoadout.weapon3Mod2 )
+ }
+ // mod3 (pro screen)
+ if ( loadout.weapon3Mod3 == "" )
+ {
+ // do nothing
+ }
+ else if ( loadout.weapon3Mod3 != "pro_screen" )
+ {
+ // fuck you and your three mod slot stuff
+ printt( " - WEAPON3 PRO SCREEN IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod3", defaultLoadout.weapon3Mod3 )
+ }
+ else if ( IsSubItemLocked( player, "secondarymod3", loadout.weapon3 ) )
+ {
+ printt( " - WEAPON3 PRO SCREEN IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod3", defaultLoadout.weapon3Mod3 )
+ }
+ }
+
+ // weapon3 camoIndex
+ // weapon3 skinIndex
+ {
+ if ( loadout.weapon3SkinIndex == WEAPON_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.weapon3CamoIndex >= camoSkins.len() || loadout.weapon3CamoIndex < 0 )
+ {
+ printt( " - INVALID TERTIARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3SkinIndex", defaultLoadout.weapon3SkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3CamoIndex", defaultLoadout.weapon3CamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.weapon3CamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.weapon3 ) )
+ {
+ printt( " - TERTIARY WEAPON CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3SkinIndex", defaultLoadout.weapon3SkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3CamoIndex", defaultLoadout.weapon3CamoIndex )
+ }
+ }
+ }
+ else if ( loadout.weapon3SkinIndex == 0 )
+ {
+ if ( loadout.weapon3CamoIndex != 0 )
+ {
+ printt( " - INVALID TERTIARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3SkinIndex", defaultLoadout.weapon3SkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3CamoIndex", defaultLoadout.weapon3CamoIndex )
+ }
+ }
+ else if ( IsSubItemLocked( player, GetWeaponWarpaintRefByIndex( loadout.weapon3SkinIndex, loadout.weapon3 ), loadout.weapon3 ) )
+ {
+ printt( " - TERTIARY WEAPON SKIN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3SkinIndex", defaultLoadout.weapon3SkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3CamoIndex", defaultLoadout.weapon3CamoIndex )
+ }
+ }
+
+ // kit 1
+ {
+ if ( !IsRefValid( loadout.passive1 ) || GetItemType( loadout.passive1 ) != eItemTypes.PILOT_PASSIVE1 )
+ {
+ printt( " - KIT 1 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].passive1", defaultLoadout.passive1 )
+ }
+ else if ( IsItemLocked( player, loadout.passive1 ) )
+ {
+ printt( " - KIT 1 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].passive1", defaultLoadout.passive1 )
+ }
+ }
+
+ // kit 2
+ {
+ if ( !IsRefValid( loadout.passive2 ) || GetItemType( loadout.passive2 ) != eItemTypes.PILOT_PASSIVE2 )
+ {
+ printt( " - KIT 2 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].passive2", defaultLoadout.passive2 )
+ }
+ else if ( IsItemLocked( player, loadout.passive2 ) )
+ {
+ printt( " - KIT 2 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].passive2", defaultLoadout.passive2 )
+ }
+ }
+
+ // execution
+ // note: not sure why defaultLoadout has this set to "", but neck snap should be default
+ {
+ if ( !IsRefValid( loadout.execution ) || GetItemType( loadout.execution ) != eItemTypes.PILOT_EXECUTION )
+ {
+ printt( " - EXECUTION IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].execution", "execution_neck_snap" )
+ }
+ else if ( IsItemLocked( player, loadout.execution ) )
+ {
+ printt( " - EXECUTION IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].execution", "execution_neck_snap" )
+ }
+ }
+
+ // equipped pilot loadout
+ {
+ if ( isSelected && IsItemLocked( player, "pilot_loadout_" + ( pilotLoadoutIndex + 1 ) ) )
+ {
+ printt( " - SELECTED PILOT LOADOUT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotSpawnLoadout.index", 0 )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_UpdatePilotModel", 0 )
+ }
+ }
+ }
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_UpdatePilotModel", player.GetPersistentVarAsInt( "pilotSpawnLoadout.index" ) )
+
+ printt( "ITEM VALIDATION COMPLETE FOR PLAYER: " + player.GetPlayerName() )
+}
+
+// basically just PopulateTitanLoadoutFromPersistentData but without validation, we are doing the validation in a better way
+// that doesnt just kick the player and reset the entire loadout, since we want to only reset parts of the loadout that we need
+TitanLoadoutDef function GetTitanLoadout( entity player, int loadoutIndex )
+{
+ TitanLoadoutDef loadout
+
+ loadout.name = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "name" )
+ loadout.titanClass = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "titanClass" )
+ loadout.primaryMod = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "primaryMod" )
+ loadout.special = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "special" )
+ loadout.antirodeo = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "antirodeo" )
+ loadout.passive1 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive1" )
+ loadout.passive2 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive2" )
+ loadout.passive3 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive3" )
+ loadout.passive4 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive4" )
+ loadout.passive5 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive5" )
+ loadout.passive6 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive6" )
+ loadout.camoIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "camoIndex" )
+ loadout.skinIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "skinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.decalIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "decalIndex" )
+ loadout.primaryCamoIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primaryCamoIndex" )
+ loadout.primarySkinIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primarySkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.titanExecution = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "titanExecution" )
+ loadout.showArmBadge = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "showArmBadge" )
+
+ //Prime Titan related vars
+ loadout.isPrime = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "isPrime" )
+ loadout.primeCamoIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primeCamoIndex" )
+ loadout.primeSkinIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primeSkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.primeDecalIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primeDecalIndex" )
+
+ UpdateDerivedTitanLoadoutData( loadout )
+ OverwriteLoadoutWithDefaultsForSetFile( loadout )
+
+ return loadout
+}
+
+// basically just PopulatePilotLoadoutFromPersistentData but without validation, we are doing the validation in a better way
+// that doesnt just kick the player and reset the entire loadout, since we want to only reset parts of the loadout that we need
+PilotLoadoutDef function GetPilotLoadout( entity player, int loadoutIndex )
+{
+ PilotLoadoutDef loadout
+
+ loadout.name = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "name" )
+ loadout.suit = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "suit" )
+ loadout.race = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "race" )
+ loadout.execution = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "execution" )
+ loadout.primary = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primary" )
+ loadout.primaryAttachment = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primaryAttachment" )
+ loadout.primaryMod1 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primaryMod1" )
+ loadout.primaryMod2 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primaryMod2" )
+ loadout.primaryMod3 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primaryMod3" )
+ loadout.secondary = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "secondary" )
+ loadout.secondaryMod1 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "secondaryMod1" )
+ loadout.secondaryMod2 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "secondaryMod2" )
+ loadout.secondaryMod3 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "secondaryMod3" )
+ loadout.weapon3 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "weapon3" )
+ loadout.weapon3Mod1 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "weapon3Mod1" )
+ loadout.weapon3Mod2 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "weapon3Mod2" )
+ loadout.weapon3Mod3 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "weapon3Mod3" )
+ loadout.ordnance = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "ordnance" )
+ loadout.passive1 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "passive1" )
+ loadout.passive2 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "passive2" )
+ loadout.camoIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "camoIndex" )
+ loadout.skinIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "skinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.primaryCamoIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "primaryCamoIndex" )
+ loadout.primarySkinIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "primarySkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.secondaryCamoIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "secondaryCamoIndex" )
+ loadout.secondarySkinIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "secondarySkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.weapon3CamoIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "weapon3CamoIndex" )
+ loadout.weapon3SkinIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "weapon3SkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+
+ UpdateDerivedPilotLoadoutData( loadout )
+
+ return loadout
+}
+
+bool function CanEquipArmBadge( entity player, string titanClass )
+{
+ string skinRef
+ switch ( titanClass )
+ {
+ case "ion":
+ skinRef = "ion_skin_fd"
+ break
+ case "scorch":
+ skinRef = "scorch_skin_fd"
+ break
+ case "northstar":
+ skinRef = "northstar_skin_fd"
+ break
+ case "ronin":
+ skinRef = "ronin_skin_fd"
+ break
+ case "tone":
+ skinRef = "tone_skin_fd"
+ break
+ case "legion":
+ skinRef = "legion_skin_fd"
+ break
+ case "vanguard":
+ skinRef = "monarch_skin_fd"
+ break
+ }
+
+ return !IsSubItemLocked( player, skinRef, titanClass )
+}
+
+string function GetWeaponWarpaintRefByIndex( int skinIndex, string parentRef )
+{
+ ItemData parentItem = GetItemData( parentRef )
+ foreach ( subItem in parentItem.subitems )
+ {
+ if ( GetSubitemType( parentRef, subItem.ref ) != eItemTypes.WEAPON_SKIN )
+ continue
+ if ( subItem.i.skinIndex != skinIndex )
+ continue
+
+ return subItem.ref
+ }
+
+ return INVALID_REF
+}
+#endif \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut
index 7f356a181..a2de99136 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut
@@ -386,8 +386,12 @@ float function RoundToNearestMultiplier( float value, float multiplier )
return value
}
-function DevEverythingUnlocked()
+function DevEverythingUnlocked( entity player = null )
{
+ // check if player has opted into progression or not
+ if ( player != null && ProgressionEnabledForPlayer( player ) )
+ return false
+
return EverythingUnlockedConVarEnabled()
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut
index 4bfeb4f8f..0436a393c 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut
@@ -10,5 +10,8 @@ void function AddTitanXP( entity player, int 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" )
+ }
} \ 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 8e1002576..4e25e3019 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut
@@ -12,8 +12,11 @@ void function AddWeaponXP( entity player, int 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" )
+ }
+
// proscreen
if ( player == activeWeapon.GetProScreenOwner() )
{