aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_english.txt4
-rw-r--r--Northstar.Client/mod/scripts/vscripts/client/rui/cl_weapon_status.gnut860
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut6
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut6
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut56
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut200
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut15
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut45
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut382
9 files changed, 1266 insertions, 308 deletions
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt
index 89ecc5fba..96e4f04e9 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt
@@ -4,7 +4,7 @@
"Tokens"
{
// This file needs to be encoded as UTF-16 LE
- // test BOM utf fix
+
"MENU_LAUNCH_NORTHSTAR" "Launch Northstar"
"MENU_TITLE_MODS" "Mods"
"RELOAD_MODS" "Reload Mods"
@@ -288,4 +288,4 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a
"INGAME_PLAYERS" "Players:"
"TOTAL_SERVERS" "Servers:"
}
-}
+} \ No newline at end of file
diff --git a/Northstar.Client/mod/scripts/vscripts/client/rui/cl_weapon_status.gnut b/Northstar.Client/mod/scripts/vscripts/client/rui/cl_weapon_status.gnut
new file mode 100644
index 000000000..26f5efd88
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/client/rui/cl_weapon_status.gnut
@@ -0,0 +1,860 @@
+global function ClWeaponStatus_Init
+global function ClWeaponStatus_SetOffhandVisible
+global function ClWeaponStatus_SetWeaponVisible
+global function ClWeaponStatus_GetWeaponHudRui
+global function ClWeaponStatus_RefreshWeaponStatus
+
+struct
+{
+ var ammo_status_hint
+ var ability_left_hud
+ var ability_center_hud
+ var ability_right_hud
+ var dpad_left_hud
+ var ammo_counter
+
+ bool[6] slotVisible = [true, true, true, true, true, true]
+ bool ammo_counter_visible = true
+
+ int lastSelectedIndex
+
+ entity lastCenterWeapon
+} file
+
+void function ClWeaponStatus_Init()
+{
+ AddCallback_OnClientScriptInit( ClWeaponStatus_AddClient )
+ AddCallback_OnSelectedWeaponChanged( OnSelectedWeaponChanged )
+
+ AddCallback_OnPlayerLifeStateChanged( OnLifeStateChanged )
+ AddCallback_PlayerClassChanged( OnPlayerClassChanged )
+
+ AddCallback_KillReplayEnded( OnKillReplayEnded )
+
+ RegisterSignal( "EndTrackOffhandWeaponSlot" )
+ RegisterSignal( "EndTrackCenterStuff" )
+}
+
+void function ClWeaponStatus_RefreshWeaponStatus( entity player )
+{
+ if ( !IsValid( player ) )
+ return
+
+ UpdatePrimaryWeaponHint()
+
+ if ( !IsValid( GetLocalViewPlayer() ) )
+ return
+
+ InitWeaponStatusRuis( GetLocalViewPlayer() )
+}
+
+var function ClWeaponStatus_GetWeaponHudRui( entity player, entity weapon )
+{
+ var index = weapon.GetWeaponInfoFileKeyField( "offhand_default_inventory_slot" )
+
+ if ( index == null )
+ return file.ammo_counter
+
+ expect int( index )
+
+ return GetRuiForIndex( player, index )
+}
+
+void function ClWeaponStatus_AddClient( entity player )
+{
+ {
+ var rui = CreateCockpitRui( $"ui/ammo_status_hint.rpak" )
+ RuiTrackFloat( rui, "ammoFrac", GetLocalClientPlayer(), RUI_TRACK_WEAPON_CLIP_AMMO_FRACTION )
+ RuiTrackFloat( rui, "remainingAmmoFrac", GetLocalClientPlayer(), RUI_TRACK_WEAPON_REMAINING_AMMO_FRACTION )
+ RuiTrackFloat( rui, "readyToFireFrac", GetLocalClientPlayer(), RUI_TRACK_WEAPON_READY_TO_FIRE_FRACTION )
+ RuiTrackFloat( rui, "reloadingFrac", GetLocalClientPlayer(), RUI_TRACK_WEAPON_RELOAD_FRACTION )
+
+ file.ammo_status_hint = rui
+ }
+
+ {
+ var rui = CreateCockpitRui( $"ui/ability_hud.rpak" )
+ RuiSetInt( rui, "xPos", 0 )
+ file.ability_left_hud = rui
+ }
+
+ {
+ var rui = CreateCockpitRui( $"ui/ability_hud.rpak" )
+ RuiSetInt( rui, "xPos", 1 )
+ file.ability_center_hud = rui
+ }
+
+ {
+ var rui = CreateCockpitRui( $"ui/ability_hud.rpak" )
+ RuiSetInt( rui, "xPos", 2 )
+ file.ability_right_hud = rui
+ }
+
+ {
+ var rui = CreateCockpitRui( $"ui/inventory_hud.rpak" )
+ RuiSetInt( rui, "xPos", 1 )
+ RuiSetInt( rui, "yPos", 1 )
+ file.dpad_left_hud = rui
+ }
+
+ {
+ var rui = CreateCockpitRui( $"ui/ammo_counter.rpak" )
+ file.ammo_counter = rui
+ }
+}
+
+
+void function OnSelectedWeaponChanged( entity selectedWeapon )
+{
+ if ( !IsValid( selectedWeapon ) )
+ {
+ RuiSetFloat( file.ammo_status_hint, "lowAmmoFrac", 0.0 )
+ RuiSetBool( file.ammo_counter, "isVisible", false )
+ return
+ }
+
+ if ( GetLocalViewPlayer().PlayerMelee_IsAttackActive() )
+ return
+
+ if ( GetLocalViewPlayer().IsUsingOffhandWeapon() )
+ return
+
+ asset primaryIcon = $""
+ asset secondaryIcon = $""
+ asset tertiaryIcon = $""
+ array<entity> mainWeapons = GetLocalViewPlayer().GetMainWeapons()
+ #if MP
+ int activeIndex
+ foreach ( index, weapon in mainWeapons )
+ {
+ asset hudIcon = GetWeaponInfoFileKeyFieldAsset_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "hud_icon" )
+ if ( index == 0 )
+ primaryIcon = hudIcon
+ else if ( index == 1 )
+ secondaryIcon = hudIcon
+ else if ( index == 2 )
+ tertiaryIcon = hudIcon
+
+ if ( weapon == selectedWeapon )
+ activeIndex = index
+ }
+
+ if ( activeIndex == 0 )
+ {
+ RuiSetImage( file.ammo_counter, "tertiaryHudIcon", tertiaryIcon )
+ RuiSetImage( file.ammo_counter, "secondaryHudIcon", secondaryIcon )
+ RuiSetGameTime( file.ammo_counter, "tapPingTime", Time() )
+ }
+ else if ( activeIndex == 1 )
+ {
+ RuiSetImage( file.ammo_counter, "tertiaryHudIcon", tertiaryIcon )
+ RuiSetImage( file.ammo_counter, "secondaryHudIcon", primaryIcon )
+ RuiSetGameTime( file.ammo_counter, "tapPingTime", Time() )
+ }
+ else if ( activeIndex == 2 )
+ {
+ if ( file.lastSelectedIndex == 0 )
+ {
+ RuiSetImage( file.ammo_counter, "tertiaryHudIcon", $"" )
+ RuiSetImage( file.ammo_counter, "secondaryHudIcon", primaryIcon )
+ }
+ else if ( file.lastSelectedIndex == 1 )
+ {
+ RuiSetImage( file.ammo_counter, "tertiaryHudIcon", $"" )
+ RuiSetImage( file.ammo_counter, "secondaryHudIcon", secondaryIcon )
+ }
+ }
+
+ file.lastSelectedIndex = activeIndex
+ #else
+ foreach ( weapon in mainWeapons )
+ {
+ if ( weapon == selectedWeapon )
+ continue
+
+ if ( selectedWeapon.GetWeaponType() == WT_ANTITITAN )
+ continue
+
+ if ( weapon.GetWeaponType() == WT_ANTITITAN )
+ continue
+
+ if ( GetLocalViewPlayer().IsTitan() )
+ continue
+
+ asset hudIcon = GetWeaponInfoFileKeyFieldAsset_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "hud_icon" )
+ secondaryIcon = hudIcon
+ }
+ RuiSetImage( file.ammo_counter, "secondaryHudIcon", secondaryIcon )
+ RuiSetImage( file.ammo_counter, "tertiaryHudIcon", $"" )
+ #endif
+
+ InitAmmoCounterRui( file.ammo_counter, GetLocalViewPlayer(), selectedWeapon )
+
+ float lowAmmoFrac = selectedWeapon.GetWeaponSettingFloat( eWeaponVar.low_ammo_fraction )
+ RuiSetFloat( file.ammo_status_hint, "lowAmmoFrac", lowAmmoFrac )
+ RuiSetBool( file.ammo_counter, "isVisible", file.ammo_counter_visible )
+ RuiSetBool( file.ammo_counter, "isTitan", CheckCenterSlotOccupied(GetLocalViewPlayer()) )
+
+ RuiSetBool( file.ammo_counter, "isWeaponAmped", selectedWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) )
+
+ UpdatePrimaryWeaponHint()
+}
+
+void function UpdatePrimaryWeaponHint()
+{
+ // Show/Hide the BT loudout switch button hint if single player
+ if ( IsSingleplayer() && GetLocalViewPlayer().IsTitan() && GetConVarInt( "hud_setting_showButtonHints" ) != 0 )
+ RuiSetString( file.ammo_counter, "hintText", Localize( "#HUD_SP_BT_LOADOUT_SWAP" ) )
+ else
+ RuiSetString( file.ammo_counter, "hintText", "" )
+}
+
+
+void function InitAmmoCounterRui( var rui, entity player, entity weapon )
+{
+ RuiTrackFloat( rui, "maxMagAmmo", player, RUI_TRACK_WEAPON_CLIP_AMMO_MAX )
+ RuiTrackFloat( rui, "maxStockpileAmmo", player, RUI_TRACK_WEAPON_STOCKPILE_AMMO_MAX )
+ RuiTrackFloat( rui, "clipAmmoFrac", player, RUI_TRACK_WEAPON_CLIP_AMMO_FRACTION )
+ RuiTrackFloat( rui, "remainingAmmoFrac", player, RUI_TRACK_WEAPON_REMAINING_AMMO_FRACTION )
+ RuiTrackFloat( rui, "lifetimeShots", player, RUI_TRACK_WEAPON_LIFETIME_SHOTS )
+ RuiTrackFloat( rui, "ammoRegenRate", player, RUI_TRACK_WEAPON_AMMO_REGEN_RATE )
+
+ RuiTrackImage( rui, "hudIcon", player, RUI_TRACK_WEAPON_HUD_ICON )
+}
+
+
+void function OnPlayerClassChanged( entity player )
+{
+ if ( player != GetLocalViewPlayer() )
+ return
+
+ InitWeaponStatusRuis( player )
+}
+
+
+void function OnLifeStateChanged( entity player, int oldLifeState, int newLifeState )
+{
+ if ( player != GetLocalViewPlayer() )
+ return
+
+ if ( newLifeState != LIFE_ALIVE )
+ return
+
+ InitWeaponStatusRuis( player )
+}
+
+void function OnKillReplayEnded()
+{
+ entity player = GetLocalViewPlayer()
+
+ InitWeaponStatusRuis( player )
+}
+
+void function UpdateOffhandRuis( entity player )
+{
+ UpdateOffhandRuiVisibility( file.ability_left_hud, "%offhand1%" )
+
+ if ( CheckCenterSlotOccupied(player) )
+ UpdateOffhandRuiVisibility( file.ability_center_hud, "%offhand2%" )
+ else
+ UpdateOffhandRuiVisibility( file.ability_center_hud, "%offhand0%" )
+
+ UpdateOffhandRuiVisibility( file.ability_right_hud, "%offhand0%" )
+
+ if ( IsMultiplayer() )
+ {
+ // need to recreate this since RuiTrackInt cannot be undone with RuiSetInt
+ RuiDestroy( file.dpad_left_hud )
+ var rui = CreateCockpitRui( $"ui/inventory_hud.rpak" )
+ RuiSetInt( rui, "xPos", 1 )
+ RuiSetInt( rui, "yPos", 1 )
+ file.dpad_left_hud = rui
+ UpdateOffhandRuiVisibility( file.dpad_left_hud, "%offhand4%" )
+ }
+ else
+ {
+ UpdateOffhandRuiVisibility( file.dpad_left_hud, "%weaponSelectOrdnance%" )
+ }
+}
+
+void function InitWeaponStatusRuis( entity player )
+{
+ player.Signal( "EndTrackOffhandWeaponSlot" )
+
+ UpdateOffhandRuis( player )
+
+ if ( CheckCenterSlotOccupied(player) )
+ {
+ thread TrackOffhandWeaponSlot( player, file.ability_left_hud, OFFHAND_LEFT )
+ thread TrackOffhandWeaponSlot( player, file.ability_center_hud, OFFHAND_TITAN_CENTER )
+ thread TrackOffhandWeaponSlot( player, file.ability_right_hud, OFFHAND_RIGHT )
+ if ( IsMultiplayer() )
+ thread TrackOffhandWeaponSlot( player, file.dpad_left_hud, OFFHAND_INVENTORY )
+ }
+ else
+ {
+ thread TrackOffhandWeaponSlot( player, file.ability_left_hud, OFFHAND_LEFT )
+ thread TrackOffhandWeaponSlot( player, file.ability_center_hud, OFFHAND_RIGHT )
+ if ( IsMultiplayer() )
+ {
+ thread TrackOffhandWeaponSlot( player, file.dpad_left_hud, OFFHAND_INVENTORY )
+ //thread TrackHoldToSwapSlot( player, file.ammo_counter )
+ }
+ else
+ {
+ thread TrackATWeaponSlot( player, file.dpad_left_hud )
+ }
+ RuiSetBool( file.ability_right_hud, "isVisible", false )
+ }
+
+ thread TrackCenterSlot( player )
+}
+
+void function UpdateOffhandRuiVisibility( var rui, string hintText )
+{
+ if ( GetConVarInt( "hud_setting_showButtonHints" ) != 0 )
+ RuiSetString( rui, "hintText", hintText )
+ else
+ RuiSetString( rui, "hintText", "" )
+}
+
+void function UpdateForChangedCenter( entity player )
+{
+ player.Signal( "EndTrackCenterStuff" )
+
+ bool centerOccupied = CheckCenterSlotOccupied(player)
+
+ if ( centerOccupied )
+ {
+ UpdateOffhandRuiVisibility( file.ability_center_hud, "%offhand2%" )
+
+ thread TrackOffhandWeaponSlot( player, file.ability_center_hud, OFFHAND_TITAN_CENTER )
+ thread TrackOffhandWeaponSlot( player, file.ability_right_hud, OFFHAND_RIGHT )
+ }
+ else
+ {
+ UpdateOffhandRuiVisibility( file.ability_center_hud, "%offhand0%" )
+
+ thread TrackOffhandWeaponSlot( player, file.ability_center_hud, OFFHAND_RIGHT )
+ }
+
+ RuiSetBool( file.ammo_counter, "isTitan", centerOccupied )
+}
+
+void function TrackCenterSlot( entity player )
+{
+ player.EndSignal( "EndTrackOffhandWeaponSlot" )
+ player.EndSignal( "OnDeath" )
+
+ entity lastWeapon = null
+ while ( IsAlive( player ) )
+ {
+ entity activeWeapon = player.GetOffhandWeapon( OFFHAND_TITAN_CENTER )
+ if ( activeWeapon != lastWeapon )
+ {
+ UpdateForChangedCenter( player )
+ }
+
+ lastWeapon = activeWeapon
+ WaitFrame()
+ }
+}
+
+void function TrackHoldToSwapSlot( entity player, var rui )
+{
+ player.EndSignal( "EndTrackOffhandWeaponSlot" )
+ player.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( rui )
+ {
+ //RuiSetBool( rui, "isVisible", false )
+ }
+ )
+
+ float holdWeaponSwapTime = GetConVarFloat( "holdWeaponSwapTime" )
+ RuiSetFloat( rui, "holdWeaponSwapTime", holdWeaponSwapTime )
+ RuiSetFloat( rui, "holdTime", 0.0 )
+ RuiSetBool( rui, "holdHintVisible", false )
+
+ entity lastWeapon = null
+ bool holdWeaponActive = false
+ bool lastIsHolding = false
+ float holdChangeTime = 0.0
+ while ( IsAlive( player ) )
+ {
+ entity activeWeapon = player.GetActiveWeapon()
+ if ( activeWeapon != lastWeapon )
+ {
+ array<entity> mainWeapons = player.GetMainWeapons()
+ if ( mainWeapons.len() > 2 )
+ {
+ if ( mainWeapons[2] == activeWeapon )
+ {
+ holdWeaponActive = true
+ }
+ else
+ {
+ holdWeaponActive = false
+ }
+ }
+ }
+
+ if ( !holdWeaponActive )
+ {
+ bool isHolding = player.IsInputCommandHeld( IN_WEAPON_CYCLE )
+ if ( isHolding != lastIsHolding )
+ holdChangeTime = Time()
+
+ if ( isHolding )
+ {
+ RuiSetFloat( rui, "holdTime", Time() - holdChangeTime )
+ }
+ else
+ {
+ RuiSetFloat( rui, "holdTime", 0.0 )
+ }
+ lastIsHolding = isHolding
+ }
+ else
+ {
+ RuiSetFloat( rui, "holdTime", 0.0 )
+ }
+ RuiSetBool( rui, "holdHintVisible", IsControllerModeActive() )
+
+ lastWeapon = activeWeapon
+ WaitFrame()
+ }
+}
+
+void function TrackOffhandWeaponSlot( entity player, var rui, int slot )
+{
+ if ( slot == OFFHAND_TITAN_CENTER || slot == OFFHAND_RIGHT )
+ {
+ player.EndSignal( "EndTrackCenterStuff" )
+ }
+
+ player.EndSignal( "EndTrackOffhandWeaponSlot" )
+ player.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( rui )
+ {
+ RuiSetBool( rui, "isVisible", false )
+ }
+ )
+
+ entity lastWeapon = null
+ bool wasVisible = file.slotVisible[slot]
+ while ( IsAlive( player ) )
+ {
+ entity weapon = player.GetOffhandWeapon( slot )
+ if ( weapon != lastWeapon || file.slotVisible[slot] != wasVisible )
+ {
+ if ( IsValid( weapon ) && file.slotVisible[slot] )
+ {
+ InitOffhandRui( rui, player, weapon )
+ #if MP
+ if ( slot == OFFHAND_INVENTORY )
+ {
+ if ( player.IsTitan() )
+ {
+ int segments = player.GetWeaponAmmoStockpile( player.GetOffhandWeapon( OFFHAND_INVENTORY ) )
+ segments += player.GetOffhandWeapon( OFFHAND_INVENTORY ).GetWeaponPrimaryClipCount()
+ RuiSetInt( rui, "segments", segments )
+ RuiSetFloat( rui, "minFireFrac", 0.01 )
+ }
+ else
+ {
+ //RuiSetInt( rui, "segments", PlayerInventory_ItemCount( player ) )
+ RuiTrackInt( rui, "segments", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "itemInventoryCount" ) )
+ RuiSetFloat( rui, "minFireFrac", 0.01 )
+ }
+ }
+ #elseif SP
+ if ( slot == OFFHAND_INVENTORY )
+ {
+ RuiSetInt( rui, "segments", 1 )
+ RuiSetFloat( rui, "minFireFrac", 0.01 )
+ }
+ #endif
+ }
+ else
+ {
+ RuiSetBool( rui, "isVisible", false )
+ }
+ }
+ #if MP
+ if ( slot == OFFHAND_INVENTORY && file.slotVisible[slot] && player.IsTitan() && IsValid( weapon ) )
+ {
+ int segments = player.GetWeaponAmmoStockpile( player.GetOffhandWeapon( OFFHAND_INVENTORY ) )
+ segments += player.GetOffhandWeapon( OFFHAND_INVENTORY ).GetWeaponPrimaryClipCount()
+ RuiSetInt( rui, "segments", segments )
+ RuiSetFloat( rui, "minFireFrac", 0.01 )
+ }
+ #endif
+
+ lastWeapon = weapon
+ wasVisible = file.slotVisible[slot]
+ WaitFrame()
+ }
+}
+
+void function TrackATWeaponSlot( entity player, var rui )
+{
+ player.EndSignal( "EndTrackOffhandWeaponSlot" )
+ player.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( rui )
+ {
+ RuiSetBool( rui, "isVisible", false )
+ }
+ )
+
+ entity lastWeapon = null
+ bool wasVisible = file.slotVisible[OFFHAND_INVENTORY]
+ bool wasHoldingWeapon = player.GetActiveWeapon() == player.GetAntiTitanWeapon()
+ while ( IsAlive( player ) )
+ {
+ entity weapon = player.GetAntiTitanWeapon()
+ entity activeWeapon = player.GetActiveWeapon()
+ bool isHoldingWeapon = weapon == activeWeapon
+ if ( weapon != lastWeapon || file.slotVisible[OFFHAND_INVENTORY] != wasVisible || isHoldingWeapon != wasHoldingWeapon )
+ {
+ if ( IsValid( weapon ) && file.slotVisible[OFFHAND_INVENTORY] && !isHoldingWeapon )
+ {
+ InitOffhandRui( rui, player, weapon )
+ RuiSetFloat2( rui, "iconSizeScale", <1.5,0.75,0> )
+ }
+ else
+ {
+ RuiSetBool( rui, "isVisible", false )
+ }
+ }
+
+ lastWeapon = weapon
+ wasVisible = file.slotVisible[OFFHAND_INVENTORY]
+ wasHoldingWeapon = isHoldingWeapon
+ WaitFrame()
+ }
+}
+/*
+ // VECTOR TYPES
+ RUI_TRACK_ABSORIGIN_FOLLOW, // Create at absorigin, and update to follow the entity
+ RUI_TRACK_POINT_FOLLOW, // Create on attachment point, and update to follow the entity
+ RUI_TRACK_OVERHEAD_FOLLOW, // Create at the top of the entity's bbox
+ RUI_TRACK_EYEANGLES_FOLLOW,
+
+ // FLOAT TYPES
+ RUI_TRACK_HEALTH, // Health as fraction from 0 to 1
+ RUI_TRACK_FRIENDLINESS, // 0 if ent is enemy, 1 if it's friendly
+ RUI_TRACK_PLAYER_SUIT_POWER, // Player's suit power from 0 to 1
+ RUI_TRACK_PLAYER_GRAPPLE_POWER, // Player's grapple power from 0 to 1
+ RUI_TRACK_PLAYER_SHARED_ENERGY, // Players shared energy value
+ RUI_TRACK_WEAPON_CHARGE_FRACTION, // Weapon charge as fraction from 0 to 1
+ RUI_TRACK_WEAPON_SMART_AMMO_LOCK_FRACTION, // Smart ammo weapon lock fraction from 0 to N
+ RUI_TRACK_WEAPON_READY_TO_FIRE_FRACTION, // Weapon cooldown as fraction from 0 to 1
+ RUI_TRACK_WEAPON_RELOAD_FRACTION, // Weapon reloading as fraction from 0 to 1
+ RUI_TRACK_WEAPON_DRYFIRE_FRACTION, //
+ RUI_TRACK_WEAPON_CLIP_AMMO_FRACTION, // Weapon clip ammo as fraction from 0 to 1
+ RUI_TRACK_WEAPON_REMAINING_AMMO_FRACTION, // Weapon remaining ammo as fraction from 0 to 1
+ RUI_TRACK_WEAPON_CLIP_AMMO_MAX, //
+ RUI_TRACK_WEAPON_STOCKPILE_AMMO_MAX, //
+ RUI_TRACK_WEAPON_LIFETIME_SHOTS, //
+ RUI_TRACK_WEAPON_AMMO_REGEN_RATE, //
+ RUI_TRACK_BOOST_METER_FRACTION, // Player boost meter as fraction from 0 to 1
+ RUI_TRACK_GLIDE_METER_FRACTION, // Player glide meter as fraction from 0 to 1
+ RUI_TRACK_SHIELD_FRACTION, // Shield health as fraction from 0 to 1
+ RUI_TRACK_STATUS_EFFECT_SEVERITY, // Status effect severity as fraction from 0 to 1; attachmentIndex used as status effect index
+ RUI_TRACK_SCRIPT_NETWORK_VAR, // Value of a script network variable (use GetNetworkedVariableIndex())
+ RUI_TRACK_SCRIPT_NETWORK_VAR_GLOBAL, // Value of a script network variable without an entity (use GetNetworkedVariableIndex())
+ RUI_TRACK_SCRIPT_NETWORK_VAR_LOCAL_VIEW_PLAYER, // Value of a script network variable on the local view player (changes automatically during kill replay) (use GetNetworkedVariableIndex())
+ RUI_TRACK_FRIENDLY_TEAM_SCORE, //
+ RUI_TRACK_FRIENDLY_TEAM_ROUND_SCORE, // The value of score2 for friendlies
+ RUI_TRACK_ENEMY_TEAM_SCORE, //
+ RUI_TRACK_ENEMY_TEAM_ROUND_SCORE, // The value of score2 for enemies
+ RUI_TRACK_MINIMAP_SCALE, //
+ RUI_TRACK_SOUND_METER, // Sound meter as fraction from 0 to 1.
+
+ // INT TYPES
+ RUI_TRACK_MINIMAP_FLAGS,
+ RUI_TRACK_MINIMAP_CUSTOM_STATE,
+ RUI_TRACK_TEAM_RELATION_VIEWPLAYER, // ENEMY: -1, NEUTRAL: 0, FRIENDLY: 1
+ RUI_TRACK_TEAM_RELATION_CLIENTPLAYER, // ENEMY: -1, NEUTRAL: 0, FRIENDLY: 1
+ RUI_TRACK_SCRIPT_NETWORK_VAR_INT, // Value of a script network variable (use GetNetworkedVariableIndex())
+ RUI_TRACK_SCRIPT_NETWORK_VAR_GLOBAL_INT, // Value of a script network variable without an entity (use GetNetworkedVariableIndex())
+ RUI_TRACK_SCRIPT_NETWORK_VAR_LOCAL_VIEW_PLAYER_INT, // Value of a script network variable on the local view player (changes automatically during kill replay) (use GetNetworkedVariableIndex())
+
+ // GAMETIME TYPES
+ RUI_TRACK_LAST_FIRED_TIME,
+ RUI_TRACK_MINIMAP_THREAT_SECTOR,
+
+ // IMAGE TYPES
+ RUI_TRACK_WEAPON_MENU_ICON,
+ RUI_TRACK_WEAPON_HUD_ICON,
+*/
+
+void function InitOffhandRui( var rui, entity player, entity weapon )
+{
+ RuiSetGameTime( rui, "hintTime", Time() )
+
+ RuiSetBool( rui, "isTitan", player.IsTitan() )
+ RuiSetBool( rui, "isVisible", true )
+ RuiSetBool( rui, "isReverseCharge", false )
+
+ RuiSetFloat( rui, "chargeFrac", 0.0 )
+ RuiSetFloat( rui, "useFrac", 0.0 )
+ RuiSetFloat( rui, "chargeMaxFrac", 1.0 )
+ RuiSetFloat( rui, "minFireFrac", 1.0 )
+ RuiSetInt( rui, "segments", 1 )
+ RuiSetFloat( rui, "refillRate", 1 ) // default to 1 to preserve default behavior. some abilities draw the refillRecharge, even without a rate setting
+
+ RuiTrackImage( rui, "hudIcon", weapon, RUI_TRACK_WEAPON_HUD_ICON )
+
+ RuiTrackFloat( rui, "readyFrac", weapon, RUI_TRACK_WEAPON_READY_TO_FIRE_FRACTION )
+ RuiTrackFloat( rui, "dryfireFrac", weapon, RUI_TRACK_WEAPON_DRYFIRE_FRACTION )
+
+ RuiSetFloat( rui, "chargeFracCaution", 0.0 )
+ RuiSetFloat( rui, "chargeFracAlert", 0.0 )
+ RuiSetFloat( rui, "chargeFracAlertSpeed", 16.0 )
+ RuiSetFloat( rui, "chargeFracAlertScale", 1.0 )
+
+ switch ( weapon.GetWeaponInfoFileKeyField( "cooldown_type" ) )
+ {
+ case "ammo":
+ int ammoClipSize = weapon.GetWeaponSettingInt( eWeaponVar.ammo_clip_size )
+ int ammoPerShot = weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot )
+ int ammoMinToFire = weapon.GetWeaponSettingInt( eWeaponVar.ammo_min_to_fire )
+
+ RuiSetFloat( rui, "minFireFrac", float ( ammoMinToFire ) / float ( ammoClipSize ) )
+ RuiSetInt( rui, "segments", ammoClipSize / ammoPerShot )
+
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CLIP_AMMO_FRACTION )
+
+ RuiSetFloat( rui, "refillRate", weapon.GetWeaponSettingFloat( eWeaponVar.regen_ammo_refill_rate ) )
+ break
+
+ case "ammo_swordblock":
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_STOCKPILE_REGEN_FRAC )
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+ RuiSetFloat( rui, "minFireFrac", 0.0 )
+
+ RuiSetFloat( rui, "chargeFracCaution", 0.6 )
+ RuiSetFloat( rui, "chargeFracAlert", 0.0 )
+ break
+
+ case "ammo_alert":
+ RuiSetFloat( rui, "chargeFrac", 0.0 )
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+ RuiSetFloat( rui, "minFireFrac", 0.0 )
+
+ RuiSetFloat( rui, "chargeFracCaution", 0.01 )
+ RuiSetFloat( rui, "chargeFracAlert", -1.0 )
+ RuiSetFloat( rui, "chargeFracAlertSpeed", 5.0 )
+ RuiSetFloat( rui, "chargeFracAlertScale", 0.6 )
+ break
+
+ case "ammo_instant":
+ case "ammo_deployed":
+ int ammoClipSize = weapon.GetWeaponSettingInt( eWeaponVar.ammo_clip_size )
+ int ammoPerShot = weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot )
+ int ammoMinToFire = weapon.GetWeaponSettingInt( eWeaponVar.ammo_min_to_fire )
+
+ RuiSetFloat( rui, "minFireFrac", float ( ammoMinToFire ) / float ( ammoClipSize ) )
+ RuiSetInt( rui, "segments", ammoClipSize / ammoPerShot )
+
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CLIP_AMMO_FRACTION )
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+
+ RuiSetFloat( rui, "refillRate", weapon.GetWeaponSettingFloat( eWeaponVar.regen_ammo_refill_rate ) )
+ break
+
+ case "ammo_per_shot":
+ int ammoClipSize = weapon.GetWeaponSettingInt( eWeaponVar.ammo_clip_size )
+ int ammoPerShot = weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot )
+ int ammoMinToFire = weapon.GetWeaponSettingInt( eWeaponVar.ammo_min_to_fire )
+
+ RuiSetFloat( rui, "minFireFrac", float ( ammoMinToFire ) / float ( ammoClipSize ) )
+ RuiSetInt( rui, "segments", 1 )
+
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CLIP_AMMO_FRACTION )
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+
+ RuiSetFloat( rui, "refillRate", weapon.GetWeaponSettingFloat( eWeaponVar.regen_ammo_refill_rate ) )
+ break
+
+ case "ammo_timed":
+ int ammoClipSize = weapon.GetWeaponSettingInt( eWeaponVar.ammo_clip_size )
+ int ammoPerShot = weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot )
+ int ammoMinToFire = weapon.GetWeaponSettingInt( eWeaponVar.ammo_min_to_fire )
+
+ RuiSetFloat( rui, "minFireFrac", float ( ammoMinToFire ) / float ( ammoClipSize ) )
+ RuiSetInt( rui, "segments", ammoClipSize / ammoPerShot )
+
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CLIP_AMMO_FRACTION )
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+
+ RuiTrackFloat( rui, "useFrac", weapon, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.simple_timer )
+
+ RuiSetFloat( rui, "refillRate", weapon.GetWeaponSettingFloat( eWeaponVar.regen_ammo_refill_rate ) )
+ break
+
+ case "shared_energy":
+ int curCost = weapon.GetWeaponCurrentEnergyCost() // 350
+
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+ RuiSetFloat( rui, "chargeMaxFrac", float( curCost ) )
+ RuiTrackFloat( rui, "chargeFrac", player, RUI_TRACK_PLAYER_SHARED_ENERGY )
+ break
+
+ case "shared_energy_drain":
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+ RuiSetFloat( rui, "minFireFrac", 0.0 )
+ RuiTrackFloat( rui, "chargeFrac", player, RUI_TRACK_PLAYER_SHARED_ENERGY )
+ RuiSetFloat( rui, "chargeMaxFrac", float( ION_ENERGY_MAX ) )
+ break
+
+ case "vortex_drain":
+ RuiSetBool( rui, "isReverseCharge", true )
+ RuiSetFloat( rui, "chargeFrac", 1.0 )
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+ RuiSetFloat( rui, "minFireFrac", 0.0 )
+
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CHARGE_FRACTION )
+ break
+
+ case "charged_shot":
+ RuiSetBool( rui, "isReverseCharge", true )
+ RuiSetFloat( rui, "chargeFrac", 1.0 )
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+ RuiSetFloat( rui, "minFireFrac", 0.2 )
+
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CHARGE_FRACTION )
+ break
+
+ case "chargeFrac":
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CHARGE_FRACTION )
+ break
+
+ case "smart":
+ RuiSetBool( rui, "isReverseCharge", true )
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CHARGE_FRACTION )
+ RuiSetFloat( rui, "readyFrac", 0.0 )
+ RuiTrackFloat( rui, "dryfireFrac", weapon, RUI_TRACK_WEAPON_DRYFIRE_FRACTION )
+ break
+
+ case "debug":
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CHARGE_FRACTION )
+ RuiTrackFloat( rui, "readyFrac", weapon, RUI_TRACK_WEAPON_READY_TO_FIRE_FRACTION )
+ //RuiTrackFloat( rui, "dryfireFrac", weapon, RUI_TRACK_WEAPON_DRYFIRE_FRACTION )
+ RuiTrackFloat( rui, "dryfireFrac", weapon, RUI_TRACK_WEAPON_SMART_AMMO_LOCK_FRACTION )
+ break
+
+ case "grapple":
+ int ammoClipSize = 100
+ float ammoMinToFire = weapon.GetWeaponSettingFloat( eWeaponVar.grapple_power_required )
+
+ RuiSetFloat( rui, "minFireFrac", ammoMinToFire / float ( ammoClipSize ) )
+ RuiSetInt( rui, "segments", int( ammoClipSize / ammoMinToFire ) )
+ RuiTrackFloat( rui, "chargeFrac", player, RUI_TRACK_PLAYER_GRAPPLE_POWER )
+ break
+
+ default:
+ float refillRate = weapon.GetWeaponSettingFloat( eWeaponVar.regen_ammo_refill_rate )
+
+ if ( refillRate > 0 )
+ {
+ //printt( "HUD: ", weapon.GetWeaponClassName(), "using", "refillRate" )
+ int ammoClipSize = weapon.GetWeaponSettingInt( eWeaponVar.ammo_clip_size )
+ int ammoPerShot = weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot )
+ int ammoMinToFire = weapon.GetWeaponSettingInt( eWeaponVar.ammo_min_to_fire )
+
+ RuiSetFloat( rui, "minFireFrac", float ( ammoMinToFire ) / float ( ammoClipSize ) )
+ RuiSetInt( rui, "segments", ammoClipSize / ammoPerShot )
+
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CLIP_AMMO_FRACTION )
+ }
+ else
+ {
+ float chargeTime = weapon.GetWeaponSettingFloat( eWeaponVar.charge_time )
+ if ( chargeTime == 0 )
+ {
+ //printt( "HUD: ", weapon.GetWeaponClassName(), "using", "chargeTime == 0" )
+ float fireDuration = weapon.GetWeaponSettingFloat( eWeaponVar.fire_duration )
+ printt( weapon.GetWeaponClassName(), fireDuration )
+ RuiSetBool( rui, "isReverseCharge", true )
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_READY_TO_FIRE_FRACTION )
+ }
+ else
+ {
+ //printt( "HUD: ", weapon.GetWeaponClassName(), "using", "chargeTime" )
+ RuiTrackFloat( rui, "chargeFrac", weapon, RUI_TRACK_WEAPON_CHARGE_FRACTION )
+ }
+ }
+ break
+ }
+}
+
+void function ClWeaponStatus_SetOffhandVisible( int offhandIndex, bool newState )
+{
+ entity player = GetLocalClientPlayer()
+
+ var rui = GetRuiForIndex( player, offhandIndex )
+
+ file.slotVisible[offhandIndex] = newState
+ RuiSetBool( rui, "isVisible", newState )
+}
+
+var function GetRuiForIndex( entity player, int offhandIndex )
+{
+ var rui
+
+ if ( CheckCenterSlotOccupied(player) )
+ {
+ switch ( offhandIndex )
+ {
+ case OFFHAND_LEFT:
+ rui = file.ability_left_hud
+ break
+ case OFFHAND_TITAN_CENTER:
+ rui = file.ability_center_hud
+ break
+ case OFFHAND_RIGHT:
+ rui = file.ability_right_hud
+ break
+ case OFFHAND_INVENTORY:
+ rui = file.dpad_left_hud
+ break
+ }
+ }
+ else
+ {
+ switch ( offhandIndex )
+ {
+ case OFFHAND_LEFT:
+ rui = file.ability_left_hud
+ break
+ case OFFHAND_RIGHT:
+ rui = file.ability_center_hud
+ break
+ case OFFHAND_INVENTORY:
+ rui = file.dpad_left_hud
+ break
+ }
+ }
+
+ return rui
+}
+
+void function ClWeaponStatus_SetWeaponVisible( bool newState )
+{
+ RuiSetBool( file.ammo_counter, "isVisible", newState )
+ file.ammo_counter_visible = newState
+}
+
+bool function CheckCenterSlotOccupied( entity player )
+{
+ entity weapon = player.GetOffhandWeapon( OFFHAND_TITAN_CENTER )
+ return IsValid( weapon )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut b/Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut
index e629e5aea..850855a0f 100644
--- a/Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut
@@ -23,7 +23,7 @@ bool function ClientCommandCallbackToggleNoclip( entity player, array<string> ar
bool function ClientCommandCallbackKill( entity player, array<string> args )
{
- if ( IsAlive( player ) && GetConVarInt( "sv_cheats" ) == 1 )
+ if ( IsAlive( player ) )
player.Die()
return true
@@ -31,8 +31,8 @@ bool function ClientCommandCallbackKill( entity player, array<string> args )
bool function ClientCommandCallbackExplode( entity player, array<string> args )
{
- if ( IsAlive( player ) && GetConVarInt( "sv_cheats" ) == 1 )
+ if ( IsAlive( player ) )
player.Die( null, null, { scriptType = DF_GIB } )
return true
-} \ No newline at end of file
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut
index b7c225c78..f64658cf5 100644
--- a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut
@@ -68,7 +68,7 @@ void function ServerCallback_FastballPanelHacked( int panelHandle, int id, int c
AnnouncementData announcement = Announcement_Create( Localize( "#FASTBALL_PANEL_CAPTURED", capturingPlayer.GetPlayerName(), panelIdString ) )
- if ( capturingPlayer.GetTeam() == GetLocalViewPlayer().GetTeam() )
+ if ( capturingPlayer.GetTeam() == GetLocalClientPlayer().GetTeam() )
Announcement_SetTitleColor( announcement, < 0, 0, 1 > )
else
Announcement_SetTitleColor( announcement, < 1, 0, 0 > )
@@ -77,7 +77,7 @@ void function ServerCallback_FastballPanelHacked( int panelHandle, int id, int c
Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc
Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
- AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+ AnnouncementFromClass( GetLocalClientPlayer(), announcement )
}
void function ServerCallback_FastballRespawnPlayer()
@@ -88,7 +88,7 @@ void function ServerCallback_FastballRespawnPlayer()
void function FastballRespawnPlayerEffects_Threaded()
{
// sometimes this seems to get called before the player has respawned clientside, so we just wait until the client thinks they're alive
- entity player = GetLocalViewPlayer()
+ entity player = GetLocalClientPlayer()
while ( !IsAlive( player ) )
WaitFrame()
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut
index 1c776ede2..11cb71f63 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut
@@ -1,6 +1,5 @@
untyped
global function GamemodeMfd_Init
-global function RateSpawnpoints_Frontline
struct {
entity imcLastMark
@@ -191,59 +190,4 @@ void function UpdateMarksForKill( entity victim, entity attacker, var damageInfo
if ( attacker.IsPlayer() )
attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 )
}
-}
-
-// could probably put this in spawn.nut? only here because i believe it's the main mode that uses this func
-void function RateSpawnpoints_Frontline( int checkClass, array<entity> spawnpoints, int team, entity player )
-{
- Frontline frontline = GetFrontline( player.GetTeam() )
-
- // heavily based on ctf spawn algo iteration 4, only changes it at the end
- array<entity> startSpawns = SpawnPoints_GetPilotStart( team )
- array<entity> enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) )
- array<entity> enemyPlayers = GetPlayerArrayOfTeam_Alive( team )
-
- // get average startspawn position and max dist between spawns
- // could probably cache this, tbh, not like it should change outside of halftimes
- vector averageFriendlySpawns
- float maxFriendlySpawnDist
-
- foreach ( entity spawn in startSpawns )
- {
- foreach ( entity otherSpawn in startSpawns )
- {
- float dist = Distance2D( spawn.GetOrigin(), otherSpawn.GetOrigin() )
- if ( dist > maxFriendlySpawnDist )
- maxFriendlySpawnDist = dist
- }
-
- averageFriendlySpawns += spawn.GetOrigin()
- }
-
- averageFriendlySpawns /= startSpawns.len()
-
- // get average enemy startspawn position
- vector averageEnemySpawns
-
- foreach ( entity spawn in enemyStartSpawns )
- averageEnemySpawns += spawn.GetOrigin()
-
- averageEnemySpawns /= enemyStartSpawns.len()
-
- // from here, rate spawns
- float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns )
- float spawnIterations = ( baseDistance / maxFriendlySpawnDist ) / 2
-
- foreach ( entity spawn in spawnpoints )
- {
- // ratings should max/min out at 100 / -100
- // start by prioritizing closer spawns, but not so much that enemies won't really affect them
- float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawn.GetOrigin() ) / baseDistance )
-
- // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir
- rating += rating * ( 1.0 - ( Distance2D( spawn.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) )
- rating *= fabs( frontline.combatDir.y - Normalize( spawn.GetOrigin() - averageFriendlySpawns ).y )
-
- spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating )
- }
} \ 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 a02b9072e..57355ad8b 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut
@@ -1,6 +1,6 @@
untyped
global function GamemodePs_Init
-global function RateSpawnpoints_SpawnZones
+//global function RateSpawnpoints_SpawnZones
struct {
array<entity> spawnzones
@@ -21,8 +21,10 @@ void function GamemodePs_Init()
SetTimeoutWinnerDecisionFunc( CheckScoreForDraw )
// spawnzone stuff
- AddCallback_OnPlayerKilled( CheckSpawnzoneSuspiciousDeaths )
- AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", SpawnzoneTriggerInit )
+ SetShouldCreateMinimapSpawnZones( true )
+
+ //AddCallback_OnPlayerKilled( CheckSpawnzoneSuspiciousDeaths )
+ //AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", SpawnzoneTriggerInit )
file.militiaPreviousSpawnZones = [ null, null, null ]
file.imcPreviousSpawnZones = [ null, null, null ]
@@ -30,20 +32,8 @@ void function GamemodePs_Init()
void function GiveScoreForPlayerKill( entity victim, entity attacker, var damageInfo )
{
- if ( victim != attacker && victim.IsPlayer() && attacker.IsPlayer() && GetGameState() == eGameState.Playing )
+ if ( victim != attacker && victim.IsPlayer() && attacker.IsPlayer() || GetGameState() != eGameState.Playing )
AddTeamScore( attacker.GetTeam(), 1 )
-
- table<int, bool> alreadyAssisted
- foreach( DamageHistoryStruct attackerInfo in victim.e.recentDamageHistory )
- {
- bool exists = attackerInfo.attacker.GetEncodedEHandle() in alreadyAssisted ? true : false
- if( attackerInfo.attacker != attacker && !exists )
- {
- alreadyAssisted[attackerInfo.attacker.GetEncodedEHandle()] <- true
- attackerInfo.attacker.AddToPlayerGameStat( PGS_ASSISTS, 1 )
- }
- }
-
}
int function CheckScoreForDraw()
@@ -54,180 +44,4 @@ int function CheckScoreForDraw()
return TEAM_MILITIA
return TEAM_UNASSIGNED
-}
-
-// spawnzone logic
-void function SpawnzoneTriggerInit( entity spawnzone )
-{
- // initialise spawnzone script vars
- spawnzone.s.lastDeathRateCheck <- 0.0
- spawnzone.s.numRecentSuspiciousDeaths <- 0
- spawnzone.s.minimapObj <- null
-
- file.spawnzones.append( spawnzone )
-}
-
-void function SetNewSpawnzoneForTeam( int team, entity spawnzone )
-{
- 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_SetAlignUpright( true )
- minimapObj.Minimap_AlwaysShow( TEAM_IMC, null )
- minimapObj.Minimap_AlwaysShow( TEAM_MILITIA, null )
- minimapObj.Minimap_SetHeightTracking( true )
- minimapObj.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
-
- if ( team == TEAM_IMC )
- {
- if ( IsValid( file.imcActiveSpawnZone ) )
- file.imcActiveSpawnZone.s.minimapObj.Destroy()
-
- // update last 3 zones
- file.imcPreviousSpawnZones[ 2 ] = file.imcPreviousSpawnZones[ 1 ]
- file.imcPreviousSpawnZones[ 1 ] = file.imcPreviousSpawnZones[ 0 ]
- file.imcPreviousSpawnZones[ 0 ] = file.imcActiveSpawnZone
-
- file.imcActiveSpawnZone = spawnzone
- minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_IMC )
- }
- else
- {
- if ( IsValid( file.militiaActiveSpawnZone ) )
- file.militiaActiveSpawnZone.s.minimapObj.Destroy()
-
- // update last 3 zones
- file.militiaPreviousSpawnZones[ 2 ] = file.militiaPreviousSpawnZones[ 1 ]
- file.militiaPreviousSpawnZones[ 1 ] = file.militiaPreviousSpawnZones[ 0 ]
- file.militiaPreviousSpawnZones[ 0 ] = file.militiaActiveSpawnZone
-
- file.militiaActiveSpawnZone = spawnzone
- minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_MIL )
- }
-
- minimapObj.DisableHibernation()
-
- spawnzone.s.minimapObj = minimapObj
- spawnzone.s.lastDeathRateCheck = 0.0
- spawnzone.s.numRecentSuspiciousDeaths = Time()
-}
-
-void function CheckSpawnzoneSuspiciousDeaths( entity victim, entity attacker, var damageInfo )
-{
- if ( victim.s.respawnTime + 10.0 < Time() )
- return
-
- entity spawnzone
- if ( victim.GetTeam() == TEAM_IMC )
- spawnzone = file.imcActiveSpawnZone
- else
- spawnzone = file.militiaActiveSpawnZone
-
- if ( Distance2D( victim.GetOrigin(), spawnzone.GetOrigin() ) <= Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) * 1.2 )
- spawnzone.s.numRecentSuspiciousDeaths++
-}
-
-entity function FindNewSpawnZone( int team )
-{
- array<entity> startSpawns = SpawnPoints_GetPilotStart( team )
- array<entity> enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) )
-
- // get average friendly startspawn position
- 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()
-
- array<entity> validZones
- array<entity> enemyPlayers = GetPlayerArrayOfTeam( GetOtherTeam( team ) )
- float averageFriendlySpawnDist
-
- foreach ( entity spawnzone in file.spawnzones )
- {
- if ( team == TEAM_IMC && file.imcPreviousSpawnZones.contains( spawnzone ) )
- continue
- else if ( file.militiaPreviousSpawnZones.contains( spawnzone ) )
- continue
-
- // check if it's too far from startspawns
- float friendlySpawnDist = Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns )
- if ( friendlySpawnDist > Distance2D( averageFriendlySpawns, averageEnemySpawns ) * 1.2 )
- continue
-
- // check if it's safe atm
- bool safe = true
- foreach ( entity enemy in enemyPlayers )
- {
- if ( Distance2D( enemy.GetOrigin(), spawnzone.GetOrigin() ) < Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) * 1.2 )
- {
- safe = false
- break
- }
- }
-
- if ( !safe )
- continue
-
- averageFriendlySpawnDist += friendlySpawnDist
- validZones.append( spawnzone )
- }
-
- averageFriendlySpawnDist /= validZones.len()
-
- array<entity> realValidZones = clone validZones
- foreach ( entity validzone in validZones )
- {
- if ( Distance2D( averageFriendlySpawns, validzone.GetOrigin() ) < averageFriendlySpawnDist * 1.4 )
- realValidZones.append( validzone )
- }
-
- entity spawnzone = realValidZones.getrandom()
- SetNewSpawnzoneForTeam( team, spawnzone )
-
- return spawnzone
-}
-
-void function RateSpawnpoints_SpawnZones( int checkClass, array<entity> spawnpoints, int team, entity player )
-{
- entity spawnzone
- if ( player.GetTeam() == TEAM_IMC )
- spawnzone = file.imcActiveSpawnZone
- else
- spawnzone = file.militiaActiveSpawnZone
-
- // spawnzones don't exist yet, create them now
- if ( !IsValid( spawnzone ) )
- spawnzone = FindNewSpawnZone( player.GetTeam() )
-
- // check if we should shift spawnzones
- // if it's been more than 15 seconds since last check, reset
- if ( spawnzone.s.lastDeathRateCheck + 15.0 < Time() )
- {
- spawnzone.s.numRecentSuspiciousDeaths = 0
- spawnzone.s.lastDeathRateCheck = Time()
- }
-
- // check if we've gone over the threshold for recent deaths too close to our current spawnzone
- if ( spawnzone.s.numRecentSuspiciousDeaths >= GetPlayerArrayOfTeam( player.GetTeam() ).len() * 0.4 )
- // over the threshold, find a new spawn zone
- spawnzone = FindNewSpawnZone( player.GetTeam() )
-
- // 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 ) )
-
- spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating )
- }
-}
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut
index 37b891699..40cf942ec 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_colony02.nut
@@ -1 +1,14 @@
-//fuck \ No newline at end of file
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+ AddEvacNode( CreateScriptRef( < -475.129913, 1480.167847, 527.363953 >, < 8.841560, 219.338501, 0 > ) )
+ AddEvacNode( CreateScriptRef( < 1009.315186, 3999.888916, 589.914917 >, < 23.945116, -146.680725, 0 > ) )
+ AddEvacNode( CreateScriptRef( < 2282.868896, -1363.706543, 846.188660 >, < 23.945116, -146.680725, 0 > ) )
+ AddEvacNode( CreateScriptRef( < 1911.771606, -752.053101, 664.741821 >, < 9.955260, 138.721191, 0 > ) )
+ AddEvacNode( CreateScriptRef( < 1985.563232, -1205.455078, 677.444763 >, < 13.809734, -239.877441, 0 > ) )
+ AddEvacNode( CreateScriptRef( < -59.625496, -1858.108887, 811.592407 >, < 20.556290, -252.775146, 0 > ) )
+ AddEvacNode( CreateScriptRef( < -1035.991211, -671.114380, 824.180908 >, < 16.220453, -24.511070, 0 > ) )
+
+ SetEvacSpaceNode( GetEnt( "intro_spacenode" ) )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
index 11587947f..3f3a05f2d 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
@@ -2,6 +2,8 @@ untyped
global function CodeCallback_MapInit
struct {
+ array<entity> marvinSpawners
+
float introStartTime
entity militiaPod
entity imcPod
@@ -20,13 +22,14 @@ void function CodeCallback_MapInit()
SetEvacSpaceNode( GetEnt( "end_spacenode" ) )
// dissolve effects
- AddDeathCallback( "player", WargamesDissolveDeadEntity )
+ AddDeathCallback( "player", WargamesDissolveDeadEntity )
AddDeathCallback( "npc_soldier", WargamesDissolveDeadEntity )
AddDeathCallback( "npc_spectre", WargamesDissolveDeadEntity )
AddDeathCallback( "npc_pilot_elite", WargamesDissolveDeadEntity )
AddDeathCallback( "npc_marvin", WargamesDissolveDeadEntity )
- FlagClear( "Disable_Marvins" )
+ AddSpawnCallback( "info_spawnpoint_marvin", AddMarvinSpawner )
+ AddCallback_GameStateEnter( eGameState.Prematch, SpawnMarvinsForRound )
// currently disabled until finished: intro
if ( !IsFFAGame() )
@@ -40,6 +43,44 @@ void function WargamesDissolveDeadEntity( entity deadEnt, var damageInfo )
{
deadEnt.Dissolve( ENTITY_DISSOLVE_CHAR, < 0, 0, 0 >, 0 )
EmitSoundAtPosition( TEAM_UNASSIGNED, deadEnt.GetOrigin(), "Object_Dissolve" )
+
+ if ( deadEnt.IsPlayer() )
+ thread EnsureWargamesDeathEffectIsClearedForPlayer( deadEnt )
+ }
+}
+
+void function EnsureWargamesDeathEffectIsClearedForPlayer( entity player )
+{
+ // this is slightly shit but whatever lol
+ player.EndSignal( "OnDestroy" )
+
+ float startTime = Time()
+ while ( player.kv.VisibilityFlags != "0" )
+ {
+ if ( Time() > startTime + 4.0 ) // if we wait too long, just ignore
+ return
+
+ WaitFrame()
+ }
+
+ player.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+}
+
+void function AddMarvinSpawner( entity spawn )
+{
+ file.marvinSpawners.append( spawn )
+}
+
+void function SpawnMarvinsForRound()
+{
+ foreach ( entity spawner in file.marvinSpawners )
+ {
+ entity marvin = CreateMarvin( TEAM_UNASSIGNED, spawner.GetOrigin(), spawner.GetAngles() )
+ marvin.kv.health = 1
+ marvin.kv.max_health = 1
+ marvin.kv.spawnflags = 516
+ DispatchSpawn( marvin )
+ HideName( marvin )
}
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
index 9b2935246..42e107e45 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
@@ -14,6 +14,13 @@ global function DeleteNoSpawnArea
global function FindSpawnPoint
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
struct NoSpawnArea
{
@@ -31,8 +38,6 @@ struct {
array< bool functionref( entity, int ) > customSpawnpointValidationRules
table<string, NoSpawnArea> noSpawnAreas
-
- array<vector> preferSpawnNodes
} file
void function Spawn_Init()
@@ -42,24 +47,12 @@ void function Spawn_Init()
AddSpawnCallback( "info_spawnpoint_titan", InitSpawnpoint )
AddSpawnCallback( "info_spawnpoint_titan_start", InitSpawnpoint )
+ // callbacks for generic spawns
AddCallback_EntitiesDidLoad( InitPreferSpawnNodes )
-}
-
-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
-
- file.preferSpawnNodes.append( hardpoint.GetOrigin() )
- }
- //foreach ( entity frontline in GetEntArrayByClass_Expensive( "info_frontline" ) )
- // file.preferSpawnNodes.append( frontline.GetOrigin() )
+ // callbacks for spawnzone spawns
+ AddCallback_GameStateEnter( eGameState.Prematch, ResetSpawnzones )
+ AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", AddSpawnZoneTrigger )
}
void function InitSpawnpoint( entity spawnpoint )
@@ -273,36 +266,32 @@ bool function IsSpawnpointValid( entity spawnpoint, int team )
return false
// los check
- array<entity> enemyLosPlayers
- if ( IsFFAGame() )
- enemyLosPlayers = GetPlayerArray()
- else
- enemyLosPlayers = GetPlayerArrayOfTeam( GetOtherTeam( team ) )
-
- foreach ( entity enemyPlayer in enemyLosPlayers )
- {
- if ( enemyPlayer.GetTeam() == team || !IsAlive( enemyPlayer ) )
- continue
-
- float dist = 1000.0
- // check fov, constant here is stolen from every other place this is done
- if ( VectorDot_PlayerToOrigin( enemyPlayer, spawnpoint.GetOrigin() ) > 0.8 )
- dist /= 0.75
-
- // check distance, constant here is basically arbitrary
- if ( Distance( enemyPlayer.GetOrigin(), spawnpoint.GetOrigin() ) > dist )
- continue
-
- // check actual los
- if ( TraceLineSimple( enemyPlayer.EyePosition(), spawnpoint.GetOrigin() + < 0, 0, 48 >, enemyPlayer ) == 1.0 )
- return false
- }
-
- return true
+ 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
@@ -311,7 +300,7 @@ void function RateSpawnpoints_Generic( int checkClass, array<entity> spawnpoints
// 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 file.preferSpawnNodes )
+ foreach ( vector preferSpawnNode in spawnStateGeneric.preferSpawnNodes )
{
float currentRating
@@ -354,13 +343,13 @@ void function RateSpawnpoints_Generic( int checkClass, array<entity> spawnpoints
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 < file.preferSpawnNodes.len(); i++ )
+ for ( int i = 0; i < spawnStateGeneric.preferSpawnNodes.len(); i++ )
{
// bonus if autotitan is nearish
- if ( IsAlive( player.GetPetTitan() ) && Distance( player.GetPetTitan().GetOrigin(), file.preferSpawnNodes[ i ] ) < 1200.0 )
+ if ( IsAlive( player.GetPetTitan() ) && Distance( player.GetPetTitan().GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] ) < 1200.0 )
petTitanModifier += 10.0
- float dist = Distance2D( spawnpoint.GetOrigin(), file.preferSpawnNodes[ i ] )
+ float dist = Distance2D( spawnpoint.GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] )
if ( dist > 750.0 )
continue
@@ -384,4 +373,301 @@ void function RateSpawnpoints_Generic( int checkClass, array<entity> spawnpoints
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() )
+ }
+
+ //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 frontline = GetFrontline( player.GetTeam() )
+
+ // heavily based on ctf spawn algo iteration 4, only changes it at the end
+ array<entity> startSpawns = SpawnPoints_GetPilotStart( team )
+ array<entity> enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) )
+
+ if ( startSpawns.len() == 0 || enemyStartSpawns.len() == 0 ) // ensure we don't crash
+ return
+
+ // get average startspawn position and max dist between spawns
+ // could probably cache this, tbh, not like it should change outside of halftimes
+ vector averageFriendlySpawns
+ float maxFriendlySpawnDist
+
+ foreach ( entity spawn in startSpawns )
+ {
+ foreach ( entity otherSpawn in startSpawns )
+ {
+ float dist = Distance2D( spawn.GetOrigin(), otherSpawn.GetOrigin() )
+ if ( dist > maxFriendlySpawnDist )
+ maxFriendlySpawnDist = dist
+ }
+
+ averageFriendlySpawns += spawn.GetOrigin()
+ }
+
+ averageFriendlySpawns /= startSpawns.len()
+
+ // get average enemy startspawn position
+ vector averageEnemySpawns
+
+ foreach ( entity spawn in enemyStartSpawns )
+ averageEnemySpawns += spawn.GetOrigin()
+
+ averageEnemySpawns /= enemyStartSpawns.len()
+
+ // from here, rate spawns
+ float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns )
+ foreach ( entity spawn in spawnpoints )
+ {
+ // ratings should max/min out at 100 / -100
+ // start by prioritizing closer spawns, but not so much that enemies won't really affect them
+ float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawn.GetOrigin() ) / baseDistance )
+
+ // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir
+ rating += rating * ( 1.0 - ( Distance2D( spawn.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) )
+ rating *= fabs( frontline.combatDir.y - Normalize( spawn.GetOrigin() - averageFriendlySpawns ).y )
+
+ spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating )
+ }
+}
+
+// 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()
+}
+
+void function AddSpawnZoneTrigger( entity trigger )
+{
+ trigger.s.spawnzoneRating <- 0.0
+ spawnStateSpawnzones.mapSpawnzoneTriggers.append( trigger )
+}
+
+void function SetSpawnZoneRatingFunc( entity functionref( array<entity>, int ) ratingFunc )
+{
+ spawnStateSpawnzones.spawnzoneRatingFunc = ratingFunc
+}
+
+void function SetShouldCreateMinimapSpawnZones( bool shouldCreateMinimapSpawnzones )
+{
+ spawnStateSpawnzones.shouldCreateMinimapSpawnzones = shouldCreateMinimapSpawnzones
+}
+
+entity 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_SetAlignUpright( true )
+ minimapObj.Minimap_AlwaysShow( TEAM_IMC, null )
+ minimapObj.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ minimapObj.Minimap_SetHeightTracking( true )
+ minimapObj.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+
+ if ( team == TEAM_IMC )
+ minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_IMC )
+ else
+ minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_MIL )
+
+ minimapObj.DisableHibernation()
+ return 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
+ {
+ 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 ) )
+
+ spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating )
+ }
+}
+
+entity function DecideSpawnZone_Generic( array<entity> spawnzones, int team )
+{
+ if ( spawnzones.len() == 0 )
+ 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 ) )
+
+ 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 )
+
+ 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
+ }
+
+ needNewZone = false
+ }
+
+ if ( needNewZone )
+ {
+ // find new zone
+ array<entity> possibleZones
+ foreach ( entity spawnzone in spawnStateSpawnzones.mapSpawnzoneTriggers )
+ {
+ // don't remeber 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 )
+ continue
+
+ // check zone validity
+ bool spawnzoneEvil = false
+ foreach ( entity player in GetPlayerArray() )
+ {
+ // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this
+ if ( player.GetTeam() != team && spawnzone.ContainsPoint( player.GetOrigin() ) )
+ {
+ spawnzoneEvil = true
+ break
+ }
+ }
+
+ // 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 > )
+ {
+ // 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 )
+ }
+
+ spawnzone.s.spawnzoneRating = rating
+ possibleZones.append( spawnzone )
+ }
+
+ if ( possibleZones.len() == 0 )
+ 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() ) ]
+
+ 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 ]
} \ No newline at end of file