aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers
diff options
context:
space:
mode:
authorRoyalBlue1 <malte.hoermeyer@web.de>2022-07-15 00:41:36 +0200
committerRoyalBlue1 <malte.hoermeyer@web.de>2022-07-15 00:41:36 +0200
commit1d9b24faf5280db4b57eea42904f24ff4fdd16ba (patch)
tree558c8c8aa6b3d044f5fd9e17284177f96ba354f3 /Northstar.CustomServers
parent88d765b41f4ff94c59fa535d53e708274fa22d26 (diff)
parent85eb27362bd295a9e1560982a3369a688e13ded0 (diff)
downloadNorthstarMods-1d9b24faf5280db4b57eea42904f24ff4fdd16ba.tar.gz
NorthstarMods-1d9b24faf5280db4b57eea42904f24ff4fdd16ba.zip
Merge remote-tracking branch 'upsteam/main' into gamemode_fd
Diffstat (limited to 'Northstar.CustomServers')
-rw-r--r--Northstar.CustomServers/mod.json15
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut5
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut45
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_northstar_cheatcommands.nut69
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut12
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut164
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut504
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut366
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut12
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut53
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut3
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut3
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut28
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut8
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut38
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut17
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut5
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_lf_deck.nut10
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut6
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut51
21 files changed, 1320 insertions, 96 deletions
diff --git a/Northstar.CustomServers/mod.json b/Northstar.CustomServers/mod.json
index 4fbb248d..dba29bc5 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.5.0",
+ "Version": "1.9.0",
"LoadPriority": 0,
"ConVars": [
{
@@ -49,6 +49,10 @@
{
"Name": "ns_should_log_unknown_clientcommands",
"DefaultValue": "1"
+ },
+ {
+ "Name": "ns_allow_kill_commands",
+ "DefaultValue": "0"
}
],
"Scripts": [
@@ -60,6 +64,13 @@
}
},
{
+ "Path": "_northstar_cheatcommands.nut",
+ "RunOn": "SERVER",
+ "ServerCallback": {
+ "After": "NorthstarCheatCommands_Init"
+ }
+ },
+ {
"Path": "_chat.gnut",
"RunOn": "SERVER"
},
@@ -149,4 +160,4 @@
"RunOn": "( SERVER ) && MP"
}
]
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut
index 9e46f99a..4a7f8189 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut
@@ -22,10 +22,11 @@ void function CServerGameDLL_ProcessMessageStartThread(int playerIndex, string m
void function CServerGameDLL_OnReceivedSayTextMessageCallback(int playerIndex, string message, bool isTeam)
{
entity player = GetPlayerByIndex(playerIndex)
- if (player == null) {
+ if (player == null || !player.hasConnected) {
print("Ignored chat message from invalid player index " + playerIndex + ": " + message)
return
}
+ print("Received message from " + player + "(" + player.GetUID() + "): " + message)
ClServer_MessageStruct localMessage
localMessage.message = message
@@ -55,4 +56,4 @@ void function AddCallback_OnReceivedSayTextMessage( ClServer_MessageStruct funct
void function NSSetupChathooksServer() {
getroottable().rawset("CServerGameDLL_ProcessMessageStartThread", CServerGameDLL_ProcessMessageStartThread)
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut
index a31963cf..3e8ac9ea 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut
@@ -3,11 +3,24 @@ global function SvLoadoutsMP_Init
global function SetLoadoutGracePeriodEnabled
global function SetWeaponDropsEnabled
+
+global function AddCallback_OnTryGetTitanLoadout
global function GetTitanLoadoutForPlayer
+global struct sTryGetTitanLoadoutCallbackReturn
+{
+ bool wasChanged = false
+ bool runMoreCallbacks = true
+ TitanLoadoutDef& loadout
+}
+
+typedef TryGetTitanLoadoutCallbackType sTryGetTitanLoadoutCallbackReturn functionref( entity player, TitanLoadoutDef loadout, bool wasChanged )
+
struct {
bool loadoutGracePeriodEnabled = true
bool weaponDropsEnabled = true
+ array< TryGetTitanLoadoutCallbackType > onTryGetTitanLoadoutCallbacks
+
array<entity> dirtyLoadouts
} file
@@ -65,16 +78,40 @@ void function DelayDestroyDroppedWeapon( entity weapon )
weapon.Destroy()
}
+void function AddCallback_OnTryGetTitanLoadout( TryGetTitanLoadoutCallbackType callback )
+{
+ file.onTryGetTitanLoadoutCallbacks.append( callback )
+}
+
TitanLoadoutDef function GetTitanLoadoutForPlayer( entity player )
{
SetActiveTitanLoadout( player ) // set right loadout
-
+ TitanLoadoutDef loadout = GetActiveTitanLoadout( player )
+
// fix bug with titan weapons having null mods
// null mods aren't valid and crash if we try to give them to npc
- TitanLoadoutDef def = GetActiveTitanLoadout( player )
- def.primaryMods.removebyvalue( "null" )
+ loadout.primaryMods.removebyvalue( "null" )
+
+ // allow scripts to modify loadouts
+ bool wasChanged = false
+ foreach ( TryGetTitanLoadoutCallbackType callback in file.onTryGetTitanLoadoutCallbacks )
+ {
+ sTryGetTitanLoadoutCallbackReturn callbackRet = callback( player, loadout, wasChanged )
+
+ // whether the callback has changed the player's titan loadout
+ wasChanged = wasChanged || callbackRet.wasChanged
+ if ( callbackRet.wasChanged )
+ loadout = callbackRet.loadout
+
+ // whether the callback has indicated that we should run no more callbacks ( e.g. if we're forcing a given loadout to be chosen, we shouldn't run any more )
+ if ( !callbackRet.runMoreCallbacks )
+ break
+ }
+
+ // do this again just in case
+ loadout.primaryMods.removebyvalue( "null" )
- return def
+ return loadout
}
void function LoadoutsMPInitPlayer( entity player )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_northstar_cheatcommands.nut b/Northstar.CustomServers/mod/scripts/vscripts/_northstar_cheatcommands.nut
new file mode 100644
index 00000000..c79265ac
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_northstar_cheatcommands.nut
@@ -0,0 +1,69 @@
+untyped
+global function NorthstarCheatCommands_Init
+
+void function NorthstarCheatCommands_Init()
+{
+ AddClientCommandCallback( "noclip", ClientCommandCallbackToggleNoclip )
+ AddClientCommandCallback( "notarget", ClientCommandCallbackToggleNotarget )
+ AddClientCommandCallback( "demigod", ClientCommandCallbackToggleDemigod )
+ AddClientCommandCallback( "kill", ClientCommandCallbackKill )
+ AddClientCommandCallback( "explode", ClientCommandCallbackExplode )
+}
+
+bool function ClientCommandCallbackToggleNoclip( entity player, array<string> args )
+{
+ if ( !GetConVarBool( "sv_cheats" ) )
+ return true
+
+ print( player + " TOGGLED NOCLIP" )
+
+ if ( player.IsNoclipping() )
+ player.SetPhysics( MOVETYPE_WALK )
+ else
+ player.SetPhysics( MOVETYPE_NOCLIP )
+
+ return true
+}
+
+bool function ClientCommandCallbackToggleNotarget( entity player, array<string> args )
+{
+ if ( !GetConVarBool( "sv_cheats" ) )
+ return true
+
+ print( player + " TOGGLED NOTARGET" )
+
+ player.SetNoTarget( !player.GetNoTarget() )
+ player.SetNoTargetSmartAmmo( player.GetNoTarget() )
+ return true
+}
+
+bool function ClientCommandCallbackToggleDemigod( entity player, array<string> args )
+{
+ if ( !GetConVarBool( "sv_cheats" ) )
+ return true
+
+ print( player + " TOGGLED DEMIGOD" )
+
+ if ( IsDemigod( player ) )
+ DisableDemigod( player )
+ else
+ EnableDemigod( player )
+
+ return true
+}
+
+bool function ClientCommandCallbackKill( entity player, array<string> args )
+{
+ if ( IsAlive( player ) && ( GetConVarBool( "sv_cheats" ) || GetConVarBool( "ns_allow_kill_commands" ) ) )
+ player.Die()
+
+ return true
+}
+
+bool function ClientCommandCallbackExplode( entity player, array<string> args )
+{
+ if ( IsAlive( player ) && ( GetConVarBool( "sv_cheats" ) || GetConVarBool( "ns_allow_kill_commands" ) ) )
+ player.Die( null, null, { scriptType = DF_GIB } )
+
+ return true
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
index e2ca9917..056f0313 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
@@ -82,10 +82,16 @@ void function BurnMeter_SetBoostRewardCount( string burnRef, int rewardCount )
file.boostRewardCount[burnRef] <- rewardCount
}
-int function BurnMeter_GetLimitedRewardCount( entity player )
+int function BurnMeter_GetLimitedRewardCount( entity player, string burnRef = "" )
{
- EarnObject earnObject = PlayerEarnMeter_GetReward( player )
- string burnRef = earnObject.ref
+ // added burnRef as a parameter, used for dice roll
+ // means we dont call two lots of BurnReward_GetRandom() whilst also being able to give multiple items per dice roll (ticks)
+ if (burnRef == "")
+ {
+ EarnObject earnObject = PlayerEarnMeter_GetReward( player )
+ burnRef = earnObject.ref
+ }
+
int limit = -1
int rewardCount = 1
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut
index 935dc4d0..a20e7aa0 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut
@@ -182,7 +182,7 @@ void function EarnMeterMP_BoostEarned( entity player )
while ( burncard.ref == "burnmeter_random_foil" )
burncard = BurnReward_GetRandom()
- for ( int i = 0; i < BurnMeter_GetLimitedRewardCount( player ); i++ )
+ for ( int i = 0; i < BurnMeter_GetLimitedRewardCount( player, burncard.ref ); i++ )
BurnMeter_GiveRewardDirect( player, burncard.ref )
PlayerEarnMeter_DisableReward( player )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
index cf7f7e15..d6d578bb 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
@@ -1,6 +1,170 @@
global function AiGameModes_Init
+global function AiGameModes_SetGruntWeapons
+global function AiGameModes_SetSpectreWeapons
+
+global function AiGameModes_SpawnDropShip
+global function AiGameModes_SpawnDropPod
+global function AiGameModes_SpawnReaper
+global function AiGameModes_SpawnTitan
+
+global function GetValidIntroDropShipSpawn
+
+
+const INTRO_DROPSHIP_CUTOFF = 2000
+
+struct
+{
+ array< string > gruntWeapons = [ "mp_weapon_rspn101" ]
+ array< string > spectreWeapons = [ "mp_weapon_hemlok_smg" ]
+} file
+
void function AiGameModes_Init()
{
+}
+
+//------------------------------------------------------
+
+void function AiGameModes_SetGruntWeapons( array< string > weapons )
+{
+ file.gruntWeapons = weapons
+}
+
+void function AiGameModes_SetSpectreWeapons( array< string > weapons )
+{
+ file.spectreWeapons = weapons
+}
+
+//------------------------------------------------------
+
+void function AiGameModes_SpawnDropShip( vector pos, vector rot, int team, int count, void functionref( array<entity> guys ) squadHandler = null )
+{
+ string squadName = MakeSquadName( team, UniqueString( "" ) )
+
+ CallinData drop
+ drop.origin = pos
+ drop.yaw = rot.y
+ drop.dist = 768
+ drop.team = team
+ drop.squadname = squadName
+ SetDropTableSpawnFuncs( drop, CreateSoldier, count )
+ SetCallinStyle( drop, eDropStyle.ZIPLINE_NPC )
+
+ thread RunDropshipDropoff( drop )
+
+ WaitSignal( drop, "OnDropoff" )
+
+ array< entity > guys = GetNPCArrayBySquad( squadName )
+
+ foreach ( guy in guys )
+ {
+ ReplaceWeapon( guy, file.gruntWeapons[ RandomInt( file.gruntWeapons.len() ) ], [] )
+ guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
+ }
+
+ if ( squadHandler != null )
+ thread squadHandler( guys )
+}
+
+
+void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string content /*( ͡° ͜ʖ ͡°)*/, void functionref( array<entity> guys ) squadHandler = null )
+{
+ string squadName = MakeSquadName( team, UniqueString( "" ) )
+ array<entity> guys
+
+ entity pod = CreateDropPod( pos, <0,0,0> )
+
+ InitFireteamDropPod( pod )
+
+ 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
+ }
+
+ npc.SetParent( pod, "ATTACH", true )
+
+ npc.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
+ guys.append( npc )
+ }
+
+ // The order here is different so we can show on minimap while were still falling
+ if ( squadHandler != null )
+ thread squadHandler( guys )
+
+ waitthread LaunchAnimDropPod( pod, "pod_testpath", pos, rot )
+
+ ActivateFireteamDropPod( pod, guys )
+}
+
+void function AiGameModes_SpawnReaper( vector pos, vector rot, int team, string aiSettings = "", void functionref( entity reaper ) reaperHandler = null )
+{
+ entity reaper = CreateSuperSpectre( team, pos, rot )
+ SetSpawnOption_Titanfall( reaper )
+ SetSpawnOption_Warpfall( reaper )
+
+ if ( aiSettings != "" )
+ SetSpawnOption_AISettings( reaper, aiSettings )
+
+ DispatchSpawn( reaper )
+
+
+ if ( reaperHandler != null )
+ thread reaperHandler( reaper )
+}
+
+// including aisettings stuff specifically for at bounty titans
+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 )
+
+ if ( aiSettings != "" )
+ SetSpawnOption_AISettings( titan, aiSettings )
+
+ DispatchSpawn( titan )
+
+ if ( titanHandler != null )
+ 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 )
+{
+ guy.TakeActiveWeapon()
+ guy.GiveWeapon( weapon, mods )
+ guy.SetActiveWeaponByName( weapon )
+}
+
+// Checks if we can spawn a dropship at a node, this should guarantee dropship ziplines
+array<entity> function GetValidIntroDropShipSpawn( array<entity> introNodes )
+{
+ array<entity> introShipSpawns
+
+ if ( GetZiplineDropshipSpawns().len() == 0 )
+ return []
+
+ foreach ( node in introNodes )
+ {
+ entity closestNode = GetClosest( GetZiplineDropshipSpawns(), node.GetOrigin() )
+ SetTeam( closestNode, node.GetTeam() )
+
+ if ( Distance( closestNode.GetOrigin(), node.GetOrigin() ) < INTRO_DROPSHIP_CUTOFF )
+ introShipSpawns.append( closestNode )
+ }
+
+ return introShipSpawns
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
index a8089679..9a94b848 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
@@ -1,6 +1,510 @@
+untyped
global function GamemodeAITdm_Init
+const SQUADS_PER_TEAM = 3
+
+const REAPERS_PER_TEAM = 2
+
+const LEVEL_SPECTRES = 125
+const LEVEL_STALKERS = 380
+const LEVEL_REAPERS = 500
+
+struct
+{
+ // Due to team based escalation everything is an array
+ array< int > levels = [ LEVEL_SPECTRES, LEVEL_SPECTRES ]
+ array< array< string > > podEntities = [ [ "npc_soldier" ], [ "npc_soldier" ] ]
+ array< bool > reapers = [ false, false ]
+} file
+
+
void function GamemodeAITdm_Init()
{
+ SetSpawnpointGamemodeOverride( ATTRITION ) // use bounty hunt spawns as vanilla game has no spawns explicitly defined for aitdm
+
+ AddCallback_GameStateEnter( eGameState.Prematch, OnPrematchStart )
+ AddCallback_GameStateEnter( eGameState.Playing, OnPlaying )
+
+ AddCallback_OnNPCKilled( HandleScoreEvent )
+ AddCallback_OnPlayerKilled( HandleScoreEvent )
+
+ AddCallback_OnClientConnected( OnPlayerConnected )
+
+ AddCallback_NPCLeeched( OnSpectreLeeched )
+
+ 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" ] )
+ }
+ else
+ {
+ AiGameModes_SetGruntWeapons( [ "mp_weapon_rocket_launcher" ] )
+ AiGameModes_SetSpectreWeapons( [ "mp_weapon_rocket_launcher" ] )
+ }
+
+ ScoreEvent_SetupEarnMeterValuesForMixedModes()
+}
+
+// Starts skyshow, this also requiers AINs but doesn't crash if they're missing
+void function OnPrematchStart()
+{
+ thread StratonHornetDogfightsIntense()
+}
+
+void function OnPlaying()
+{
+ // don't run spawning code if ains and nms aren't up to date
+ if ( GetAINScriptVersion() == AIN_REV && GetNodeCount() != 0 )
+ {
+ thread SpawnIntroBatch_Threaded( TEAM_MILITIA )
+ thread SpawnIntroBatch_Threaded( TEAM_IMC )
+ }
+}
+
+// Sets up mode specific hud on client
+void function OnPlayerConnected( entity player )
+{
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AITDM_OnPlayerConnected" )
+}
+
+// Used to handle both player and ai events
+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
+
+ // Split score so we can check if we are over the score max
+ // without showing the wrong value on client
+ int teamScore
+ int playerScore
+ string eventName
+
+ // Handle AI, marvins aren't setup so we check for them to prevent crash
+ if ( victim.IsNPC() && victim.GetClassName() != "npc_marvin" )
+ {
+ switch ( victim.GetClassName() )
+ {
+ case "npc_soldier":
+ case "npc_spectre":
+ case "npc_stalker":
+ playerScore = 1
+ break
+ case "npc_super_spectre":
+ playerScore = 3
+ break
+ default:
+ playerScore = 0
+ break
+ }
+
+ // Titan kills get handled bellow this
+ if ( eventName != "KillNPCTitan" && eventName != "" )
+ playerScore = ScoreEvent_GetPointValue( GetScoreEvent( eventName ) )
+ }
+
+ if ( victim.IsPlayer() )
+ playerScore = 5
+
+ // Player ejecting triggers this without the extra check
+ if ( victim.IsTitan() && victim.GetBossPlayer() != attacker )
+ playerScore += 10
+
+
+ teamScore = playerScore
+
+ // Check score so we dont go over max
+ if ( GameRules_GetTeamScore(attacker.GetTeam()) + teamScore > GetScoreLimit_FromPlaylist() )
+ teamScore = GetScoreLimit_FromPlaylist() - GameRules_GetTeamScore(attacker.GetTeam())
+
+ // Add score + update network int to trigger the "Score +n" popup
+ AddTeamScore( attacker.GetTeam(), teamScore )
+ attacker.AddToPlayerGameStat( PGS_ASSAULT_SCORE, playerScore )
+ attacker.SetPlayerNetInt("AT_bonusPoints", attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) )
+}
+
+// When attrition starts both teams spawn ai on preset nodes, after that
+// Spawner_Threaded is used to keep the match populated
+void function SpawnIntroBatch_Threaded( int team )
+{
+ array<entity> dropPodNodes = GetEntArrayByClass_Expensive( "info_spawnpoint_droppod_start" )
+ array<entity> dropShipNodes = GetValidIntroDropShipSpawn( dropPodNodes )
+
+ array<entity> podNodes
+
+ array<entity> shipNodes
+
+
+ // mp_rise has weird droppod_start nodes, this gets around it
+ // To be more specific the teams aren't setup and some nodes are scattered in narnia
+ if( GetMapName() == "mp_rise" )
+ {
+ entity spawnPoint
+
+ // Get a spawnpoint for team
+ foreach ( point in GetEntArrayByClass_Expensive( "info_spawnpoint_dropship_start" ) )
+ {
+ if ( point.HasKey( "gamemode_tdm" ) )
+ if ( point.kv[ "gamemode_tdm" ] == "0" )
+ continue
+
+ if ( point.GetTeam() == team )
+ {
+ spawnPoint = point
+ break
+ }
+ }
+
+ // Get nodes close enough to team spawnpoint
+ foreach ( node in dropPodNodes )
+ {
+ if ( node.HasKey("teamnum") && Distance2D( node.GetOrigin(), spawnPoint.GetOrigin()) < 2000 )
+ podNodes.append( node )
+ }
+ }
+ else
+ {
+ // Sort per team
+ foreach ( node in dropPodNodes )
+ {
+ if ( node.GetTeam() == team )
+ podNodes.append( node )
+ }
+ }
+
+ shipNodes = GetValidIntroDropShipSpawn( podNodes )
+
+
+ // Spawn logic
+ int startIndex = 0
+ bool first = true
+ entity node
+
+ int pods = RandomInt( podNodes.len() + 1 )
+
+ int ships = shipNodes.len()
+
+ for ( int i = 0; i < SQUADS_PER_TEAM; i++ )
+ {
+ if ( pods != 0 || ships == 0 )
+ {
+ int index = i
+
+ if ( index > podNodes.len() - 1 )
+ index = RandomInt( podNodes.len() )
+
+ node = podNodes[ index ]
+ thread AiGameModes_SpawnDropPod( node.GetOrigin(), node.GetAngles(), team, "npc_soldier", SquadHandler )
+
+ pods--
+ }
+ else
+ {
+ if ( startIndex == 0 )
+ startIndex = i // save where we started
+
+ node = shipNodes[ i - startIndex ]
+ thread AiGameModes_SpawnDropShip( node.GetOrigin(), node.GetAngles(), team, 4, SquadHandler )
+
+ ships--
+ }
+
+ // Vanilla has a delay after first spawn
+ if ( first )
+ wait 2
+
+ first = false
+ }
+
+ wait 15
+
+ thread Spawner_Threaded( team )
+}
+
+// Populates the match
+void function Spawner_Threaded( int team )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+
+ // used to index into escalation arrays
+ int index = team == TEAM_MILITIA ? 0 : 1
+
+
+ while( true )
+ {
+ Escalate( team )
+
+ // TODO: this should possibly not count scripted npc spawns, probably only the ones spawned by this script
+ array<entity> npcs = GetNPCArrayOfTeam( team )
+ int count = npcs.len()
+ int reaperCount = GetNPCArrayEx( "npc_super_spectre", team, -1, <0,0,0>, -1 ).len()
+
+ // REAPERS
+ if ( file.reapers[ index ] )
+ {
+ array< entity > points = SpawnPoints_GetDropPod()
+ if ( reaperCount < REAPERS_PER_TEAM )
+ {
+ entity node = points[ GetSpawnPointIndex( points, team ) ]
+ waitthread AiGameModes_SpawnReaper( node.GetOrigin(), node.GetAngles(), team, "npc_super_spectre_aitdm", ReaperHandler )
+ }
+ }
+
+ // NORMAL SPAWNS
+ if ( count < SQUADS_PER_TEAM * 4 - 2 )
+ {
+ string ent = file.podEntities[ index ][ RandomInt( file.podEntities[ index ].len() ) ]
+
+ array< entity > points = GetZiplineDropshipSpawns()
+ // Prefer dropship when spawning grunts
+ if ( ent == "npc_soldier" && points.len() != 0 )
+ {
+ if ( RandomInt( points.len() ) )
+ {
+ entity node = points[ GetSpawnPointIndex( points, team ) ]
+ waitthread Aitdm_SpawnDropShip( node, team )
+ continue
+ }
+ }
+
+ points = SpawnPoints_GetDropPod()
+ entity node = points[ GetSpawnPointIndex( points, team ) ]
+ waitthread AiGameModes_SpawnDropPod( node.GetOrigin(), node.GetAngles(), team, ent, SquadHandler )
+ }
+
+ WaitFrame()
+ }
+}
+
+void function Aitdm_SpawnDropShip( entity node, int team )
+{
+ thread AiGameModes_SpawnDropShip( node.GetOrigin(), node.GetAngles(), team, 4, SquadHandler )
+ wait 20
+}
+
+// Based on points tries to balance match
+void function Escalate( int team )
+{
+ int score = GameRules_GetTeamScore( team )
+ int index = team == TEAM_MILITIA ? 1 : 0
+ // This does the "Enemy x incoming" text
+ string defcon = team == TEAM_MILITIA ? "IMCdefcon" : "MILdefcon"
+
+ // Return if the team is under score threshold to escalate
+ if ( score < file.levels[ index ] || file.reapers[ index ] )
+ return
+
+ // Based on score escalate a team
+ switch ( file.levels[ index ] )
+ {
+ case LEVEL_SPECTRES:
+ file.levels[ index ] = LEVEL_STALKERS
+ file.podEntities[ index ].append( "npc_spectre" )
+ SetGlobalNetInt( defcon, 2 )
+ return
+
+ case LEVEL_STALKERS:
+ file.levels[ index ] = LEVEL_REAPERS
+ file.podEntities[ index ].append( "npc_stalker" )
+ SetGlobalNetInt( defcon, 3 )
+ return
+
+ case LEVEL_REAPERS:
+ file.reapers[ index ] = true
+ SetGlobalNetInt( defcon, 4 )
+ return
+ }
+
+ unreachable // hopefully
+}
+
+
+// Decides where to spawn ai
+// Each team has their "zone" where they and their ai spawns
+// These zones should swap based on which team is dominating where
+int function GetSpawnPointIndex( array< entity > points, int team )
+{
+ entity zone = DecideSpawnZone_Generic( points, team )
+
+ if ( IsValid( zone ) )
+ {
+ // 20 Tries to get a random point close to the zone
+ for ( int i = 0; i < 20; i++ )
+ {
+ int index = RandomInt( points.len() )
+
+ if ( Distance2D( points[ index ].GetOrigin(), zone.GetOrigin() ) < 6000 )
+ return index
+ }
+ }
+
+ return RandomInt( points.len() )
+}
+
+// tells infantry where to go
+// In vanilla there seem to be preset paths ai follow to get to the other teams vone and capture it
+// AI can also flee deeper into their zone suggesting someone spent way too much time on this
+void function SquadHandler( array<entity> guys )
+{
+ // 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 )
+ return
+
+ vector point
+ point = points[ RandomInt( points.len() ) ].GetOrigin()
+
+ array<entity> players = GetPlayerArrayOfEnemies( guys[0].GetTeam() )
+
+ // Setup AI
+ 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 )
+ }
+
+ // Every 5 - 15 secs change AssaultPoint
+ while ( true )
+ {
+ foreach ( guy in guys )
+ {
+ // Check if alive
+ if ( !IsAlive( guy ) )
+ {
+ guys.removebyvalue( guy )
+ continue
+ }
+ // Stop func if our squad has been killed off
+ if ( guys.len() == 0 )
+ return
+
+ // 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 )
+ }
+ wait RandomFloatRange(5.0,15.0)
+ }
+}
+
+// Award for hacking
+void function OnSpectreLeeched( entity spectre, entity player )
+{
+ // Set Owner so we can filter in HandleScore
+ spectre.SetOwner( player )
+ // Add score + update network int to trigger the "Score +n" popup
+ AddTeamScore( player.GetTeam(), 1 )
+ player.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 )
+ player.SetPlayerNetInt("AT_bonusPoints", player.GetPlayerGameStat( PGS_ASSAULT_SCORE ) )
+}
+
+// Same as SquadHandler, just for reapers
+void function ReaperHandler( entity reaper )
+{
+ array<entity> players = GetPlayerArrayOfEnemies( reaper.GetTeam() )
+ foreach ( player in players )
+ reaper.Minimap_AlwaysShow( 0, player )
+
+ reaper.AssaultSetGoalRadius( 500 )
+
+ // Every 10 - 20 secs get a player and go to him
+ // Definetly not annoying or anything :)
+ while( IsAlive( reaper ) )
+ {
+ players = GetPlayerArrayOfEnemies( reaper.GetTeam() )
+ if ( players.len() != 0 )
+ {
+ entity player = GetClosest2D( players, reaper.GetOrigin() )
+ reaper.AssaultPoint( player.GetOrigin() )
+ }
+ wait RandomFloatRange(10.0,20.0)
+ }
+ // thread AITdm_CleanupBoredNPCThread( reaper )
+}
+
+// Currently unused as this is handled by SquadHandler
+// May need to use this if my implementation falls apart
+void function AITdm_CleanupBoredNPCThread( entity guy )
+{
+ // track all ai that we spawn, ensure that they're never "bored" (i.e. stuck by themselves doing fuckall with nobody to see them) for too long
+ // if they are, kill them so we can free up slots for more ai to spawn
+ // we shouldn't ever kill ai if players would notice them die
+
+ // NOTE: this partially covers up for the fact that we script ai alot less than vanilla probably does
+ // vanilla probably messes more with making ai assaultpoint to fights when inactive and stuff like that, we don't do this so much
+ guy.EndSignal( "OnDestroy" )
+ wait 15.0 // cover spawning time from dropship/pod + before we start cleaning up
+
+ int cleanupFailures = 0 // when this hits 2, cleanup the npc
+ while ( cleanupFailures < 2 )
+ {
+ wait 10.0
+
+ if ( guy.GetParent() != null )
+ continue // never cleanup while spawning
+
+ array<entity> otherGuys = GetPlayerArray()
+ otherGuys.extend( GetNPCArrayOfTeam( GetOtherTeam( guy.GetTeam() ) ) )
+
+ bool failedChecks = false
+
+ foreach ( entity otherGuy in otherGuys )
+ {
+ // skip dead people
+ if ( !IsAlive( otherGuy ) )
+ continue
+
+ failedChecks = false
+
+ // don't kill if too close to anything
+ if ( Distance( otherGuy.GetOrigin(), guy.GetOrigin() ) < 2000.0 )
+ break
+
+ // don't kill if ai or players can see them
+ if ( otherGuy.IsPlayer() )
+ {
+ if ( PlayerCanSee( otherGuy, guy, true, 135 ) )
+ break
+ }
+ else
+ {
+ if ( otherGuy.CanSee( guy ) )
+ break
+ }
+
+ // don't kill if they can see any ai
+ if ( guy.CanSee( otherGuy ) )
+ break
+
+ failedChecks = true
+ }
+
+ if ( failedChecks )
+ cleanupFailures++
+ else
+ cleanupFailures--
+ }
+
+ print( "cleaning up bored npc: " + guy + " from team " + guy.GetTeam() )
+ guy.Destroy()
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
index 573ea72f..915e03e0 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
@@ -1,12 +1,376 @@
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
+
+const array<string> VALID_BOUNTY_TITAN_SETTINGS = [
+ "npc_titan_atlas_stickybomb_bounty",
+ "npc_titan_atlas_tracker_bounty",
+ "npc_titan_ogre_minigun_bounty",
+ "npc_titan_ogre_meteor_bounty",
+ "npc_titan_stryder_leadwall_bounty",
+ "npc_titan_stryder_sniper_bounty",
+ "npc_titan_atlas_vanguard_bounty"
+]
+
+
+// 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
+
+struct {
+ array<entity> campsToRegisterOnEntitiesDidLoad
+
+ array<entity> banks
+ array<AT_WaveOrigin> camps
+
+ table< int, table< string, int > > trackedCampNPCSpawns
+} file
+
void function GamemodeAt_Init()
{
+ AddCallback_GameStateEnter( eGameState.Playing, RunATGame )
+
+ AddCallback_OnClientConnected( InitialiseATPlayer )
+ AddSpawnCallbackEditorClass( "info_target", "info_attrition_bank", CreateATBank )
+ AddSpawnCallbackEditorClass( "info_target", "info_attrition_camp", CreateATCamp )
+ AddCallback_EntitiesDidLoad( CreateATCamps_Delayed )
}
void function RateSpawnpoints_AT( int checkclass, array<entity> spawnpoints, int team, entity player )
{
RateSpawnpoints_Generic( checkclass, spawnpoints, team, player ) // temp
-} \ No newline at end of file
+}
+
+// world and player inits
+
+void function InitialiseATPlayer( entity player )
+{
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AT_OnPlayerConnected" )
+}
+
+void function CreateATBank( entity spawnpoint )
+{
+ 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 ) )
+
+ thread PlayAnim( bank, "mh_inactive_idle" )
+
+ file.banks.append( bank )
+}
+
+void function CreateATCamp( entity spawnpoint )
+{
+ // delay this so we don't do stuff before all spawns are initialised and that
+ file.campsToRegisterOnEntitiesDidLoad.append( spawnpoint )
+}
+
+void function CreateATCamps_Delayed()
+{
+ // 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 )
+ {
+ 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()
+
+ // 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" )
+
+ // get droppod spawns
+ foreach ( entity spawnpoint in SpawnPoints_GetDropPod() )
+ if ( Distance( camp.GetOrigin(), spawnpoint.GetOrigin() ) < 1500.0 )
+ campStruct.dropPodSpawnPoints.append( spawnpoint )
+
+ foreach ( entity spawnpoint in SpawnPoints_GetTitan() )
+ if ( Distance( camp.GetOrigin(), spawnpoint.GetOrigin() ) < 1500.0 )
+ campStruct.titanSpawnPoints.append( spawnpoint )
+
+ // todo: turret spawns someday maybe
+
+ file.camps.append( campStruct )
+ }
+
+ file.campsToRegisterOnEntitiesDidLoad.clear()
+}
+
+// scoring funcs
+
+// don't use this where possible as it doesn't set score and stuff
+void function AT_SetPlayerCash( entity player, int amount )
+{
+ // split into stacks of 256 where necessary
+ int stacks = amount / 256 // automatically rounds down because int division
+
+ player.SetPlayerNetInt( "AT_bonusPoints256", stacks )
+ player.SetPlayerNetInt( "AT_bonusPoints", amount - stacks * 256 )
+}
+
+void function AT_AddPlayerCash( 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 )
+}
+
+// run gamestate
+
+void function RunATGame()
+{
+ thread RunATGame_Threaded()
+}
+
+void function RunATGame_Threaded()
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+
+ OnThreadEnd( function()
+ {
+ SetGlobalNetBool( "banksOpen", false )
+ })
+
+ wait WAVE_STATE_TRANSITION_TIME // initial wait before first wave
+
+ for ( int waveCount = 1; ; waveCount++ )
+ {
+ wait WAVE_STATE_TRANSITION_TIME
+
+ // 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 )
+ {
+ waveId = GetWaveDataSize() - 2
+ waveCount = waveId * 2
+ }
+
+ SetGlobalNetInt( "AT_currentWave", waveId )
+ bool isBossWave = waveCount / float( 2 ) > waveId // odd 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 )
+ }
+
+ wait WAVE_STATE_TRANSITION_TIME
+
+ // run the wave
+
+ AT_WaveData wave = GetWaveData( waveId )
+ array< array<AT_SpawnData> > campSpawnData
+
+ if ( isBossWave )
+ campSpawnData = wave.bossSpawnData
+ else
+ campSpawnData = wave.spawnDataArrays
+
+ // initialise pending spawns
+ foreach ( array< AT_SpawnData > campData in campSpawnData )
+ {
+ foreach ( AT_SpawnData spawnData in campData )
+ spawnData.pendingSpawns = spawnData.totalToSpawn
+ }
+
+ // clear tracked spawns
+ file.trackedCampNPCSpawns = {}
+ while ( true )
+ {
+ // if this is ever 0 by the end of this loop, wave is complete
+ int numActiveCampSpawners = 0
+
+ // iterate over camp data for wave
+ for ( int campIdx = 0; campIdx < campSpawnData.len() && campIdx < file.camps.len(); campIdx++ )
+ {
+ if ( !( campIdx in file.trackedCampNPCSpawns ) )
+ file.trackedCampNPCSpawns[ campIdx ] <- {}
+
+ // iterate over ai spawn data for camp
+ foreach ( AT_SpawnData spawnData in campSpawnData[ campIdx ] )
+ {
+ if ( !( spawnData.aitype in file.trackedCampNPCSpawns[ campIdx ] ) )
+ file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] <- 0
+
+ if ( spawnData.pendingSpawns > 0 || file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] > 0 )
+ numActiveCampSpawners++
+
+ // 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 )
+ }
+ }
+
+ // track spawns
+ file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] += spawnCount
+ spawnData.pendingSpawns -= spawnCount
+ }
+ }
+
+ if ( numActiveCampSpawners == 0 )
+ break
+
+ wait 0.5
+ }
+
+ wait WAVE_STATE_TRANSITION_TIME
+
+ // banking phase
+ }
+}
+
+// entity funcs
+
+void function AT_SpawnDroppodSquad( int camp, string aiType )
+{
+ entity spawnpoint
+ if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 )
+ spawnpoint = file.camps[ camp ].ent
+ else
+ spawnpoint = file.camps[ camp ].dropPodSpawnPoints.getrandom()
+
+ // 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 )
+ })
+}
+
+void function AT_HandleSquadSpawn( array<entity> guys, int camp, string aiType )
+{
+ 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 )
+ }
+}
+
+void function AT_SpawnReaper( int camp )
+{
+ entity spawnpoint
+ if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 )
+ spawnpoint = file.camps[ camp ].ent
+ else
+ spawnpoint = file.camps[ camp ].titanSpawnPoints.getrandom()
+
+ // 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 )
+ {
+ thread AT_WaitToUntrackNPC( reaper, camp, "npc_super_spectre" )
+ })
+}
+
+void function AT_SpawnBountyTitan( int camp )
+{
+ entity spawnpoint
+ if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 )
+ spawnpoint = file.camps[ camp ].ent
+ else
+ spawnpoint = file.camps[ camp ].titanSpawnPoints.getrandom()
+
+ // add variation to spawns
+ wait RandomFloat( 1.0 )
+
+ // look up titan to use
+ int bountyID = 0
+ try
+ {
+ bountyID = ReserveBossID( VALID_BOUNTY_TITAN_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(), BH_AI_TEAM, titanClass, aisettings, void function( entity titan ) : ( camp, bountyID )
+ {
+ // 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" )
+ } )
+}
+
+// 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 )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ 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 )
+ }
+}
+
+void function OnBountyKilled( entity titan, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !attacker.IsPlayer() )
+ attacker = GetLatestAssistingPlayerInfo( titan ).player
+
+ if ( IsValid( attacker ) && attacker.IsPlayer() )
+ AT_AddPlayerCash( attacker, BOUNTY_TITAN_KILL_REWARD )
+}
+
+void function AT_WaitToUntrackNPC( entity guy, int camp, string aiType )
+{
+ guy.WaitSignal( "OnDeath", "OnDestroy" )
+ file.trackedCampNPCSpawns[ camp ][ aiType ]--
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
index 74e6b58c..705b7836 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
@@ -150,8 +150,8 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama
void function removePlayerFromCapperArray_threaded(array<entity> capperArray,entity player)
{
WaitFrame()
- if(capperArray.contains(player))
- capperArray.remove(capperArray.find(player))
+ FindAndRemove(capperArray,player)
+
}
void function RateSpawnpoints_CP( int checkClass, array<entity> spawnpoints, int team, entity player )
@@ -589,12 +589,12 @@ void function HardpointThink( HardpointStruct hardpoint )
foreach(entity player in hardpoint.imcCappers)
{
if(DistanceSqr(player.GetOrigin(),hardpointEnt.GetOrigin())>1200000)
- hardpoint.imcCappers.remove(hardpoint.imcCappers.find(player))
+ FindAndRemove(hardpoint.imcCappers,player)
}
foreach(entity player in hardpoint.militiaCappers)
{
if(DistanceSqr(player.GetOrigin(),hardpointEnt.GetOrigin())>1200000)
- hardpoint.militiaCappers.remove(hardpoint.militiaCappers.find(player))
+ FindAndRemove(hardpoint.militiaCappers,player)
}
@@ -656,7 +656,7 @@ void function OnHardpointLeft( entity trigger, entity player )
hardpoint = hardpointStruct
if ( player.GetTeam() == TEAM_IMC )
- hardpoint.imcCappers.remove( hardpoint.imcCappers.find( player ) )
+ FindAndRemove( hardpoint.imcCappers, player )
else
FindAndRemove( hardpoint.militiaCappers, player )
foreach(CP_PlayerStruct playerStruct in file.players)
@@ -702,4 +702,4 @@ string function GetHardpointGroup(entity hardpoint) //Hardpoint Entity B on Home
return "B"
return string(hardpoint.kv.hardpointGroup)
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut
index b4ab26ea..99f34164 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut
@@ -36,6 +36,7 @@ void function CaptureTheFlag_Init()
AddCallback_OnClientConnected( CTFInitPlayer )
AddCallback_GameStateEnter( eGameState.Prematch, CreateFlags )
+ AddCallback_GameStateEnter( eGameState.Epilogue, RemoveFlags )
AddCallback_OnTouchHealthKit( "item_flag", OnFlagCollected )
AddCallback_OnPlayerKilled( OnPlayerKilled )
AddCallback_OnPilotBecomesTitan( DropFlagForBecomingTitan )
@@ -109,6 +110,8 @@ void function CTFInitPlayer( entity player )
void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
{
+ if ( !IsValid( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ) ) ) // getting a crash idk
+ return
if ( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ).GetParent() == victim )
{
if ( victim != attacker && attacker.IsPlayer() )
@@ -137,8 +140,13 @@ void function CreateFlags()
// likely this is because respawn uses distance checks from spawns to check this in official
// but i don't like doing that so just using a list of maps to swap them on lol
bool switchedSides = HasSwitchedSides() == 1
- bool shouldSwap = SWAP_FLAG_MAPS.contains( GetMapName() ) ? !switchedSides : switchedSides
-
+
+ // i dont know why this works and whatever we had before didn't, but yeah
+ bool shouldSwap = switchedSides
+ if (!shouldSwap && SWAP_FLAG_MAPS.contains( GetMapName() ))
+ shouldSwap = !shouldSwap
+
+
int flagTeam = spawn.GetTeam()
if ( shouldSwap )
{
@@ -199,10 +207,35 @@ void function CreateFlags()
}
}
+ // reset the flag states, prevents issues where flag is home but doesnt think it's home when halftime goes
+ SetFlagStateForTeam( TEAM_MILITIA, eFlagState.None )
+ SetFlagStateForTeam( TEAM_IMC, eFlagState.None )
+
foreach ( entity player in GetPlayerArray() )
CTFInitPlayer( player )
}
+void function RemoveFlags()
+{
+ // destroy all the flag related things
+ if ( IsValid( file.imcFlagSpawn ) )
+ {
+ file.imcFlagSpawn.Destroy()
+ file.imcFlag.Destroy()
+ file.imcFlagReturnTrigger.Destroy()
+ }
+ if ( IsValid( file.militiaFlagSpawn ) )
+ {
+ file.militiaFlagSpawn.Destroy()
+ file.militiaFlag.Destroy()
+ file.militiaFlagReturnTrigger.Destroy()
+ }
+
+ // unsure if this is needed, since the flags are destroyed? idk
+ SetFlagStateForTeam( TEAM_MILITIA, eFlagState.None )
+ SetFlagStateForTeam( TEAM_IMC, eFlagState.None )
+}
+
void function TrackFlagReturnTrigger( entity flag, entity returnTrigger )
{
// this is a bit of a hack, it seems parenting the return trigger to the flag actually sets the pickup radius of the flag to be the same as the trigger
@@ -270,10 +303,11 @@ void function DropFlagIfPhased( entity player, entity flag )
OnThreadEnd( function() : ( player )
{
- DropFlag( player, true )
+ if (GetGameState() == eGameState.Playing || GetGameState() == eGameState.SuddenDeath)
+ DropFlag( player, true )
})
-
- while( flag.GetParent() == player )
+ // the IsValid check is purely to prevent a crash due to a destroyed flag (epilogue)
+ while( IsValid(flag) && flag.GetParent() == player )
WaitFrame()
}
@@ -331,6 +365,9 @@ void function TrackFlagDropTimeout( entity flag )
void function ResetFlag( entity flag )
{
+ // prevents crash when flag is reset after it's been destroyed due to epilogue
+ if (!IsValid(flag))
+ return
// ensure we can't pickup the flag after it's been dropped but before it's been reset
flag.s.canTake = false
@@ -355,6 +392,12 @@ void function ResetFlag( entity flag )
void function CaptureFlag( entity player, entity flag )
{
+ // can only capture flags during normal play or sudden death
+ if (GetGameState() != eGameState.Playing && GetGameState() != eGameState.SuddenDeath)
+ {
+ printt( player + " tried to capture the flag, but the game state was " + GetGameState() + " not " + eGameState.Playing + " or " + eGameState.SuddenDeath)
+ return
+ }
// reset flag
ResetFlag( flag )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
index 33c0b8e9..ae933b71 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
@@ -25,6 +25,7 @@ void function Lobby_OnClientConnectionStarted( entity player )
void function Lobby_OnClientConnectionCompleted( entity player )
{
+ player.hasConnected = true
FinishClientScriptInitialization( player )
}
@@ -35,4 +36,4 @@ bool function ClientCommandCallback_StartPrivateMatchSearch( entity player, arra
GameRules_ChangeMap( "mp_lobby", GAMETYPE )
return true
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut
index 719ea336..ccccefaf 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut
@@ -38,7 +38,8 @@ void function PrivateMatchModesInit()
AddPrivateMatchModeSettingEnum( "#GAMEMODE_cp", "cp_amped_capture_points", [ "#SETTING_DISABLED", "#SETTING_ENABLED" ], "1" ) // would've been nice to use amped_capture_points, but this var is already used ingame and its value is default 0
AddPrivateMatchModeSettingEnum( "#GAMEMODE_coliseum", "coliseum_loadouts_#SETTING_ENABLED", [ "#SETTING_DISABLED", "#SETTING_ENABLED" ], "1" )
-
+
+ AddPrivateMatchModeSettingEnum( "#PL_aitdm", "aitdm_archer_grunts", [ "Disabled", "Enabled" ], "0" )
// modes
AddPrivateMatchMode( "ffa" )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
index df6829c8..1c53167f 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
@@ -35,6 +35,8 @@ void function BaseGametype_Init_MPSP()
AddPostDamageCallback( "player", AddToTitanDamageStat )
AddPostDamageCallback( "npc_titan", AddToTitanDamageStat )
+ AddCallback_OnNPCKilled( CheckForAutoTitanDeath )
+ AddCallback_OnPlayerKilled( CheckForAutoTitanDeath )
RegisterSignal( "PlayerRespawnStarted" )
RegisterSignal( "KillCamOver" )
}
@@ -122,6 +124,7 @@ void function CodeCallback_OnClientConnectionStarted( entity player )
void function CodeCallback_OnClientConnectionCompleted( entity player )
{
InitPersistentData( player )
+
if ( IsLobby() )
{
Lobby_OnClientConnectionCompleted( player )
@@ -485,7 +488,12 @@ void function RespawnAsTitan( entity player, bool manualPosition = false )
player.RespawnPlayer( null ) // spawn player as pilot so they get their pilot loadout on embark
player.SetOrigin( titan.GetOrigin() )
- PilotBecomesTitan( player, titan ) // make player titan
+ // don't make player titan when entity batteryContainer is not valid.
+ // This will prevent a servercrash that sometimes occur when evac is disabled and somebody is calling a titan in the defeat screen.
+ if( IsValid( titan.GetTitanSoul().soul.batteryContainer ) )
+ PilotBecomesTitan( player, titan ) // make player titan
+ else
+ print( "batteryContainer is not a valid entity in RespawnAsTitan(). Skipping PilotBecomesTitan()." )
}
@@ -528,6 +536,24 @@ void function AddToTitanDamageStat( entity victim, var damageInfo )
attacker.AddToPlayerGameStat( file.titanDamageGameStat, amount ) // titan damage on
}
+void function CheckForAutoTitanDeath( entity victim, entity attacker, var damageInfo )
+{
+ if( !IsValid(victim) || !victim.IsTitan() )
+ return
+
+ if( !victim.IsPlayer() )
+ {
+ if (GetPetTitanOwner(victim) && GetPetTitanOwner(victim) != attacker)
+ foreach(player in GetPlayerArray())
+ Remote_CallFunction_NonReplay( player, "ServerCallback_OnTitanKilled", attacker.GetEncodedEHandle(), victim.GetEncodedEHandle(), DamageInfo_GetCustomDamageType( damageInfo ), DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ }
+ else
+ {
+ if (victim != attacker)
+ foreach(player in GetPlayerArray())
+ Remote_CallFunction_NonReplay( player, "ServerCallback_OnTitanKilled", attacker.GetEncodedEHandle(), victim.GetEncodedEHandle(), DamageInfo_GetCustomDamageType( damageInfo ), DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ }
+}
// stuff to change later
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
index 80fae331..0555df9b 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
@@ -105,8 +105,12 @@ void function OnPrematchStart()
DispatchSpawn( dropship )
// have to do this after dispatch otherwise it won't work for some reason
- dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
- // could also use $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl", unsure which
+ // weirdly enough, tf2 actually does use different dropships for imc and militia, despite these concepts not really being a thing for players in tf2
+ // probably was just missed by devs, but keeping it in for accuracy
+ if ( dropshipSpawn.GetTeam() == TEAM_IMC )
+ dropship.SetModel( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" )
+ else
+ dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
thread PlayAnim( dropship, "dropship_classic_mp_flyin" )
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
index 2e565142..425a8b8b 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
@@ -6,6 +6,9 @@ global function CodeCallback_DamagePlayerOrNPC
global function GameModeRulesShouldGiveTimerCredit
global function SetGameModeRulesShouldGiveTimerCredit
global function SetGameModeRulesEarnMeterOnDamage
+global function GameModeRulesRegisterTimerCreditExceptions
+global function GameModeRulesRegisterTimerCreditException
+global function GameModeRulesGetTimerCreditExceptions
global function GetDamageOrigin
global function CodeCallBack_ShouldTriggerSniperCam
global function CodeCallback_ForceAIMissPlayer
@@ -31,6 +34,13 @@ struct
float titanMeterGainScale = 0.0001
bool functionref( entity, entity, var ) ShouldGiveTimerCreditGameModeRules
void functionref( entity, entity, TitanDamage, float ) earnMeterOnDamageGameModeRulesCallback
+ array<int> gameModeRulesTimerCreditExceptions = [
+ eDamageSourceId.mp_titancore_flame_wave,
+ eDamageSourceId.mp_titancore_flame_wave_secondary,
+ eDamageSourceId.mp_titancore_salvo_core,
+ damagedef_titan_fall,
+ damagedef_nuclear_core
+ ]
table<entity, AccumulatedDamageData> playerAccumulatedDamageData
} file
@@ -582,15 +592,8 @@ bool function ShouldGiveTimerCredit_Default( entity player, entity victim, var d
return false
int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
- switch ( damageSourceID )
- {
- case eDamageSourceId.mp_titancore_flame_wave:
- case eDamageSourceId.mp_titancore_flame_wave_secondary:
- case eDamageSourceId.mp_titancore_salvo_core:
- case damagedef_titan_fall:
- case damagedef_nuclear_core:
- return false
- }
+ if ( file.gameModeRulesTimerCreditExceptions.contains( damageSourceID ) )
+ return false
return true
}
@@ -605,6 +608,23 @@ void function SetGameModeRulesShouldGiveTimerCredit( bool functionref( entity, e
file.ShouldGiveTimerCreditGameModeRules = rules
}
+void function GameModeRulesRegisterTimerCreditExceptions( array<int> ids )
+{
+ foreach( id in ids )
+ GameModeRulesRegisterTimerCreditException( id )
+}
+
+void function GameModeRulesRegisterTimerCreditException( int id )
+{
+ if ( !file.gameModeRulesTimerCreditExceptions.contains( id ) )
+ file.gameModeRulesTimerCreditExceptions.append( id )
+}
+
+array<int> function GameModeRulesGetTimerCreditExceptions()
+{
+ return file.gameModeRulesTimerCreditExceptions
+}
+
function TitanDamageFlinch( entity ent, damageInfo )
{
if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
index e8b636ff..bfcd23e0 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
@@ -9,6 +9,7 @@ global function AddCallback_OnRoundEndCleanup
global function SetShouldUsePickLoadoutScreen
global function SetSwitchSidesBased
global function SetSuddenDeathBased
+global function SetTimerBased
global function SetShouldUseRoundWinningKillReplay
global function SetRoundWinningKillReplayKillClasses
global function SetRoundWinningKillReplayAttacker
@@ -28,6 +29,7 @@ struct {
bool usePickLoadoutScreen
bool switchSidesBased
bool suddenDeathBased
+ bool timerBased = true
int functionref() timeoutWinnerDecisionFunc
// for waitingforplayers
@@ -228,7 +230,7 @@ void function GameStateEnter_Playing_Threaded()
endTime = expect float( GetServerVar( "gameEndTime" ) )
// time's up!
- if ( Time() >= endTime )
+ if ( Time() >= endTime && file.timerBased )
{
int winningTeam
if ( file.timeoutWinnerDecisionFunc != null )
@@ -279,9 +281,13 @@ void function GameStateEnter_WinnerDetermined_Threaded()
}
WaitFrame() // wait a frame so other scripts can setup killreplay stuff
+
+ // set gameEndTime to current time, so hud doesn't display time left in the match
+ SetServerVar( "gameEndTime", Time() )
+ SetServerVar( "roundEndTime", Time() )
entity replayAttacker = file.roundWinningKillReplayAttacker
- bool doReplay = Replay_IsEnabled() && IsRoundWinningKillReplayEnabled() && IsValid( replayAttacker )
+ bool doReplay = Replay_IsEnabled() && IsRoundWinningKillReplayEnabled() && IsValid( replayAttacker ) && !ClassicMP_ShouldRunEpilogue()
&& Time() - file.roundWinningKillReplayTime <= ROUND_WINNING_KILL_REPLAY_LENGTH_OF_REPLAY && winningTeam != TEAM_UNASSIGNED
float replayLength = 2.0 // extra delay if no replay
@@ -676,7 +682,7 @@ void function CleanUpEntitiesForRoundEnd()
foreach ( entity npc in GetNPCArray() )
{
- if ( !IsValid( npc ) )
+ if ( !IsValid( npc ) || !IsAlive( npc ) )
continue
// kill rather than destroy, as destroying will cause issues with children which is an issue especially for dropships and titans
npc.Die( svGlobal.worldspawn, svGlobal.worldspawn, { damageSourceId = eDamageSourceId.round_end } )
@@ -715,6 +721,11 @@ void function SetSuddenDeathBased( bool suddenDeathBased )
file.suddenDeathBased = suddenDeathBased
}
+void function SetTimerBased( bool timerBased )
+{
+ file.timerBased = timerBased
+}
+
void function SetShouldUseRoundWinningKillReplay( bool shouldUse )
{
SetServerVar( "roundWinningKillReplayEnabled", shouldUse )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut
index 83338c8e..02b11010 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut
@@ -2,6 +2,11 @@ global function CodeCallback_MapInit
void function CodeCallback_MapInit()
{
+ AddCallback_EntitiesDidLoad( CreateEvacNodes )
+}
+
+void function CreateEvacNodes()
+{
AddEvacNode( CreateScriptRef( < -475.129913, 1480.167847, 527.363953 >, < 8.841560, 219.338501, 0 > ) )
AddEvacNode( CreateScriptRef( < 1009.315186, 3999.888916, 589.914917 >, < 23.945116, -146.680725, 0 > ) )
AddEvacNode( CreateScriptRef( < 2282.868896, -1363.706543, 846.188660 >, < 23.945116, -146.680725, 0 > ) )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_lf_deck.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_lf_deck.nut
index 592422ee..d5162f0b 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_lf_deck.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_lf_deck.nut
@@ -14,9 +14,11 @@ void function CodeCallback_MapInit()
void function DeckSpawnWorkerDrone( entity spawnpoint )
{
-
- entity drone = CreateWorkerDrone( TEAM_UNASSIGNED, spawnpoint.GetOrigin(), spawnpoint.GetAngles() )
+ entity drone = CreateWorkerDrone( TEAM_UNASSIGNED, spawnpoint.GetOrigin() - < 0, 0, 150 >, spawnpoint.GetAngles() )
DispatchSpawn( drone )
+
+ // this seems weird for drones
+ thread AssaultMoveTarget( drone, spawnpoint )
}
void function DeckSpawnMarvinForIdleNode( entity node )
@@ -24,7 +26,5 @@ void function DeckSpawnMarvinForIdleNode( entity node )
entity marvin = CreateMarvin( TEAM_UNASSIGNED, node.GetOrigin(), node.GetAngles() )
DispatchSpawn( marvin )
- // doing this because no ai rn
- if ( GetAINScriptVersion() == -1 )
- thread PlayAnim( marvin, node.kv.leveled_animation )
+ thread AssaultMoveTarget( marvin, node )
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
index c1290bb0..4a53c4fe 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
@@ -88,10 +88,12 @@ void function SpawnMarvinsForRound()
entity marvin = CreateMarvin( TEAM_UNASSIGNED, spawner.GetOrigin(), spawner.GetAngles() )
marvin.kv.health = 1
marvin.kv.max_health = 1
- marvin.kv.spawnflags = 516
- marvin.kv.contents = (int(marvin.kv.contents) | CONTENTS_NOGRAPPLE)
+ //marvin.kv.spawnflags = 516
+ marvin.kv.contents = ( int( marvin.kv.contents ) | CONTENTS_NOGRAPPLE )
DispatchSpawn( marvin )
HideName( marvin )
+
+ thread MarvinJobThink( marvin )
}
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
index 84317c6e..5bf150c0 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
@@ -396,55 +396,10 @@ void function InitPreferSpawnNodes()
// frontline
void function RateSpawnpoints_Frontline( int checkClass, array<entity> spawnpoints, int team, entity player )
{
- Frontline frontline = GetFrontline( player.GetTeam() )
-
- // heavily based on ctf spawn algo iteration 4, only changes it at the end
- array<entity> startSpawns = SpawnPoints_GetPilotStart( team )
- array<entity> enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) )
-
- if ( startSpawns.len() == 0 || enemyStartSpawns.len() == 0 ) // ensure we don't crash
- return
-
- // get average startspawn position and max dist between spawns
- // could probably cache this, tbh, not like it should change outside of halftimes
- vector averageFriendlySpawns
- float maxFriendlySpawnDist
-
- foreach ( entity spawn in startSpawns )
- {
- foreach ( entity otherSpawn in startSpawns )
- {
- float dist = Distance2D( spawn.GetOrigin(), otherSpawn.GetOrigin() )
- if ( dist > maxFriendlySpawnDist )
- maxFriendlySpawnDist = dist
- }
-
- averageFriendlySpawns += spawn.GetOrigin()
- }
-
- averageFriendlySpawns /= startSpawns.len()
-
- // get average enemy startspawn position
- vector averageEnemySpawns
-
- foreach ( entity spawn in enemyStartSpawns )
- averageEnemySpawns += spawn.GetOrigin()
-
- averageEnemySpawns /= enemyStartSpawns.len()
-
- // from here, rate spawns
- float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns )
- foreach ( entity spawn in spawnpoints )
+ foreach ( entity spawnpoint in spawnpoints )
{
- // ratings should max/min out at 100 / -100
- // start by prioritizing closer spawns, but not so much that enemies won't really affect them
- float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawn.GetOrigin() ) / baseDistance )
-
- // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir
- rating += rating * ( 1.0 - ( Distance2D( spawn.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) )
- rating *= fabs( frontline.combatDir.y - Normalize( spawn.GetOrigin() - averageFriendlySpawns ).y )
-
- spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating )
+ float rating = spawnpoint.CalculateFrontlineRating()
+ spawnpoint.CalculateRating( checkClass, player.GetTeam(), rating, rating > 0 ? rating * 0.25 : rating )
}
}