aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/mod/scripts/vscripts
diff options
context:
space:
mode:
authorWilliam Miller <william-millennium@hotmail.com>2024-09-10 16:31:12 -0300
committerGitHub <noreply@github.com>2024-09-10 16:31:12 -0300
commit228aefe63642ba8b43b1a137031ea606ccec2501 (patch)
treedd762c11c79559eeda63eed706e494c4d0820ab5 /Northstar.CustomServers/mod/scripts/vscripts
parenta1502e9ad3ed44b2a0a8ab5f17d4d2bf5238d9cf (diff)
parent3dfd2091b4e72aad14b5d9cfd2332e7ec643c7fd (diff)
downloadNorthstarMods-228aefe63642ba8b43b1a137031ea606ccec2501.tar.gz
NorthstarMods-228aefe63642ba8b43b1a137031ea606ccec2501.zip
Merge branch 'main' into gamemode_fdgamemode_fd
Diffstat (limited to 'Northstar.CustomServers/mod/scripts/vscripts')
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_items.nut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut20
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut16
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut10
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut9
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut10
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut6
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut41
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut607
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut33
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut22
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut7
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut31
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut270
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut190
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut25
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut530
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut19
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut39
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut4
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut771
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut24
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut22
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut7
29 files changed, 1719 insertions, 1003 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
index a23a68b0..96623086 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
@@ -10094,7 +10094,7 @@ void function InitUnlockAsEntitlement( string itemRef, string parentRef, int ent
unlock = file.entitlementUnlocks[fullRef]
}
- unlock.entitlementIds.append( entitlementId )
+ unlock.entitlementIds.append( 1 ) // Using `1` here instead of the huge DLC check I did previously. Having the `1` seems to keep all paid cosmetics unlocked with progression enabled.
}
array<int> function GetEntitlementIds( string itemRef, string parentRef = "" )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut
index 103efe6f..bd289d4c 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut
@@ -54,8 +54,6 @@ void function SvLoadoutsMP_Init()
AddClientCommandCallback( "InGameMPMenuClosed", ClientCommandCallback_InGameMPMenuClosed )
AddClientCommandCallback( "LoadoutMenuClosed", ClientCommandCallback_LoadoutMenuClosed )
}
-
- AddCallback_OnPlayerKilled( DestroyDroppedWeapon )
}
void function SetLoadoutGracePeriodEnabled( bool enabled )
@@ -70,20 +68,10 @@ void function SetAllowLoadoutChangeFunc( bool functionref( entity ) func )
void function SetWeaponDropsEnabled( bool enabled )
{
- file.weaponDropsEnabled = enabled
-}
-
-void function DestroyDroppedWeapon( entity victim, entity attacker, var damageInfo )
-{
- if ( !file.weaponDropsEnabled && IsValid( victim.GetActiveWeapon() ) )
- thread DelayDestroyDroppedWeapon( victim.GetActiveWeapon() )
-}
-
-void function DelayDestroyDroppedWeapon( entity weapon )
-{
- WaitEndFrame()
- if ( IsValid( weapon ) )
- weapon.Destroy()
+ if( enabled )
+ FlagSet( "WeaponDropsAllowed" )
+ else
+ FlagClear( "WeaponDropsAllowed" )
}
void function AddCallback_OnTryGetTitanLoadout( TryGetTitanLoadoutCallbackType callback )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
index 2b95d1a8..97d993e6 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
@@ -16,7 +16,10 @@ void function SetupPlayerPreviousXPValues( entity player )
player.SetPersistentVar( "previousFactionXP[" + xpFaction + "]", FactionGetXP( player, xpFaction ) )
foreach ( string xpTitan in shTitanXP.titanClasses )
+ {
player.SetPersistentVar( "previousTitanXP[" + xpTitan + "]", TitanGetXP( player, xpTitan ) )
+ player.SetPersistentVar( "fdPreviousTitanXP[" + xpTitan + "]", FD_TitanGetXP( player, xpTitan ) )
+ }
foreach ( string xpWeapon in shWeaponXP.weaponClassNames )
player.SetPersistentVar( GetItemPersistenceStruct( xpWeapon ) + ".previousWeaponXP", WeaponGetXP( player, xpWeapon ) )
@@ -35,6 +38,14 @@ void function HandleXPGainForScoreEvent( entity player, ScoreEvent event )
int weaponXp = ScoreEvent_GetXPValueWeapon( event )
int titanXp = ScoreEvent_GetXPValueTitan( event )
int factionXp = ScoreEvent_GetXPValueFaction( event )
+
+ if ( player.GetPlayerNetInt( "xpMultiplier" ) > 0 || GetCurrentPlaylistVarInt( "double_xp_enabled", 0 ) == 1 )
+ {
+ xpValue *= 2
+ weaponXp *= 2
+ titanXp *= 2
+ factionXp *= 2
+ }
entity weapon = player.GetActiveWeapon()
if ( IsValid( weapon ) && ShouldTrackXPForWeapon( weapon.GetWeaponClassName() ) && weaponXp != 0 )
@@ -63,5 +74,10 @@ void function AddXP( entity player, int amount )
int newXp = player.GetPersistentVarAsInt( "xp" )
int newLevel = GetLevelForXP( newXp )
if ( newLevel != oldLevel )
+ {
Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerLeveledUp", player.GetPersistentVarAsInt( "gen" ), newLevel )
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForPlayerLevels( player, player.GetPersistentVarAsInt( "gen" ), newLevel )
+ }
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
index af074689..760daef0 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
@@ -246,6 +246,9 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
SetTeamActiveObjective( evacTeam, "EG_DropshipExtract", Time() + arrivalTime, file.evacIcon )
SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract", Time() + arrivalTime, file.evacIcon )
+ foreach( entity player in GetPlayerArrayOfTeam( evacTeam ) ) //Show the Evac Match Goal for players of the team that lost the match
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 0 )
+
// would've liked to use cd_dropship_rescue_side_start length here, but can't since this is done before dropship spawn, can't
wait arrivalTime - 4.33333
@@ -280,6 +283,9 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
{
SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipDestroyed" )
SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_DropshipExtractDropshipDestroyed" )
+
+ foreach( entity player in GetPlayerArrayOfTeam( evacTeam ) )
+ SetPlayerChallengeEvacState( player, 0 )
}
foreach ( entity player in dropship.s.evacSlots )
@@ -402,7 +408,10 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
if ( !PlayerInDropship( player, dropship ) )
{
if ( player.GetTeam() == dropship.GetTeam() )
+ {
SetPlayerActiveObjective( player, "EG_DropshipExtractFailedEscape" )
+ SetPlayerChallengeEvacState( player, 2 )
+ }
continue
}
@@ -417,6 +426,7 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
Remote_CallFunction_NonReplay( player, "ServerCallback_DisableHudForEvac" )
Remote_CallFunction_NonReplay( player, "ServerCallback_SetClassicSkyScale", dropship.GetEncodedEHandle(), 0.7 )
Remote_CallFunction_NonReplay( player, "ServerCallback_SetMapSettings", 4.0, false, 0.4, 0.125 )
+ SetPlayerChallengeEvacState( player, 1 )
// display player [Evacuated] in killfeed
foreach ( entity otherPlayer in GetPlayerArray() )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut
index 6555c875..5aee1104 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut
@@ -4,10 +4,19 @@ void function AddFactionXP( entity player, int amount )
{
string faction = GetFactionChoice( player )
int oldLevel = FactionGetLevel( player, faction )
+ int FactionXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.FACTION_LEVELED + "]" )
+
// increment xp
player.SetPersistentVar( "factionXP[" + faction + "]", min( FactionGetXP( player, faction ) + amount, FactionGetMaxXP( faction ) ) )
// note: no notif for faction level up
if ( FactionGetLevel( player, faction ) != oldLevel )
+ {
AddPlayerScore( player, "FactionLevelUp" )
+ IncrementPlayerChallengeFactionLeveledUp( player )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.FACTION_LEVELED + "]", FactionXPMatch + 1 )
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForFactionLevels( player, faction, oldLevel, FactionGetLevel( player, faction ) )
+ }
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
index f47ee90f..cacb54cf 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
@@ -60,6 +60,7 @@ void function GamemodeAITdm_Init()
}
ScoreEvent_SetupEarnMeterValuesForMixedModes()
+ SetupGenericTDMChallenge()
}
// add settings
@@ -434,9 +435,12 @@ void function SquadHandler( array<entity> guys )
// Setup AI, first assault point
foreach ( guy in guys )
{
- guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
- guy.AssaultPoint( point )
- guy.AssaultSetGoalRadius( 1600 ) // 1600 is minimum for npc_stalker, works fine for others
+ if ( IsAlive( guy ) )
+ {
+ guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
+ guy.AssaultPoint( point )
+ guy.AssaultSetGoalRadius( 1600 ) // 1600 is minimum for npc_stalker, works fine for others
+ }
//thread AITdm_CleanupBoredNPCThread( guy )
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
index 93a3aa16..9cf0021d 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
@@ -178,6 +178,12 @@ void function AT_PlayerTitleThink( entity player )
{
// Set player money count
player.SetTitle( "$" + string( AT_GetPlayerBonusPoints( player ) ) )
+
+ if( AT_GetPlayerBonusPoints( player ) >= 600 && !HasPlayerCompletedMeritScore( player ) ) //Challenge is: "Earn $600."
+ {
+ AddPlayerScore( player, "ChallengeATAssault" )
+ SetPlayerChallengeMeritScore( player )
+ }
}
else if ( GetGameState() >= eGameState.WinnerDetermined )
{
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
index c5765300..d8b0c9bd 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
@@ -29,6 +29,8 @@ struct {
array<HardpointStruct> hardpoints
array<CP_PlayerStruct> players
+ table<entity,int> playerAssaultPoints
+ table<entity,int> playerDefensePoints
} file
void function GamemodeCP_Init()
@@ -112,11 +114,13 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama
{
AddPlayerScore( attacker , "HardpointDefense", victim )
attacker.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_DEFENSE)
+ UpdatePlayerScoreForChallenge(attacker,0,POINTVALUE_HARDPOINT_DEFENSE)
}
else if((victimCP.hardpoint.GetTeam()==victim.GetTeam())||(GetHardpointCappingTeam(victimCP)==victim.GetTeam()))
{
AddPlayerScore( attacker, "HardpointAssault", victim )
attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_ASSAULT)
+ UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_ASSAULT,0)
}
}
}
@@ -127,10 +131,12 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama
{
AddPlayerScore( attacker , "HardpointSnipe", victim )
attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_SNIPE)
+ UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_SNIPE,0)
}
else{
AddPlayerScore( attacker , "HardpointSiege", victim )
attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_SIEGE)
+ UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_SIEGE,0)
}
}
else if(attackerCP.hardpoint!=null)//Perimeter Defense
@@ -138,6 +144,7 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama
if(attackerCP.hardpoint.GetTeam()==attacker.GetTeam())
AddPlayerScore( attacker , "HardpointPerimeterDefense", victim)
attacker.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_PERIMETER_DEFENSE)
+ UpdatePlayerScoreForChallenge(attacker,0,POINTVALUE_HARDPOINT_PERIMETER_DEFENSE)
}
foreach(CP_PlayerStruct player in file.players) //Reset Victim Holdtime Counter
@@ -308,6 +315,7 @@ void function CapturePointForTeam(HardpointStruct hardpoint, int Team)
if(player.IsPlayer()){
AddPlayerScore(player,"ControlPointCapture")
player.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_CAPTURE)
+ UpdatePlayerScoreForChallenge(player,POINTVALUE_HARDPOINT_CAPTURE,0)
}
}
}
@@ -319,12 +327,17 @@ void function GamemodeCP_InitPlayer(entity player)
playerStruct.timeOnPoints = [0.0,0.0,0.0]
playerStruct.isOnHardpoint = false
file.players.append(playerStruct)
+ file.playerAssaultPoints[player] <- 0
+ file.playerDefensePoints[player] <- 0
thread PlayerThink(playerStruct)
}
void function GamemodeCP_RemovePlayer(entity player)
{
-
+ if(player in file.playerAssaultPoints)
+ delete file.playerAssaultPoints[player]
+ if(player in file.playerDefensePoints)
+ delete file.playerDefensePoints[player]
foreach(index,CP_PlayerStruct playerStruct in file.players)
{
if(playerStruct.player==player)
@@ -376,11 +389,13 @@ void function PlayerThink(CP_PlayerStruct player)
{
AddPlayerScore(player.player,"ControlPointAmpedHold")
player.player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, POINTVALUE_HARDPOINT_AMPED_HOLD )
+ UpdatePlayerScoreForChallenge(player.player,0,POINTVALUE_HARDPOINT_AMPED_HOLD)
}
else
{
AddPlayerScore(player.player,"ControlPointHold")
player.player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, POINTVALUE_HARDPOINT_HOLD )
+ UpdatePlayerScoreForChallenge(player.player,0,POINTVALUE_HARDPOINT_HOLD)
}
}
break
@@ -574,6 +589,7 @@ void function HardpointThink( HardpointStruct hardpoint )
{
AddPlayerScore(player,"ControlPointAmped")
player.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_AMPED)
+ UpdatePlayerScoreForChallenge(player,0,POINTVALUE_HARDPOINT_AMPED)
}
}
}
@@ -714,3 +730,26 @@ string function GetHardpointGroup(entity hardpoint) //Hardpoint Entity B on Home
return string(hardpoint.kv.hardpointGroup)
}
+
+void function UpdatePlayerScoreForChallenge(entity player,int assaultpoints = 0,int defensepoints = 0)
+{
+ if(player in file.playerAssaultPoints)
+ {
+ file.playerAssaultPoints[player] += assaultpoints
+ if( file.playerAssaultPoints[player] >= 1000 && !HasPlayerCompletedMeritScore(player) )
+ {
+ AddPlayerScore(player,"ChallengeCPAssault")
+ SetPlayerChallengeMeritScore(player)
+ }
+ }
+
+ if(player in file.playerDefensePoints)
+ {
+ file.playerDefensePoints[player] += defensepoints
+ if( file.playerDefensePoints[player] >= 500 && !HasPlayerCompletedMeritScore(player) )
+ {
+ AddPlayerScore(player,"ChallengeCPDefense")
+ SetPlayerChallengeMeritScore(player)
+ }
+ }
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut
index 9b05c3d4..85b80d74 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut
@@ -1,5 +1,4 @@
untyped
-// this needs a refactor lol
global function CaptureTheFlag_Init
global function RateSpawnpoints_CTF
@@ -12,16 +11,31 @@ const array<string> SWAP_FLAG_MAPS = [
struct {
entity imcFlagSpawn
entity imcFlag
- entity imcFlagReturnTrigger
entity militiaFlagSpawn
entity militiaFlag
- entity militiaFlagReturnTrigger
array<entity> imcCaptureAssistList
array<entity> militiaCaptureAssistList
} file
+
+
+
+
+
+
+
+
+
+/*
+ ██████ █████ ██████ ████████ ██ ██ ██████ ███████ ████████ ██ ██ ███████ ███████ ██ █████ ██████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ ███████ ██████ ██ ██ ██ ██████ █████ ██ ███████ █████ █████ ██ ███████ ██ ███
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ ██████ ██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ ██ ███████ ██ ███████ ██ ██ ██████
+*/
+
void function CaptureTheFlag_Init()
{
PrecacheModel( CTF_FLAG_MODEL )
@@ -30,30 +44,31 @@ void function CaptureTheFlag_Init()
PrecacheParticleSystem( FLAG_FX_ENEMY )
CaptureTheFlagShared_Init()
+
SetSwitchSidesBased( true )
SetSuddenDeathBased( true )
+
SetShouldUseRoundWinningKillReplay( true )
- SetRoundWinningKillReplayKillClasses( false, false ) // make these fully manual
+ SetRoundWinningKillReplayKillClasses( false, false )
AddCallback_OnClientConnected( CTFInitPlayer )
-
+ AddCallback_OnClientDisconnected( CTFPlayerDisconnected )
+
AddCallback_GameStateEnter( eGameState.Prematch, CreateFlags )
AddCallback_GameStateEnter( eGameState.Epilogue, RemoveFlags )
+ AddCallback_GameStateEnter( eGameState.Playing, OnPlaying )
+
AddCallback_OnTouchHealthKit( "item_flag", OnFlagCollected )
+
AddCallback_OnPlayerKilled( OnPlayerKilled )
AddCallback_OnPilotBecomesTitan( DropFlagForBecomingTitan )
- SetSpawnZoneRatingFunc( DecideSpawnZone_CTF )
AddSpawnpointValidationRule( VerifyCTFSpawnpoint )
- RegisterSignal( "FlagReturnEnded" )
RegisterSignal( "ResetDropTimeout" )
- // setup stuff for the functions in sh_gamemode_ctf
- // don't really like using level for stuff but just how it be
level.teamFlags <- {}
- // setup score event earnmeter values
ScoreEvent_SetEarnMeterValues( "KillPilot", 0.05, 0.20 )
ScoreEvent_SetEarnMeterValues( "Headshot", 0.0, 0.02 )
ScoreEvent_SetEarnMeterValues( "FirstStrike", 0.0, 0.05 )
@@ -67,61 +82,21 @@ void function CaptureTheFlag_Init()
ScoreEvent_SetEarnMeterValues( "FlagReturn", 0.0, 0.20 )
}
-void function RateSpawnpoints_CTF( int checkClass, array<entity> spawnpoints, int team, entity player )
-{
- RateSpawnpoints_SpawnZones( checkClass, spawnpoints, team, player )
-}
-bool function VerifyCTFSpawnpoint( entity spawnpoint, int team )
-{
- // ensure spawnpoints aren't too close to enemy base
-
- if ( HasSwitchedSides() )
- team = GetOtherTeam( team )
-
- array<entity> startSpawns = SpawnPoints_GetPilotStart( team )
- array<entity> enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) )
-
- vector averageFriendlySpawns
- vector averageEnemySpawns
-
- foreach ( entity spawn in startSpawns )
- averageFriendlySpawns += spawn.GetOrigin()
-
- averageFriendlySpawns /= startSpawns.len()
-
- foreach ( entity spawn in enemyStartSpawns )
- averageEnemySpawns += spawn.GetOrigin()
-
- averageEnemySpawns /= startSpawns.len()
-
- return Distance2D( spawnpoint.GetOrigin(), averageEnemySpawns ) / Distance2D( averageFriendlySpawns, averageEnemySpawns ) > 0.35
-}
-void function CTFInitPlayer( entity player )
-{
- if ( !IsValid( file.imcFlagSpawn ) )
- return
-
- vector imcSpawn = file.imcFlagSpawn.GetOrigin()
- Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z )
-
- vector militiaSpawn = file.militiaFlagSpawn.GetOrigin()
- Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z )
-}
-void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
-{
- if ( !IsValid( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ) ) ) // getting a crash idk
- return
- if ( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ).GetParent() == victim )
- {
- if ( victim != attacker && attacker.IsPlayer() )
- AddPlayerScore( attacker, "FlagCarrierKill", victim )
-
- DropFlag( victim )
- }
-}
+
+
+
+
+
+/*
+███████ ██████ █████ ██ ██ ███ ██ ██ ██████ ██████ ██ ██████
+██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██
+███████ ██████ ███████ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██
+ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+███████ ██ ██ ██ ███ ███ ██ ████ ███████ ██████ ██████ ██ ██████
+*/
void function CreateFlags()
{
@@ -129,25 +104,20 @@ void function CreateFlags()
{
file.imcFlagSpawn.Destroy()
file.imcFlag.Destroy()
- file.imcFlagReturnTrigger.Destroy()
-
+ }
+ if ( IsValid( file.militiaFlagSpawn ) )
+ {
file.militiaFlagSpawn.Destroy()
file.militiaFlag.Destroy()
- file.militiaFlagReturnTrigger.Destroy()
}
foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) )
{
- // on some maps flags are on the opposite side from what they should be
- // likely this is because respawn uses distance checks from spawns to check this in official
- // but i don't like doing that so just using a list of maps to swap them on lol
bool switchedSides = HasSwitchedSides() == 1
-
- // i dont know why this works and whatever we had before didn't, but yeah
+
bool shouldSwap = switchedSides
- if (!shouldSwap && SWAP_FLAG_MAPS.contains( GetMapName() ))
+ if ( !shouldSwap && SWAP_FLAG_MAPS.contains( GetMapName() ) )
shouldSwap = !shouldSwap
-
int flagTeam = spawn.GetTeam()
if ( shouldSwap )
@@ -155,48 +125,33 @@ void function CreateFlags()
flagTeam = GetOtherTeam( flagTeam )
SetTeam( spawn, flagTeam )
}
-
- // create flag base
+
entity base = CreatePropDynamic( CTF_FLAG_BASE_MODEL, spawn.GetOrigin(), spawn.GetAngles(), 0 )
SetTeam( base, spawn.GetTeam() )
svGlobal.flagSpawnPoints[ flagTeam ] = base
- // create flag
entity flag = CreateEntity( "item_flag" )
flag.SetValueForModelKey( CTF_FLAG_MODEL )
SetTeam( flag, flagTeam )
flag.MarkAsNonMovingAttachment()
- flag.Minimap_AlwaysShow( TEAM_IMC, null ) // show flag icon on minimap
+ flag.Minimap_AlwaysShow( TEAM_IMC, null )
flag.Minimap_AlwaysShow( TEAM_MILITIA, null )
flag.Minimap_SetAlignUpright( true )
DispatchSpawn( flag )
flag.SetModel( CTF_FLAG_MODEL )
- flag.SetOrigin( spawn.GetOrigin() + < 0, 0, base.GetBoundingMaxs().z * 2 > ) // ensure flag doesn't spawn clipped into geometry
+ flag.SetOrigin( spawn.GetOrigin() + < 0, 0, base.GetBoundingMaxs().z * 2 > )
flag.SetVelocity( < 0, 0, 1 > )
flag.s.canTake <- true
- flag.s.playersReturning <- []
level.teamFlags[ flag.GetTeam() ] <- flag
-
- entity returnTrigger = CreateEntity( "trigger_cylinder" )
- SetTeam( returnTrigger, flagTeam )
- returnTrigger.SetRadius( CTF_GetFlagReturnRadius() )
- returnTrigger.SetAboveHeight( CTF_GetFlagReturnRadius() )
- returnTrigger.SetBelowHeight( CTF_GetFlagReturnRadius() )
-
- returnTrigger.SetEnterCallback( OnPlayerEntersFlagReturnTrigger )
- returnTrigger.SetLeaveCallback( OnPlayerExitsFlagReturnTrigger )
- DispatchSpawn( returnTrigger )
-
- thread TrackFlagReturnTrigger( flag, returnTrigger )
+ thread FlagProximityTracker( flag )
if ( flagTeam == TEAM_IMC )
{
file.imcFlagSpawn = base
file.imcFlag = flag
- file.imcFlagReturnTrigger = returnTrigger
SetGlobalNetEnt( "imcFlag", file.imcFlag )
SetGlobalNetEnt( "imcFlagHome", file.imcFlagSpawn )
@@ -205,76 +160,185 @@ void function CreateFlags()
{
file.militiaFlagSpawn = base
file.militiaFlag = flag
- file.militiaFlagReturnTrigger = returnTrigger
SetGlobalNetEnt( "milFlag", file.militiaFlag )
SetGlobalNetEnt( "milFlagHome", file.militiaFlagSpawn )
}
}
- // reset the flag states, prevents issues where flag is home but doesnt think it's home when halftime goes
SetFlagStateForTeam( TEAM_MILITIA, eFlagState.None )
SetFlagStateForTeam( TEAM_IMC, eFlagState.None )
-
- foreach ( entity player in GetPlayerArray() )
- CTFInitPlayer( player )
}
void function RemoveFlags()
{
- // destroy all the flag related things
if ( IsValid( file.imcFlagSpawn ) )
{
+ PlayFX( $"P_phase_shift_main", file.imcFlagSpawn.GetOrigin() )
file.imcFlagSpawn.Destroy()
file.imcFlag.Destroy()
- file.imcFlagReturnTrigger.Destroy()
}
+
if ( IsValid( file.militiaFlagSpawn ) )
{
+ PlayFX( $"P_phase_shift_main", file.militiaFlagSpawn.GetOrigin() )
file.militiaFlagSpawn.Destroy()
file.militiaFlag.Destroy()
- file.militiaFlagReturnTrigger.Destroy()
}
-
- // unsure if this is needed, since the flags are destroyed? idk
+
SetFlagStateForTeam( TEAM_MILITIA, eFlagState.None )
SetFlagStateForTeam( TEAM_IMC, eFlagState.None )
}
-void function TrackFlagReturnTrigger( entity flag, entity returnTrigger )
+void function RateSpawnpoints_CTF( int checkClass, array<entity> spawnpoints, int team, entity player )
{
- // this is a bit of a hack, it seems parenting the return trigger to the flag actually sets the pickup radius of the flag to be the same as the trigger
- // this isn't wanted since only pickups should use that additional radius
- flag.EndSignal( "OnDestroy" )
+ vector allyFlagSpot
+ vector enemyFlagSpot
+ foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) )
+ {
+ if( spawn.GetTeam() == team )
+ allyFlagSpot = spawn.GetOrigin()
+ else
+ enemyFlagSpot = spawn.GetOrigin()
+ }
+
+ foreach ( entity spawn in spawnpoints )
+ {
+ float rating = 0.0
+ float allyFlagDistance = Distance2D( spawn.GetOrigin(), allyFlagSpot )
+ float enemyFlagDistance = Distance2D( spawn.GetOrigin(), enemyFlagSpot )
+
+ if( enemyFlagDistance > allyFlagDistance )
+ {
+ rating += spawn.NearbyAllyScore( team, "ai" )
+ rating += spawn.NearbyAllyScore( team, "titan" )
+ rating += spawn.NearbyAllyScore( team, "pilot" )
+
+ rating += spawn.NearbyEnemyScore( team, "ai" )
+ rating += spawn.NearbyEnemyScore( team, "titan" )
+ rating += spawn.NearbyEnemyScore( team, "pilot" )
+
+ rating = rating / allyFlagDistance
+ }
+
+ if ( spawn == player.p.lastSpawnPoint )
+ rating += GetConVarFloat( "spawnpoint_last_spawn_rating" )
- while ( true )
+ spawn.CalculateRating( checkClass, team, rating, rating * 0.25 )
+ }
+}
+
+bool function VerifyCTFSpawnpoint( entity spawnpoint, int team )
+{
+ vector allyFlagSpot
+ vector enemyFlagSpot
+ foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) )
{
- returnTrigger.SetOrigin( flag.GetOrigin() )
- WaitFrame()
+ if( spawn.GetTeam() == team )
+ allyFlagSpot = spawn.GetOrigin()
+ else
+ enemyFlagSpot = spawn.GetOrigin()
}
+
+ if( Distance2D( spawnpoint.GetOrigin(), allyFlagSpot ) > Distance2D( spawnpoint.GetOrigin(), enemyFlagSpot ) )
+ return false
+
+ return true
}
-void function SetFlagStateForTeam( int team, int state )
+
+
+
+
+
+
+
+
+/*
+██████ ██ █████ ██ ██ ███████ ██████ ██ ██████ ██████ ██ ██████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██████ ██ ███████ ████ █████ ██████ ██ ██ ██ ██ ███ ██ ██
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ ███████ ██ ██ ██ ███████ ██ ██ ███████ ██████ ██████ ██ ██████
+*/
+
+void function OnPlaying()
{
- if ( state == eFlagState.Away ) // we tell the client the flag is the player carrying it if they're carrying it
- SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", ( team == TEAM_IMC ? file.imcFlag : file.militiaFlag ).GetParent() )
- else
- SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", team == TEAM_IMC ? file.imcFlag : file.militiaFlag )
+ foreach ( entity player in GetPlayerArray() )
+ CTFInitPlayer( player )
+}
- SetGlobalNetInt( team == TEAM_IMC ? "imcFlagState" : "milFlagState", state )
+void function CTFInitPlayer( entity player )
+{
+ if ( !GamePlaying() )
+ return
+
+ if ( IsValid( file.imcFlagSpawn ) )
+ {
+ vector imcSpawn = file.imcFlagSpawn.GetOrigin()
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z )
+ }
+
+ if ( IsValid( file.militiaFlagSpawn ) )
+ {
+ vector militiaSpawn = file.militiaFlagSpawn.GetOrigin()
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z )
+ }
+}
+
+void function CTFPlayerDisconnected( entity player )
+{
+ // This has no validity checks on the player because the disconnection callback happens in the exact last frame the player entity still exists
+ if( !GamePlaying() )
+ return
+
+ if ( PlayerHasEnemyFlag( player ) )
+ DropFlag( player, false )
}
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !IsValid( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ) ) ) // getting a crash idk
+ return
+
+ if ( PlayerHasEnemyFlag( victim ) )
+ {
+ if ( victim != attacker && attacker.IsPlayer() )
+ AddPlayerScore( attacker, "FlagCarrierKill", victim )
+
+ DropFlag( victim )
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+/*
+███████ ██ █████ ██████ ██ ██████ ██████ ██ ██████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+█████ ██ ███████ ██ ███ ██ ██ ██ ██ ███ ██ ██
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ ███████ ██ ██ ██████ ███████ ██████ ██████ ██ ██████
+*/
+
bool function OnFlagCollected( entity player, entity flag )
{
if ( !IsAlive( player ) || flag.GetParent() != null || player.IsTitan() || player.IsPhaseShifted() )
return false
if ( player.GetTeam() != flag.GetTeam() && flag.s.canTake )
- GiveFlag( player, flag ) // pickup enemy flag
+ GiveFlag( player, flag )
else if ( player.GetTeam() == flag.GetTeam() && IsFlagHome( flag ) && PlayerHasEnemyFlag( player ) )
- CaptureFlag( player, GetFlagForTeam( GetOtherTeam( flag.GetTeam() ) ) ) // cap the flag
+ CaptureFlag( player, GetFlagForTeam( GetOtherTeam( flag.GetTeam() ) ) )
- return false // don't wanna delete the flag entity
+ return false // Don't delete the flag
}
void function GiveFlag( entity player, entity flag )
@@ -283,7 +347,8 @@ void function GiveFlag( entity player, entity flag )
flag.Signal( "ResetDropTimeout" )
flag.SetParent( player, "FLAG" )
- thread DropFlagIfPhased( player, flag )
+ if ( GetCurrentPlaylistVarInt( "phase_shift_drop_flag", 0 ) == 1 )
+ thread DropFlagIfPhased( player, flag )
// do notifications
MessageToPlayer( player, eEventNotifications.YouHaveTheEnemyFlag )
@@ -301,103 +366,10 @@ void function GiveFlag( entity player, entity flag )
SetFlagStateForTeam( flag.GetTeam(), eFlagState.Away ) // used for held
}
-void function DropFlagIfPhased( entity player, entity flag )
-{
- player.EndSignal( "StartPhaseShift" )
- player.EndSignal( "OnDestroy" )
-
- OnThreadEnd( function() : ( player )
- {
- if (GetGameState() == eGameState.Playing || GetGameState() == eGameState.SuddenDeath)
- DropFlag( player, true )
- })
- // the IsValid check is purely to prevent a crash due to a destroyed flag (epilogue)
- while( IsValid(flag) && flag.GetParent() == player )
- WaitFrame()
-}
-
-void function DropFlagForBecomingTitan( entity pilot, entity titan )
-{
- DropFlag( pilot, true )
-}
-
-void function DropFlag( entity player, bool realDrop = true )
-{
- entity flag = GetFlagForTeam( GetOtherTeam( player.GetTeam() ) )
-
- if ( flag.GetParent() != player )
- return
-
- print( player + " dropped the flag!" )
-
- flag.ClearParent()
- flag.SetAngles( < 0, 0, 0 > )
- flag.SetVelocity( < 0, 0, 0 > )
-
- if ( realDrop )
- {
- // start drop timeout countdown
- thread TrackFlagDropTimeout( flag )
-
- // add to capture assists
- if ( player.GetTeam() == TEAM_IMC )
- file.imcCaptureAssistList.append( player )
- else
- file.militiaCaptureAssistList.append( player )
-
- // do notifications
- MessageToPlayer( player, eEventNotifications.YouDroppedTheEnemyFlag )
- EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagDrop" )
-
- MessageToTeam( player.GetTeam(), eEventNotifications.PlayerDroppedEnemyFlag, player, player )
- // todo need a sound here maybe
- MessageToTeam( GetOtherTeam( player.GetTeam() ), eEventNotifications.PlayerDroppedFriendlyFlag, player, player )
- // todo need a sound here maybe
- }
-
- SetFlagStateForTeam( flag.GetTeam(), eFlagState.Home ) // used for return prompt
-}
-
-void function TrackFlagDropTimeout( entity flag )
-{
- flag.EndSignal( "ResetDropTimeout" )
-
- wait CTF_GetDropTimeout()
-
- ResetFlag( flag )
-}
-
-void function ResetFlag( entity flag )
-{
- // prevents crash when flag is reset after it's been destroyed due to epilogue
- if (!IsValid(flag))
- return
- // ensure we can't pickup the flag after it's been dropped but before it's been reset
- flag.s.canTake = false
-
- if ( flag.GetParent() != null )
- DropFlag( flag.GetParent(), false )
-
- entity spawn
- if ( flag.GetTeam() == TEAM_IMC )
- spawn = file.imcFlagSpawn
- else
- spawn = file.militiaFlagSpawn
-
- flag.SetOrigin( spawn.GetOrigin() + < 0, 0, spawn.GetBoundingMaxs().z + 1 > )
-
- // we can take it again now
- flag.s.canTake = true
-
- SetFlagStateForTeam( flag.GetTeam(), eFlagState.None ) // used for home
-
- flag.Signal( "ResetDropTimeout" )
-}
-
void function CaptureFlag( entity player, entity flag )
{
// can only capture flags during normal play or sudden death
- if (GetGameState() != eGameState.Playing && GetGameState() != eGameState.SuddenDeath)
+ 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
@@ -421,8 +393,18 @@ void function CaptureFlag( entity player, entity flag )
assistList = file.militiaCaptureAssistList
foreach( entity assistPlayer in assistList )
- if ( player != assistPlayer )
- AddPlayerScore( assistPlayer, "FlagCaptureAssist", player )
+ {
+ if ( IsValidPlayer( assistPlayer ) )
+ {
+ if ( player != assistPlayer )
+ AddPlayerScore( assistPlayer, "FlagCaptureAssist", player )
+ if( !HasPlayerCompletedMeritScore( assistPlayer ) )
+ {
+ AddPlayerScore( assistPlayer, "ChallengeCTFCapAssist" )
+ SetPlayerChallengeMeritScore( assistPlayer )
+ }
+ }
+ }
assistList.clear()
@@ -430,84 +412,207 @@ void function CaptureFlag( entity player, entity flag )
MessageToPlayer( player, eEventNotifications.YouCapturedTheEnemyFlag )
EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_PlayerScore" )
+ if( !HasPlayerCompletedMeritScore( player ) )
+ SetPlayerChallengeMeritScore( player )
+
MessageToTeam( team, eEventNotifications.PlayerCapturedEnemyFlag, player, player )
EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamScore", player.GetTeam(), player )
MessageToTeam( GetOtherTeam( team ), eEventNotifications.PlayerCapturedFriendlyFlag, player, player )
EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyScores", flag.GetTeam() )
- if ( GameRules_GetTeamScore( team ) == GameMode_GetRoundScoreLimit( GAMETYPE ) - 1 )
+ if ( GameRules_GetTeamScore( team ) == GetScoreLimit_FromPlaylist() - 1 )
{
PlayFactionDialogueToTeam( "ctf_notifyWin1more", team )
PlayFactionDialogueToTeam( "ctf_notifyLose1more", GetOtherTeam( team ) )
+ foreach( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_CTF_PlayMatchNearEndMusic" )
}
}
-void function OnPlayerEntersFlagReturnTrigger( entity trigger, entity player )
+void function DropFlag( entity player, bool realDrop = true )
{
- entity flag
- if ( trigger.GetTeam() == TEAM_IMC )
- flag = file.imcFlag
- else
- flag = file.militiaFlag
-
- if( !IsValid( flag ) || !IsValid( player ) )
- return
+ entity flag = GetFlagForTeam( GetOtherTeam( player.GetTeam() ) )
- if ( !player.IsPlayer() || player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null )
+ if( !IsValid( flag ) || flag.GetParent() != player )
return
- thread TryReturnFlag( player, flag )
+ print( player + " dropped the flag!" )
+
+ flag.ClearParent()
+ flag.SetAngles( < 0, 0, 0 > )
+ flag.SetVelocity( < 0, 0, 0 > )
+
+ if ( realDrop )
+ {
+ if ( player.GetTeam() == TEAM_IMC && !file.imcCaptureAssistList.contains( player ) )
+ file.imcCaptureAssistList.append( player )
+
+ else if( !file.militiaCaptureAssistList.contains( player ) )
+ file.militiaCaptureAssistList.append( player )
+
+ MessageToPlayer( player, eEventNotifications.YouDroppedTheEnemyFlag )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagDrop" )
+
+ MessageToTeam( player.GetTeam(), eEventNotifications.PlayerDroppedEnemyFlag, player, player )
+ MessageToTeam( GetOtherTeam( player.GetTeam() ), eEventNotifications.PlayerDroppedFriendlyFlag, player, player )
+ }
+
+ thread TrackFlagDropTimeout( flag )
+ SetFlagStateForTeam( flag.GetTeam(), eFlagState.Home )
}
-void function OnPlayerExitsFlagReturnTrigger( entity trigger, entity player )
+void function ResetFlag( entity flag )
{
- entity flag
- if ( trigger.GetTeam() == TEAM_IMC )
- flag = file.imcFlag
+ flag.s.canTake = false
+
+ if ( flag.GetParent() != null )
+ DropFlag( flag.GetParent(), false )
+
+ entity flagBase
+ if ( flag.GetTeam() == TEAM_IMC )
+ flagBase = file.imcFlagSpawn
else
- flag = file.militiaFlag
+ flagBase = file.militiaFlagSpawn
- if ( !player.IsPlayer() || player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null )
- return
+ flag.SetOrigin( flagBase.GetOrigin() + < 0, 0, flagBase.GetBoundingMaxs().z + 1 > )
+
+ flag.s.canTake = true
- player.Signal( "FlagReturnEnded" )
-}
+ SetFlagStateForTeam( flag.GetTeam(), eFlagState.None )
+
+ flag.Signal( "ResetDropTimeout" )
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check proximity for flag returns
+// Input : flag - The flag entity
+//-----------------------------------------------------------------------------
+void function FlagProximityTracker( entity flag )
+{
+ flag.EndSignal( "OnDestroy" )
+
+ array < entity > playerInsidePerimeter
+ while( true )
+ {
+ if( !playerInsidePerimeter.len() )
+ ArrayRemoveDead( playerInsidePerimeter )
+
+ foreach ( player in GetPlayerArrayOfTeam_Alive( flag.GetTeam() ) )
+ {
+ if ( Distance( player.GetOrigin(), flag.GetOrigin() ) < CTF_GetFlagReturnRadius() )
+ {
+ if ( player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null )
+ continue
+
+ if( playerInsidePerimeter.contains( player ) )
+ continue
+
+ playerInsidePerimeter.append( player )
+ thread TryReturnFlag( player, flag )
+ }
+ else
+ {
+ if( playerInsidePerimeter.contains( player ) )
+ {
+ player.Signal( "CTF_LeftReturnTriggerArea" ) // Cut the progress if outside range
+ playerInsidePerimeter.removebyvalue( player )
+ }
+ }
+ }
+
+ WaitFrame()
+ }
+}
void function TryReturnFlag( entity player, entity flag )
{
- // start return progress bar
Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StartReturnFlagProgressBar", Time() + CTF_GetFlagReturnTime() )
EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagReturnMeter" )
- OnThreadEnd( function() : ( player )
+ OnThreadEnd( function() : ( flag, player )
{
- // cleanup
- Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StopReturnFlagProgressBar" )
- StopSoundOnEntity( player, "UI_CTF_1P_FlagReturnMeter" )
+ if ( IsValidPlayer( player ) )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StopReturnFlagProgressBar" )
+ StopSoundOnEntity( player, "UI_CTF_1P_FlagReturnMeter" )
+ }
})
- player.EndSignal( "FlagReturnEnded" )
- flag.EndSignal( "FlagReturnEnded" ) // avoid multiple players to return one flag at once
+ flag.EndSignal( "CTF_ReturnedFlag" )
+ flag.EndSignal( "OnDestroy" )
+
+ player.EndSignal( "CTF_LeftReturnTriggerArea" )
player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
wait CTF_GetFlagReturnTime()
- // flag return succeeded
- // return flag
ResetFlag( flag )
- flag.Signal( "FlagReturnEnded" )
-
- // do notifications for return
- MessageToPlayer( player, eEventNotifications.YouReturnedFriendlyFlag )
- AddPlayerScore( player, "FlagReturn", player )
- player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, 1 )
MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerReturnedFriendlyFlag, null, player )
EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_TeamReturnsFlag", flag.GetTeam() )
PlayFactionDialogueToTeam( "ctf_flagReturnedFriendly", flag.GetTeam() )
+ MessageToPlayer( player, eEventNotifications.YouReturnedFriendlyFlag )
+ AddPlayerScore( player, "FlagReturn", player )
+ player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, 1 )
+
+ if ( !HasPlayerCompletedMeritScore( player ) )
+ {
+ AddPlayerScore( player, "ChallengeCTFRetAssist" )
+ SetPlayerChallengeMeritScore( player )
+ }
+
MessageToTeam( GetOtherTeam( flag.GetTeam() ), eEventNotifications.PlayerReturnedEnemyFlag, null, player )
EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyReturnsFlag", GetOtherTeam( flag.GetTeam() ) )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_ReturnsFlag" )
PlayFactionDialogueToTeam( "ctf_flagReturnedEnemy", GetOtherTeam( flag.GetTeam() ) )
-} \ No newline at end of file
+
+ flag.Signal( "CTF_ReturnedFlag" )
+}
+
+void function SetFlagStateForTeam( int team, int state )
+{
+ if ( state == eFlagState.Away ) // we tell the client the flag is the player carrying it if they're carrying it
+ SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", ( team == TEAM_IMC ? file.imcFlag : file.militiaFlag ).GetParent() )
+ else
+ SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", team == TEAM_IMC ? file.imcFlag : file.militiaFlag )
+
+ SetGlobalNetInt( team == TEAM_IMC ? "imcFlagState" : "milFlagState", state )
+}
+
+void function DropFlagIfPhased( entity player, entity flag )
+{
+ player.EndSignal( "StartPhaseShift" )
+ player.EndSignal( "OnDestroy" )
+ flag.EndSignal( "OnDestroy" )
+
+ OnThreadEnd( function() : ( player )
+ {
+ if ( IsValidPlayer( player ) )
+ {
+ if ( GetGameState() == eGameState.Playing || GetGameState() == eGameState.SuddenDeath )
+ DropFlag( player, true )
+ }
+ })
+
+ while( flag.GetParent() == player )
+ WaitFrame()
+}
+
+void function DropFlagForBecomingTitan( entity pilot, entity titan )
+{
+ DropFlag( pilot, true )
+}
+
+void function TrackFlagDropTimeout( entity flag )
+{
+ flag.EndSignal( "CTF_ReturnedFlag" )
+ flag.EndSignal( "ResetDropTimeout" )
+ flag.EndSignal( "OnDestroy" )
+
+ wait CTF_GetDropTimeout()
+
+ ResetFlag( flag )
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut
index 9d8f84b5..6d0fd3c7 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut
@@ -17,6 +17,7 @@ void function GamemodeFRA_Init()
ScoreEvent_SetEarnMeterValues( "PilotBatteryPickup", 0.0, 0.34 )
EarnMeterMP_SetPassiveMeterGainEnabled( false )
PilotBattery_SetMaxCount( 3 )
+ SetupGenericFFAChallenge()
AddCallback_OnPlayerKilled( FRARemoveEarnMeter )
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut
index 31c85a57..8999231d 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut
@@ -8,6 +8,8 @@ struct {
float lastDamageInfoTime
bool shouldDoHighlights
+
+ table< entity, int > pilotstreak
} file
void function GamemodeLts_Init()
@@ -34,6 +36,37 @@ void function GamemodeLts_Init()
ClassicMP_SetCustomIntro( ClassicMP_DefaultNoIntro_Setup, ClassicMP_DefaultNoIntro_GetLength() )
ClassicMP_ForceDisableEpilogue( true )
AddCallback_GameStateEnter( eGameState.Playing, WaitForThirtySecondsLeft )
+
+ AddCallback_OnClientConnected( SetupPlayerLTSChallenges ) //Just to make up the Match Goals tracking
+ AddCallback_OnClientDisconnected( RemovePlayerLTSChallenges ) //Safety removal of data to prevent crashes
+ AddCallback_OnPlayerKilled( LTSChallengeForPlayerKilled )
+}
+
+void function SetupPlayerLTSChallenges( entity player )
+{
+ file.pilotstreak[ player ] <- 0
+}
+
+void function RemovePlayerLTSChallenges( entity player )
+{
+ if( player in file.pilotstreak )
+ delete file.pilotstreak[ player ]
+}
+
+void function LTSChallengeForPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( victim.IsPlayer() && attacker in file.pilotstreak )
+ {
+ file.pilotstreak[attacker]++
+ if( file.pilotstreak[attacker] >= 2 && !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengeLTS" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
}
void function WaitForThirtySecondsLeft()
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut
index 659dbb7a..768bbde1 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut
@@ -180,6 +180,12 @@ void function MarkPlayers( entity imcMark, entity militiaMark )
entity livingMark = GetMarked( GetOtherTeam( deadMark.GetTeam() ) )
livingMark.SetPlayerGameStat( PGS_DEFENSE_SCORE, livingMark.GetPlayerGameStat( PGS_DEFENSE_SCORE ) + 1 )
+ if( !HasPlayerCompletedMeritScore( livingMark ) )
+ {
+ AddPlayerScore( livingMark, "ChallengeMFD" )
+ SetPlayerChallengeMeritScore( livingMark )
+ }
+
// thread this so we don't kill our own thread
thread AddTeamScore( livingMark.GetTeam(), 1 )
}
@@ -188,10 +194,22 @@ void function UpdateMarksForKill( entity victim, entity attacker, var damageInfo
{
if ( victim == GetMarked( victim.GetTeam() ) )
{
- MessageToAll( eEventNotifications.MarkedForDeathKill, null, victim, attacker.GetEncodedEHandle() )
+ // handle suicides. Not sure what the actual message is that vanilla shows for this
+ // but this will prevent crashing for now
+ bool isSuicide = IsSuicide( victim, attacker, DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ entity actualAttacker = isSuicide ? victim : attacker
+
+ MessageToAll( eEventNotifications.MarkedForDeathKill, null, victim, actualAttacker.GetEncodedEHandle() )
svGlobal.levelEnt.Signal( "MarkKilled", { mark = victim } )
- if ( attacker.IsPlayer() )
+ if ( !isSuicide && attacker.IsPlayer() )
+ {
attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 )
+ if( !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengeMFD" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
}
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut
index 57355ad8..fb84cc82 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut
@@ -19,12 +19,7 @@ void function GamemodePs_Init()
AddCallback_OnPlayerKilled( GiveScoreForPlayerKill )
ScoreEvent_SetupEarnMeterValuesForMixedModes()
SetTimeoutWinnerDecisionFunc( CheckScoreForDraw )
-
- // spawnzone stuff
- SetShouldCreateMinimapSpawnZones( true )
-
- //AddCallback_OnPlayerKilled( CheckSpawnzoneSuspiciousDeaths )
- //AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", SpawnzoneTriggerInit )
+ SetupGenericFFAChallenge()
file.militiaPreviousSpawnZones = [ null, null, null ]
file.imcPreviousSpawnZones = [ null, null, null ]
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut
index cb277b00..4617476e 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut
@@ -18,6 +18,7 @@ void function GamemodeSpeedball_Init()
Riff_ForceTitanAvailability( eTitanAvailability.Never )
Riff_ForceSetEliminationMode( eEliminationMode.Pilots )
ScoreEvent_SetupEarnMeterValuesForMixedModes()
+ SetupGenericFFAChallenge()
AddSpawnCallbackEditorClass( "script_ref", "info_speedball_flag", CreateFlag )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut
index 5c0e6fec..61ede2d4 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut
@@ -6,6 +6,7 @@ void function GamemodeTdm_Init()
AddCallback_OnPlayerKilled( GiveScoreForPlayerKill )
ScoreEvent_SetupEarnMeterValuesForMixedModes()
SetTimeoutWinnerDecisionFunc( CheckScoreForDraw )
+ SetupGenericTDMChallenge()
}
void function GiveScoreForPlayerKill( entity victim, entity attacker, var damageInfo )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut
index 6b30a399..3ba84394 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut
@@ -2,6 +2,11 @@ global function GamemodeTTDM_Init
const float TTDMIntroLength = 15.0
+struct
+{
+ table< entity, int > challengeCount
+} file
+
void function GamemodeTTDM_Init()
{
Riff_ForceSetSpawnAsTitan( eSpawnAsTitan.Always )
@@ -14,6 +19,8 @@ void function GamemodeTTDM_Init()
ClassicMP_ForceDisableEpilogue( true )
SetTimeoutWinnerDecisionFunc( CheckScoreForDraw )
+ AddCallback_OnClientConnected( SetupPlayerTTDMChallenges ) //Just to make up the Match Goals tracking
+ AddCallback_OnClientDisconnected( RemovePlayerTTDMChallenges ) //Safety removal of data to prevent crashes
AddCallback_OnPlayerKilled( AddTeamScoreForPlayerKilled ) // dont have to track autotitan kills since you cant leave your titan in this mode
// probably needs scoreevent earnmeter values
@@ -56,6 +63,17 @@ void function TTDMIntroShowIntermissionCam( entity player )
thread PlayerWatchesTTDMIntroIntermissionCam( player )
}
+void function SetupPlayerTTDMChallenges( entity player )
+{
+ file.challengeCount[ player ] <- 0
+}
+
+void function RemovePlayerTTDMChallenges( entity player )
+{
+ if( player in file.challengeCount )
+ delete file.challengeCount[ player ]
+}
+
void function PlayerWatchesTTDMIntroIntermissionCam( entity player )
{
player.EndSignal( "OnDestroy" )
@@ -79,6 +97,19 @@ void function AddTeamScoreForPlayerKilled( entity victim, entity attacker, var d
if ( victim == attacker || !victim.IsPlayer() || !attacker.IsPlayer() && GetGameState() == eGameState.Playing )
return
+ if( victim in file.challengeCount )
+ file.challengeCount[victim] = 0
+
+ if( attacker in file.challengeCount )
+ {
+ file.challengeCount[attacker]++
+ if( file.challengeCount[attacker] >= 2 && !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengeTTDM" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
+
AddTeamScore( GetOtherTeam( victim.GetTeam() ), 1 )
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut
index 60bad8d1..4e0d65d4 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut
@@ -32,7 +32,7 @@ void function PrematchClearInventory() // vanilla behavior
{
foreach( entity player in GetPlayerArray() )
{
- PlayerInventory_TakeAllInventoryItems( player )
+ thread PlayerInventory_TakeAllInventoryItems( player )
}
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
index 9288f75e..b77a37b2 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
@@ -43,6 +43,8 @@ void function BaseGametype_Init_MPSP()
AddCallback_OnPlayerKilled( CheckForAutoTitanDeath )
RegisterSignal( "PlayerRespawnStarted" )
RegisterSignal( "KillCamOver" )
+
+ FlagInit( "WeaponDropsAllowed", true )
}
void function SetIntermissionCamera( entity camera )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut
index 466a5042..016097f2 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut
@@ -1,6 +1,276 @@
global function InitChallenges
+global function SetPlayerChallengeEvacState //Hooked in _evac.gnut
+global function SetPlayerChallengeMatchWon //Hooked in _score.nut
+global function SetPlayerChallengeMatchComplete //Hooked in _score.nut
+global function SetPlayerChallengeMeritScore //Up to gamemodes to use this directly if needed
+global function IncrementPlayerChallengeTitanLeveledUp //Hooked in titan_xp.gnut
+global function IncrementPlayerChallengeWeaponLeveledUp //Hooked in weapon_xp.gnut
+global function IncrementPlayerChallengeFactionLeveledUp //Hooked in faction_xp.gnut (invisible but necessary for post-summary menu)
+global function RegisterChallenges_OnMatchEnd //Hooked in _gamestate_mp.gnut
+
+global function HasPlayerCompletedMeritScore //Check from gamemodes to not reapply SetPlayerChallengeMeritScore
+global function SetupGenericTDMChallenge //Used by gamemodes which simply adopts the: "Kill 3 Pilots without dying." Challenge
+global function SetupGenericFFAChallenge //Used by gamemodes which simply adopts the: "Kill 5 Pilots." Challenge
+
+struct
+{
+ table< entity, int > playerTotalMeritCount
+ table< entity, bool > playerChallenge
+ table< entity, int > pilotstreak
+ bool isHappyHourActive
+} file
+
+
+
+
+
+
+/*=============================================================================================================
+ __ __ _ _ ____ _ _ _
+ | \/ | __ _ | |_ ___ | |__ / ___|| |__ __ _ | || | ___ _ __ __ _ ___ ___
+ | |\/| | / _` || __|/ __|| '_ \ | | | '_ \ / _` || || | / _ \| '_ \ / _` | / _ \/ __|
+ | | | || (_| || |_| (__ | | | | | |___ | | | || (_| || || || __/| | | || (_| || __/\__ \
+ |_| |_| \__,_| \__|\___||_| |_| \____||_| |_| \__,_||_||_| \___||_| |_| \__, | \___||___/
+ |___/
+=============================================================================================================*/
void function InitChallenges()
{
+#if (UI && CLIENT)
+
+ SCB_SetCompleteMeritState( 4 )
+ SCB_SetEvacMeritState( 4 )
+ SCB_SetMeritCount( 4 )
+ SCB_SetScoreMeritState( 4 )
+ SCB_SetWinMeritState( 4 )
+ SCB_SetWeaponMeritCount( -1 )
+ SCB_SetTitanMeritCount( -1 )
+
+#elseif (SERVER && MP)
+
+ AddCallback_OnClientConnected( SetupPlayerMenuChallenges )
+ AddCallback_OnClientDisconnected( RemovePlayerFromChallengePool )
+
+#endif
+}
+
+void function SetupPlayerMenuChallenges( entity player )
+{
+ file.playerTotalMeritCount[ player ] <- 0
+ file.pilotstreak[ player ] <- 0
+ file.playerChallenge[ player ] <- false
+
+ thread SetupChallenges_Threaded( player )
+}
+void function SetupChallenges_Threaded( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ WaitFrame()
+
+ Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 4 ) //4 tells RUI to hide it
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetScoreMeritState", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetWinMeritState", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetWeaponMeritCount", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetTitanMeritCount", 0 )
+}
+
+void function SetupGenericTDMChallenge()
+{
+ AddCallback_OnPlayerKilled( TDMChallenges_OnPlayerKilled )
+}
+
+void function SetupGenericFFAChallenge()
+{
+ AddCallback_OnPlayerKilled( FFAChallenges_OnPlayerKilled )
+}
+
+void function RemovePlayerFromChallengePool( entity player )
+{
+ if( player in file.playerChallenge )
+ delete file.playerChallenge[ player ]
+ if( player in file.playerTotalMeritCount )
+ delete file.playerTotalMeritCount[ player ]
+ if( player in file.pilotstreak )
+ delete file.pilotstreak[ player ]
+}
+
+void function RegisterChallenges_OnMatchEnd()
+{
+ bool eliteWarpaintRNG = false
+
+ if( RandomIntRange( 0, 100 ) <= 30 ) //30% Chance to trigger akin to vanilla, apply always since all players have paid cosmetics unlocked
+ eliteWarpaintRNG = true
+
+ foreach( player in GetPlayerArray() )
+ {
+ player.SetPersistentVar( "isPostGameScoreboardValid", true )
+ player.SetPersistentVar( "isFDPostGameScoreboardValid", false ) //FD itself overrides this right after when match ends
+ SetUIVar( level, "showGameSummary", true )
+
+ if( eliteWarpaintRNG )
+ SetPlayerChallengeSquadLeader( player )
+
+ if( ShouldAwardHappyHourBonus( player ) )
+ {
+ AddPlayerScore( player, "HappyHourBonus" )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.HAPPY_HOUR + "]", 5 ) //The XP Given from Happy Hour Score is 5 merits
+ }
+ }
+}
+
+void function TDMChallenges_OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( victim.IsPlayer() )
+ {
+ if( victim in file.pilotstreak )
+ file.pilotstreak[victim] = 0
+ if( attacker in file.pilotstreak )
+ {
+ file.pilotstreak[attacker]++
+ if( file.pilotstreak[attacker] >= 3 && !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengeTDM" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
+ }
+}
+
+void function FFAChallenges_OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( victim.IsPlayer() && attacker in file.pilotstreak )
+ {
+ file.pilotstreak[attacker]++
+ if( file.pilotstreak[attacker] >= 5 && !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengePVPKillCount" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
+}
+
+bool function HasPlayerCompletedMeritScore( entity player )
+{
+ Assert( player in file.playerChallenge, player + " is not registered in the challenge pool hooks." )
+ return file.playerChallenge[ player ]
+}
+
+
+
+
+
+
+
+/*=============================================================================================================
+ ____ _ _ _ _
+ / ___| __ _ _ __ ___ ___ _ __ ___ ___ __| | ___ | | | | ___ ___ | | __ ___
+ | | _ / _` || '_ ` _ \ / _ \| '_ ` _ \ / _ \ / _` | / _ \ | |_| | / _ \ / _ \ | |/ // __|
+ | |_| || (_| || | | | | || __/| | | | | || (_) || (_| || __/ | _ || (_) || (_) || < \__ \
+ \____| \__,_||_| |_| |_| \___||_| |_| |_| \___/ \__,_| \___| |_| |_| \___/ \___/ |_|\_\|___/
+
+=============================================================================================================*/
+
+void function SetPlayerChallengeEvacState( entity player, int successEvac = 0 )
+{
+ if( successEvac == 0 ) //Evac Ship destroyed
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 2 )
+
+ else if( successEvac == 1 ) //Player itself managed to evac
+ {
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 1 )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.EVAC + "]", 1 )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+ }
+
+ else if( successEvac == 2 ) //Team managed to evac
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 3 )
+}
+
+void function SetPlayerChallengeMatchWon( entity player, bool playerWon )
+{
+ if( playerWon )
+ {
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetWinMeritState", 1 )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.MATCH_VICTORY + "]", 1 )
+ player.SetPersistentVar( "matchWin", true )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+ }
+ else
+ Remote_CallFunction_UI( player, "SCB_SetWinMeritState", -1 )
+}
+
+void function SetPlayerChallengeMatchComplete( entity player )
+{
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 1 )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.MATCH_COMPLETED + "]", 1 )
+ player.SetPersistentVar( "matchComplete", true )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+}
+
+void function SetPlayerChallengeSquadLeader( entity player )
+{
+ if( !ProgressionEnabledForPlayer( player ) )
+ return
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderDoubleXP" )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderBonus", player.GetEncodedEHandle() )
+ player.SetPersistentVar( "matchSquadBonus", true )
+ Player_GiveDoubleXP( player, 1 )
+ foreach( entity teamplayer in GetPlayerArrayOfTeam( player.GetTeam() ) )
+ {
+ if( teamplayer == player )
+ continue
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderBonus", teamplayer.GetEncodedEHandle() )
+ }
+}
+
+void function SetPlayerChallengeMeritScore( entity player )
+{
+ if( !HasPlayerCompletedMeritScore( player ) )
+ {
+ file.playerChallenge[ player ] = true
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetScoreMeritState", 1 )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.SCORE_MILESTONE + "]", 1 )
+ player.SetPersistentVar( "matchScoreEvent", true )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+ }
+}
+
+void function IncrementPlayerChallengeTitanLeveledUp( entity player )
+{
+ player.p.meritData.titanMerits++
+ file.playerTotalMeritCount[ player ]++
+
+ Remote_CallFunction_UI( player, "SCB_SetTitanMeritCount", player.p.meritData.titanMerits++ )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+}
+
+void function IncrementPlayerChallengeWeaponLeveledUp( entity player )
+{
+ player.p.meritData.weaponMerits++
+ file.playerTotalMeritCount[ player ]++
+
+ Remote_CallFunction_UI( player, "SCB_SetWeaponMeritCount", player.p.meritData.weaponMerits )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+}
+
+void function IncrementPlayerChallengeFactionLeveledUp( entity player )
+{
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
index 23ae37a1..c3bdf01c 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
@@ -26,18 +26,9 @@ const int MAX_DROPSHIP_PLAYERS = 4
global const float DROPSHIP_INTRO_LENGTH = 15.0 // TODO tweak this
-struct IntroDropship
-{
- entity dropship
-
- int playersInDropship
- entity[MAX_DROPSHIP_PLAYERS] players
-}
-
struct {
- // these used to be IntroDropship[2]s but i wanted to be able to use array.getrandom so they have to be actual arrays
- array<IntroDropship> militiaDropships
- array<IntroDropship> imcDropships
+ table< entity, array<entity> > militiaDropships
+ table< entity, array<entity> > imcDropships
float introStartTime
} file
@@ -52,7 +43,12 @@ void function ClassicMP_DefaultDropshipIntro_Setup()
void function DropshipIntro_OnClientConnected( entity player )
{
if ( GetGameState() == eGameState.Prematch )
- thread SpawnPlayerIntoDropship( player )
+ {
+ if( PlayerCanSpawn( player ) )
+ DoRespawnPlayer( player, null )
+
+ PutPlayerInDropship( player )
+ }
}
void function OnPrematchStart()
@@ -62,11 +58,11 @@ void function OnPrematchStart()
print( "starting dropship intro!" )
file.introStartTime = Time()
- // make 2 empty dropship structs per team
- IntroDropship emptyDropship
+ // Clear Dropship arrays of Teams for Match Restarts (i.e Half-Times)
file.militiaDropships.clear()
file.imcDropships.clear()
+ // Try to gather all possible Dropship spawn points for Team
array<entity> validDropshipSpawns
array<entity> dropshipSpawns = GetEntArrayByClass_Expensive( "info_spawnpoint_dropship_start" )
foreach ( entity dropshipSpawn in dropshipSpawns )
@@ -78,47 +74,47 @@ void function OnPrematchStart()
validDropshipSpawns.append( dropshipSpawn )
}
- // if no dropship spawns for this mode, just allow any dropship spawns
+ // Use any spawn point if not enough valid for this Gamemode exists
if ( validDropshipSpawns.len() < 2 )
validDropshipSpawns = dropshipSpawns
// spawn dropships
foreach ( entity dropshipSpawn in validDropshipSpawns )
{
- // todo: possibly make this only spawn dropships if we've got enough players to need them
int createTeam = HasSwitchedSides() ? GetOtherTeam( dropshipSpawn.GetTeam() ) : dropshipSpawn.GetTeam()
- array<IntroDropship> teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships
+ table< entity, array<entity> > teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships
if ( teamDropships.len() >= 2 )
- continue
+ break
- // create entity
entity dropship = CreateDropship( createTeam, dropshipSpawn.GetOrigin(), dropshipSpawn.GetAngles() )
-
- teamDropships.append( clone emptyDropship )
- teamDropships[ teamDropships.len() - 1 ].dropship = dropship
-
AddAnimEvent( dropship, "dropship_warpout", WarpoutEffect )
+
dropship.SetValueForModelKey( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
- dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+ if ( dropshipSpawn.GetTeam() == TEAM_IMC )
+ dropship.SetValueForModelKey( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" )
DispatchSpawn( dropship )
- // have to do this after dispatch otherwise it won't work for some reason
- // weirdly enough, tf2 actually does use different dropships for imc and militia, despite these concepts not really being a thing for players in tf2
- // probably was just missed by devs, but keeping it in for accuracy
+ dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
if ( dropshipSpawn.GetTeam() == TEAM_IMC )
dropship.SetModel( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" )
- else
- dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+
+ teamDropships[ dropship ] <- [ null, null, null, null ]
thread PlayAnim( dropship, "dropship_classic_mp_flyin" )
}
+ // Populate Dropships
foreach ( entity player in GetPlayerArray() )
{
if ( !IsPrivateMatchSpectator( player ) )
- thread SpawnPlayerIntoDropship( player )
+ {
+ if( PlayerCanSpawn( player ) )
+ DoRespawnPlayer( player, null )
+
+ PutPlayerInDropship( player )
+ }
else
RespawnPrivateMatchSpectator( player )
}
@@ -128,76 +124,79 @@ void function OnPrematchStart()
void function EndIntroWhenFinished()
{
- wait 15.0
+ wait DROPSHIP_INTRO_LENGTH
ClassicMP_OnIntroFinished()
}
-void function SpawnPlayerIntoDropship( entity player )
+void function PutPlayerInDropship( entity player )
{
- player.EndSignal( "OnDestroy" )
+ //Find the player's dropship and seat
+ table< entity, array<entity> > teamDropships
+ if ( player.GetTeam() == TEAM_MILITIA )
+ teamDropships = file.militiaDropships
+ else
+ teamDropships = file.imcDropships
+
+ entity playerDropship
+ array< int > availableShipSlots
+ array< entity > introDropships
+ int playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS )
+ foreach( dropship, playerslot in teamDropships )
+ {
+ introDropships.append( dropship )
+ for ( int i = 0; i < MAX_DROPSHIP_PLAYERS; i++ )
+ {
+ if ( !IsValidPlayer( playerslot[i] ) )
+ availableShipSlots.append( i )
+ }
+
+ if( !availableShipSlots.len() )
+ continue
+
+ int slotPick = availableShipSlots.getrandom()
+ playerslot[slotPick] = player
+ playerDropship = dropship
+ playerDropshipIndex = slotPick
+ break
+ }
+
+ if( !IsAlive( playerDropship ) ) //If we're at this point, we have more players than we do dropships, so just pick a random one
+ playerDropship = introDropships.getrandom()
+
+ thread SpawnPlayerIntoDropship( player, playerDropshipIndex, playerDropship )
+}
- if ( IsAlive( player ) )
- player.Die() // kill them so we don't have any issues respawning them later
+void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, entity playerDropship )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
- player.s.dropshipIntroIsJumping <- false
- OnThreadEnd( function() : ( player )
+ OnThreadEnd( function() : ( player, playerDropshipIndex, playerDropship )
{
if ( IsValid( player ) )
{
player.ClearParent()
ClearPlayerAnimViewEntity( player )
-
- if ( !player.s.dropshipIntroIsJumping )
- {
- player.MovementEnable()
- player.EnableWeaponViewModel()
- RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
- }
+ }
+ if( IsAlive( playerDropship ) )
+ {
+ if ( playerDropship.GetTeam() == TEAM_MILITIA )
+ file.militiaDropships[ playerDropship ][ playerDropshipIndex ] = null
+ else
+ file.imcDropships[ playerDropship ][ playerDropshipIndex ] = null
}
})
- WaitFrame()
-
- player.EndSignal( "OnDeath" )
-
- // find the player's dropship and seat
- array<IntroDropship> teamDropships
- if ( player.GetTeam() == TEAM_MILITIA )
- teamDropships = file.militiaDropships
- else
- teamDropships = file.imcDropships
-
- IntroDropship playerDropship
- int playerDropshipIndex = -1
- foreach ( IntroDropship dropship in teamDropships )
- for ( int i = 0; i < dropship.players.len(); i++ )
- if ( dropship.players[ i ] == null )
- {
- playerDropship = dropship
- playerDropshipIndex = i
-
- dropship.players[ i ] = player
- break
- }
-
- if ( playerDropship.dropship == null )
- {
- // if we're at this point, we have more players than we do dropships, so just pick a random one
- playerDropship = teamDropships.getrandom()
- playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS )
- }
-
- // respawn player and holster their weapons so they aren't out
- if ( !IsAlive( player ) )
- player.RespawnPlayer( null )
- HolsterAndDisableWeapons(player)
+ HolsterAndDisableWeapons( player )
player.DisableWeaponViewModel()
+ UnMuteAll( player )
+ StopSoundOnEntity( player, "Duck_For_FrontierDefenseTitanSelectScreen" )
// hide hud and fade screen out from black
AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
ScreenFadeFromBlack( player, 0.5, 0.5 )
// faction leaders are done clientside, spawn them here
- Remote_CallFunction_NonReplay( player, "ServerCallback_SpawnFactionCommanderInDropship", playerDropship.dropship.GetEncodedEHandle(), file.introStartTime )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SpawnFactionCommanderInDropship", playerDropship.GetEncodedEHandle(), file.introStartTime )
// do firstperson sequence
FirstPersonSequenceStruct idleSequence
@@ -208,9 +207,7 @@ void function SpawnPlayerIntoDropship( entity player )
idleSequence.viewConeFunction = ViewConeRampFree
idleSequence.hideProxy = true
idleSequence.setInitialTime = Time() - file.introStartTime
- thread FirstPersonSequence( idleSequence, player, playerDropship.dropship )
- WaittillAnimDone( player )
-
+ waitthread FirstPersonSequence( idleSequence, player, playerDropship )
// todo: possibly rework this to actually get the time the idle anim takes and start the starttime of the jump sequence for very late joiners using that
// jump sequence
@@ -218,13 +215,17 @@ void function SpawnPlayerIntoDropship( entity player )
jumpSequence.firstPersonAnim = DROPSHIP_JUMP_ANIMS_POV[ playerDropshipIndex ]
jumpSequence.thirdPersonAnim = DROPSHIP_JUMP_ANIMS[ playerDropshipIndex ]
jumpSequence.attachment = "ORIGIN"
+ jumpSequence.viewConeFunction = ViewConeFree
jumpSequence.setInitialTime = max( 0.0, Time() - ( file.introStartTime + 11.0 ) ) // pretty sure you should do this with GetScriptedAnimEventCycleFrac?
// idk unsure how to use that, all i know is getsequenceduration > the length it actually should be
- thread FirstPersonSequence( jumpSequence, player, playerDropship.dropship )
- WaittillAnimDone( player ) // somehow this is better than just waiting for the blocking FirstPersonSequence call?
+ #if BATTLECHATTER_ENABLED
+ if( playerDropshipIndex == 0 )
+ PlayBattleChatterLine( player, "bc_pIntroChat" )
+ #endif
+
+ waitthread FirstPersonSequence( jumpSequence, player, playerDropship )
- player.s.dropshipIntroIsJumping <- true
thread PlayerJumpsFromDropship( player )
}
@@ -240,20 +241,21 @@ void function PlayerJumpsFromDropship( entity player )
// show weapon viewmodel and hud and let them move again
player.MovementEnable()
player.EnableWeaponViewModel()
- DeployAndEnableWeapons(player)
+ DeployAndEnableWeapons( player )
RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
}
})
-
- // wait for intro timer to be fully done
- wait ( file.introStartTime + DROPSHIP_INTRO_LENGTH ) - Time()
- player.MovementDisable() // disable all movement but let them look around still
- player.ConsumeDoubleJump() // movementdisable doesn't prevent double jumps
// wait for player to hit the ground
- wait 0.1 // assume players will never actually hit ground before this
+ player.ClearParent()
+ WaitFrame()
+ player.SetVelocity( < 0, 0, -100 > ) // Toss players a bit down so it makes a smoother transition when jumping off the Dropship
+ player.MovementDisable() // Disable all movement but let them look around still
+ player.ConsumeDoubleJump() // MovementDisable doesn't prevent double jumps
+ WaitFrame()
while ( !player.IsOnGround() && !player.IsWallRunning() && !player.IsWallHanging() ) // todo this needs tweaking
WaitFrame()
- TryGameModeAnnouncement( player )
+ if ( GetRoundsPlayed() == 0 ) //Intro is announced only for the first round in Vanilla as certain gamemodes have different announcements for rounds restarts
+ TryGameModeAnnouncement( player )
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
index ff281d6e..0d1b42b7 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
@@ -21,6 +21,8 @@ global function SetTitanMeterGainScale
#if MP
global function CodeCallback_OnServerAnimEvent
+global function CodeCallback_WeaponDropped
+global function AddCallback_OnWeaponDropped
#endif
struct AccumulatedDamageData
@@ -43,6 +45,7 @@ struct
]
table<entity, AccumulatedDamageData> playerAccumulatedDamageData
+ array< void functionref( entity ) > weaponDroppedCallbacks
} file
void function CodeCallback_Init()
@@ -1030,4 +1033,26 @@ void function CodeCallback_OnServerAnimEvent( entity ent, string eventName )
PerfEnd( PerfIndexServer.CB_OnServerAnimEvent )
}
+
+void function AddCallback_OnWeaponDropped( void functionref( entity ) callback )
+{
+ file.weaponDroppedCallbacks.append( callback )
+}
+
+void function CodeCallback_WeaponDropped( entity weapon )
+{
+ // shamelessly taken form SP
+ if ( !IsValid( weapon ) )
+ return
+
+ // Might look a bit hacky to put it here, but thats how SP does it
+ if ( !Flag( "WeaponDropsAllowed" ) )
+ {
+ weapon.Destroy()
+ return
+ }
+
+ foreach( callback in file.weaponDroppedCallbacks )
+ callback( weapon )
+}
#endif // #if MP \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
index 4c52a9bf..0c66f5a9 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
@@ -59,18 +59,21 @@ struct {
bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) shouldTryUseProjectileReplayCallback
} file
-void function SetCallback_TryUseProjectileReplay( bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) callback )
-{
- file.shouldTryUseProjectileReplayCallback = callback
-}
-bool function ShouldTryUseProjectileReplay( entity victim, entity attacker, var damageInfo, bool isRoundEnd )
-{
- if ( file.shouldTryUseProjectileReplayCallback != null )
- return file.shouldTryUseProjectileReplayCallback( victim, attacker, damageInfo, isRoundEnd )
- // default to true (vanilla behaviour)
- return true
-}
+
+
+
+
+
+
+
+/*
+ ██████ █████ ███ ███ ███████ ███████ ████████ █████ ████████ ███████ ███████
+██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██
+██ ███ ███████ ██ ████ ██ █████ ███████ ██ ███████ ██ █████ ███████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ ██████ ██ ██ ██ ██ ███████ ███████ ██ ██ ██ ██ ███████ ███████
+*/
void function PIN_GameStart()
{
@@ -96,10 +99,66 @@ void function PIN_GameStart()
AddCallback_OnPlayerKilled( OnPlayerKilled )
AddDeathCallback( "npc_titan", OnTitanKilled )
+ AddCallback_EntityChangedTeam( "player", OnPlayerChangedTeam )
RegisterSignal( "CleanUpEntitiesForRoundEnd" )
}
+void function GameState_EntitiesDidLoad()
+{
+ if ( GetClassicMPMode() || ClassicMP_ShouldTryIntroAndEpilogueWithoutClassicMP() )
+ ClassicMP_SetupIntro()
+}
+
+void function WaittillGameStateOrHigher( int gameState )
+{
+ while ( GetGameState() < gameState )
+ svGlobal.levelEnt.WaitSignal( "GameStateChanged" )
+}
+
+bool function ShouldTryUseProjectileReplay( entity victim, entity attacker, var damageInfo, bool isRoundEnd )
+{
+ if ( file.shouldTryUseProjectileReplayCallback != null )
+ return file.shouldTryUseProjectileReplayCallback( victim, attacker, damageInfo, isRoundEnd )
+ // default to true (vanilla behaviour)
+ return true
+}
+
+/// This is to move all NPCs that a player owns from one team to the other during a match
+/// Auto-Titans, Turrets, Ticks and Hacked Spectres will all move along together with the player to the new Team
+/// Also possibly prevents mods that spawns other types of NPCs that players can own from breaking when switching (i.e Drones, Hacked Reapers)
+void function OnPlayerChangedTeam( entity player )
+{
+ if ( !player.hasConnected ) // Prevents players who just joined to trigger below code, as server always pre setups their teams
+ return
+
+ if( IsIMCOrMilitiaTeam( player.GetTeam() ) )
+ NotifyClientsOfTeamChange( player, GetOtherTeam( player.GetTeam() ), player.GetTeam() )
+
+ foreach( npc in GetNPCArray() )
+ {
+ entity bossPlayer = npc.GetBossPlayer()
+ if ( IsValidPlayer( bossPlayer ) && bossPlayer == player && IsAlive( npc ) )
+ SetTeam( npc, player.GetTeam() )
+ }
+}
+
+
+
+
+
+
+
+
+
+/*
+ ██████ █████ ███ ███ ███████ ███████ ███████ ████████ ██ ██ ██████
+██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██
+██ ███ ███████ ██ ████ ██ █████ ███████ █████ ██ ██ ██ ██████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ ██████ ██ ██ ██ ██ ███████ ███████ ███████ ██ ██████ ██
+*/
+
void function SetGameState( int newState )
{
if ( newState == GetGameState() )
@@ -114,23 +173,166 @@ void function SetGameState( int newState )
callbackFunc()
}
-void function GameState_EntitiesDidLoad()
+void function AddTeamScore( int team, int amount )
{
- if ( GetClassicMPMode() || ClassicMP_ShouldTryIntroAndEpilogueWithoutClassicMP() )
- ClassicMP_SetupIntro()
+ GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + amount )
+ GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + amount )
+
+ int scoreLimit
+ if ( IsRoundBased() )
+ scoreLimit = GameMode_GetRoundScoreLimit( GAMETYPE )
+ else
+ scoreLimit = GameMode_GetScoreLimit( GAMETYPE )
+
+ int score = GameRules_GetTeamScore( team )
+ if ( score >= scoreLimit || GetGameState() == eGameState.SuddenDeath )
+ SetWinner( team )
+ else if ( ( file.switchSidesBased && !file.hasSwitchedSides ) && score >= ( scoreLimit.tofloat() / 2.0 ) )
+ SetGameState( eGameState.SwitchingSides )
}
-void function WaittillGameStateOrHigher( int gameState )
+void function SetWinner( int team, string winningReason = "", string losingReason = "" )
+{
+ SetServerVar( "winningTeam", team )
+
+ file.gameWonThisFrame = true
+ thread UpdateGameWonThisFrameNextFrame()
+
+ if ( winningReason.len() == 0 )
+ file.announceRoundWinnerWinningSubstr = 0
+ else
+ file.announceRoundWinnerWinningSubstr = GetStringID( winningReason )
+
+ if ( losingReason.len() == 0 )
+ file.announceRoundWinnerLosingSubstr = 0
+ else
+ file.announceRoundWinnerLosingSubstr = GetStringID( losingReason )
+
+ if ( GamePlayingOrSuddenDeath() )
+ {
+ if ( IsRoundBased() )
+ {
+ if ( team != TEAM_UNASSIGNED )
+ {
+ GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + 1 )
+ GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + 1 )
+ }
+
+ SetGameState( eGameState.WinnerDetermined )
+ ScoreEvent_RoundComplete( team )
+ }
+ else
+ {
+ SetGameState( eGameState.WinnerDetermined )
+ ScoreEvent_MatchComplete( team )
+
+ array<entity> players = GetPlayerArray()
+ int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( GAMETYPE )
+ if ( compareFunc != null )
+ {
+ players.sort( compareFunc )
+ int playerCount = players.len()
+ int currentPlace = 1
+ for ( int i = 0; i < 3; i++ )
+ {
+ if ( i >= playerCount )
+ continue
+
+ if ( i > 0 && compareFunc( players[i - 1], players[i] ) != 0 )
+ currentPlace += 1
+
+ switch( currentPlace )
+ {
+ case 1:
+ UpdatePlayerStat( players[i], "game_stats", "mvp" )
+ UpdatePlayerStat( players[i], "game_stats", "mvp_total" )
+ UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" )
+ break
+ case 2:
+ UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" )
+ break
+ case 3:
+ UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" )
+ break
+ }
+ }
+ }
+ }
+ }
+}
+
+void function SetTimeoutWinnerDecisionFunc( int functionref() callback )
{
- while ( GetGameState() < gameState )
- svGlobal.levelEnt.WaitSignal( "GameStateChanged" )
+ file.timeoutWinnerDecisionFunc = callback
+}
+
+void function SetCallback_TryUseProjectileReplay( bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) callback )
+{
+ file.shouldTryUseProjectileReplayCallback = callback
+}
+
+void function AddCallback_OnRoundEndCleanup( void functionref() callback )
+{
+ file.roundEndCleanupCallbacks.append( callback )
+}
+
+void function SetShouldUsePickLoadoutScreen( bool shouldUse )
+{
+ file.usePickLoadoutScreen = shouldUse
+}
+
+void function SetSwitchSidesBased( bool switchSides )
+{
+ file.switchSidesBased = switchSides
+}
+
+void function SetSuddenDeathBased( bool suddenDeathBased )
+{
+ file.suddenDeathBased = suddenDeathBased
}
+void function SetTimerBased( bool timerBased )
+{
+ file.timerBased = timerBased
+}
-// logic for individual gamestates:
+void function SetShouldUseRoundWinningKillReplay( bool shouldUse )
+{
+ SetServerVar( "roundWinningKillReplayEnabled", shouldUse )
+}
+void function SetRoundWinningKillReplayKillClasses( bool pilot, bool titan )
+{
+ file.roundWinningKillReplayTrackPilotKills = pilot
+ file.roundWinningKillReplayTrackTitanKills = titan // player kills in titans should get tracked anyway, might be worth renaming this
+}
+
+void function SetRoundWinningKillReplayAttacker( entity attacker, int inflictorEHandle = -1 )
+{
+ file.roundWinningKillReplayTime = Time()
+ file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker )
+ file.roundWinningKillReplayAttacker = attacker
+ file.roundWinningKillReplayInflictorEHandle = inflictorEHandle == -1 ? attacker.GetEncodedEHandle() : inflictorEHandle
+ file.roundWinningKillReplayTimeOfDeath = Time()
+}
+
+
+
+
+
+
+
+
+
+
+/*
+ ██████ ██ ██ ███████ ████████ ██████ ███ ███ ███████ ████████ █████ ██████ ████████
+██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██
+██ ██ ██ ███████ ██ ██ ██ ██ ████ ██ ███████ ██ ███████ ██████ ██
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ ██████ ██████ ███████ ██ ██████ ██ ██ ███████ ██ ██ ██ ██ ██ ██
+*/
-// eGameState.WaitingForCustomStart
void function GameStateEnter_WaitingForCustomStart()
{
// unused in release, comments indicate this was supposed to be used for an e3 demo
@@ -138,7 +340,22 @@ void function GameStateEnter_WaitingForCustomStart()
}
-// eGameState.WaitingForPlayers
+
+
+
+
+
+
+
+
+/*
+██ ██ █████ ██ ████████ ██ ███ ██ ██████ ███████ ██████ ██████ ██████ ██ █████ ██ ██ ███████ ██████ ███████
+██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ █ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ███ █████ ██ ██ ██████ ██████ ██ ███████ ████ █████ ██████ ███████
+██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ ███ ███ ██ ██ ██ ██ ██ ██ ████ ██████ ██ ██████ ██ ██ ██ ███████ ██ ██ ██ ███████ ██ ██ ███████
+*/
+
void function GameStateEnter_WaitingForPlayers()
{
foreach ( entity player in GetPlayerArray() )
@@ -170,7 +387,22 @@ void function WaitingForPlayers_ClientConnected( entity player )
ScreenFadeToBlackForever( player, 0.0 )
}
-// eGameState.PickLoadout
+
+
+
+
+
+
+
+
+/*
+██████ ██ ██████ ██ ██ ██ ██████ █████ ██████ ██████ ██ ██ ████████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██████ ██ ██ █████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ ██ ██████ ██ ██ ███████ ██████ ██ ██ ██████ ██████ ██████ ██
+*/
+
void function GameStateEnter_PickLoadout()
{
thread GameStateEnter_PickLoadout_Threaded()
@@ -189,7 +421,22 @@ void function GameStateEnter_PickLoadout_Threaded()
}
-// eGameState.Prematch
+
+
+
+
+
+
+
+
+/*
+██████ ██████ ███████ ███ ███ █████ ████████ ██████ ██ ██
+██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██
+██████ ██████ █████ ██ ████ ██ ███████ ██ ██ ███████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ ██ ██ ███████ ██ ██ ██ ██ ██ ██████ ██ ██
+*/
+
void function GameStateEnter_Prematch()
{
int timeLimit = GameMode_GetTimeLimit( GAMETYPE ) * 60
@@ -201,6 +448,14 @@ void function GameStateEnter_Prematch()
if ( !GetClassicMPMode() && !ClassicMP_ShouldTryIntroAndEpilogueWithoutClassicMP() )
thread StartGameWithoutClassicMP()
+
+ // Initialise any spectators. Hopefully they are all initialised already in CodeCallback_OnClientConnectionCompleted
+ // (_base_gametype_mp.gnut) but for modes like LTS this doesn't seem to happen late enough to work properly.
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( IsPrivateMatchSpectator( player ) )
+ InitialisePrivateMatchSpectatorPlayer( player )
+ }
}
void function StartGameWithoutClassicMP()
@@ -227,7 +482,22 @@ void function StartGameWithoutClassicMP()
}
-// eGameState.Playing
+
+
+
+
+
+
+
+
+/*
+██████ ██ █████ ██ ██ ██ ███ ██ ██████
+██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
+██████ ██ ███████ ████ ██ ██ ██ ██ ██ ███
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ ███████ ██ ██ ██ ██ ██ ████ ██████
+*/
+
void function GameStateEnter_Playing()
{
thread GameStateEnter_Playing_Threaded()
@@ -270,7 +540,22 @@ void function GameStateEnter_Playing_Threaded()
}
-// eGameState.WinnerDetermined
+
+
+
+
+
+
+
+
+/*
+██ ██ ██ ███ ██ ███ ██ ███████ ██████ ██████ ███████ ████████ ███████ ██████ ███ ███ ██ ███ ██ ███████ ██████
+██ ██ ██ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██
+██ █ ██ ██ ██ ██ ██ ██ ██ ██ █████ ██████ ██ ██ █████ ██ █████ ██████ ██ ████ ██ ██ ██ ██ ██ █████ ██ ██
+██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ ███ ███ ██ ██ ████ ██ ████ ███████ ██ ██ ██████ ███████ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ███████ ██████
+*/
+
// these are likely innacurate
const float ROUND_END_FADE_KILLREPLAY = 1.0
const float ROUND_END_DELAY_KILLREPLAY = 3.0
@@ -387,6 +672,7 @@ void function GameStateEnter_WinnerDetermined_Threaded()
}
else
{
+ RegisterChallenges_OnMatchEnd()
if ( ClassicMP_ShouldRunEpilogue() )
{
ClassicMP_SetupEpilogue()
@@ -436,7 +722,22 @@ void function PlayerWatchesRoundWinningKillReplay( entity player, float replayLe
}
-// eGameState.SwitchingSides
+
+
+
+
+
+
+
+
+/*
+███████ ██ ██ ██ ████████ ██████ ██ ██ ██ ███ ██ ██████ ███████ ██ ██████ ███████ ███████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██
+███████ ██ █ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ███ ███████ ██ ██ ██ █████ ███████
+ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+███████ ███ ███ ██ ██ ██████ ██ ██ ██ ██ ████ ██████ ███████ ██ ██████ ███████ ███████
+*/
+
void function GameStateEnter_SwitchingSides()
{
thread GameStateEnter_SwitchingSides_Threaded()
@@ -527,7 +828,22 @@ void function PlayerWatchesSwitchingSidesKillReplay( entity player, bool doRepla
}
-// eGameState.SuddenDeath
+
+
+
+
+
+
+
+
+/*
+███████ ██ ██ ██████ ██████ ███████ ███ ██ ██████ ███████ █████ ████████ ██ ██
+██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██
+███████ ██ ██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ██ █████ ███████ ██ ███████
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+███████ ██████ ██████ ██████ ███████ ██ ████ ██████ ███████ ██ ██ ██ ██ ██
+*/
+
void function GameStateEnter_SuddenDeath()
{
// disable respawns, suddendeath calling is done on a kill callback
@@ -549,7 +865,22 @@ void function GameStateEnter_SuddenDeath()
}
-// eGameState.Postmatch
+
+
+
+
+
+
+
+
+/*
+██████ ██████ ███████ ████████ ███ ███ █████ ████████ ██████ ██ ██
+██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██
+██████ ██ ██ ███████ ██ ██ ████ ██ ███████ ██ ██ ███████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ ██████ ███████ ██ ██ ██ ██ ██ ██ ██████ ██ ██
+*/
+
void function GameStateEnter_Postmatch()
{
foreach ( entity player in GetPlayerArray() )
@@ -582,7 +913,21 @@ void function ForceFadeToBlack( entity player )
}
-// shared across multiple gamestates
+
+
+
+
+
+
+
+
+/*
+██ ██ ██ ██ ██ ██████ █████ ██ ██ ██████ █████ ██████ ██ ██ ███████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+█████ ██ ██ ██ ██ ███████ ██ ██ ██████ ███████ ██ █████ ███████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ ██ ██ ███████ ███████ ██████ ██ ██ ███████ ███████ ██████ ██ ██ ██████ ██ ██ ███████
+*/
void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
{
@@ -712,10 +1057,22 @@ void function OnTitanKilled( entity victim, var damageInfo )
}
}
-void function AddCallback_OnRoundEndCleanup( void functionref() callback )
-{
- file.roundEndCleanupCallbacks.append( callback )
-}
+
+
+
+
+
+
+
+
+
+/*
+████████ ██████ ██████ ██ ███████ ██ ██ ███ ██ ██████ ████████ ██ ██████ ███ ██ ███████
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██
+ ██ ██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ ██ ██████ ██████ ███████ ██ ██████ ██ ████ ██████ ██ ██ ██████ ██ ████ ███████
+*/
void function CleanUpEntitiesForRoundEnd()
{
@@ -753,86 +1110,6 @@ void function CleanUpEntitiesForRoundEnd()
SetPlayerDeathsHidden( false )
}
-
-
-// stuff for gamemodes to call
-
-void function SetShouldUsePickLoadoutScreen( bool shouldUse )
-{
- file.usePickLoadoutScreen = shouldUse
-}
-
-void function SetSwitchSidesBased( bool switchSides )
-{
- file.switchSidesBased = switchSides
-}
-
-void function SetSuddenDeathBased( bool suddenDeathBased )
-{
- file.suddenDeathBased = suddenDeathBased
-}
-
-void function SetTimerBased( bool timerBased )
-{
- file.timerBased = timerBased
-}
-
-void function SetShouldUseRoundWinningKillReplay( bool shouldUse )
-{
- SetServerVar( "roundWinningKillReplayEnabled", shouldUse )
-}
-
-void function SetRoundWinningKillReplayKillClasses( bool pilot, bool titan )
-{
- file.roundWinningKillReplayTrackPilotKills = pilot
- file.roundWinningKillReplayTrackTitanKills = titan // player kills in titans should get tracked anyway, might be worth renaming this
-}
-
-void function SetRoundWinningKillReplayAttacker( entity attacker, int inflictorEHandle = -1 )
-{
- file.roundWinningKillReplayTime = Time()
- file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker )
- file.roundWinningKillReplayAttacker = attacker
- file.roundWinningKillReplayInflictorEHandle = inflictorEHandle == -1 ? attacker.GetEncodedEHandle() : inflictorEHandle
- file.roundWinningKillReplayTimeOfDeath = Time()
-}
-
-void function SetWinner( int team, string winningReason = "", string losingReason = "" )
-{
- SetServerVar( "winningTeam", team )
-
- file.gameWonThisFrame = true
- thread UpdateGameWonThisFrameNextFrame()
-
- if ( winningReason.len() == 0 )
- file.announceRoundWinnerWinningSubstr = 0
- else
- file.announceRoundWinnerWinningSubstr = GetStringID( winningReason )
-
- if ( losingReason.len() == 0 )
- file.announceRoundWinnerLosingSubstr = 0
- else
- file.announceRoundWinnerLosingSubstr = GetStringID( losingReason )
-
- if ( GamePlayingOrSuddenDeath() )
- {
- if ( IsRoundBased() )
- {
- if ( team != TEAM_UNASSIGNED )
- {
- GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + 1 )
- GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + 1 )
- }
-
- SetGameState( eGameState.WinnerDetermined )
- }
- else
- SetGameState( eGameState.WinnerDetermined )
-
- ScoreEvent_MatchComplete( team )
- }
-}
-
void function UpdateGameWonThisFrameNextFrame()
{
WaitFrame()
@@ -840,29 +1117,6 @@ void function UpdateGameWonThisFrameNextFrame()
file.hasKillForGameWonThisFrame = false
}
-void function AddTeamScore( int team, int amount )
-{
- GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + amount )
- GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + amount )
-
- int scoreLimit
- if ( IsRoundBased() )
- scoreLimit = GameMode_GetRoundScoreLimit( GAMETYPE )
- else
- scoreLimit = GameMode_GetScoreLimit( GAMETYPE )
-
- int score = GameRules_GetTeamScore( team )
- if ( score >= scoreLimit || GetGameState() == eGameState.SuddenDeath )
- SetWinner( team )
- else if ( ( file.switchSidesBased && !file.hasSwitchedSides ) && score >= ( scoreLimit.tofloat() / 2.0 ) )
- SetGameState( eGameState.SwitchingSides )
-}
-
-void function SetTimeoutWinnerDecisionFunc( int functionref() callback )
-{
- file.timeoutWinnerDecisionFunc = callback
-}
-
int function GetWinningTeamWithFFASupport()
{
if ( !IsFFAGame() )
@@ -892,8 +1146,6 @@ int function GetWinningTeamWithFFASupport()
unreachable
}
-// idk
-
float function GameState_GetTimeLimitOverride()
{
return 100
@@ -923,8 +1175,6 @@ float function GetTimeLimit_ForGameMode()
return GetCurrentPlaylistVarFloat( playlistString, 10 )
}
-// faction dialogue
-
void function DialoguePlayNormal()
{
int totalScore = GameMode_GetScoreLimit( GameRules_GetGameMode() )
@@ -1008,4 +1258,4 @@ void function DialoguePlayWinnerDetermined()
PlayFactionDialogueToTeam( "scoring_won", winningTeam )
PlayFactionDialogueToTeam( "scoring_lost", losingTeam )
}
-}
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut
index be20982d..2a4c4282 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut
@@ -8,6 +8,7 @@ global function ScoreEvent_TitanDoomed
global function ScoreEvent_TitanKilled
global function ScoreEvent_NPCKilled
global function ScoreEvent_MatchComplete
+global function ScoreEvent_RoundComplete
global function ScoreEvent_SetEarnMeterValues
global function ScoreEvent_SetupEarnMeterValuesForMixedModes
@@ -287,8 +288,24 @@ void function ScoreEvent_MatchComplete( int winningTeam )
foreach( entity player in GetPlayerArray() )
{
AddPlayerScore( player, "MatchComplete" )
+ SetPlayerChallengeMatchComplete( player )
if ( player.GetTeam() == winningTeam )
+ {
AddPlayerScore( player, "MatchVictory" )
+ SetPlayerChallengeMatchWon( player, true )
+ }
+ else
+ SetPlayerChallengeMatchWon( player, false )
+ }
+}
+
+void function ScoreEvent_RoundComplete( int winningTeam )
+{
+ foreach( entity player in GetPlayerArray() )
+ {
+ AddPlayerScore( player, "RoundComplete" )
+ if ( player.GetTeam() == winningTeam )
+ AddPlayerScore( player, "RoundVictory" )
}
}
@@ -304,7 +321,7 @@ void function ScoreEvent_SetupEarnMeterValuesForMixedModes() // mixed modes in t
{
// todo needs earn/overdrive values
// player-controlled stuff
- ScoreEvent_SetEarnMeterValues( "KillPilot", 0.07, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "KillPilot", 0.07, 0.15, 0.33 ) // 5% for titan cores
ScoreEvent_SetEarnMeterValues( "KillTitan", 0.0, 0.15 )
ScoreEvent_SetEarnMeterValues( "TitanKillTitan", 0.0, 0.0 ) // unsure
ScoreEvent_SetEarnMeterValues( "PilotBatteryStolen", 0.0, 0.35 ) // this actually just doesn't have overdrive in vanilla even
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut
index bd64e4ca..74a9088b 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut
@@ -334,6 +334,10 @@ void function OnPlayerOrNPCKilled( entity victim, entity attacker, var damageInf
thread SetLastPosForDistanceStatValid_Threaded( victim, false )
HandleDeathStats( victim, attacker, damageInfo )
+
+ if( victim == attacker ) //Suicides are registering stats, afaik vanilla ignores them
+ return
+
HandleKillStats( victim, attacker, damageInfo )
HandleWeaponKillStats( victim, attacker, damageInfo )
HandleTitanStats( victim, attacker, damageInfo )
@@ -489,23 +493,32 @@ void function HandleKillStats( entity victim, entity attacker, var damageInfo )
// get the player and it's pet titan
entity player
entity playerPetTitan
- if ( attacker.IsPlayer() )
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+
+ if ( IsValid( inflictor ) )
{
- // the player is just the attacker
- player = attacker
- playerPetTitan = player.GetPetTitan()
+ if ( inflictor.IsProjectile() && IsValid( inflictor.GetOwner() ) ) // Attackers are always the final entity in the owning hierarchy, projectile owners though migh be a player's NPC minion (i.e Auto-Titans)
+ attacker = inflictor.GetOwner()
+
+ else if ( inflictor.IsNPC() ) // NPCs are bypassed as Attackers if they are owned by players, instead they become just inflictors
+ attacker = inflictor
}
- else if ( attacker.IsTitan() && IsPetTitan( attacker ) )
+
+ if ( attacker.IsNPC() )
{
- // the attacker is the player's auto titan
+ if ( !attacker.IsTitan() ) // Normal NPCs case
+ return
+
+ if ( !IsPetTitan( attacker ) ) // NPC Titans case
+ return
+
player = attacker.GetTitanSoul().GetBossPlayer()
playerPetTitan = attacker
}
+ else if ( attacker.IsPlayer() ) // Still checks this because worldspawn might be the attacker
+ player = attacker
else
- {
- // attacker could be something like an NPC, or worldspawn
return
- }
// check things once, for performance
int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
@@ -931,6 +944,9 @@ void function HandleDistanceAndTimeStats_Threaded()
// track distance stats
foreach ( entity player in GetPlayerArray() )
{
+ if ( !IsValid( player ) )
+ continue
+
if ( player.p.lastPosForDistanceStatValid )
{
// not 100% sure on using Distance2D over Distance tbh
@@ -1035,7 +1051,10 @@ void function SaveStatsPeriodically_Threaded()
while( true )
{
foreach( entity player in GetPlayerArray() )
- Stats_SaveAllStats( player )
+ {
+ if ( IsValid( player ) )
+ Stats_SaveAllStats( player )
+ }
wait 5
}
}
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 d0ce4f79..49720bf4 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
@@ -236,6 +236,8 @@ void function OnPrematchStart()
void function PlayerWatchesWargamesIntro( entity player )
{
+ player.EndSignal( "OnDestroy" )
+
if ( IsAlive( player ) )
player.Die()
@@ -258,8 +260,6 @@ void function PlayerWatchesWargamesIntro( entity player )
// we need to wait a frame if we killed ourselves to spawn into this, so just easier to do it all the time to remove any weirdness
WaitFrame()
-
- player.EndSignal( "OnDestroy" )
player.EndSignal( "OnDeath" )
int factionTeam = ConvertPlayerFactionToIMCOrMilitiaTeam( player )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
index 4956375b..c47552b3 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
@@ -1,27 +1,30 @@
untyped
-global function InitRatings // temp for testing
-
global function Spawn_Init
-global function SetRespawnsEnabled
-global function RespawnsEnabled
+global function FindSpawnPoint
+
global function SetSpawnpointGamemodeOverride
global function GetSpawnpointGamemodeOverride
global function AddSpawnpointValidationRule
+
+global function SetRespawnsEnabled
+global function RespawnsEnabled
global function CreateNoSpawnArea
global function DeleteNoSpawnArea
-
-global function FindSpawnPoint
+global function SpawnPointInNoSpawnArea
global function RateSpawnpoints_Generic
global function RateSpawnpoints_Frontline
-
-global function SetSpawnZoneRatingFunc
-global function SetShouldCreateMinimapSpawnZones
-global function CreateTeamSpawnZoneEntity
global function RateSpawnpoints_SpawnZones
global function DecideSpawnZone_Generic
-global function DecideSpawnZone_CTF
+
+global struct spawnZoneProperties{
+ int controllingTeam = TEAM_UNASSIGNED
+ entity minimapEnt = null
+ float zoneRating = 0.0
+}
+
+global table< entity, spawnZoneProperties > mapSpawnZones // Global so other scripts can access this for custom ratings if needed
struct NoSpawnArea
{
@@ -35,30 +38,61 @@ struct NoSpawnArea
struct {
bool respawnsEnabled = true
+ array<NoSpawnArea> noSpawnAreas
string spawnpointGamemodeOverride
array< bool functionref( entity, int ) > customSpawnpointValidationRules
-
- table<string, NoSpawnArea> noSpawnAreas
+ bool shouldCreateMinimapSpawnzones
} file
+
+
+
+
+
+
+
+
+
+
+/*
+██████ █████ ███████ ███████ ███████ ██ ██ ███ ██ ██████ ████████ ██ ██████ ███ ██ ███████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██
+██████ ███████ ███████ █████ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██████ ██ ██ ███████ ███████ ██ ██████ ██ ████ ██████ ██ ██ ██████ ██ ████ ███████
+*/
+
void function Spawn_Init()
-{
+{
+ // callbacks for generic spawns
AddSpawnCallback( "info_spawnpoint_human", InitSpawnpoint )
- AddSpawnCallback( "info_spawnpoint_human_start", InitSpawnpoint )
AddSpawnCallback( "info_spawnpoint_titan", InitSpawnpoint )
+ AddSpawnCallback( "info_spawnpoint_droppod", InitSpawnpoint )
+ AddSpawnCallback( "info_spawnpoint_dropship", InitSpawnpoint )
+ AddSpawnCallback( "info_spawnpoint_human_start", InitSpawnpoint )
AddSpawnCallback( "info_spawnpoint_titan_start", InitSpawnpoint )
-
- // callbacks for generic spawns
- AddCallback_EntitiesDidLoad( InitPreferSpawnNodes )
+ AddSpawnCallback( "info_spawnpoint_droppod_start", InitSpawnpoint )
+ AddSpawnCallback( "info_spawnpoint_dropship_start", InitSpawnpoint )
// callbacks for spawnzone spawns
AddCallback_GameStateEnter( eGameState.Prematch, ResetSpawnzones )
AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", AddSpawnZoneTrigger )
-}
-
-void function InitSpawnpoint( entity spawnpoint )
-{
- spawnpoint.s.lastUsedTime <- -999
+
+ float friendlyAIValue = 1.75
+ if ( GameModeHasCapturePoints() )
+ friendlyAIValue = 0.75
+
+ SpawnPoints_SetRatingMultipliers_Enemy( TD_TITAN, -10.0, -6.0, -1.0 )
+ SpawnPoints_SetRatingMultipliers_Enemy( TD_PILOT, -10.0, -6.0, -1.0 )
+ SpawnPoints_SetRatingMultipliers_Enemy( TD_AI, -2.0, -0.25, 0.0 )
+
+ SpawnPoints_SetRatingMultipliers_Friendly( TD_TITAN, 0.25, 1.75, friendlyAIValue )
+ SpawnPoints_SetRatingMultipliers_Friendly( TD_PILOT, 0.25, 1.75, friendlyAIValue )
+ SpawnPoints_SetRatingMultipliers_Friendly( TD_AI, 0.5, 0.25, 0.0 )
+
+ SpawnPoints_SetRatingMultiplier_PetTitan( 2.0 )
+
+ file.shouldCreateMinimapSpawnzones = GetCurrentPlaylistVarInt( "spawn_zone_enabled", 1 ) != 0
}
void function SetRespawnsEnabled( bool enabled )
@@ -71,9 +105,10 @@ bool function RespawnsEnabled()
return file.respawnsEnabled
}
-void function AddSpawnpointValidationRule( bool functionref( entity spawn, int team ) rule )
+void function InitSpawnpoint( entity spawnpoint )
{
- file.customSpawnpointValidationRules.append( rule )
+ spawnpoint.s.lastUsedTime <- -999
+ spawnpoint.s.inUse <- false
}
string function CreateNoSpawnArea( int blockSpecificTeam, int blockEnemiesOfTeam, vector position, float lifetime, float radius )
@@ -85,11 +120,12 @@ string function CreateNoSpawnArea( int blockSpecificTeam, int blockEnemiesOfTeam
noSpawnArea.lifetime = lifetime
noSpawnArea.radius = radius
- // generate an id
noSpawnArea.id = UniqueString( "noSpawnArea" )
- thread NoSpawnAreaLifetime( noSpawnArea )
+ if ( lifetime > 0 )
+ thread NoSpawnAreaLifetime( noSpawnArea )
+ file.noSpawnAreas.append( noSpawnArea )
return noSpawnArea.id
}
@@ -101,8 +137,41 @@ void function NoSpawnAreaLifetime( NoSpawnArea noSpawnArea )
void function DeleteNoSpawnArea( string noSpawnIdx )
{
- if ( noSpawnIdx in file.noSpawnAreas )
- delete file.noSpawnAreas[ noSpawnIdx ]
+ foreach ( noSpawnArea in file.noSpawnAreas )
+ {
+ if ( noSpawnArea.id == noSpawnIdx )
+ file.noSpawnAreas.removebyvalue( noSpawnArea )
+ }
+}
+
+bool function SpawnPointInNoSpawnArea( vector vec, int team )
+{
+ foreach ( noSpawnArea in file.noSpawnAreas )
+ {
+ if ( Distance( noSpawnArea.position, vec ) < noSpawnArea.radius )
+ {
+ if ( noSpawnArea.blockedTeam != TEAM_INVALID && noSpawnArea.blockedTeam == team )
+ return true
+
+ if ( noSpawnArea.blockOtherTeams != TEAM_INVALID && noSpawnArea.blockOtherTeams != team )
+ return true
+ }
+ }
+
+ return false
+}
+
+bool function IsSpawnpointValidDrop( entity spawnpoint, int team )
+{
+ if ( spawnpoint.IsOccupied() || spawnpoint.s.inUse )
+ return false
+
+ return true
+}
+
+void function AddSpawnpointValidationRule( bool functionref( entity spawn, int team ) rule )
+{
+ file.customSpawnpointValidationRules.append( rule )
}
void function SetSpawnpointGamemodeOverride( string gamemode )
@@ -114,35 +183,38 @@ string function GetSpawnpointGamemodeOverride()
{
if ( file.spawnpointGamemodeOverride != "" )
return file.spawnpointGamemodeOverride
- else
- return GAMETYPE
- unreachable
+ return GAMETYPE
}
-void function InitRatings( entity player, int team )
-{
- if ( player != null )
- SpawnPoints_InitRatings( player, team ) // no idea what the second arg supposed to be lol
-}
+
+
+
+
+
+
+
+
+
+/*
+███████ ██████ █████ ██ ██ ███ ██ ██████ ██████ ██████ ███████ ██████ ██ ███ ██ ██████
+██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
+███████ ██████ ███████ ██ █ ██ ██ ██ ██ ██ ██ ██████ ██ ██ █████ ██████ ██ ██ ██ ██ ██ ███
+ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+███████ ██ ██ ██ ███ ███ ██ ████ ██████ ██ ██ ██████ ███████ ██ ██ ██ ██ ████ ██████
+*/
entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnpoint )
{
int team = player.GetTeam()
- if ( HasSwitchedSides() )
- team = GetOtherTeam( team )
-
+
array<entity> spawnpoints
if ( useStartSpawnpoint )
spawnpoints = isTitan ? SpawnPoints_GetTitanStart( team ) : SpawnPoints_GetPilotStart( team )
else
spawnpoints = isTitan ? SpawnPoints_GetTitan() : SpawnPoints_GetPilot()
- InitRatings( player, player.GetTeam() )
-
- // don't think this is necessary since we call discardratings
- //foreach ( entity spawnpoint in spawnpoints )
- // spawnpoint.CalculateRating( isTitan ? TD_TITAN : TD_PILOT, team, 0.0, 0.0 )
+ SpawnPoints_InitRatings( player, team )
void functionref( int, array<entity>, int, entity ) ratingFunc = isTitan ? GameMode_GetTitanSpawnpointsRatingFunc( GAMETYPE ) : GameMode_GetPilotSpawnpointsRatingFunc( GAMETYPE )
ratingFunc( isTitan ? TD_TITAN : TD_PILOT, spawnpoints, team, player )
@@ -166,46 +238,46 @@ entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnp
spawnpoints = useStartSpawnpoint ? SpawnPoints_GetPilotStart( team ) : SpawnPoints_GetPilot()
}
- entity spawnpoint = GetBestSpawnpoint( player, spawnpoints )
+ entity spawnpoint = GetBestSpawnpoint( player, spawnpoints, isTitan )
spawnpoint.s.lastUsedTime = Time()
player.SetLastSpawnPoint( spawnpoint )
+
+ //SpawnPoints_DiscardRatings()
return spawnpoint
}
-entity function GetBestSpawnpoint( entity player, array<entity> spawnpoints )
+entity function GetBestSpawnpoint( entity player, array<entity> spawnpoints, bool isTitan )
{
- // not really 100% sure on this randomisation, needs some thought
array<entity> validSpawns
+
+ // I know this looks hacky but the native funcs to get the spawns is returning null arrays for FFA idk why.
+ if ( IsFFAGame() )
+ {
+ spawnpoints.clear()
+ if ( isTitan )
+ spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_titan" )
+ else
+ spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_human" )
+ }
+
foreach ( entity spawnpoint in spawnpoints )
{
if ( IsSpawnpointValid( spawnpoint, player.GetTeam() ) )
- {
validSpawns.append( spawnpoint )
-
- if ( validSpawns.len() == 3 ) // arbitrary small sample size
- break
- }
}
- if ( validSpawns.len() == 0 )
+ if ( !validSpawns.len() ) // First validity check
{
- // no valid spawns, very bad, so dont care about spawns being valid anymore
- print( "found no valid spawns! spawns may be subpar!" )
+ CodeWarning( "Map has no valid spawn points for " + GAMETYPE + " gamemode, attempting any other possible spawn point" )
foreach ( entity spawnpoint in spawnpoints )
- {
validSpawns.append( spawnpoint )
-
- if ( validSpawns.len() == 3 ) // arbitrary small sample size
- break
- }
}
- // last resort
- if ( validSpawns.len() == 0 )
+ if ( !validSpawns.len() ) // On all validity check, just gather the most basic spawn
{
- print( "map has literally 0 spawnpoints, as such everything is fucked probably, attempting to use info_player_start if present" )
+ CodeWarning( "Map has no proper spawn points, falling back to info_player_start" )
entity start = GetEnt( "info_player_start" )
if ( IsValid( start ) )
@@ -213,14 +285,19 @@ entity function GetBestSpawnpoint( entity player, array<entity> spawnpoints )
start.s.lastUsedTime <- -999
validSpawns.append( start )
}
+ else
+ throw( "Map has no player spawns at all" )
}
- return validSpawns[ RandomInt( validSpawns.len() ) ] // slightly randomize it
+ if ( IsFFAGame() )
+ return validSpawns.getrandom()
+
+ return validSpawns[0] // Return first entry in the array because native have already sorted everything through the ratings, so first one is the best one
}
bool function IsSpawnpointValid( entity spawnpoint, int team )
{
- if ( !spawnpoint.HasKey( "ignoreGamemode" ) || ( spawnpoint.HasKey( "ignoreGamemode" ) && spawnpoint.kv.ignoreGamemode == "0" ) ) // used by script-spawned spawnpoints
+ if ( !spawnpoint.HasKey( "ignoreGamemode" ) || spawnpoint.HasKey( "ignoreGamemode" ) && spawnpoint.kv.ignoreGamemode == "0" ) // used by script-spawned spawnpoints
{
if ( file.spawnpointGamemodeOverride != "" )
{
@@ -232,223 +309,149 @@ bool function IsSpawnpointValid( entity spawnpoint, int team )
return false
}
- int compareTeam = spawnpoint.GetTeam()
- if ( HasSwitchedSides() && ( compareTeam == TEAM_MILITIA || compareTeam == TEAM_IMC ) )
- compareTeam = GetOtherTeam( compareTeam )
-
foreach ( bool functionref( entity, int ) customValidationRule in file.customSpawnpointValidationRules )
if ( !customValidationRule( spawnpoint, team ) )
return false
- if ( spawnpoint.GetTeam() > 0 && compareTeam != team && !IsFFAGame() )
+ if ( !IsSpawnpointValidDrop( spawnpoint, team ) || Time() - spawnpoint.s.lastUsedTime <= 10.0 )
return false
- if ( spawnpoint.IsOccupied() )
+ if ( SpawnPointInNoSpawnArea( spawnpoint.GetOrigin(), team ) )
return false
-
- if ( Time() - spawnpoint.s.lastUsedTime <= 10.0 )
- return false
-
- foreach ( k, NoSpawnArea noSpawnArea in file.noSpawnAreas )
+
+ // Line of Sight Check, could use IsVisibleToEnemies but apparently that considers only players, not NPCs
+ array< entity > enemyTitans = GetTitanArrayOfEnemies( team )
+ if ( GetConVarBool( "spawnpoint_avoid_npc_titan_sight" ) )
{
- if ( Distance( noSpawnArea.position, spawnpoint.GetOrigin() ) > noSpawnArea.radius )
- continue
-
- if ( noSpawnArea.blockedTeam != TEAM_INVALID && noSpawnArea.blockedTeam == team )
- return false
-
- if ( noSpawnArea.blockOtherTeams != TEAM_INVALID && noSpawnArea.blockOtherTeams != team )
- return false
+ foreach ( titan in enemyTitans )
+ {
+ if ( IsAlive( titan ) && titan.IsNPC() && titan.CanSee( spawnpoint ) )
+ return false
+ }
}
-
- const minEnemyDist = 1000.0 // about 20 meters?
- // in rsquirrel extend returns null unlike in vanilla squirrel
- array< entity > spawnBlockers = GetPlayerArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), minEnemyDist )
- spawnBlockers.extend( GetProjectileArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), minEnemyDist ) )
- foreach ( entity blocker in spawnBlockers )
- if ( blocker.GetTeam() != team )
- return false
- // los check
return !spawnpoint.IsVisibleToEnemies( team )
}
-// SPAWNPOINT RATING FUNCS BELOW
-// generic
-struct {
- array<vector> preferSpawnNodes
-} spawnStateGeneric
-void function RateSpawnpoints_Generic( int checkClass, array<entity> spawnpoints, int team, entity player )
-{
- if ( !IsFFAGame() )
- {
- // use frontline spawns in 2-team modes
- RateSpawnpoints_Frontline( checkClass, spawnpoints, team, player )
- return
- }
- else
- {
- // todo: ffa spawns :terror:
- }
- // old algo: keeping until we have a better ffa spawn algo
- // i'm not a fan of this func, but i really don't have a better way to do this rn, and it's surprisingly good with los checks implemented now
-
- // calculate ratings for preferred nodes
- // this tries to prefer nodes with more teammates, then activity on them
- // todo: in the future it might be good to have this prefer nodes with enemies up to a limit of some sort
- // especially in ffa modes i could deffo see this falling apart a bit rn
- // perhaps dead players could be used to calculate some sort of activity rating? so high-activity points with an even balance of friendly/unfriendly players are preferred
- array<float> preferSpawnNodeRatings
- foreach ( vector preferSpawnNode in spawnStateGeneric.preferSpawnNodes )
+
+
+
+
+/*
+██████ ██████ ██ ███ ██ ████████ ██████ █████ ████████ ██ ███ ██ ██████
+██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
+██████ ██ ██ ██ ██ ██ ██ ██ ██████ ███████ ██ ██ ██ ██ ██ ██ ███
+██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+██ ██████ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██████
+*/
+
+void function RateSpawnpoints_Generic( int checkClass, array<entity> spawnpoints, int team, entity player )
+{
+ foreach ( entity spawnpoint in spawnpoints )
{
- float currentRating
-
- // this seems weird, not using rn
- //Frontline currentFrontline = GetCurrentFrontline( team )
- //if ( !IsFFAGame() || currentFrontline.friendlyCenter != < 0, 0, 0 > )
- // currentRating += max( 0.0, ( 1000.0 - Distance2D( currentFrontline.origin, preferSpawnNode ) ) / 200 )
+ float currentRating = 0.0
- foreach ( entity nodePlayer in GetPlayerArray() )
- {
- float currentChange = 0.0
-
- // the closer a player is to a node the more they matter
- float dist = Distance2D( preferSpawnNode, nodePlayer.GetOrigin() )
- if ( dist > 600.0 )
- continue
-
- currentChange = ( 600.0 - dist ) / 5
- if ( player == nodePlayer )
- currentChange *= -3 // always try to stay away from places we've already spawned
- else if ( !IsAlive( nodePlayer ) ) // dead players mean activity which is good, but they're also dead so they don't matter as much as living ones
- currentChange *= 0.6
- if ( nodePlayer.GetTeam() != player.GetTeam() ) // if someone isn't on our team and alive they're probably bad
- {
- if ( IsFFAGame() ) // in ffa everyone is on different teams, so this isn't such a big deal
- currentChange *= -0.2
- else
- currentChange *= -0.6
- }
-
- currentRating += currentChange
- }
+ // Gather friendly scoring first to give positive rating first
+ currentRating += spawnpoint.NearbyAllyScore( team, "ai" )
+ currentRating += spawnpoint.NearbyAllyScore( team, "titan" )
+ currentRating += spawnpoint.NearbyAllyScore( team, "pilot" )
- preferSpawnNodeRatings.append( currentRating )
- }
-
- foreach ( entity spawnpoint in spawnpoints )
- {
- float currentRating
- float petTitanModifier
- // scale how much a given spawnpoint matters to us based on how far it is from each node
- bool spawnHasRecievedInitialBonus = false
- for ( int i = 0; i < spawnStateGeneric.preferSpawnNodes.len(); i++ )
- {
- // bonus if autotitan is nearish
- if ( IsAlive( player.GetPetTitan() ) && Distance( player.GetPetTitan().GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] ) < 1200.0 )
- petTitanModifier += 10.0
-
- float dist = Distance2D( spawnpoint.GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] )
- if ( dist > 750.0 )
- continue
-
- if ( dist < 600.0 && !spawnHasRecievedInitialBonus )
- {
- currentRating += 10.0
- spawnHasRecievedInitialBonus = true // should only get a bonus for simply being by a node once to avoid over-rating
- }
+ // Enemies then subtract that rating ( Values already returns negative, so no need to apply subtract again )
+ currentRating += spawnpoint.NearbyEnemyScore( team, "ai" )
+ currentRating += spawnpoint.NearbyEnemyScore( team, "titan" )
+ currentRating += spawnpoint.NearbyEnemyScore( team, "pilot" )
- currentRating += ( preferSpawnNodeRatings[ i ] * ( ( 750.0 - dist ) / 75 ) ) + max( RandomFloat( 1.25 ), 0.9 )
- if ( dist < 250.0 ) // shouldn't get TOO close to an active node
- currentRating *= 0.7
-
- if ( spawnpoint.s.lastUsedTime < 10.0 )
- currentRating *= 0.7
- }
-
- float rating = spawnpoint.CalculateRating( checkClass, team, currentRating, currentRating + petTitanModifier )
- //print( "spawnpoint at " + spawnpoint.GetOrigin() + " has rating: " + )
+ if ( spawnpoint == player.p.lastSpawnPoint ) // Reduce the rating of the spawn point used previously
+ currentRating += GetConVarFloat( "spawnpoint_last_spawn_rating" )
- if ( rating != 0.0 || currentRating != 0.0 )
- print( "rating = " + rating + ", internal rating = " + currentRating )
- }
-}
-
-void function InitPreferSpawnNodes()
-{
- foreach ( entity hardpoint in GetEntArrayByClass_Expensive( "info_hardpoint" ) )
- {
- if ( !hardpoint.HasKey( "hardpointGroup" ) )
- continue
-
- if ( hardpoint.kv.hardpointGroup != "A" && hardpoint.kv.hardpointGroup != "B" && hardpoint.kv.hardpointGroup != "C" )
- continue
-
- spawnStateGeneric.preferSpawnNodes.append( hardpoint.GetOrigin() )
+ spawnpoint.CalculateRating( checkClass, team, currentRating, currentRating * 0.25 )
}
-
- //foreach ( entity frontline in GetEntArrayByClass_Expensive( "info_frontline" ) )
- // spawnStateGeneric.preferSpawnNodes.append( frontline.GetOrigin() )
}
-// frontline
void function RateSpawnpoints_Frontline( int checkClass, array<entity> spawnpoints, int team, entity player )
{
+ Frontline currentFrontline = GetFrontline( team )
+
+ vector inverseFrontlineDir = currentFrontline.combatDir * -1
+ vector adjustedPosition = currentFrontline.origin + currentFrontline.combatDir * 8000
+
+ SpawnPoints_InitFrontlineData( adjustedPosition, currentFrontline.combatDir, currentFrontline.origin, currentFrontline.friendlyCenter, 4000 )
+
foreach ( entity spawnpoint in spawnpoints )
{
- float rating = spawnpoint.CalculateFrontlineRating()
- spawnpoint.CalculateRating( checkClass, player.GetTeam(), rating, rating > 0 ? rating * 0.25 : rating )
+ float frontlineRating = spawnpoint.CalculateFrontlineRating()
+
+ spawnpoint.CalculateRating( checkClass, team, frontlineRating, frontlineRating * 0.25 )
}
}
-// spawnzones
-struct {
- array<entity> mapSpawnzoneTriggers
- entity functionref( array<entity>, int ) spawnzoneRatingFunc
- bool shouldCreateMinimapSpawnzones = false
-
- // for DecideSpawnZone_Generic
- table<int, entity> activeTeamSpawnzones
- table<int, entity> activeTeamSpawnzoneMinimapEnts
-} spawnStateSpawnzones
+
+
+
+
+
+
+
+
+
+/*
+███████ ██████ █████ ██ ██ ███ ██ ███████ ██████ ███ ██ ███████ ███████
+██ ██ ██ ██ ██ ██ ██ ████ ██ ███ ██ ██ ████ ██ ██ ██
+███████ ██████ ███████ ██ █ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ █████ ███████
+ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██
+███████ ██ ██ ██ ███ ███ ██ ████ ███████ ██████ ██ ████ ███████ ███████
+*/
void function ResetSpawnzones()
{
- spawnStateSpawnzones.activeTeamSpawnzones.clear()
-
- foreach ( int team, entity minimapEnt in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts )
- if ( IsValid( minimapEnt ) )
- minimapEnt.Destroy()
-
- spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts.clear()
+ foreach ( zone, zoneProperties in mapSpawnZones )
+ {
+ if ( IsValid( zoneProperties.minimapEnt ) )
+ zoneProperties.minimapEnt.Destroy()
+
+ zoneProperties.controllingTeam = TEAM_UNASSIGNED
+ zoneProperties.zoneRating = 0.0
+ }
}
void function AddSpawnZoneTrigger( entity trigger )
{
- trigger.s.spawnzoneRating <- 0.0
- spawnStateSpawnzones.mapSpawnzoneTriggers.append( trigger )
+ spawnZoneProperties zoneProperties
+ mapSpawnZones[trigger] <- zoneProperties
}
-void function SetSpawnZoneRatingFunc( entity functionref( array<entity>, int ) ratingFunc )
+bool function TeamHasDirtySpawnzone( int team )
{
- spawnStateSpawnzones.spawnzoneRatingFunc = ratingFunc
-}
-
-void function SetShouldCreateMinimapSpawnZones( bool shouldCreateMinimapSpawnzones )
-{
- spawnStateSpawnzones.shouldCreateMinimapSpawnzones = shouldCreateMinimapSpawnzones
+ foreach ( zone, zoneProperties in mapSpawnZones )
+ {
+ if ( zoneProperties.controllingTeam == team )
+ {
+ int numDeadInZone = 0
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( team )
+ foreach ( entity player in teamPlayers )
+ {
+ if ( Time() - player.p.postDeathThreadStartTime < 20.0 && zone.ContainsPoint( player.p.deathOrigin ) )
+ numDeadInZone++
+ }
+
+ if ( numDeadInZone < teamPlayers.len() )
+ return false
+ }
+ }
+
+ return true
}
-entity function CreateTeamSpawnZoneEntity( entity spawnzone, int team )
+void function CreateTeamSpawnZoneEntity( entity spawnzone, int team )
{
entity minimapObj = CreatePropScript( $"models/dev/empty_model.mdl", spawnzone.GetOrigin() )
SetTeam( minimapObj, team )
- minimapObj.Minimap_SetObjectScale( 100.0 / Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) )
+ minimapObj.Minimap_SetObjectScale( Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) / 16000 ) // 16000 cuz thats the total space Minimap uses
minimapObj.Minimap_SetAlignUpright( true )
minimapObj.Minimap_AlwaysShow( TEAM_IMC, null )
minimapObj.Minimap_AlwaysShow( TEAM_MILITIA, null )
@@ -461,67 +464,58 @@ entity function CreateTeamSpawnZoneEntity( entity spawnzone, int team )
minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_MIL )
minimapObj.DisableHibernation()
- return minimapObj
+ mapSpawnZones[spawnzone].minimapEnt = minimapObj
}
void function RateSpawnpoints_SpawnZones( int checkClass, array<entity> spawnpoints, int team, entity player )
{
- if ( spawnStateSpawnzones.spawnzoneRatingFunc == null )
- spawnStateSpawnzones.spawnzoneRatingFunc = DecideSpawnZone_Generic
-
- // don't use spawnzones if we're using start spawns
if ( ShouldStartSpawn( player ) )
{
RateSpawnpoints_Generic( checkClass, spawnpoints, team, player )
return
}
-
- entity spawnzone = spawnStateSpawnzones.spawnzoneRatingFunc( spawnStateSpawnzones.mapSpawnzoneTriggers, player.GetTeam() )
- if ( !IsValid( spawnzone ) ) // no spawn zone, use generic algo
+
+ array< entity > zoneTriggers
+ foreach ( zone, zoneProperties in mapSpawnZones )
+ zoneTriggers.append( zone )
+
+ entity spawnzone = DecideSpawnZone_Generic( zoneTriggers, player.GetTeam() )
+ if ( !IsValid( spawnzone ) )
{
RateSpawnpoints_Generic( checkClass, spawnpoints, team, player )
return
}
- // rate spawnpoints
foreach ( entity spawn in spawnpoints )
{
float rating = 0.0
float distance = Distance2D( spawn.GetOrigin(), spawnzone.GetOrigin() )
if ( distance < Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) )
- rating = 100.0
- else // max 35 rating if not in zone, rate by closest
- rating = 35.0 * ( 1 - ( distance / 5000.0 ) )
+ rating = 10.0
+ else
+ rating = 2.0 * ( 1 - ( distance / 3000.0 ) )
- spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating )
+ spawn.CalculateRating( checkClass, team, rating, rating * 0.25 )
}
}
entity function DecideSpawnZone_Generic( array<entity> spawnzones, int team )
{
- if ( spawnzones.len() == 0 )
+ if ( !spawnzones.len() )
return null
- // get average team startspawn positions
- int spawnCompareTeam = team
- if ( HasSwitchedSides() )
- spawnCompareTeam = GetOtherTeam( team )
-
- array<entity> startSpawns = SpawnPoints_GetPilotStart( spawnCompareTeam )
- array<entity> enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( spawnCompareTeam ) )
+ 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
+ if ( !startSpawns.len() || !enemyStartSpawns.len() )
return null
-
- // get average startspawn position and max dist between spawns
- // could probably cache this, tbh, not like it should change outside of halftimes
- vector averageFriendlySpawns
+
+ vector averageFriendlySpawns
foreach ( entity spawn in startSpawns )
averageFriendlySpawns += spawn.GetOrigin()
averageFriendlySpawns /= startSpawns.len()
- // get average enemy startspawn position
vector averageEnemySpawns
foreach ( entity spawn in enemyStartSpawns )
averageEnemySpawns += spawn.GetOrigin()
@@ -530,250 +524,87 @@ entity function DecideSpawnZone_Generic( array<entity> spawnzones, int team )
float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns )
- bool needNewZone = true
- if ( team in spawnStateSpawnzones.activeTeamSpawnzones )
- {
- foreach ( entity player in GetPlayerArray() )
- {
- // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this
- if ( player.GetTeam() != team && spawnStateSpawnzones.activeTeamSpawnzones[ team ].ContainsPoint( player.GetOrigin() ) )
- break
- }
-
- int numDeadInZone = 0
- array<entity> teamPlayers = GetPlayerArrayOfTeam( team )
- foreach ( entity player in teamPlayers )
- {
- // check if they died in the zone recently, get a new zone if too many died
- if ( Time() - player.p.postDeathThreadStartTime < 15.0 && spawnStateSpawnzones.activeTeamSpawnzones[ team ].ContainsPoint( player.p.deathOrigin ) )
- numDeadInZone++
- }
-
- // cast to float so result is float
- if ( float( numDeadInZone ) / teamPlayers.len() <= 0.1 )
- needNewZone = false
- }
-
- if ( needNewZone )
+ if ( TeamHasDirtySpawnzone( team ) )
{
- // find new zone
array<entity> possibleZones
- foreach ( entity spawnzone in spawnStateSpawnzones.mapSpawnzoneTriggers )
+ foreach ( zone, zoneProperties in mapSpawnZones )
{
- // don't remember if you can do a "value in table.values" sorta thing in squirrel so doing manual lookup
- bool spawnzoneTaken = false
- foreach ( int otherTeam, entity otherSpawnzone in spawnStateSpawnzones.activeTeamSpawnzones )
- {
- if ( otherSpawnzone == spawnzone )
- {
- spawnzoneTaken = true
- break
- }
- }
-
- if ( spawnzoneTaken )
+ if ( zoneProperties.controllingTeam == GetOtherTeam( team ) )
continue
- // check zone validity
- bool spawnzoneEvil = false
- foreach ( entity player in GetPlayerArray() )
+ bool spawnzoneHasEnemies = false
+ foreach ( entity enemy in GetPlayerArrayOfEnemies_Alive( team ) )
{
- // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this
- if ( player.GetTeam() != team && spawnzone.ContainsPoint( player.GetOrigin() ) )
+ if ( zone.ContainsPoint( enemy.GetOrigin() ) )
{
- spawnzoneEvil = true
+ spawnzoneHasEnemies = true
break
}
}
- // don't choose spawnzones that are closer to enemy base than friendly base
- // note: vanilla spawns might not necessarily require this, worth checking
- if ( !spawnzoneEvil && Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns ) > Distance2D( spawnzone.GetOrigin(), averageEnemySpawns ) )
- spawnzoneEvil = true
+ if ( !spawnzoneHasEnemies && Distance2D( zone.GetOrigin(), averageFriendlySpawns ) > Distance2D( zone.GetOrigin(), averageEnemySpawns ) )
+ spawnzoneHasEnemies = true
- if ( spawnzoneEvil )
+ if ( spawnzoneHasEnemies )
continue
- // rate spawnzone based on distance to frontline
Frontline frontline = GetFrontline( team )
-
- // prefer spawns close to base pos
- float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawnzone.GetOrigin() ) / baseDistance )
+ float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, zone.GetOrigin() ) / baseDistance )
if ( frontline.friendlyCenter != < 0, 0, 0 > )
{
- // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir
- rating += rating * ( 1.0 - ( Distance2D( spawnzone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) )
- rating *= fabs( frontline.combatDir.y - Normalize( spawnzone.GetOrigin() - averageFriendlySpawns ).y )
+ rating += rating * ( 1.0 - ( Distance2D( zone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) )
+ rating *= fabs( frontline.combatDir.y - Normalize( zone.GetOrigin() - averageFriendlySpawns ).y )
}
- spawnzone.s.spawnzoneRating = rating
- possibleZones.append( spawnzone )
+ zoneProperties.zoneRating = rating
+ possibleZones.append( zone )
}
- if ( possibleZones.len() == 0 )
+ if ( !possibleZones.len() )
return null
- possibleZones.sort( int function( entity a, entity b )
- {
- if ( a.s.spawnzoneRating > b.s.spawnzoneRating )
- return -1
-
- if ( b.s.spawnzoneRating > a.s.spawnzoneRating )
- return 1
-
- return 0
- } )
- entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ]
+ possibleZones.sort( SortPossibleZones )
- if ( spawnStateSpawnzones.shouldCreateMinimapSpawnzones )
- {
- entity oldEnt
- if ( team in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts )
- oldEnt = spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ]
-
- spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] <- CreateTeamSpawnZoneEntity( chosenZone, team )
- if ( IsValid( oldEnt ) )
- oldEnt.Destroy()
- }
-
- spawnStateSpawnzones.activeTeamSpawnzones[ team ] <- chosenZone
- }
-
- return spawnStateSpawnzones.activeTeamSpawnzones[ team ]
-}
-
-// ideally this should be in the gamemode_ctf file, but would need refactors to expose more stuff that's not available there rn
-entity function DecideSpawnZone_CTF( array<entity> spawnzones, int team )
-{
- if ( spawnzones.len() == 0 )
- return null
-
- int otherTeam = GetOtherTeam( team )
- array<entity> enemyPlayers = GetPlayerArrayOfTeam( otherTeam )
-
- // get average team startspawn positions
- int spawnCompareTeam = team
- if ( HasSwitchedSides() )
- spawnCompareTeam = GetOtherTeam( team )
-
- array<entity> startSpawns = SpawnPoints_GetPilotStart( spawnCompareTeam )
- array<entity> enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( spawnCompareTeam ) )
-
- if ( startSpawns.len() == 0 || enemyStartSpawns.len() == 0 ) // ensure we don't crash
- return null
-
- // get average startspawn position and max dist between spawns
- // could probably cache this, tbh, not like it should change outside of halftimes
- vector averageFriendlySpawns
- foreach ( entity spawn in startSpawns )
- averageFriendlySpawns += spawn.GetOrigin()
-
- averageFriendlySpawns /= startSpawns.len()
-
- // get average enemy startspawn position
- vector averageEnemySpawns
- foreach ( entity spawn in enemyStartSpawns )
- averageEnemySpawns += spawn.GetOrigin()
-
- averageEnemySpawns /= enemyStartSpawns.len()
-
- float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns )
-
- // find new zone
- array<entity> possibleZones
- foreach ( entity spawnzone in spawnStateSpawnzones.mapSpawnzoneTriggers )
- {
- // can't choose zone if another team has it
- if ( otherTeam in spawnStateSpawnzones.activeTeamSpawnzones && spawnStateSpawnzones.activeTeamSpawnzones[ otherTeam ] == spawnzone )
- continue
+ entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ]
- // check zone validity
- bool spawnzoneEvil = false
- foreach ( entity player in enemyPlayers )
+ if ( file.shouldCreateMinimapSpawnzones )
{
- // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this
- if ( spawnzone.ContainsPoint( player.GetOrigin() ) )
+ foreach ( zone, zoneProperties in mapSpawnZones )
{
- spawnzoneEvil = true
- break
+ if ( chosenZone == zone )
+ continue
+
+ if ( IsValid( zoneProperties.minimapEnt ) && zoneProperties.controllingTeam == team )
+ zoneProperties.minimapEnt.Destroy()
}
+
+ CreateTeamSpawnZoneEntity( chosenZone, team )
}
- // don't choose spawnzones that are closer to enemy base than friendly base
- if ( !spawnzoneEvil && Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns ) > Distance2D( spawnzone.GetOrigin(), averageEnemySpawns ) )
- spawnzoneEvil = true
-
- if ( spawnzoneEvil )
- continue
-
- // rate spawnzone based on distance to frontline
- Frontline frontline = GetFrontline( team )
-
- // prefer spawns close to base pos
- float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawnzone.GetOrigin() ) / baseDistance )
-
- if ( frontline.friendlyCenter != < 0, 0, 0 > )
+ foreach ( zone, zoneProperties in mapSpawnZones )
{
- // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir
- rating += rating * ( 1.0 - ( Distance2D( spawnzone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) )
- rating *= fabs( frontline.combatDir.y - Normalize( spawnzone.GetOrigin() - averageFriendlySpawns ).y )
-
- // reduce rating based on players that can currently see the zone
- bool hasAppliedInitialLoss = false
- foreach ( entity player in enemyPlayers )
- {
- // don't trace here, just do an angle check
- if ( PlayerCanSee( player, spawnzone, false, 65 ) && Distance2D( player.GetOrigin(), spawnzone.GetOrigin() ) <= 2000.0 )
- {
- float distFrac = TraceLineSimple( player.GetOrigin(), spawnzone.GetOrigin(), player )
-
- if ( distFrac >= 0.65 )
- {
- // give a fairly large loss if literally anyone can see it
- if ( !hasAppliedInitialLoss )
- {
- rating *= 0.8
- hasAppliedInitialLoss = true
- }
-
- rating *= ( 1.0 / enemyPlayers.len() ) * distFrac
- }
- }
- }
+ if ( chosenZone == zone )
+ continue
+
+ if ( zoneProperties.controllingTeam == team )
+ zoneProperties.controllingTeam = TEAM_UNASSIGNED
}
- spawnzone.s.spawnzoneRating = rating
- possibleZones.append( spawnzone )
+ mapSpawnZones[chosenZone].controllingTeam = team
+ return chosenZone
}
- if ( possibleZones.len() == 0 )
- return null
-
- possibleZones.sort( int function( entity a, entity b )
- {
- if ( a.s.spawnzoneRating > b.s.spawnzoneRating )
- return -1
+ return null
+}
+
+int function SortPossibleZones( entity a, entity b )
+{
+ if ( mapSpawnZones[a].zoneRating > mapSpawnZones[b].zoneRating )
+ return -1
- if ( b.s.spawnzoneRating > a.s.spawnzoneRating )
- return 1
+ if ( mapSpawnZones[b].zoneRating > mapSpawnZones[a].zoneRating )
+ return 1
- return 0
- } )
- entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ]
-
- if ( spawnStateSpawnzones.shouldCreateMinimapSpawnzones )
- {
- entity oldEnt
- if ( team in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts )
- oldEnt = spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ]
-
- spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] <- CreateTeamSpawnZoneEntity( chosenZone, team )
- if ( IsValid( oldEnt ) )
- oldEnt.Destroy()
- }
-
- spawnStateSpawnzones.activeTeamSpawnzones[ team ] <- chosenZone
-
- return spawnStateSpawnzones.activeTeamSpawnzones[ team ]
+ return 0
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut
index 2dc88d0d..3297643e 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut
@@ -88,7 +88,8 @@ bool function ClientCommand_SetProgression( entity player, array<string> args )
/// Resets a specific Titan's Aegis rank back to `0`
/// * `player` - The player entity to perform the action on
-/// * `args` - The arguments passed from the client command. `args[0]` should be an integer corresponding to the index of the Titan to reset.
+/// * `args` - The arguments passed from the client command. `args[0]` should be a string corresponding to the chassis name of the Titan to reset.
+/// Valid chassis are: ion, tone, vanguard, northstar, ronin, legion, and scorch.
///
/// Returns `true` on success and `false` on missing args.
bool function ClientCommand_ResetTitanAegis( entity player, array<string> args )
@@ -96,7 +97,12 @@ bool function ClientCommand_ResetTitanAegis( entity player, array<string> args )
if ( !args.len() )
return false
- int suitIndex = args[0].tointeger()
+ string titanRef = args[0].tolower()
+ if( !PersistenceEnumValueIsValid( "titanClasses", titanRef ) )
+ return false
+
+ int suitIndex = PersistenceGetEnumIndexForItemName( "titanClasses", titanRef )
+
player.SetPersistentVar( "titanFDUnlockPoints[" + suitIndex + "]", 0 )
player.SetPersistentVar( "previousFDUnlockPoints[" + suitIndex + "]", 0 )
player.SetPersistentVar( "fdTitanXP[" + suitIndex + "]", 0 )
@@ -218,11 +224,12 @@ void function ValidateEquippedItems( entity player )
}
// titan loadouts
+ int selectedTitanLoadoutIndex = player.GetPersistentVarAsInt( "titanSpawnLoadout.index" )
for ( int titanLoadoutIndex = 0; titanLoadoutIndex < NUM_PERSISTENT_TITAN_LOADOUTS; titanLoadoutIndex++ )
{
printt( "- VALIDATING TITAN LOADOUT: " + titanLoadoutIndex )
- bool isSelected = titanLoadoutIndex == player.GetPersistentVarAsInt( "titanSpawnLoadout.index" )
+ bool isSelected = titanLoadoutIndex == selectedTitanLoadoutIndex
TitanLoadoutDef loadout = GetTitanLoadout( player, titanLoadoutIndex )
TitanLoadoutDef defaultLoadout = shGlobal.defaultTitanLoadouts[titanLoadoutIndex]
@@ -440,11 +447,18 @@ void function ValidateEquippedItems( entity player )
{
printt( " - SELECTED TITAN CLASS IS LOCKED, RESETTING" )
player.SetPersistentVar( "titanSpawnLoadout.index", 0 )
- Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateTitanModel", 0 )
+ selectedTitanLoadoutIndex = 0
}
}
- Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateTitanModel", player.GetPersistentVarAsInt( "titanSpawnLoadout.index" ) )
+ if ( selectedTitanLoadoutIndex < 0 || selectedTitanLoadoutIndex >= NUM_PERSISTENT_TITAN_LOADOUTS )
+ {
+ printt( "- SELECTED TITAN CLASS IS INVALID, RESETTING" )
+ player.SetPersistentVar( "titanSpawnLoadout.index", 0 )
+ selectedTitanLoadoutIndex = 0
+ }
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateTitanModel", selectedTitanLoadoutIndex )
// pilot loadouts
for ( int pilotLoadoutIndex = 0; pilotLoadoutIndex < NUM_PERSISTENT_PILOT_LOADOUTS; pilotLoadoutIndex++ )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut
index 0436a393..847881b5 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut
@@ -1,17 +1,39 @@
global function AddTitanXP
+global function AddFDTitanXP
void function AddTitanXP( entity player, int amount )
{
string titan = GetActiveTitanLoadout( player ).titanClass
int oldLevel = TitanGetLevel( player, titan )
+ int TitanXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.TITAN_LEVELED + "]" )
// increment xp
player.SetPersistentVar( "titanXP[" + titan + "]", min( TitanGetXP( player, titan ) + amount, TitanGetMaxXP( titan ) ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_TitanXPAdded", shTitanXP.titanClasses.find( titan ), TitanGetXP( player, titan ), amount )
// level up notif
if ( TitanGetLevel( player, titan ) != oldLevel )
{
Remote_CallFunction_NonReplay( player, "ServerCallback_TitanLeveledUp", shTitanXP.titanClasses.find( titan ), TitanGetGen( player, titan ), TitanGetLevel( player, titan ) )
AddPlayerScore( player, "TitanLevelUp" )
+ IncrementPlayerChallengeTitanLeveledUp( player )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.TITAN_LEVELED + "]", TitanXPMatch + 1 )
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForTitanLevels( player, titan, oldLevel, TitanGetLevel( player, titan ) )
}
+}
+
+void function AddFDTitanXP( entity player, int fdXPamount )
+{
+ string titanRef = GetActiveTitanLoadout( player ).titanClass
+
+ player.SetPersistentVar( "fdTitanXP[" + titanRef + "]", FD_TitanGetPreviousXP( player, titanRef ) + fdXPamount )
+ int startingLevel = FD_TitanGetLevelForXP( titanRef, FD_TitanGetPreviousXP( player, titanRef ) )
+ int endingLevel = FD_TitanGetLevelForXP( titanRef, FD_TitanGetXP( player, titanRef ) )
+
+ Player_GiveFDUnlockPoints( player, endingLevel - startingLevel )
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForFDTitanLevels( player, titanRef, startingLevel, endingLevel )
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut
index 4e25e301..0b0084b3 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut
@@ -6,15 +6,22 @@ void function AddWeaponXP( entity player, int amount )
entity activeWeapon = player.GetActiveWeapon()
string weaponClassname = activeWeapon.GetWeaponClassName()
int oldLevel = WeaponGetLevel( player, weaponClassname )
+ int WeaponXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.WEAPON_LEVELED + "]" )
// increment xp
player.SetPersistentVar( GetItemPersistenceStruct( weaponClassname ) + ".weaponXP", min( WeaponGetXP( player, weaponClassname ) + amount, WeaponGetMaxXP( weaponClassname ) ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_WeaponXPAdded", shWeaponXP.weaponClassNames.find( weaponClassname ), WeaponGetXP( player, weaponClassname ), amount )
// level up notif
if ( WeaponGetLevel( player, weaponClassname ) != oldLevel )
{
Remote_CallFunction_NonReplay( player, "ServerCallback_WeaponLeveledUp", shWeaponXP.weaponClassNames.find( weaponClassname ), WeaponGetGen( player, weaponClassname ), WeaponGetLevel( player, weaponClassname ) )
AddPlayerScore( player, "WeaponLevelUp" )
+ IncrementPlayerChallengeWeaponLeveledUp( player )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.WEAPON_LEVELED + "]", WeaponXPMatch + 1 )
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForWeaponLevels( player, weaponClassname, oldLevel, WeaponGetLevel( player, weaponClassname ) )
}
// proscreen