aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/mod/scripts
diff options
context:
space:
mode:
authorRoyalBlue <11448698+RoyalBlue1@users.noreply.github.com>2023-10-15 02:09:29 +0200
committerRoyalBlue <11448698+RoyalBlue1@users.noreply.github.com>2023-10-15 02:09:29 +0200
commitad70253601a77844347d8aa1989ae5ab4fb4217c (patch)
tree336778ee63ed5bd82a0245dda2eedd8c99e47e83 /Northstar.CustomServers/mod/scripts
parent9c13170e72f04050055695c25bc3b0a807d400e3 (diff)
parenta7d40aac072c6725548c2ba6d6a9ab649191594f (diff)
downloadNorthstarMods-ad70253601a77844347d8aa1989ae5ab4fb4217c.tar.gz
NorthstarMods-ad70253601a77844347d8aa1989ae5ab4fb4217c.zip
Merge remote-tracking branch 'upsteam/main' into gamemode_fd
Diffstat (limited to 'Northstar.CustomServers/mod/scripts')
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut4
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_items.nut20
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_utility_shared.nut3
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut39
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers.gnut13
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/conversation/_grunt_chatter_mp.gnut195
-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/gamemodes/_gamemode_at.nut20
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut9
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut12
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut34
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut4
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut5
-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/mp/spawn.nut9
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut4
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_northstar_utils.gnut6
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut1117
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut8
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut3
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut5
27 files changed, 2543 insertions, 76 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut
index da4e50f5..191c3b24 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut
@@ -27,7 +27,9 @@ HarvesterStruct function SpawnHarvester( vector origin, vector angles, int healt
harvester.SetShieldHealthMax( shieldHealth )
harvester.SetShieldHealth( shieldHealth )
harvester.EnableAttackableByAI( 30, 0, AI_AP_FLAG_NONE )
- SetObjectCanBeMeleed( harvester, false )
+ SetCustomSmartAmmoTarget( harvester, true )
+ SetObjectCanBeMeleed( harvester, true )
+ SetVisibleEntitiesInConeQueriableEnabled( harvester, true )
SetTeam(harvester,team)
// create dangerous area to all AI because we dont want any AI clipping into the harvester ever
// radius of 90 cos thats like 7.5 metres? AI shouldnt rally need to get closer than that (except nuke titans and stalkers)
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
index 539b72bc..a5c3e270 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
@@ -5698,7 +5698,13 @@ bool function IsUnlockValid( string ref, string parentRef = "" )
bool function IsSubItemLocked( entity player, string ref, string parentRef )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
+ return false
+
+ if ( IsItemPurchasableEntitlement( ref, parentRef ) )
+ return false
+
+ if ( GetItemType( ref ) == eItemTypes.PRIME_TITAN || GetSubitemType( parentRef, ref ) == eItemTypes.PRIME_TITAN )
return false
if ( IsItemInEntitlementUnlock( ref, parentRef ) )
@@ -5817,7 +5823,13 @@ bool function IsSubItemLocked( entity player, string ref, string parentRef )
bool function IsItemLocked( entity player, string ref )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
+ return false
+
+ if ( IsItemPurchasableEntitlement( ref ) )
+ return false
+
+ if ( GetItemType( ref ) == eItemTypes.PRIME_TITAN )
return false
if ( IsItemInEntitlementUnlock( ref ) )
@@ -5906,7 +5918,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 +6002,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 1092bf2d..6499faa2 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/_utility_shared.nut b/Northstar.CustomServers/mod/scripts/vscripts/_utility_shared.nut
index c5887f2b..47dd9294 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_utility_shared.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_utility_shared.nut
@@ -1601,6 +1601,9 @@ float function GetPulseFrac( rate = 1, startTime = 0 )
bool function IsPetTitan( titan )
{
Assert( titan.IsTitan() )
+
+ if ( !titan.GetTitanSoul() )
+ return false
return titan.GetTitanSoul().GetBossPlayer() != null
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
index 6f044b7a..2b95d1a8 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/ai/_ai_soldiers.gnut b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers.gnut
index 9717c76d..89fb7a82 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers.gnut
@@ -60,6 +60,19 @@ function AiSoldiers_Init()
level.COOP_AT_WEAPON_RATES[ "mp_weapon_smr" ] <- 0.4
level.COOP_AT_WEAPON_RATES[ "mp_weapon_mgl" ] <- 0.1
+ // add stub death callback, because in _codecallbacks_common.gnut there is
+ // CodeCallback_OnEntityKilled which is only called when an entity is being tracked. An
+ // entity is set to be tracked if it has a death callback for it's class, unfortunately this
+ // is then relayed to clients and used for client side death callbacks. The end result of
+ // not having this function called is that clients become completely unaware of any grunt
+ // deaths. A noticeable difference here is that grunts do not play the kill confirmed audio
+ // except on War Games, which does register a callback for grunt deaths to make them dissolve.
+ //
+ // Whilst this may seem like a bit of a hacky solution, it is generally better than simply
+ // tracking all entities. If a different callback is created in the future for grunt deaths
+ // that is not specific to a gamemode, map, etc. then this could be removed
+ AddDeathCallback( "npc_soldier", void function( entity guy, var damageInfo ){} )
+
PrecacheSprite( $"sprites/glow_05.vmt" )
FlagInit( "disable_npcs" )
FlagInit( "Disable_IMC" )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
index f193643c..eb4e111c 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
@@ -266,6 +266,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/conversation/_grunt_chatter_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/conversation/_grunt_chatter_mp.gnut
index 1a70c289..4eb423fd 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/conversation/_grunt_chatter_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/conversation/_grunt_chatter_mp.gnut
@@ -1,11 +1,33 @@
global function GruntChatter_MP_Init
global function PlayGruntChatterMPLine
+const float CHATTER_FRIENDLY_GRUNT_DOWN_DIST_MAX = 1100.0
+const float CHATTER_SQUAD_DEPLETED_FRIENDLY_NEARBY_DIST = 650.0 // if any other friendly grunt is within this dist, squad deplete chatter won't play
+const float CHATTER_ENEMY_TITAN_DOWN_DIST_MAX = 1500.0
+const float CHATTER_NEARBY_GRUNT_TRACEFRAC_MIN = 0.95 // for when we need "LOS" trace
+
void function GruntChatter_MP_Init()
{
- //ShGruntChatter_MP_Init()
+ Assert( IsMultiplayer(), "MP Grunt chatter is restricted to Multiplayer only." )
+
+ AddCallback_OnPlayerKilled( GruntChatter_OnPlayerOrNPCKilled )
+ AddCallback_OnNPCKilled( GruntChatter_OnPlayerOrNPCKilled )
}
+
+
+
+/*=====================================================================================================================================================
+ _____ _ _____ _ _ _ __ __ _ _ _ _
+ / ____| | | / ____|| | | | | | | \/ | | || | (_) | |
+ | | __ _ __ _ _ _ __ | |_ | | | |__ __ _ | |_ | |_ ___ _ __ | \ / | _ _ | || |_ _ _ __ | | __ _ _ _ ___ _ __
+ | | |_ || '__|| | | || '_ \ | __| | | | '_ \ / _` || __|| __|/ _ \| '__| | |\/| || | | || || __|| || '_ \ | | / _` || | | | / _ \| '__|
+ | |__| || | | |_| || | | || |_ | |____ | | | || (_| || |_ | |_| __/| | | | | || |_| || || |_ | || |_) || || (_| || |_| || __/| |
+ \_____||_| \__,_||_| |_| \__| \_____||_| |_| \__,_| \__| \__|\___||_| |_| |_| \__,_||_| \__||_|| .__/ |_| \__,_| \__, | \___||_|
+ | | __/ |
+ |_| |___/
+/*===================================================================================================================================================*/
+
void function PlayGruntChatterMPLine( entity grunt, string conversationType )
{
#if !GRUNT_CHATTER_MP_ENABLED
@@ -15,4 +37,175 @@ void function PlayGruntChatterMPLine( entity grunt, string conversationType )
foreach ( entity player in GetPlayerArray() )
if ( ShouldPlayGruntChatterMPLine( conversationType, player, grunt ) )
Remote_CallFunction_Replay( player, "ServerCallback_PlayGruntChatterMP", GetConversationIndex( conversationType ), grunt.GetEncodedEHandle() )
+}
+
+void function GruntChatter_OnPlayerOrNPCKilled( entity deadGuy, entity attacker, var damageInfo )
+{
+ if ( !IsValid( deadGuy ) || !IsValid( attacker ) )
+ return
+
+ if( IsGrunt( attacker ) && IsPilot( deadGuy ) )
+ PlayGruntChatterMPLine( attacker, "bc_killenemypilot" )
+ else
+ GruntChatter_TryEnemyTitanDown( deadGuy )
+
+ if ( IsGrunt( deadGuy ) )
+ {
+ GruntChatter_TryFriendlyDown( deadGuy )
+ GruntChatter_TrySquadDepleted( deadGuy )
+ }
+}
+
+void function GruntChatter_TryFriendlyDown( entity deadGuy )
+{
+ entity closestGrunt = GruntChatter_FindClosestFriendlyHumanGrunt_LOS( deadGuy.GetOrigin(), deadGuy.GetTeam(), CHATTER_FRIENDLY_GRUNT_DOWN_DIST_MAX )
+ if ( !closestGrunt )
+ return
+
+ if ( !GruntChatter_CanGruntChatterNow( closestGrunt ) )
+ return
+
+ PlayGruntChatterMPLine( closestGrunt, "bc_allygruntdown" )
+}
+
+void function GruntChatter_TrySquadDepleted( entity deadGuy )
+{
+ string deadGuySquadName = expect string( deadGuy.kv.squadname )
+ if ( deadGuySquadName == "" )
+ return
+
+ array<entity> squad = GetNPCArrayBySquad( deadGuySquadName )
+ entity lastSquadMember
+ if ( squad.len() == 1 )
+ lastSquadMember = squad[0]
+
+ if ( !GruntChatter_CanGruntChatterNow( lastSquadMember ) )
+ return
+
+ if ( lastSquadMember.GetNPCState() == "idle" )
+ return
+
+ // if another grunt from another squad is nearby, don't chatter about being alone
+ array<entity> nearbyGrunts = GetNearbyFriendlyGrunts( lastSquadMember.GetOrigin(), lastSquadMember.GetTeam(), CHATTER_SQUAD_DEPLETED_FRIENDLY_NEARBY_DIST )
+ nearbyGrunts.fastremovebyvalue( lastSquadMember )
+ if ( nearbyGrunts.len() )
+ return
+
+ PlayGruntChatterMPLine( lastSquadMember, "bc_squaddeplete" )
+}
+
+void function GruntChatter_TryEnemyTitanDown( entity deadGuy )
+{
+ if ( deadGuy.IsTitan() )
+ {
+ entity closestGrunt = GruntChatter_FindClosestEnemyHumanGrunt_LOS( deadGuy.GetOrigin(), deadGuy.GetTeam(), CHATTER_ENEMY_TITAN_DOWN_DIST_MAX )
+ if ( !closestGrunt )
+ return
+
+ PlayGruntChatterMPLine( closestGrunt, "bc_enemytitandown" )
+ }
+}
+
+entity function GruntChatter_FindClosestEnemyHumanGrunt_LOS( vector searchOrigin, int enemyTeam, float searchDist )
+{
+ array<entity> humanGrunts = GetNearbyEnemyHumanGrunts( searchOrigin, enemyTeam, searchDist )
+ return GruntChatter_GetClosestGrunt_LOS( humanGrunts, searchOrigin )
+}
+
+entity function GruntChatter_FindClosestFriendlyHumanGrunt_LOS( vector searchOrigin, int friendlyTeam, float searchDist )
+{
+ array<entity> humanGrunts = GetNearbyFriendlyHumanGrunts( searchOrigin, friendlyTeam, searchDist )
+ return GruntChatter_GetClosestGrunt_LOS( humanGrunts, searchOrigin )
+}
+
+entity function GruntChatter_GetClosestGrunt_LOS( array<entity> nearbyGrunts, vector searchOrigin )
+{
+ entity closestGrunt = null
+ float closestDist = 10000
+
+ foreach ( grunt in nearbyGrunts )
+ {
+ vector gruntOrigin = grunt.GetOrigin()
+
+ // CanSee doesn't return true if the target is dead
+ if ( !GruntChatter_CanGruntTraceToLocation( grunt, searchOrigin ) )
+ continue
+
+ if ( !closestGrunt )
+ {
+ closestGrunt = grunt
+ continue
+ }
+
+ float distFromSearchOrigin = Distance( grunt.GetOrigin(), searchOrigin )
+
+ if ( closestDist > distFromSearchOrigin )
+ continue
+
+ closestGrunt = grunt
+ closestDist = distFromSearchOrigin
+ }
+
+ return closestGrunt
+}
+
+bool function GruntChatter_CanGruntTraceToLocation( entity grunt, vector traceEnd )
+{
+ float traceFrac = TraceLineSimple( grunt.GetOrigin(), traceEnd, grunt )
+ return traceFrac > CHATTER_NEARBY_GRUNT_TRACEFRAC_MIN
+}
+
+array<entity> function GetNearbyFriendlyHumanGrunts( vector searchOrigin, int friendlyTeam, float ornull searchRange = null )
+{
+ array<entity> nearbyGrunts = GetNearbyFriendlyGrunts( searchOrigin, friendlyTeam, searchRange )
+ array<entity> humanGrunts = []
+ foreach ( grunt in nearbyGrunts )
+ {
+ if ( grunt.IsMechanical() )
+ continue
+
+ humanGrunts.append( grunt )
+ }
+
+ return humanGrunts
+}
+
+array<entity> function GetNearbyEnemyHumanGrunts( vector searchOrigin, int enemyTeam, float ornull searchRange = null )
+{
+ array<entity> nearbyGrunts = GetNearbyEnemyGrunts( searchOrigin, enemyTeam, searchRange )
+ array<entity> humanGrunts = []
+ foreach ( grunt in nearbyGrunts )
+ {
+ if ( grunt.IsMechanical() )
+ continue
+
+ humanGrunts.append( grunt )
+ }
+
+ return humanGrunts
+}
+
+bool function GruntChatter_CanGruntChatterNow( entity grunt )
+{
+ if ( !IsAlive( grunt ) )
+ return false
+
+ if ( !GruntChatter_IsGruntTypeEligibleForChatter( grunt ) )
+ return false
+
+ if ( grunt.ContextAction_IsMeleeExecution() )
+ return false
+
+ string squadname = expect string( grunt.kv.squadname )
+ // we only care about this because the grunt conversation system wants it
+ return squadname != ""
+}
+
+bool function GruntChatter_IsGruntTypeEligibleForChatter( entity grunt )
+{
+ if ( !IsGrunt( grunt ) )
+ return false
+
+ // mechanical grunts don't chatter
+ return !grunt.IsMechanical()
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
index f23c841d..af074689 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 5fd7d101..6555c875 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/gamemodes/_gamemode_at.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
index c61cb585..93a3aa16 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
@@ -107,7 +107,6 @@ void function GamemodeAt_Init()
// Set-up score callbacks
ScoreEvent_SetupEarnMeterValuesForMixedModes()
- AddDamageFinalCallback( "npc_titan", OnNPCTitanFinalDamaged )
AddCallback_OnPlayerKilled( AT_PlayerOrNPCKilledScoreEvent )
AddCallback_OnNPCKilled( AT_PlayerOrNPCKilledScoreEvent )
@@ -1658,9 +1657,10 @@ void function AT_HandleBossTitanSpawn( entity titan, AT_WaveOrigin campData, int
titan.Minimap_AlwaysShow( TEAM_MILITIA, null )
thread BountyBossHighlightThink( titan )
- // set up titan-specific death callbacks, mark it as bounty boss for finalDamageCallbacks to work
+ // set up titan-specific death callbacks, mark it as bounty boss
file.titanIsBountyBoss[ titan ] <- true
file.bountyTitanRewards[ titan ] <- ATTRITION_SCORE_BOSS_DAMAGE
+ AddEntityCallback_OnPostDamaged( titan, OnBountyTitanPostDamage )
AddEntityCallback_OnKilled( titan, OnBountyTitanKilled )
titan.GetTitanSoul().soul.skipDoomState = true
@@ -1684,13 +1684,7 @@ void function BountyBossHighlightThink( entity titan )
}
}
-void function OnNPCTitanFinalDamaged( entity titan, var damageInfo )
-{
- if ( titan in file.titanIsBountyBoss )
- OnBountyTitanDamaged( titan, damageInfo )
-}
-
-void function OnBountyTitanDamaged( entity titan, var damageInfo )
+void function OnBountyTitanPostDamage( entity titan, var damageInfo )
{
entity attacker = DamageInfo_GetAttacker( damageInfo )
if ( !IsValid( attacker ) ) // delayed by projectile shots
@@ -1703,14 +1697,6 @@ void function OnBountyTitanDamaged( entity titan, var damageInfo )
return
}
- // respawn FUCKED UP pilot weapon against titan's damage calculation, have to copy-paste this check from Titan_NPCTookDamage()
- if ( HeavyArmorCriticalHitRequired( damageInfo ) &&
- CritWeaponInDamageInfo( damageInfo ) &&
- !IsCriticalHit( attacker, titan, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) ) &&
- IsValid( attacker ) &&
- !attacker.IsTitan() )
- return
-
int rewardSegment = ATTRITION_SCORE_BOSS_DAMAGE
int healthSegment = titan.GetMaxHealth() / rewardSegment
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut
index 6f228f7c..60bad8d1 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut
@@ -20,6 +20,7 @@ void function Sv_ItemInventory_Init()
{
AddCallback_OnClientConnected( Sv_ItemInventory_OnClientConnected )
AddCallback_OnPlayerGetsNewPilotLoadout( Sv_ItemInventory_OnPlayerGetsNewPilotLoadout )
+ AddCallback_GameStateEnter( eGameState.Prematch, PrematchClearInventory )
}
void function Sv_ItemInventory_OnClientConnected( entity player )
@@ -27,6 +28,14 @@ void function Sv_ItemInventory_OnClientConnected( entity player )
file.playerInventoryStacks[ player ] <- []
}
+void function PrematchClearInventory() // vanilla behavior
+{
+ foreach( entity player in GetPlayerArray() )
+ {
+ PlayerInventory_TakeAllInventoryItems( player )
+ }
+}
+
void function Sv_ItemInventory_OnPlayerGetsNewPilotLoadout( entity player, PilotLoadoutDef newPilotLoadout )
{
array<InventoryItem> playerInventoryStack = file.playerInventoryStacks[ player ]
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
index ae933b71..8b65ec93 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/lobby/sh_private_lobby_modes_init.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut
index ccccefaf..d2be2ab4 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut
@@ -17,6 +17,7 @@ void function PrivateMatchModesInit()
AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_PILOT", "boosts_enabled", [ "#SETTING_DEFAULT", "#SETTING_DISABLED" ], "1" )
AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_PILOT", "earn_meter_pilot_overdrive", [ "#SETTING_DISABLED", "#SETTING_ENABLED", "Only" ], "1" )
AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_PILOT", "earn_meter_pilot_multiplier", "1.0" )
+ AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_PILOT", "player_force_respawn", "5" )
AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_TITAN", "earn_meter_titan_multiplier", "1.0" )
AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_TITAN", "aegis_upgrades", [ "#SETTING_DISABLED", "#SETTING_ENABLED" ], "0" )
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 ec426754..bab7eaed 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
@@ -274,6 +283,9 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga
ClearRespawnAvailable( player )
+ // reset this so that we default to pilot spawn
+ player.SetPersistentVar( "spawnAsTitan", false )
+
OnThreadEnd( function() : ( player )
{
if ( !IsValid( player ) )
@@ -379,6 +391,13 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga
SetRespawnAvailable( player )
wait respawnDelay
+
+ int forceRespawn = GetCurrentPlaylistVarInt( "player_force_respawn", -1 )
+
+ // -1 is disabled, anything over is the time we wait in seconds
+ // before respawning the player
+ if( forceRespawn >= 0 )
+ thread ForceRespawnMeSignalAfterDelay( player, forceRespawn )
player.WaitSignal( "RespawnMe" ) // set in base_gametype: ClientCommand_RespawnPlayer
@@ -398,6 +417,21 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga
}
}
+// idk if this is a good delay or if it matches vanilla
+void function ForceRespawnMeSignalAfterDelay( entity player, int delay = 5 )
+{
+ player.EndSignal( "RespawnMe" )
+ player.EndSignal( "OnDestroy" )
+
+ if( player.IsWatchingKillReplay() )
+ player.WaitSignal( "KillCamOver" )
+
+ wait delay
+
+ printt( format( "Forcing player respawn for player %s (took >%d seconds to respawn)", player.GetPlayerName(), delay ) )
+ player.Signal( "RespawnMe" )
+}
+
void function PlayerWatchesKillReplayWrapper( entity player, entity attacker, float timeSinceAttackerSpawned, float timeOfDeath, float beforeTime, table replayTracker )
{
player.EndSignal( "RespawnMe" )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut
index 16a3ce92..0ababfc7 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/_classic_mp_dropship_intro.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
index 0555df9b..23ae37a1 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
@@ -188,7 +188,8 @@ void function SpawnPlayerIntoDropship( entity player )
}
// respawn player and holster their weapons so they aren't out
- player.RespawnPlayer( null )
+ if ( !IsAlive( player ) )
+ player.RespawnPlayer( null )
HolsterAndDisableWeapons(player)
player.DisableWeaponViewModel()
@@ -255,4 +256,4 @@ void function PlayerJumpsFromDropship( entity player )
WaitFrame()
TryGameModeAnnouncement( player )
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
index 46b39ebc..3426cec5 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 dacd43b0..0b55e9ff 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 0e8b58f4..208e6da1 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.WinnerDetermined, OnWinnerDetermined )
+ 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, string statAlias = "" )
{
+ if ( !IsValid( player ) )
+ return
+
+ Stats_IncrementStat( player, statCategory, subStat, statAlias, 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 OnWinnerDetermined()
+{
+ // 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/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
index 5bf150c0..4956375b 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
@@ -261,9 +261,12 @@ bool function IsSpawnpointValid( entity spawnpoint, int team )
return false
}
- array<entity> projectiles = GetProjectileArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), 600 )
- foreach ( entity projectile in projectiles )
- if ( projectile.GetTeam() != team )
+ const minEnemyDist = 1000.0 // about 20 meters?
+ // in rsquirrel extend returns null unlike in vanilla squirrel
+ array< entity > spawnBlockers = GetPlayerArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), minEnemyDist )
+ spawnBlockers.extend( GetProjectileArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), minEnemyDist ) )
+ foreach ( entity blocker in spawnBlockers )
+ if ( blocker.GetTeam() != team )
return false
// los check
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut
index 85f5aa05..7a7498b8 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_northstar_utils.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_northstar_utils.gnut
index b26e48ca..f8597744 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/sh_northstar_utils.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_northstar_utils.gnut
@@ -1,11 +1,5 @@
globalize_all_functions
-// whether the server is a modded, northstar server
-bool function IsNorthstarServer()
-{
- return GetConVarBool( "ns_is_modded_server" )
-}
-
// whether the game should return to the lobby on GameRules_EndMatch()
bool function ShouldReturnToLobby()
{
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 00000000..ceb5e837
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut
@@ -0,0 +1,1117 @@
+global function Progression_Init
+global function ProgressionEnabledForPlayer
+#if CLIENT || UI
+global function Progression_SetPreference
+global function Progression_GetPreference
+global function UpdateCachedLoadouts_Delayed
+#endif
+
+#if SP // literally just stub the global functions and call it a day
+
+void function Progression_Init() {}
+bool function ProgressionEnabledForPlayer( entity player ) { return false }
+#if CLIENT || UI
+void function Progression_SetPreference( bool enabled ) {}
+bool function Progression_GetPreference() { return false }
+void function UpdateCachedLoadouts_Delayed() {}
+#endif // CLIENT || UI
+
+#else // MP || UI basically
+
+// 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
+ {
+ string warpaintRef = GetWeaponWarpaintRefByIndex( loadout.primarySkinIndex, loadout.primary )
+ if ( warpaintRef == INVALID_REF || IsSubItemLocked( player, warpaintRef, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON SKIN LOCKED/INVALID, 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
+ {
+ string warpaintRef = GetWeaponWarpaintRefByIndex( loadout.secondarySkinIndex, loadout.secondary )
+ if ( warpaintRef == INVALID_REF || IsSubItemLocked( player, warpaintRef, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON SKIN LOCKED/INVALID, 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
+ {
+ string warpaintRef = GetWeaponWarpaintRefByIndex( loadout.weapon3SkinIndex, loadout.weapon3 )
+ if ( warpaintRef == INVALID_REF || IsSubItemLocked( player, warpaintRef, loadout.weapon3 ) )
+ {
+ printt( " - TERTIARY WEAPON SKIN LOCKED/INVALID, 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 // SERVER
+
+#endif // MP
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut
index 7f356a18..9e762985 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut
@@ -362,7 +362,7 @@ string function StringReplace( string baseString, string searchString, string re
source = part1 + replaceString + part2
loopedOnce = true
- findResult = source.find( searchString )
+ findResult = source.find( searchString, findResult + replaceString.len() )
}
return baseString
@@ -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 4bfeb4f8..0436a393 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 8e100257..4e25e301 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() )
{