aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers
diff options
context:
space:
mode:
authorRoyalBlue <11448698+RoyalBlue1@users.noreply.github.com>2023-05-12 00:02:08 +0200
committerRoyalBlue <11448698+RoyalBlue1@users.noreply.github.com>2023-05-12 00:02:08 +0200
commit71ab870841c4ed048887289e9ad3ed9ff0c25c35 (patch)
tree516483c9a172cedadfd3716e250c65cc8b4ae910 /Northstar.CustomServers
parentdd457ef265f440bbcb4ff6f07bfc76ea1661d40b (diff)
parentd205d4440ffbf0947ca5cf4a2705157ba2d873e2 (diff)
downloadNorthstarMods-71ab870841c4ed048887289e9ad3ed9ff0c25c35.tar.gz
NorthstarMods-71ab870841c4ed048887289e9ad3ed9ff0c25c35.zip
Merge remote-tracking branch 'upsteam/main' into gamemode_fd
Diffstat (limited to 'Northstar.CustomServers')
-rw-r--r--Northstar.CustomServers/mod.json2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut29
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut23
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut119
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut157
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut1839
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut17
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut17
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut14
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_store.gnut933
11 files changed, 2841 insertions, 310 deletions
diff --git a/Northstar.CustomServers/mod.json b/Northstar.CustomServers/mod.json
index ab03a425..a54f8ee2 100644
--- a/Northstar.CustomServers/mod.json
+++ b/Northstar.CustomServers/mod.json
@@ -1,7 +1,7 @@
{
"Name": "Northstar.CustomServers",
"Description": "Attempts to recreate the behaviour of vanilla Titanfall 2 servers, as well as changing some scripts to allow better support for mods",
- "Version": "1.10.0",
+ "Version": "1.13.0",
"LoadPriority": 0,
"ConVars": [
{
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut
index d2621db3..3704b5cc 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut
@@ -46,6 +46,8 @@ global function CodeCallback_OnEntityChangedTeam
global function AddEntityCallback_OnDamaged
global function RemoveEntityCallback_OnDamaged
+global function AddEntityCallback_OnFinalDamaged
+global function RemoveEntityCallback_OnFinalDamaged
global function AddEntityCallback_OnPostDamaged
global function RemoveEntityCallback_OnPostDamaged
global function AddEntityCallback_OnKilled
@@ -121,6 +123,13 @@ void function CodeCallback_DamageEntity( entity ent, var damageInfo )
printt( " after class damage final callbacks:", DamageInfo_GetDamage( damageInfo ) )
#endif
+ foreach ( callbackFunc in ent.e.entFinalDamageCallbacks )
+ callbackFunc( ent, damageInfo )
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after AddEntityCallback_OnFinalDamaged callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
// make destructible vehicles take more damage from DF_EXPLOSION damage type
if ( "isDestructibleVehicle" in ent.s && DamageInfo_GetCustomDamageType( damageInfo ) & DF_EXPLOSION )
{
@@ -566,7 +575,23 @@ void function RemoveEntityCallback_OnDamaged( entity ent, void functionref( enti
Assert( index != -1, "Requested DamageCallback " + string( callbackFunc ) + " to be removed not found! " )
ent.e.entDamageCallbacks.fastremove( index )
- if ( ent.e.entDamageCallbacks.len() == 0 && ent.e.entPostDamageCallbacks.len() == 0 )
+ if ( ent.e.entDamageCallbacks.len() == 0 && ent.e.entFinalDamageCallbacks.len() == 0 && ent.e.entPostDamageCallbacks.len() == 0 )
+ ent.SetDamageNotifications( false )
+}
+
+void function AddEntityCallback_OnFinalDamaged( entity ent, void functionref( entity ent, var damageInfo ) callbackFunc )
+{
+ Assert( !ent.e.entFinalDamageCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " to entity" )
+
+ ent.SetDamageNotifications( true )
+ ent.e.entFinalDamageCallbacks.append( callbackFunc )
+}
+
+void function RemoveEntityCallback_OnFinalDamaged( entity ent, void functionref( entity ent, var damageInfo ) callbackFunc )
+{
+ ent.e.entFinalDamageCallbacks.fastremovebyvalue( callbackFunc )
+
+ if ( ent.e.entFinalDamageCallbacks.len() == 0 && ent.e.entPostDamageCallbacks.len() == 0 && ent.e.entDamageCallbacks.len() == 0 )
ent.SetDamageNotifications( false )
}
@@ -585,7 +610,7 @@ void function RemoveEntityCallback_OnPostDamaged( entity ent, void functionref(
Assert( index != -1, "Requested PostDamageCallback " + string( callbackFunc ) + " to be removed not found! " )
ent.e.entPostDamageCallbacks.fastremove( index )
- if ( ent.e.entPostDamageCallbacks.len() == 0 && ent.e.entDamageCallbacks.len() == 0 )
+ if ( ent.e.entPostDamageCallbacks.len() == 0 && ent.e.entDamageCallbacks.len() == 0 && ent.e.entFinalDamageCallbacks.len() == 0 )
ent.SetDamageNotifications( false )
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut
index 378ceae3..9dadea15 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut
@@ -232,6 +232,7 @@ global struct ServerEntityStruct
SpawnPointData spawnPointData
array<void functionref( entity ent, var damageInfo )> entDamageCallbacks
+ array<void functionref( entity ent, var damageInfo )> entFinalDamageCallbacks
array<void functionref( entity ent, var damageInfo )> entPostDamageCallbacks
array<void functionref( entity titan, entity attacker )> entSegmentLostCallbacks
array<void functionref( entity ent, var damageInfo, float actualShieldDamage )> entPostShieldDamageCallbacks
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut
index 3546e3b7..4e5c5aa9 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut
@@ -4017,7 +4017,7 @@ int function GameTime_TimeLeftMinutes()
if ( GetGameState() == eGameState.Prematch )
return int( ( expect float( GetServerVar( "gameStartTime" ) ) - Time()) / 60.0 )
- return floor( GameTime_TimeLimitMinutes() - GameTime_PlayingTime() / 60 ).tointeger()
+ return floor( GameTime_PlayingTime() / 60 ).tointeger()
}
int function GameTime_TimeLeftSeconds()
@@ -4025,30 +4025,25 @@ int function GameTime_TimeLeftSeconds()
if ( GetGameState() == eGameState.Prematch )
return int( expect float( GetServerVar( "gameStartTime" ) ) - Time() )
- return floor( GameTime_TimeLimitSeconds() - GameTime_PlayingTime() ).tointeger()
+ return GameTime_PlayingTime().tointeger()
}
+// WARN: this function includes WaitingForPlayers and Prematch duration!
int function GameTime_Seconds()
{
return floor( Time() ).tointeger()
}
+// WARN: this function includes WaitingForPlayers Prematch duration!
int function GameTime_Minutes()
{
return int( floor( GameTime_Seconds() / 60 ) )
}
+// this function only counts the time limit during eGameState.Playing
float function GameTime_PlayingTime()
{
- return GameTime_PlayingTimeSince( Time() )
-}
-
-float function GameTime_PlayingTimeSince( float sinceTime )
-{
int gameState = GetGameState()
-
- // temp fix because i have no fucking clue why this crashes
-
if ( gameState < eGameState.Playing )
return 0
@@ -4057,17 +4052,15 @@ float function GameTime_PlayingTimeSince( float sinceTime )
if ( gameState > eGameState.SuddenDeath )
return (expect float( GetServerVar( "roundEndTime" ) ) - expect float( GetServerVar( "roundStartTime" ) ) )
else
- return sinceTime - expect float( GetServerVar( "roundStartTime" ) )
-
+ return floor( expect float( GetServerVar( "roundEndTime" ) ) - Time() )
}
else
{
if ( gameState > eGameState.SuddenDeath )
return (expect float( GetServerVar( "gameEndTime" ) ) - expect float( GetServerVar( "gameStartTime" ) ) )
else
- return sinceTime - expect float( GetServerVar( "gameStartTime" ) )
+ return floor( expect float( GetServerVar( "gameEndTime" ) ) - Time() )
}
-
unreachable
}
@@ -4411,4 +4404,4 @@ bool function PlayerHasTitan( entity player )
return true
return false
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
index 0fad768c..4ed7ee4a 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
@@ -1,7 +1,6 @@
global function AiGameModes_Init
-global function AiGameModes_SetGruntWeapons
-global function AiGameModes_SetSpectreWeapons
+global function AiGameModes_SetNPCWeapons
global function AiGameModes_SpawnDropShip
global function AiGameModes_SpawnDropPod
@@ -15,25 +14,20 @@ const INTRO_DROPSHIP_CUTOFF = 2000
struct
{
- array< string > gruntWeapons = [ "mp_weapon_rspn101" ]
- array< string > spectreWeapons = [ "mp_weapon_hemlok_smg" ]
+ table< string, array<string> > npcWeaponsTable // npcs have their default weapon in aisettings file
} file
void function AiGameModes_Init()
{
-
}
//------------------------------------------------------
-void function AiGameModes_SetGruntWeapons( array< string > weapons )
+void function AiGameModes_SetNPCWeapons( string npcClass, array<string> weapons )
{
- file.gruntWeapons = weapons
-}
-
-void function AiGameModes_SetSpectreWeapons( array< string > weapons )
-{
- file.spectreWeapons = weapons
+ if ( !( npcClass in file.npcWeaponsTable ) )
+ file.npcWeaponsTable[ npcClass ] <- []
+ file.npcWeaponsTable[ npcClass ] = weapons
}
//------------------------------------------------------
@@ -59,7 +53,7 @@ void function AiGameModes_SpawnDropShip( vector pos, vector rot, int team, int c
foreach ( guy in guys )
{
- ReplaceWeapon( guy, file.gruntWeapons[ RandomInt( file.gruntWeapons.len() ) ], [] )
+ SetUpNPCWeapons( guy )
guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
}
@@ -68,31 +62,23 @@ void function AiGameModes_SpawnDropShip( vector pos, vector rot, int team, int c
}
-void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string content /*( ͡° ͜ʖ ͡°)*/, void functionref( array<entity> guys ) squadHandler = null )
+void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string content /*( ͡° ͜ʖ ͡°)*/, void functionref( array<entity> guys ) squadHandler = null, int flags = 0 )
{
- string squadName = MakeSquadName( team, UniqueString( "" ) )
- array<entity> guys
-
entity pod = CreateDropPod( pos, <0,0,0> )
- InitFireteamDropPod( pod )
-
+ InitFireteamDropPod( pod, flags )
+
+ waitthread LaunchAnimDropPod( pod, "pod_testpath", pos, rot )
+
+ string squadName = MakeSquadName( team, UniqueString( "" ) )
+ array<entity> guys
for ( int i = 0; i < 4 ;i++ )
{
entity npc = CreateNPC( content, team, pos,<0,0,0> )
DispatchSpawn( npc )
SetSquad( npc, squadName )
- switch ( content )
- {
- case "npc_soldier":
- ReplaceWeapon( npc, file.gruntWeapons[ RandomInt( file.gruntWeapons.len() ) ], [] )
- break
-
- case "npc_spectre":
- ReplaceWeapon( npc, file.spectreWeapons[ RandomInt( file.spectreWeapons.len() ) ], [] )
- break
- }
+ SetUpNPCWeapons( npc )
npc.SetParent( pod, "ATTACH", true )
@@ -100,25 +86,26 @@ void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string
guys.append( npc )
}
- // The order here is different so we can show on minimap while were still falling
+ ActivateFireteamDropPod( pod, guys )
+
+ // start searching for enemies
if ( squadHandler != null )
thread squadHandler( guys )
-
- waitthread LaunchAnimDropPod( pod, "pod_testpath", pos, rot )
-
- ActivateFireteamDropPod( pod, guys )
}
+const float REAPER_WARPFALL_DELAY = 4.7 // same as fd does
void function AiGameModes_SpawnReaper( vector pos, vector rot, int team, string aiSettings = "", void functionref( entity reaper ) reaperHandler = null )
{
- thread Reaper_Spawnpoint( pos, team, 11.2 )
+ float reaperLandTime = REAPER_WARPFALL_DELAY + 1.2 // reaper takes ~1.2s to warpfall
+ thread HotDrop_Spawnpoint( pos, team, reaperLandTime, false, damagedef_reaper_fall )
- wait 10
- // spawn reapers right before it warpfalls, or round_end clean up will crash the game
+ wait REAPER_WARPFALL_DELAY
entity reaper = CreateSuperSpectre( team, pos, rot )
+ reaper.EndSignal( "OnDestroy" )
// reaper highlight
Highlight_SetFriendlyHighlight( reaper, "sp_enemy_pilot" )
- reaper.Highlight_SetParam( 1, 0, < 3,3,3 > )
+ reaper.Highlight_SetParam( 1, 0, < 1,1,1 > )
+ SetDefaultMPEnemyHighlight( reaper )
Highlight_SetEnemyHighlight( reaper, "enemy_titan" )
SetSpawnOption_Titanfall( reaper )
@@ -127,15 +114,18 @@ void function AiGameModes_SpawnReaper( vector pos, vector rot, int team, string
if ( aiSettings != "" )
SetSpawnOption_AISettings( reaper, aiSettings )
+ HideName( reaper ) // prevent flash a name onto it
DispatchSpawn( reaper )
-
+
+ reaper.WaitSignal( "WarpfallComplete" )
+ ShowName( reaper ) // show name again after drop
if ( reaperHandler != null )
thread reaperHandler( reaper )
}
// copied from cl_replacement_titan_hud.gnut
-void function Reaper_Spawnpoint( vector origin, int team, float impactTime, bool hasFriendlyWarning = false )
+void function HotDrop_Spawnpoint( vector origin, int team, float impactTime, bool hasFriendlyWarning = false, int damageDef = -1 )
{
array<entity> targetEffects = []
vector surfaceNormal = < 0, 0, 1 >
@@ -146,32 +136,50 @@ void function Reaper_Spawnpoint( vector origin, int team, float impactTime, bool
{
entity effectFriendly = StartParticleEffectInWorld_ReturnEntity( index, origin, surfaceNormal )
SetTeam( effectFriendly, team )
- EffectSetControlPointVector( effectFriendly, 1, < 128,188,255 > )
+ EffectSetControlPointVector( effectFriendly, 1, FRIENDLY_COLOR_FX )
effectFriendly.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
+ effectFriendly.DisableHibernation() // prevent it from fading out
targetEffects.append( effectFriendly )
}
entity effectEnemy = StartParticleEffectInWorld_ReturnEntity( index, origin, surfaceNormal )
SetTeam( effectEnemy, team )
- EffectSetControlPointVector( effectEnemy, 1, < 255,99,0 > )
+ EffectSetControlPointVector( effectEnemy, 1, ENEMY_COLOR_FX )
effectEnemy.kv.VisibilityFlags = ENTITY_VISIBLE_TO_ENEMY
+ effectEnemy.DisableHibernation() // prevent it from fading out
targetEffects.append( effectEnemy )
-
+
+ // so enemy npcs will mostly avoid them
+ entity damageAreaInfo
+ if ( damageDef > -1 )
+ {
+ damageAreaInfo = CreateEntity( "info_target" )
+ DispatchSpawn( damageAreaInfo )
+ AI_CreateDangerousArea_DamageDef( damageDef, damageAreaInfo, team, true, true )
+ }
+
wait impactTime
+ // clean up
foreach( entity targetEffect in targetEffects )
{
if ( IsValid( targetEffect ) )
EffectStop( targetEffect )
}
+ if ( IsValid( damageAreaInfo ) )
+ damageAreaInfo.Destroy()
}
// including aisettings stuff specifically for at bounty titans
+const float TITANFALL_WARNING_DURATION = 5.0
void function AiGameModes_SpawnTitan( vector pos, vector rot, int team, string setFile, string aiSettings = "", void functionref( entity titan ) titanHandler = null )
{
entity titan = CreateNPCTitan( setFile, TEAM_BOTH, pos, rot )
SetSpawnOption_Titanfall( titan )
SetSpawnOption_Warpfall( titan )
+
+ // modified: do a hotdrop spawnpoint warning
+ thread HotDrop_Spawnpoint( pos, team, TITANFALL_WARNING_DURATION, false, damagedef_titan_fall )
if ( aiSettings != "" )
SetSpawnOption_AISettings( titan, aiSettings )
@@ -182,12 +190,27 @@ void function AiGameModes_SpawnTitan( vector pos, vector rot, int team, string s
thread titanHandler( titan )
}
-// entity.ReplaceActiveWeapon gave grunts archers sometimes, this is my replacement for it
-void function ReplaceWeapon( entity guy, string weapon, array<string> mods )
+void function SetUpNPCWeapons( entity guy )
{
- guy.TakeActiveWeapon()
- guy.GiveWeapon( weapon, mods )
- guy.SetActiveWeaponByName( weapon )
+ string className = guy.GetClassName()
+
+ array<string> mainWeapons
+ if ( className in file.npcWeaponsTable )
+ mainWeapons = file.npcWeaponsTable[ className ]
+
+ if ( mainWeapons.len() == 0 ) // no valid weapons
+ return
+
+ // take off existing main weapons, or sometimes they'll have a archer by default
+ foreach ( entity weapon in guy.GetMainWeapons() )
+ guy.TakeWeapon( weapon.GetWeaponClassName() )
+
+ if ( mainWeapons.len() > 0 )
+ {
+ string weaponName = mainWeapons[ RandomInt( mainWeapons.len() ) ]
+ guy.GiveWeapon( weaponName )
+ guy.SetActiveWeaponByName( weaponName )
+ }
}
// Checks if we can spawn a dropship at a node, this should guarantee dropship ziplines
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
index fae778d6..f47ee90f 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
@@ -1,22 +1,36 @@
untyped
global function GamemodeAITdm_Init
-const SQUADS_PER_TEAM = 3
+// these are now default settings
+const int SQUADS_PER_TEAM = 4
-const REAPERS_PER_TEAM = 2
+const int REAPERS_PER_TEAM = 2
-const LEVEL_SPECTRES = 125
-const LEVEL_STALKERS = 380
-const LEVEL_REAPERS = 500
+const int LEVEL_SPECTRES = 125
+const int LEVEL_STALKERS = 380
+const int LEVEL_REAPERS = 500
+
+// add settings
+global function AITdm_SetSquadsPerTeam
+global function AITdm_SetReapersPerTeam
+global function AITdm_SetLevelSpectres
+global function AITdm_SetLevelStalkers
+global function AITdm_SetLevelReapers
struct
{
// Due to team based escalation everything is an array
- array< int > levels = [ LEVEL_SPECTRES, LEVEL_SPECTRES ]
+ array< int > levels = [] // Initilazed in `Spawner_Threaded`
array< array< string > > podEntities = [ [ "npc_soldier" ], [ "npc_soldier" ] ]
array< bool > reapers = [ false, false ]
-} file
+ // default settings
+ int squadsPerTeam = SQUADS_PER_TEAM
+ int reapersPerTeam = REAPERS_PER_TEAM
+ int levelSpectres = LEVEL_SPECTRES
+ int levelStalkers = LEVEL_STALKERS
+ int levelReapers = LEVEL_REAPERS
+} file
void function GamemodeAITdm_Init()
{
@@ -34,18 +48,47 @@ void function GamemodeAITdm_Init()
if ( GetCurrentPlaylistVarInt( "aitdm_archer_grunts", 0 ) == 0 )
{
- AiGameModes_SetGruntWeapons( [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] )
- AiGameModes_SetSpectreWeapons( [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] )
+ AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] )
+ AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] )
+ AiGameModes_SetNPCWeapons( "npc_stalker", [ "mp_weapon_hemlok_smg", "mp_weapon_lstar", "mp_weapon_mastiff" ] )
}
else
{
- AiGameModes_SetGruntWeapons( [ "mp_weapon_rocket_launcher" ] )
- AiGameModes_SetSpectreWeapons( [ "mp_weapon_rocket_launcher" ] )
+ AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rocket_launcher" ] )
+ AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_rocket_launcher" ] )
+ AiGameModes_SetNPCWeapons( "npc_stalker", [ "mp_weapon_rocket_launcher" ] )
}
ScoreEvent_SetupEarnMeterValuesForMixedModes()
}
+// add settings
+void function AITdm_SetSquadsPerTeam( int squads )
+{
+ file.squadsPerTeam = squads
+}
+
+void function AITdm_SetReapersPerTeam( int reapers )
+{
+ file.reapersPerTeam = reapers
+}
+
+void function AITdm_SetLevelSpectres( int level )
+{
+ file.levelSpectres = level
+}
+
+void function AITdm_SetLevelStalkers( int level )
+{
+ file.levelStalkers = level
+}
+
+void function AITdm_SetLevelReapers( int level )
+{
+ file.levelReapers = level
+}
+//
+
// Starts skyshow, this also requiers AINs but doesn't crash if they're missing
void function OnPrematchStart()
{
@@ -74,11 +117,9 @@ void function HandleScoreEvent( entity victim, entity attacker, var damageInfo )
// Basic checks
if ( victim == attacker || !( attacker.IsPlayer() || attacker.IsTitan() ) || GetGameState() != eGameState.Playing )
return
-
// Hacked spectre filter
if ( victim.GetOwner() == attacker )
return
-
// NPC titans without an owner player will not count towards any team's score
if ( attacker.IsNPC() && attacker.IsTitan() && !IsValid( GetPetTitanOwner( attacker ) ) )
return
@@ -193,7 +234,7 @@ void function SpawnIntroBatch_Threaded( int team )
int ships = shipNodes.len()
- for ( int i = 0; i < SQUADS_PER_TEAM; i++ )
+ for ( int i = 0; i < file.squadsPerTeam; i++ )
{
if ( pods != 0 || ships == 0 )
{
@@ -238,6 +279,7 @@ void function Spawner_Threaded( int team )
// used to index into escalation arrays
int index = team == TEAM_MILITIA ? 0 : 1
+ file.levels = [ file.levelSpectres, file.levelSpectres ] // due we added settings, should init levels here!
while( true )
{
@@ -252,7 +294,7 @@ void function Spawner_Threaded( int team )
if ( file.reapers[ index ] )
{
array< entity > points = SpawnPoints_GetDropPod()
- if ( reaperCount < REAPERS_PER_TEAM )
+ if ( reaperCount < file.reapersPerTeam )
{
entity node = points[ GetSpawnPointIndex( points, team ) ]
waitthread AiGameModes_SpawnReaper( node.GetOrigin(), node.GetAngles(), team, "npc_super_spectre_aitdm", ReaperHandler )
@@ -260,7 +302,7 @@ void function Spawner_Threaded( int team )
}
// NORMAL SPAWNS
- if ( count < SQUADS_PER_TEAM * 4 - 2 )
+ if ( count < file.squadsPerTeam * 4 - 2 )
{
string ent = file.podEntities[ index ][ RandomInt( file.podEntities[ index ].len() ) ]
@@ -306,19 +348,19 @@ void function Escalate( int team )
// Based on score escalate a team
switch ( file.levels[ index ] )
{
- case LEVEL_SPECTRES:
- file.levels[ index ] = LEVEL_STALKERS
+ case file.levelSpectres:
+ file.levels[ index ] = file.levelStalkers
file.podEntities[ index ].append( "npc_spectre" )
SetGlobalNetInt( defcon, 2 )
return
- case LEVEL_STALKERS:
- file.levels[ index ] = LEVEL_REAPERS
+ case file.levelStalkers:
+ file.levels[ index ] = file.levelReapers
file.podEntities[ index ].append( "npc_stalker" )
SetGlobalNetInt( defcon, 3 )
return
- case LEVEL_REAPERS:
+ case file.levelReapers:
file.reapers[ index ] = true
SetGlobalNetInt( defcon, 4 )
return
@@ -355,30 +397,47 @@ int function GetSpawnPointIndex( array< entity > points, int team )
// AI can also flee deeper into their zone suggesting someone spent way too much time on this
void function SquadHandler( array<entity> guys )
{
+ int team = guys[0].GetTeam()
+ // show the squad enemy radar
+ array<entity> players = GetPlayerArrayOfEnemies( team )
+ foreach ( entity guy in guys )
+ {
+ if ( IsAlive( guy ) )
+ {
+ foreach ( player in players )
+ guy.Minimap_AlwaysShow( 0, player )
+ }
+ }
+
// Not all maps have assaultpoints / have weird assault points ( looking at you ac )
// So we use enemies with a large radius
- array< entity > points = GetNPCArrayOfEnemies( guys[0].GetTeam() )
-
- if ( points.len() == 0 )
+ while ( GetNPCArrayOfEnemies( team ).len() == 0 ) // if we can't find any enemy npcs, keep waiting
+ WaitFrame()
+
+ // our waiting is end, check if any soldiers left
+ bool squadAlive = false
+ foreach ( entity guy in guys )
+ {
+ if ( IsAlive( guy ) )
+ squadAlive = true
+ else
+ guys.removebyvalue( guy )
+ }
+ if ( !squadAlive )
return
+
+ array<entity> points = GetNPCArrayOfEnemies( team )
vector point
point = points[ RandomInt( points.len() ) ].GetOrigin()
- array<entity> players = GetPlayerArrayOfEnemies( guys[0].GetTeam() )
-
- // Setup AI
+ // Setup AI, first assault point
foreach ( guy in guys )
{
guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
guy.AssaultPoint( point )
guy.AssaultSetGoalRadius( 1600 ) // 1600 is minimum for npc_stalker, works fine for others
-
- // show on enemy radar
- foreach ( player in players )
- guy.Minimap_AlwaysShow( 0, player )
-
-
+
//thread AITdm_CleanupBoredNPCThread( guy )
}
@@ -396,16 +455,32 @@ void function SquadHandler( array<entity> guys )
// Stop func if our squad has been killed off
if ( guys.len() == 0 )
return
+ }
+
+ // Get point and send our whole squad to it
+ points = GetNPCArrayOfEnemies( team )
+ if ( points.len() == 0 ) // can't find any points here
+ {
+ // Have to wait some amount of time before continuing
+ // because if we don't the server will continue checking this
+ // forever, aren't loops fun?
+ // This definitely didn't waste ~8 hours of my time reverting various
+ // launcher PRs before finding this mods PR that caused servers to
+ // freeze forever before having their process killed by the dedi watchdog
+ // without any logging. If anyone reads this, PLEASE add logging to your scripts
+ // for when weird edge cases happen, it can literally only help debugging. -Spoon
+ WaitFrame()
+ continue
+ }
- // Get point and send guy to it
- points = GetNPCArrayOfEnemies( guy.GetTeam() )
- if ( points.len() == 0 )
- continue
-
- point = points[ RandomInt( points.len() ) ].GetOrigin()
-
- guy.AssaultPoint( point )
+ point = points[ RandomInt( points.len() ) ].GetOrigin()
+
+ foreach ( guy in guys )
+ {
+ if ( IsAlive( guy ) )
+ guy.AssaultPoint( point )
}
+
wait RandomFloatRange(5.0,15.0)
}
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
index 915e03e0..c61cb585 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
@@ -1,12 +1,54 @@
+untyped // AddCallback_OnUseEntity() needs this
global function GamemodeAt_Init
global function RateSpawnpoints_AT
-const int BH_AI_TEAM = TEAM_BOTH
-const int BOUNTY_TITAN_DAMAGE_POOL = 400 // Rewarded for damage
-const int BOUNTY_TITAN_KILL_REWARD = 100 // Rewarded for kill
-const float WAVE_STATE_TRANSITION_TIME = 5.0
+// Old bobr note which still applies after a year :)
+// IMPLEMENTATION NOTES:
+// bounty hunt is a mode that was clearly pretty heavily developed, and had alot of scrapped concepts (i.e. most wanted player bounties, turret bounties, collectable blackbox objectives)
+// in the interest of time, this script isn't gonna support any of that atm
+// alot of the remote functions also take parameters that aren't used, i'm not gonna populate these and just use default values for now instead
+// however, if you do want to mess with this stuff, almost all the remote functions for this stuff are still present in cl_gamemode_at, and should work fine with minimal fuckery in my experience
+
+
+// Bank settings
+const float AT_BANKS_OPEN_DURATION = 45.0 // Bank open time
+const int AT_BANK_DEPOSIT_RATE = 10 // Amount deposited per second
+const int AT_BANK_DEPOSIT_RADIUS = 256 // bank radius for depositing
+const float AT_BANK_FORCE_CLOSE_DELAY = 4.0 // If all bonus money has been deposited close the banks after this constant early
+
+// TODO: The reference function no longer exists, check if this still holds true
+// VoyageDB: HACK score events... respawn made things in AT_SetScoreEventOverride() really messed up, have to do some hack here
+const array<string> AT_ENABLE_SCOREEVENTS =
+[
+ // these are disabled in AT_SetScoreEventOverride(), but related scoreEvents are not implemented into gamemode
+ // needs to re-enable them
+ "DoomTitan",
+ "DoomAutoTitan"
+]
+const array<string> AT_DISABLE_SCOREEVENTS =
+[
+ // these are missed in AT_SetScoreEventOverride(), but game actually used them
+ // needs to disable them
+ "KillStalker"
+]
+
+// Wave settings
+// General
+const int AT_AI_TEAM = TEAM_BOTH // Allow AI to attack and be attacked by both player teams
+const float AT_FIRST_WAVE_START_DELAY = 10.0 // First wave has an extra delay before begining
+const float AT_WAVE_TRANSITION_DELAY = 5.0 // Time between each wave and banks opening/closing
+const float AT_WAVE_END_ANNOUNCEMENT_DELAY = 1.0 // Extra wait before announcing wave cleaned
+
+// Squad settings
+const int AT_DROPPOD_SQUADS_ALLOWED_ON_FIELD = 4 // default is 4 droppod squads on field, won't use if AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK turns on // TODO: verify this
-const array<string> VALID_BOUNTY_TITAN_SETTINGS = [
+// Titan bounty settings
+const float AT_BOUNTY_TITAN_CHECK_DELAY = 10.0 // wait for bounty titans landing before we start checking their life state
+const float AT_BOUNTY_TITAN_HEALTH_MULTIPLIER = 3 // TODO: Verify this
+
+// Titan boss settings, check sh_gamemode_at.nut for more info
+const array<string> AT_BOUNTY_TITANS_AI_SETTINGS =
+[
"npc_titan_atlas_stickybomb_bounty",
"npc_titan_atlas_tracker_bounty",
"npc_titan_ogre_minigun_bounty",
@@ -16,102 +58,577 @@ const array<string> VALID_BOUNTY_TITAN_SETTINGS = [
"npc_titan_atlas_vanguard_bounty"
]
+// Extra
+// Respawn didn't use the "totalAllowedOnField" for npc spawning, they only allow 1 squad to be on field for each type of npc. enabling this might cause too much npcs spawning and crash the game
+const bool AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK = false
-// IMPLEMENTATION NOTES:
-// bounty hunt is a mode that was clearly pretty heavily developed, and had alot of scrapped concepts (i.e. most wanted player bounties, turret bounties, collectable blackbox objectives)
-// in the interest of time, this script isn't gonna support any of that atm
-// alot of the remote functions also take parameters that aren't used, i'm not gonna populate these and just use default values for now instead
-// however, if you do want to mess with this stuff, almost all the remote functions for this stuff are still present in cl_gamemode_at, and should work fine with minimal fuckery in my experience
+// Objectives
+const int AT_OBJECTIVE_EMPTY = -1 // Remove objective
+const int AT_OBJECTIVE_KILL_DZ = 104 // #AT_OBJECTIVE_KILL_DZ
+const int AT_OBJECTIVE_KILL_DZ_MULTI = 105 // #AT_OBJECTIVE_KILL_DZ_MULTI
+const int AT_OBJECTIVE_KILL_BOSS = 106 // #AT_OBJECTIVE_KILL_BOSS
+const int AT_OBJECTIVE_KILL_BOSS_MULTI = 107 // #AT_OBJECTIVE_KILL_BOSS_MULTI
+const int AT_OBJECTIVE_BANK_OPEN = 109 // #AT_BANK_OPEN_OBJECTIVE
-struct {
- array<entity> campsToRegisterOnEntitiesDidLoad
+// When a player tries to deposit when they have 0 bonus money
+// we show a help mesage, this is the ratelimit for that message
+// so that we dont spam it too much
+const float AT_PLAYER_HUD_MESSAGE_COOLDOWN = 2.5
+// Due to bad navmeshes NPCs may wonder off to bumfuck nowhere or the game
+// might teleport them into the map while trying to correct their position
+// This obviously breaks bounty hunt where the objective is to kill ALL ai
+// so we try to cleanup the camps after a set amount of time of inactivity
+const int AT_CAMP_BORED_NPCS_LEFT_TO_START_CLEANUP = 3
+const float AT_CAMP_BORED_CLEANUP_WAIT = 60.0
+struct
+{
array<entity> banks
array<AT_WaveOrigin> camps
+
+ // Used to track ScriptmanagedEntArrays of ai squads
+ table< int, array<int> > campScriptEntArrays
- table< int, table< string, int > > trackedCampNPCSpawns
+ table< entity, bool > titanIsBountyBoss
+ table< entity, int > bountyTitanRewards
+ table< entity, int > npcStolenBonus
+ table< entity, bool > playerBankUploading
+ table< entity, table<entity, int> > playerSavedBountyDamage
+ table< entity, float > playerHudMessageAllowedTime
} file
void function GamemodeAt_Init()
{
- AddCallback_GameStateEnter( eGameState.Playing, RunATGame )
-
+ // wave
+ RegisterSignal( "ATWaveEnd" )
+ // camp
+ RegisterSignal( "ATCampClean" )
+ RegisterSignal( "ATAllCampsClean" )
+
+ // Set-up score callbacks
+ ScoreEvent_SetupEarnMeterValuesForMixedModes()
+ AddDamageFinalCallback( "npc_titan", OnNPCTitanFinalDamaged )
+ AddCallback_OnPlayerKilled( AT_PlayerOrNPCKilledScoreEvent )
+ AddCallback_OnNPCKilled( AT_PlayerOrNPCKilledScoreEvent )
+
+ // Set npc weapons
+ AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] )
+ AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] )
+ AiGameModes_SetNPCWeapons( "npc_stalker", [ "mp_weapon_hemlok_smg", "mp_weapon_lstar", "mp_weapon_mastiff" ] )
+
+ // Gamestate callbacks
+ AddCallback_GameStateEnter( eGameState.Prematch, OnATGamePrematch )
+ AddCallback_GameStateEnter( eGameState.Playing, OnATGamePlaying )
+
+ // Initilaze player
AddCallback_OnClientConnected( InitialiseATPlayer )
- AddSpawnCallbackEditorClass( "info_target", "info_attrition_bank", CreateATBank )
- AddSpawnCallbackEditorClass( "info_target", "info_attrition_camp", CreateATCamp )
- AddCallback_EntitiesDidLoad( CreateATCamps_Delayed )
+ // Initilaze gamemode entities
+ AddCallback_EntitiesDidLoad( OnEntitiesDidLoad )
}
void function RateSpawnpoints_AT( int checkclass, array<entity> spawnpoints, int team, entity player )
{
- RateSpawnpoints_Generic( checkclass, spawnpoints, team, player ) // temp
+ RateSpawnpoints_Generic( checkclass, spawnpoints, team, player )
}
-// world and player inits
+
+
+////////////////////////////////////////
+///// GAMESTATE CALLBACK FUNCTIONS /////
+////////////////////////////////////////
+
+void function OnATGamePrematch()
+{
+ AT_ScoreEventsValueSetUp()
+}
+
+void function OnATGamePlaying()
+{
+ thread AT_GameLoop_Threaded()
+}
+
+////////////////////////////////////////////
+///// GAMESTATE CALLBACK FUNCTIONS END /////
+////////////////////////////////////////////
+
+
+
+////////////////////////////
+///// PLAYER FUNCTIONS /////
+////////////////////////////
void function InitialiseATPlayer( entity player )
{
Remote_CallFunction_NonReplay( player, "ServerCallback_AT_OnPlayerConnected" )
+ player.SetPlayerNetInt( "AT_bonusPointMult", 1 )
+ file.playerBankUploading[ player ] <- false
+ file.playerSavedBountyDamage[ player ] <- {}
+ file.playerHudMessageAllowedTime[ player ] <- 0.0
+ thread AT_PlayerTitleThink( player )
+ thread AT_PlayerObjectiveThink( player )
}
-void function CreateATBank( entity spawnpoint )
+void function AT_PlayerTitleThink( entity player )
{
- entity bank = CreatePropDynamic( spawnpoint.GetModelName(), spawnpoint.GetOrigin(), spawnpoint.GetAngles(), SOLID_VPHYSICS )
- bank.SetScriptName( "AT_Bank" )
-
- // create tracker ent
- // we don't need to store these at all, client just needs to get them
- DispatchSpawn( GetAvailableBankTracker( bank ) )
+ player.EndSignal( "OnDestroy" )
+
+ while ( true )
+ {
+ if ( GetGameState() == eGameState.Playing )
+ {
+ // Set player money count
+ player.SetTitle( "$" + string( AT_GetPlayerBonusPoints( player ) ) )
+ }
+ else if ( GetGameState() >= eGameState.WinnerDetermined )
+ {
+ if ( player.IsTitan() )
+ player.SetTitle( GetTitanPlayerTitle( player ) )
+ else
+ player.SetTitle( "" )
+
+ return
+ }
+
+ WaitFrame()
+ }
+}
+
+string function GetTitanPlayerTitle( entity player )
+{
+ entity soul = player.GetTitanSoul()
+
+ if ( !IsValid( soul ) )
+ return ""
- thread PlayAnim( bank, "mh_inactive_idle" )
+ string settings = GetSoulPlayerSettings( soul )
+ var title = GetPlayerSettingsFieldForClassName( settings, "printname" )
+
+ if ( title == null )
+ return ""
- file.banks.append( bank )
+ return expect string( title )
}
-void function CreateATCamp( entity spawnpoint )
+void function AT_PlayerObjectiveThink( entity player )
{
- // delay this so we don't do stuff before all spawns are initialised and that
- file.campsToRegisterOnEntitiesDidLoad.append( spawnpoint )
+ player.EndSignal( "OnDestroy" )
+
+ int curObjective = AT_OBJECTIVE_EMPTY
+ while ( true )
+ {
+ // game entered other state
+ if ( GetGameState() >= eGameState.WinnerDetermined )
+ {
+ player.SetPlayerNetInt( "gameInfoStatusText", AT_OBJECTIVE_EMPTY )
+ return
+ }
+
+ int nextObjective = AT_OBJECTIVE_EMPTY
+
+ // Determine objective text for player
+ if ( !IsAlive( player ) ) // Don't show objective to dead players
+ {
+ nextObjective = AT_OBJECTIVE_EMPTY
+ }
+ else // We're still alive
+ {
+ if ( GetGlobalNetBool( "banksOpen" ) )
+ {
+ nextObjective = AT_OBJECTIVE_BANK_OPEN
+ }
+ else if ( GetGlobalNetBool( "preBankPhase" ) )
+ {
+ nextObjective = AT_OBJECTIVE_EMPTY
+ }
+ else
+ {
+ // No checks have passed, try to do a "Kill all x near the marked dropzone" objective
+ int dropZoneActiveCount = 0
+ int bossAliveCount = 0
+ array<entity> campEnts
+ campEnts.append( GetGlobalNetEnt( "camp1Ent" ) )
+ campEnts.append( GetGlobalNetEnt( "camp2Ent" ) )
+
+ foreach ( entity ent in campEnts )
+ {
+ if ( IsValid( ent ) )
+ {
+ if ( ent.IsTitan() )
+ bossAliveCount += 1
+ else
+ dropZoneActiveCount += 1
+ }
+ }
+
+ switch( dropZoneActiveCount )
+ {
+ case 1:
+ nextObjective = AT_OBJECTIVE_KILL_DZ
+ break
+ case 2:
+ nextObjective = AT_OBJECTIVE_KILL_DZ_MULTI
+ break
+ }
+
+ switch( bossAliveCount )
+ {
+ case 1:
+ nextObjective = AT_OBJECTIVE_KILL_BOSS
+ break
+ case 2:
+ nextObjective = AT_OBJECTIVE_KILL_BOSS_MULTI
+ break
+ }
+
+ // We couldn't get an objective, set it to empty
+ if ( dropZoneActiveCount == 0 && bossAliveCount == 0 )
+ nextObjective = AT_OBJECTIVE_EMPTY
+ }
+ }
+
+ // Set the objective when changed
+ if ( curObjective != nextObjective )
+ {
+ player.SetPlayerNetInt( "gameInfoStatusText", nextObjective )
+ curObjective = nextObjective
+ }
+
+ WaitFrame()
+ }
}
-void function CreateATCamps_Delayed()
+////////////////////////////////
+///// PLAYER FUNCTIONS END /////
+////////////////////////////////
+
+
+
+////////////////////////////////////////
+///// GAMEMODE INITILAZE FUNCTIONS /////
+////////////////////////////////////////
+
+void function OnEntitiesDidLoad()
{
- // we delay registering camps until EntitiesDidLoad since they rely on spawnpoints and stuff, which might not all be ready in the creation callback
- // unsure if this would be an issue in practice, but protecting against it in case it would be
- foreach ( entity camp in file.campsToRegisterOnEntitiesDidLoad )
+ foreach ( entity info_target in GetEntArrayByClass_Expensive( "info_target" ) )
{
- AT_WaveOrigin campStruct
- campStruct.ent = camp
- campStruct.origin = camp.GetOrigin()
- campStruct.radius = expect string( camp.kv.radius ).tofloat()
- campStruct.height = expect string( camp.kv.height ).tofloat()
+ if( info_target.HasKey( "editorclass" ) )
+ {
+ switch( info_target.kv.editorclass )
+ {
+ case "info_attrition_bank":
+ entity bank = CreateEntity( "prop_script" )
+ bank.SetScriptName( "AT_Bank" ) // VoyageDB: don't know how to make client able to track it
+ bank.SetOrigin( info_target.GetOrigin() )
+ bank.SetAngles( info_target.GetAngles() )
+ DispatchSpawn( bank )
+ bank.kv.solid = SOLID_VPHYSICS
+ bank.SetModel( info_target.GetModelName() )
+
+ // Minimap icon init
+ bank.Minimap_SetCustomState( eMinimapObject_prop_script.AT_BANK )
+ bank.Minimap_SetAlignUpright( true )
+ bank.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ bank.Minimap_Hide( TEAM_IMC, null )
+ bank.Minimap_Hide( TEAM_MILITIA, null )
+
+ // Create tracker ent
+ // we don't need to store these at all, client just needs to get them
+ DispatchSpawn( GetAvailableBankTracker( bank ) )
+
+ // Make sure the bank is in it's disabled pose
+ thread PlayAnim( bank, "mh_inactive_idle" )
+ // Set the bank usable
+ AddCallback_OnUseEntity( bank, OnPlayerUseBank )
+ bank.SetUsable()
+ bank.SetUsePrompts( "#AT_USE_BANK_CLOSED", "#AT_USE_BANK_CLOSED" )
+
+ file.banks.append( bank )
+ break;
+ case "info_attrition_camp":
+ AT_WaveOrigin campStruct
+ campStruct.ent = info_target
+ campStruct.origin = info_target.GetOrigin()
+ campStruct.radius = expect string( info_target.kv.radius ).tofloat()
+ campStruct.height = expect string( info_target.kv.height ).tofloat()
+
+ // Assumes every info_attrition_camp will have all 9 phases, possibly not a good idea?
+ // TODO: verify this on all vanilla maps before release
+ for ( int i = 0; i < 9; i++ )
+ campStruct.phaseAllowed.append( expect string( info_target.kv[ "phase_" + ( i + 1 ) ] ) == "1" )
+
+ // Get droppod spawns within the camp
+ foreach ( entity spawnpoint in SpawnPoints_GetDropPod() )
+ {
+ vector campPos = info_target.GetOrigin()
+ vector spawnPos = spawnpoint.GetOrigin()
+ if ( Distance( campPos, spawnPos ) < campStruct.radius )
+ campStruct.dropPodSpawnPoints.append( spawnpoint )
+ }
+
+ // Get titan spawns within the camp
+ foreach ( entity spawnpoint in SpawnPoints_GetTitan() )
+ {
+ vector campPos = info_target.GetOrigin()
+ vector spawnPos = spawnpoint.GetOrigin()
+ if ( Distance( campPos, spawnPos ) < campStruct.radius )
+ campStruct.titanSpawnPoints.append( spawnpoint )
+ }
+
+ file.camps.append( campStruct )
+ break;
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////
+///// GAMEMODE INITILAZE FUNCTIONS END /////
+////////////////////////////////////////////
+
+
+
+/////////////////////////////
+///// SCORING FUNCTIONS /////
+/////////////////////////////
+
+// TODO: Don't reward in postmatch
+// TODO: Dropping a titan on a bounty with it's dome-shield still up rewards you the bonus, but
+// it doesn't actually damage the bounty titan
+
+void function AT_ScoreEventsValueSetUp()
+{
+ ScoreEvent_SetEarnMeterValues( "KillTitan", 0.10, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "KillAutoTitan", 0.10, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "AttritionTitanKilled", 0.10, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "KillPilot", 0.10, 0.10 )
+ ScoreEvent_SetEarnMeterValues( "AttritionPilotKilled", 0.10, 0.10 )
+ ScoreEvent_SetEarnMeterValues( "AttritionBossKilled", 0.10, 0.20 )
+ ScoreEvent_SetEarnMeterValues( "AttritionGruntKilled", 0.02, 0.02, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "AttritionSpectreKilled", 0.02, 0.02, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "AttritionStalkerKilled", 0.02, 0.02, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "AttritionSuperSpectreKilled", 0.10, 0.10, 0.5 )
+
+ // HACK
+ foreach ( string eventName in AT_ENABLE_SCOREEVENTS )
+ ScoreEvent_Enable( GetScoreEvent( eventName ) )
+
+ foreach ( string eventName in AT_DISABLE_SCOREEVENTS )
+ ScoreEvent_Disable( GetScoreEvent( eventName ) )
+}
+
+void function AT_PlayerOrNPCKilledScoreEvent( entity victim, entity attacker, var damageInfo )
+{
+ if ( !IsValid( attacker ) )
+ return
+
+ // Suicide
+ if ( attacker == victim )
+ {
+ if ( victim.IsPlayer() )
+ AT_PlayerBonusLoss( victim, AT_GetPlayerBonusPoints( victim ) / 2 )
+
+ return
+ }
+
+ // NPC is the attacker
+ if ( !attacker.IsPlayer() )
+ {
+ if ( attacker.IsTitan() && IsValid( GetPetTitanOwner( attacker ) ) ) // Re-asign attacker
+ attacker = GetPetTitanOwner( attacker )
+ else // NPC steals money from player, killing it will award the stolen bonus + normal reward
+ AT_NPCTryStealBonusPoints( attacker, victim )
- // assumes every info_attrition_camp will have all 9 phases, possibly not a good idea?
- for ( int i = 0; i < 9; i++ )
- campStruct.phaseAllowed.append( expect string( camp.kv[ "phase_" + ( i + 1 ) ] ) == "1" )
+ return
+ }
+
+ // Get event name
+ string eventName = GetAttritionScoreEventName( victim.GetClassName() )
+
+ if ( victim.IsTitan() ) // titan specific
+ eventName = GetAttritionScoreEventNameFromAI( victim )
+
+ if ( eventName == "" ) // no valid scoreEvent
+ return
+
+ int scoreVal = ScoreEvent_GetPointValue( GetScoreEvent( eventName ) )
+
+ // pet titan check
+ if ( victim.IsTitan() && IsValid( GetPetTitanOwner( victim ) ) )
+ {
+ if( GetPetTitanOwner( victim ) == attacker ) // Player ejected
+ return
- // get droppod spawns
- foreach ( entity spawnpoint in SpawnPoints_GetDropPod() )
- if ( Distance( camp.GetOrigin(), spawnpoint.GetOrigin() ) < 1500.0 )
- campStruct.dropPodSpawnPoints.append( spawnpoint )
+ if( GetPetTitanOwner( victim ).IsPlayer() ) // Killed player npc titan
+ return
- foreach ( entity spawnpoint in SpawnPoints_GetTitan() )
- if ( Distance( camp.GetOrigin(), spawnpoint.GetOrigin() ) < 1500.0 )
- campStruct.titanSpawnPoints.append( spawnpoint )
+ scoreVal = ATTRITION_SCORE_TITAN_MIN
+ }
+
+ // killed npc
+ if ( victim.IsNPC() )
+ {
+ int bonusFromNPC = 0
+ // If NPC was carrying a bonus award it to the attacker
+ if ( victim in file.npcStolenBonus )
+ {
+ bonusFromNPC = file.npcStolenBonus[ victim ]
+ delete file.npcStolenBonus[ victim ]
+ }
+ AT_AddPlayerBonusPointsForEntityKilled( attacker, scoreVal, damageInfo, bonusFromNPC )
+ AddPlayerScore( attacker, eventName ) // we add scoreEvent here, since basic score events has been overwrited by sh_gamemode_at.nut
+ // update score difference and scoreboard
+ AT_AddToPlayerTeamScore( attacker, scoreVal )
+ }
+
+ // bonus stealing check
+ if ( victim.IsPlayer() )
+ AT_PlayerTryStealBonusPoints( attacker, victim, damageInfo )
+}
+
+bool function AT_NPCTryStealBonusPoints( entity attacker, entity victim )
+{
+ // basic checks
+ if ( !attacker.IsNPC() )
+ return false
+
+ if ( !victim.IsPlayer() )
+ return false
+
+ int victimBonus = AT_GetPlayerBonusPoints( victim )
+ int bonusToSteal = victimBonus / 2 // npc always steal half the bonus from player, no extra bonus for killing the player
+ if ( bonusToSteal == 0 ) // player has no bonus!
+ return false
+
+ if ( !( attacker in file.npcStolenBonus ) ) // init
+ file.npcStolenBonus[ attacker ] <- 0
+
+ file.npcStolenBonus[ attacker ] += bonusToSteal
+
+ AT_PlayerBonusLoss( victim, bonusToSteal ) // tell victim of bonus stolen
+
+ if ( !( attacker in file.titanIsBountyBoss ) ) // if attacker npc is not a bounty titan, we make them highlighted
+ NPCBountyStolenHighlight( attacker )
+
+ return true
+}
+
+void function NPCBountyStolenHighlight( entity npc )
+{
+ Highlight_SetEnemyHighlight( npc, "enemy_boss_bounty" )
+}
+
+bool function AT_PlayerTryStealBonusPoints( entity attacker, entity victim, var damageInfo )
+{
+ // basic checks
+ if ( !attacker.IsPlayer() )
+ return false
- // todo: turret spawns someday maybe
+ if ( !victim.IsPlayer() )
+ return false
- file.camps.append( campStruct )
+ int victimBonus = AT_GetPlayerBonusPoints( victim )
+
+ int minScoreCanSteal = ATTRITION_SCORE_PILOT_MIN
+ if ( victim.IsTitan() )
+ minScoreCanSteal = ATTRITION_SCORE_TITAN_MIN
+
+ int bonusToSteal = victimBonus / 2
+ int attackerScore = bonusToSteal
+ bool realStealBonus = true
+ if ( bonusToSteal <= minScoreCanSteal ) // no enough bonus to steal
+ {
+ attackerScore = minScoreCanSteal // give attacker min bonus
+ realStealBonus = false // we don't do attacker steal events below, just half victim's bonus
}
+
+ // servercallback
+ int victimEHandle = victim.GetEncodedEHandle()
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
+
+ // only do attacker events if victim has enough bonus to steal
+ if ( realStealBonus )
+ {
+ Remote_CallFunction_NonReplay(
+ attacker,
+ "ServerCallback_AT_PlayerKillScorePopup",
+ bonusToSteal, // stolenScore
+ victimEHandle, // victimEHandle
+ damageOrigin.x, // x
+ damageOrigin.y, // y
+ damageOrigin.z // z
+ )
+ }
+ else // otherwise we do a normal entity killed scoreEvent
+ {
+ AT_AddPlayerBonusPointsForEntityKilled( attacker, attackerScore, damageInfo )
+ }
+
+ // update score difference and scoreboard
+ AT_AddToPlayerTeamScore( attacker, minScoreCanSteal )
+
+ // steal bonus
+ // only do attacker events if victim has enough bonus to steal
+ if ( realStealBonus )
+ {
+ AT_AddPlayerBonusPoints( attacker, bonusToSteal )
+ AddPlayerScore( attacker, "AttritionBonusStolen" )
+ }
+
+ // tell victim of bonus stolen
+ AT_PlayerBonusLoss( victim, bonusToSteal )
- file.campsToRegisterOnEntitiesDidLoad.clear()
+ return realStealBonus
}
-// scoring funcs
+void function AT_PlayerBonusLoss( entity player, int bonusLoss )
+{
+ AT_AddPlayerBonusPoints( player, -bonusLoss )
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_ShowStolenBonus",
+ bonusLoss // stolenScore
+ )
+}
-// don't use this where possible as it doesn't set score and stuff
-void function AT_SetPlayerCash( entity player, int amount )
+// team score meter
+void function AT_AddToPlayerTeamScore( entity player, int amount )
+{
+ // do not award any score after the match is ended
+ if ( GetGameState() > eGameState.Playing )
+ return
+
+ // add to scoreboard
+ player.AddToPlayerGameStat( PGS_ASSAULT_SCORE, amount )
+
+ // Check score so we dont go over max
+ if ( GameRules_GetTeamScore(player.GetTeam()) + amount > GetScoreLimit_FromPlaylist() )
+ {
+ amount = GetScoreLimit_FromPlaylist() - GameRules_GetTeamScore(player.GetTeam())
+ }
+
+ // update score difference
+ AddTeamScore( player.GetTeam(), amount )
+}
+
+// bonus points, players earn from killing
+void function AT_AddPlayerBonusPoints( entity player, int amount )
+{
+ // do not award any score after the match is ended
+ if ( GetGameState() > eGameState.Playing )
+ return
+
+ // add to scoreboard
+ player.AddToPlayerGameStat( PGS_SCORE, amount )
+ AT_SetPlayerBonusPoints( player, player.GetPlayerNetInt( "AT_bonusPoints" ) + ( player.GetPlayerNetInt( "AT_bonusPoints256" ) * 256 ) + amount )
+}
+
+int function AT_GetPlayerBonusPoints( entity player )
+{
+ return player.GetPlayerNetInt( "AT_bonusPoints" ) + player.GetPlayerNetInt( "AT_bonusPoints256" ) * 256
+}
+
+void function AT_SetPlayerBonusPoints( entity player, int amount )
{
// split into stacks of 256 where necessary
int stacks = amount / 256 // automatically rounds down because int division
@@ -120,198 +637,986 @@ void function AT_SetPlayerCash( entity player, int amount )
player.SetPlayerNetInt( "AT_bonusPoints", amount - stacks * 256 )
}
-void function AT_AddPlayerCash( entity player, int amount )
+// total points, the value player actually uploaded to team score
+void function AT_AddPlayerTotalPoints( entity player, int amount )
{
- // update score difference
- AddTeamScore( player.GetTeam(), amount / 2 )
- AT_SetPlayerCash( player, player.GetPlayerNetInt( "AT_bonusPoints" ) + ( player.GetPlayerNetInt( "AT_bonusPoints256" ) * 256 ) + amount )
+ // update score difference and scoreboard, calling this function meaning player has deposited their bonus to team score
+ AT_AddToPlayerTeamScore( player, amount )
+ AT_SetPlayerTotalPoints( player, player.GetPlayerNetInt( "AT_totalPoints" ) + ( player.GetPlayerNetInt( "AT_totalPoints256" ) * 256 ) + amount )
+}
+
+void function AT_SetPlayerTotalPoints( entity player, int amount )
+{
+ // split into stacks of 256 where necessary
+ int stacks = amount / 256 // automatically rounds down because int division
+
+ player.SetPlayerNetInt( "AT_totalPoints256", stacks )
+ player.SetPlayerNetInt( "AT_totalPoints", amount - stacks * 256 )
}
-// run gamestate
+// earn points, seems not used
+void function AT_AddPlayerEarnedPoints( entity player, int amount )
+{
+ AT_SetPlayerBonusPoints( player, player.GetPlayerNetInt( "AT_earnedPoints" ) + ( player.GetPlayerNetInt( "AT_earnedPoints256" ) * 256 ) + amount )
+}
+
+void function AT_SetPlayerEarnedPoints( entity player, int amount )
+{
+ // split into stacks of 256 where necessary
+ int stacks = amount / 256 // automatically rounds down because int division
+
+ player.SetPlayerNetInt( "AT_earnedPoints256", stacks )
+ player.SetPlayerNetInt( "AT_earnedPoints", amount - stacks * 256 )
+}
+
+// damaging bounty
+void function AT_AddPlayerBonusPointsForBossDamaged( entity player, entity victim, int amount, var damageInfo )
+{
+ AT_AddPlayerBonusPoints( player, amount )
+ // update score difference and scoreboard
+ AT_AddToPlayerTeamScore( player, amount )
+
+ // send servercallback for damaging
+ int bossEHandle = victim.GetEncodedEHandle()
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
+
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_BossDamageScorePopup",
+ amount, // damageScore
+ amount, // damageBonus
+ bossEHandle, // bossEHandle
+ damageOrigin.x, // x
+ damageOrigin.y, // y
+ damageOrigin.z // z
+ )
+}
-void function RunATGame()
+void function AT_AddPlayerBonusPointsForEntityKilled( entity player, int amount, var damageInfo, int extraBonus = 0 )
{
- thread RunATGame_Threaded()
+ AT_AddPlayerBonusPoints( player, amount + extraBonus )
+
+ // send servercallback for damaging
+ int attackerEHandle = player.GetEncodedEHandle()
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
+
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_ShowATScorePopup",
+ attackerEHandle, // attackerEHandle
+ amount, // damageScore
+ amount + extraBonus, // damageBonus
+ damageOrigin.x, // damagePosX
+ damageOrigin.y, // damagePosX
+ damageOrigin.z, // damagePosX
+ 0 // damageType ( not used )
+ )
}
-void function RunATGame_Threaded()
+/////////////////////////////////
+///// SCORING FUNCTIONS END /////
+/////////////////////////////////
+
+
+
+//////////////////////////////
+///// GAMELOOP FUNCTIONS /////
+//////////////////////////////
+
+void function AT_GameLoop_Threaded()
{
svGlobal.levelEnt.EndSignal( "GameStateChanged" )
- OnThreadEnd( function()
- {
- SetGlobalNetBool( "banksOpen", false )
- })
+ // game end func
+ // TODO: Cant seem to be able to get this crash ???
+ OnThreadEnd
+ (
+ function()
+ {
+ // prevent crash before entity creation on map change
+ if ( GetGameState() >= eGameState.Prematch )
+ {
+ SetGlobalNetBool( "preBankPhase", false )
+ SetGlobalNetBool( "banksOpen", false )
+ }
+ }
+ )
- wait WAVE_STATE_TRANSITION_TIME // initial wait before first wave
-
- for ( int waveCount = 1; ; waveCount++ )
+ // Initial wait before first wave
+ wait AT_FIRST_WAVE_START_DELAY - AT_WAVE_TRANSITION_DELAY
+
+ int lastWaveId = -1
+ for ( int waveCount = 1; ; waveCount++ )
{
- wait WAVE_STATE_TRANSITION_TIME
+ wait AT_WAVE_TRANSITION_DELAY
// cap to number of real waves
- int waveId = ( waveCount / 2 )
- // last wave is clearly unfinished so don't use, just cap to last actually used one
- if ( waveId >= GetWaveDataSize() - 1 )
+ int waveId = ( waveCount - 1 ) / 2
+ int waveCapAmount = 2
+ waveId = int( min( waveId, GetWaveDataSize() - waveCapAmount ) )
+
+ // New wave dialogue
+ bool waveChanged = lastWaveId != waveId
+ if ( waveChanged )
+ {
+ PlayFactionDialogueToTeam( "bh_newWave", TEAM_IMC )
+ PlayFactionDialogueToTeam( "bh_newWave", TEAM_MILITIA )
+ }
+ else // same wave, second half
{
- waveId = GetWaveDataSize() - 2
- waveCount = waveId * 2
+ PlayFactionDialogueToTeam( "bh_incoming", TEAM_IMC )
+ PlayFactionDialogueToTeam( "bh_incoming", TEAM_MILITIA )
}
+
+ lastWaveId = waveId
SetGlobalNetInt( "AT_currentWave", waveId )
- bool isBossWave = waveCount / float( 2 ) > waveId // odd number waveCount means boss wave
+ bool isBossWave = waveCount % 2 == 0 // even number waveCount means boss wave
// announce the wave
foreach ( entity player in GetPlayerArray() )
{
if ( isBossWave )
+ {
Remote_CallFunction_NonReplay( player, "ServerCallback_AT_AnnounceBoss" )
+ }
else
- Remote_CallFunction_NonReplay( player, "ServerCallback_AT_AnnouncePreParty", 0.0, waveId )
+ {
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_AnnouncePreParty",
+ 0.0, // endTime ( not used )
+ waveId // waveNum
+ )
+ }
}
- wait WAVE_STATE_TRANSITION_TIME
+ wait AT_WAVE_TRANSITION_DELAY
- // run the wave
+ // Run the wave
+ thread AT_CampSpawnThink( waveId, isBossWave )
+
+ if ( !isBossWave )
+ {
+ svGlobal.levelEnt.WaitSignal( "ATAllCampsClean" ) // signaled when all camps cleaned in spawn functions
+ }
+ else
+ {
+ wait AT_BOUNTY_TITAN_CHECK_DELAY
+ // wait until all bounty titans killed
+ while ( IsAlive( GetGlobalNetEnt( "camp1Ent" ) ) || IsAlive( GetGlobalNetEnt( "camp2Ent" ) ) )
+ WaitFrame()
+ }
+
+ // wave end, prebank phase
+ svGlobal.levelEnt.Signal( "ATWaveEnd" ) // defensive fix, destroy existing campEnts
+ SetGlobalNetBool( "preBankPhase", true )
+
+ wait AT_WAVE_END_ANNOUNCEMENT_DELAY
- AT_WaveData wave = GetWaveData( waveId )
- array< array<AT_SpawnData> > campSpawnData
+ // announce wave end
+ foreach ( entity player in GetPlayerArray() )
+ {
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_AnnounceWaveOver",
+ waveId, // waveNum ( not used )
+ 0, // militiaDamageTotal ( not used )
+ 0, // imcDamageTotal ( not used )
+ 0, // milMVP ( not used )
+ 0, // imcMVP ( not used )
+ 0, // milMVPDamage ( not used )
+ 0 // imcMVPDamage ( not used )
+ )
+ }
+
+ wait AT_WAVE_TRANSITION_DELAY
- if ( isBossWave )
- campSpawnData = wave.bossSpawnData
- else
- campSpawnData = wave.spawnDataArrays
+ // banking phase
+ SetGlobalNetBool( "preBankPhase", false )
+ SetGlobalNetTime( "AT_bankStartTime", Time() )
+ SetGlobalNetTime( "AT_bankEndTime", Time() + AT_BANKS_OPEN_DURATION )
+ SetGlobalNetBool( "banksOpen", true )
+
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AT_BankOpen" )
+
+ foreach ( entity bank in file.banks )
+ thread AT_BankActiveThink( bank )
+
- // initialise pending spawns
- foreach ( array< AT_SpawnData > campData in campSpawnData )
+ float endTime = Time() + AT_BANKS_OPEN_DURATION
+ bool forceCloseTriggered = false
+ // wait until no player is holding bonus, or max wait time
+ while ( Time() <= endTime )
{
- foreach ( AT_SpawnData spawnData in campData )
- spawnData.pendingSpawns = spawnData.totalToSpawn
+ // If everyone has deposited their bonuses close the banks early
+ if ( !ATAnyPlayerHasBonus() && !forceCloseTriggered )
+ {
+ forceCloseTriggered = true
+ endTime = Time() + AT_BANK_FORCE_CLOSE_DELAY
+ }
+
+ WaitFrame()
}
- // clear tracked spawns
- file.trackedCampNPCSpawns = {}
- while ( true )
- {
- // if this is ever 0 by the end of this loop, wave is complete
- int numActiveCampSpawners = 0
+ SetGlobalNetBool( "banksOpen", false )
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AT_BankClose" )
+ }
+}
+
+bool function ATAnyPlayerHasBonus()
+{
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( AT_GetPlayerBonusPoints( player ) )
+ return true
+ }
+ return false
+}
+
+//////////////////////////////////
+///// GAMELOOP FUNCTIONS END /////
+//////////////////////////////////
+
+
+
+//////////////////////////
+///// CAMP FUNCTIONS /////
+//////////////////////////
+
+void function AT_CampSpawnThink( int waveId, bool isBossWave )
+{
+ AT_WaveData wave = GetWaveData( waveId )
+ array< array<AT_SpawnData> > campSpawnData
+
+ if ( isBossWave )
+ campSpawnData = wave.bossSpawnData
+ else
+ campSpawnData = wave.spawnDataArrays
+
+ array<AT_WaveOrigin> allCampsToUse
+ foreach ( AT_WaveOrigin campStruct in file.camps )
+ {
+ if ( campStruct.phaseAllowed[ waveId ] )
+ allCampsToUse.append( campStruct )
+ }
+
+ // HACK
+ // There's too many phase3 camps on exoplanet and rise, make sure we always have the correct count
+ int maxCampsForWave = waveId == 0 ? 1 : 2
+ while( allCampsToUse.len() > maxCampsForWave )
+ {
+ // Get the required number of camps
+ array<AT_WaveOrigin> tempCamps
+ for( int i = 0; i < maxCampsForWave; i++ )
+ tempCamps.append( allCampsToUse[RandomInt( allCampsToUse.len() )] )
- // iterate over camp data for wave
- for ( int campIdx = 0; campIdx < campSpawnData.len() && campIdx < file.camps.len(); campIdx++ )
+
+ // Check if they're intersecting, if they are, try again
+ bool intersecting = false
+ for( int i = 0; i < tempCamps.len(); i++ )
+ {
+ AT_WaveOrigin campA = tempCamps[i]
+ for( int j = 0; j < tempCamps.len(); j++ )
{
- if ( !( campIdx in file.trackedCampNPCSpawns ) )
- file.trackedCampNPCSpawns[ campIdx ] <- {}
+ // Don't compare the same two camps
+ if( j == i )
+ continue
+
+ AT_WaveOrigin campB = tempCamps[j]
+
+ if( Distance( campA.origin, campB.origin ) < campA.radius + campB.radius )
+ intersecting = true
+ }
+ }
+
+ if( !intersecting )
+ allCampsToUse = tempCamps
+
+ // If we ever get really unlucky just wait a frame
+ WaitFrame()
+ }
+
+ foreach ( int spawnId, AT_WaveOrigin curCampData in allCampsToUse )
+ {
+ array<AT_SpawnData> curSpawnData = campSpawnData[ spawnId ]
+
+ int totalNPCsToSpawn = 0
+ // initialise pending spawns and get total npcs
+ foreach ( AT_SpawnData spawnData in curSpawnData )
+ {
+ spawnData.pendingSpawns = spawnData.totalToSpawn
+ // add to network variables
+ string npcNetVar = GetNPCNetVarName( spawnData.aitype, spawnId )
+ SetGlobalNetInt( npcNetVar, spawnData.totalToSpawn )
+
+ totalNPCsToSpawn += spawnData.totalToSpawn
+ }
+
+ if ( !isBossWave )
+ {
+ // camp Ent, boss wave will use boss themselves as campEnt
+ string campEntVarName = "camp" + string( spawnId + 1 ) + "Ent"
+ bool waveNotActive = GetGlobalNetBool( "preBankPhase" ) || GetGlobalNetBool( "banksOpen" )
+ if ( !IsValid( GetGlobalNetEnt( campEntVarName ) ) && !waveNotActive )
+ SetGlobalNetEnt( campEntVarName, CreateCampTracker( curCampData, spawnId ) )
- // iterate over ai spawn data for camp
- foreach ( AT_SpawnData spawnData in campSpawnData[ campIdx ] )
+ array<AT_SpawnData> minionSquadDatas
+ foreach ( AT_SpawnData data in curSpawnData )
+ {
+ switch ( data.aitype )
{
- if ( !( spawnData.aitype in file.trackedCampNPCSpawns[ campIdx ] ) )
- file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] <- 0
-
- if ( spawnData.pendingSpawns > 0 || file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] > 0 )
- numActiveCampSpawners++
+ case "npc_soldier":
+ case "npc_spectre":
+ case "npc_stalker":
+ if ( !AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK )
+ minionSquadDatas.append( data )
+ else
+ thread AT_DroppodSquadEvent_Single( curCampData, spawnId, data )
+ break
+
+ case "npc_super_spectre":
+ thread AT_ReaperEvent( curCampData, spawnId, data )
+ break
+ }
+ }
+
+ // minions squad spawn
+ if ( !AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK )
+ {
+ if ( minionSquadDatas.len() > 0 )
+ thread AT_DroppodSquadEvent( curCampData, spawnId, minionSquadDatas )
+ }
+
+ // use campProgressThink for handling wave state
+ thread CampProgressThink( spawnId, totalNPCsToSpawn )
+ }
+ else // bosswave spawn
+ {
+ foreach ( AT_SpawnData data in curSpawnData )
+ {
+ if( data.aitype != "npc_titan" )
+ continue
- // try to spawn as many ai as we can, as long as the camp doesn't already have too many spawned
- int spawnCount
- for ( spawnCount = 0; spawnCount < spawnData.pendingSpawns && spawnCount < spawnData.totalAllowedOnField - file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ]; )
- {
- // not doing this in a generic way atm, but could be good for the future if we want to support more ai
- switch ( spawnData.aitype )
- {
- case "npc_soldier":
- case "npc_spectre":
- case "npc_stalker":
- thread AT_SpawnDroppodSquad( campIdx, spawnData.aitype )
- spawnCount += 4
- break
-
- case "npc_super_spectre":
- thread AT_SpawnReaper( campIdx )
- spawnCount += 1
- break
-
- case "npc_titan":
- thread AT_SpawnBountyTitan( campIdx )
- spawnCount += 1
- break
-
- default:
- print( "BOUNTY HUNT: Tried to spawn unsupported ai of type \"" + "\" at camp " + campIdx )
- }
+ thread AT_BountyTitanEvent( curCampData, spawnId, data )
+ break
+ }
+ }
+ }
+}
+
+void function CampProgressThink( int spawnId, int totalNPCsToSpawn )
+{
+ string campLetter = GetCampLetter( spawnId )
+ string campProgressName = campLetter + "campProgress"
+ string campEntVarName = "camp" + string( spawnId + 1 ) + "Ent"
+
+ // initial wait
+ SetGlobalNetFloat( campProgressName, 1.0 )
+
+ // TODO: random wait, make this a constant ??
+ wait 3.0
+
+ float cleanUpTime = -1.0
+
+ while ( true )
+ {
+ int npcsLeft
+ // get all npcs might be in this camp
+ for ( int i = 0; i < 5; i++ )
+ {
+ string netVarName = string( i + 1 ) + campLetter + "campCount"
+ int netVarValue = GetGlobalNetInt( netVarName )
+ if ( netVarValue >= 0 ) // uninitialized network var starts from -1, avoid checking them
+ npcsLeft += netVarValue
+ }
+
+ float campLeft = float( npcsLeft ) / float( totalNPCsToSpawn )
+ SetGlobalNetFloat( campProgressName, campLeft )
+
+ if( npcsLeft <= AT_CAMP_BORED_NPCS_LEFT_TO_START_CLEANUP && cleanUpTime < 0.0 )
+ {
+ cleanUpTime = Time() + AT_CAMP_BORED_CLEANUP_WAIT
+ print("Cleanup timer started!")
+ }
+
+ if( Time() > cleanUpTime && cleanUpTime > 0.0 && spawnId in file.campScriptEntArrays )
+ {
+ foreach( int handle in file.campScriptEntArrays[spawnId] )
+ {
+ array<entity> entities = GetScriptManagedEntArray( handle )
+ entities.removebyvalue( null )
+ foreach ( entity ent in entities )
+ {
+ if ( IsAlive( ent ) && ent.IsNPC() )
+ {
+ printt( "Killing bored AI " + ent.GetClassName() + " at " + ent.GetOrigin() )
+ ent.Die()
}
-
- // track spawns
- file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] += spawnCount
- spawnData.pendingSpawns -= spawnCount
}
}
+ }
+
+ if ( campLeft <= 0.0 ) // camp wiped!
+ {
+ PlayFactionDialogueToTeam( "bh_cleared" + campLetter, TEAM_IMC )
+ PlayFactionDialogueToTeam( "bh_cleared" + campLetter, TEAM_MILITIA )
+
+ entity campEnt = GetGlobalNetEnt( campEntVarName )
+ if ( IsValid( campEnt ) )
+ campEnt.Signal( "ATCampClean" ) // destroy the camp ent
+
+ // check if both camps being destroyed
+ if ( !IsValid( GetGlobalNetEnt( "camp1Ent" ) ) && !IsValid( GetGlobalNetEnt( "camp2Ent" ) ) )
+ svGlobal.levelEnt.Signal( "ATAllCampsClean" ) // end the wave
- if ( numActiveCampSpawners == 0 )
- break
-
- wait 0.5
+ return
}
-
- wait WAVE_STATE_TRANSITION_TIME
-
- // banking phase
+
+ WaitFrame()
}
}
// entity funcs
+// camp
+entity function CreateCampTracker( AT_WaveOrigin campData, int spawnId )
+{
+ // store data
+ vector campOrigin = campData.origin
+ float campRadius = campData.radius
+ float campHeight = campData.height
+ // add a minimap icon
+ entity mapIconEnt = CreateEntity( "prop_script" )
+ DispatchSpawn( mapIconEnt )
+
+ mapIconEnt.SetOrigin( campOrigin )
+ mapIconEnt.DisableHibernation()
+ SetTeam( mapIconEnt, AT_AI_TEAM )
+ mapIconEnt.Minimap_AlwaysShow( TEAM_IMC, null )
+ mapIconEnt.Minimap_AlwaysShow( TEAM_MILITIA, null )
+
+ mapIconEnt.Minimap_SetCustomState( GetCampMinimapState( spawnId ) )
+ mapIconEnt.Minimap_SetAlignUpright( true )
+ mapIconEnt.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ mapIconEnt.Minimap_SetObjectScale( campRadius / 16000.0 ) // proper icon on the map
+
+ // attach a location tracker
+ entity tracker = GetAvailableLocationTracker()
+ tracker.SetOwner( mapIconEnt ) // needs a owner to show up
+ tracker.SetOrigin( campOrigin )
+ SetLocationTrackerRadius( tracker, campRadius )
+ SetLocationTrackerID( tracker, spawnId )
+ DispatchSpawn( tracker )
+
+ thread TrackWaveEndForCampInfo( tracker, mapIconEnt )
+ return tracker
+}
+
+void function TrackWaveEndForCampInfo( entity tracker, entity mapIconEnt )
+{
+ tracker.EndSignal( "OnDestroy" )
+ tracker.EndSignal( "ATCampClean" )
+
+ OnThreadEnd
+ (
+ function(): ( tracker, mapIconEnt )
+ {
+ // camp cleaned, wave or game ended, destroy the camp info
+ if ( IsValid( tracker ) )
+ tracker.Destroy()
+
+ if ( IsValid( mapIconEnt ) )
+ mapIconEnt.Destroy()
+ }
+ )
+
+ WaitSignal( svGlobal.levelEnt, "GameStateChanged", "ATWaveEnd" )
+}
+
+string function GetCampLetter( int spawnId )
+{
+ return spawnId == 0 ? "A" : "B"
+}
+
+int function GetCampMinimapState( int id )
+{
+ switch ( id )
+ {
+ case 0:
+ return eMinimapObject_prop_script.AT_DROPZONE_A
+ case 1:
+ return eMinimapObject_prop_script.AT_DROPZONE_B
+ case 2:
+ return eMinimapObject_prop_script.AT_DROPZONE_C
+ }
+
+ unreachable
+}
+
+//////////////////////////////
+///// CAMP FUNCTIONS END /////
+//////////////////////////////
+
+
+
+//////////////////////////
+///// BANK FUNCTIONS /////
+//////////////////////////
+
+void function AT_BankActiveThink( entity bank )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+ bank.EndSignal( "OnDestroy" )
+
+ // Banks closed
+ OnThreadEnd
+ (
+ function(): ( bank )
+ {
+ if ( IsValid( bank ) )
+ {
+ // Update use prompt
+ if ( GetGameState() != eGameState.Playing )
+ bank.UnsetUsable()
+ else
+ bank.SetUsePrompts( "#AT_USE_BANK_CLOSED", "#AT_USE_BANK_CLOSED" )
+
+ thread PlayAnim( bank, "mh_active_2_inactive" )
+ FadeOutSoundOnEntity( bank, "Mobile_Hardpoint_Idle", 0.5 )
+ bank.Minimap_Hide( TEAM_IMC, null )
+ bank.Minimap_Hide( TEAM_MILITIA, null )
+ }
+ }
+ )
+
+ // Update use prompt to usable
+ bank.SetUsable()
+ bank.SetUsePrompts( "#AT_USE_BANK", "#AT_USE_BANK_PC" )
+
+ thread PlayAnim( bank, "mh_inactive_2_active" )
+ EmitSoundOnEntity( bank, "Mobile_Hardpoint_Idle" )
+
+ // Show minimap icon for bank
+ bank.Minimap_AlwaysShow( TEAM_IMC, null )
+ bank.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ bank.Minimap_SetCustomState( eMinimapObject_prop_script.AT_BANK )
+
+ // Wait for bank close or game end
+ while ( GetGlobalNetBool( "banksOpen" ) )
+ WaitFrame()
+}
+
+function OnPlayerUseBank( bank, player )
+{
+ // Banks are always usable so that we can show the use prompt
+ // Only allow deposit when banks are open
+ if ( !GetGlobalNetBool( "banksOpen" ) )
+ return
+
+ expect entity( bank )
+ expect entity( player )
+
+ // bank.SetUsableByGroup( "pilot" ) didn't seem to work so we just
+ // exit here if player is in a titan
+ if( player.IsTitan() )
+ return
+
+ // Player has no bonus, try to send a tip using SendHUDMessage
+ if ( AT_GetPlayerBonusPoints( player ) == 0 )
+ {
+ ATSendDepositTipToPlayer( player, "#AT_USE_BANK_NO_BONUS_HINT" )
+ return
+ }
+
+ // Prevent more than one instance of this thread running
+ if ( !file.playerBankUploading[ player ] )
+ thread PlayerUploadingBonus_Threaded( bank, player )
+}
+
+bool function ATSendDepositTipToPlayer( entity player, string message )
+{
+ if ( Time() < file.playerHudMessageAllowedTime[ player ] )
+ return false
+
+ SendHudMessage( player, message, -1, 0.4, 255, 255, 255, 255, 0.5, 1.0, 0.5 )
+ file.playerHudMessageAllowedTime[ player ] = Time() + AT_PLAYER_HUD_MESSAGE_COOLDOWN
+
+ return true
+}
+
+struct AT_playerUploadStruct
+{
+ bool uploadSuccess = false
+ int depositedPoints = 0
+}
+
+void function PlayerUploadingBonus_Threaded( entity bank, entity player )
+{
+ bank.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+
+ file.playerBankUploading[ player ] = true
+
+ // this literally only exists because structs are passed by ref,
+ // and primitives like ints and bools are passed by val
+ // which meant that the OnThreadEnd was just getting 0 and false
+ AT_playerUploadStruct uploadInfo
+
+ // Cleanup and call finish deposit func
+ OnThreadEnd
+ (
+ function(): ( player, uploadInfo )
+ {
+ if ( IsValid( player ) )
+ {
+ file.playerBankUploading[ player ] = false
+
+ // Clean up looping sound
+ StopSoundOnEntity( player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_1P" )
+ StopSoundOnEntity( player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_3P" )
+
+ // Do medal event
+ // TODO: Check if vanilla actually do.s this every time you finish depositing???
+ AddPlayerScore( player, "AttritionCashedBonus" )
+
+ // Do server callback
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_FinishDeposit",
+ uploadInfo.depositedPoints // deposit
+ )
+
+ player.SetPlayerNetBool( "AT_playerUploading", false )
+
+ if ( uploadInfo.uploadSuccess ) // Player deposited all remaining bonus
+ {
+ // Emit uploading successful sound
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Successful_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Successful_3P" )
+
+ // player is MVP
+ int ourScore = player.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ bool isMVP = true
+ foreach(teamPlayer in GetPlayerArrayOfTeam(player.GetTeam()))
+ {
+ if (ourScore < teamPlayer.GetPlayerGameStat( PGS_ASSAULT_SCORE ))
+ {
+ isMVP = false
+ break
+ }
+ }
+ if (isMVP)
+ PlayFactionDialogueToPlayer( "bh_mvp", player )
+ }
+ else // Player was killed or left the bank radius
+ {
+ // Emit uploading failed sound
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Unsuccessful_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Unsuccessful_3P" )
+ }
+ }
+ }
+ )
+
+ // Uploading start sound
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_Start_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_Start_3P" )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_3P" )
+
+ player.SetPlayerNetBool( "AT_playerUploading", true )
+
+ // Upload bonus while the player is within range of the bank
+ while ( Distance( player.GetOrigin(), bank.GetOrigin() ) <= AT_BANK_DEPOSIT_RADIUS && GetGlobalNetBool( "banksOpen" ) )
+ {
+ // Calling this moves the "Uploading..." graphic to the same place it is
+ // in vanilla
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AT_ShowRespawnBonusLoss" )
+
+ int bonusToUpload = int( min( AT_BANK_DEPOSIT_RATE, AT_GetPlayerBonusPoints( player ) ) )
+ // No more bonus to upload, return
+ if ( bonusToUpload == 0 )
+ {
+ uploadInfo.uploadSuccess = true
+ return
+ }
-void function AT_SpawnDroppodSquad( int camp, string aiType )
+ // Remove bonus points and add them to total poins
+ AT_AddPlayerBonusPoints( player, -bonusToUpload )
+ AT_AddPlayerTotalPoints( player, bonusToUpload )
+
+ uploadInfo.depositedPoints += bonusToUpload
+ WaitFrame()
+ }
+}
+
+//////////////////////////////
+///// BANK FUNCTIONS END /////
+//////////////////////////////
+
+
+
+/////////////////////////
+///// NPC FUNCTIONS /////
+/////////////////////////
+
+int function GetScriptManagedNPCArrayLength_Alive( int scriptManagerId )
+{
+ array<entity> entities = GetScriptManagedEntArray( scriptManagerId )
+ entities.removebyvalue( null )
+ int npcsAlive = 0
+ foreach ( entity ent in entities )
+ {
+ if ( IsAlive( ent ) && ent.IsNPC() )
+ npcsAlive += 1
+ }
+ return npcsAlive
+}
+
+void function AT_DroppodSquadEvent( AT_WaveOrigin campData, int spawnId, array<AT_SpawnData> minionDatas )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+ // create a script managed array for all handled minions
+ int eventManager = CreateScriptManagedEntArray()
+
+ if( !(spawnId in file.campScriptEntArrays) )
+ file.campScriptEntArrays[spawnId] <- []
+
+ file.campScriptEntArrays[spawnId].append(eventManager)
+
+ int totalAllowedOnField = SQUAD_SIZE * AT_DROPPOD_SQUADS_ALLOWED_ON_FIELD
+ while ( true )
+ {
+ foreach ( AT_SpawnData data in minionDatas )
+ {
+ string ent = data.aitype
+ waitthread AT_SpawnDroppodSquad( campData, spawnId, ent, eventManager )
+ data.pendingSpawns -= SQUAD_SIZE
+ if ( data.pendingSpawns <= 0 ) // current spawn data has reached max spawn amount
+ minionDatas.removebyvalue( data ) // remove this data
+ if ( GetScriptManagedNPCArrayLength_Alive( eventManager ) >= totalAllowedOnField ) // we have enough npcs on field?
+ break // stop following spawning functions
+ }
+ if ( minionDatas.len() == 0 ) // all spawn data has finished spawn
+ return
+
+ int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ while ( npcOnFieldCount >= totalAllowedOnField - SQUAD_SIZE ) // wait until we have lost more than 1 squad
+ {
+ WaitFrame()
+ npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ }
+ }
+}
+
+// for AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK, handles a single spawndata
+void function AT_DroppodSquadEvent_Single( AT_WaveOrigin campData, int spawnId, AT_SpawnData data )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+
+ // get ent and create a script managed array for current event
+ string ent = data.aitype
+ int eventManager = CreateScriptManagedEntArray()
+
+ if( !(spawnId in file.campScriptEntArrays) )
+ file.campScriptEntArrays[spawnId] <- []
+
+ file.campScriptEntArrays[spawnId].append(eventManager)
+
+ int totalAllowedOnField = data.totalAllowedOnField // mostly 12 for grunts and spectres, too much!
+ // start spawner
+ while ( true )
+ {
+ waitthread AT_SpawnDroppodSquad( campData, spawnId, ent, eventManager )
+ data.pendingSpawns -= SQUAD_SIZE
+ if ( data.pendingSpawns <= 0 ) // we have reached max npcs
+ return // stop any spawning functions
+
+ int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ while ( npcOnFieldCount >= totalAllowedOnField - SQUAD_SIZE ) // wait until we have less npcs than allowed count
+ {
+ WaitFrame()
+ npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ }
+ }
+}
+
+void function AT_SpawnDroppodSquad( AT_WaveOrigin campData, int spawnId, string aiType, int scriptManagerId )
{
entity spawnpoint
- if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 )
- spawnpoint = file.camps[ camp ].ent
+ if ( campData.dropPodSpawnPoints.len() == 0 )
+ spawnpoint = campData.ent
else
- spawnpoint = file.camps[ camp ].dropPodSpawnPoints.getrandom()
+ spawnpoint = campData.dropPodSpawnPoints.getrandom()
+ // anti-crash
+ if ( !IsValid( spawnpoint ) )
+ spawnpoint = campData.ent
// add variation to spawns
wait RandomFloat( 1.0 )
- AiGameModes_SpawnDropPod( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), BH_AI_TEAM, aiType, void function( array<entity> guys ) : ( camp, aiType )
- {
- AT_HandleSquadSpawn( guys, camp, aiType )
- })
+ AiGameModes_SpawnDropPod(
+ spawnpoint.GetOrigin(),
+ spawnpoint.GetAngles(),
+ AT_AI_TEAM,
+ aiType,
+ // squad handler
+ void function( array<entity> guys ) : ( campData, spawnId, aiType, scriptManagerId )
+ {
+ AT_HandleSquadSpawn( guys, campData, spawnId, aiType, scriptManagerId )
+ },
+ eDropPodFlag.DISSOLVE_AFTER_DISEMBARKS
+ )
}
-void function AT_HandleSquadSpawn( array<entity> guys, int camp, string aiType )
+void function AT_HandleSquadSpawn( array<entity> guys, AT_WaveOrigin campData, int spawnId, string aiType, int scriptManagerId )
{
foreach ( entity guy in guys )
{
- guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
-
- // untrack them on death
- thread AT_WaitToUntrackNPC( guy, camp, aiType )
+ // TODO: NPCs still seem to go outside their camp ???
+ //guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
+
+ // tracking lifetime
+ AddToScriptManagedEntArray( scriptManagerId, guy )
+ thread AT_TrackNPCLifeTime( guy, spawnId, aiType )
+
+ thread AT_ForceAssaultAroundCamp( guy, campData )
+ }
+}
+
+void function AT_ForceAssaultAroundCamp( entity guy, AT_WaveOrigin campData )
+{
+ guy.EndSignal( "OnDestroy" )
+ guy.EndSignal( "OnDeath" )
+
+ // goal check
+ vector ornull goalPos = NavMesh_ClampPointForAI(campData.origin, guy)
+ goalPos = goalPos == null ? campData.origin : goalPos
+ expect vector(goalPos)
+
+ float goalRadius = campData.radius / 4
+ float guyGoalRadius = guy.GetMinGoalRadius()
+ if ( guyGoalRadius > goalRadius ) // this npc cannot use forced goal radius?
+ goalRadius = guyGoalRadius
+
+ while( true )
+ {
+ guy.AssaultPoint( goalPos )
+ guy.AssaultSetGoalRadius( goalRadius )
+ guy.AssaultSetFightRadius( 0 )
+ guy.AssaultSetArrivalTolerance( int(goalRadius) )
+
+ wait RandomFloatRange( 1, 5 )
+ }
+}
+
+void function AT_ReaperEvent( AT_WaveOrigin campData, int spawnId, AT_SpawnData data )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+
+ // create a script managed array for current event
+ int eventManager = CreateScriptManagedEntArray()
+
+ if( !(spawnId in file.campScriptEntArrays) )
+ file.campScriptEntArrays[spawnId] <- []
+
+ file.campScriptEntArrays[spawnId].append(eventManager)
+
+ int totalAllowedOnField = 1 // 1 allowed at the same time for heavy armor units
+ if ( AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK )
+ totalAllowedOnField = data.totalAllowedOnField
+
+ while ( true )
+ {
+ waitthread AT_SpawnReaper( campData, spawnId, eventManager )
+ data.pendingSpawns -= 1
+ if ( data.pendingSpawns <= 0 ) // we have reached max npcs
+ return // stop any spawning functions
+
+ int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ while ( npcOnFieldCount >= totalAllowedOnField ) // wait until we have less npcs than allowed count
+ {
+ WaitFrame()
+ npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ }
}
}
-void function AT_SpawnReaper( int camp )
+void function AT_SpawnReaper( AT_WaveOrigin campData, int spawnId, int scriptManagerId )
{
entity spawnpoint
- if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 )
- spawnpoint = file.camps[ camp ].ent
+ if ( campData.dropPodSpawnPoints.len() == 0 )
+ spawnpoint = campData.ent
else
- spawnpoint = file.camps[ camp ].titanSpawnPoints.getrandom()
+ spawnpoint = campData.dropPodSpawnPoints.getrandom()
+ // anti-crash
+ if ( !IsValid( spawnpoint ) )
+ spawnpoint = campData.ent
// add variation to spawns
wait RandomFloat( 1.0 )
- AiGameModes_SpawnReaper( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), BH_AI_TEAM, "npc_super_spectre",void function( entity reaper ) : ( camp )
+ AiGameModes_SpawnReaper(
+ spawnpoint.GetOrigin(),
+ spawnpoint.GetAngles(),
+ AT_AI_TEAM,
+ "npc_super_spectre_aitdm",
+ // reaper handler
+ void function( entity reaper ) : ( campData, spawnId, scriptManagerId )
+ {
+ AT_HandleReaperSpawn( reaper, campData, spawnId, scriptManagerId )
+ }
+ )
+}
+
+void function AT_HandleReaperSpawn( entity reaper, AT_WaveOrigin campData, int spawnId, int scriptManagerId )
+{
+ // tracking lifetime
+ AddToScriptManagedEntArray( scriptManagerId, reaper )
+ thread AT_TrackNPCLifeTime( reaper, spawnId, "npc_super_spectre" )
+
+ thread AT_ForceAssaultAroundCamp( reaper, campData )
+}
+
+void function AT_BountyTitanEvent( AT_WaveOrigin campData, int spawnId, AT_SpawnData data )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+
+ // create a script managed array for current event
+ int eventManager = CreateScriptManagedEntArray()
+
+ int totalAllowedOnField = 1 // 1 allowed at the same time for heavy armor units
+ if ( AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK )
+ totalAllowedOnField = data.totalAllowedOnField
+ while ( true )
{
- thread AT_WaitToUntrackNPC( reaper, camp, "npc_super_spectre" )
- })
+ waitthread AT_SpawnBountyTitan( campData, spawnId, eventManager )
+ data.pendingSpawns -= 1
+ if ( data.pendingSpawns <= 0 ) // we have reached max npcs
+ return // stop any spawning functions
+
+ int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ while ( npcOnFieldCount >= totalAllowedOnField ) // wait until we have less npcs than allowed count
+ {
+ WaitFrame()
+ npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ }
+ }
}
-void function AT_SpawnBountyTitan( int camp )
+void function AT_SpawnBountyTitan( AT_WaveOrigin campData, int spawnId, int scriptManagerId )
{
entity spawnpoint
- if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 )
- spawnpoint = file.camps[ camp ].ent
+ if ( campData.titanSpawnPoints.len() == 0 )
+ spawnpoint = campData.ent
else
- spawnpoint = file.camps[ camp ].titanSpawnPoints.getrandom()
+ spawnpoint = campData.titanSpawnPoints.getrandom()
+ // anti-crash
+ if ( !IsValid( spawnpoint ) )
+ spawnpoint = campData.ent
// add variation to spawns
wait RandomFloat( 1.0 )
@@ -320,57 +1625,191 @@ void function AT_SpawnBountyTitan( int camp )
int bountyID = 0
try
{
- bountyID = ReserveBossID( VALID_BOUNTY_TITAN_SETTINGS.getrandom() )
+ bountyID = ReserveBossID( AT_BOUNTY_TITANS_AI_SETTINGS.getrandom() )
}
catch ( ex ) {} // if we go above the expected wave count that vanilla supports, there's basically no way to ensure that this func won't error, so default 0 after that point
string aisettings = GetTypeFromBossID( bountyID )
string titanClass = expect string( Dev_GetAISettingByKeyField_Global( aisettings, "npc_titan_player_settings" ) )
+ AiGameModes_SpawnTitan(
+ spawnpoint.GetOrigin(),
+ spawnpoint.GetAngles(),
+ AT_AI_TEAM,
+ titanClass,
+ aisettings,
+ // titan handler
+ void function( entity titan ) : ( campData, spawnId, bountyID, scriptManagerId )
+ {
+ AT_HandleBossTitanSpawn( titan, campData, spawnId, bountyID, scriptManagerId )
+ }
+ )
+}
+
+void function AT_HandleBossTitanSpawn( entity titan, AT_WaveOrigin campData, int spawnId, int bountyID, int scriptManagerId )
+{
+ // set the bounty to be campEnt, for client tracking
+ SetGlobalNetEnt( "camp" + string( spawnId + 1 ) + "Ent", titan )
+ // set up health
+ titan.SetMaxHealth( titan.GetMaxHealth() * AT_BOUNTY_TITAN_HEALTH_MULTIPLIER )
+ titan.SetHealth( titan.GetMaxHealth() )
+ // make minimap always show them and highlight them
+ titan.Minimap_AlwaysShow( TEAM_IMC, null )
+ titan.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ thread BountyBossHighlightThink( titan )
+
+ // set up titan-specific death callbacks, mark it as bounty boss for finalDamageCallbacks to work
+ file.titanIsBountyBoss[ titan ] <- true
+ file.bountyTitanRewards[ titan ] <- ATTRITION_SCORE_BOSS_DAMAGE
+ AddEntityCallback_OnKilled( titan, OnBountyTitanKilled )
- AiGameModes_SpawnTitan( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), BH_AI_TEAM, titanClass, aisettings, void function( entity titan ) : ( camp, bountyID )
+ titan.GetTitanSoul().soul.skipDoomState = true
+ // i feel like this should be localised, but there's nothing for it in r1_english?
+ titan.SetTitle( GetNameFromBossID( bountyID ) )
+
+ // tracking lifetime
+ AddToScriptManagedEntArray( scriptManagerId, titan )
+ thread AT_TrackNPCLifeTime( titan, spawnId, "npc_titan" )
+}
+
+void function BountyBossHighlightThink( entity titan )
+{
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDeath" )
+
+ while ( true )
{
- // set up titan-specific death/damage callbacks
- AddEntityCallback_OnDamaged( titan, OnBountyDamaged)
- AddEntityCallback_OnKilled( titan, OnBountyKilled )
-
- titan.GetTitanSoul().soul.skipDoomState = true
- // i feel like this should be localised, but there's nothing for it in r1_english?
- titan.SetTitle( GetNameFromBossID( bountyID ) )
- thread AT_WaitToUntrackNPC( titan, camp, "npc_titan" )
- } )
+ Highlight_SetEnemyHighlight( titan, "enemy_boss_bounty" )
+ titan.WaitSignal( "StopPhaseShift" ) // prevent phase shift mess up highlights
+ }
}
-// Tracked entities will require their own "wallet"
-// for titans it should be used for rounding error compenstation
-// for infantry it sould be used to store money if the npc kills a player
-void function OnBountyDamaged( entity titan, var damageInfo )
+void function OnNPCTitanFinalDamaged( entity titan, var damageInfo )
+{
+ if ( titan in file.titanIsBountyBoss )
+ OnBountyTitanDamaged( titan, damageInfo )
+}
+
+void function OnBountyTitanDamaged( entity titan, var damageInfo )
{
entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsValid( attacker ) ) // delayed by projectile shots
+ return
+ // damaged by npc or something?
if ( !attacker.IsPlayer() )
- attacker = GetLatestAssistingPlayerInfo( titan ).player
-
- if ( IsValid( attacker ) && attacker.IsPlayer() )
{
- int reward = int ( BOUNTY_TITAN_DAMAGE_POOL * DamageInfo_GetDamage( damageInfo ) / titan.GetMaxHealth() )
- printt ( titan.GetMaxHealth(), DamageInfo_GetDamage( damageInfo ) )
-
- AT_AddPlayerCash( attacker, reward )
+ attacker = GetBountyBossDamageOwner( attacker, titan )
+ if ( !IsValid( attacker ) || !attacker.IsPlayer() )
+ 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
+
+ // sometimes damage is not enough to add 1 point, we save the damage for player's next attack
+ if ( !( titan in file.playerSavedBountyDamage[ attacker ] ) )
+ file.playerSavedBountyDamage[ attacker ][ titan ] <- 0
+
+ file.playerSavedBountyDamage[ attacker ][ titan ] += int( DamageInfo_GetDamage( damageInfo ) )
+ if ( file.playerSavedBountyDamage[ attacker ][ titan ] < healthSegment )
+ return // they can't earn reward from this shot
+
+ int damageSegment = file.playerSavedBountyDamage[ attacker ][ titan ] / healthSegment
+ int savedDamageLeft = file.playerSavedBountyDamage[ attacker ][ titan ] % healthSegment
+ file.playerSavedBountyDamage[ attacker ][ titan ] = savedDamageLeft
+
+ float damageFrac = float( damageSegment ) / rewardSegment
+ int rewardLeft = file.bountyTitanRewards[ titan ]
+ int reward = int( ATTRITION_SCORE_BOSS_DAMAGE * damageFrac )
+ if ( reward >= rewardLeft ) // overloaded shot?
+ reward = rewardLeft
+ file.bountyTitanRewards[ titan ] -= reward
+
+ if ( reward > 0 )
+ AT_AddPlayerBonusPointsForBossDamaged( attacker, titan, reward, damageInfo )
}
-void function OnBountyKilled( entity titan, var damageInfo )
+void function OnBountyTitanKilled( entity titan, var damageInfo )
{
entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsValid( attacker ) ) // delayed by projectile shots
+ return
+ // damaged by npc or something?
if ( !attacker.IsPlayer() )
- attacker = GetLatestAssistingPlayerInfo( titan ).player
+ {
+ attacker = GetBountyBossDamageOwner( attacker, titan )
+ if ( !IsValid( attacker ) || !attacker.IsPlayer() )
+ return
+ }
+
+ // add all remaining reward to attacker
+ // bounty killed bonus handled by AT_PlayerOrNPCKilledScoreEvent()
+ int rewardLeft = file.bountyTitanRewards[ titan ]
+ delete file.bountyTitanRewards[ titan ]
+ if ( rewardLeft > 0 )
+ AT_AddPlayerBonusPointsForBossDamaged( attacker, titan, rewardLeft, damageInfo )
+
+ // remove this bounty's damage saver
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( titan in file.playerSavedBountyDamage[ player ] )
+ delete file.playerSavedBountyDamage[ player ][ titan ]
+ }
+
+ // faction dialogue
+ int team = attacker.GetTeam()
+ PlayFactionDialogueToPlayer( "bh_playerKilledBounty", attacker )
+ PlayFactionDialogueToTeamExceptPlayer( "bh_bountyClaimedByFriendly", team, attacker )
+ PlayFactionDialogueToTeam( "bh_bountyClaimedByEnemy", GetOtherTeam( team ) )
+}
+
+entity function GetBountyBossDamageOwner( entity attacker, entity titan )
+{
+ if ( attacker.IsPlayer() ) // already a player
+ return attacker
- if ( IsValid( attacker ) && attacker.IsPlayer() )
- AT_AddPlayerCash( attacker, BOUNTY_TITAN_KILL_REWARD )
+ if ( attacker.IsTitan() ) // attacker is a npc titan
+ {
+ // try to find it's pet titan owner
+ if ( IsValid( GetPetTitanOwner( attacker ) ) )
+ return GetPetTitanOwner( attacker )
+ }
+
+ // other damages or non-owner npcs, not sure how it happens, just use this titan's last attacker
+ return GetLatestAssistingPlayerInfo( titan ).player
}
-void function AT_WaitToUntrackNPC( entity guy, int camp, string aiType )
+void function AT_TrackNPCLifeTime( entity guy, int spawnId, string aiType )
{
guy.WaitSignal( "OnDeath", "OnDestroy" )
- file.trackedCampNPCSpawns[ camp ][ aiType ]--
+
+ string npcNetVar = GetNPCNetVarName( aiType, spawnId )
+ SetGlobalNetInt( npcNetVar, GetGlobalNetInt( npcNetVar ) - 1 )
+}
+
+
+// network var
+string function GetNPCNetVarName( string className, int spawnId )
+{
+ string npcId = string( GetAiTypeInt( className ) + 1 )
+ string campLetter = GetCampLetter( spawnId )
+ if ( npcId == "0" ) // cannot find this ai support!
+ {
+ if ( className == "npc_super_spectre" ) // stupid, reapers are not handled by GetAiTypeInt(), but it must be 4
+ return "4" + campLetter + "campCount"
+ return ""
+ }
+ return npcId + campLetter + "campCount"
}
+
+/////////////////////////////
+///// NPC FUNCTIONS END /////
+/////////////////////////////
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
index 705b7836..c5765300 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
@@ -471,8 +471,10 @@ void function HardpointThink( HardpointStruct hardpoint )
}
else if(cappingTeam==TEAM_UNASSIGNED) // nobody on point
{
- if((GetHardpointState(hardpoint)==CAPTURE_POINT_STATE_AMPED)||(GetHardpointState(hardpoint)==CAPTURE_POINT_STATE_AMPING))
+ if((GetHardpointState(hardpoint)>=CAPTURE_POINT_STATE_AMPED) || (GetHardpointState(hardpoint)==CAPTURE_POINT_STATE_SELF_UNAMPING))
{
+ if (GetHardpointState(hardpoint) == CAPTURE_POINT_STATE_AMPED)
+ SetHardpointState(hardpoint,CAPTURE_POINT_STATE_SELF_UNAMPING) // plays a pulsating effect on the UI only when the hardpoint is amped
SetHardpointCappingTeam(hardpoint,hardpointEnt.GetTeam())
SetHardpointCaptureProgress(hardpoint,max(1.0,GetHardpointCaptureProgress(hardpoint)-(deltaTime/HARDPOINT_AMPED_DELAY)))
if(GetHardpointCaptureProgress(hardpoint)<=1.001) // unamp
@@ -546,8 +548,10 @@ void function HardpointThink( HardpointStruct hardpoint )
}
else if(file.ampingEnabled)//amping or reamping
{
- if(GetHardpointState(hardpoint)<CAPTURE_POINT_STATE_AMPING)
- SetHardpointState(hardpoint,CAPTURE_POINT_STATE_AMPING)
+ // i have no idea why but putting it CAPTURE_POINT_STATE_AMPING will say 'CONTESTED' on the UI
+ // since whether the point is contested is checked above, putting the hardpoint state to a value of 8 fixes it somehow
+ if(GetHardpointState(hardpoint)<=CAPTURE_POINT_STATE_AMPING)
+ SetHardpointState( hardpoint, 8 )
SetHardpointCaptureProgress( hardpoint, min( 2.0, GetHardpointCaptureProgress( hardpoint ) + ( deltaTime / HARDPOINT_AMPED_DELAY * capperAmount ) ) )
if(GetHardpointCaptureProgress(hardpoint)==2.0&&!(GetHardpointState(hardpoint)==CAPTURE_POINT_STATE_AMPED))
{
@@ -645,7 +649,10 @@ void function OnHardpointEntered( entity trigger, entity player )
hardpoint.militiaCappers.append( player )
foreach(CP_PlayerStruct playerStruct in file.players)
if(playerStruct.player == player)
+ {
playerStruct.isOnHardpoint = true
+ player.SetPlayerNetInt( "playerHardpointID", hardpoint.hardpoint.GetHardpointID() )
+ }
}
void function OnHardpointLeft( entity trigger, entity player )
@@ -661,7 +668,10 @@ void function OnHardpointLeft( entity trigger, entity player )
FindAndRemove( hardpoint.militiaCappers, player )
foreach(CP_PlayerStruct playerStruct in file.players)
if(playerStruct.player == player)
+ {
playerStruct.isOnHardpoint = false
+ player.SetPlayerNetInt( "playerHardpointID", 69 ) // an arbitary number to remove the hud from the player
+ }
}
string function CaptureStateToString( int state )
@@ -675,6 +685,7 @@ string function CaptureStateToString( int state )
case CAPTURE_POINT_STATE_CAPTURED:
return "CAPTURED"
case CAPTURE_POINT_STATE_AMPING:
+ case 8:
return "AMPING"
case CAPTURE_POINT_STATE_AMPED:
return "AMPED"
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut
index 27eef177..4bff6038 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut
@@ -6,6 +6,9 @@ void function FFA_Init()
ScoreEvent_SetupEarnMeterValuesForMixedModes()
AddCallback_OnPlayerKilled( OnPlayerKilled )
+
+ // modified for northstar
+ AddCallback_OnClientConnected( OnClientConnected )
}
void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
@@ -16,4 +19,18 @@ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
// why isn't this PGS_SCORE? odd game
attacker.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 )
}
+}
+
+// modified for northstar
+void function OnClientConnected( entity player )
+{
+ thread FFAPlayerScoreThink( player ) // good to have this! instead of DisconnectCallback this could handle a null player
+}
+
+void function FFAPlayerScoreThink( entity player )
+{
+ int team = player.GetTeam()
+
+ player.WaitSignal( "OnDestroy" ) // this can handle disconnecting
+ AddTeamScore( team, -GameRules_GetTeamScore( team ) )
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
index 425a8b8b..ff281d6e 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
@@ -283,12 +283,26 @@ void function CodeCallback_DamagePlayerOrNPC( entity ent, var damageInfo )
return
RunClassDamageFinalCallbacks( ent, damageInfo )
+
#if VERBOSE_DAMAGE_PRINTOUTS
printt( " after class damage final callbacks:", DamageInfo_GetDamage( damageInfo ) )
#endif
if ( DamageInfo_GetDamage( damageInfo ) == 0 )
return
+ // Added via AddEntityCallback_OnFinalDamaged
+ foreach ( callbackFunc in ent.e.entFinalDamageCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " afterAddEntityCallback_OnFinalDamaged callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+
if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
DamageInfo_AddDamageFlags( damageInfo, DAMAGEFLAG_NOPAIN )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_store.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_store.gnut
new file mode 100644
index 00000000..371cc1c7
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_store.gnut
@@ -0,0 +1,933 @@
+
+global function Store_Init
+global function Store_GetCustomizationRefs
+global function Store_GetPatchRefs
+global function Store_GetBannerRefs
+global function Store_GetCamoRefs
+
+global struct CamoRef
+{
+ string ref
+ string pilotRef
+ string titanRef
+}
+
+struct RefData
+{
+ string ref
+ string parentRef
+}
+
+struct
+{
+ table< int, array<string> > customizationRefs
+ table< int, array<string> > patchRefs
+ table< int, array<string> > bannerRefs
+ table< int, array<CamoRef> > camoRefs
+ table< int, array<RefData> > limitedEditionFDRefData
+} file
+
+void function Store_Init()
+{
+ #if SERVER
+ AddClientCommandCallback( "SetHasSeenStore", ClientCommand_SetHasSeenStore )
+ AddClientCommandCallback( "StoreSetNewItemStatus", ClientCommand_StoreSetNewItemStatus )
+ #endif
+
+ file.customizationRefs[ ET_DLC1_ION ] <- []
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_skin_10" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_20" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_ION ] <- []
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_skin_11" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_25" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_ION ] <- []
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_skin_07" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_30" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_31" )
+
+ file.customizationRefs[ ET_DLC1_SCORCH ] <- []
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_skin_07" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_15" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_16" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_19" )
+ file.customizationRefs[ ET_DLC3_SCORCH ] <- []
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_skin_08" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_20" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_24" )
+ file.customizationRefs[ ET_DLC5_SCORCH ] <- []
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_skin_06" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_25" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_29" )
+
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ] <- []
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_skin_10" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_20" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_21" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ] <- []
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_skin_11" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_25" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_26" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ] <- []
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_skin_06" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_30" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_31" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_32" )
+
+ file.customizationRefs[ ET_DLC1_RONIN ] <- []
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_skin_10" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_16" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_20" )
+ file.customizationRefs[ ET_DLC3_RONIN ] <- []
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_skin_11" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_25" )
+ file.customizationRefs[ ET_DLC5_RONIN ] <- []
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_skin_07" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_30" )
+
+ file.customizationRefs[ ET_DLC1_TONE ] <- []
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_skin_06" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_20" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_TONE ] <- []
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_skin_07" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_25" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_TONE ] <- []
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_skin_08" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_30" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_31" )
+
+ file.customizationRefs[ ET_DLC1_LEGION ] <- []
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_skin_07" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_20" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_LEGION ] <- []
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_skin_08" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_25" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_LEGION ] <- []
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_skin_09" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_30" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_31" )
+
+ file.patchRefs[ ET_DLC1_CALLSIGN ] <- []
+ file.patchRefs[ ET_DLC3_CALLSIGN ] <- []
+ file.patchRefs[ ET_DLC5_CALLSIGN ] <- []
+
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_64" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_aces" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_alien" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_apex" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_ares" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_controller" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_drone" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_heartbreaker" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_hexes" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_kodai" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_lastimosa" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_lawai" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_mcor" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_phoenix" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_pilot" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_robot" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_sentry" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_super_spectre" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_vinson" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_wonyeon" )
+
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_balance" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_boot" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_bt_eye" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_buzzsaw" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_crossed_lighting" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_flying_bullet" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_hammer2" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_keyboard" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_lightbulb" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_narwhal" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_peace" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_pilot2" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_robot_eye" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_srs" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_starline" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_taco" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_thumbdown" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_thumbup" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_treble" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_vanguard" )
+
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_monarch_dlc5" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_militia" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_militia_alt" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_imc" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_hammond" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_tri_chevron" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_pilot_circle" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_x" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_nessie" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_spicy" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_crown" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_pawn" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_excite" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_duck" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_sock" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_rabbit" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_peanut" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_clock" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_shamrock" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_trident" )
+
+ file.bannerRefs[ ET_DLC1_CALLSIGN ] <- []
+ file.bannerRefs[ ET_DLC3_CALLSIGN ] <- []
+ file.bannerRefs[ ET_DLC5_CALLSIGN ] <- []
+
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_106_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_107_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_108_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_109_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_110_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_111_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_112_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_113_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_114_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_115_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_116_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_117_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_118_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_119_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_120_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_121_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_122_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_123_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_124_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_125_col" )
+
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_143_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_144_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_145_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_146_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_147_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_148_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_149_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_150_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_151_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_152_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_153_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_154_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_155_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_156_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_157_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_158_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_159_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_160_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_161_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_162_col" )
+
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_166_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_167_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_168_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_169_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_170_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_171_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_172_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_173_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_174_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_175_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_176_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_177_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_178_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_179_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_180_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_181_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_182_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_183_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_184_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_185_col" )
+
+ file.camoRefs[ ET_DLC1_CAMO ] <- []
+ file.camoRefs[ ET_DLC3_CAMO ] <- []
+ file.camoRefs[ ET_DLC5_CAMO ] <- []
+
+ for ( int i = 101; i <= 120; i++ )
+ {
+ AddCamoRef( ET_DLC1_CAMO, i )
+ }
+
+ for ( int i = 121; i <= 140; i++ )
+ {
+ AddCamoRef( ET_DLC3_CAMO, i )
+ }
+
+ // You did it reddit!
+ int numRefs = file.camoRefs[ ET_DLC3_CAMO ].len()
+ CamoRef tempRef = file.camoRefs[ ET_DLC3_CAMO ][numRefs - 1]
+ file.camoRefs[ ET_DLC3_CAMO ][numRefs - 1] = file.camoRefs[ ET_DLC3_CAMO ][numRefs - 2]
+ file.camoRefs[ ET_DLC3_CAMO ][numRefs - 2] = tempRef
+
+ for ( int i = 141; i <= 160; i++ )
+ {
+ AddCamoRef( ET_DLC5_CAMO, i )
+ }
+
+ file.limitedEditionFDRefData[ ET_DLC7_ION_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_ION_WARPAINT ].append( CreateRefData( "ion_skin_fd", "ion" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_ION_WARPAINT ].append( CreateRefData( "callsign_fd_ion_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_SCORCH_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_SCORCH_WARPAINT ].append( CreateRefData( "scorch_skin_fd", "scorch" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_SCORCH_WARPAINT ].append( CreateRefData( "callsign_fd_scorch_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_NORTHSTAR_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_NORTHSTAR_WARPAINT ].append( CreateRefData( "northstar_skin_fd", "northstar" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_NORTHSTAR_WARPAINT ].append( CreateRefData( "callsign_fd_northstar_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_RONIN_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_RONIN_WARPAINT ].append( CreateRefData( "ronin_skin_fd", "ronin" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_RONIN_WARPAINT ].append( CreateRefData( "callsign_fd_ronin_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_TONE_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_TONE_WARPAINT ].append( CreateRefData( "tone_skin_fd", "tone" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_TONE_WARPAINT ].append( CreateRefData( "callsign_fd_tone_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_LEGION_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_LEGION_WARPAINT ].append( CreateRefData( "legion_skin_fd", "legion" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_LEGION_WARPAINT ].append( CreateRefData( "callsign_fd_legion_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_MONARCH_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_MONARCH_WARPAINT ].append( CreateRefData( "monarch_skin_fd", "vanguard" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_MONARCH_WARPAINT ].append( CreateRefData( "callsign_fd_monarch_dynamic" ) )
+}
+
+RefData function CreateRefData( string ref, string parentRef = "" )
+{
+ RefData data
+ data.ref = ref
+ data.parentRef = parentRef
+
+ return data
+}
+
+array<string> function Store_GetCustomizationRefs( int entitlementId )
+{
+ return file.customizationRefs[ entitlementId ]
+}
+
+array<string> function Store_GetPatchRefs( int entitlementId )
+{
+ return file.patchRefs[ entitlementId ]
+}
+
+array<string> function Store_GetBannerRefs( int entitlementId )
+{
+ return file.bannerRefs[ entitlementId ]
+}
+
+array<CamoRef> function Store_GetCamoRefs( int entitlementId )
+{
+ return file.camoRefs[ entitlementId ]
+}
+
+void function AddCamoRef( int entitlementId, int index )
+{
+ CamoRef cref
+ cref.ref = "camo_skin" + index
+ cref.pilotRef = "pilot_camo_skin" + index
+ cref.titanRef = "titan_camo_skin" + index
+ file.camoRefs[ entitlementId ].append( cref )
+}
+
+#if SERVER
+bool function ClientCommand_SetHasSeenStore( entity player, array<string> args )
+{
+ player.SetPersistentVar( "hasSeenStore", true )
+
+ return true
+}
+
+// TODO: refParam is problematic, because it assumes an entitlement maps to a single ref which is not the case for limited edition frontier defense entitlements
+void function StoreUpdatePIN( entity player, int entitlementId, string refParam )
+{
+ printt( "StoreUpdatePIN", entitlementId, refParam)
+
+ switch ( entitlementId )
+ {
+ case ET_DLC1_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc1_bundle", 0 )
+ break
+
+ case ET_DLC3_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc3_bundle", 0 )
+ break
+
+ case ET_DLC5_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc5_bundle", 0 )
+ break
+
+ case ET_PRIME_TITANS_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "sprime_titans_bundle", 0 )
+ break
+
+ case ET_DLC1_PRIME_ION:
+ case ET_DLC1_PRIME_SCORCH:
+ case ET_DLC3_PRIME_NORTHSTAR:
+ case ET_DLC3_PRIME_LEGION:
+ case ET_DLC5_PRIME_TONE:
+ case ET_DLC5_PRIME_RONIN:
+ Assert( refParam != "" )
+ if ( refParam == "" || !ItemDefined( refParam ) )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, refParam, 0 )
+ break
+
+ case ET_DLC1_ION:
+ case ET_DLC1_SCORCH:
+ case ET_DLC1_NORTHSTAR:
+ case ET_DLC1_RONIN:
+ case ET_DLC1_TONE:
+ case ET_DLC1_LEGION:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "customization_" + refParam, 0 )
+ break
+
+ case ET_DLC3_ION:
+ case ET_DLC3_SCORCH:
+ case ET_DLC3_NORTHSTAR:
+ case ET_DLC3_RONIN:
+ case ET_DLC3_TONE:
+ case ET_DLC3_LEGION:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc3_customization_" + refParam, 0 )
+ break
+
+ case ET_DLC5_ION:
+ case ET_DLC5_SCORCH:
+ case ET_DLC5_NORTHSTAR:
+ case ET_DLC5_RONIN:
+ case ET_DLC5_TONE:
+ case ET_DLC5_LEGION:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc5_customization_" + refParam, 0 )
+ break
+
+ case ET_DLC1_CAMO:
+ PIN_BuyItemWithRealMoney( player, false, "dlc_camos", 0 )
+ break
+
+ case ET_DLC3_CAMO:
+ PIN_BuyItemWithRealMoney( player, false, "dlc3_camos", 0 )
+ break
+
+ case ET_DLC5_CAMO:
+ PIN_BuyItemWithRealMoney( player, false, "dlc5_camos", 0 )
+ break
+
+ case ET_DLC1_CALLSIGN:
+ PIN_BuyItemWithRealMoney( player, false, "dlc_callsigns", 0 )
+ break
+
+ case ET_DLC3_CALLSIGN:
+ PIN_BuyItemWithRealMoney( player, false, "dlc3_callsigns", 0 )
+ break
+
+ case ET_DLC5_CALLSIGN:
+ PIN_BuyItemWithRealMoney( player, false, "dlc5_callsigns", 0 )
+ break
+
+ case ET_DLC7_TITAN_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc7_frontier_titan_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC7_ION_WARPAINT:
+ case ET_DLC7_SCORCH_WARPAINT:
+ case ET_DLC7_NORTHSTAR_WARPAINT:
+ case ET_DLC7_RONIN_WARPAINT:
+ case ET_DLC7_TONE_WARPAINT:
+ case ET_DLC7_LEGION_WARPAINT:
+ case ET_DLC7_MONARCH_WARPAINT:
+ PIN_BuyItemWithRealMoney( player, false, "dlc7_frontier_titan_warpaint_" + file.limitedEditionFDRefData[ entitlementId ][0].ref, 0 )
+ break
+
+ case ET_DLC7_WEAPON_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc7_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC7_R201_WARPAINT:
+ case ET_DLC7_G2A5_WARPAINT:
+ case ET_DLC7_FLATLINE_WARPAINT:
+ case ET_DLC7_CAR_WARPAINT:
+ case ET_DLC7_ALTERNATOR_WARPAINT:
+ case ET_DLC7_EVA8_WARPAINT:
+ case ET_DLC7_WINGMAN_WARPAINT:
+ case ET_DLC7_ARCHER_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc7_weapon_warpaint_" + refParam, 0 )
+ break
+
+ case ET_DLC8_WEAPON_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc8_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC8_R201_WARPAINT:
+ case ET_DLC8_HEMLOK_WARPAINT:
+ case ET_DLC8_R97_WARPAINT:
+ case ET_DLC8_KRABER_WARPAINT:
+ case ET_DLC8_SPITFIRE_WARPAINT:
+ case ET_DLC8_DEVOTION_WARPAINT:
+ case ET_DLC8_MOZAMBIQUE_WARPAINT:
+ case ET_DLC8_THUNDERBOLT_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc8_weapon_warpaint_" + refParam, 0 )
+ break
+
+ case ET_JUMPSTARTERBUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc10_jump_starter_pack", 0 )
+
+ case ET_DLC9_WEAPON_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc9_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC9_LSTAR_WARPAINT:
+ case ET_DLC9_MASTIFF_WARPAINT:
+ case ET_DLC9_SIDEWINDER_WARPAINT:
+ case ET_DLC9_R201_WARPAINT:
+ case ET_DLC9_CAR_WARPAINT:
+ case ET_DLC9_SPITFIRE_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc9_weapon_warpaint_" + refParam, 0 )
+ break
+
+ case ET_DLC10_WEAPON_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc10_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC10_R101_WARPAINT:
+ case ET_DLC10_FLATLINE_WARPAINT:
+ case ET_DLC10_VOLT_WARPAINT:
+ case ET_DLC10_ALTERNATOR_WARPAINT:
+ case ET_DLC10_SOFTBALL_WARPAINT:
+ case ET_DLC10_EPG1_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc10_weapon_warpaint_" + refParam, 0 )
+ break
+
+ case ET_DLC11_WEAPON_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc11_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC11_DMR_WARPAINT:
+ case ET_DLC11_DOUBLETAKE_WARPAINT:
+ case ET_DLC11_G2A5_WARPAINT:
+ case ET_DLC11_COLDWAR_WARPAINT:
+ case ET_DLC11_R97_WARPAINT:
+ case ET_DLC11_R101_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc11_weapon_warpaint_" + refParam, 0 )
+ break
+ }
+}
+
+bool function ClientCommand_StoreSetNewItemStatus( entity player, array<string> args )
+{
+ // fix crash
+ if( args.len() < 1 )
+ return true
+
+ int entitlementId = int( args[0] )
+ string refParam
+ string parentRefParam
+
+ if ( args.len() >= 2 )
+ refParam = args[1]
+
+ if ( args.len() >= 3 )
+ parentRefParam = args[2]
+
+ StoreUpdatePIN( player, entitlementId, refParam )
+ StoreSetNewItemStatus( player, entitlementId, refParam, parentRefParam )
+
+ return true
+}
+
+bool function StoreSetNewItemStatus( entity player, int entitlementId, string refParam, string parentRefParam )
+{
+ string e = entitlementId == ET_JUMPSTARTERBUNDLE ? "ET_JUMPSTARTERBUNDLE" : string( entitlementId )
+ printt( "!!!!!!!!!!! StoreSetNewItemStatus() running for entitlement:", e )
+
+ switch ( entitlementId )
+ {
+ case 3:
+ // Prime Titans
+ SetItemNewStatus( player, "ion_prime", "", true )
+ SetItemNewStatus( player, "scorch_prime", "", true )
+
+ // Customization Packs
+ table< string, array<string> > dlc1BundleCustomizationRefs
+ dlc1BundleCustomizationRefs[ "ion" ] <- file.customizationRefs[ ET_DLC1_ION ]
+ dlc1BundleCustomizationRefs[ "tone" ] <- file.customizationRefs[ ET_DLC1_TONE ]
+ dlc1BundleCustomizationRefs[ "scorch" ] <- file.customizationRefs[ ET_DLC1_SCORCH ]
+ dlc1BundleCustomizationRefs[ "legion" ] <- file.customizationRefs[ ET_DLC1_LEGION ]
+ dlc1BundleCustomizationRefs[ "ronin" ] <- file.customizationRefs[ ET_DLC1_RONIN ]
+ dlc1BundleCustomizationRefs[ "northstar" ] <- file.customizationRefs[ ET_DLC1_NORTHSTAR ]
+
+ foreach ( parentRef, childRefs in dlc1BundleCustomizationRefs )
+ {
+ foreach ( ref in childRefs )
+ {
+ if ( !SubitemDefined( parentRef, ref ) )
+ return false
+
+ SetItemNewStatus( player, ref, parentRef, true )
+ }
+ }
+
+ // Callsigns
+ foreach ( ref in file.patchRefs[ ET_DLC1_CALLSIGN ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+
+ break
+
+ case ET_DLC3_BUNDLE:
+ // Prime Titans
+ SetItemNewStatus( player, "northstar_prime", "", true )
+ SetItemNewStatus( player, "legion_prime", "", true )
+
+ // Customization Packs
+ table< string, array<string> > dlc3BundleCustomizationRefs
+ dlc3BundleCustomizationRefs[ "ion" ] <- file.customizationRefs[ ET_DLC3_ION ]
+ dlc3BundleCustomizationRefs[ "tone" ] <- file.customizationRefs[ ET_DLC3_TONE ]
+ dlc3BundleCustomizationRefs[ "scorch" ] <- file.customizationRefs[ ET_DLC3_SCORCH ]
+ dlc3BundleCustomizationRefs[ "legion" ] <- file.customizationRefs[ ET_DLC3_LEGION ]
+ dlc3BundleCustomizationRefs[ "ronin" ] <- file.customizationRefs[ ET_DLC3_RONIN ]
+ dlc3BundleCustomizationRefs[ "northstar" ] <- file.customizationRefs[ ET_DLC3_NORTHSTAR ]
+
+ foreach ( parentRef, childRefs in dlc3BundleCustomizationRefs )
+ {
+ foreach ( ref in childRefs )
+ {
+ if ( !SubitemDefined( parentRef, ref ) )
+ return false
+
+ SetItemNewStatus( player, ref, parentRef, true )
+ }
+ }
+
+ // Callsigns
+ foreach ( ref in file.patchRefs[ ET_DLC3_CALLSIGN ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+
+ break
+
+ case ET_DLC5_BUNDLE:
+ // Prime Titans
+ SetItemNewStatus( player, "ronin_prime", "", true )
+ SetItemNewStatus( player, "tone_prime", "", true )
+
+ // Customization Packs
+ table< string, array<string> > dlc5BundleCustomizationRefs
+ dlc5BundleCustomizationRefs[ "ion" ] <- file.customizationRefs[ ET_DLC5_ION ]
+ dlc5BundleCustomizationRefs[ "tone" ] <- file.customizationRefs[ ET_DLC5_TONE ]
+ dlc5BundleCustomizationRefs[ "scorch" ] <- file.customizationRefs[ ET_DLC5_SCORCH ]
+ dlc5BundleCustomizationRefs[ "legion" ] <- file.customizationRefs[ ET_DLC5_LEGION ]
+ dlc5BundleCustomizationRefs[ "ronin" ] <- file.customizationRefs[ ET_DLC5_RONIN ]
+ dlc5BundleCustomizationRefs[ "northstar" ] <- file.customizationRefs[ ET_DLC5_NORTHSTAR ]
+
+ foreach ( parentRef, childRefs in dlc5BundleCustomizationRefs )
+ {
+ foreach ( ref in childRefs )
+ {
+ if ( !SubitemDefined( parentRef, ref ) )
+ return false
+
+ SetItemNewStatus( player, ref, parentRef, true )
+ }
+ }
+
+ // Callsigns
+ foreach ( ref in file.patchRefs[ ET_DLC5_CALLSIGN ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+
+ break
+
+ case ET_PRIME_TITANS_BUNDLE:
+ SetItemNewStatus( player, "ion_prime", "", true )
+ SetItemNewStatus( player, "scorch_prime", "", true )
+ SetItemNewStatus( player, "northstar_prime", "", true )
+ SetItemNewStatus( player, "legion_prime", "", true )
+ SetItemNewStatus( player, "ronin_prime", "", true )
+ SetItemNewStatus( player, "tone_prime", "", true )
+ break
+
+ case ET_DLC1_PRIME_ION:
+ case ET_DLC1_PRIME_SCORCH:
+ case ET_DLC3_PRIME_NORTHSTAR:
+ case ET_DLC3_PRIME_LEGION:
+ case ET_DLC5_PRIME_TONE:
+ case ET_DLC5_PRIME_RONIN:
+ Assert( refParam != "" )
+ if ( refParam == "" || !ItemDefined( refParam ) )
+ return false
+
+ SetItemNewStatus( player, refParam, "", true )
+ break
+
+ case ET_DLC1_ION:
+ case ET_DLC3_ION:
+ case ET_DLC5_ION:
+ case ET_DLC1_SCORCH:
+ case ET_DLC3_SCORCH:
+ case ET_DLC5_SCORCH:
+ case ET_DLC1_NORTHSTAR:
+ case ET_DLC3_NORTHSTAR:
+ case ET_DLC5_NORTHSTAR:
+ case ET_DLC1_RONIN:
+ case ET_DLC3_RONIN:
+ case ET_DLC5_RONIN:
+ case ET_DLC1_TONE:
+ case ET_DLC3_TONE:
+ case ET_DLC5_TONE:
+ case ET_DLC1_LEGION:
+ case ET_DLC3_LEGION:
+ case ET_DLC5_LEGION:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return false
+
+ foreach ( ref in file.customizationRefs[ entitlementId ] )
+ {
+ if ( !SubitemDefined( refParam, ref ) )
+ return false
+
+ SetItemNewStatus( player, ref, refParam, true )
+ }
+ break
+
+ case ET_DLC1_CAMO:
+ case ET_DLC3_CAMO:
+ case ET_DLC5_CAMO:
+ // Not implemented, way too many camos would show as new
+ break
+
+ case ET_DLC1_CALLSIGN:
+ case ET_DLC3_CALLSIGN:
+ case ET_DLC5_CALLSIGN:
+ foreach ( ref in file.patchRefs[ entitlementId ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+
+ foreach ( ref in file.bannerRefs[ entitlementId ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+ break
+
+ case ET_DLC7_TITAN_WARPAINT_BUNDLE:
+ array<int> childEntitlements = GetChildEntitlements( entitlementId )
+
+ foreach ( entitlement in childEntitlements )
+ {
+ foreach ( data in file.limitedEditionFDRefData[ entitlement ] )
+ SetItemNewStatus( player, data.ref, data.parentRef, true )
+ }
+ break
+
+ case ET_DLC7_ION_WARPAINT:
+ case ET_DLC7_SCORCH_WARPAINT:
+ case ET_DLC7_NORTHSTAR_WARPAINT:
+ case ET_DLC7_RONIN_WARPAINT:
+ case ET_DLC7_TONE_WARPAINT:
+ case ET_DLC7_LEGION_WARPAINT:
+ case ET_DLC7_MONARCH_WARPAINT:
+ foreach ( data in file.limitedEditionFDRefData[ entitlementId ] )
+ SetItemNewStatus( player, data.ref, data.parentRef, true )
+ break
+
+ case ET_DLC7_R201_WARPAINT:
+ case ET_DLC7_G2A5_WARPAINT:
+ case ET_DLC7_FLATLINE_WARPAINT:
+ case ET_DLC7_CAR_WARPAINT:
+ case ET_DLC7_ALTERNATOR_WARPAINT:
+ case ET_DLC7_EVA8_WARPAINT:
+ case ET_DLC7_WINGMAN_WARPAINT:
+ case ET_DLC7_ARCHER_WARPAINT:
+ case ET_DLC8_R201_WARPAINT:
+ case ET_DLC8_HEMLOK_WARPAINT:
+ case ET_DLC8_R97_WARPAINT:
+ case ET_DLC8_KRABER_WARPAINT:
+ case ET_DLC8_SPITFIRE_WARPAINT:
+ case ET_DLC8_DEVOTION_WARPAINT:
+ case ET_DLC8_MOZAMBIQUE_WARPAINT:
+ case ET_DLC8_THUNDERBOLT_WARPAINT:
+ case ET_DLC9_LSTAR_WARPAINT:
+ case ET_DLC9_MASTIFF_WARPAINT:
+ case ET_DLC9_SIDEWINDER_WARPAINT:
+ case ET_DLC9_R201_WARPAINT:
+ case ET_DLC9_CAR_WARPAINT:
+ case ET_DLC9_SPITFIRE_WARPAINT:
+ case ET_DLC10_R101_WARPAINT:
+ case ET_DLC10_FLATLINE_WARPAINT:
+ case ET_DLC10_VOLT_WARPAINT:
+ case ET_DLC10_ALTERNATOR_WARPAINT:
+ case ET_DLC10_SOFTBALL_WARPAINT:
+ case ET_DLC10_EPG1_WARPAINT:
+ case ET_DLC11_DMR_WARPAINT:
+ case ET_DLC11_DOUBLETAKE_WARPAINT:
+ case ET_DLC11_G2A5_WARPAINT:
+ case ET_DLC11_COLDWAR_WARPAINT:
+ case ET_DLC11_R97_WARPAINT:
+ case ET_DLC11_R101_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" || !ItemDefined( refParam ) )
+ return false
+
+ Assert( parentRefParam != "" )
+ if ( parentRefParam == "" || !ItemDefined( parentRefParam ) )
+ return false
+
+ SetItemNewStatus( player, refParam, parentRefParam, true )
+ break
+
+ case ET_DLC7_WEAPON_BUNDLE:
+ SetItemNewStatus( player, "skin_rspn101_wasteland", "mp_weapon_rspn101", true )
+ SetItemNewStatus( player, "skin_g2_masterwork", "mp_weapon_g2", true )
+ SetItemNewStatus( player, "skin_vinson_blue_fade", "mp_weapon_vinson", true )
+ SetItemNewStatus( player, "skin_car_crimson_fury", "mp_weapon_car", true )
+ SetItemNewStatus( player, "skin_alternator_patriot", "mp_weapon_alternator_smg", true )
+ SetItemNewStatus( player, "skin_shotgun_badlands", "mp_weapon_shotgun", true )
+ SetItemNewStatus( player, "skin_wingman_aqua_fade", "mp_weapon_wingman", true )
+ SetItemNewStatus( player, "skin_rocket_launcher_psych_spectre", "mp_weapon_rocket_launcher", true )
+ break
+
+ case ET_DLC8_WEAPON_WARPAINT_BUNDLE:
+ SetItemNewStatus( player, "skin_rspn101_patriot", "mp_weapon_rspn101", true )
+ SetItemNewStatus( player, "skin_hemlok_mochi", "mp_weapon_hemlok", true )
+ SetItemNewStatus( player, "skin_r97_purple_fade", "mp_weapon_r97", true )
+ SetItemNewStatus( player, "skin_kraber_masterwork", "mp_weapon_sniper", true )
+ SetItemNewStatus( player, "skin_spitfire_lead_farmer", "mp_weapon_lmg", true )
+ SetItemNewStatus( player, "skin_devotion_rspn_customs", "mp_weapon_esaw", true )
+ SetItemNewStatus( player, "skin_mozambique_crimson_fury", "mp_weapon_shotgun_pistol", true )
+ SetItemNewStatus( player, "skin_thunderbolt_8bit", "mp_weapon_arc_launcher", true )
+ break
+
+ case ET_DLC9_WEAPON_WARPAINT_BUNDLE:
+ SetItemNewStatus( player, "skin_lstar_heatsink", "mp_weapon_lstar", true )
+ SetItemNewStatus( player, "skin_mastiff_crimson_fury", "mp_weapon_mastiff", true )
+ SetItemNewStatus( player, "skin_sidewinder_masterwork", "mp_weapon_smr", true )
+ SetItemNewStatus( player, "skin_rspn101_halloween", "mp_weapon_rspn101", true )
+ SetItemNewStatus( player, "skin_car_halloween", "mp_weapon_car", true )
+ SetItemNewStatus( player, "skin_spitfire_halloween", "mp_weapon_lmg", true )
+ break
+
+ case ET_DLC10_WEAPON_WARPAINT_BUNDLE:
+ SetItemNewStatus( player, "skin_rspn101_og_blue_fade", "mp_weapon_rspn101_og", true )
+ SetItemNewStatus( player, "skin_vinson_badlands", "mp_weapon_vinson", true )
+ SetItemNewStatus( player, "skin_volt_heatsink", "mp_weapon_hemlok_smg", true )
+ SetItemNewStatus( player, "skin_alternator_headhunter", "mp_weapon_alternator_smg", true )
+ SetItemNewStatus( player, "skin_softball_masterwork", "mp_weapon_softball", true )
+ SetItemNewStatus( player, "skin_epg_mrvn", "mp_weapon_epg", true )
+ break
+
+ case ET_DLC11_WEAPON_WARPAINT_BUNDLE:
+ SetItemNewStatus( player, "skin_dmr_phantom", "mp_weapon_dmr", true )
+ SetItemNewStatus( player, "skin_doubletake_masterwork", "mp_weapon_doubletake", true )
+ SetItemNewStatus( player, "skin_g2_purple_fade", "mp_weapon_g2", true )
+ SetItemNewStatus( player, "skin_coldwar_heatsink", "mp_weapon_pulse_lmg", true )
+ SetItemNewStatus( player, "skin_r97_sky", "mp_weapon_r97", true )
+ SetItemNewStatus( player, "skin_rspn101_crimson_fury", "mp_weapon_rspn101", true )
+ break
+
+ case ET_JUMPSTARTERBUNDLE:
+ UnlockUltimateEdition( player )
+ break
+ }
+
+ return true
+}
+#endif