aboutsummaryrefslogtreecommitdiff
path: root/Northstar.Custom/mod/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.Custom/mod/scripts')
-rw-r--r--Northstar.Custom/mod/scripts/levels/mp_box.rson8
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/_custom_air_accel.gnut23
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut79
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut54
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/_promode.gnut25
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_boost_store.gnut762
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut488
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/client/cl_gamestate.gnut1119
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_arena.gnut185
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut195
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut201
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut114
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut179
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_kr.gnut126
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_sbox.gnut49
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tt.gnut71
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_riff_instagib.gnut65
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut40
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut96
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut26
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut75
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut241
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut26
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_arena_loadouts.gnut98
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut58
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut0
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_ctf_comp.gnut114
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut47
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut73
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut165
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut58
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut55
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut20
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut36
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut262
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/lobby/sh_private_lobby_custom_modes_init.gnut11
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/mp/levels/mp_box.nut6
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/northstar_custom_autoprecache.gnut14
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/titan/sh_first_person_embark.gnut41
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/titan/sh_titan_embark.gnut2253
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_peacekraber.nut162
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_toolgun.nut39
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut30
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut24
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut196
-rw-r--r--Northstar.Custom/mod/scripts/weapons/melee_pilot_emptyhanded.txt149
-rw-r--r--Northstar.Custom/mod/scripts/weapons/melee_pilot_kunai.txt149
-rw-r--r--Northstar.Custom/mod/scripts/weapons/mp_weapon_peacekraber.txt347
-rw-r--r--Northstar.Custom/mod/scripts/weapons/mp_weapon_sniper.txt651
-rw-r--r--Northstar.Custom/mod/scripts/weapons/mp_weapon_toolgun.txt587
50 files changed, 9892 insertions, 0 deletions
diff --git a/Northstar.Custom/mod/scripts/levels/mp_box.rson b/Northstar.Custom/mod/scripts/levels/mp_box.rson
new file mode 100644
index 00000000..1a8d1ee7
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/levels/mp_box.rson
@@ -0,0 +1,8 @@
+//C:\depot\r2dev\game autofastfunction.pl
+
+IsTestMap: false
+When: "SERVER"
+Scripts:
+[
+ mp/levels/mp_box.nut
+] \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/_custom_air_accel.gnut b/Northstar.Custom/mod/scripts/vscripts/_custom_air_accel.gnut
new file mode 100644
index 00000000..5c228af4
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/_custom_air_accel.gnut
@@ -0,0 +1,23 @@
+global function CustomAirAccelVars_Init
+
+void function CustomAirAccelVars_Init()
+{
+ AddPrivateMatchModeSettingArbitrary( "Pilot", "custom_air_accel_pilot", "500" ) // 500 is the default airaccel
+
+ #if SERVER
+ AddCallback_OnPlayerRespawned( ApplyCustomPlayerAirAccel )
+ AddCallback_OnPilotBecomesTitan( ApplyCustomPlayerAirAccelFromTitan ) // not sure if necessary but assuming it is
+ #endif
+}
+
+#if SERVER
+void function ApplyCustomPlayerAirAccel( entity player )
+{
+ player.kv.airAcceleration = GetCurrentPlaylistVarInt( "custom_air_accel_pilot", int( player.GetPlayerSettingsField( "airAcceleration" ) ) )
+}
+
+void function ApplyCustomPlayerAirAccelFromTitan( entity player, entity titan )
+{
+ player.kv.airAcceleration = GetCurrentPlaylistVarInt( "custom_air_accel_pilot", int( player.GetPlayerSettingsField( "airAcceleration" ) ) )
+}
+#endif \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut b/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut
new file mode 100644
index 00000000..7447fc59
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut
@@ -0,0 +1,79 @@
+untyped
+global function DropPodSpawn_Init
+global function SpawnPlayersInDropPod
+
+struct {
+ array< entity > droppods
+} file
+
+void function DropPodSpawn_Init()
+{
+ AddCallback_OnRoundEndCleanup( CleanupSpawningDropPods )
+}
+
+void function CleanupSpawningDropPods()
+{
+ foreach ( entity pod in file.droppods )
+ pod.Destroy()
+
+ file.droppods.clear()
+}
+
+void function SpawnPlayersInDropPod( array< entity > players, vector targetOrigin, vector angles, float destructionTime = -1 )
+{
+ entity pod = CreateDropPod( targetOrigin, angles )
+
+ file.droppods.append( pod )
+ svGlobal.levelEnt.EndSignal( "CleanUpEntitiesForRoundEnd" )
+
+ // TODO: we need to make a door for this, CreateDropPodDoor in _droppod_fireteam is just busted for some reason tho
+
+ entity camera = CreateEntity( "point_viewcontrol" )
+ camera.SetParent( pod, "ATTACH", false )
+ camera.SetLocalOrigin( < 0, 150, 450 > )
+ camera.SetAngles( < 60, -90, 0 > )
+
+ foreach ( entity player in players )
+ {
+ if ( !IsAlive( player ) )
+ player.RespawnPlayer( null )
+
+ player.SetOrigin( pod.GetOrigin() )
+ player.SetAngles( pod.GetAngles() )
+ player.SetParent( pod )
+ player.FreezeControlsOnServer()
+ AddCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD )
+ player.SetViewEntity( camera, true )
+ }
+
+ // wait for this
+ LaunchAnimDropPod( pod, "pod_testpath", targetOrigin, angles )
+
+ foreach ( entity player in players )
+ {
+ player.ClearParent()
+ player.ClearViewEntity()
+ player.UnfreezeControlsOnServer()
+ RemoveCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD )
+ }
+
+ // wait a frame, otherwise this won't properly work
+ WaitFrame()
+ vector doorPos = pod.GetAttachmentOrigin( pod.LookupAttachment( "hatch" ) )
+
+ foreach ( entity player in players )
+ {
+ vector viewAngles = doorPos - player.GetOrigin()
+ viewAngles.x = 3.0
+
+ player.SetAngles( viewAngles )
+ }
+
+ if ( destructionTime != -1 )
+ {
+ wait destructionTime
+ pod.Dissolve( ENTITY_DISSOLVE_NORMAL, < 0, 0, 0 >, 0 )
+
+ file.droppods.remove( file.droppods.find( pod ) )
+ }
+} \ 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
new file mode 100644
index 00000000..960e619e
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut
@@ -0,0 +1,54 @@
+untyped
+global function NorthstarDevCommands_Init
+
+void function NorthstarDevCommands_Init()
+{
+ AddClientCommandCallback( "noclip", ClientCommandCallbackToggleNoclip )
+ AddClientCommandCallback( "script", ClientCommandCallbackEvalScript )
+ AddClientCommandCallback( "kill", ClientCommandCallbackKill )
+}
+
+bool function ClientCommandCallbackToggleNoclip( entity player, array<string> args )
+{
+ if ( GetConVarInt( "sv_cheats" ) != 1 )
+ return true
+
+ if ( player.IsNoclipping() )
+ player.SetPhysics( MOVETYPE_WALK )
+ else
+ player.SetPhysics( MOVETYPE_NOCLIP )
+
+ return true
+}
+
+bool function ClientCommandCallbackEvalScript( entity player, array<string> args )
+{
+ if ( args.len() < 1 || GetConVarInt( "sv_cheats" ) != 1 )
+ return true
+
+ // todo: rewrite this at some point to use a concommand because clientcommands can't just take in a single string with spaces, quotes etc
+ // should just have the concommand call a clientcommand manually with properly formatted args
+ string joinedArgs = args[0]
+ for ( int i = 1; i < args.len(); i++ )
+ joinedArgs += " " + args[i]
+
+ try
+ {
+ compilestring( joinedArgs )()
+ }
+ catch (exception)
+ {
+ // should probably send this to the client at some point
+ // no need to log here because compilestring errors already do that
+ }
+
+ return true
+}
+
+bool function ClientCommandCallbackKill( entity player, array<string> args )
+{
+ if ( IsAlive( player ) && GetConVarInt( "sv_cheats" ) == 1 )
+ player.Die()
+
+ return true
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/_promode.gnut b/Northstar.Custom/mod/scripts/vscripts/_promode.gnut
new file mode 100644
index 00000000..3e6d6d65
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/_promode.gnut
@@ -0,0 +1,25 @@
+untyped
+global function Promode_Init
+global function PromodeEnabled
+
+void function Promode_Init()
+{
+ AddPrivateMatchModeSettingEnum( "Promode", "promode_enable", [ "Disabled", "Enabled" ], "0" )
+
+ #if SERVER
+ AddCallback_OnPlayerRespawned( GivePromodeWeaponMod )
+ #endif
+}
+
+bool function PromodeEnabled()
+{
+ return GetCurrentPlaylistVarInt( "promode_enable", 0 ) == 1
+}
+
+#if SERVER
+void function GivePromodeWeaponMod( entity player )
+{
+ if ( PromodeEnabled() )
+ player.GiveExtraWeaponMod( "promode" )
+}
+#endif \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_boost_store.gnut b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_boost_store.gnut
new file mode 100644
index 00000000..515ffd9d
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_boost_store.gnut
@@ -0,0 +1,762 @@
+global function ShInitBoostStore
+global function ShInitBoostStoreForMode
+global function GetAvailableBoosts
+global function BoostStoreEnabled
+global function CanPurchaseBoost
+global function CanAffordBoost
+global function GetPlaylistBoostCost
+
+#if CLIENT || SERVER
+global function BoostStoreOpen
+#endif
+
+#if SERVER
+global function SetBoostPurchaseCallback
+global function SetBoostRefundCallback
+global function SetTeamReserveInteractCallback
+global function AddMoneyToPlayer
+global function SetMoneyForPlayer
+global function CreateBoostStoreLocation
+global function OpenBoostStores
+global function CloseBoostStores
+global function GetBoostStores
+global function BurnRewardRefundThink
+global function SetJoinInProgressBonus
+#endif
+
+#if CLIENT
+global function ServerCallback_EnableDropshipBoostStore
+global function ServerCallback_DisableDropshipBoostStore
+global function ServerCallback_BoostStoreTitanHint
+#endif
+
+#if UI
+global function UpdatePlayerMoney
+global function ServerCallback_UpdateTurretCount
+global function ServerCallback_UpdatePlayerHasBattery
+global function ServerCallback_UpdateAmpedWeaponState
+#endif
+
+global function GetPlayerMoney
+global function GetTeamReserve
+global function UpdateTeamReserve
+
+const int MAX_MONEY = 5000
+const float BOOST_STORE_TRIGGER_RADIUS = 300
+const int BOOST_STORE_DEFAULT_EXCHANGE = 100
+
+global struct BoostStoreData
+{
+ string itemRef
+ string modesAllowed
+ string additionalDesc
+ int cost
+ bool autoActivate
+ asset storeIcon
+ asset lockedStoreIcon
+ string additionalDescFail
+}
+
+struct
+{
+ array<BoostStoreData> allBoosts
+ array<BoostStoreData> availableBoosts
+ table<entity,int> playerBonusData
+ int joinInProgressBonus = 0
+ array<string> joinInProgressGivenToPlayers
+
+ int teamReserveAmount = 0
+
+ var boostStoreHintRui
+
+ #if UI
+ int playerMoney = 0
+ int turretCount = 0
+ int maxTurretCount = 5
+ bool playerHasBattery = false
+ bool playerHasAmpedWeapons = false
+ #endif
+
+ array<entity> storeLocations
+ table<string,bool functionref(entity)> boostPurchaseExtraChecks
+ void functionref(entity,BoostStoreData) boostPurchaseCallback
+ void functionref( entity, string ) boostRefundCallback
+ void functionref( entity, string, int ) teamReserveDepositOrWithdrawCallback
+} file
+
+void function ShInitBoostStore()
+{
+ var dataTable = GetDataTable( $"datatable/burn_meter_store.rpak" )
+ int len = GetDatatableRowCount( dataTable )
+ for ( int i=0; i<len; i++ )
+ {
+ string allowedModes = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "modes" ) )
+ string itemRef = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "itemRef" ) )
+ string additionalDesc = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "extraDesc" ) )
+ int cost = GetDataTableInt( dataTable, i, GetDataTableColumnByName( dataTable, "cost" ) )
+ bool autoActivate = GetDataTableBool( dataTable, i, GetDataTableColumnByName( dataTable, "autoActivate" ) )
+ asset storeIcon = GetDataTableAsset( dataTable, i, GetDataTableColumnByName( dataTable, "storeIcon" ) )
+ asset lockedStoreIcon = GetDataTableAsset( dataTable, i, GetDataTableColumnByName( dataTable, "lockedStoreIcon" ) )
+ string additionalDescFail = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "extraDescFail" ) )
+ BoostStoreData data
+ data.itemRef = itemRef
+ data.cost = cost
+ data.modesAllowed = allowedModes
+ data.additionalDesc = additionalDesc
+ data.autoActivate = autoActivate
+ data.storeIcon = storeIcon
+ data.lockedStoreIcon = lockedStoreIcon
+ data.additionalDescFail = additionalDescFail
+ file.allBoosts.append( data )
+ }
+
+ foreach ( turretType in GetAllBoostTurretTypes() )
+ {
+ file.boostPurchaseExtraChecks[ turretType ] <- CheckTooManyTurrets
+ }
+
+ file.boostPurchaseExtraChecks[ "burnmeter_instant_battery" ] <- CheckHasNoBattery
+ file.boostPurchaseExtraChecks[ "burnmeter_amped_weapons_permanent" ] <- CheckHasNoAmpedWeapons
+
+ #if UI
+ AddUICallback_OnLevelInit( ShInitBoostStoreForMode )
+ #else
+ if ( !IsLobby() )
+ ShInitBoostStoreForMode()
+ #endif
+
+ #if SERVER
+ if ( !BoostStoreEnabled() )
+ return
+
+ AddCallback_OnClientConnected( Store_OnClientConnected )
+ AddClientCommandCallback( "PurchaseBoost", ClientCommand_PurchaseBoost )
+ AddClientCommandCallback( "TeamReserveDeposit", ClientCommand_Deposit )
+ AddClientCommandCallback( "TeamReserveWithdraw", ClientCommand_Withdraw )
+ RegisterSignal( "BoostRefunded" )
+ RegisterSignal( "CancelRefund" )
+ #endif
+}
+
+#if SERVER
+void function SetJoinInProgressBonus( int moneyToAdd )
+{
+ file.joinInProgressBonus = file.joinInProgressBonus + moneyToAdd
+}
+
+void function SetBoostPurchaseCallback( void functionref( entity, BoostStoreData ) func )
+{
+ file.boostPurchaseCallback = func
+}
+
+void function SetBoostRefundCallback( void functionref( entity, string ) func )
+{
+ file.boostRefundCallback = func
+}
+
+void function SetTeamReserveInteractCallback( void functionref( entity, string, int ) func )
+{
+ file.teamReserveDepositOrWithdrawCallback = func
+}
+
+void function Store_OnClientConnected( entity player )
+{
+ file.playerBonusData[ player ] <- 0
+ string playerUID = player.GetUID()
+ if ( !file.joinInProgressGivenToPlayers.contains( playerUID ) )
+ {
+ AddMoneyToPlayer( player, file.joinInProgressBonus )
+ file.joinInProgressGivenToPlayers.append( playerUID )
+ }
+}
+#endif
+
+void function ShInitBoostStoreForMode()
+{
+ string boostStoreMode = GetCurrentPlaylistVarString( "boost_store_mode", "off" )
+ file.availableBoosts.clear()
+
+ if ( boostStoreMode == "off" )
+ return
+
+ foreach ( data in file.allBoosts )
+ {
+ array<string> tokens = split( data.modesAllowed, " " )
+ if ( tokens.contains( boostStoreMode ) )
+ {
+ file.availableBoosts.append( data )
+ }
+ }
+ file.availableBoosts.sort( SortCompareBoostCost )
+ printt( file.availableBoosts.len() )
+}
+
+int function SortCompareBoostCost( BoostStoreData a, BoostStoreData b )
+{
+ int acost = GetPlaylistBoostCost( a.itemRef, a.cost )
+ int bcost = GetPlaylistBoostCost( b.itemRef, b.cost )
+
+ if ( acost > bcost )
+ return 1
+
+ if ( bcost > acost )
+ return -1
+
+ return 0
+}
+
+array<BoostStoreData> function GetAvailableBoosts()
+{
+ // custom stuff for arena
+ string boostStoreMode = GetCurrentPlaylistVarString( "boost_store_mode", "off" )
+ if ( boostStoreMode == "arena" )
+ {
+ array<BoostStoreData> arenaLoadouts = PopulateArenaLoadouts()
+
+ #if SERVER
+ // bit of a hack, but assign weapon checks to weapon refs
+ foreach ( BoostStoreData data in arenaLoadouts )
+ if ( data.itemRef in eDamageSourceId )
+ file.boostPurchaseExtraChecks[ data.itemRef ] <- bool function( entity player ) { return player.GetMainWeapons().len() != 3 }
+ #endif
+
+ return arenaLoadouts
+ }
+
+ return file.availableBoosts
+}
+
+bool function CanAffordBoost( entity player, BoostStoreData data )
+{
+ int money = GetPlayerMoney( player )
+ return money >= GetPlaylistBoostCost( data.itemRef, data.cost )
+}
+
+bool function CanPurchaseBoost( entity player, BoostStoreData data, bool checkMoney = true )
+{
+ bool canBuy = true
+
+ if ( checkMoney )
+ canBuy = canBuy && CanAffordBoost( player, data )
+
+ if ( data.itemRef in file.boostPurchaseExtraChecks )
+ {
+ canBuy = canBuy && file.boostPurchaseExtraChecks[ data.itemRef ]( player )
+ }
+
+ return canBuy
+}
+
+#if SERVER
+bool function ClientCommand_PurchaseBoost( entity player, array<string> args )
+{
+ // bob note: readded this check, why the fuck is it not a check in vanilla
+ if ( !BoostStoreOpen() )
+ return true
+
+ if ( player.IsPhaseShifted() )
+ return false
+
+ if ( !IsAlive( player ) )
+ return false
+
+ if ( player.IsTitan() )
+ return false
+
+ array<BoostStoreData> availableBoosts = GetAvailableBoosts()
+
+ if ( args.len() == 0 )
+ return false
+
+ foreach ( data in availableBoosts )
+ {
+ if ( data.itemRef == args[0] )
+ {
+ if ( IsItemLocked( player, data.itemRef ) )
+ break
+
+ if ( CanPurchaseBoost( player, data ) )
+ {
+ if ( file.boostPurchaseCallback != null )
+ file.boostPurchaseCallback( player, data )
+
+ AddMoneyToPlayer( player, -1*GetPlaylistBoostCost( data.itemRef, data.cost ) )
+ BurnReward burnReward = BurnReward_GetByRef( data.itemRef )
+ if ( !data.autoActivate )
+ {
+ BurnMeter_GiveRewardDirect( player, data.itemRef )
+ }
+ else
+ {
+ RunBurnCardUseFunc( player, data.itemRef )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_RewardUsed", burnReward.id )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_Boost_Card_Earned_1P" )
+ }
+ EmitSoundOnEntityOnlyToPlayer( player, player, "UI_InGame_FD_ArmoryPurchase" )
+ MessageToTeam( player.GetTeam(), eEventNotifications.FD_BoughtItem, null, player, burnReward.id )
+ break
+ }
+ }
+ }
+
+ return true
+}
+
+bool function ClientCommand_Deposit( entity player, array<string> args )
+{
+ // if ( !BoostStoreOpen() )
+ // return true
+
+ int depositAmount = minint( file.playerBonusData[ player ], BOOST_STORE_DEFAULT_EXCHANGE )
+ file.teamReserveAmount += depositAmount
+ AddMoneyToPlayer( player, -1*depositAmount )
+
+ if ( IsValid( file.teamReserveDepositOrWithdrawCallback ) )
+ file.teamReserveDepositOrWithdrawCallback( player, "deposit", depositAmount )
+
+ foreach ( player in GetPlayerArray() )
+ {
+ Remote_CallFunction_UI( player, "ServerCallback_UpdateTeamReserve", file.teamReserveAmount )
+ }
+
+ return true
+}
+
+
+bool function ClientCommand_Withdraw( entity player, array<string> args )
+{
+ // if ( !BoostStoreOpen() )
+ // return true
+
+ if ( GetPlayerMoney( player ) >= MAX_MONEY )
+ return true
+
+ int withdrawAmount = minint( file.teamReserveAmount, BOOST_STORE_DEFAULT_EXCHANGE )
+ file.teamReserveAmount -= withdrawAmount
+ AddMoneyToPlayer( player, withdrawAmount )
+
+ if ( IsValid( file.teamReserveDepositOrWithdrawCallback ) )
+ file.teamReserveDepositOrWithdrawCallback( player, "withdraw", withdrawAmount )
+
+ foreach ( player in GetPlayerArray() )
+ {
+ Remote_CallFunction_UI( player, "ServerCallback_UpdateTeamReserve", file.teamReserveAmount )
+ }
+
+ return true
+}
+
+void function AddMoneyToPlayer( entity player, int points )
+{
+ SetMoneyForPlayer( player, file.playerBonusData[ player ] + points )
+}
+
+void function SetMoneyForPlayer( entity player, int points )
+{
+ file.playerBonusData[ player ] = int( Clamp( float( points ), 0, MAX_MONEY ) )
+
+ int bonusPoints = file.playerBonusData[ player ]
+ int bonusPointStack = int( max( ( bonusPoints - ( bonusPoints % 256 ) ) / 256, 0 ) )
+ int bonusPointRemainder = ( bonusPoints % 256 )
+
+ player.SetPlayerNetInt( "FD_money", bonusPointRemainder )
+ player.SetPlayerNetInt( "FD_money256", bonusPointStack )
+ player.SetTitle( "$" + bonusPoints )
+
+ Remote_CallFunction_UI( player, "ServerCallback_UpdateMoney", file.playerBonusData[ player ] )
+}
+
+void function CreateBoostStoreLocation( int team, vector origin, vector angles, bool showOnMinimap = true )
+{
+ if ( !BoostStoreEnabled() )
+ return
+
+ entity crate = CreatePropDynamic( MODEL_ATTRITION_BANK, origin, angles, 6 )
+
+ entity invisibleCrate = CreatePropScript( MODEL_ATTRITION_BANK, origin, angles, 6 )
+ SetTargetName( invisibleCrate, "boostStore" )
+ SetTeam( invisibleCrate, team )
+ invisibleCrate.Hide()
+ invisibleCrate.NotSolid()
+
+ // just to create an association
+ crate.SetParent( invisibleCrate )
+
+ thread PlayAnim( crate, "mh_inactive_idle", crate.GetParent() )
+ if ( BoostStoreOpen() )
+ thread EnableBoostStoreCrate( crate )
+
+ crate.SetUsePrompts( "#BOOST_STORE_HOLD_USE", "#BOOST_STORE_PRESS_USE" )
+ crate.SetForceVisibleInPhaseShift( true )
+
+ file.storeLocations.append( crate )
+
+ if ( showOnMinimap )
+ {
+ invisibleCrate.Minimap_SetObjectScale( MINIMAP_LOADOUT_CRATE_SCALE )
+ invisibleCrate.Minimap_SetAlignUpright( true )
+ invisibleCrate.Minimap_AlwaysShow( TEAM_IMC, null )
+ invisibleCrate.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ invisibleCrate.Minimap_SetHeightTracking( true )
+ invisibleCrate.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ invisibleCrate.Minimap_SetCustomState( eMinimapObject_prop_script.BOOST_STORE )
+ }
+ if ( !GetGlobalNetBool("boostStoreOpen") )
+ {
+ invisibleCrate.Minimap_Hide( TEAM_IMC, null )
+ invisibleCrate.Minimap_Hide( TEAM_MILITIA, null )
+ }
+
+ thread CreateBoostStoreHintTrigger( crate )
+ thread BoostStoreThink( crate )
+}
+
+void function EnableBoostStoreCrate( entity crate )
+{
+ crate.EndSignal( "OnDestroy" )
+ crate.SetUsable()
+
+ entity parentCrate = crate.GetParent()
+ int team = parentCrate.GetTeam()
+ if ( team == TEAM_MILITIA || team == TEAM_IMC )
+ crate.SetUsableByGroup( "friendlies pilot" )
+ else
+ crate.SetUsableByGroup( "pilot" )
+
+ SetTeam( crate, team )
+
+ waitthread PlayAnim( crate, "mh_inactive_2_active", crate.GetParent() )
+ thread PlayAnim( crate, "mh_active_idle", crate.GetParent() )
+ EmitSoundOnEntity( crate, "Mobile_Hardpoint_Idle" )
+}
+
+void function DisableBoostStoreCrate( entity crate )
+{
+ crate.EndSignal( "OnDestroy" )
+ crate.UnsetUsable()
+ SetTeam( crate, TEAM_UNASSIGNED )
+ FadeOutSoundOnEntity( crate, "Mobile_Hardpoint_Idle", 1.0 )
+ waitthread PlayAnim( crate, "mh_active_2_inactive", crate.GetParent() )
+ thread PlayAnim( crate, "mh_inactive_idle", crate.GetParent() )
+}
+
+void function CreateBoostStoreHintTrigger( entity crate )
+{
+ entity trig = CreateEntity( "trigger_cylinder" )
+ trig.SetRadius( BOOST_STORE_TRIGGER_RADIUS )
+ trig.SetAboveHeight( 200 )
+ trig.SetBelowHeight( 0)
+ trig.SetOrigin( crate.GetOrigin() )
+ trig.kv.triggerFilterNpc = "none"
+ trig.kv.triggerFilterPlayer = "all"
+ DispatchSpawn( trig )
+
+ trig.EndSignal( "OnDestroy" )
+ crate.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( trig )
+ {
+ if ( IsValid( trig ) )
+ trig.Destroy()
+ }
+ )
+
+ trig.SetEnterCallback( BoostHintTrigEnter )
+
+ WaitForever()
+}
+
+void function BoostHintTrigEnter( entity trigger, entity ent )
+{
+ if ( !ent.IsPlayer() )
+ return
+ if ( !ent.IsTitan() )
+ return
+ if ( !GetGlobalNetBool( "boostStoreOpen" ) )
+ return
+ vector org = trigger.GetOrigin()
+ Remote_CallFunction_NonReplay( ent, "ServerCallback_BoostStoreTitanHint", org.x, org.y, org.z )
+}
+
+void function CloseBoostStores()
+{
+ foreach ( crate in file.storeLocations )
+ thread DisableBoostStoreCrate( crate )
+ SetGlobalNetBool("boostStoreOpen", false)
+}
+
+void function OpenBoostStores()
+{
+ foreach ( crate in file.storeLocations )
+ thread EnableBoostStoreCrate( crate )
+ SetGlobalNetBool("boostStoreOpen", true)
+}
+
+array<entity> function GetBoostStores()
+{
+ return file.storeLocations
+}
+
+void function BurnRewardRefundThink( entity useEnt, entity ent )
+{
+ useEnt.EndSignal( "OnDestroy" )
+ ent.EndSignal( "OnDestroy" )
+ ent.EndSignal( "OnDeath" )
+ ent.EndSignal( "CancelRefund" )
+ useEnt.SetUsable()
+ useEnt.SetUsableByGroup( "owner pilot" )
+ useEnt.SetUsePrompts( "#REFUND_HOLD_USE", "#REFUND_PRESS_USE" )
+
+ entity player = expect entity( useEnt.WaitSignal( "OnPlayerUse" ).player )
+ {
+ if ( ent.e.burnReward == "" )
+ return
+
+ BurnMeter_GiveRewardDirect( player, ent.e.burnReward )
+ entity weapon = player.GetOffhandWeapon( OFFHAND_INVENTORY )
+
+ // Defensive: meaning the boost didn't make it to the inventory for some reason
+ if ( weapon == null )
+ return
+
+ if ( IsTurret( ent ) )
+ {
+ weapon.w.savedKillCount = int( ent.kv.killCount )
+ ent.DisableTurret()
+ ent.Signal( "StopTurretLaser" )
+ }
+ weapon.e.fd_roundDeployed = ent.e.fd_roundDeployed
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, ent.GetOrigin(), "Emplacement_Move_Dissolve" )
+ ent.Signal( "BoostRefunded" )
+ ent.UnsetUsable()
+ ent.SetInvulnerable()
+ ent.Dissolve( ENTITY_DISSOLVE_CORE, Vector( 0, 0, 0 ), 100 )
+
+ if ( file.boostRefundCallback != null )
+ file.boostRefundCallback( player, ent.e.burnReward )
+ }
+}
+
+void function BoostStoreThink( entity crate )
+{
+ while( IsValid( crate ) )
+ {
+ entity player = expect entity( crate.WaitSignal( "OnPlayerUse" ).player )
+ if ( IsValid( player ) )
+ OpenBoostStoreMenu( player )
+ }
+}
+
+void function OpenBoostStoreMenu( entity player )
+{
+ if ( !BoostStoreEnabled() )
+ return
+
+ CalculatePlayerTurretCount( player )
+ Remote_CallFunction_UI( player, "ServerCallback_UpdateMoney", file.playerBonusData[ player ] )
+ Remote_CallFunction_UI( player, "ServerCallback_UpdateTeamReserve", file.teamReserveAmount )
+ Remote_CallFunction_UI( player, "ServerCallback_UpdatePlayerHasBattery", PlayerHasBattery( player ) )
+ Remote_CallFunction_UI( player, "ServerCallback_UpdateAmpedWeaponState", PlayerHasAmpedWeapons( player ) )
+ Remote_CallFunction_UI( player, "ServerCallback_OpenBoostStore" )
+}
+#endif
+
+#if CLIENT
+void function ServerCallback_BoostStoreTitanHint( float x, float y, float z )
+{
+ vector org = <x,y,z>
+ thread ServerCallback_BoostStoreTitanHint_Internal( org )
+}
+
+void function ServerCallback_BoostStoreTitanHint_Internal( vector org )
+{
+ entity player = GetLocalViewPlayer()
+
+ player.EndSignal( "OnDeath" )
+
+ float titanHullCompensate = 75.0
+ float maxDist = ((BOOST_STORE_TRIGGER_RADIUS+titanHullCompensate) * (BOOST_STORE_TRIGGER_RADIUS+titanHullCompensate))
+ float minDot = 0.8
+ float fadeTime = 0.25
+ float hintTime = 3.5
+ float showHintTime = -999.0
+ bool hintShowing = false
+
+ OnThreadEnd(
+ function() : ( )
+ {
+ HidePlayerHint( "#BOOST_STORE_NEED_PILOT_HINT" )
+ }
+ )
+
+ while ( player.IsTitan() )
+ {
+ float dist = DistanceSqr( org, player.GetOrigin() )
+
+ bool showHint = false
+
+ if ( dist <= maxDist )
+ {
+ if ( PlayerCanSeePos( player, org, false, 35.0 ) )
+ {
+ if ( Time() - showHintTime + fadeTime >= hintTime )
+ {
+ AddPlayerHint( hintTime, fadeTime, $"", "#BOOST_STORE_NEED_PILOT_HINT" )
+ showHintTime = Time()
+ }
+ showHint = true
+ }
+ }
+ else
+ {
+ return
+ }
+
+ if ( !showHint && Time() - showHintTime + fadeTime < hintTime )
+ {
+ showHintTime = -999.0
+ HidePlayerHint( "#BOOST_STORE_NEED_PILOT_HINT" )
+ }
+
+ WaitFrame()
+ }
+}
+
+
+void function OpenBoostStoreMenu( entity player )
+{
+ if ( !BoostStoreEnabled() )
+ return
+
+ RunUIScript( "OpenBoostStore" )
+}
+
+void function ServerCallback_EnableDropshipBoostStore()
+{
+ if ( !BoostStoreEnabled() )
+ return
+
+ RegisterButtonPressedCallback( BUTTON_A, OpenBoostStoreMenu )
+ RegisterButtonPressedCallback( KEY_SPACE, OpenBoostStoreMenu )
+ if ( file.boostStoreHintRui != null )
+ RuiDestroyIfAlive( file.boostStoreHintRui )
+ file.boostStoreHintRui = CreatePermanentCockpitRui( $"ui/hint_display.rpak" )
+ RuiSetString( file.boostStoreHintRui, "hintText", Localize( "#BOOST_STORE_DROPSHIP_PRESS_USE" ) )
+}
+
+void function ServerCallback_DisableDropshipBoostStore()
+{
+ if ( !BoostStoreEnabled() )
+ return
+
+ if ( file.boostStoreHintRui != null )
+ {
+ RuiDestroyIfAlive( file.boostStoreHintRui )
+ DeregisterButtonPressedCallback( BUTTON_A, OpenBoostStoreMenu )
+ DeregisterButtonPressedCallback( KEY_SPACE, OpenBoostStoreMenu )
+ file.boostStoreHintRui = null
+ }
+}
+#endif
+
+bool function BoostStoreEnabled()
+{
+ return ( GetCurrentPlaylistVarString( "boost_store_mode", "off" ) != "off" )
+}
+
+#if CLIENT || SERVER
+bool function BoostStoreOpen()
+{
+ return GetGlobalNetBool("boostStoreOpen" )
+}
+#endif
+
+int function GetTeamReserve()
+{
+ return file.teamReserveAmount
+}
+
+void function UpdateTeamReserve( int reserveMoney )
+{
+ file.teamReserveAmount = reserveMoney
+}
+
+
+int function GetPlayerMoney( entity player )
+{
+ #if SERVER
+ return file.playerBonusData[ player ]
+ #endif
+
+ #if CLIENT
+ int money = player.GetPlayerNetInt( "FD_money" )
+ int money256 = player.GetPlayerNetInt( "FD_money256" )
+ return ( money256 * 256 ) + money
+ #endif
+
+ #if UI
+ return file.playerMoney
+ #endif
+}
+
+bool function CheckTooManyTurrets( entity player )
+{
+ #if SERVER || CLIENT
+ return player.GetPlayerNetInt( "burn_numTurrets" ) < GetGlobalNetInt( "burn_turretLimit" )
+ #elseif UI
+ return file.turretCount < file.maxTurretCount
+ #endif
+}
+
+bool function CheckHasNoBattery( entity player )
+{
+ #if SERVER || CLIENT
+ return !PlayerHasBattery( player )
+ #elseif UI
+ return !file.playerHasBattery
+ #endif
+}
+
+bool function CheckHasNoAmpedWeapons( entity player )
+{
+ #if SERVER || CLIENT
+ return !PlayerHasAmpedWeapons( player )
+ #elseif UI
+ return !file.playerHasAmpedWeapons
+ #endif
+}
+
+#if UI
+void function ServerCallback_UpdatePlayerHasBattery( bool hasBattery )
+{
+ file.playerHasBattery = hasBattery
+}
+
+void function ServerCallback_UpdateAmpedWeaponState( bool hasAmpedWeapons )
+{
+ file.playerHasAmpedWeapons = hasAmpedWeapons
+}
+
+void function ServerCallback_UpdateTurretCount( int count, int max )
+{
+ file.turretCount = count
+ file.maxTurretCount = max
+}
+
+void function UpdatePlayerMoney( int money )
+{
+ file.playerMoney = money
+}
+#endif
+
+int function GetPlaylistBoostCost( string ref, int originalCost )
+{
+ int cost = GetCurrentPlaylistVarInt( "override_boost_cost_" + ref, -1 )
+
+ if ( cost > 0 )
+ return cost
+
+ return originalCost
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut
new file mode 100644
index 00000000..3f914745
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut
@@ -0,0 +1,488 @@
+global function ShBurnMeter_Init
+global function BurnReward_GetById
+global function BurnReward_GetByRef
+global function BurnReward_GetRandom
+global function GetSelectedBurnCardRef
+global function GetBoostSkin
+#if SERVER || CLIENT
+global function BurnMeterEnabled
+global function CanUseWeaponAsBurnCard
+global function TryUsingBurnCardWeapon
+global function TryUsingBurnCardWeaponInCriticalSection
+global function BurnMeterPlayer_CanUseEquipped
+global function OnWeaponAttemptOffhandSwitch_burncardweapon
+global function BurnMeterPlayer_CanUseReward
+global function BurnMeterPlayer_GetRewardOrGoal
+global function BurnReward_GetTopInventoryItemBurnCard
+global function OnWeaponPrimaryAttack_nuke_eject
+global function BurnMeter_HarvesterShieldCanUse
+#endif
+
+global function GetAllBoostTurretTypes
+
+global const BURN_METER_SMALL_POINT_VALUE = 1
+global const BURN_METER_MID_POINT_VALUE = 2
+global const BURN_METER_LARGE_POINT_VALUE = 5
+global const BURN_METER_EXTRA_LARGE_POINT_VALUE = 10
+
+global const BURN_METER_RADAR_JAMMER_PULSE_DURATION = 6.0
+global const BURN_METER_RADAR_JAMMER_EASE_OFF_TIME = 1.0
+
+global enum eBurnMeterRewardAvailableFor
+{
+ PILOT_ONLY,
+ TITAN_ONLY,
+ PILOT_AND_TITAN,
+ SPECIAL_PLAYERS_ONLY,
+}
+
+global struct BurnReward
+{
+ int id = -1
+ string ref = ""
+ string localizedName = ""
+ string description = ""
+ asset image = $""
+ float cost
+ int userType = -1
+ int skinIndex = 0
+ string weaponName = ""
+ string extraWeaponMod = ""
+ string activationText = ""
+
+ void functionref( entity ) ornull rewardAvailableCallback = null
+}
+
+global struct BurnStruct
+{
+ table< string, BurnReward > burnRewards
+ array< BurnReward > allCards //Kinda inefficient to store the same burn cards 3 times (once in burnRewards, once in foil/normal, once in allcards. Prefer speed over memory? )
+ array< BurnReward > allowedCards
+
+}
+
+global BurnStruct burn
+
+global table< string, bool functionref( entity ) > burnMeterCanUseFuncTable
+
+struct
+{
+
+ bool shouldCycleOnRelease = false
+
+} file
+
+array<string> turretBurnCards = [
+ "burnmeter_ap_turret_weapon",
+ "burnmeter_at_turret_weapon",
+ "burnmeter_ap_turret_weapon_infinite",
+ "burnmeter_at_turret_weapon_infinite",
+]
+
+void function ShBurnMeter_Init()
+{
+
+ #if SERVER || CLIENT
+ //burnMeterCanUseFuncTable[ "burnmeter_random_foil" ] <- BurnMeter_RandomFoilCanUse
+ burnMeterCanUseFuncTable[ "burnmeter_emergency_titan" ] <- BurnMeter_EmergencyTitanCanUse
+ burnMeterCanUseFuncTable[ "burnmeter_nuke_titan" ] <- BurnMeter_NukeTitanCanUse
+ // burnMeterCanUseFuncTable[ "burnmeter_harvester_shield" ] <- BurnMeter_HarvesterShieldCanUse
+ #endif
+
+
+
+ var dataTable = GetDataTable( $"datatable/burn_meter_rewards.rpak" )
+
+ for ( int row = 0; row < GetDatatableRowCount( dataTable ); row++ )
+ {
+ BurnReward burnReward
+ burnReward.id = row
+ burnReward.ref = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_REF_COLUMN_NAME ) )
+ burnReward.localizedName = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_NAME_COLUMN_NAME ) )
+ burnReward.description = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_DESCRIPTION_COLUMN_NAME ) )
+ burnReward.image = GetDataTableAsset( dataTable, row, GetDataTableColumnByName( dataTable, BURN_IMAGE_COLUMN_NAME ) )
+ burnReward.cost = GetDataTableFloat( dataTable, row, GetDataTableColumnByName( dataTable, BURN_COST_COLUMN_NAME ) )
+ string userType = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_AVAILABLE_COLUMN_NAME ) )
+ burnReward.userType = eBurnMeterRewardAvailableFor[userType]
+ burnReward.weaponName = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_WEAPON_COLUMN_NAME ) )
+ burnReward.extraWeaponMod = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_EXTRA_WEAPON_MOD_NAME ) )
+ burnReward.activationText = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "activationText" ) )
+ burnReward.skinIndex = GetDataTableInt( dataTable, row, GetDataTableColumnByName( dataTable, "skinIndex" ) )
+
+ if ( IsDisabledRef( burnReward.ref ) )
+ continue
+
+#if SERVER || CLIENT
+ if ( burnReward.weaponName != "" )
+ PrecacheWeapon( burnReward.weaponName )
+ #endif
+
+ burn.burnRewards[burnReward.ref] <- burnReward
+
+ bool selectable = GetDataTableBool( dataTable, row, GetDataTableColumnByName( dataTable, "selectable" ) )
+ burn.allCards.append( burnReward )
+ if ( selectable )
+ burn.allowedCards.append( burnReward )
+ }
+
+ #if CLIENT
+ //Registering here instead of in CreateWeaponData() in _items.nut to remove dependence on HUD script with items. If more weapons other than smart pistol run into this problem, just add a column in burn_meter_rewards to specify we need to do this. ( ticks are fine because they have a damagedef associated with them )
+ RegisterWeaponDamageSourceName( "mp_weapon_smart_pistol", expect string( GetWeaponInfoFileKeyField_GlobalNotNull( "mp_weapon_smart_pistol", "shortprintname" ) ) )
+
+ RegisterConCommandTriggeredCallback( "-offhand4", CycleOnRelease )
+ #endif
+}
+
+#if SERVER || CLIENT
+bool function BurnMeterEnabled()
+{
+ return Riff_BoostAvailability() != eBoostAvailability.Disabled
+}
+#endif
+
+BurnReward function BurnReward_GetById( int id )
+{
+ return burn.allCards[ id ]
+}
+
+
+BurnReward function BurnReward_GetByRef( string ref )
+{
+ Assert( ref in burn.burnRewards )
+
+ // more hacks for arena
+ if ( !( ref in burn.burnRewards ) && GetCurrentPlaylistVarString( "boost_store_mode", "off" ) == "arena" )
+ return GetArenaLoadoutItemAsBurnReward( ref )
+
+ return burn.burnRewards[ref]
+}
+
+int function GetBoostSkin( string ref )
+{
+ BurnReward reward = BurnReward_GetByRef( ref )
+ //printt( "ref: " + ref + ", Boost skin: " + reward.skinIndex )
+ return reward.skinIndex
+}
+
+
+BurnReward function BurnReward_GetRandom()
+{
+ return burn.allowedCards.getrandom()
+}
+
+string function GetSelectedBurnCardRef( entity player )
+{
+ int burnCardID = expect int ( player.GetPersistentVar( "burnmeterSlot" ) )
+ BurnReward burnCard = burn.allCards[ burnCardID ]
+ string ref = burnCard.ref
+
+ #if SERVER
+ if ( GetItemDisplayData( ref ).hidden )
+ ClientCommand( player, "disconnect" )
+ #endif
+
+ #if SERVER || CLIENT
+
+ if ( Riff_TitanAvailability() == eTitanAvailability.Never )
+ {
+ //JFS: Batteries and Titan turrets are useless in PvP, turn them into amped weapons instead. Not an elegant solution at all. Fix next game. (Make boosts editable mid-match? )
+ if ( ref == "burnmeter_emergency_battery" || ref == "burnmeter_at_turret_weapon" )
+ ref = "burnmeter_amped_weapons"
+ }
+
+ if ( GetCurrentPlaylistVarInt( "featured_mode_all_ticks", 0 ) >= 1 )
+ {
+ if ( ref == "burnmeter_ticks" || ref == "burnmeter_random_foil" )
+ ref = "burnmeter_amped_weapons"
+ }
+ #endif
+
+ return ref
+}
+
+
+#if SERVER || CLIENT
+bool function CanUseWeaponAsBurnCard( entity weapon, entity ownerPlayer )
+{
+ Assert( weapon.HasMod( "burn_card_weapon_mod" ) )
+
+ if ( !BurnMeterPlayer_CanUseEquipped( ownerPlayer ) )
+ {
+ weapon.EmitWeaponSound( "CoOp_SentryGun_DeploymentDeniedBeep" )
+ return false
+ }
+
+ return true
+}
+
+bool function TryUsingBurnCardWeapon( entity weapon, entity ownerPlayer )
+{
+ Assert( weapon.HasMod( "burn_card_weapon_mod" ) )
+
+ if ( !CanUseWeaponAsBurnCard( weapon, ownerPlayer ) )
+ return false
+
+ #if SERVER
+ UseBurnCardWeapon( weapon, ownerPlayer )
+ #endif
+
+ return true
+}
+
+bool function TryUsingBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer )
+{
+ Assert( weapon.HasMod( "burn_card_weapon_mod" ) )
+
+ if ( !CanUseWeaponAsBurnCard( weapon, ownerPlayer ) )
+ return false
+
+ #if SERVER
+ UseBurnCardWeaponInCriticalSection( weapon, ownerPlayer )
+ #endif
+
+ return true
+}
+
+
+
+bool function BurnMeterPlayer_CanUseReward( entity player, BurnReward burnReward )
+{
+ if ( !BurnMeterPlayer_CanUseGlobal( player ) )
+ return false
+
+ switch( burnReward.userType )
+ {
+ case eBurnMeterRewardAvailableFor.PILOT_ONLY:
+ return !player.IsTitan()
+
+ case eBurnMeterRewardAvailableFor.TITAN_ONLY:
+ return player.IsTitan()
+
+ case eBurnMeterRewardAvailableFor.PILOT_AND_TITAN:
+ return true
+
+ case eBurnMeterRewardAvailableFor.SPECIAL_PLAYERS_ONLY:
+ return BurnMeterPlayer_CanUseSpecial( player, burnReward ) //TODO: Note that for the case of reaperfall we also send the message to the client if they can't summon reaper in the validation function. Not ideal...
+ }
+
+ unreachable
+
+}
+
+
+bool function BurnMeterPlayer_CanUseEquipped( entity player )
+{
+ BurnReward burnReward = BurnReward_GetTopInventoryItemBurnCard( player )
+ return BurnMeterPlayer_CanUseReward( player, burnReward )
+}
+
+
+EarnObject function BurnMeterPlayer_GetRewardOrGoal( entity player )
+{
+ if ( PlayerEarnMeter_IsRewardEnabled( player ) ) // TODO: more robust
+ return PlayerEarnMeter_GetReward( player )
+ else
+ return PlayerEarnMeter_GetGoal( player )
+
+ unreachable
+}
+
+
+bool function BurnMeterPlayer_CanUseGlobal( entity player )
+{
+ if ( player.IsBot() )
+ return false
+
+ if ( !IsAlive( player ) )
+ return false
+
+ if ( player.IsPhaseShifted() )
+ return false
+
+ if ( player.ContextAction_IsInVehicle() ) //no rewards when in dropship
+ return false
+
+ if ( !IsBurnMeterRewardAvailableForGameState() )
+ return false
+
+ return true
+}
+
+
+bool function IsBurnMeterRewardAvailableForGameState() //Based off IsReplacementTitanAvailable
+{
+ int currentGameState = GetGameState()
+
+ switch ( currentGameState ) //need to add a new entry in here for every new game state we make
+ {
+ case eGameState.WaitingForCustomStart:
+ case eGameState.WaitingForPlayers:
+ case eGameState.PickLoadout:
+ case eGameState.Prematch:
+ case eGameState.SwitchingSides:
+ case eGameState.Postmatch:
+ return false
+
+ case eGameState.Playing:
+ case eGameState.SuddenDeath:
+ return true
+
+ case eGameState.WinnerDetermined:
+ case eGameState.Epilogue:
+ {
+ if ( IsRoundBased() )
+ {
+ // TODO: make this work on the client
+ #if SERVER
+ if ( !IsRoundBasedGameOver() )
+ return false
+
+ if ( !ShouldRunEvac() )
+ return false
+ #endif
+ }
+
+ return true
+ }
+
+ default:
+ Assert( false, "Unknown Game State: " + currentGameState )
+ return false
+ }
+
+ unreachable
+}
+
+
+bool function OnWeaponAttemptOffhandSwitch_burncardweapon( entity weapon )
+{
+ entity ownerPlayer = weapon.GetWeaponOwner()
+ Assert( ownerPlayer.IsPlayer() )
+
+ if ( ownerPlayer.IsUsingOffhandWeapon() )
+ return false
+
+ if ( weapon.HasMod( "burn_card_weapon_mod" ) )
+ {
+ bool canUse = BurnMeterPlayer_CanUseEquipped( ownerPlayer )
+
+ return canUse
+ }
+ else
+ return true
+
+ unreachable
+}
+
+BurnReward function BurnReward_GetTopInventoryItemBurnCard( entity player )
+{
+ int burnRewardID = player.GetPlayerNetInt( TOP_INVENTORY_ITEM_BURN_CARD_ID )
+ return BurnReward_GetById( burnRewardID )
+}
+
+bool function BurnMeterPlayer_CanUseSpecial( entity player, BurnReward burnReward )
+{
+ Assert( burnReward.ref in burnMeterCanUseFuncTable )
+ return burnMeterCanUseFuncTable[ burnReward.ref ]( player )
+}
+
+bool function BurnMeter_HarvesterShieldCanUse( entity player )
+{
+// entity harvester = GetGlobalNetEnt( "FD_activeHarvester" )
+ bool canUse = GetGlobalNetTime( "FD_harvesterInvulTime" ) < Time()
+ // harvester.GetShieldHealth() < harvester.GetShieldHealthMax()
+
+ if ( !canUse )
+ {
+ #if CLIENT
+ AddPlayerHint( 3.0, 0.5, $"", "#HINT_HARVESTER_BOOST_CANT_USE" )
+ #endif
+ }
+
+ return canUse
+}
+
+bool function BurnMeter_NukeTitanCanUse( entity player )
+{
+ bool canUse = BurnMeter_EmergencyTitanCanUse( player )
+
+ if ( !canUse )
+ {
+ #if CLIENT
+ AddPlayerHint( 5.0, 0.5, $"", "#HINT_NUKE_TITAN_CANT_USE" )
+ #endif
+ }
+
+ return canUse
+}
+
+bool function BurnMeter_EmergencyTitanCanUse( entity player )
+{
+ if ( IsAlive( player.GetPetTitan() ) )
+ return false
+
+ #if SERVER
+ if ( !IsReplacementTitanAvailableForGameState() )
+ return false
+
+ if ( player.isSpawning )
+ return false
+
+ return true
+ #endif
+
+ #if CLIENT
+ return true
+ #endif
+}
+
+bool function BurnMeter_SummonReaperCanUse( entity player )
+{
+ array< entity > allReapers = GetNPCArrayByClass( "npc_super_spectre" )
+
+ int playerTeam = player.GetTeam()
+
+ foreach( reaper in allReapers )
+ {
+ if ( reaper.GetTeam() == playerTeam )
+ {
+ #if SERVER
+ MessageToPlayer( player, eEventNotifications.BurnMeter_CantSummonReaper )
+ #endif
+
+ return false
+ }
+ }
+ return true
+}
+
+var function OnWeaponPrimaryAttack_nuke_eject( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ #if CLIENT
+ entity weaponOwner = weapon.GetWeaponOwner()
+ if ( weaponOwner.IsPlayer() && IsValid( weaponOwner.GetCockpit() ) )
+ {
+ weaponOwner.ClientCommand( "TitanEject " + 3 )
+ PlayerEjects( weaponOwner, weaponOwner.GetCockpit() ) //Note that this can be run multiple times in a frame, e.g. get damaged by 4 pellets of a shotgun that brings the Titan into a doomed state with auto eject. Not ideal
+ }
+ #endif
+ return 0
+}
+
+#endif
+
+#if CLIENT
+void function CycleOnRelease( entity player )
+{
+ if ( file.shouldCycleOnRelease )
+ {
+ CycleInventory( player )
+ file.shouldCycleOnRelease = false
+ }
+}
+#endif
+
+array<string> function GetAllBoostTurretTypes()
+{
+ return turretBurnCards
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/client/cl_gamestate.gnut b/Northstar.Custom/mod/scripts/vscripts/client/cl_gamestate.gnut
new file mode 100644
index 00000000..93be01ea
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/client/cl_gamestate.gnut
@@ -0,0 +1,1119 @@
+untyped
+
+global function ClGameState_Init
+global function ClGameState_Think
+global function ClGameState_GetRui
+global function ClGameState_RegisterNetworkFunctions
+global function ClGameState_SetInfoStatusText
+
+global function ServerCallback_AnnounceWinner
+global function ServerCallback_AnnounceRoundWinner
+
+global function ShowRoundScoresInAnnouncement
+
+global function PlayRoundWonConversationWithAnnouncementDelay
+
+global function UpdatePilotTitanStatusDisplay
+global function UpdatePilotTitanStatusDisplayWithCount
+
+global function SetGameModeScoreBarUpdateRules
+global function SetGameModeScoreBarUpdateRulesWithFlags
+
+global function DisplayPostMatchTop3
+
+global function GetGameStartTime
+
+global function ClGameState_RegisterGameStateAsset
+
+global function ClGameState_SetPilotTitanStatusCallback
+global function GetPilotTitanStatusForPlayer
+
+global enum ePlayerStatusType
+{
+ PTS_TYPE_NONE,
+ PTS_TYPE_DEAD,
+ PTS_TYPE_DEAD_PILOT_TITAN,
+ PTS_TYPE_DEAD_READY,
+ PTS_TYPE_PILOT,
+ PTS_TYPE_READY,
+ PTS_TYPE_PILOT_TITAN,
+ PTS_TYPE_EVAC,
+ PTS_TYPE_ION,
+ PTS_TYPE_SCORCH,
+ PTS_TYPE_RONIN,
+ PTS_TYPE_TONE,
+ PTS_TYPE_LEGION,
+ PTS_TYPE_NORTHSTAR,
+ PTS_TYPE_VANGUARD,
+ PTS_TYPE_WAVE_READY,
+ PTS_TYPE_WAVE_NOT_READY,
+ PTS_TYPE_COUNT,
+}
+
+struct PilotTitanStatus
+{
+ int playerCount
+ int[8] slotTypes
+}
+
+global enum sbflag
+{
+ SKIP_STANDARD_UPDATE = (1<<0),
+ _count = 1
+}
+
+struct
+{
+ var gamestate_info
+ var letterBoxRui = null
+ bool thirtySecondWarningDone = false
+
+ void functionref( var ) gameModeScoreBarUpdateRules
+ int gameModeScoreBarUpdateFlags = 0
+
+ table< int, PilotTitanStatus> pilotTitanStatus
+ int functionref( entity,int ) pilotTitanStatusCallback
+
+ asset gameStateRuiAsset = $""
+} file
+
+
+const DEV_COUNTDOWNTIMER = 0//turn on to see the countdown timer to match starts - helps with lining up intros
+
+function ClGameState_Init()
+{
+ RegisterSignal( "GameStateChanged" )
+ RegisterServerVarChangeCallback( "gameState", ClGameState_Changed )
+ RegisterServerVarChangeCallback( "badRepPresent", UpdateScoreboardBadRepPresentMessage )
+
+ AddCallback_OnClientScriptInit( ClGameState_AddClient )
+
+ var rui = CreatePermanentCockpitRui( GetGameStateAsset(), MINIMAP_Z_BASE - 1 )
+ file.gamestate_info = rui
+
+ if ( file.gameModeScoreBarUpdateRules == null )
+ SetGameModeScoreBarUpdateRules( GameModeScoreBarUpdateRules_Default )
+}
+
+void function GameModeScoreBarUpdateRules_Default( var rui )
+{
+}
+
+void function SetGameModeScoreBarUpdateRulesWithFlags( void functionref( var ) rules, int sbflags )
+{
+ file.gameModeScoreBarUpdateRules = rules
+ file.gameModeScoreBarUpdateFlags = sbflags
+}
+
+void function SetGameModeScoreBarUpdateRules( void functionref( var ) rules )
+{
+ SetGameModeScoreBarUpdateRulesWithFlags( rules, 0 )
+}
+
+void function ClGameState_RegisterNetworkFunctions()
+{
+ RegisterNetworkedVariableChangeCallback_int( "gameInfoStatusText", ClGameState_UpdateInfoStatusText )
+}
+
+void function ClGameState_UpdateInfoStatusText( entity player, int oldValue, int newValue, bool actuallyChanged )
+{
+ printt( "UPDATE", player, oldValue, newValue )
+
+ if ( newValue == -1 )
+ {
+ RuiSetString( file.gamestate_info, "statusText", "" )
+ return
+ }
+
+ string statusText = GetStringFromNetworkableID( newValue )
+ printt( "SET", player, statusText, Localize( statusText ) )
+ RuiSetString( file.gamestate_info, "statusText", Localize( statusText ) )
+}
+
+
+void function ClGameState_SetInfoStatusText( string statusText )
+{
+ RuiSetString( file.gamestate_info, "statusText", Localize( statusText ) )
+}
+
+
+asset function GetGameStateAsset()
+{
+ if ( GAMETYPE == ATTRITION )
+ return $"ui/gamestate_info_at.rpak"
+
+ if ( GAMETYPE == FREE_AGENCY )
+ return $"ui/gamestate_info_fra.rpak"
+
+ if ( GAMETYPE == FFA )
+ return $"ui/gamestate_info_ffa.rpak"
+
+ if ( GAMETYPE == FORT_WAR )
+ return $"ui/gamestate_info_fw.rpak"
+
+ if ( GAMETYPE == LAST_TITAN_STANDING )
+ return $"ui/gamestate_info_lts.rpak"
+
+ if ( GAMETYPE == LTS_BOMB )
+ return $"ui/gamestate_info_lts_bomb.rpak"
+
+ if ( GAMETYPE == CAPTURE_POINT )
+ return $"ui/gamestate_info_cp.rpak"
+
+ if ( GAMETYPE == PILOT_SKIRMISH )
+ return $"ui/gamestate_info_ps.rpak"
+
+ if ( GAMETYPE == COLISEUM )
+ return $"ui/gamestate_info_coliseum.rpak"
+
+ if ( GAMETYPE == SPEEDBALL )
+ return $"ui/gamestate_info_speedball.rpak"
+
+ if ( GAMETYPE == MARKED_FOR_DEATH )
+ return $"ui/gamestate_info_mfd.rpak"
+
+ if ( file.gameStateRuiAsset != $"" )
+ return file.gameStateRuiAsset
+
+ return $"ui/gamestate_info.rpak"
+}
+
+void function ClGameState_RegisterGameStateAsset( asset gameStateAsset )
+{
+ file.gameStateRuiAsset = gameStateAsset
+}
+
+void function ClGameState_AddClient( entity player )
+{
+ player.cv.scoreboardBadRepPresentMessage <- HudElement( "ScoreboardBadRepPresentMessage", HudElement( "Scoreboard" ) )
+
+ #if PC_PROG
+ player.cv.scoreboardBadRepPresentMessage.SetText( "#ASTERISK_FAIRFIGHT_CHEATER" )
+ #else
+ player.cv.scoreboardBadRepPresentMessage.SetText( "#ASTERISK_BAD_REPUTATION" )
+ #endif
+
+ player.cv.hudCheaterMessage <- HudElement( "HudCheaterMessage" )
+ if ( !Durango_IsDurango() && !IsLobby() && player.HasBadReputation() )
+ player.cv.hudCheaterMessage.Show()
+ else
+ player.cv.hudCheaterMessage.Hide()
+}
+
+
+var function ClGameState_GetRui()
+{
+ return file.gamestate_info
+}
+
+
+void function ClGameState_Think()
+{
+ entity player = GetLocalClientPlayer()
+
+ TEMP_UpdateRuiScores( player )
+ switch( GetGameState() )
+ {
+ case eGameState.WaitingForPlayers:
+ ClGameStateThink_WaitingForPlayers( player )
+ break
+
+ case eGameState.PickLoadout:
+ ClGameStateThink_PickLoadOut( player )
+ break
+
+ case eGameState.Prematch:
+ ClGameStateThink_Prematch( player )
+ break
+
+ case eGameState.Playing:
+ ClGameStateThink_Playing( player )
+ break
+
+ case eGameState.SwitchingSides:
+ ClGameStateThink_SwitchingSides( player )
+ break
+
+ case eGameState.Postmatch:
+ ClGameStateThink_Postmatch( player )
+ break
+ }
+}
+
+
+void function ClGameStateThink_Prematch( entity player )
+{
+ string factionChoice = GetFactionChoice( player )
+ ItemDisplayData displayData = GetItemDisplayData( factionChoice )
+ asset factionLogo = displayData.image
+ string factionTitle = displayData.name
+
+ RuiSetImage( ClGameState_GetRui(), "factionImage", factionLogo )
+
+ //removing countdown timer all together
+ if ( !DEV_COUNTDOWNTIMER )
+ return
+
+ local timeRemaining = ceil( level.nv.gameStartTime - Time() )
+
+ player.cv.prematchTimer.Show()
+ player.cv.prematchTimerGlow.Show()
+ player.cv.prematchDesc.Show()
+
+ player.cv.prematchTimer.SetText( string( timeRemaining ) )
+ player.cv.prematchTimerGlow.SetText( string( timeRemaining ) )
+}
+
+void function ClGameStateThink_WaitingForPlayers( player )
+{
+ int reservedCount
+ int connectingCount
+ int loadingCount
+ if ( IsFFAGame() )
+ {
+ reservedCount = GetTotalPendingPlayersReserved()
+ connectingCount = GetTotalPendingPlayersConnecting()
+ loadingCount = GetTotalPendingPlayersLoading()
+ }
+ else
+ {
+ reservedCount = GetTeamPendingPlayersReserved( TEAM_MILITIA ) + GetTeamPendingPlayersReserved( TEAM_IMC )
+ connectingCount = GetTeamPendingPlayersConnecting( TEAM_MILITIA ) + GetTeamPendingPlayersConnecting( TEAM_IMC )
+ loadingCount = GetTeamPendingPlayersLoading( TEAM_MILITIA ) + GetTeamPendingPlayersLoading( TEAM_IMC )
+ }
+
+ int connectedCount = GetPlayerArray().len()
+ int allKnownPlayersCount = reservedCount + connectingCount + loadingCount + connectedCount
+ int minPlayers = GetCurrentPlaylistVarInt( "min_players", 0 )
+ int expectedPlayers = maxint( minPlayers, allKnownPlayersCount )
+
+ if ( !("lastTimeRemaining" in player.cv) )
+ player.cv.lastTimeRemaining <- null
+
+ if ( Time() <= level.nv.connectionTimeout )
+ {
+ local timeRemainingFloat = level.nv.connectionTimeout - Time()
+ local timeRemaining = ceil( timeRemainingFloat )
+
+ //player.cv.waitingForPlayersDesc.SetText( "#HUD_WAITING_FOR_PLAYERS", connectedCount, expectedPlayers, timeRemaining )
+ //player.cv.waitingForPlayersDesc.Show()
+
+ if ( player.cv.lastTimeRemaining && timeRemaining != player.cv.lastTimeRemaining )
+ EmitSoundOnEntity( player, WAITING_FOR_PLAYERS_COUNTDOWN_SOUND )
+
+ player.cv.lastTimeRemaining = timeRemaining
+ }
+ else
+ {
+ //player.cv.waitingForPlayersDesc.SetText( "#HUD_WAITING_FOR_PLAYERS", connectedCount, expectedPlayers, "" )
+ //player.cv.waitingForPlayersDesc.Show()
+ }
+}
+
+void function ClGameStateThink_PickLoadOut( entity player )
+{
+ if ( IsMultiplayerPlaylist() && ClassicMP_Client_GetGameStateThinkFunc_PickLoadOut() != null )
+ ClassicMP_Client_GetGameStateThinkFunc_PickLoadOut()( player )
+}
+
+entity function GetTopCompetitor( int team )
+{
+ array<entity> players = GetPlayerArrayOfEnemies( team )
+
+ entity topCompetitor
+ foreach ( player in players )
+ {
+ if ( !topCompetitor || (GameRules_GetTeamScore( player.GetTeam()) ) > GameRules_GetTeamScore( topCompetitor.GetTeam()) )
+ topCompetitor = player
+ }
+
+ return topCompetitor
+}
+
+
+string function GetTopCompetitorName( int team )
+{
+ entity topCompetitor = GetTopCompetitor( team )
+ return topCompetitor != null ? topCompetitor.GetPlayerName() : "None"
+}
+
+
+int function GetTopCompetitorTeam( int team )
+{
+ entity topCompetitor = GetTopCompetitor( team )
+ return topCompetitor != null ? topCompetitor.GetTeam() : 0
+}
+
+
+void function TEMP_UpdateRuiScores( entity player )
+{
+ float endTime
+ if ( IsRoundBased() )
+ {
+ endTime = expect float( level.nv.roundEndTime )
+ if (
+ GetGameState() == eGameState.Prematch ||
+ GetGameState() == eGameState.Playing
+ )
+ RuiSetString( file.gamestate_info, "roundText", Localize( "#GAMESTATE_ROUND_N", GetRoundsPlayed() + 1 ) )
+ }
+ else
+ {
+ endTime = expect float( level.nv.gameEndTime )
+ }
+
+ RuiSetInt( file.gamestate_info, "maxTeamScore", GetScoreLimit_FromPlaylist() )
+ int maxPlayers = GetCurrentPlaylistVarInt( "max_players", 12 )
+ RuiSetInt( file.gamestate_info, "maxTeamPlayers", maxPlayers / 2 )
+
+ int friendlyTeam = player.GetTeam()
+ RuiSetFloat( file.gamestate_info, "leftTeamScore", float( GameRules_GetTeamScore( friendlyTeam ) ) )
+
+ UpdatePilotTitanStatusDisplay( friendlyTeam )
+
+ int enemyTeam
+ if ( IsFFAGame() )
+ {
+ enemyTeam = GetTopCompetitorTeam( player.GetTeam() )
+ RuiSetImage( file.gamestate_info, "friendlyPlayerCardImage", CallsignIcon_GetImage( PlayerCallsignIcon_GetActive( player ) ) )
+ array<entity> enemyPlayers = GetPlayerArrayOfTeam( enemyTeam )
+ if ( enemyPlayers.len() > 0 )
+ RuiSetImage( file.gamestate_info, "enemyPlayerCardImage", CallsignIcon_GetImage( PlayerCallsignIcon_GetActive( enemyPlayers[0] ) ) )
+ }
+ else
+ {
+ enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC
+ }
+
+ RuiSetFloat( file.gamestate_info, "rightTeamScore", float( GameRules_GetTeamScore( enemyTeam ) ) )
+
+ //Run any gamemode specific score bar logic (e.g. the bonus segment on the scorebar in bounty hunt ). No special logic is run by default.
+ file.gameModeScoreBarUpdateRules( file.gamestate_info )
+
+ if ( !endTime )
+ return
+
+ if ( Time() > endTime )
+ {
+ RuiSetGameTime( file.gamestate_info, "endTime", RUI_BADGAMETIME )
+ return
+ }
+
+ RuiSetGameTime( file.gamestate_info, "endTime", endTime )
+}
+
+
+void function ClGameStateThink_Playing( entity player )
+{
+ //TEMP_UpdateRuiScores( player )
+
+ local endTime
+ if ( IsRoundBased() )
+ endTime = level.nv.roundEndTime
+ else
+ endTime = level.nv.gameEndTime
+
+ if ( !endTime )
+ return
+
+ if ( Time() > endTime )
+ return
+
+ if ( endTime - Time() > 30.0 )
+ return
+
+ if ( file.thirtySecondWarningDone )
+ return
+
+ thread ThirtySecondWarning()
+ file.thirtySecondWarningDone = true
+}
+
+
+void function ThirtySecondWarning()
+{
+ string conversation = GameMode_GetGameEndingConversation( GameRules_GetGameMode() )
+ if ( conversation != "" )
+ PlayConversationToLocalClient( conversation )
+
+ int lastSecond = -1
+ while ( Time() < level.nv.gameEndTime && GetGameState() == eGameState.Playing )
+ {
+ int second = int( floor( level.nv.gameEndTime - Time() ) )
+ if ( lastSecond == -1 )
+ {
+ lastSecond = second
+ }
+ else if ( second != lastSecond && second < 30 )
+ {
+ if ( second > 5 )
+ {
+ EmitSoundOnEntity( GetLocalClientPlayer(), "HUD_match_start_timer_tick_1P" )
+ }
+ else if ( second >= 0 )
+ {
+ EmitSoundOnEntity( GetLocalClientPlayer(), "HUD_match_start_timer_5_seconds_1P" )
+ }
+ }
+
+ lastSecond = second
+
+ WaitFrame()
+ }
+
+ EmitSoundOnEntity( GetLocalClientPlayer(), "HUD_match_start_timer_0_seconds_1P" )
+
+}
+
+
+string function GetTitanClass( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+ string settingsName = PlayerSettingsIndexToName( soul.GetPlayerSettingsNum() )
+
+ return expect string( Dev_GetPlayerSettingByKeyField_Global( settingsName, "titanCharacterName" ) )
+}
+
+
+array<int> function GetPilotTitanStatusForTeam( int team )
+{
+ array<int> statusTypes = [ ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE ]
+
+ int playerIndex = 0
+ foreach ( teamPlayer in GetPlayerArrayOfTeam( team ) )
+ {
+ // fix for situations where number of players > 8, which happens in infection
+ // could be good to move this to northstar.client at some point, unsure
+ if ( playerIndex == statusTypes.len() )
+ break
+
+ statusTypes[playerIndex] = GetPilotTitanStatusForPlayer( teamPlayer )
+ playerIndex++
+ }
+
+ statusTypes.sort( PilotTitanStatusSort )
+
+ return statusTypes
+}
+
+int function GetPilotTitanStatusForPlayer( entity teamPlayer )
+{
+ int statusIndex = ePlayerStatusType.PTS_TYPE_NONE
+ entity titan
+ if ( teamPlayer.GetPetTitan() )
+ titan = teamPlayer.GetPetTitan()
+ else if ( teamPlayer.IsTitan() )
+ titan = teamPlayer
+
+ entity playerParent = teamPlayer.GetParent()
+ bool playerIsInDropship = playerParent != null && IsDropship( playerParent )
+
+ if ( playerIsInDropship && ( GetWaveSpawnType() == eWaveSpawnType.DROPSHIP || GetGameState() == eGameState.Epilogue ) )
+ {
+ statusIndex = ePlayerStatusType.PTS_TYPE_EVAC
+ }
+ else if ( titan && titan.GetTitanSoul() )
+ {
+ if ( !teamPlayer.IsTitan() )
+ {
+ if ( IsAlive( teamPlayer ) )
+ statusIndex = ePlayerStatusType.PTS_TYPE_PILOT_TITAN
+ else
+ statusIndex = ePlayerStatusType.PTS_TYPE_DEAD_PILOT_TITAN
+ }
+ else
+ {
+ if ( !IsAlive( teamPlayer ) )
+ {
+ statusIndex = ePlayerStatusType.PTS_TYPE_DEAD
+ }
+ else
+ {
+ switch ( GetTitanClass( titan ) )
+ {
+ case "ion":
+ statusIndex = ePlayerStatusType.PTS_TYPE_ION
+ break
+ case "scorch":
+ statusIndex = ePlayerStatusType.PTS_TYPE_SCORCH
+ break
+ case "ronin":
+ statusIndex = ePlayerStatusType.PTS_TYPE_RONIN
+ break
+ case "tone":
+ statusIndex = ePlayerStatusType.PTS_TYPE_TONE
+ break
+ case "legion":
+ statusIndex = ePlayerStatusType.PTS_TYPE_LEGION
+ break
+ case "northstar":
+ statusIndex = ePlayerStatusType.PTS_TYPE_NORTHSTAR
+ break
+ case "vanguard":
+ statusIndex = ePlayerStatusType.PTS_TYPE_VANGUARD
+ break
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( IsAlive( teamPlayer ) )
+ statusIndex = IsTitanAvailable( teamPlayer ) ? ePlayerStatusType.PTS_TYPE_READY : ePlayerStatusType.PTS_TYPE_PILOT
+ else
+ statusIndex = IsTitanAvailable( teamPlayer ) ? ePlayerStatusType.PTS_TYPE_DEAD_READY : ePlayerStatusType.PTS_TYPE_DEAD
+ }
+
+ if ( file.pilotTitanStatusCallback != null )
+ statusIndex = file.pilotTitanStatusCallback( teamPlayer, statusIndex )
+
+ return statusIndex
+}
+
+int function PilotTitanStatusSort( int a, int b )
+{
+ if ( a > b )
+ return -1
+ else if ( a < b )
+ return 1
+
+ return 0
+}
+
+
+void function UpdatePilotTitanStatusDisplayWithCount( int team, int friendlyCount, int enemyCount )
+{
+ if ( IsFFAGame() )
+ return
+ var statusRui = ClGameState_GetRui()
+
+ array<int> playerStatus = GetPilotTitanStatusForTeam( team )
+ for ( int index = 0; index < friendlyCount; index++ )
+ RuiSetInt( statusRui, "friendlyPlayerStatusType" + (index + 1), playerStatus[index] )
+
+ array<int> enemyPlayerStatus = GetPilotTitanStatusForTeam( GetOtherTeam( team ) )
+ for ( int index = 0; index < enemyCount; index++ )
+ RuiSetInt( statusRui, "enemyPlayerStatusType" + (index + 1), enemyPlayerStatus[index] )
+}
+
+void function UpdatePilotTitanStatusDisplay( int team )
+{
+ if ( GAMETYPE == "fd" )
+ UpdatePilotTitanStatusDisplayWithCount( team, 4, 0 )
+ else
+ UpdatePilotTitanStatusDisplayWithCount( team, 8, 8 )
+}
+
+void function ClGameStateThink_Postmatch( player )
+{
+}
+
+void function ClGameStateThink_SwitchingSides( player )
+{
+ //player.cv.vignette.SetColor( 0, 0, 0, 255 )
+ //player.cv.vignette.Show()
+}
+
+
+void function ClGameState_Changed()
+{
+ entity player = GetLocalClientPlayer()
+ player.Signal( "GameStateChanged" )
+
+ foreach ( callbackFunc in clGlobal.gameStateEnterCallbacks[ GetGameState() ] )
+ {
+ callbackFunc()
+ }
+
+ switch ( GetGameState() )
+ {
+ case eGameState.WaitingForCustomStart:
+ //player.cv.gamescomWaitTillReady.Show()
+ break
+
+ case eGameState.WaitingForPlayers:
+ if ( IsMultiplayerPlaylist() && ClassicMP_Client_GetGameStateEnterFunc_WaitingForPlayers() != null )
+ ClassicMP_Client_GetGameStateEnterFunc_WaitingForPlayers()( player )
+ //player.cv.gamescomWaitTillReady.Hide()
+
+ //player.cv.waitingForPlayersDesc.SetText( "#HUD_WAITING_FOR_PLAYERS_BASIC" )
+ //player.cv.waitingForPlayersDesc.Show()
+ //player.cv.waitingForPlayersLine.Show()
+ //player.cv.waitingForPlayersTimer.Show()
+ break
+
+ case eGameState.PickLoadout:
+ if ( IsMultiplayerPlaylist() && ClassicMP_Client_GetGameStateEnterFunc_PickLoadOut() != null )
+ ClassicMP_Client_GetGameStateEnterFunc_PickLoadOut()( player )
+
+ break
+
+ case eGameState.Prematch:
+ RemoveAllRagdolls()
+ HideEventNotification()
+
+ SetCrosshairPriorityState( crosshairPriorityLevel.PREMATCH, CROSSHAIR_STATE_HIDE_ALL )
+
+ //player.cv.waitingForPlayersDesc.HideOverTime( 0.25 )
+ //player.cv.waitingForPlayersLine.HideOverTime( 0.25 )
+ //player.cv.waitingForPlayersTimer.HideOverTime( 0.25 )
+
+ file.letterBoxRui = RuiCreate( $"ui/letter_box.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 0 )
+
+ //player.cv.prematchTimer.SetAlpha( 255 )
+ //player.cv.prematchTimerGlow.SetAlpha( 255 )
+ //player.cv.prematchDesc.SetAlpha( 255 )
+
+ file.thirtySecondWarningDone = false
+
+ StopPlayerDeathSound( player ) //If you were watching your own kill replay, don't want this playing through till next round
+
+ clGlobal.levelEnt.Signal( "AnnoucementPurge" )
+
+ break
+
+ case eGameState.Playing:
+ ClearCrosshairPriority( crosshairPriorityLevel.PREMATCH )
+ //player.cv.vignette.HideOverTime( 0.25 )
+ if ( file.letterBoxRui != null )
+ RuiSetGameTime( file.letterBoxRui, "hideStartTime", Time() )
+ //player.cv.prematchTimer.HideOverTime( 0.25 )
+ //player.cv.prematchTimerGlow.HideOverTime( 0.25 )
+ //player.cv.prematchDesc.HideOverTime( 0.25 )
+
+ ShowScriptHUD( player )
+ break
+
+ case eGameState.SuddenDeath:
+ AnnouncementData announcement = Announcement_Create( "#GAMEMODE_ANNOUNCEMENT_SUDDEN_DEATH" )
+
+ switch ( GAMETYPE )
+ {
+ case CAPTURE_THE_FLAG:
+ case RAID:
+ Announcement_SetSubText( announcement, "#GAMEMODE_ANNOUNCEMENT_SUDDEN_DEATH_CTF" )
+ break
+
+ case TEAM_DEATHMATCH:
+ case TITAN_BRAWL:
+ case HARDCORE_TDM:
+ Announcement_SetSubText( announcement, "#GAMEMODE_ANNOUNCEMENT_SUDDEN_DEATH_TDM" )
+ break
+
+ default:
+ Announcement_SetSubText( announcement, "" )
+ }
+
+ Announcement_SetHideOnDeath( announcement, false )
+ Announcement_SetDuration( announcement, 7.0 )
+ Announcement_SetPurge( announcement, true )
+ AnnouncementFromClass( player, announcement )
+ break
+
+ case eGameState.WinnerDetermined:
+ player.cv.roundSpawnCount = 0
+ break
+
+ case eGameState.Epilogue:
+ thread MainHud_Outro( level.nv.winningTeam )
+ break
+
+ case eGameState.SwitchingSides:
+
+ float announcementDuration = 7.0
+ if ( IsRoundWinningKillReplayEnabled() == true && ( !IsRoundBased() && IsSwitchSidesBased() ) ) //Ideally this should be a call to WillShowRoundWinningKillReplay(), but that's only available on the server
+ announcementDuration = SWITCHING_SIDES_DELAY + ROUND_WINNING_KILL_REPLAY_TOTAL_LENGTH
+
+ AnnouncementData announcement = Announcement_Create( "#GameState_HALFTIME" )
+ announcement.sortKey = RUI_SORT_SCREENFADE + 1 // Draw over screen fade
+ announcement.drawOverScreenFade = true
+ Announcement_SetSubText( announcement, "#GameState_SWITCHING_SIDES" )
+ Announcement_SetHideOnDeath( announcement, false )
+ Announcement_SetDuration( announcement, announcementDuration )
+ Announcement_SetPurge( announcement, true )
+
+ EmitSoundOnEntity( player, "UI_InGame_HalftimeText_Enter" )
+ EmitSoundOnEntityAfterDelay( player, "UI_InGame_HalftimeText_Exit", announcementDuration )
+
+ local friendlyTeam = player.GetTeam()
+ local enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC
+
+ if ( friendlyTeam == TEAM_IMC )
+ {
+ Announcement_SetLeftIcon( announcement, TEAM_ICON_IMC )
+ Announcement_SetRightIcon( announcement, TEAM_ICON_MILITIA )
+ }
+ else
+ {
+ Announcement_SetLeftIcon( announcement, TEAM_ICON_MILITIA )
+ Announcement_SetRightIcon( announcement, TEAM_ICON_IMC )
+ }
+
+ if ( IsRoundBased() )
+ {
+ Announcement_SetSubText2( announcement, "#GAMEMODE_ROUND_WIN_CONDITION", GetRoundScoreLimit_FromPlaylist() )
+ Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore2( friendlyTeam ) )
+ Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore2( enemyTeam ) )
+ }
+ else
+ {
+ Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore( friendlyTeam ) )
+ Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore( enemyTeam ) )
+ }
+
+ AnnouncementFromClass( player, announcement )
+
+ break
+
+ case eGameState.Postmatch:
+ ShowScoreboard()
+ //wait for scoreboard to be up
+ delaythread( 1.5 ) ServerCallback_ResetMapSettings()
+ break
+ }
+}
+
+void function SwitchingSides_Changed()
+{
+ if ( IsMenuLevel() )
+ return
+
+ thread SwitchingSides_Changed_threaded()
+}
+
+void function SwitchingSides_Changed_threaded()
+{
+ //entity player = GetLocalViewPlayer()
+ //
+ //if ( level.nv.switchingSides )
+ //{
+ // player.cv.halfTimeText.SetText( "Test - Switching Sides" )
+ // player.cv.halfTimeText.Show()
+ // wait 1.5
+ //}
+ //else
+ //{
+ // player.cv.halfTimeText.Hide()
+ //
+ //}
+}
+
+void function ServerCallback_AnnounceWinner( int teamIndex, int subStringIndex, float winnerDeterminedWait )
+{
+ entity player = GetLocalClientPlayer()
+
+ string subString = ""
+ if ( subStringIndex )
+ subString = GetStringFromID( subStringIndex )
+
+ if ( !level.nv.winningTeam )
+ {
+ AnnouncementData outcomeAnnouncement = Announcement_Create( "#GAMEMODE_DRAW" )
+ outcomeAnnouncement.drawOverScreenFade = true
+ Announcement_SetSubText( outcomeAnnouncement, subString )
+ //Announcement_SetTitleColor( outcomeAnnouncement, TEAM_COLOR_YOU )
+ Announcement_SetHideOnDeath( outcomeAnnouncement, false )
+ Announcement_SetDuration( outcomeAnnouncement, winnerDeterminedWait )
+ Announcement_SetPriority( outcomeAnnouncement, 500 )
+ Announcement_SetPurge( outcomeAnnouncement, true )
+ AnnouncementFromClass( player, outcomeAnnouncement )
+ }
+ else if ( IsFFAGame() )
+ {
+ array<entity> players = GetPlayerArrayOfTeam( level.nv.winningTeam )
+ if ( players.len() )
+ {
+ string winText = ((players[0] == player) ? "#GAMEMODE_MATCH_WON_BY_SELF" : "#GAMEMODE_MATCH_WON_BY_ENEMY_TEAM")
+ AnnouncementData outcomeAnnouncement = Announcement_Create( winText )
+ outcomeAnnouncement.drawOverScreenFade = true
+ Announcement_SetSubText( outcomeAnnouncement, subString )
+ Announcement_SetOptionalTextArgsArray( outcomeAnnouncement, [players[0].GetPlayerName()] )
+ Announcement_SetTitleColor( outcomeAnnouncement, TEAM_COLOR_YOU )
+ Announcement_SetHideOnDeath( outcomeAnnouncement, false )
+ Announcement_SetDuration( outcomeAnnouncement, winnerDeterminedWait )
+ Announcement_SetPriority( outcomeAnnouncement, 500 )
+ Announcement_SetPurge( outcomeAnnouncement, true )
+ AnnouncementFromClass( player, outcomeAnnouncement )
+ }
+ }
+ else if ( player.GetTeam() == level.nv.winningTeam )
+ {
+
+ AnnouncementData outcomeAnnouncement = Announcement_Create( "#GAMEMODE_VICTORY" )
+ outcomeAnnouncement.announcementStyle = ANNOUNCEMENT_STYLE_RESULTS
+ outcomeAnnouncement.drawOverScreenFade = true
+ outcomeAnnouncement.soundAlias = "HUD_MP_Match_End_WinLoss_UI_Sweep_1P"
+ Announcement_SetSubText( outcomeAnnouncement, subString )
+ Announcement_SetTitleColor( outcomeAnnouncement, TEAM_COLOR_PARTY )
+ Announcement_SetHideOnDeath( outcomeAnnouncement, false )
+ Announcement_SetDuration( outcomeAnnouncement, winnerDeterminedWait )
+ Announcement_SetPriority( outcomeAnnouncement, 500 )
+ Announcement_SetPurge( outcomeAnnouncement, true )
+ AnnouncementFromClass( player, outcomeAnnouncement )
+ }
+ else if ( level.nv.winningTeam != TEAM_UNASSIGNED )
+ {
+ AnnouncementData outcomeAnnouncement = Announcement_Create( "#GAMEMODE_DEFEATED" )
+ outcomeAnnouncement.announcementStyle = ANNOUNCEMENT_STYLE_RESULTS
+ outcomeAnnouncement.drawOverScreenFade = true
+ outcomeAnnouncement.soundAlias = "HUD_MP_Match_End_WinLoss_UI_Sweep_1P"
+ Announcement_SetSubText( outcomeAnnouncement, subString )
+ Announcement_SetTitleColor( outcomeAnnouncement, TEAM_COLOR_ENEMY )
+ Announcement_SetHideOnDeath( outcomeAnnouncement, false )
+ Announcement_SetDuration( outcomeAnnouncement, winnerDeterminedWait )
+ Announcement_SetPriority( outcomeAnnouncement, 500 )
+ Announcement_SetPurge( outcomeAnnouncement, true )
+ AnnouncementFromClass( player, outcomeAnnouncement )
+ }
+}
+
+void function ServerCallback_AnnounceRoundWinner( int teamIndex, int subStringIndex, float winnerDeterminedWait, int imcTeamScore2, int militiaTeamScore2 )
+{
+ string subString = ""
+ if ( subStringIndex )
+ subString = GetStringFromID( subStringIndex )
+
+ entity player = GetLocalClientPlayer()
+
+ // ANNOUNCEMENT_FLICKER_BUFFER Ensures that the INTERPOLATOR_FLICKER animation is played before the message is hidden in
+ // AnnouncementMessage_DisplayOnHud(). I'm not certain why this is needed. There may be a delay before
+ // ServerCallback_AnnounceRoundWinner is actually received and/or the hud fades may be framerate
+ // dependent (they don't seem to play nice with timescale), or it could be something much more simple that I'm missing.
+ const ANNOUNCEMENT_FLICKER_BUFFER = 0.2
+
+ float announcementDuration = winnerDeterminedWait - ANNOUNCEMENT_FLICKER_BUFFER
+ float subtext2IconDelay = winnerDeterminedWait - 4.5
+ float conversationDelay = subtext2IconDelay + 1.5 // Bit of time to give it to breathe, and to let RoundWinningKillReplay be fully over before starting the conversation.
+
+ if ( !level.nv.winningTeam )
+ {
+ AnnouncementData announcement = Announcement_Create( "#GAMEMODE_ROUND_DRAW" )
+ announcement.drawOverScreenFade = true
+ Announcement_SetSubText( announcement, subString )
+ //Announcement_SetTitleColor( announcement, TEAM_COLOR_YOU )
+ Announcement_SetHideOnDeath( announcement, false )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetDuration( announcement, announcementDuration)
+
+ ShowRoundScoresInAnnouncement( announcement, subtext2IconDelay, imcTeamScore2, militiaTeamScore2 )
+ AnnouncementFromClass( player, announcement )
+ }
+ else if ( player.GetTeam() == level.nv.winningTeam )
+ {
+ AnnouncementData announcement = Announcement_Create( "#GAMEMODE_ROUND_WIN" )
+ announcement.drawOverScreenFade = true
+ Announcement_SetSubText( announcement, subString )
+ Announcement_SetTitleColor(announcement, TEAM_COLOR_FRIENDLY )
+ Announcement_SetHideOnDeath( announcement, false )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetDuration( announcement, announcementDuration)
+
+ ShowRoundScoresInAnnouncement( announcement, subtext2IconDelay, imcTeamScore2, militiaTeamScore2 )
+ thread PlayRoundWonConversationWithAnnouncementDelay( conversationDelay )
+
+ AnnouncementFromClass( player, announcement )
+ }
+ else if ( level.nv.winningTeam != TEAM_UNASSIGNED )
+ {
+ AnnouncementData announcement = Announcement_Create( "#GAMEMODE_ROUND_LOSS" )
+ announcement.drawOverScreenFade = true
+ Announcement_SetSubText( announcement, subString )
+ Announcement_SetTitleColor( announcement, TEAM_COLOR_ENEMY )
+ Announcement_SetHideOnDeath( announcement, false )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetDuration( announcement, announcementDuration)
+
+ ShowRoundScoresInAnnouncement( announcement, subtext2IconDelay, imcTeamScore2, militiaTeamScore2 )
+ thread PlayRoundWonConversationWithAnnouncementDelay( conversationDelay )
+
+ AnnouncementFromClass( player, announcement )
+ }
+}
+
+//Note that RoundWinningKillReplay doesn't send imcTeamScore2 and militiaTeamScore2 overrides.
+void function ShowRoundScoresInAnnouncement( AnnouncementData announcement, float subtext2IconDelay, int ornull imcTeamScore2 = null, int ornull militiaTeamScore2 = null )
+{
+ entity player = GetLocalClientPlayer()
+
+ local friendlyTeam = player.GetTeam()
+ local enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC
+
+ asset leftIcon
+ asset rightIcon
+
+ if ( friendlyTeam == TEAM_IMC )
+ {
+ leftIcon = TEAM_ICON_IMC
+ rightIcon = TEAM_ICON_MILITIA
+ }
+ else
+ {
+ leftIcon = TEAM_ICON_MILITIA
+ rightIcon = TEAM_ICON_IMC
+ }
+
+ if ( level.nv.roundScoreLimitComplete == true ) //Generally this is never true except for modes with RoundWinningKillReplay enabled
+ {
+ if ( friendlyTeam == level.nv.winningTeam )
+ {
+ Announcement_SetSubText( announcement, "#GAMEMODE_MATCH_WON_BY_FRIENDLY_TEAM" )
+ string friendlyTeamString = friendlyTeam == TEAM_IMC ? "#TEAM_IMC" : "#TEAM_MCOR"
+ Announcement_SetOptionalSubTextArgsArray( announcement, [ friendlyTeamString ] )
+ }
+ else if ( enemyTeam == level.nv.winningTeam )
+ {
+ Announcement_SetSubText( announcement, "#GAMEMODE_MATCH_WON_BY_ENEMY_TEAM" )
+ string enemyTeamString = enemyTeam == TEAM_IMC ? "#TEAM_IMC" : "#TEAM_MCOR"
+ Announcement_SetOptionalSubTextArgsArray( announcement, [ enemyTeamString ] )
+ }
+
+ }
+ else
+ {
+ Announcement_SetSubText2( announcement, "#GAMEMODE_ROUND_WIN_CONDITION", GetRoundScoreLimit_FromPlaylist() )
+ Announcement_SetSubText2AndIconDelay( announcement, subtext2IconDelay )
+ }
+
+ //Hack: GetTeamScore2 doesn't work mid-kill replay because we get the rewound values as opposed to the current values.
+ //Fix for R2 when we get the ability to flag certain values as "use current value instead of rewound value"
+ if ( imcTeamScore2 == null && militiaTeamScore2 == null )
+ {
+ Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore2( friendlyTeam ) )
+ Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore2( enemyTeam ) )
+ }
+ else
+ {
+ Assert( imcTeamScore2 != null && militiaTeamScore2 != null ) //Don't have only one team with teamScore2 override
+ if ( friendlyTeam == TEAM_IMC )
+ {
+ Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", imcTeamScore2 )
+ Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", militiaTeamScore2 )
+
+ }
+ else
+ {
+ Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", militiaTeamScore2 )
+ Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", imcTeamScore2 )
+ }
+ }
+
+ Announcement_SetLeftIcon( announcement, leftIcon )
+ Announcement_SetRightIcon( announcement, rightIcon )
+}
+
+void function PlayRoundWonConversationWithAnnouncementDelay( conversationDelay )
+{
+ WaitEndFrame() //Necessary so we don't get the AnnouncementPurge signal from the same announcement we are originating from
+ clGlobal.levelEnt.EndSignal( "AnnoucementPurge" )
+
+ if ( conversationDelay != 0 )
+ wait conversationDelay
+
+ if ( level.nv.winningTeam == null )
+ return
+
+ entity player = GetLocalClientPlayer()
+ if ( player.GetTeam() == level.nv.winningTeam )
+ PlayConversationToLocalClient( "RoundWonAnnouncement" )
+ else if ( level.nv.winningTeam != TEAM_UNASSIGNED )
+ PlayConversationToLocalClient( "RoundLostAnnouncement" )
+}
+
+void function UpdateScoreboardBadRepPresentMessage()
+{
+ if ( IsLobby() )
+ return
+
+ entity player = GetLocalClientPlayer()
+
+ if ( level.nv.badRepPresent )
+ player.cv.scoreboardBadRepPresentMessage.Show()
+ else
+ player.cv.scoreboardBadRepPresentMessage.Hide()
+}
+
+void function UpdateChatHudLocationForTop3()
+{
+ var hudElement = HudElement( "IngameTextChat" )
+ var height = hudElement.GetHeight()
+ var screenSize = Hud.GetScreenSize()
+ var position = hudElement.GetPos()
+ HudElement( "IngameTextChat" ).SetPos( position[0], -1 * ( screenSize[1] - ( height + screenSize[1] * 0.10 ) ) )
+}
+
+void function DisplayPostMatchTop3()
+{
+ array<entity> players = GetPlayerArray()
+
+ for ( int i = players.len() - 1; i >= 0; i-- )
+ {
+ if ( IsPrivateMatchSpectator( players[ i ] ) )
+ players.remove( i )
+ }
+
+ if ( players.len() >= 1 )
+ {
+ int localTeam = GetLocalClientPlayer().GetTeam()
+ #if PC_PROG
+ var rui = RuiCreate( $"ui/scoreboard_postmatch_top3_pc.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 1001 )
+ UpdateChatHudLocationForTop3()
+ #else
+ var rui = RuiCreate( $"ui/scoreboard_postmatch_top3.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 1001 )
+ #endif
+ string gamemode = GameRules_GetGameMode()
+ int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( gamemode )
+ if ( compareFunc == null )
+ {
+ printt( "gamemode doesn't have a compare func to display the top 3")
+ return
+ }
+ players.sort( compareFunc )
+ int playerCount = players.len()
+ int currentPlace = 1
+ for ( int i = 0; i < 3; i++ )
+ {
+ if ( i >= playerCount )
+ continue
+
+ string playerRank
+ if ( i > 0 && compareFunc( players[i - 1], players[i] ) != 0 )
+ currentPlace += 1
+
+ switch( currentPlace )
+ {
+ case 1:
+ playerRank = "#GENERATION_NUMERIC_1"
+ break
+ case 2:
+ playerRank = "#GENERATION_NUMERIC_2"
+ break
+ case 3:
+ playerRank = "#GENERATION_NUMERIC_3"
+ break
+ }
+
+ float cardScale = currentPlace == 1 ? 1.0 : 0.9
+
+ RuiSetString( rui, "playerName" + i, players[i].GetPlayerName() )
+ RuiSetString( rui, "playerRank" + i, Localize( playerRank ) )
+ RuiSetFloat( rui, "cardScale" + i, cardScale )
+ RuiSetImage( rui, "cardImage" + i, CallingCard_GetImage( PlayerCallingCard_GetActive( players[i] ) ) )
+ RuiSetImage( rui, "iconImage" + i, CallsignIcon_GetImage( PlayerCallsignIcon_GetActive( players[i] ) ) )
+ RuiSetInt( rui, "layoutType" + i, CallingCard_GetLayout( PlayerCallingCard_GetActive( players[i] ) ) )
+ RuiSetImage( rui, "cardGenImage" + i, PlayerXPGetGenIcon( players[i] ) )
+ RuiSetString( rui, "playerLevel" + i, PlayerXPDisplayGenAndLevel( players[i].GetGen(), players[i].GetLevel() ) )
+ RuiSetBool( rui, "isFriendly" + i, localTeam == players[i].GetTeam() )
+ }
+ }
+}
+
+
+float function GetGameStartTime()
+{
+ return expect float( level.nv.gameStartTime.tofloat() )
+}
+
+void function ClGameState_SetPilotTitanStatusCallback( int functionref(entity,int) func )
+{
+ file.pilotTitanStatusCallback = func
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_arena.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_arena.gnut
new file mode 100644
index 00000000..1765fd9b
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_arena.gnut
@@ -0,0 +1,185 @@
+global function GameModeArena_Init
+
+struct {
+ bool inBuyPhase
+
+ entity imcBoostStore
+ entity militiaBoostStore
+
+ entity imcShield
+ entity militiaShield
+} file
+
+void function GameModeArena_Init()
+{
+ SetSpawnpointGamemodeOverride( TEAM_DEATHMATCH )
+
+ SetShouldUseRoundWinningKillReplay( true )
+ SetRoundBased( true )
+ SetRespawnsEnabled( false )
+ SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ Riff_ForceBoostAvailability( eBoostAvailability.Disabled )
+ Riff_ForceSetEliminationMode( eEliminationMode.Pilots )
+
+ ClassicMP_SetCustomIntro( GameModeArena_BuyPhaseSetup, 30.0 )
+ AddCallback_EntitiesDidLoad( CreateBoostStores )
+ AddCallback_OnPlayerRespawned( SetupArenaLoadoutForPlayer )
+}
+
+// intro / buy phase
+
+void function GameModeArena_BuyPhaseSetup()
+{
+ AddCallback_OnClientConnected( SpawnPlayerIntoArenasIntro )
+ AddCallback_GameStateEnter( eGameState.Prematch, void function() { thread BuyPhase() } )
+}
+
+void function SpawnPlayerIntoArenasIntro( entity player )
+{
+ if ( GetGameState() == eGameState.Prematch )
+ RespawnAsPilot( player )
+}
+
+void function CreateBoostStores()
+{
+ array<entity> startspawns = GetEntArrayByClass_Expensive( "info_spawnpoint_human_start" ) // easier to do this than use a spawn callback imo
+
+ vector imcAverageOrigin
+ float imcAverageAngle
+ int imcNumSpawns
+
+ vector militiaAverageOrigin
+ float militiaAverageAngle
+ int militiaNumSpawns
+
+ foreach ( entity startspawn in startspawns )
+ {
+ if ( !startspawn.HasKey( "gamemode_tdm" ) || startspawn.kv.gamemode_tdm == "0" )
+ continue
+
+ if ( startspawn.GetTeam() == TEAM_IMC )
+ {
+ imcAverageOrigin += startspawn.GetOrigin()
+ imcAverageAngle += startspawn.GetAngles().y
+ imcNumSpawns++
+ }
+ else
+ {
+ militiaAverageOrigin += startspawn.GetOrigin()
+ militiaAverageAngle += startspawn.GetAngles().y
+ militiaNumSpawns++
+ }
+ }
+
+ // create imc boost store
+ vector finalPositionImc = < imcAverageOrigin.x / imcNumSpawns, imcAverageOrigin.y / imcNumSpawns, imcAverageOrigin.z / imcNumSpawns >
+ finalPositionImc += ( 200 * AnglesToForward( < 0, imcAverageAngle / imcNumSpawns, 0 > ) )
+ CreateBoostStoreLocation( TEAM_IMC, finalPositionImc, < 0, 0, 0 >, true )
+
+ vector finalPositionMilitia = < militiaAverageOrigin.x / militiaNumSpawns, militiaAverageOrigin.y / militiaNumSpawns, militiaAverageOrigin.z / militiaNumSpawns >
+ finalPositionMilitia += ( 200 * AnglesToForward( < 0, militiaAverageAngle / militiaNumSpawns, 0 > ) )
+ CreateBoostStoreLocation( TEAM_MILITIA, finalPositionMilitia, < 0, 0, 0 >, true )
+
+ // createbooststorelocation is void so have to do this
+ // also boost store code is just fully fucked lol, teams only get set on open so can't compare teams at this point
+ // sorry if someone else makes their own boost stores lol this'll just break
+ // if there's some way to get the invisible crates used for boost stores i will be very happy
+
+ if ( GetBoostStores().len() != 2 )
+ print( "_gamemode_arena.gnut: there are more than 2 boost stores, very bad no good" )
+
+ file.imcBoostStore = GetBoostStores()[0]
+ file.militiaBoostStore = GetBoostStores()[1]
+}
+
+void function BuyPhase()
+{
+ ClassicMP_OnIntroStarted()
+
+ foreach ( entity player in GetPlayerArray() )
+ {
+ ScreenFadeFromBlack( player )
+ RespawnAsPilot( player )
+
+ AddMoneyToPlayer( player, GetCashBoostForRoundCount( GetRoundsPlayed() ) )
+ }
+
+ SetJoinInProgressBonus( GetCashBoostForRoundCount( GetRoundsPlayed() ) )
+
+ // sort of a hack, set up a new intro here, so dropship intro only ever plays once
+
+ //file.imcShield = CreateBubbleShieldWithSettings( TEAM_IMC, file.imcBoostStore.GetOrigin(), <0,0,0>, null, 15.0 )
+ //file.militiaShield = CreateBubbleShieldWithSettings( TEAM_MILITIA, file.militiaBoostStore.GetOrigin(), <0,0,0>, null, 15.0 )
+
+ entity imcShield = CreateEntity( "prop_dynamic" )
+ imcShield.SetValueForModelKey( $"models/fx/xo_shield.mdl" )
+ imcShield.kv.solid = 0
+ imcShield.kv.rendercolor = "255 255 255" // white
+ imcShield.kv.modelscale = 2.25
+ imcShield.SetOrigin( file.imcBoostStore.GetOrigin() )
+ DispatchSpawn( imcShield )
+
+ entity militiaShield = CreateEntity( "prop_dynamic" )
+ militiaShield.SetValueForModelKey( $"models/fx/xo_shield.mdl" )
+ militiaShield.kv.solid = 0
+ militiaShield.kv.rendercolor = "255 255 255" // white
+ militiaShield.kv.modelscale = 2.25
+ militiaShield.SetOrigin( file.militiaBoostStore.GetOrigin() )
+ DispatchSpawn( militiaShield )
+
+ // current problem, there is seemingly no way of getting a shield we can resize which actually resizes the collision
+ // could probably just damage players that try to leave lol
+
+ OpenBoostStores()
+
+ thread DamageLeavingPlayers( imcShield.GetOrigin(), militiaShield.GetOrigin() )
+
+ wait 30.0 // intro length
+
+ CloseBoostStores()
+ imcShield.Destroy()
+ militiaShield.Destroy()
+
+ foreach ( entity player in GetPlayerArray() )
+ if ( player.GetMainWeapons().len() != 3 )
+ player.GiveWeapon( "mp_weapon_semipistol" )
+
+ ClassicMP_OnIntroFinished()
+}
+
+void function DamageLeavingPlayers( vector imcOrigin, vector militiaOrigin )
+{
+ while ( GetGameState() == eGameState.Prematch )
+ {
+ wait 0.5
+ foreach ( entity player in GetPlayerArray() )
+ {
+ vector pos = imcOrigin
+ if ( player.GetTeam() == TEAM_MILITIA )
+ pos = militiaOrigin
+
+ if ( Distance( player.GetOrigin(), pos ) > 510.0 ) // roughly the size of the shield
+ player.TakeDamage( 25, svGlobal.worldspawn, svGlobal.worldspawn, {} )
+ }
+ }
+}
+
+void function SetupArenaLoadoutForPlayer( entity player )
+{
+ PilotLoadoutDef playerLoadout = clone GetActivePilotLoadout( player )
+
+ if ( GetGameState() == eGameState.Prematch ) // buy phase
+ {
+ playerLoadout.primary = ""
+ playerLoadout.primaryMods = []
+ playerLoadout.secondary = ""
+ playerLoadout.secondaryMods = []
+ playerLoadout.weapon3 = ""
+ playerLoadout.weapon3Mods = []
+ playerLoadout.ordnance = ""
+ playerLoadout.special = ""
+ }
+
+ GivePilotLoadout( player, playerLoadout )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut
new file mode 100644
index 00000000..b59cd2dd
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut
@@ -0,0 +1,195 @@
+untyped
+global function GamemodeFastball_Init
+
+struct {
+ // first panel is a, second is b, third is c
+ array<Point> panelSpawns
+} file
+
+void function GamemodeFastball_Init()
+{
+ // used for intro
+ PrecacheModel( $"models/titans/buddy/titan_buddy.mdl" )
+ PrecacheParticleSystem( $"P_BT_eye_SM" )
+
+ // used for respawn
+ PrecacheParticleSystem( $"P_pod_screen_lasers_OUT" )
+
+ SetShouldUseRoundWinningKillReplay( true )
+ SetRoundBased( true )
+ SetRespawnsEnabled( false )
+ Riff_ForceSetEliminationMode( eEliminationMode.Pilots )
+
+ // implementing intro in a different file because it'll likely be big
+ ClassicMP_SetCustomIntro( GamemodeFastballIntroSetup, 14.5 ) // bit of a guess number
+ AddCallback_EntitiesDidLoad( SpawnPanelsForLevel )
+ AddCallback_GameStateEnter( eGameState.Prematch, ResetPanels )
+ SetTimeoutWinnerDecisionFunc( FastballDecideWinner )
+
+ AddCallback_OnClientConnected( FastballInitPlayer )
+ AddCallback_OnPlayerKilled( FastballOnPlayerKilled ) // move this to a system in _gamestate soon!!
+
+ // setup spawns
+ // first is a, second is b, third is c
+
+ FastballAddBuddySpawnForLevel( "mp_angel_city", TEAM_IMC, < 2281.39, -3333.06, 200.031 >, < 0, 91.23, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_angel_city", TEAM_MILITIA, < -4139.57, 4684.4, 41.0313 >, <0, -14.326, 0 > )
+ FastballAddPanelSpawnsForLevel( "mp_angel_city", [
+ < 2055.94, 2040.03, 128.031 >, < 0, -180, 0 >,
+ < -274.757, 2255.22, 400.031 >, < 0, -176.251, 0 >,
+ < -3208.28, 2741.17, 128.031 >, < 0, -0.0821686, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_thaw", TEAM_MILITIA, < 2049.29, -4085.22, -274.839 >, < 0, 89.2991, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_thaw", TEAM_IMC, < 834.484, 2664.28, -380.515 >, < 0.532141, -90.875, -0.542593 > )
+ FastballAddPanelSpawnsForLevel( "mp_thaw", [
+ < -1026.71, -1691.93, -319.969 >, < 0, 90, 0 >,
+ < 1836.07, -538.823, -64.1245 >, < 0, -135, 0 >,
+ < 2840.15, 1321.17, -63.9688 >, < 0, 0, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_wargames", TEAM_MILITIA, < -4848.87, 682.17, -127.969 >, < 0, 0, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_wargames", TEAM_IMC, < 2960.78, 1229.36, -127.969 >, < 7.89891e-005, 146.505, 1.38387e-005 > )
+ FastballAddPanelSpawnsForLevel( "mp_wargames", [
+ < -691.961, 1888.56, 112.031 >, < 0, 90, 0 >,
+ < -1072.03, -508.229, -127.969 >, < 0, 180, 0 >,
+ < -24.291, -1403.69, -119.969 >, < 0, -90, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_eden", TEAM_MILITIA, < -2404.4, 1738.07, 167.767 >, <0, -40.0894, 0> )
+ FastballAddBuddySpawnForLevel( "mp_eden", TEAM_IMC, < 5248.01, 414.698, 77.1051 >, < 0, 180, 0 > )
+ FastballAddPanelSpawnsForLevel( "mp_eden", [
+ < 704.333, 1530.18, 144.031 >, < 0, 90, 0 >,
+ < -108.43, 272.638, 72.0313 >, < 0, -90, 0 >,
+ <1044.25, -1145.68, 68.0313>, < 0, 180, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_black_water_canal", TEAM_MILITIA, < 1222.88, -5050.63, -187.763 >, < 0, 45, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_black_water_canal", TEAM_IMC, < -235.375, 4736.75, -255.969 >, < 0, -90, 0 > )
+ FastballAddPanelSpawnsForLevel( "mp_black_water_canal", [
+ < 1566.13, -731.539, -63.9688 >, < 0, -90, 0 >,
+ < 502.603, 1102.92, 260.031 >, < 0, 152.328, 0 >,
+ < 2337.37, 2099.91, -26.9688 >, < 0, 0, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_grave", TEAM_MILITIA, < 11026.8, -5163.18, 1885.64 >, < 0, 155.05, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_grave", TEAM_IMC, < -1952, -3120, 1993.33 >, < 0, 0, 0 > )
+ FastballAddPanelSpawnsForLevel( "mp_grave", [
+ < 5204.54, -2726.54, 2376.03 >, < 0, -90, 0 >,
+ < 6001.58, -4126.61, 2252.03 >, < 0, -135, 0 >,
+ < 3595.96, -4568.04, 2376.03 >, < 0, -135, 0 >
+ ])
+}
+
+void function SpawnPanelsForLevel()
+{
+ int panelId
+ foreach ( Point panelSpawn in file.panelSpawns )
+ {
+ entity panel = CreatePanel( panelSpawn.origin, panelSpawn.angles )
+ panel.s.panelId <- panelId++
+ }
+}
+
+void function ResetPanels()
+{
+ foreach ( entity panel in GetAllControlPanels() )
+ SetTeam( panel, TEAM_UNASSIGNED )
+}
+
+void function FastballInitPlayer( entity player )
+{
+ foreach ( entity panel in GetAllControlPanels() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_FastballUpdatePanelRui", panel.GetEncodedEHandle(), panel.s.panelId )
+}
+
+void function FastballAddPanelSpawnsForLevel( string level, array<vector> positionsAndOrigins )
+{
+ if ( GetMapName() != level )
+ return
+
+ for ( int i = 0; i < positionsAndOrigins.len(); i += 2 )
+ {
+ Point spawnPoint
+ spawnPoint.origin = positionsAndOrigins[ i ]
+ spawnPoint.angles = positionsAndOrigins[ i + 1 ]
+
+ file.panelSpawns.append( spawnPoint )
+ }
+}
+
+entity function CreatePanel( vector origin, vector angles )
+{
+ entity panel = CreateEntity( "prop_control_panel" )
+ panel.SetValueForModelKey( $"models/communication/terminal_usable_imc_01.mdl" )
+ panel.SetOrigin( origin )
+ panel.SetAngles( angles )
+ panel.kv.solid = SOLID_VPHYSICS
+ DispatchSpawn( panel )
+
+ panel.SetModel( $"models/communication/terminal_usable_imc_01.mdl" )
+ panel.s.onPlayerFinishesUsing_func = FastballOnPanelHacked
+
+ Highlight_SetNeutralHighlight( panel, "sp_enemy_pilot" )
+
+ return panel
+}
+
+// control panel code isn't very statically typed, pain
+function FastballOnPanelHacked( panel, player, success )
+{
+ expect entity( panel )
+ expect entity( player )
+ expect bool( success )
+
+ if ( !success )
+ return
+
+ print( panel + " was hacked by " + player )
+ PanelFlipsToPlayerTeamAndUsableByEnemies( panel, player )
+ player.SetPlayerGameStat( PGS_ASSAULT_SCORE, player.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 )
+
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_FastballPanelHacked", panel.GetEncodedEHandle(), panel.s.panelId, player.GetEncodedEHandle() )
+
+ // respawn dead players
+ foreach ( entity deadPlayer in GetPlayerArrayOfTeam( player.GetTeam() ) )
+ {
+ if ( !IsAlive( deadPlayer ) )
+ {
+ deadPlayer.SetOrigin( player.GetOrigin() )
+ deadPlayer.RespawnPlayer( null )
+ Remote_CallFunction_NonReplay( deadPlayer, "ServerCallback_FastballRespawnPlayer" )
+ }
+ }
+}
+
+void function FastballOnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( GetPlayerArrayOfTeam_Alive( victim.GetTeam() ).len() == 0 )
+ SetWinner( GetOtherTeam( victim.GetTeam() ) )
+}
+
+int function FastballDecideWinner()
+{
+ int militiaPanels
+ int imcPanels
+
+ foreach ( entity panel in GetAllControlPanels() )
+ {
+ if ( panel.GetTeam() == TEAM_MILITIA )
+ militiaPanels++
+ else if ( panel.GetTeam() == TEAM_IMC )
+ imcPanels++
+ }
+
+ if ( militiaPanels > imcPanels )
+ return TEAM_MILITIA
+ else if ( imcPanels > militiaPanels )
+ return TEAM_IMC
+
+ return TEAM_UNASSIGNED
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut
new file mode 100644
index 00000000..75482558
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut
@@ -0,0 +1,201 @@
+untyped
+global function GamemodeFastballIntroSetup
+global function FastballAddBuddySpawnForLevel
+
+struct {
+ float introStartTime
+
+ table< int, Point > buddySpawns
+ entity militiaBuddy
+ entity imcBuddy
+
+ table<entity, bool> playersHoldingJump
+} file
+
+void function GamemodeFastballIntroSetup()
+{
+ RegisterSignal( "fastball_start_throw" )
+ RegisterSignal( "fastball_release" )
+
+ AddCallback_GameStateEnter( eGameState.Prematch, OnPrematchStart )
+ AddCallback_OnClientConnected( AddPlayerToFastballIntro )
+}
+
+void function FastballAddBuddySpawnForLevel( string level, int team, vector origin, vector angles )
+{
+ if ( GetMapName() != level )
+ return
+
+ Point spawnPoint
+ spawnPoint.origin = origin
+ spawnPoint.angles = angles
+
+ file.buddySpawns[ team ] <- spawnPoint
+}
+
+void function OnPrematchStart()
+{
+ ClassicMP_OnIntroStarted()
+
+ file.introStartTime = Time()
+ file.playersHoldingJump = {} // clear it
+
+ Point militiaBuddySpawn
+ Point imcBuddySpawn
+
+ // figure out positions if there's none manually specified
+ if ( file.buddySpawns.len() == 0 )
+ {
+ array<entity> militiaSpawns
+ array<entity> imcSpawns
+
+ foreach ( entity spawnpoint in GetEntArrayByClass_Expensive( "info_spawnpoint_titan_start" ) )
+ {
+ // trace from top to bottom
+ float result = TraceHullSimple( spawnpoint.GetOrigin() + < 0, 0, 250 >, spawnpoint.GetOrigin(), < -200, -200, 0 >, < 200, 200, 400 >, null )
+
+ // don't need to trace much, as long as it's over 0 that means it fits
+ // cases where it's over 0 but less than 1 are usually caused by terrain
+ if ( result > 0 )
+ {
+ if ( spawnpoint.GetTeam() == TEAM_MILITIA )
+ militiaSpawns.append( spawnpoint )
+ else
+ imcSpawns.append( spawnpoint )
+ }
+ }
+
+ entity milititaSpawnEnt = militiaSpawns[ RandomInt( militiaSpawns.len() ) ]
+ militiaBuddySpawn.origin = milititaSpawnEnt.GetOrigin()
+ militiaBuddySpawn.angles = milititaSpawnEnt.GetAngles()
+
+ entity imcSpawnEnt = imcSpawns[ RandomInt( imcSpawns.len() ) ]
+ imcBuddySpawn.origin = imcSpawnEnt.GetOrigin()
+ imcBuddySpawn.angles = imcSpawnEnt.GetAngles()
+ }
+ else
+ {
+ militiaBuddySpawn = file.buddySpawns[ TEAM_MILITIA ]
+ imcBuddySpawn = file.buddySpawns[ TEAM_IMC ]
+ }
+
+ file.militiaBuddy = CreatePropDynamic( $"models/titans/buddy/titan_buddy.mdl" )
+ file.militiaBuddy.SetOrigin( militiaBuddySpawn.origin )
+ file.militiaBuddy.SetAngles( militiaBuddySpawn.angles )
+ thread AnimateBuddy( file.militiaBuddy )
+
+ file.imcBuddy = CreatePropDynamic( $"models/titans/buddy/titan_buddy.mdl" )
+ file.imcBuddy.SetOrigin( imcBuddySpawn.origin )
+ file.imcBuddy.SetAngles( imcBuddySpawn.angles )
+ thread AnimateBuddy( file.imcBuddy )
+
+ foreach ( entity player in GetPlayerArray() )
+ thread FastballPlayer( player )
+}
+
+void function AnimateBuddy( entity buddy )
+{
+ print( "buddy spawn at " + buddy.GetOrigin() + " " + buddy.GetAngles() )
+
+ thread PlayAnim( buddy, "bt_beacon_fastball_throw_end" )
+
+ // play dialogue at the right time
+ buddy.WaitSignal( "fastball_start_throw" )
+ float diagDuration = EmitSoundOnEntity( buddy, "diag_sp_spoke1_BE117_04_01_mcor_bt" ) // trust me
+ StartParticleEffectOnEntity( buddy, GetParticleSystemIndex( $"P_BT_eye_SM" ), FX_PATTACH_POINT_FOLLOW, buddy.LookupAttachment( "EYEGLOW" ) )
+
+ wait diagDuration
+ if ( GetGameState() != eGameState.Playing )
+ ClassicMP_OnIntroFinished()
+
+ buddy.WaitSignal( "fastball_release" )
+ wait 5.0
+ buddy.Destroy()
+}
+
+void function AddPlayerToFastballIntro( entity player )
+{
+ if ( GetGameState() == eGameState.Prematch )
+ thread FastballPlayer( player )
+}
+
+void function FastballPlayer( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+
+ OnThreadEnd( function() : ( player )
+ {
+ RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
+ player.ClearParent()
+ ClearPlayerAnimViewEntity( player )
+ player.DeployWeapon()
+ player.PlayerCone_Disable()
+ })
+
+ FirstPersonSequenceStruct throwSequence
+ throwSequence.attachment = "REF"
+ throwSequence.useAnimatedRefAttachment = true
+ throwSequence.hideProxy = true
+ throwSequence.viewConeFunction = ViewConeFastball // this seemingly does not trigger for some reason
+ throwSequence.firstPersonAnim = "ptpov_beacon_fastball_throw_end"
+ // mp models seemingly have no 3p animation for this
+ throwSequence.firstPersonBlendOutTime = 0.0
+ throwSequence.teleport = true
+ throwSequence.setInitialTime = Time() - file.introStartTime
+
+ // get our buddy
+ entity buddy
+ if ( player.GetTeam() == TEAM_MILITIA )
+ buddy = file.militiaBuddy
+ else
+ buddy = file.imcBuddy
+
+ // respawn the player
+ player.SetOrigin( buddy.GetOrigin() )
+ player.RespawnPlayer( null )
+ player.Hide()
+ player.HolsterWeapon()
+
+ // hide hud, fade screen out from black
+ AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
+ ScreenFadeFromBlack( player, 0.5, 0.5 )
+
+ // start fp sequence
+ thread FirstPersonSequence( throwSequence, player, buddy )
+
+ // manually do this because i can't get viewconefastball to work
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -50 )
+ player.PlayerCone_SetMaxYaw( 25 )
+ player.PlayerCone_SetMinPitch( -15 )
+ player.PlayerCone_SetMaxPitch( 15 )
+
+ buddy.WaitSignal( "fastball_start_throw" )
+ // lock in their final angles at this point
+ vector throwVel = AnglesToForward( player.EyeAngles() ) * 950
+ throwVel.z = 675.0
+
+ // wait for it to finish
+ buddy.WaitSignal( "fastball_release" )
+
+ if ( player.IsInputCommandHeld( IN_JUMP ) )
+ throwVel.z = 850.0
+
+ // have to correct this manually here since due to no 3p animation our position isn't set right during this sequence
+ player.SetOrigin( buddy.GetAttachmentOrigin( buddy.LookupAttachment( "FASTBALL_R" ) ) )
+ player.Show()
+ player.SetVelocity( throwVel )
+
+ TryGameModeAnnouncement( player )
+}
+
+void function PlayerHoldingJumpInIntro( entity player )
+{
+ file.playersHoldingJump[ player ] <- true
+}
+
+void function PlayerNoLongerHoldingJumpInIntro( entity player )
+{
+ file.playersHoldingJump[ player ] <- false
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut
new file mode 100644
index 00000000..b9ee40f1
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut
@@ -0,0 +1,114 @@
+global function GamemodeGG_Init
+
+void function GamemodeGG_Init()
+{
+ SetSpawnpointGamemodeOverride( FFA )
+
+ SetShouldUseRoundWinningKillReplay( true )
+ Evac_SetEnabled( false )
+ SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period
+ SetWeaponDropsEnabled( false )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ Riff_ForceBoostAvailability( eBoostAvailability.Disabled )
+
+ AddCallback_OnPlayerRespawned( OnPlayerRespawned )
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+
+ AddCallback_GameStateEnter( eGameState.WinnerDetermined, OnWinnerDetermined )
+}
+
+void function OnPlayerRespawned( entity player )
+{
+ UpdateLoadout( player )
+ thread OnPlayerRespawned_Threaded( player )
+}
+
+void function OnPlayerRespawned_Threaded( entity player )
+{
+ // bit of a hack, need to rework earnmeter code to have better support for completely disabling it
+ // rn though this just waits for earnmeter code to set the mode before we set it back
+ WaitFrame()
+ PlayerEarnMeter_SetMode( player, eEarnMeterMode.DISABLED )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() || !attacker.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( attacker == victim ) // suicide
+ {
+ string message = victim.GetPlayerName() + " committed suicide."
+ foreach ( entity player in GetPlayerArray() )
+ SendHudMessage( player, message, -1, 0.4, 255, 0, 0, 0, 0, 3, 0.15 )
+
+ if ( GameRules_GetTeamScore( victim.GetTeam() ) != 0 )
+ {
+ AddTeamScore( victim.GetTeam(), -1 ) // get absolutely fucking destroyed lol
+ victim.AddToPlayerGameStat( PGS_ASSAULT_SCORE, -1 )
+ }
+ }
+ else
+ {
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) != eDamageSourceId.melee_pilot_emptyhanded )
+ {
+ AddTeamScore( attacker.GetTeam(), 1 )
+ attacker.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 )
+ UpdateLoadout( attacker )
+ }
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == eDamageSourceId.human_execution )
+ {
+ string message = victim.GetPlayerName() + " got executed."
+ foreach ( entity player in GetPlayerArray() )
+ SendHudMessage( player, message, -1, 0.4, 255, 0, 0, 0, 0, 3, 0.15 )
+
+ if ( GameRules_GetTeamScore( victim.GetTeam() ) != 0 )
+ {
+ AddTeamScore( victim.GetTeam(), -1 ) // get absolutely fucking destroyed lol
+ victim.AddToPlayerGameStat( PGS_ASSAULT_SCORE, -1 )
+ }
+ }
+ }
+}
+
+void function UpdateLoadout( entity player )
+{
+ // todo: honestly, this should be reworked to use PilotLoadoutDefs instead of directly modifying weapons and shit
+
+ int currentWeaponIndex = GameRules_GetTeamScore( player.GetTeam() )
+ array<GunGameWeapon> weapons = GetGunGameWeapons()
+
+ if ( currentWeaponIndex >= weapons.len() )
+ currentWeaponIndex = weapons.len() - 1
+
+ if ( currentWeaponIndex > 18 ) // play end of game music for special weapons
+ PlayMusicToAll( eMusicPieceID.LEVEL_LAST_MINUTE ) // this *shouldn't* overlap if done multiple times
+
+ GunGameWeapon weapon = weapons[ currentWeaponIndex ]
+
+ foreach ( entity weapon in player.GetMainWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ foreach ( entity weapon in player.GetOffhandWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ if ( weapon.offhandSlot != -1 )
+ {
+ // TEMP: give archer so player so player has a weapon which lets them use offhands
+ // need to replace this with a custom empty weapon at some point
+ player.GiveWeapon( "mp_weapon_rocket_launcher" )
+
+ player.GiveOffhandWeapon( weapon.weapon, weapon.offhandSlot, weapon.mods )
+ }
+ else
+ player.GiveWeapon( weapon.weapon, weapon.mods )
+
+ player.GiveOffhandWeapon( "melee_pilot_emptyhanded", OFFHAND_MELEE )
+}
+
+void function OnWinnerDetermined()
+{
+ SetRespawnsEnabled( false )
+ SetKillcamsEnabled( false )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut
new file mode 100644
index 00000000..8706d53b
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut
@@ -0,0 +1,179 @@
+global function GamemodeInfection_Init
+
+struct {
+ bool hasHadFirstInfection = false
+ array<entity> playersToNotifyOfInfection
+} file
+
+void function GamemodeInfection_Init()
+{
+ SetSpawnpointGamemodeOverride( FFA )
+ SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period
+ SetWeaponDropsEnabled( false )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ Riff_ForceBoostAvailability( eBoostAvailability.Disabled )
+
+ AddCallback_OnClientConnected( InfectionInitPlayer )
+ AddCallback_OnPlayerKilled( InfectionOnPlayerKilled )
+ AddCallback_OnPlayerRespawned( RespawnInfected )
+ AddCallback_GameStateEnter( eGameState.Playing, SelectFirstInfected )
+
+ SetTimeoutWinnerDecisionFunc( TimeoutCheckSurvivors )
+}
+
+void function InfectionInitPlayer( entity player )
+{
+ if ( GetGameState() < eGameState.Playing )
+ SetTeam( player, INFECTION_TEAM_SURVIVOR )
+ else
+ InfectPlayer( player )
+}
+
+void function SelectFirstInfected()
+{
+ thread SelectFirstInfectedDelayed()
+}
+
+void function SelectFirstInfectedDelayed()
+{
+ wait 10.0 + RandomFloat( 5.0 )
+
+ array<entity> players = GetPlayerArray()
+ entity infected = players[ RandomInt( players.len() ) ]
+
+ InfectPlayer( infected )
+ RespawnInfected( infected )
+}
+
+void function InfectionOnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( victim.GetTeam() == INFECTION_TEAM_SURVIVOR )
+ InfectPlayer( victim )
+
+ if ( attacker.IsPlayer() )
+ attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 )
+}
+
+void function InfectPlayer( entity player )
+{
+ SetTeam( player, INFECTION_TEAM_INFECTED )
+ player.SetPlayerGameStat( PGS_ASSAULT_SCORE, 0 ) // reset kills
+ file.playersToNotifyOfInfection.append( player )
+
+ // check how many survivors there are
+ array<entity> survivors = GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR )
+ if ( survivors.len() == 0 )
+ SetWinner( INFECTION_TEAM_INFECTED )
+ else if ( survivors.len() == 1 )
+ SetLastSurvivor( survivors[ 0 ] )
+
+ if ( !file.hasHadFirstInfection )
+ {
+ file.hasHadFirstInfection = true
+
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ if ( player != otherPlayer )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_AnnounceFirstInfected", player.GetEncodedEHandle() )
+
+ PlayMusicToAll( eMusicPieceID.GAMEMODE_1 )
+ }
+}
+
+void function RespawnInfected( entity player )
+{
+ if ( player.GetTeam() != INFECTION_TEAM_INFECTED )
+ return
+
+ // notify newly infected players of infection
+ if ( file.playersToNotifyOfInfection.contains( player ) )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_YouAreInfected" )
+ file.playersToNotifyOfInfection.remove( file.playersToNotifyOfInfection.find( player ) )
+ }
+
+ // set camo to pond scum
+ player.SetSkin( 1 )
+ player.SetCamo( 110 )
+
+ // stats for infected
+ StimPlayer( player, 9999.9 ) // can't do endless since we don't get the visual effect in endless
+ player.kv.airAcceleration = 2500
+
+ // scale health with num of infected, with 50 as base health
+ player.SetMaxHealth( ( GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR ).len() + 1 ) * 10 )
+
+ // set loadout
+ foreach ( entity weapon in player.GetMainWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ foreach ( entity weapon in player.GetOffhandWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ // TEMP: give archer so player so player has a weapon which lets them use offhands
+ // need to replace this with a custom empty weapon at some point
+ //player.GiveWeapon( "mp_weapon_rocket_launcher" )
+ player.GiveWeapon( "mp_weapon_mgl" )
+ player.GiveOffhandWeapon( "melee_pilot_emptyhanded", OFFHAND_MELEE )
+
+ thread PlayInfectedSounds( player )
+}
+
+void function PlayInfectedSounds( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+
+ float nextRandomSound
+ while ( true )
+ {
+ WaitFrame()
+
+ int meleeState = player.PlayerMelee_GetState()
+ if ( nextRandomSound < Time() || meleeState != 0 )
+ {
+ string selectedSound
+ if ( CoinFlip() )
+ selectedSound = "prowler_vocal_attack"
+ else
+ selectedSound = "prowler_vocal_attackmiss"
+
+ bool canSeeSurvivor
+ foreach ( entity survivor in GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR ) )
+ if ( TraceLineSimple( player.GetOrigin(), survivor.GetOrigin(), survivor ) == 1.0 )
+ canSeeSurvivor = true
+
+ // _int sounds are less agressive so only play them if we aren't in some sorta fight
+ if ( player.GetHealth() == player.GetMaxHealth() || !canSeeSurvivor || meleeState != 0 )
+ selectedSound += "_int"
+
+ EmitSoundOnEntity( player, selectedSound )
+
+ nextRandomSound = Time() + max( 2.5, RandomFloat( 12.0 ) )
+ while ( player.PlayerMelee_GetState() != 0 ) // need to ensure this is updated
+ WaitFrame()
+ }
+ }
+}
+
+void function SetLastSurvivor( entity player )
+{
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_AnnounceLastSurvivor", player.GetEncodedEHandle() )
+
+ Highlight_SetEnemyHighlight( player, "enemy_sonar" )
+ thread CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) )
+
+ if ( GameTime_TimeLeftSeconds() > 45 )
+ SetServerVar( "gameEndTime", Time() + 45.0 )
+}
+
+int function TimeoutCheckSurvivors()
+{
+ if ( GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR ).len() > 0 )
+ return INFECTION_TEAM_SURVIVOR
+
+ return INFECTION_TEAM_INFECTED
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_kr.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_kr.gnut
new file mode 100644
index 00000000..cf9d6bc5
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_kr.gnut
@@ -0,0 +1,126 @@
+global function GamemodeKR_Init
+
+struct {
+ float currentHighestKillraceAmount
+ int currentKillraceScore
+ entity currentRacer
+ array<vector> flagSpawnPoints
+} file
+
+void function GamemodeKR_Init()
+{
+ PrecacheModel( CTF_FLAG_MODEL )
+
+ SetSpawnpointGamemodeOverride( FFA )
+
+ Evac_SetEnabled( false )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ Riff_ForceBoostAvailability( eBoostAvailability.Disabled )
+
+ AddSpawnCallback( "info_hardpoint", AddFlagSpawnPoint )
+
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+ AddCallback_OnTouchHealthKit( "item_flag", StartPlayerKillrace )
+
+ AddCallback_GameStateEnter( eGameState.Playing, StartKillraceSpawnThink )
+}
+
+void function AddFlagSpawnPoint( entity hardpoint )
+{
+ if ( hardpoint.HasKey( "hardpointGroup" ) && ( hardpoint.kv.hardpointGroup == "A" || hardpoint.kv.hardpointGroup == "B" || hardpoint.kv.hardpointGroup == "C" ) )
+ file.flagSpawnPoints.append( hardpoint.GetOrigin() )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() || !attacker.IsPlayer() || attacker == victim )
+ return
+
+ float killRaceTime = attacker.GetPlayerNetTime( "killRaceTime" ) + 5.0
+ attacker.SetPlayerNetTime( "killRaceTime", killRaceTime )
+ if ( killRaceTime > file.currentHighestKillraceAmount )
+ file.currentHighestKillraceAmount = killRaceTime
+ if ( file.currentRacer != null )
+ file.currentKillraceScore++
+}
+
+bool function StartPlayerKillrace( entity player, entity flag )
+{
+ float killRaceTime = player.GetPlayerNetTime( "killRaceTime" )
+ if ( killRaceTime > 0.0 )
+ {
+ thread PlayerKillrace( player, killRaceTime )
+ return true // delete the flag entity
+ }
+
+ return false // keep it alive
+}
+
+void function PlayerKillrace( entity player, float raceTime )
+{
+ file.currentKillraceScore = 0
+ file.currentRacer = player
+ int oldMaxHealth = player.GetMaxHealth()
+
+ player.SetMaxHealth( oldMaxHealth * 10 )
+ player.SetHealth( player.GetMaxHealth() )
+
+ foreach ( entity weapon in player.GetMainWeapons() )
+ foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
+ weapon.AddMod( mod )
+
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_NewKillRacer", player.GetEncodedEHandle(), Time() + raceTime )
+
+ float raceEnd = Time() + raceTime
+ while ( raceEnd > Time() && IsAlive( player ) )
+ WaitFrame()
+
+ player.SetPlayerNetTime( "killRaceTime", 0.0 )
+ player.SetMaxHealth( oldMaxHealth )
+
+ foreach ( entity weapon in player.GetMainWeapons() )
+ foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
+ weapon.RemoveMod( mod )
+
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_EndKillrace", player.GetEncodedEHandle(), file.currentKillraceScore )
+
+ if ( GameRules_GetTeamScore( player.GetTeam() ) < file.currentKillraceScore )
+ {
+ GameRules_SetTeamScore( player.GetTeam(), file.currentKillraceScore )
+ player.SetPlayerGameStat( PGS_ASSAULT_SCORE, file.currentKillraceScore )
+ }
+
+ thread KillraceSpawnThink() // go to spawn next flag
+}
+
+void function StartKillraceSpawnThink()
+{
+ thread KillraceSpawnThink()
+}
+
+void function KillraceSpawnThink()
+{
+ file.currentHighestKillraceAmount = 0
+ file.currentRacer = null
+ file.currentKillraceScore = 0
+ float time = Time()
+ while ( time + 20.0 > Time() && file.currentHighestKillraceAmount < 25 )
+ WaitFrame()
+
+ vector spawnpos = file.flagSpawnPoints[ RandomInt( file.flagSpawnPoints.len() ) ]
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_FlagSpawnIncoming", spawnpos.x, spawnpos.y, spawnpos.z, Time() + 15 )
+
+ wait 15.0
+
+ // create a flag
+ entity flag = CreateEntity( "item_flag" )
+ flag.SetValueForModelKey( CTF_FLAG_MODEL )
+ SetTargetName( flag, "krflag" )
+ DispatchSpawn( flag )
+ flag.SetModel( CTF_FLAG_MODEL )
+ flag.SetOrigin( spawnpos + < 0, 0, flag.GetBoundingMaxs().z / 2 > ) // get it out of the ground
+ flag.SetVelocity( < 0, 0, 1 > ) // make it do gravity again
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_sbox.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_sbox.gnut
new file mode 100644
index 00000000..27581aea
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_sbox.gnut
@@ -0,0 +1,49 @@
+untyped
+global function GamemodeSbox_Init
+
+struct {
+ array<entity> spawnpoints
+} file
+
+void function GamemodeSbox_Init()
+{
+ SetConVarInt( "sv_cheats", 1 ) // cheats on by default
+
+ // cache spawnpoints
+ //file.spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_human" )
+ // todo just use a spawn callback for this rather than weird late cache in spawn
+
+ AddCallback_OnClientConnected( SboxSpawnPlayer )
+ AddDeathCallback( "player", SboxRespawnPlayer )
+}
+
+void function SboxSpawnPlayer( entity player )
+{
+ if ( player.GetPlayerSettings() == "spectator" ) // if they haven't spawned yet
+ player.SetPlayerSettings( "pilot_grapple_male" )
+
+ if ( file.spawnpoints.len() == 0 ) // have to cache late rather than on init due to spawnpoints not existing in init
+ file.spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_human" )
+
+ if ( GetGameState() != eGameState.Playing ) // hacky but can't set this in init either
+ SetGameState( eGameState.Playing )
+
+ entity spawnpoint = file.spawnpoints[ RandomInt( file.spawnpoints.len() ) ]
+
+ ScreenFadeFromBlack( player, 0.0, 0.0 ) // HACK before non-classicmp intros are ready, remove the blackscreen we get from waitingforplayers
+ player.RespawnPlayer( spawnpoint )
+ player.GiveWeapon( "mp_weapon_toolgun" )
+}
+
+void function SboxRespawnPlayer( entity player, var damageInfo )
+{
+ thread SboxRespawnPlayerThreaded( player )
+}
+
+void function SboxRespawnPlayerThreaded( entity player )
+{
+ // todo: replace this with real respawn logic when that's ready
+
+ wait 2.5
+ SboxSpawnPlayer( player )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tt.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tt.gnut
new file mode 100644
index 00000000..6a53ef87
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tt.gnut
@@ -0,0 +1,71 @@
+global function GamemodeTT_Init
+
+struct {
+ entity lastPlayerDropped
+} file
+
+void function GamemodeTT_Init()
+{
+ SetSpawnpointGamemodeOverride( TEAM_DEATHMATCH )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+
+ AddCallback_GameStateEnter( eGameState.Playing, OnEnterPlaying )
+
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+ AddDeathCallback( "npc_titan", OnTitanKilled )
+}
+
+void function OnEnterPlaying()
+{
+ thread DropRandomTitan()
+}
+
+void function DropRandomTitan()
+{
+ array<entity> players = GetPlayerArray()
+
+ if ( players.len() == 1 )
+ file.lastPlayerDropped = null // don't wanna loop forever if only 1 player
+
+ entity titanPlayer
+ do {
+ titanPlayer = players[ RandomInt( players.len() ) ]
+ } while ( titanPlayer == file.lastPlayerDropped )
+
+ DropTitanForPlayer( titanPlayer, 5.0 )
+}
+
+void function DropTitanForPlayer( entity player, float delay )
+{
+ wait delay
+
+ file.lastPlayerDropped = player
+ CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) )
+}
+
+void function AttemptToDropTitanForKill( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsTitan() )
+ return
+
+ if ( !attacker.IsPlayer() || victim == attacker )
+ thread DropRandomTitan()
+ else
+ thread DropTitanForPlayer( attacker, 2.0 )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim.IsTitan() )
+ AttemptToDropTitanForKill( victim, attacker, damageInfo )
+ else if ( attacker.IsTitan() )
+ {
+ AddTeamScore( attacker.GetTeam(), 1 )
+ }
+}
+
+void function OnTitanKilled( entity victim, var damageInfo )
+{
+ if ( IsPetTitan( victim ) )
+ AttemptToDropTitanForKill( victim, DamageInfo_GetAttacker( damageInfo ), damageInfo )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_riff_instagib.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_riff_instagib.gnut
new file mode 100644
index 00000000..b3868359
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_riff_instagib.gnut
@@ -0,0 +1,65 @@
+global function RiffInstagib_Init
+
+struct {
+ table<entity, int> playerWeapons
+ array<string> instagibWeapons
+} file
+
+void function RiffInstagib_Init()
+{
+ if ( GetCurrentPlaylistVarInt( "riff_instagib", 0 ) == 0 )
+ return
+
+ SetLoadoutGracePeriodEnabled( false )
+ SetWeaponDropsEnabled( false )
+
+ file.instagibWeapons = [
+ "mp_weapon_sniper",
+ "mp_weapon_wingman",
+ "mp_weapon_defender",
+ "mp_weapon_arena3",
+ "mp_weapon_wingman_n",
+ "mp_weapon_doubletake",
+ ]
+ file.instagibWeapons.randomize()
+
+ AddCallback_OnPlayerRespawned( InstagibSetWeapons )
+ AddCallback_OnPlayerKilled( InstagibCycleWeaponsForKill )
+ AddCallback_OnClientDisconnected( InstagibCleanupClient )
+}
+
+void function InstagibSetWeapons( entity player )
+{
+ if ( !( player in file.playerWeapons ) )
+ file.playerWeapons[ player ] <- 0
+
+ player.SetMaxHealth( 1 )
+ InstagibUpdateWeapons( player )
+}
+
+void function InstagibUpdateWeapons( entity player )
+{
+ foreach( entity weapon in player.GetMainWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ player.TakeWeaponNow( player.GetOffhandWeapon( OFFHAND_RIGHT ).GetWeaponClassName() )
+ if ( !HasOffhandWeapon( player, "mp_weapon_grenade_sonar" ) )
+ player.GiveOffhandWeapon( "mp_weapon_grenade_sonar", OFFHAND_RIGHT )
+
+ player.GiveWeapon( file.instagibWeapons[ file.playerWeapons[ player ] ] )
+}
+
+void function InstagibCycleWeaponsForKill( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() || !attacker.IsPlayer() || victim == attacker )
+ return
+
+ file.playerWeapons[ attacker ] = ( file.playerWeapons[ attacker ] + 1 ) % file.instagibWeapons.len()
+ InstagibUpdateWeapons( attacker )
+}
+
+void function InstagibCleanupClient( entity player )
+{
+ if ( player in file.playerWeapons )
+ delete file.playerWeapons[ player ]
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut
new file mode 100644
index 00000000..632a7efc
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut
@@ -0,0 +1,40 @@
+global function ClGameModeArena_Init
+global function ServerCallback_CreateMoneyParticles
+
+struct {
+ var moneyRui
+} file
+
+void function ClGameModeArena_Init()
+{
+ AddCallback_OnClientScriptInit( CreateArenaUI )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_fd_intro_easy", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_fd_intro_easy", TEAM_IMC )
+}
+
+void function CreateArenaUI( entity player )
+{
+ AddEventNotificationCallback( eEventNotifications.FD_BoughtItem, void function( entity e, var v ) {} )
+
+ var rui = CreateCockpitRui( $"ui/fd_score_splash.rpak", 500 )
+ RuiTrackInt( rui, "pointValue", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "FD_money" ) )
+ RuiTrackInt( rui, "pointStack", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "FD_money256" ) )
+ file.moneyRui = rui
+}
+
+void function ServerCallback_CreateMoneyParticles( int playerHandle, int amount )
+{
+ // largely taken from cl_gamemode_fd
+ entity player = GetEntityFromEncodedEHandle( playerHandle )
+
+ vector randDir2D = < RandomFloatRange( -1, 1 ), 1, 0 >
+ randDir2D = Normalize( randDir2D )
+
+ var rui = RuiCreate( $"ui/at_score_popup.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 100 )
+ RuiSetInt( rui, "scoreVal", amount )
+ RuiSetGameTime( rui, "startTime", Time() )
+ RuiSetFloat3( rui, "pos", player.EyePosition() )
+ RuiSetFloat2( rui, "driftDir", randDir2D )
+ RuiSetBool( rui, "showNormalPoints", false )
+} \ 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
new file mode 100644
index 00000000..80dc548a
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut
@@ -0,0 +1,96 @@
+global function ClGamemodeFastball_Init
+global function ServerCallback_FastballUpdatePanelRui
+global function ServerCallback_FastballPanelHacked
+global function ServerCallback_FastballRespawnPlayer
+
+struct {
+ var panelARui
+ var panelBRui
+ var panelCRui
+} file
+
+void function ClGamemodeFastball_Init()
+{
+ ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_lts.rpak" )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "Music_Beacon_14_BTThrowThruFirstCrane", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "Music_Beacon_14_BTThrowThruFirstCrane", TEAM_MILITIA )
+
+ AddCallback_OnClientScriptInit( FastballCreateRui )
+}
+
+void function FastballCreateRui( entity player )
+{
+ file.panelARui = CreateCockpitRui( $"ui/cp_hardpoint_marker.rpak", 200 )
+ file.panelBRui = CreateCockpitRui( $"ui/cp_hardpoint_marker.rpak", 200 )
+ file.panelCRui = CreateCockpitRui( $"ui/cp_hardpoint_marker.rpak", 200 )
+
+}
+
+void function ServerCallback_FastballUpdatePanelRui( int panelHandle, int id )
+{
+ entity panel = GetEntityFromEncodedEHandle( panelHandle )
+ var rui
+ if ( id == 0 )
+ rui = file.panelARui
+ else if ( id == 1 )
+ rui = file.panelBRui
+ else if ( id == 2 )
+ rui = file.panelCRui
+
+ RuiSetInt( rui, "hardpointId", id )
+ RuiTrackFloat3( rui, "pos", panel, RUI_TRACK_OVERHEAD_FOLLOW )
+ RuiSetInt( rui, "viewerTeam", GetLocalClientPlayer().GetTeam() )
+ ////RuiTrackInt( rui, "cappingTeam", null, RUI_TRACK_SCRIPT_NETWORK_VAR_GLOBAL_INT, GetNetworkedVariableIndex( "panel" + id + "progress" ) )
+ RuiTrackInt( rui, "hardpointTeamRelation", panel, RUI_TRACK_TEAM_RELATION_VIEWPLAYER )
+
+ RuiSetBool( rui, "isVisible", true )
+}
+
+void function ServerCallback_FastballPanelHacked( int panelHandle, int id, int capturingPlayerHandle )
+{
+ ServerCallback_FastballUpdatePanelRui( panelHandle, id ) // may not be necessary, just wanna ensure this is always right
+
+ entity panel = GetEntityFromEncodedEHandle( panelHandle )
+ entity capturingPlayer = GetEntityFromEncodedEHandle( capturingPlayerHandle )
+
+ if ( capturingPlayer == GetLocalViewPlayer() )
+ return
+
+ string panelIdString
+ if ( id == 0 )
+ panelIdString = "A"
+ if ( id == 1 )
+ panelIdString = "B"
+ else if ( id == 2 )
+ panelIdString = "C"
+
+ AnnouncementData announcement = Announcement_Create( Localize( "#FASTBALL_PANEL_CAPTURED", capturingPlayer.GetPlayerName(), panelIdString ) )
+
+ if ( capturingPlayer.GetTeam() == GetLocalViewPlayer().GetTeam() )
+ Announcement_SetTitleColor( announcement, < 0, 0, 1 > )
+ else
+ Announcement_SetTitleColor( announcement, < 1, 0, 0 > )
+
+ Announcement_SetPurge( announcement, true )
+ 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 )
+}
+
+void function ServerCallback_FastballRespawnPlayer()
+{
+ thread FastballRespawnPlayerEffects_Threaded()
+}
+
+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()
+
+ while ( !IsAlive( player ) )
+ WaitFrame()
+
+ StartParticleEffectOnEntity( player.GetCockpit(), GetParticleSystemIndex( $"P_pod_screen_lasers_OUT" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut
new file mode 100644
index 00000000..de8a3449
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut
@@ -0,0 +1,26 @@
+global function ClGamemodeGG_Init
+
+void function ClGamemodeGG_Init()
+{
+ // add ffa gamestate asset
+ ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ffa.rpak" )
+
+ // add music for mode, this is copied directly from the ffa/fra music registered in cl_music.gnut
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_MILITIA )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut
new file mode 100644
index 00000000..56763bd4
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut
@@ -0,0 +1,75 @@
+global function ClGamemodeInfection_Init
+global function ServerCallback_YouAreInfected
+global function ServerCallback_AnnounceFirstInfected
+global function ServerCallback_AnnounceLastSurvivor
+
+void function ClGamemodeInfection_Init()
+{
+ //ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ffa.rpak" )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_beacon_8a_jumpingsuccess", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_beacon_8a_jumpingsuccess", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "Music_Beacon_24_BTLob", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "Music_Beacon_24_BTLob", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_titanwar_lastminute", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_titanwar_lastminute", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC )
+}
+
+void function ServerCallback_YouAreInfected()
+{
+ // heavily based on mfd code
+ entity localPlayer = GetLocalViewPlayer()
+
+ StartParticleEffectOnEntity( localPlayer.GetCockpit(), GetParticleSystemIndex( $"P_MFD" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 )
+ EmitSoundOnEntity( localPlayer, "UI_InGame_MarkedForDeath_PlayerMarked" )
+ HideEventNotification()
+ AnnouncementData announcement = Announcement_Create( "#INFECTION_YOU_ARE_INFECTED" )
+ Announcement_SetSubText( announcement, "#INFECTION_KILL_SURVIVORS" )
+ Announcement_SetTitleColor( announcement, <1,0,0> )
+ Announcement_SetPurge( announcement, true )
+ 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( localPlayer, announcement )
+}
+
+void function ServerCallback_AnnounceFirstInfected( int survivorEHandle )
+{
+ entity player = GetEntityFromEncodedEHandle( survivorEHandle )
+
+ AnnouncementData announcement = Announcement_Create( Localize( "#INFECTION_FIRST_INFECTED", player.GetPlayerName() ) )
+ //Announcement_SetSubText( announcement, "#INFECTION_KILL_LAST_SURVIVOR" )
+ Announcement_SetTitleColor( announcement, <1,0,0> )
+ Announcement_SetPurge( announcement, true )
+ 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 )
+}
+
+void function ServerCallback_AnnounceLastSurvivor( int survivorEHandle )
+{
+ entity player = GetEntityFromEncodedEHandle( survivorEHandle )
+
+ string announcementString = Localize( "#INFECTION_LAST_SURVIVOR", player.GetPlayerName() )
+ string announcementSubString = "#INFECTION_KILL_LAST_SURVIVOR"
+ if ( player == GetLocalViewPlayer() )
+ {
+ announcementString = "#INFECTION_YOU_ARE_LAST_SURVIVOR"
+ announcementSubString = "#INFECTION_SURVIVE_LAST_SURVIVOR"
+ }
+
+ AnnouncementData announcement = Announcement_Create( announcementString )
+ Announcement_SetSubText( announcement, announcementSubString )
+ Announcement_SetTitleColor( announcement, <1,0,0> )
+ Announcement_SetPurge( announcement, true )
+ 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 )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut
new file mode 100644
index 00000000..269057c7
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut
@@ -0,0 +1,241 @@
+global function ClGamemodeKR_Init
+
+global function ShowTimeGainOnKill
+global function ServerCallback_FlagSpawnIncoming
+global function ServerCallback_NewKillRacer
+global function ServerCallback_EndKillrace
+
+struct {
+ var currentTimeRui
+ var currentTimeAdditionRui
+ var flagRui
+ var flagIncomingRui
+ var killRacerRui
+
+ bool isCurrentlyInRace = false
+ float currentTimeAmount
+ float currentTimeLastAdditionTime
+ float currentTimeAdditionCombined
+} file
+
+void function ClGamemodeKR_Init()
+{
+ // add ffa gamestate asset
+ ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ffa.rpak" )
+
+ // add music for mode, this is copied directly from the ffa/fra music registered in cl_music.gnut
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_MILITIA )
+
+ AddCallback_OnClientScriptInit( CreateKRUI )
+ AddCreateCallback( "item_flag", OnFlagCreated )
+ AddDestroyCallback( "item_flag", OnFlagDestroyed )
+}
+
+void function CreateKRUI( entity player )
+{
+ file.currentTimeRui = CreateCockpitRui( $"ui/titan_protocol_text_center.rpak", 200 )
+ RuiSetInt( file.currentTimeRui, "lineNum", 1 )
+ RuiSetGameTime( file.currentTimeRui, "startTime", 0.0 )
+ UpdateCurrentTimeAmount()
+
+ file.flagRui = CreateCockpitRui( $"ui/speedball_flag_marker.rpak", 200 )
+ RuiSetBool( file.flagRui, "playerIsCarrying", false )
+ RuiSetBool( file.flagRui, "isCarried", false )
+
+ file.killRacerRui = CreateCockpitRui( $"ui/mfd_target_marker.rpak", 200 )
+ RuiSetBool( file.killRacerRui, "isVisible", false )
+ RuiSetImage( file.killRacerRui, "markedIcon", $"rui/hud/gametype_icons/mfd/mfd_enemy" )
+ RuiSetBool( file.killRacerRui, "isMarked", true )
+}
+
+void function OnFlagCreated( entity flag )
+{
+ if ( IsValid( file.flagIncomingRui ) )
+ RuiDestroy( file.flagIncomingRui )
+
+ RuiSetBool( file.flagRui, "isVisible", true )
+ RuiTrackFloat3( file.flagRui, "pos", flag, RUI_TRACK_OVERHEAD_FOLLOW )
+}
+
+void function OnFlagDestroyed( entity flag )
+{
+ RuiSetBool( file.flagRui, "isVisible", false )
+}
+
+void function ShowTimeGainOnKill( entity player, float oldVal, float newVal, bool actuallyChanged )
+{
+ if ( file.isCurrentlyInRace || player != GetLocalViewPlayer() || !actuallyChanged )
+ return
+
+ if ( newVal > oldVal ) // time increase: likely given on kill
+ {
+ float amount = newVal - oldVal
+
+ // show a combined number on the addition rui if last addition was recent enough
+ float additionShowAmount = amount
+ file.currentTimeAdditionCombined += amount
+ if ( Time() - file.currentTimeLastAdditionTime < 1.25 )
+ amount = file.currentTimeAdditionCombined
+ else
+ {
+ file.currentTimeAdditionRui = CreateCockpitRui( $"ui/titan_protocol_text_center.rpak", 200 )
+ RuiSetInt( file.currentTimeAdditionRui, "lineNum", 2 )
+ file.currentTimeAdditionCombined = amount
+ }
+
+ RuiSetString( file.currentTimeAdditionRui, "displayString", "+" + amount + "s 00ms " ) // formatted so that it lines up with other rui
+ RuiSetGameTime( file.currentTimeAdditionRui, "startTime", Time() )
+ RuiSetGameTime( file.currentTimeAdditionRui, "endTime", Time() + 1.5 )
+
+ file.currentTimeLastAdditionTime = Time()
+
+ thread UpdateFullTimeAmountAfterAdditionDone( file.currentTimeLastAdditionTime )
+ }
+ else // time decrease either a reset or
+ UpdateCurrentTimeAmount()
+}
+
+void function UpdateFullTimeAmountAfterAdditionDone( float previousAdditionTime )
+{
+ wait 1.25
+
+ if ( previousAdditionTime == file.currentTimeLastAdditionTime ) // if not, there's been another addition since this was last updated and we'll wait for that instead
+ UpdateCurrentTimeAmount()
+}
+
+void function UpdateCurrentTimeAmount( float overrideTime = -1 )
+{
+ if ( overrideTime == -1 )
+ file.currentTimeAmount = GetLocalViewPlayer().GetPlayerNetTime( "killRaceTime" )
+ else
+ file.currentTimeAmount = overrideTime
+
+ string currentTimeString
+ int seconds = file.currentTimeAmount.tointeger()
+
+ string secondsString = seconds.tostring()
+ if ( secondsString.len() < 2 ) // pad to 2 chars
+ secondsString = "0" + secondsString
+ currentTimeString += secondsString + "s "
+
+ string msString = ( ( file.currentTimeAmount - file.currentTimeAmount.tointeger() ) * 100 ).tostring()
+ if ( msString.len() < 2 ) // pad to 2 chars
+ msString = "0" + msString
+ currentTimeString += msString.slice( 0, 2 ) + "ms "
+
+ RuiSetString( file.currentTimeRui, "displayString", currentTimeString )
+ RuiSetGameTime( file.currentTimeRui, "endTime", Time() + 99999.0 ) // arbitrarily large number so this doesn't disappear
+}
+
+void function ServerCallback_FlagSpawnIncoming( float x, float y, float z , float spawnTime )
+{
+ print( "flagspawn: < " + x + ", " + y + ", " + z + " > in " + ( spawnTime - Time() ) + " seconds" )
+
+ AnnouncementData announcement = Announcement_Create( Localize( "#KR_FLAG_INCOMING", spawnTime.tostring() ) )
+ Announcement_SetSubText( announcement, "#KR_COLLECT_FLAG" )
+ Announcement_SetTitleColor( announcement, < 0, 0, 1 > )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 )
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+
+ RuiSetFloat3( file.flagRui, "pos", < x, y, z > )
+ RuiSetBool( file.flagRui, "isVisible", true )
+
+ file.flagIncomingRui = RuiCreate( $"ui/titanfall_timer.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 0 )
+ RuiTrackFloat3( file.flagIncomingRui, "playerPos", GetLocalViewPlayer(), RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiSetFloat3( file.flagIncomingRui, "pos", < x, y, z > + < 0, 0, 48 > )
+ RuiSetGameTime( file.flagIncomingRui, "impactTime", spawnTime )
+}
+
+void function ServerCallback_NewKillRacer( int playerHandle, float endTime )
+{
+ entity player = GetEntityFromEncodedEHandle( playerHandle )
+
+ string announcementMessage = Localize( "#KR_NEW_RACER", player.GetPlayerName() )
+ string announcementSubMessage
+ if ( player == GetLocalViewPlayer() )
+ {
+ file.isCurrentlyInRace = true
+ thread LerpTimeDuringRace( endTime )
+
+ StartParticleEffectOnEntity( player.GetCockpit(), GetParticleSystemIndex( $"P_MFD" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 )
+ EmitSoundOnEntity( player, "UI_InGame_MarkedForDeath_PlayerMarked" )
+ HideEventNotification()
+
+ announcementMessage = "#KR_YOU_ARE_NEW_RACER"
+ announcementSubMessage = "#KR_YOU_SET_NEW_RECORD"
+ }
+ else
+ {
+ // mark the player
+ RuiTrackFloat3( file.killRacerRui, "pos", player, RUI_TRACK_OVERHEAD_FOLLOW )
+ RuiSetBool( file.killRacerRui, "isVisible", true )
+ }
+
+ AnnouncementData announcement = Announcement_Create( announcementMessage )
+ Announcement_SetSubText( announcement, announcementSubMessage )
+ Announcement_SetTitleColor( announcement, < 1, 0, 0 > )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 )
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+}
+
+void function LerpTimeDuringRace( float endTime )
+{
+ while ( Time() < endTime )
+ {
+ // manually update this here so we can get more frequent updates than what we'd get with networked vars
+ UpdateCurrentTimeAmount( endTime - Time() )
+ WaitFrame()
+ }
+
+ UpdateCurrentTimeAmount( 0.0 )
+ file.isCurrentlyInRace = false
+}
+
+void function ServerCallback_EndKillrace( int playerHandle, int score )
+{
+ entity player = GetEntityFromEncodedEHandle( playerHandle )
+
+ vector colour = < 0, 0, 1 >
+ string announcementMessage = Localize( "#KR_ENEMY_KILLRACE_OVER", player.GetPlayerName() )
+ string announcementSubMessage
+ if ( player == GetLocalViewPlayer() )
+ {
+ StartParticleEffectOnEntity( GetLocalViewPlayer().GetCockpit(), GetParticleSystemIndex( $"P_MFD_unmark" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 )
+ colour = < 1, 0, 0 >
+ announcementMessage = "#KR_YOUR_KILLRACE_OVER"
+ announcementSubMessage = Localize( "#KR_YOUR_KILLRACE_SCORE", score )
+ }
+
+ AnnouncementData announcement = Announcement_Create( announcementMessage )
+ Announcement_SetSubText( announcement, announcementSubMessage )
+ Announcement_SetTitleColor( announcement, colour )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 )
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+
+ RuiSetBool( file.killRacerRui, "isVisible", false )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut
new file mode 100644
index 00000000..b73ed958
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut
@@ -0,0 +1,26 @@
+global function ClGamemodeTT_Init
+
+void function ClGamemodeTT_Init()
+{
+ // register gamestate asset, this is default so not necessary but doing it anyway
+ ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ps.rpak" )
+
+ // add music for mode, this is copied directly from the attrition/tdm music registered in cl_music.gnut
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_pilothunt_intro_flyin", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_pilothunt_intro_flyin", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_pilothunt_epilogue_win", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_pilothunt_epilogue_win", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_pilothunt_epilogue_win", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_pilothunt_epilogue_win", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_pilothunt_epilogue_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_pilothunt_epilogue_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_pilothunt_almostdone", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_pilothunt_almostdone", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_pilothunt_lastminute", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_pilothunt_lastminute", TEAM_MILITIA )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_arena_loadouts.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_arena_loadouts.gnut
new file mode 100644
index 00000000..ae0fa7d6
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_arena_loadouts.gnut
@@ -0,0 +1,98 @@
+// the purpose of this script is basically just to provide methods that are used in modified burnmeter and boost_store scripts to get custom items to work
+
+global function InitialiseArenaLoadouts
+global function PopulateArenaLoadouts
+global function GetArenaLoadoutForRoundCount
+global function GetArenaLoadoutItemAsBurnReward
+global function GetCashBoostForRoundCount
+
+#if SERVER
+ global function GivePlayerArenaLoadoutItem
+#endif
+
+const int ABILITY_ROUND = 2
+const int CRATE_ROUND = 4
+const int TITAN_ROUND = 7
+
+struct {
+ array<BoostStoreData> tier0Weapons
+ array<BoostStoreData> tier1Weapons
+ array<BoostStoreData> antiTitanWeapons
+
+ BoostStoreData droppodRespawn
+ BoostStoreData titanBattery
+ BoostStoreData titanfall
+} file
+
+void function InitialiseArenaLoadouts()
+{
+ BoostStoreData g2Data = { itemRef="mp_weapon_g2", modesAllowed="arena", cost=75, ... }
+ file.tier0Weapons.append( g2Data )
+
+ BoostStoreData flatlineData = { itemRef="mp_weapon_vinson", modesAllowed="arena", cost=125, ... }
+ file.tier0Weapons.append( flatlineData )
+
+ BoostStoreData wingmanData = { itemRef="mp_weapon_wingman", modesAllowed="arena", cost=50, ... }
+ file.tier0Weapons.append( wingmanData )
+
+ BoostStoreData respawnData = { itemRef="droppodRespawn", modesAllowed="arena", cost=50, ... }
+ file.tier0Weapons.append( respawnData )
+
+ #if SERVER
+ SetBoostPurchaseCallback( GivePlayerArenaLoadoutItem )
+ #endif
+}
+
+array<BoostStoreData> function PopulateArenaLoadouts()
+{
+ return GetArenaLoadoutForRoundCount( 0 )
+}
+
+array<BoostStoreData> function GetArenaLoadoutForRoundCount( int round )
+{
+ return file.tier0Weapons
+}
+
+BurnReward function GetArenaLoadoutItemAsBurnReward( string itemRef )
+{
+ BurnReward reward
+ reward.ref = itemRef
+ reward.localizedName = itemRef
+
+ return reward
+}
+
+bool function ArenaLoadoutItemIsWeapon( string item )
+{
+ if ( item.find( "mp_weapon" ) == 0 )
+ return true
+
+ return false
+}
+
+int function GetCashBoostForRoundCount( int round )
+{
+ if ( round == 0 )
+ return 150
+
+ if ( round < 4 )
+ return 250
+
+ return 350
+}
+
+#if SERVER
+ void function GivePlayerArenaLoadoutItem( entity player, BoostStoreData item )
+ {
+ if ( ArenaLoadoutItemIsWeapon( item.itemRef ) )
+ {
+ array<string> mods
+ // apply mods
+ //if ( item.itemRef = "mp_weapon_wingman" )
+
+ player.GiveWeapon( item.itemRef, mods )
+ player.SetActiveWeaponByName( item.itemRef )
+ return
+ }
+ }
+#endif \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut
new file mode 100644
index 00000000..b634f1d3
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut
@@ -0,0 +1,58 @@
+global function Sh_GamemodeArena_Init
+
+global const string GAMEMODE_ARENA = "arena"
+
+void function Sh_GamemodeArena_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeArena )
+ AddCallback_OnRegisteringCustomNetworkVars( ArenaRegisterNetworkVars )
+}
+
+void function CreateGamemodeArena()
+{
+ GameMode_Create( GAMEMODE_ARENA )
+ GameMode_SetName( GAMEMODE_ARENA, "#GAMEMODE_arena" )
+ GameMode_SetDesc( GAMEMODE_ARENA, "#PL_arena_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_ARENA, "gnrc_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_ARENA, 5, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_ARENA, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_SetColor( GAMEMODE_ARENA, [147, 204, 57, 255] )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_ARENA, GameModeArena_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_ARENA, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_ARENA, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_ARENA, ClGameModeArena_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_ARENA, CompareAssaultScore )
+ #endif
+}
+
+void function ArenaRegisterNetworkVars()
+{
+ if ( GAMETYPE != GAMEMODE_ARENA )
+ return
+
+ // boost store stuff
+ Remote_RegisterFunction( "ServerCallback_OpenBoostStore" )
+ Remote_RegisterFunction( "ServerCallback_UpdateMoney" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTeamReserve" )
+ Remote_RegisterFunction( "ServerCallback_UpdatePlayerHasBattery" )
+ Remote_RegisterFunction( "ServerCallback_UpdateAmpedWeaponState" )
+ Remote_RegisterFunction( "ServerCallback_BoostStoreTitanHint" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTurretCount" )
+
+ RegisterNetworkedVariable( "boostStoreOpen", SNDC_GLOBAL, SNVT_BOOL, false )
+ RegisterNetworkedVariable( "FD_money", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "FD_money256", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+
+ // these are required to prevent crashes in fd code that's called from menu_boost_store
+ RegisterNetworkedVariable( "numSuperRodeoGrenades", SNDC_PLAYER_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_waveActive", SNDC_GLOBAL, SNVT_BOOL, false )
+
+ // arena-exclusive stuff
+ Remote_RegisterFunction( "ServerCallback_CreateMoneyParticles" )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_ctf_comp.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_ctf_comp.gnut
new file mode 100644
index 00000000..1a1ce645
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_ctf_comp.gnut
@@ -0,0 +1,114 @@
+global function ShGamemodeCTFComp_Init
+
+global const string GAMEMODE_CTF_COMP = "ctf_comp"
+
+void function ShGamemodeCTFComp_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeCTFComp )
+ AddCallback_OnRegisteringCustomNetworkVars( CTFCompRegisterNetworkVars )
+}
+
+void function CreateGamemodeCTFComp()
+{
+ GameMode_Create( GAMEMODE_CTF_COMP )
+ GameMode_SetName( GAMEMODE_CTF_COMP, "#GAMEMODE_ctf_comp" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_CTF_COMP, "ctf_modeDesc" )
+ GameMode_SetDesc( GAMEMODE_CTF_COMP, "#PL_capture_the_flag_hint" )
+ GameMode_SetIcon( GAMEMODE_CTF_COMP, $"ui/menu/playlist/ctf" )
+ GameMode_SetSuddenDeath( GAMEMODE_CTF_COMP, true )
+ GameMode_SetDefaultScoreLimits( GAMEMODE_CTF_COMP, 0, 5 )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_CTF_COMP, 0, 3.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_CTF_COMP, "#SCOREBOARD_CAPTURES", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_CTF_COMP, "#SCOREBOARD_RETURNS", PGS_DEFENSE_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_CTF_COMP, "#SCOREBOARD_KILLS", PGS_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_CTF_COMP, "#SCOREBOARD_TITAN_DAMAGE", PGS_DISTANCE_SCORE, 6 ) // gotta use a weird pgs here since we're running out of them lol
+ GameMode_SetColor( GAMEMODE_CTF_COMP, [61, 117, 193, 255] )
+
+ AddPrivateMatchMode( GAMEMODE_CTF_COMP ) // add to private lobby modes
+
+ // this gamemode is literally just normal ctf + a few extra settings
+ // as such we do all the inits in this file, not enough logic to be worth splitting it up
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_CTF_COMP, InitCTFCompSpecificSettings )
+ GameMode_AddServerInit( GAMEMODE_CTF_COMP, CaptureTheFlag_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_CTF_COMP, RateSpawnpoints_CTF )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_CTF_COMP, RateSpawnpoints_CTF )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_CTF_COMP, InitCTFCompSpecificSettings )
+ GameMode_AddClientInit( GAMEMODE_CTF_COMP, ClCaptureTheFlag_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_CTF_COMP, CompareCTF )
+ GameMode_AddSharedInit( GAMEMODE_CTF_COMP, GamemodeCtfDialogue_Init )
+ GameMode_AddSharedInit( GAMEMODE_CTF_COMP, CaptureTheFlagShared_Init )
+ #endif
+}
+
+void function CTFCompRegisterNetworkVars()
+{
+ if ( GAMETYPE != GAMEMODE_CTF_COMP )
+ return
+
+ // copied from the vanilla ctf remote functions
+ RegisterNetworkedVariable( "imcFlag", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "milFlag", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "imcFlagHome", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "milFlagHome", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "imcFlagState", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "milFlagState", SNDC_GLOBAL, SNVT_INT, 0 )
+
+ RegisterNetworkedVariable( "flagReturnProgress", SNDC_GLOBAL, SNVT_FLOAT_RANGE_OVER_TIME, 0.0, 0.0, 1.0 )
+ RegisterNetworkedVariable( "returningFlag", SNDC_PLAYER_EXCLUSIVE, SNVT_BOOL, false )
+
+ Remote_RegisterFunction( "ServerCallback_CTF_PlayMatchNearEndMusic" )
+ Remote_RegisterFunction( "ServerCallback_CTF_StartReturnFlagProgressBar" )
+ Remote_RegisterFunction( "ServerCallback_CTF_StopReturnFlagProgressBar" )
+
+ #if CLIENT
+ CLCaptureTheFlag_RegisterNetworkFunctions()
+ #endif
+}
+
+void function InitCTFCompSpecificSettings()
+{
+ #if SERVER
+ SetShouldUsePickLoadoutScreen( true )
+ TrackTitanDamageInPlayerGameStat( PGS_DISTANCE_SCORE )
+ SetSpawnpointGamemodeOverride( CAPTURE_THE_FLAG )
+ TeamTitanSelectMenu_Init()
+ #elseif CLIENT
+ ClTeamTitanSelectMenu_Init()
+
+ // gotta register the music here because this is done hardcoded to ctf in cl_music
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_ctf_intro_flyin", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_ctf_intro_flyin", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_ctf_epilogue_win", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_ctf_epilogue_win", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_ctf_halftime_losing", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_ctf_halftime_losing", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_SUDDEN_DEATH, "music_mp_ctf_draw", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_SUDDEN_DEATH, "music_mp_ctf_draw", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_ctf_epilogue_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_ctf_epilogue_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.ROUND_BASED_GAME_WON, "music_mp_ctf_halftime_winning", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.ROUND_BASED_GAME_WON, "music_mp_ctf_halftime_winning", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.ROUND_BASED_GAME_LOST, "music_mp_ctf_halftime_losing", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.ROUND_BASED_GAME_LOST, "music_mp_ctf_halftime_losing", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_ctf_flag_4", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_ctf_flag_4", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_ctf_lastminute", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_ctf_lastminute", TEAM_MILITIA )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut
new file mode 100644
index 00000000..2462d537
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut
@@ -0,0 +1,47 @@
+global function Sh_GamemodeFastball_Init
+
+global const string GAMEMODE_FASTBALL = "fastball"
+
+void function Sh_GamemodeFastball_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeFastball )
+ AddCallback_OnRegisteringCustomNetworkVars( FastballRegisterNetworkVars )
+}
+
+void function CreateGamemodeFastball()
+{
+ GameMode_Create( GAMEMODE_FASTBALL )
+ GameMode_SetName( GAMEMODE_FASTBALL, "#GAMEMODE_FASTBALL" )
+ GameMode_SetDesc( GAMEMODE_FASTBALL, "#PL_fastball_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_FASTBALL, "grnc_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_FASTBALL, 1, 0 )
+ GameMode_SetDefaultScoreLimits( GAMEMODE_FASTBALL, 5, 0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_FASTBALL, "#SCOREBOARD_FASTBALL_HACKS", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_FASTBALL, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_FASTBALL, "#SCOREBOARD_DEATHS", PGS_DEATHS, 2 )
+ GameMode_SetColor( GAMEMODE_FASTBALL, [147, 204, 57, 255] )
+
+ AddPrivateMatchMode( GAMEMODE_FASTBALL ) // add to private lobby modes
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_FASTBALL, GamemodeFastball_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_FASTBALL, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_FASTBALL, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_FASTBALL, ClGamemodeFastball_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_FASTBALL, CompareAssaultScore )
+ #endif
+}
+
+void function FastballRegisterNetworkVars()
+{
+ if ( GAMETYPE != GAMEMODE_FASTBALL )
+ return
+
+ Remote_RegisterFunction( "ServerCallback_FastballUpdatePanelRui" )
+ Remote_RegisterFunction( "ServerCallback_FastballPanelHacked" )
+ Remote_RegisterFunction( "ServerCallback_FastballRespawnPlayer" )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut
new file mode 100644
index 00000000..8c6e3f63
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut
@@ -0,0 +1,73 @@
+// this script only exists to create the fw gamemode
+// all client/shared gamelogic is still done in the gamemode's respective client and shared scripts
+// these scripts are shipped with the game's official build so no need to recreate these
+// their paths are gamemodes/cl_gamemode_fw.nut and gamemodes/sh_gamemode_fw.nut, respectively
+
+global function SHCreateGamemodeFW_Init
+
+void function SHCreateGamemodeFW_Init()
+{
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeFW )
+ AddCallback_OnRegisteringCustomNetworkVars( FWOnRegisteringNetworkVars )
+}
+
+void function CreateGamemodeFW()
+{
+ //entity e = CreateEntity("npc_turret_mega"); SetAISettingsWrapper( e, "npc_turret_mega_fortwar" ); e.SetOrigin(GetPlayerArray()[0].GetOrigin()); SetTeam(e,3); DispatchSpawn(e)
+
+ // we have to manually add the client/shared scripts to scripts.rson atm so we need to prevent compile errors when they aren't included
+ // best way to do this is to just ignore this whole block for now and wait until we don't have to add them manually
+
+ GameMode_Create( FORT_WAR )
+ GameMode_SetName( FORT_WAR, "#GAMEMODE_fw" )
+ GameMode_SetDesc( FORT_WAR, "#PL_fw_desc" )
+ GameMode_SetGameModeAnnouncement( FORT_WAR, "ffa_modeDesc" ) // fw lines are unfortunately not registered to faction dialogue
+
+ #if SERVER
+ //GameMode_AddServerInit( FORT_WAR, GamemodeFW_Init ) // doesn't exist yet lol
+ #elseif CLIENT
+ GameMode_AddClientInit( FORT_WAR, CLGamemodeFW_Init )
+ #endif
+ #if !UI
+ GameMode_AddSharedInit( FORT_WAR, SHGamemodeFW_Init )
+ #endif
+}
+
+void function FWOnRegisteringNetworkVars()
+{
+ if ( GAMETYPE != FORT_WAR )
+ return
+
+ RegisterNetworkedVariable( "turretSite1", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite2", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite3", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite4", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite5", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite6", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite7", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite8", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite9", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "turretStateFlags1", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags2", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags3", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags4", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags5", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags6", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags7", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags8", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags9", SNDC_GLOBAL, SNVT_INT )
+
+ RegisterNetworkedVariable( "imcTowerThreatLevel", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "milTowerThreatLevel", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampAlertA", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampStressA", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampAlertB", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampStressB", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampAlertC", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampStressC", SNDC_GLOBAL, SNVT_INT )
+
+ #if CLIENT
+ CLFortWar_RegisterNetworkFunctions()
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut
new file mode 100644
index 00000000..c4021a3c
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut
@@ -0,0 +1,165 @@
+global function Sh_GamemodeGG_Init
+global function GetGunGameWeapons
+
+global const string GAMEMODE_GG = "gg"
+
+global struct GunGameWeapon
+{
+ string weapon
+ array<string> mods
+ int offhandSlot = -1
+}
+
+struct {
+ array<GunGameWeapon> weapons
+} file
+
+void function Sh_GamemodeGG_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeGG )
+}
+
+void function CreateGamemodeGG()
+{
+ GameMode_Create( GAMEMODE_GG )
+ GameMode_SetName( GAMEMODE_GG, "#GAMEMODE_GG" )
+ GameMode_SetDesc( GAMEMODE_GG, "#PL_gg_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_GG, "ffa_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_GG, 10, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_GG, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_GG, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_SetColor( GAMEMODE_GG, [147, 204, 57, 255] )
+
+ AddPrivateMatchMode( GAMEMODE_GG ) // add to private lobby modes
+
+ // setup guns
+
+ // smgs
+ // car
+ GunGameWeapon ggCar = { weapon = "mp_weapon_car", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggCar )
+
+ // alternator
+ GunGameWeapon ggAlternator = { weapon = "mp_weapon_alternator_smg", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggAlternator )
+
+ // volt
+ GunGameWeapon ggVolt = { weapon = "mp_weapon_hemlok_smg", ... }
+ file.weapons.append( ggVolt )
+
+
+ // rifles
+ // hemlok
+ GunGameWeapon ggHemlok = { weapon = "mp_weapon_hemlok", mods = [ ], ... }
+ file.weapons.append( ggHemlok )
+
+ // flatline
+ GunGameWeapon ggFlatline = { weapon = "mp_weapon_vinson", mods = [ "hcog" ], ... }
+ file.weapons.append( ggFlatline )
+
+ // r201
+ GunGameWeapon ggR101 = { weapon = "mp_weapon_rspn101", ... }
+ file.weapons.append( ggR101 )
+
+
+ // lmgs
+ // devotion
+ GunGameWeapon ggDevotion = { weapon = "mp_weapon_esaw", ... }
+ file.weapons.append( ggDevotion )
+
+ // l-star
+ GunGameWeapon ggLstar = { weapon = "mp_weapon_lstar", mods = [ "pas_run_and_gun" ], ... }
+ if ( RandomInt( 100 ) <= 5 )
+ ggLstar.mods.append( "rcee" ) // easter egg mod that changes the screen of the lstar
+
+ file.weapons.append( ggLstar )
+
+
+ // shotguns
+ // eva-8
+ GunGameWeapon ggEva = { weapon = "mp_weapon_shotgun", ... }
+ file.weapons.append( ggEva )
+
+ // mastiff
+ GunGameWeapon ggMastiff = { weapon = "mp_weapon_mastiff", ... }
+ file.weapons.append( ggMastiff )
+
+
+ // grenadiers
+ // softball
+ GunGameWeapon ggSoftball = { weapon = "mp_weapon_softball", ... }
+ file.weapons.append( ggSoftball )
+
+ // epg
+ GunGameWeapon ggEpg = { weapon = "mp_weapon_epg", mods = [ "jump_kit" ], ... }
+ file.weapons.append( ggEpg )
+
+
+ // primary pistols
+ // mozambique
+ GunGameWeapon ggMozam = { weapon = "mp_weapon_shotgun_pistol", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggMozam )
+
+ // wingman elite
+ GunGameWeapon ggWme = { weapon = "mp_weapon_wingman_n", mods = [ "pas_run_and_gun", "ricochet" ], ... }
+ file.weapons.append( ggWme )
+
+
+ // snipers
+ // double take
+ GunGameWeapon ggTaketake = { weapon = "mp_weapon_doubletake", ... }
+ file.weapons.append( ggTaketake )
+
+ // kraber
+ GunGameWeapon ggKraber = { weapon = "mp_weapon_sniper", mods = [ "pas_fast_ads", "ricochet" ], ... }
+ file.weapons.append( ggKraber )
+
+
+ // secondary pistols
+ // re-45
+ GunGameWeapon ggRe45 = { weapon = "mp_weapon_autopistol", mods = [ "pas_run_and_gun", "temp_sight" ], ... }
+ file.weapons.append( ggRe45 )
+
+ // p2016
+ GunGameWeapon ggP2016 = { weapon = "mp_weapon_semipistol", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggP2016 )
+
+ // wingman
+ GunGameWeapon ggWingman = { weapon = "mp_weapon_wingman", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggWingman )
+
+
+ // final/special weapons
+ // charge rifle
+ GunGameWeapon ggChargeRifle = { weapon = "mp_weapon_defender", ... }
+ file.weapons.append( ggChargeRifle )
+
+ // pulse blade
+ GunGameWeapon ggPulseBlade = { weapon = "mp_weapon_grenade_sonar", mods = [ "pas_power_cell", "amped_tacticals" ], offhandSlot = 0 }
+ file.weapons.append( ggPulseBlade )
+
+
+ // set this to the number of guns
+ GameMode_SetDefaultScoreLimits( GAMEMODE_GG, file.weapons.len(), 0 )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_GG, GamemodeGG_Init )
+ GameMode_AddServerInit( GAMEMODE_GG, GamemodeFFAShared_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_GG, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_GG, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_GG, ClGamemodeGG_Init )
+ GameMode_AddClientInit( GAMEMODE_GG, GamemodeFFAShared_Init )
+ GameMode_AddClientInit( GAMEMODE_GG, ClGamemodeFFA_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_GG, CompareAssaultScore )
+ GameMode_AddSharedInit( GAMEMODE_GG, GamemodeFFA_Dialogue_Init )
+ #endif
+}
+
+array<GunGameWeapon> function GetGunGameWeapons()
+{
+ return file.weapons
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut
new file mode 100644
index 00000000..bcd86378
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut
@@ -0,0 +1,58 @@
+global function Sh_GamemodeInfection_Init
+
+global const string GAMEMODE_INFECTION = "inf"
+global const int INFECTION_TEAM_SURVIVOR = TEAM_MILITIA
+global const int INFECTION_TEAM_INFECTED = TEAM_IMC
+
+void function Sh_GamemodeInfection_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeInfection )
+ AddCallback_OnRegisteringCustomNetworkVars( InfectionRegisterNetworkVars )
+}
+
+void function CreateGamemodeInfection()
+{
+ GameMode_Create( GAMEMODE_INFECTION )
+ GameMode_SetName( GAMEMODE_INFECTION, "#GAMEMODE_inf" )
+ GameMode_SetDesc( GAMEMODE_INFECTION, "#PL_inf_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_INFECTION, "ffa_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_INFECTION, 5, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_INFECTION, "#SCOREBOARD_KILLS", PGS_ASSAULT_SCORE, 2 )
+ GameMode_SetColor( GAMEMODE_INFECTION, [147, 204, 57, 255] )
+
+ AddPrivateMatchMode( GAMEMODE_INFECTION ) // add to private lobby modes
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_INFECTION, GamemodeInfection_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_INFECTION, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_INFECTION, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_INFECTION, ClGamemodeInfection_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_INFECTION, CompareAssaultScoreAndInfection )
+ #endif
+}
+
+void function InfectionRegisterNetworkVars()
+{
+ if ( GAMETYPE != GAMEMODE_INFECTION )
+ return
+
+ Remote_RegisterFunction( "ServerCallback_YouAreInfected" )
+ Remote_RegisterFunction( "ServerCallback_AnnounceFirstInfected" )
+ Remote_RegisterFunction( "ServerCallback_AnnounceLastSurvivor" )
+}
+
+int function CompareAssaultScoreAndInfection( entity a, entity b )
+{
+ // survivors should be on top, then sort by assault score
+
+ if ( a.GetTeam() == INFECTION_TEAM_INFECTED && b.GetTeam() == INFECTION_TEAM_SURVIVOR )
+ return 1
+ else if ( a.GetTeam() == INFECTION_TEAM_SURVIVOR && b.GetTeam() == INFECTION_TEAM_INFECTED )
+ return -1
+
+ return CompareAssaultScore( a, b )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut
new file mode 100644
index 00000000..7cd91de9
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut
@@ -0,0 +1,55 @@
+global function Sh_GamemodeKR_Init
+
+global const string GAMEMODE_KR = "kr"
+
+void function Sh_GamemodeKR_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeKR )
+ AddCallback_OnRegisteringCustomNetworkVars( KRRegisterNetworkVars )
+}
+
+void function CreateGamemodeKR()
+{
+ GameMode_Create( GAMEMODE_KR )
+ GameMode_SetName( GAMEMODE_KR, "#GAMEMODE_kr" )
+ GameMode_SetDesc( GAMEMODE_KR, "#PL_kr_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_KR, "ffa_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_KR, 10, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_KR, "#SCOREBOARD_KR_RECORD", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_KR, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_SetColor( GAMEMODE_KR, [147, 204, 57, 255] )
+
+ AddPrivateMatchMode( GAMEMODE_KR ) // add to private lobby modes
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_KR, GamemodeKR_Init )
+ GameMode_AddServerInit( GAMEMODE_KR, GamemodeFFAShared_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_KR, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_KR, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_KR, ClGamemodeKR_Init )
+ GameMode_AddClientInit( GAMEMODE_KR, GamemodeFFAShared_Init )
+ GameMode_AddClientInit( GAMEMODE_KR, ClGamemodeFFA_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_KR, CompareAssaultScore )
+ GameMode_AddSharedInit( GAMEMODE_KR, GamemodeFFA_Dialogue_Init )
+ #endif
+}
+
+void function KRRegisterNetworkVars()
+{
+ if ( GAMETYPE != GAMEMODE_KR )
+ return
+
+ Remote_RegisterFunction( "ServerCallback_FlagSpawnIncoming" )
+ Remote_RegisterFunction( "ServerCallback_NewKillRacer" )
+ Remote_RegisterFunction( "ServerCallback_EndKillrace" )
+
+ RegisterNetworkedVariable( "killRaceTime", SNDC_PLAYER_EXCLUSIVE, SNVT_TIME, 0.0 )
+
+ #if CLIENT
+ RegisterNetworkedVariableChangeCallback_time( "killRaceTime", ShowTimeGainOnKill )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut
new file mode 100644
index 00000000..893d9410
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut
@@ -0,0 +1,20 @@
+global function Sh_GamemodeSbox_Init
+
+global const string GAMEMODE_SBOX = "sbox"
+
+void function Sh_GamemodeSbox_Init()
+{
+ // create custom gametype
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeSbox )
+}
+
+void function CreateGamemodeSbox()
+{
+ GameMode_Create( GAMEMODE_SBOX )
+ GameMode_SetName( GAMEMODE_SBOX, "#PL_sbox" )
+ GameMode_SetDesc( GAMEMODE_SBOX, "#PL_sbox_desc" )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_SBOX, GamemodeSbox_Init )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut
new file mode 100644
index 00000000..f3fbff28
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut
@@ -0,0 +1,36 @@
+global function Sh_GamemodeTT_Init
+
+global const string GAMEMODE_TT = "tt"
+
+void function Sh_GamemodeTT_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeTT )
+}
+
+void function CreateGamemodeTT()
+{
+ GameMode_Create( GAMEMODE_TT )
+ GameMode_SetName( GAMEMODE_TT, "#GAMEMODE_TT" )
+ GameMode_SetDesc( GAMEMODE_TT, "#PL_tt_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_TT, "gnrc_modeDesc" )
+ GameMode_SetDefaultScoreLimits( GAMEMODE_TT, 20, 0 )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_TT, 15, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_TT, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_TT, "#SCOREBOARD_TITAN_KILLS", PGS_TITAN_KILLS, 1 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_TT, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_SetColor( GAMEMODE_TT, [200, 40, 40, 255] )
+
+ AddPrivateMatchMode( GAMEMODE_TT ) // add to private lobby modes
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_TT, GamemodeTT_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_TT, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_TT, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_TT, ClGamemodeTT_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_TT, CompareAssaultScore )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut
new file mode 100644
index 00000000..3a8ac8e7
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut
@@ -0,0 +1,262 @@
+untyped
+
+global function RiffFloorIsLavaShared_Init
+
+global function GetFogHeight
+global function GetLethalFogTopTitan
+global function GetLethalFogTop
+global function GetLethalFogBottom
+global function GetVisibleFogTop
+global function GetVisibleFogBottom
+global function GetMaxTitanSpawnFogHeight
+
+global function IsEntInSafeVolume
+global function IsEntInLethalVolume
+
+struct
+{
+ float fogDepth = 64.0
+ float maxTitanSpawnFogDepth = 170.0
+ array lethalTitanVolumes
+ array lethalPilotVolumes
+ array safePilotVolumes
+ bool volumesDebug = false
+ table< string, float > lethalFogHeights
+} file
+
+function RiffFloorIsLavaShared_Init()
+{
+ switch ( GetMapName() )
+ {
+ case "mp_lagoon":
+ AddLethalTitanVolume( Vector( -45.656845, 3555.449463, 40.422455 ), Vector( 1209.944092, 5599.152832, 234.813217 ) )
+ AddLethalTitanVolume( Vector( -5232.395020, 205.406250, 0.031250 ), Vector( -777.285400, 4075.119385, 300.634771 ) )
+ AddLethalTitanVolume( Vector( -4686.448730, 4190.655273, 20.642021 ), Vector( -41.171387, 9072.019043, 200.697632 ) )
+ AddLethalTitanVolume( Vector( -7586.861328, 4072.843994, 0.031254 ), Vector( -7012.854004, 4614.723145, 302.714966 ) )
+ break
+
+ case "mp_nexus":
+ AddLethalTitanVolume( Vector( 1567.173523, -27.374023, 209.422455 ), Vector( 2516.944092, 2585.152832, 500.813217 ) )
+ AddLethalTitanVolume( Vector( -2825.766113, 5056.203125, 243.706253 ), Vector( -2255.893555, 5688.334961, 400.251160 ) )
+ AddLethalTitanVolume( Vector( -5717.068359, -349.599976, 189.669785 ), Vector( -4960.125000, 758.196350, 400.268097 ) )
+ AddLethalTitanVolume( Vector( -3292.942139, 1713.916626, 233.749817 ), Vector( -2322.137695, 3091.497070, 477.462799 ) )
+ AddLethalTitanVolume( Vector( -878.712769, -5878.528809, 71.145332 ), Vector( 338.741943, -5014.183594, 443.146179 ) )
+ AddLethalTitanVolume( Vector( -6930.957031, -1277.388550, 107.619537 ), Vector( -6574.779297, -779.338013, 685.485901 ) )
+ break
+
+ case "mp_outpost_207":
+ AddSafePilotVolume( Vector( 2359.524658, -631.065918, -256.714142 ), Vector( 2623.051270, -182.453323, -220.125641 ) )
+
+ AddLethalTitanVolume( Vector( -100.349350, 2218.763916, -330.968750 ), Vector( 2561.511230, 4030.028320, -133.065369 ) )
+ AddLethalTitanVolume( Vector( -452.031647, 282.244629, -255.968750 ), Vector( 2241.971069, 1594.146851, -100.212967 ) )
+ break
+
+ case "mp_training_ground":
+ AddSafePilotVolume( Vector( -2618.053223, -3435.505615, 40.215054 ), Vector( -2309.167236, -3321.788330, 146.218491 ) )
+ AddSafePilotVolume( Vector( -3187.767090, -2886.333496, 45.746925 ), Vector( -2865.753174, -2681.679443, 109.089279 ) )
+ AddSafePilotVolume( Vector( -3717.815674, -2350.831543, 47.694588 ), Vector( -3431.980957, -2145.194092, 120.640717 ) )
+
+ AddLethalTitanVolume( Vector( -3439.702179, -2227.359741, -8.036909 ), Vector( 2185.765076, 2384.459412, 225.199013 ) )
+ AddLethalTitanVolume( Vector( -3200.747681, -4456.148926, 0.0 ), Vector( -1261.621826, -3000.667480, 160.689011 ) )
+ AddLethalTitanVolume( Vector( 1261.621826, 3000.667480, 0.0 ), Vector( 2700.747681, 4456.148926, 160.689011 ) )
+ AddLethalTitanVolume( Vector( -3291.510986, 3483.724609, 4.031250 ), Vector( -2018.871826, 4463.995850, 122.675621 ) )
+ AddLethalTitanVolume( Vector( 2018.871826, -3638.995850, 4.031250 ), Vector( 2241.510986, -3483.724609, 122.675621 ) )
+ AddLethalTitanVolume( Vector( -2798.816528, -2302.519897, -30.285933 ), Vector( -1561.589355, -791.616699, 300.917297 ) )
+ AddLethalTitanVolume( Vector( 3809.276123, 1639.001587, 11.272846 ), Vector( 4056.847412, 1862.587036, 100.205643 ) )
+ AddLethalTitanVolume( Vector( -4189.979492, -3298.505127, -5.597572 ), Vector( -3398.622803, -560.027344, 147.054291 ) )
+ break
+
+ case "mp_runoff":
+ AddLethalPilotVolume( Vector( -621.502319, -5743.472656, 299.838928 ), Vector( -397.317047, -5578.512207, 425.437927 ) )
+ break
+ }
+}
+
+float function GetFogHeight()
+{
+ string mapName = GetMapName()
+
+ file.lethalFogHeights = {}
+ file.lethalFogHeights[ "mp_angel_city" ] <- 216.0 // cp ctf mfd
+ file.lethalFogHeights[ "mp_lagoon" ] <- 98.0 // cp mfd
+ file.lethalFogHeights[ "mp_nexus" ] <- 310.0 // mfd
+ file.lethalFogHeights[ "mp_o2" ] <- 40.0 // mfd
+ file.lethalFogHeights[ "mp_outpost_207" ] <- -225.0 // mfd
+ file.lethalFogHeights[ "mp_training_ground" ] <- 80.0 // cp mfd
+ file.lethalFogHeights[ "mp_harmony_mines" ] <- 260.0 // cp ctf mfd
+ file.lethalFogHeights[ "mp_haven" ] <- 128.0 // mfd
+
+ // good map, needs spawns, etc...
+ file.lethalFogHeights[ "mp_rise" ] <- 420.0 // mfd
+ file.lethalFogHeights[ "mp_runoff" ] <- 340.0 // mfd
+ file.lethalFogHeights[ "mp_zone_18" ] <- 460.0 // mfd
+ file.lethalFogHeights[ "mp_sandtrap" ] <- 64.0
+
+ // these don't work as well
+ file.lethalFogHeights[ "mp_swampland" ] <- 350.0 // mfd
+ file.lethalFogHeights[ "mp_backwater" ] <- 320.0 // mfd
+ file.lethalFogHeights[ "mp_airbase" ] <- 450.0
+ file.lethalFogHeights[ "mp_boneyard" ] <- 64.0
+ file.lethalFogHeights[ "mp_colony" ] <- 270.0
+ file.lethalFogHeights[ "mp_corporate" ] <- -765.0
+ file.lethalFogHeights[ "mp_fracture" ] <- 270.0
+ file.lethalFogHeights[ "mp_overlook" ] <- 16.0
+ file.lethalFogHeights[ "mp_relic" ] <- 475.0
+ file.lethalFogHeights[ "mp_smugglers_cove" ] <- 400.0
+ file.lethalFogHeights[ "mp_wargames" ] <- 64.0
+ file.lethalFogHeights[ "mp_switchback" ] <- 840.0
+
+ file.lethalFogHeights[ "mp_chin_rodeo_express" ] <- 1580.0
+
+ // custom: titanfall 2 maps
+ // TODO: really need a modular system here
+ file.lethalFogHeights[ "mp_colony02" ] <- 270.0 // map changed name from tf1 => tf2
+ file.lethalFogHeights[ "mp_glitch" ] <- 200.0
+ file.lethalFogHeights[ "mp_grave" ] <- 2350.0
+ file.lethalFogHeights[ "mp_homestead" ] <- 64.0
+ file.lethalFogHeights[ "mp_forwardbase_kodai" ] <- 930.0
+ file.lethalFogHeights[ "mp_thaw" ] <- 32.0
+ file.lethalFogHeights[ "mp_black_water_canal" ] <- 32.0
+ file.lethalFogHeights[ "mp_eden" ] <- 175.0
+ file.lethalFogHeights[ "mp_drydock" ] <- 300.0
+ file.lethalFogHeights[ "mp_crashsite3" ] <- 800.0 // crashsite is just as awful for this as it is for anything else
+ file.lethalFogHeights[ "mp_complex3" ] <- 630.0
+ file.lethalFogHeights[ "mp_relic02" ] <- 250.0 // not great, tf1's would honestly be worse though imo
+
+ // lf maps: overall a bit hit or miss, many likely have spawn issues
+ file.lethalFogHeights[ "mp_lf_stacks" ] <- -9999.0 // entirely nonworking, breaks spawns no matter what from what i can tell, could potentially use safe zones for this?
+ file.lethalFogHeights[ "mp_lf_deck" ] <- -9999.0 // nonworking fogcontroller so fog is invisible
+ file.lethalFogHeights[ "mp_lf_uma" ] <- 64.0
+ file.lethalFogHeights[ "mp_lf_meadow" ] <- 64.0
+ file.lethalFogHeights[ "mp_lf_traffic" ] <- 50.0
+ file.lethalFogHeights[ "mp_lf_township" ] <- 64.0
+
+ if ( mapName in file.lethalFogHeights )
+ return file.lethalFogHeights[ mapName ]
+
+ return 64.0
+}
+
+float function GetLethalFogTopTitan()
+{
+ float fogTop = GetLethalFogTop()
+
+ switch ( GetMapName() )
+ {
+ case "mp_lagoon":
+ case "mp_nexus":
+ case "mp_outpost_207":
+ case "mp_training_ground":
+ case "mp_chin_rodeo_express":
+ return fogTop
+ }
+
+ return fogTop + 256.0
+}
+
+float function GetLethalFogTop()
+{
+ return GetFogHeight() - file.fogDepth * 0.2
+}
+
+float function GetLethalFogBottom()
+{
+ return GetFogHeight() - file.fogDepth * 0.7
+}
+
+float function GetVisibleFogTop()
+{
+ return GetFogHeight() + file.fogDepth * 0.5
+}
+
+float function GetVisibleFogBottom()
+{
+ return GetFogHeight() - file.fogDepth * 0.5
+}
+
+float function GetMaxTitanSpawnFogHeight()
+{
+ return GetFogHeight() - file.maxTitanSpawnFogDepth
+}
+
+function AddLethalTitanVolume( vector volumeMins, vector volumeMaxs )
+{
+ Assert( volumeMins.x < volumeMaxs.x )
+ Assert( volumeMins.y < volumeMaxs.y )
+ Assert( volumeMins.z < volumeMaxs.z )
+
+ file.lethalTitanVolumes.append( { mins = volumeMins, maxs = volumeMaxs } )
+}
+
+function AddLethalPilotVolume( vector volumeMins, vector volumeMaxs )
+{
+ Assert( volumeMins.x < volumeMaxs.x )
+ Assert( volumeMins.y < volumeMaxs.y )
+ Assert( volumeMins.z < volumeMaxs.z )
+
+ file.lethalPilotVolumes.append( { mins = volumeMins, maxs = volumeMaxs } )
+}
+
+function AddSafePilotVolume( vector volumeMins, vector volumeMaxs )
+{
+ Assert( volumeMins.x < volumeMaxs.x )
+ Assert( volumeMins.y < volumeMaxs.y )
+ Assert( volumeMins.z < volumeMaxs.z )
+
+ file.safePilotVolumes.append( { mins = volumeMins, maxs = volumeMaxs } )
+}
+
+function IsEntInSafeVolume( entity ent )
+{
+ if ( ent.IsPlayer() )
+ {
+ foreach ( volume in file.safePilotVolumes )
+ {
+ vector entOrg = ent.GetOrigin()
+
+ #if SERVER
+ if ( file.volumesDebug )
+ DebugDrawBox( Vector( 0.0, 0.0, 0.0 ), volume.mins, volume.maxs, 0, 0, 255, 1, 0.1 )
+ #endif
+
+ if ( PointIsWithinBounds( entOrg, expect vector( volume.mins ), expect vector( volume.maxs ) ) )
+ return true
+ }
+ }
+}
+
+function IsEntInLethalVolume( entity ent )
+{
+ if ( ent.IsTitan() )
+ {
+ foreach ( volume in file.lethalTitanVolumes )
+ {
+ vector entOrg = ent.GetOrigin()
+
+ #if SERVER
+ if ( file.volumesDebug )
+ DebugDrawBox( Vector( 0.0, 0.0, 0.0 ), volume.mins, volume.maxs, 255, 255, 0, 1, 0.1 )
+ #endif
+
+ if ( PointIsWithinBounds( entOrg, expect vector( volume.mins ), expect vector( volume.maxs ) ) )
+ return true
+ }
+ }
+ else if ( ent.IsPlayer() )
+ {
+ foreach ( volume in file.lethalPilotVolumes )
+ {
+ vector entOrg = ent.GetOrigin()
+
+ #if SERVER
+ if ( file.volumesDebug )
+ DebugDrawBox( Vector( 0.0, 0.0, 0.0 ), volume.mins, volume.maxs, 255, 255, 0, 1, 0.1 )
+ #endif
+
+ if ( PointIsWithinBounds( entOrg, expect vector( volume.mins ), expect vector( volume.maxs ) ) )
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/lobby/sh_private_lobby_custom_modes_init.gnut b/Northstar.Custom/mod/scripts/vscripts/lobby/sh_private_lobby_custom_modes_init.gnut
new file mode 100644
index 00000000..81c08f22
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/lobby/sh_private_lobby_custom_modes_init.gnut
@@ -0,0 +1,11 @@
+global function CustomPrivateMatchModesInit
+
+void function CustomPrivateMatchModesInit()
+{
+ // modes
+ AddPrivateMatchMode( "gg" )
+ AddPrivateMatchMode( "inf" )
+ AddPrivateMatchMode( "kr" )
+ AddPrivateMatchMode( "tt" )
+ AddPrivateMatchMode( "ctf_comp" )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/mp/levels/mp_box.nut b/Northstar.Custom/mod/scripts/vscripts/mp/levels/mp_box.nut
new file mode 100644
index 00000000..a1828009
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/mp/levels/mp_box.nut
@@ -0,0 +1,6 @@
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/northstar_custom_autoprecache.gnut b/Northstar.Custom/mod/scripts/vscripts/northstar_custom_autoprecache.gnut
new file mode 100644
index 00000000..1d5b7ba9
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/northstar_custom_autoprecache.gnut
@@ -0,0 +1,14 @@
+untyped
+global function NorthstarCustomAutoprecache
+
+void function NorthstarCustomAutoprecache()
+{
+ PrecacheWeapon( "mp_weapon_peacekraber" )
+ PrecacheWeapon( "melee_pilot_kunai" )
+
+ if ( GAMETYPE == GAMEMODE_SBOX )
+ PrecacheWeapon( "mp_weapon_toolgun" )
+
+ // will include this as a custom asset when mod v2 is written
+ PrecacheModel( $"models/SPOILER_w_titan_particle_accelerator.mdl" )
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/titan/sh_first_person_embark.gnut b/Northstar.Custom/mod/scripts/vscripts/titan/sh_first_person_embark.gnut
new file mode 100644
index 00000000..0c95ae4c
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/titan/sh_first_person_embark.gnut
@@ -0,0 +1,41 @@
+global function FirstPersonEmbark_Init
+
+#if CLIENT
+ global function ServerCallback_HideHudForFPEmbark
+#endif
+
+void function FirstPersonEmbark_Init()
+{
+ // atm do this no matter what playlist we're on since playlist overrides seem to get sent to clients after networkvar registration
+ // not nice but whatever lol
+ AddCallback_OnRegisteringCustomNetworkVars( FirstPersonEmbark_RegisterCustomNetworkFunctions )
+
+ // busted rn lol
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 0 )
+ return
+
+ #if CLIENT
+ AddCallback_PlayerClassChanged( ShowHudOnEmbarkFinished )
+ #endif
+}
+
+void function FirstPersonEmbark_RegisterCustomNetworkFunctions()
+{
+ Remote_RegisterFunction( "ServerCallback_HideHudForFPEmbark" )
+}
+
+#if CLIENT
+void function ServerCallback_HideHudForFPEmbark()
+{
+ thread MainHud_TurnOff_RUI( true )
+ HidePermanentCockpitRui()
+}
+
+void function ShowHudOnEmbarkFinished( entity player )
+{
+ if ( !player.IsTitan() )
+ return
+
+ ShowPermanentCockpitRui()
+}
+#endif \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan_embark.gnut b/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan_embark.gnut
new file mode 100644
index 00000000..f9df2730
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan_embark.gnut
@@ -0,0 +1,2253 @@
+untyped
+
+global function TitanEmbark_Init
+
+global function TitanCanStand
+global function DebugEmbarkTimes
+global function TitanIsCurrentlyEmbarkableForPlayer
+global function PlayerCanEmbarkTitan
+global function PlayerCanImmediatelyEmbarkTitan
+global function FindBestEmbark
+global function GenerateEmbarkActionTable
+global function FindEmbarkActionForCriteria
+global function GetRandomEmbarkAction
+global function PlayerCanDisembarkTitan
+global function IsPlayerDisembarking
+
+#if SERVER
+ global function ForceScriptedEmbark
+ global function PlayerCanEmbarkIntoTitan
+ global function IsPlayerEmbarking
+ global function PlayerEmbarksTitan
+ global function PlayerLungesToEmbark
+ global function FindBestEmbarkForNpcAnim
+ global function PlayerDisembarksTitan
+ global function ForcedTitanDisembark
+ global function ForcedTitanDisembarkCustomAnims
+ global function PhaseEmbarkPhaseStart
+ global function PhaseEmbarkPhaseStop
+ global function OverrideCockpitLightFX
+ global function StartCockpitLightThink
+ global function CockpitLightStop
+ global function Embark_DelayedFadeOut
+ global function PlayerIsFarOffTheGround
+
+ global function SetSmallDisembarkFailSafeTeleportVector //TODO: Re-examine this for next game, probably should have different values for SP versus MP
+ global function SetLargeDisembarkFailSafeTeleportVector //TODO: Re-examine this for next game, probably should have different values for SP versus MP
+#endif
+
+#if DEV
+ global function SetEmbarkDebugPrint
+#endif
+
+const FAST_EMBARK = 1
+const SKIP_AHEAD_TIME = 2.0
+
+#if MP
+const EMBARK_FADE_TIME = 0.2
+#else
+const EMBARK_FADE_TIME = 0.3
+#endif
+
+struct
+{
+ asset cockpitLightFX = $"xo_cockpit_dlight"
+ bool embarkDebugPrint = false
+ vector smallDisembarkFailSafeTeleportVector = < 400, 400, 200 >
+ vector largeDisembarkFailSafeTeleportVector = < 600, 600, 600 >
+} file
+
+function TitanEmbark_Init()
+{
+ if ( reloadingScripts )
+ return
+
+ level.pilotDisembarkBounds <- {}
+ local end = {}
+ end.up <- 50.363811
+ end.forward <- 110.146927
+ end.right <- 13.045869
+ end.yaw <- -8.381051
+
+ local start = {}
+ start.up <- 156.750015
+ start.forward <- -13.429688
+ start.right <- -11.374998
+ start.yaw <- 0.409042
+
+ RefreshTitanEmbarkActions()
+
+ level.pilotDisembarkBounds.end <- end
+ level.pilotDisembarkBounds.start <- start
+
+ RegisterSignal( "OnComplete" )
+ RegisterSignal( "startembark" ) // temp
+
+ RegisterSignal( "DisembarkingTitan" )
+ RegisterSignal( "player_embarks_titan" )
+
+ #if SERVER
+ // add all the embark anims with this suffix
+ AddEmbarkAnims( "titan_atlas", "atlas", true )
+ AddEmbarkAnims( "titan_buddy", "buddy", true )
+ AddEmbarkAnims( "titan_ogre", "ogre", true )
+ AddEmbarkAnims( "titan_stryder", "stryder", true )
+
+ // AddEmbarkAudio( "titan_atlas", "atlas" )
+ // AddEmbarkAudio( "titan_buddy", "buddy" )
+ // AddEmbarkAudio( "titan_ogre", "ogre" )
+ // AddEmbarkAudio( "titan_stryder", "stryder" )
+
+ RegisterSignal( "titanKneel" )
+ RegisterSignal( "titanStand" )
+ RegisterSignal( "titanEmbark" )
+ RegisterSignal( "PhaseEmbarkPhaseStop" )
+
+ RegisterSignal( "CockpitLightStop" )
+ PrecacheParticleSystem( $"xo_cockpit_dlight" )
+
+ AddClientCommandCallback( "TitanDisembark", ClientCommand_TitanDisembark ) //
+ AddClientCommandCallback( "TitanKneel", ClientCommand_TitanKneel ) //
+ //AddClientCommandCallback( "TitanStand", ClientCommand_TitanStand ) //
+ AddClientCommandCallback( "TitanNextMode", ClientCommand_TitanNextMode ) //
+
+ AddCallback_OnTitanBecomesPilot( TitanBecomesPilot_UpdateRodeoRiderHud )
+ AddCallback_OnPilotBecomesTitan( PilotBecomesTitan_UpdateRodeoRiderHud )
+ #endif
+}
+
+void function OverrideCockpitLightFX( asset fx )
+{
+ file.cockpitLightFX = fx
+}
+
+void function AddEmbarkAnims( string titan, string titanSubClass, bool thirdPersonOnly = false )
+{
+ // anims are string-constructed from these types:
+ local Array =
+ [
+ "kneel_front",
+ "kneel_behind",
+ "kneel_right",
+ "kneel_left",
+ "kneel_airgrab",
+
+ "stand_front",
+ "stand_right",
+ "stand_left",
+ "stand_behind",
+ "stand_airgrab",
+
+ "above_right",
+ "above_left",
+ "kneel_above_right",
+ "kneel_above_left",
+ ]
+
+
+ // force consistency in animation names
+ foreach ( item in Array )
+ {
+ array<string> suffixes = [ "", "_fast" ]
+ foreach ( suffix in suffixes )
+ {
+ //printt( "Adding base " + item + " to " + titan )
+ local thirdPersonAlias = "pt_mount_" + item + suffix
+ local firstPersonAlias = "ptpov_mount_" + item + suffix
+ local thirdPersonAnim = "pt_mount_" + titanSubClass + "_" + item + suffix
+ local firstPersonAnim = "ptpov_mount_" + titanSubClass + "_" + item + suffix
+
+ if ( thirdPersonOnly )
+ firstPersonAnim = ""
+
+ AddAnimAlias( titanSubClass, thirdPersonAlias, thirdPersonAnim )
+ AddAnimAlias( titanSubClass, firstPersonAlias, firstPersonAnim )
+ }
+ }
+}
+
+function AddEmbarkAudio( titan, titanSubClass )
+{
+ // audio files are string-constructed from these types:
+ local Array =
+ [
+ "Kneeling_Front",
+ "Kneeling_Behind",
+ "Kneeling_Right",
+ "Kneeling_Left",
+ "Kneeling_AboveRight",
+ "Kneeling_AboveLeft",
+
+ "Standing_Front",
+ "Standing_Behind",
+ "Standing_Airgrab",
+ "Standing_AboveRight",
+ "Standing_AboveLeft"
+ ]
+
+
+ // force consistency in audio file names
+ foreach ( item in Array )
+ {
+ //printt( "Adding base " + item + " to " + titan )
+ local thirdPersonAlias = "Embark_" + item + "_3P"
+ local firstPersonAlias = "Embark_" + item + "_1P"
+ local thirdPersonAnim = titanSubClass + "_Embark_" + item + "_3P"
+ local firstPersonAnim = titanSubClass + "_Embark_" + item + "_1P"
+
+ AddAudioAlias( titanSubClass, thirdPersonAlias, thirdPersonAnim )
+ AddAudioAlias( titanSubClass, firstPersonAlias, firstPersonAnim )
+ }
+}
+
+function RefreshTitanEmbarkActions()
+{
+ if ( "titanEmbarkActions" in level )
+ {
+ delete level.titanEmbarkActions
+ delete level.titanEmbarkFarthestDistance
+ }
+
+ local groundDist = 260
+
+ level.titanEmbarkActions <- []
+ level.titanEmbarkFarthestDistance <- 0
+ local action
+
+ action =
+ {
+ direction = Vector( 1, 0, 0 )
+ distance = groundDist
+ embark = "front"
+ minDot = 0.4
+ priority = 1 // tried after priority 0 actions
+ titanCanStandRequired = false
+ //onGround = null // either
+ useAnimatedRefAttachment = true
+ alignFrontEnabled = true
+ canSkipAhead = true
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_front"
+ thirdPersonKneelingAlias = "pt_mount_kneel_front"
+ firstPersonStandingAlias = "ptpov_mount_stand_front"
+ thirdPersonStandingAlias = "pt_mount_stand_front"
+ titanKneelingAnim = "at_mount_kneel_front"
+ titanStandingAnim = "at_mount_stand_front"
+
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Front_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Front_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 1, 0, 0 )
+ distance = groundDist
+ embark = "front"
+ minDot = -1
+ priority = 2 // tried after priority 1 actions
+ titanCanStandRequired = false
+ //onGround = null // either
+ useAnimatedRefAttachment = true
+ alignFrontEnabled = true
+ canSkipAhead = true
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_front"
+ thirdPersonKneelingAlias = "pt_mount_kneel_front"
+ firstPersonStandingAlias = "ptpov_mount_stand_front"
+ thirdPersonStandingAlias = "pt_mount_stand_front"
+ titanKneelingAnim = "at_mount_kneel_front"
+ titanStandingAnim = "at_mount_stand_front"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Front_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Front_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 0, 1, 0 )
+ distance = groundDist
+ embark = "left"
+ minDot = 0.4
+ priority = 1 // tried after priority 0 actions
+ titanCanStandRequired = true
+ useAnimatedRefAttachment = true
+ alignFrontEnabled = true
+ canSkipAhead = true
+ //onGround = null // either
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_left"
+ thirdPersonKneelingAlias = "pt_mount_kneel_left"
+ firstPersonStandingAlias = "ptpov_mount_stand_front"
+ thirdPersonStandingAlias = "pt_mount_stand_left"
+ titanKneelingAnim = "at_mount_kneel_left"
+ titanStandingAnim = "at_mount_stand_left"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Left_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Left_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 0, -1, 0 )
+ distance = groundDist
+ embark = "right"
+ minDot = 0.4
+ priority = 1 // tried after priority 0 actions
+ titanCanStandRequired = true
+ useAnimatedRefAttachment = true
+ alignFrontEnabled = true
+ canSkipAhead = true
+ //onGround = null // either
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_right"
+ thirdPersonKneelingAlias = "pt_mount_kneel_right"
+ firstPersonStandingAlias = "ptpov_mount_stand_front"
+ thirdPersonStandingAlias = "pt_mount_stand_right"
+ titanKneelingAnim = "at_mount_kneel_right"
+ titanStandingAnim = "at_mount_stand_right"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Right_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Right_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+
+ action =
+ {
+ direction = Vector( -1, 0, 0 )
+ distance = groundDist
+ embark = "behind"
+ minDot = 0.4
+ priority = 1 // tried after priority 0 actions
+ titanCanStandRequired = true
+ useAnimatedRefAttachment = true
+ canSkipAhead = false
+ //onGround = null // either
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_behind"
+ thirdPersonKneelingAlias = "pt_mount_kneel_behind"
+ firstPersonStandingAlias = "ptpov_mount_stand_behind"
+ thirdPersonStandingAlias = "pt_mount_stand_behind"
+ titanKneelingAnim = "at_mount_kneel_behind"
+ titanStandingAnim = "at_mount_stand_behind"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Behind_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Behind_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Behind_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Behind_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 0, 0, 1 ) // 0 -1 1
+ distance = 350
+ embark = "above_close"
+ minDot = 0.88
+ canSkipAhead = false
+ priority = 0 // priority actions are checked first
+
+ titanCanStandRequired = true
+ useAnimatedRefAttachment = true
+ //onGround = false // must be in air
+
+ animSets =
+ {
+ right =
+ {
+ direction = Vector( 0, -1, 0 ) // 0 -1 1
+ firstPersonKneelingAlias = "ptpov_mount_kneel_above_right"
+ thirdPersonKneelingAlias = "pt_mount_kneel_above_right"
+ firstPersonStandingAlias = "ptpov_mount_above_right"
+ thirdPersonStandingAlias = "pt_mount_above_right"
+ titanKneelingAnim = "at_mount_kneel_above"
+ titanStandingAnim = "at_mount_above_right"
+
+ audioSet = //An annoying exception to how the audioSet is organized, better this than the alternative and having to find the "best audio set"
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_AboveRight_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_AboveRight_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_AboveRight_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_AboveRight_3P"
+ }
+ }
+
+ left =
+ {
+ direction = Vector( 0, 1, 0 ) // 0 -1 1
+ firstPersonKneelingAlias = "ptpov_mount_kneel_above_left"
+ thirdPersonKneelingAlias = "pt_mount_kneel_above_left"
+ firstPersonStandingAlias = "ptpov_mount_above_left"
+ thirdPersonStandingAlias = "pt_mount_above_left"
+ titanKneelingAnim = "at_mount_kneel_above"
+ titanStandingAnim = "at_mount_above_left"
+
+ audioSet = //An annoying exception to how the audioSet is organized, better this than the alternative and having to find the "best audio set"
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_AboveLeft_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_AboveLeft_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_AboveLeft_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_AboveLeft_3P"
+ }
+ }
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 0, 0, 1 )
+ distance = 275
+ embark = "above_grab"
+ minDot = 0.3
+ titanCanStandRequired = true
+ //onGround = null // false // must be in air
+ useAnimatedRefAttachment = true
+ //lungeCheck = true
+ canSkipAhead = true
+
+ alignFrontEnabled = true
+
+ priority = 0 // priority actions are checked first
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_airgrab"
+ thirdPersonKneelingAlias = "pt_mount_kneel_airgrab"
+ titanKneelingAnim = "at_mount_kneel_airgrab"
+
+ firstPersonStandingAlias = "ptpov_mount_stand_airgrab"
+ thirdPersonStandingAlias = "pt_mount_stand_airgrab"
+ titanStandingAnim = "at_mount_stand_airgrab"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Front_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Front_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ local autoParms =
+ [
+ "lungeCheck"
+ "alignFrontEnabled"
+ ]
+
+ foreach ( action in level.titanEmbarkActions )
+ {
+ if ( action.distance > level.titanEmbarkFarthestDistance )
+ {
+ level.titanEmbarkFarthestDistance = action.distance
+ }
+ foreach ( parm in autoParms )
+ {
+ if ( !( parm in action ) )
+ action[ parm ] <- false
+ }
+ }
+}
+
+function DebugEmbarkTimes()
+{
+ local settings = [ "atlas", "ogre", "stryder" ]
+
+ array< asset > models = [ $"models/Humans/imc_pilot/male_cq/imc_pilot_male_cq.mdl", $"models/humans/pilot/female_cq/pilot_female_cq.mdl" ]
+ local times = {}
+
+ foreach ( model in models )
+ {
+ times[ model ] <- []
+ entity prop = CreatePropDynamic( model, Vector(0,0,0), Vector(0,0,0) )
+ printt( "Human model: " + model )
+
+ foreach ( setting in settings )
+ {
+ printt( "Titan: " + setting )
+ foreach ( action in level.titanEmbarkActions )
+ {
+ printt( "Embark Direction: " + action.embark )
+ if ( "animSet" in action )
+ {
+ local animation = GetAnimFromAlias( setting, action.animSet.thirdPersonKneelingAlias )
+ local time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Kneeling: " + time )
+
+ animation = GetAnimFromAlias( setting, action.animSet.thirdPersonStandingAlias )
+ time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Standing: " + time )
+ }
+
+ if ( "animSets" in action )
+ {
+ local animation = GetAnimFromAlias( setting, action.animSets.left.thirdPersonKneelingAlias )
+ local time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Kneeling Left: " + time )
+
+ animation = GetAnimFromAlias( setting, action.animSets.left.thirdPersonStandingAlias )
+ time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Standing Left: " + time )
+
+ animation = GetAnimFromAlias( setting, action.animSets.right.thirdPersonKneelingAlias )
+ time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Kneeling Right: " + time )
+
+ animation = GetAnimFromAlias( setting, action.animSets.right.thirdPersonStandingAlias )
+ time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Standing Right: " + time )
+ }
+
+ printt( " " )
+ }
+ }
+
+ prop.Kill_Deprecated_UseDestroyInstead()
+ }
+
+ printt( "Time comparison: " )
+ bool wrong = false
+ for ( int i = 0; i < times[ models[0] ].len(); i++ )
+ {
+ if ( times[models[0]][i].time == times[models[1]][i].time )
+ {
+ printt( "MATCH: " + ( i + 1 ) + " times: " + times[models[0]][i].time + " " + times[models[1]][i].time + " " + times[models[1]][i].animation )
+ }
+ else
+ {
+ printt( "MISMATCH: " + ( i + 1 ) + " times: " + times[models[0]][i].time + " " + times[models[1]][i].time + " " + times[models[1]][i].animation )
+ wrong = true
+ }
+ }
+// Assert( !wrong, "Times did not match between male and female, see above" )
+}
+
+
+#if SERVER
+bool function ClientCommand_TitanKneel( entity player, array<string> args )
+{
+ entity titan = player.GetPetTitan()
+ if ( !IsAlive( titan ) )
+ return true
+
+ titan.Signal( "titanKneel" )
+ titan.s.standQueued = false
+ return true
+}
+
+/*bool function ClientCommand_TitanStand( entity player, array<string> args )
+{
+ entity titan = player.GetPetTitan()
+ if ( !IsAlive( titan ) )
+ return true
+
+ titan.Signal( "titanStand" )
+ titan.s.standQueued = true
+ titan.s.kneelQueued = false
+ return true
+}*/
+
+bool function ClientCommand_TitanNextMode( entity player, array<string> args )
+{
+ if ( !IsAlive( player ) )
+ return true
+
+ entity titan = player.GetPetTitan()
+ if ( IsAlive( titan ) )
+ NPCTitanNextMode( titan, player )
+
+ return true
+}
+#endif // SERVER
+
+
+function EmbarkLine( player, titan )
+{
+ player.EndSignal( "startembark" )
+ local ref = player.LookupAttachment( "ref" )
+ local hijack = titan.LookupAttachment( "hijack" )
+ local origin
+ for ( ;; )
+ {
+ origin = titan.GetAttachmentOrigin( hijack )
+ DebugDrawLine( player.GetOrigin(), origin, 255, 0, 0, true, 0.15 )
+
+ origin = player.GetAttachmentOrigin( ref )
+ DebugDrawLine( player.GetOrigin(), origin, 0, 255, 0, true, 0.15 )
+ WaitFrame()
+ }
+}
+
+
+#if SERVER
+function PlayerEmbarksTitan( entity player, entity titan, table embark )
+{
+ //player.SetOrigin( Vector(314.971405, -1826.728638, 116.031250))
+ //player.SetAngles( Vector(0.000000, 133.945892, 0.000000))
+ //titan.SetOrigin( Vector(284.887970, -1622.180542, 112.093750))
+ //titan.SetAngles(Vector(0.000000, 112.264252, 0))
+
+ entity soul = titan.GetTitanSoul()
+ string settings = GetSoulTitanSubClass( soul )
+ printt( "TitanEmbarkDebug: Player ", player.GetOrigin(), player.GetAngles(), " Titan ", titan.GetOrigin(), titan.GetAngles(), settings, GetMapName() )
+
+
+ Assert( IsAlive( titan ) )
+ Assert( IsAlive( player ) )
+
+ player.SetInvulnerable()
+
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "TitanEjectionStarted" )
+ titan.EndSignal( "OnDeath" )
+
+ titan.Signal( "player_embarks_titan" )
+ player.Signal( "player_embarks_titan" )
+
+// Assert( !InSolid( titan ), titan + " is in solid" )
+
+ DisableCloak( player )
+
+ entity groundEntity = titan.GetGroundEntity()
+ vector startOrigin = titan.GetGroundRelativePos()
+ vector startAngles = titan.GetAngles()
+
+ OnThreadEnd(
+ function() : ( player, groundEntity, startOrigin, startAngles )
+ {
+ if ( IsValid( player ) )
+ {
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() & (~CE_FLAG_EMBARK) )
+ TitanEmbark_PlayerCleanup( player )
+ player.p.isEmbarking = false
+
+ if ( IsAlive( player ) )
+ {
+ if ( player.IsTitan() ) //Defensive fix, sometimes in SP (when game is shutting down etc ) this can run without the player being alive
+ {
+ TitanEmbarkFailsafe( player, null, groundEntity, startOrigin )
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanEmbark" )
+ LetTitanPlayerShootThroughBubbleShield( player )
+ }
+ else
+ {
+ player.Die() //Defensive fix, sometimes in SP (when game is shutting down etc ) this can run without the player being alive
+ }
+
+ }
+ }
+ }
+ )
+
+ // track the embarking player so we can kill him if the titan dies
+ titan.s.embarkingPlayer <- player
+ player.p.isEmbarking = true
+
+ #if HAS_STATS
+ UpdatePlayerStat( player, "misc_stats", "titanEmbarks", 1 )
+ #endif
+ #if SERVER && MP
+ PIN_AddToPlayerCountStat( player, "embarks" )
+ PIN_PlayerAbility( player, "", "embark", {}, 0 )
+ #endif
+
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() | CE_FLAG_EMBARK )
+
+ waitthread PlayerEmbarksTitan_PlayerBecomesTitan( player, titan, embark )
+}
+
+function PlayerEmbarksTitan_PlayerBecomesTitan( entity player, entity titan, table embark )
+{
+ // a place to store the player finish time
+ table e
+
+ e.threads <- 0
+ e.embarkAction <- embark.action
+ e.animSet <- embark.animSet
+ e.audioSet <- embark.audioSet
+
+ e.canStand <- TitanCanStand( titan )
+
+ e.shouldDoRegularEmbark <- ShouldDoRegularEmbark( titan )
+
+ // player and titan do their anims and wait for each other to finish
+ thread TitanEmbark_TitanEmbarks( player, titan, e )
+ waitthread TitanEmbark_PlayerEmbarks( player, titan, e )
+}
+
+bool function ShouldDoRegularEmbark( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+
+ if ( IsSingleplayer() && GetSoulPlayerSettings( soul ) == "titan_buddy" )
+ {
+ return titan.GetNPCState() == "combat" || titan.GetNPCState() == "alert"
+ }
+
+ return true
+}
+
+bool function TitanHasLeftAndRightEmbarkAnims( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+ string settings = GetSoulPlayerSettings( soul )
+ var hasAnims = Dev_GetPlayerSettingByKeyField_Global( settings, "hasLeftRightEmbarks" )
+ if ( hasAnims != null && hasAnims == 1 )
+ {
+ return true
+ }
+
+ return false
+}
+
+function TitanEmbark_PlayerCleanup( player )
+{
+ expect entity( player )
+
+ player.SetSyncedEntity( null )
+ DeployViewModelAndEnableWeapons( player )
+ player.UnforceStand()
+ player.UnforceCrouch()
+ //printt("Clearing invulnerable")
+ player.ClearInvulnerable()
+ player.ClearParent()
+ //Let player jump in air after getting out if he wants to
+ player.TouchGround()
+ player.Anim_Stop()
+}
+
+
+void function ForceScriptedEmbark( entity player, entity titan )
+{
+ HolsterViewModelAndDisableWeapons( player )
+ ClearPlayerAnimViewEntity( player )
+ player.ClearParent()
+ PilotBecomesTitan( player, titan )
+
+ thread PlayAnim( player, "cqb_idle_mp" )
+ player.Anim_Stop()
+
+ player.SetOrigin( titan.GetOrigin() )
+ vector angles = titan.GetAngles()
+ angles.z = 0
+ angles.x = 0
+ player.SetAngles( angles )
+ player.SnapEyeAngles( angles )
+
+ SetStanceStand( player.GetTitanSoul() )
+
+ TitanEmbark_PlayerCleanup( player )
+ if ( player.ContextAction_IsBusy() )
+ player.ContextAction_ClearBusy()
+}
+
+
+function TitanEmbarkFailsafe( entity player, entity titan, entity groundEnt, vector startOrigin )
+{
+ if ( GetCurrentPlaylistVarInt( "player_embark_in_solid_checks", 0 ) != 1 )
+ return
+
+ #if DEV
+ if ( file.embarkDebugPrint )
+ {
+ if ( IsValid( titan ) )
+ printt( "TitanEmbarkFailsafe, before PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + startOrigin )
+ else
+ printt( "TitanEmbarkFailsafe, before PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", null titan, safeStartPoint: " + startOrigin )
+ }
+ #endif
+
+ if ( !PutEntityInSafeSpot( player, titan, groundEnt, startOrigin, player.GetOrigin() ) )
+ player.SetOrigin( startOrigin )
+
+ #if DEV
+ if ( file.embarkDebugPrint )
+ {
+ if ( IsValid( titan ) )
+ printt( "TitanEmbarkFailsafe, after PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + startOrigin )
+ else
+ printt( "TitanEmbarkFailsafe, after PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", null titan, safeStartPoint: " + startOrigin )
+ }
+ #endif
+}
+
+function TitanEmbark_PlayerEmbarks( entity player, entity titan, table e )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "TitanEjectionStarted" )
+ titan.EndSignal( "OnDeath" )
+
+ e.threads++
+ OnThreadEnd(
+ function() : ( player, e )
+ {
+ if ( IsValid( player ) )
+ {
+ // ensure these are cleared regardless
+ ClearPlayerAnimViewEntity( player )
+
+ if ( player.ContextAction_IsBusy() )
+ player.ContextAction_ClearBusy()
+ }
+
+ e.threads--
+ if ( !e.threads )
+ {
+ Signal( e, "OnComplete" )
+ }
+ }
+ )
+
+ player.ContextAction_SetBusy()
+
+ bool standing = false
+
+ if ( e.canStand )
+ {
+ if ( e.shouldDoRegularEmbark )
+ {
+ player.ForceStand()
+ switch ( titan.GetTitanSoul().GetStance() )
+ {
+ case STANCE_KNEELING:
+ case STANCE_KNEEL:
+ standing = false
+ break
+
+ default:
+ standing = true
+ break
+ }
+ }
+ else
+ {
+ standing = false
+ }
+ }
+ else
+ {
+ player.ForceCrouch()
+ }
+
+ HolsterViewModelAndDisableWeapons( player )
+
+ FirstPersonSequenceStruct sequence
+ sequence.attachment = "hijack"
+ sequence.useAnimatedRefAttachment = expect bool ( e.embarkAction.useAnimatedRefAttachment )
+ sequence.blendTime = 0.5
+
+ entity soul = titan.GetTitanSoul()
+ string settings = GetSoulTitanSubClass( soul )
+
+ local hasViewCone
+
+ // string thirdPersonAudio
+ // string firstPersonAudio
+
+ if ( standing )
+ {
+ sequence.firstPersonAnim = GetAnimFromAlias( settings, e.animSet.firstPersonStandingAlias )
+ sequence.thirdPersonAnim = GetAnimFromAlias( settings, e.animSet.thirdPersonStandingAlias )
+ hasViewCone = false
+ // thirdPersonAudio = GetAudioFromAlias( settings, e.audioSet.thirdPersonStandingAudioAlias )
+ // firstPersonAudio = GetAudioFromAlias( settings, e.audioSet.firstPersonStandingAudioAlias )
+
+ }
+ else
+ {
+ sequence.firstPersonAnim = GetAnimFromAlias( settings, e.animSet.firstPersonKneelingAlias )
+ sequence.thirdPersonAnim = GetAnimFromAlias( settings, e.animSet.thirdPersonKneelingAlias )
+ hasViewCone = true
+ // thirdPersonAudio = GetAudioFromAlias( settings, e.audioSet.thirdPersonKneelingAudioAlias )
+ // firstPersonAudio = GetAudioFromAlias( settings, e.audioSet.firstPersonKneelingAudioAlias )
+ }
+
+ sequence.thirdPersonAnimIdle = "pt_mount_idle"
+
+ bool doFirstPersonAnim = true
+
+ if ( sequence.firstPersonAnim == "" )
+ {// if there is no first person anim, then there must be a third person camera
+ sequence.thirdPersonCameraAttachments.append( "VDU" )
+ sequence.thirdPersonCameraVisibilityChecks = true
+ doFirstPersonAnim = false
+ }
+
+ if ( hasViewCone )
+ {
+ sequence.viewConeFunction = EmbarkViewCone
+ thread DelayedClearViewCone( player )
+ }
+
+ //thread DelayedDisableEmbarkPlayerHud( player, sequence )
+
+ AddAnimEvent( player, "phase_shift_start", PhaseEmbarkPhaseStart )
+ AddAnimEvent( player, "phase_shift_stop", PhaseEmbarkPhaseStop )
+ AddAnimEvent( titan, "cockpit_light_start", CockpitLightStart )
+ AddAnimEvent( titan, "cockpit_light_stop", CockpitLightStop )
+
+ OnThreadEnd(
+ function() : ( player, titan )
+ {
+ if ( IsValid( player ) )
+ {
+ Signal( player, "PhaseEmbarkPhaseStop" )
+ DeleteAnimEvent( player, "phase_shift_start" )
+ DeleteAnimEvent( player, "phase_shift_stop" )
+ }
+
+ if ( IsAlive( titan ) ) //Consider clearing titan.s.embarkingPlayer here?
+ titan.Die()
+ }
+ )
+
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 1 )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_HideHudForFPEmbark" )
+
+ // fp embark hacks
+ entity viewControl = CreateEntity( "point_viewcontrol" )
+ viewControl.kv.spawnflags = 56
+ DispatchSpawn( viewControl )
+
+ viewControl.SetParent( player, "headshot" )
+ viewControl.SetOrigin( < 4, 0, 0 > )
+ viewControl.SetAngles( < 0, 0, 0 > )
+ player.SetViewEntity( viewControl, false )
+ }
+
+ thread FirstPersonSequence( sequence, player, titan )
+ // EmitDifferentSoundsOnEntityForPlayerAndWorld( firstPersonAudio, thirdPersonAudio, titan, player )
+
+ float animDuration = player.GetSequenceDuration( sequence.thirdPersonAnim )
+
+ if ( ShouldSkipAheadIntoEmbark( standing, player, titan, e ) )
+ {
+ local duration = player.GetSequenceDuration( sequence.thirdPersonAnim )
+ if ( duration >= SKIP_AHEAD_TIME )
+ {
+ player.Anim_SetInitialTime( duration - SKIP_AHEAD_TIME )
+ entity viewModel = player.GetFirstPersonProxy()
+
+ if ( IsValid( viewModel ) && EntHasModelSet( viewModel ) && doFirstPersonAnim ) //JFS: Defensive fix for player not having view models sometimes
+ viewModel.Anim_SetInitialTime( duration - SKIP_AHEAD_TIME )
+
+ animDuration = SKIP_AHEAD_TIME
+ }
+ }
+
+ thread Embark_DelayedFadeOut( player, titan, animDuration )
+
+ WaittillAnimDone( player )
+
+ Signal( player, "PhaseEmbarkPhaseStop" )
+
+ ClearPlayerAnimViewEntity( player )
+ PilotBecomesTitan( player, titan )
+
+ thread PlayAnim( player, "cqb_idle_mp" )
+ player.Anim_Stop()
+ player.SetVelocity( <0,0,0> )
+
+ player.SetOrigin( titan.GetOrigin() )
+ local angles = titan.GetAngles()
+ angles.z = 0
+ angles.x = 0
+ player.SetAngles( angles )
+ player.SnapEyeAngles( angles )
+
+ // soul stuff should be from anim event
+ Assert( IsServer() )
+ SetStanceStand( player.GetTitanSoul() )
+ titan.Destroy()
+}
+
+void function Embark_DelayedFadeOut( entity player, entity titan, float delay )
+{
+ if ( !IsAlive( player ) )
+ return
+
+ if ( !IsValid( titan ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ wait delay - EMBARK_FADE_TIME
+
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 0 )
+ {
+ ScreenFadeToBlack( player, EMBARK_FADE_TIME, EMBARK_FADE_TIME + 0.2 ) // a little extra so we stay black
+ wait EMBARK_FADE_TIME
+ }
+ else
+ {
+ OnThreadEnd( function() : ( player )
+ {
+ player.ClearViewEntity()
+ })
+
+ wait EMBARK_FADE_TIME - 0.2
+ ScreenFadeToBlack( player, 0.2, 0.4 )
+ wait 0.2
+ player.ClearViewEntity() // make sure player is in normal first person again
+ }
+
+ ScreenFadeFromBlack( player, EMBARK_FADE_TIME, EMBARK_FADE_TIME )
+}
+
+void function PlayStartupSounds( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return
+ entity player = soul.GetBossPlayer()
+ if ( !IsValid( player ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+
+ string titanSettings = GetSoulPlayerSettings( soul )
+ var startupSound = Dev_GetPlayerSettingByKeyField_Global( titanSettings, "startup_sound" )
+
+ if ( startupSound != null )
+ EmitSoundOnEntityOnlyToPlayer( player, player, expect string(startupSound) )
+}
+
+
+void function CockpitLightStart( entity titan )
+{
+ thread StartCockpitLightThink( titan, 5.0 )
+}
+
+void function StartCockpitLightThink( entity titan, float timeout )
+{
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "CockpitLightStop" )
+
+ int attachID = titan.LookupAttachment( "HIJACK" )
+ int fxID = GetParticleSystemIndex( file.cockpitLightFX )
+ entity fx = StartParticleEffectOnEntity_ReturnEntity( titan, fxID, FX_PATTACH_POINT_FOLLOW, attachID )
+
+ OnThreadEnd(
+ function() : ( fx )
+ {
+ if ( IsValid( fx ) )
+ EffectStop( fx )
+ }
+ )
+
+ if ( timeout < 0 )
+ WaitForever()
+ else
+ wait timeout
+}
+
+void function CockpitLightStop( entity titan )
+{
+ titan.Signal( "CockpitLightStop" )
+}
+
+void function PhaseEmbarkPhaseStart( entity player )
+{
+ player.MakeInvisible()
+ PlayPhaseShiftDisappearFX( player )
+ EmitSoundOnEntity( player, "pilot_phaseembark_activate_3p" )
+
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 1 )
+ {
+ player.PhaseShiftBegin( 0.0, 0.2 )
+ player.GetPetTitan().SetForceVisibleInPhaseShift( true ) // doesn't work for some reason
+ }
+
+ thread PhaseEmbarkPhaseCleanup( player )
+}
+
+void function PhaseEmbarkPhaseCleanup( player )
+{
+ EndSignal( player, "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ {
+ player.MakeVisible()
+ }
+ }
+ )
+
+ WaitSignal( player, "PhaseEmbarkPhaseStop" )
+}
+
+void function PhaseEmbarkPhaseStop( entity player )
+{
+ Signal( player, "PhaseEmbarkPhaseStop" )
+ PlayPhaseShiftDisappearFX( player )
+ EmitSoundOnEntity( player, "pilot_phaseembark_end_3p" )
+
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 1 )
+ player.PhaseShiftCancel()
+}
+
+function ShouldSkipAheadIntoEmbark( standing, player, titan, e )
+{
+ if ( !standing )
+ return false
+
+ if ( !e.embarkAction.canSkipAhead )
+ return false
+
+ local playerEye = player.EyePosition()
+ local titanOrg = titan.GetOrigin()
+ local vec = playerEye - titanOrg
+ vec.Norm()
+ vec.z = 0
+ local start = playerEye
+ local end = playerEye + vec * 24
+
+ if ( Distance( player.GetOrigin(), titan.GetOrigin() ) >= 145 )
+ return false
+
+ local mask = TRACE_MASK_PLAYERSOLID
+ TraceResults result = TraceLine( start, end, [ titan, player ], mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( start, result.endPos, 0, 255, 0, true, 10.0 )
+ //DebugDrawLine( result.endPos, end, 255, 0, 0, true, 10.0 )
+ return result.fraction < 1.0
+}
+#endif // SERVER
+
+function DelayedDisableEmbarkPlayerHud( player, sequence )
+{
+ player.EndSignal( "OnDeath" )
+
+ local duration = player.GetSequenceDuration( sequence.thirdPersonAnim )
+ wait duration - 1.0
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() | CE_FLAG_EMBARK )
+}
+
+#if SERVER
+function DelayedClearViewCone( player )
+{
+ player.EndSignal( "OnDeath" )
+ wait 1.0
+ player.PlayerCone_SetLerpTime( 0.5 )
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( 0 )
+ player.PlayerCone_SetMaxYaw( 0 )
+ player.PlayerCone_SetMinPitch( 0 )
+ player.PlayerCone_SetMaxPitch( 0 )
+}
+
+void function EmbarkViewCone( entity player )
+{
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -70 )
+ player.PlayerCone_SetMaxYaw( 60 )
+ player.PlayerCone_SetMinPitch( -80 )
+ player.PlayerCone_SetMaxPitch( 30 )
+}
+
+function TitanEmbark_TitanEmbarks( player, titan, e )
+{
+ expect entity( player )
+ expect entity( titan )
+
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "TitanEjectionStarted" )
+ titan.EndSignal( "OnDeath" )
+
+ titan.ContextAction_SetBusy()
+
+ AddAnimEvent( titan, "play_startup_sound", PlayStartupSounds )
+
+ e.threads++
+ OnThreadEnd(
+ function() : ( e, player, titan )
+ {
+ e.threads--
+ if ( !e.threads )
+ {
+ Signal( e, "OnComplete" )
+ }
+
+ if ( !IsAlive( player ) && IsAlive( titan ) )
+ {
+ titan.Anim_Stop()
+ titan.ContextAction_ClearBusy()
+ DeleteAnimEvent( titan, "play_startup_sound" )
+ }
+ }
+ )
+
+ local soul = titan.GetTitanSoul()
+
+ // dont let other players get in
+
+ local animation
+// local waittillAnimDone
+ local alignFront
+ bool standing = false
+
+ if ( e.canStand )
+ {
+ if ( e.shouldDoRegularEmbark )
+ {
+ // default
+ switch ( titan.GetTitanSoul().GetStance() )
+ {
+ case STANCE_KNEELING:
+ case STANCE_KNEEL:
+ animation = e.animSet.titanKneelingAnim
+ alignFront = false
+ break
+
+ default:
+ animation = e.animSet.titanStandingAnim
+ if ( TitanHasLeftAndRightEmbarkAnims( titan ) )
+ alignFront = false
+ else
+ alignFront = true
+ standing = true
+ break
+ }
+ }
+ else
+ {
+ // special for BT if he is in casual mode
+ animation = e.animSet.titanKneelingAnim
+ alignFront = false
+ }
+ }
+ else
+ {
+ animation = "at_mount_kneel_without_standing"
+ alignFront = false
+// waittillAnimDone = false
+ }
+
+ //sequence.blendTime = 0.5
+
+ printt("This is mount animation name: " + animation )
+ if ( e.embarkAction.alignFrontEnabled && alignFront )
+ {
+ local titanOrg = titan.GetOrigin()
+ local vec = player.GetOrigin() - titanOrg
+ local angles = VectorToAngles( vec )
+ angles.x = 0
+ angles.z = 0
+ thread PlayAnimGravityClientSyncing( titan, animation, titanOrg, angles )
+ }
+ else
+ {
+ thread PlayAnimGravityClientSyncing( titan, animation )
+ }
+
+ if ( ShouldSkipAheadIntoEmbark( standing, player, titan, e ) )
+ {
+ local duration = titan.GetSequenceDuration( animation )
+
+ if ( duration >= SKIP_AHEAD_TIME ) // failsafe
+ titan.Anim_SetInitialTime( duration - SKIP_AHEAD_TIME )
+ }
+
+ // titan will become player now
+ WaitForever()
+}
+
+bool function ClientCommand_TitanDisembark( entity player, array<string> args )
+{
+ if ( !PlayerCanDisembarkTitan( player ) )
+ return true
+
+ ScreenFade( player, 0, 1, 0, 255, 0.2, 0.2, FFADE_IN | FFADE_PURGE )
+ player.CockpitStartDisembark()
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" )
+
+ thread PlayerDisembarksTitan( player )
+
+ return true
+}
+
+void function ForcedTitanDisembark( entity player )
+{
+ Assert( PlayerCanDisembarkTitan( player ) )
+
+ player.CockpitStartDisembark()
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" )
+
+ waitthread PlayerDisembarksTitan( player )
+}
+
+function ForcedTitanDisembarkCustomAnims( entity player, FirstPersonSequenceStruct functionref( entity, entity ) playerSequenceFunc, FirstPersonSequenceStruct functionref( entity, entity ) titanSequenceFunc )
+{
+ Assert( PlayerCanDisembarkTitan( player ) )
+
+ player.CockpitStartDisembark()
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" )
+
+ player.p.isCustomDisembark = true
+ waitthread PlayerDisembarksTitanWithSequenceFuncs( player, playerSequenceFunc, titanSequenceFunc )
+ player.p.isCustomDisembark = false
+}
+
+#endif // SERVER
+
+function PlayerCanDisembarkTitan( entity player )
+{
+ if ( !player.IsTitan() )
+ return false
+
+ if ( !IsAlive( player ) )
+ return false
+
+ if ( IsValid( player.GetParent() ) )
+ return false
+
+ if ( Riff_TitanExitEnabled() == eTitanExitEnabled.Never )
+ return false
+
+ if ( !CanDisembark( player ) )
+ return false
+
+ #if SERVER
+ if ( player.IsNoclipping() )
+ return false
+ // client doesn't know these things
+ if ( IsPlayerDisembarking( player ) )
+ return false
+ if ( IsPlayerEmbarking( player ) )
+ return false
+ #endif
+
+ if ( !HasSoul( player ) )
+ return false
+
+ local soul = player.GetTitanSoul()
+ if ( soul.IsEjecting() )
+ return false
+
+ Assert( soul == player.GetTitanSoul() )
+
+ return true
+}
+
+#if SERVER
+
+function PlayerDisembarksTitan( player )
+{
+ expect entity( player )
+ PlayerDisembarksTitanWithSequenceFuncs( player, GetDisembarkSequenceForPlayer, GetDisembarkSequenceForTitan )
+}
+
+void function PlayerDisembarksTitanWithSequenceFuncs( entity player, FirstPersonSequenceStruct functionref( entity, entity ) playerSequenceFunc, FirstPersonSequenceStruct functionref( entity, entity ) titanSequenceFunc )
+{
+ //printt( "Player disembarking with origin " + player.GetOrigin() + " and yaw " + player.GetAngles().y )
+
+ //player.SetOrigin( Vector(420.847626, -5214.960938, 173.789520) )
+ //player.SetAngles( Vector(0.000000, 179.572052, 0.000000 ) )
+
+ printt( "TitanDisembarkDebug: Player ", player.GetOrigin(), player.GetAngles(), GetMapName() )
+
+ player.EndSignal( "OnDeath" )
+ player.Signal( "DisembarkingTitan" )
+
+ //Assert( !InSolid( player ), player + " is in solid" )
+
+ local e = {}
+ e.titan <- null
+ e.PilotCleanUpDone <- false
+
+ e.startOrigin <- player.GetOrigin()
+ //e.startAngles <- player.GetAngles()
+
+ player.p.isDisembarking = true
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() | CE_FLAG_DISEMBARK )
+
+ bool wasCustomDisembark = player.p.isCustomDisembark
+
+ player.ContextAction_SetBusy()
+
+ OnThreadEnd(
+ function() : ( player, e )
+ {
+ if ( IsValid( player ) )
+ {
+ PlayerEndsDisembark( player, e )
+
+ local titan = e.titan
+ if ( !IsValid( titan ) )
+ titan = null
+ }
+
+ if ( IsAlive( expect entity( e.titan ) ) )
+ {
+ if ( IsAlive( player ) )
+ {
+ thread PlayerOwnsTitanUntilSeparation( player, e.titan, 80 )
+ }
+
+ delete e.titan.s.disembarkingPlayer
+ ClearInvincible( expect entity( e.titan ) )
+
+
+ //Make Titan get up immediately if he's kneeling and can stand
+ //If he can't get up, well, then since the titan doesn't move when crouched he's going to be stuck...
+ if ( !( "embarkingPlayer" in e.titan.s ) )
+ {
+ thread TitanNPC_Think( expect entity( e.titan ) ) //titan.s.disableAutoTitanConversation is deleted inside here
+ }
+ }
+ }
+ )
+
+ bool standing = player.IsStanding()
+
+ player.SetInvulnerable()
+ player.SnapFeetToEyes()
+
+ entity titan = CreateAutoTitanForPlayer_ForTitanBecomesPilot( player )
+ DispatchSpawn( titan )
+ e.titan = titan
+
+ if ( !PlayerIsFarOffTheGround( player, [ player,titan ] ) ) //PlayerIsFarOffTheGround() necessary now for R2 because we have Titans that can jump/geo where Titans can fall down from large heights. Without check, mid-air disembarking will cause player to be teleported to the ground
+ {
+ vector ornull clampedPos = NavMesh_ClampPointForAIWithExtents( titan.GetOrigin(), titan, file.smallDisembarkFailSafeTeleportVector )
+ if ( clampedPos == null )
+ clampedPos = NavMesh_ClampPointForAIWithExtents( titan.GetOrigin(), titan, file.largeDisembarkFailSafeTeleportVector )
+
+ if ( clampedPos != null )
+ {
+ expect vector( clampedPos )
+ vector titanOrigin = titan.GetOrigin()
+
+ array<entity> ignoreEnts = []
+ ignoreEnts.append( titan )
+
+ TraceResults result = TraceHull( titanOrigin, titanOrigin, titan.GetBoundingMins(), titan.GetBoundingMaxs(), ignoreEnts, TRACE_MASK_TITANSOLID, TRACE_COLLISION_GROUP_NONE )
+
+ // expensive checks to make sure titan doesn't teleport to navmesh on other side of wall usually in invalid places
+ if ( result.startSolid ||
+ TraceLineSimple( titanOrigin + Vector( 0, 0, 128 ), clampedPos + Vector( 0, 0, 0 ), titan ) == 1.0 ||
+ TraceLineSimple( titanOrigin + Vector( 0, 0, 200 ), clampedPos + Vector( 0, 0, 0 ), titan ) == 1.0 ||
+ TraceLineSimple( titanOrigin + Vector( 0, 0, 200 ), clampedPos + Vector( 0, 0, 128 ), titan ) == 1.0 )
+ {
+ #if DEV
+ if ( file.embarkDebugPrint )
+ {
+ printt( "PlayerDisembarksTitanWithSequenceFuncs, player origin: " + player.GetOrigin()+ ", titan origin: " + titan.GetOrigin() + ", clampedPos: " + clampedPos )
+ }
+ #endif
+ titan.SetOrigin( clampedPos )
+ titan.ForceCheckGroundEntity()
+ }
+ }
+ }
+ else
+ {
+ #if DEV
+ if ( file.embarkDebugPrint )
+ {
+ printt( "PlayerIsFarOffGround() returned true, skip doing NavMesh_ClampPointForAIWithExtents checks" )
+ }
+ #endif
+ }
+
+ titan.s.disembarkingPlayer <- player
+ titan.EndSignal( "OnDeath" )
+
+ player.SetSyncedEntity( titan )
+ titan.s.disableAutoTitanConversation <- true
+
+ Assert( titan.IsTitan() )
+ Assert( IsAlive( player ) )
+ Assert( player.IsTitan() )
+ //Set player to be temporarily invulnerable. Will be removed at end of animation
+ //printt("Set player invulnerable")
+ HolsterViewModelAndDisableWeapons( player ) //Holstering weapon before becoming pilot so we don't play the holster animation as a pilot. Player as Titan won't play the holster animation either since it'll be interrupted by the disembark animation
+
+ TitanBecomesPilot( player, titan )
+
+ string titanSubClass = GetSoulTitanSubClass( titan.GetTitanSoul() )
+ switch ( titanSubClass )
+ {
+ case "ogre":
+ ShowMainTitanWeapons( titan ) //JFS: Because we hide the Titan's weapons upon kneeling for ogre
+ break
+ }
+
+ titan.s.disembarkTime <- Time() // disembark debounce
+
+ // compound strings into these animations:
+ // pt_dismount_atlas_stand
+ // pt_dismount_ogre_stand
+ // pt_dismount_stryder_stand
+ // pt_dismount_atlas_crouch
+ // pt_dismount_ogre_crouch
+ // pt_dismount_stryder_crouch
+ // ptpov_dismount_atlas_stand
+ // ptpov_dismount_ogre_stand
+ // ptpov_dismount_stryder_stand
+ // ptpov_dismount_atlas_crouch
+ // ptpov_dismount_ogre_crouch
+ // ptpov_dismount_stryder_crouch
+
+ FirstPersonSequenceStruct playerSequence = playerSequenceFunc( player, titan )
+ FirstPersonSequenceStruct titanSequence = titanSequenceFunc( player, titan )
+
+ #if SERVER
+ StatusEffect_StopAll( player, eStatusEffect.lockon_detected_titan )
+ #endif
+
+ player.ForceStand()
+
+ thread FirstPersonSequence( titanSequence, titan )
+ thread FirstPersonSequence( playerSequence, player, titan )
+
+ if ( !wasCustomDisembark )
+ thread ClearParentBeforeIntersect( player, titan, playerSequence.thirdPersonAnim, e )
+
+ if ( !standing )
+ {
+ SetStanceKneel( titan.GetTitanSoul() )
+ }
+
+ //player.Anim_EnablePlanting()
+
+ #if SERVER && MP
+ PIN_AddToPlayerCountStat( player, "disembarks" )
+ PIN_PlayerAbility( player, "", "disembark", {}, 0 )
+ #endif
+
+ WaittillAnimDone( player )
+}
+
+bool function PlayerIsFarOffTheGround( entity player, array<entity> ignoreEnts )
+{
+ vector boundingMaxs = player.GetBoundingMaxs()
+ float halfHeight = boundingMaxs.z / 2.0
+
+ vector startpos = player.GetOrigin()
+ vector endpos = startpos
+ endpos.z -= halfHeight
+
+ TraceResults result = TraceHull( startpos, endpos, player.GetBoundingMins(), player.GetBoundingMaxs(), ignoreEnts, TRACE_MASK_PLAYERSOLID, TRACE_COLLISION_GROUP_PLAYER )
+ //PrintTraceResults( result )
+
+ if ( result.startSolid )
+ return false
+
+ if ( result.allSolid )
+ return false
+
+ return ( result.fraction >= 1.0 )
+}
+
+FirstPersonSequenceStruct function GetDisembarkSequenceForPlayer( entity player, entity titan )
+{
+ string titanSubClass = GetSoulTitanSubClass( titan.GetTitanSoul() )
+
+ string player3pAnim, player1pAnim
+ if ( player.IsStanding() )
+ {
+ player3pAnim = "pt_dismount_" + titanSubClass + "_stand"
+ player1pAnim = "ptpov_dismount_" + titanSubClass + "_stand"
+ }
+ else
+ {
+ player3pAnim = "pt_dismount_" + titanSubClass + "_crouch"
+ player1pAnim = "ptpov_dismount_" + titanSubClass + "_crouch"
+ }
+
+ if ( player.HasPassive( ePassives.PAS_FAST_EMBARK ) )
+ {
+ player1pAnim += "_fast"
+ player3pAnim += "_fast"
+ }
+
+ FirstPersonSequenceStruct playerSequence
+ playerSequence.blendTime = 0
+ playerSequence.teleport = true
+ playerSequence.attachment = "hijack"
+ playerSequence.thirdPersonAnim = player3pAnim
+ playerSequence.firstPersonAnim = player1pAnim
+ playerSequence.useAnimatedRefAttachment = true
+
+ return playerSequence
+}
+
+FirstPersonSequenceStruct function GetDisembarkSequenceForTitan( entity player, entity titan )
+{
+ bool standing = player.IsStanding()
+
+ string titanDisembarkAnim
+ if ( standing )
+ titanDisembarkAnim = "at_dismount_stand"
+ else
+ titanDisembarkAnim = "at_dismount_crouch"
+
+ if ( player.HasPassive( ePassives.PAS_FAST_EMBARK ) )
+ titanDisembarkAnim += "_fast"
+
+ vector origin = titan.GetOrigin()
+ vector angles = titan.EyeAngles()
+ angles.z = 0
+ angles.x = 0
+
+ FirstPersonSequenceStruct titanSequence
+ titanSequence.blendTime = 0.3
+ titanSequence.thirdPersonAnim = titanDisembarkAnim
+ if ( !standing )
+ titanSequence.thirdPersonAnimIdle = "at_MP_embark_idle_blended"
+ titanSequence.gravity = true
+ titanSequence.origin = origin
+ titanSequence.angles = angles
+
+ return titanSequence
+}
+
+function DelayedSafePlayerLocationForDisembark( entity player, entity titan )
+{
+ float currentTime = Time()
+ float allowedTime = player.p.isCustomDisembark ? 10.0 : 2.0
+
+ player.EndSignal( "OnDestroy" )
+
+ while( IsPlayerDisembarking( player ) )
+ {
+ Assert( Time() - currentTime < allowedTime ) // Failsafe of waiting 2 seconds in case SOMETHING REALLY GOES WRONG.
+ if ( !IsAlive( player ) )
+ return
+
+ WaitFrame()
+ }
+
+ if ( player.ContextAction_IsActive() ) //Immediately after disembarking player might have gotten pulled into another context action e.g. embarking into evac dropship
+ return
+
+ player.ClearParent()
+ player.PlayerCone_Disable()
+ player.ViewOffsetEntity_Clear()
+ player.GetFirstPersonProxy().HideFirstPersonProxy()
+
+ vector safeStartPoint
+
+ if ( IsValid( titan ) )
+ {
+ vector titanBoundingMaxs = titan.GetBoundingMaxs()
+ float halfTitanHeight = titanBoundingMaxs.z * 0.5
+ safeStartPoint = titan.GetOrigin() + < 0, 0, halfTitanHeight > //Let the start point of PutEntityInSafeSpot be closer to where the player is when disebmarking instead of the ground origin
+ }
+ else
+ {
+ titan = null
+ safeStartPoint = player.GetOrigin()
+ }
+
+ #if DEV
+ if ( file.embarkDebugPrint )
+ printt( "DelayedSafePlayerLocationForDisembark, before PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + safeStartPoint )
+ #endif
+
+
+
+ if ( !PutEntityInSafeSpot( player, titan, null, safeStartPoint, player.GetOrigin() ) )
+ player.SetOrigin( safeStartPoint )
+
+ #if DEV
+ if ( file.embarkDebugPrint )
+ printt( "DelayedSafePlayerLocationForDisembark, after PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + safeStartPoint )
+ #endif
+
+}
+
+function ClearParentBeforeIntersect( entity player, entity titan, anim, e )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnAnimationDone" )
+ player.EndSignal( "OnAnimationInterrupted" )
+ //local mins = player.GetBoundingMins()
+ //local maxs = player.GetBoundingMaxs()
+
+ OnThreadEnd(
+ function() : ( player, e )
+ {
+ if ( IsValid( player ) )
+ thread DelayedSafePlayerLocationForDisembark( player, expect entity( e.titan ) )
+ }
+ )
+
+ wait 0.25
+
+ vector lastOrigin = player.GetOrigin()
+ for ( ;; )
+ {
+ if ( EntityInSolid( player, titan, 24 ) )
+ break
+
+ lastOrigin = player.GetOrigin()
+ WaitFrame()
+ }
+
+ player.SetOrigin( lastOrigin )
+}
+
+function LockedViewCone( human )
+{
+ human.PlayerCone_FromAnim()
+ human.PlayerCone_SetMinYaw( 0 )
+ human.PlayerCone_SetMaxYaw( 0 )
+ human.PlayerCone_SetMinPitch( 0 )
+ human.PlayerCone_SetMaxPitch( 0 )
+}
+
+
+function PlayerOwnsTitanUntilSeparation( player, titan, dist )
+{
+ titan.SetOwner( player )
+
+ player.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function () : ( player, titan )
+ {
+ if ( !IsValid( titan ) )
+ return
+
+ titan.SetOwner( null )
+ }
+ )
+
+ // wait until player moves away
+ local distSqr = dist * dist
+ for ( ;; )
+ {
+ if ( DistanceSqr( titan.GetOrigin(), player.GetOrigin() ) > distSqr )
+ break
+
+ wait 0.5
+ }
+}
+
+function PlayerEndsDisembark( player, e )
+{
+ thread PlayerEndsDisembarkThread( player, e )
+}
+
+function PlayerEndsDisembarkThread( player, e )
+{
+ expect entity( player )
+ player.EndSignal( "OnDestroy" )
+
+ if ( e.PilotCleanUpDone )
+ return
+
+ e.PilotCleanUpDone = true
+ bool wasCustomDisembark = player.p.isCustomDisembark
+
+ wait 0.1
+
+ ClearPlayerAnimViewEntity( player )
+ if ( player.ContextAction_IsBusy() )
+ player.ContextAction_ClearBusy()
+
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() & (~CE_FLAG_DISEMBARK) )
+
+ player.Show()
+ TitanEmbark_PlayerCleanup( player )
+ player.p.isDisembarking = false
+
+ //// give a player a boost out the door
+ //
+ //
+ if ( IsAlive( player ) && !wasCustomDisembark )
+ {
+ local angles = player.EyeAngles()
+ if ( IsValid( e.titan ) )
+ angles = e.titan.GetAngles()
+
+ angles.x = 0
+ angles.z = 0
+ local forward = AnglesToForward( angles )
+ local up = AnglesToUp( angles )
+ local vel = forward * 250 + up * 200
+ player.SetVelocity( vel )
+ //DebugDrawLine( player.GetOrigin(), player.GetOrigin() + forward * 500, 255, 0, 0, true, 5.0 )
+ }
+}
+
+function IsPlayerEmbarking( player )
+{
+ expect entity ( player )
+ return player.p.isEmbarking
+}
+#endif // SERVER
+
+function IsPlayerDisembarking( player )
+{
+ expect entity ( player )
+ return player.p.isDisembarking
+}
+
+function PlayerCanEmbarkIntoTitan( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function
+{
+ if ( player.IsNoclipping() )
+ return false
+
+ if ( !TitanIsCurrentlyEmbarkableForPlayer( player, titan ) )
+ return false
+
+ return FindBestEmbark( player, titan ) != null
+}
+
+bool function TitanIsCurrentlyEmbarkableForPlayer( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function
+{
+ if ( !CanEmbark( player ) )
+ return false
+
+ if ( player.Anim_IsActive() )
+ return false
+
+ if ( !player.IsHuman() )
+ return false
+
+ if ( player.ContextAction_IsActive() )
+ return false
+
+ if ( !titan.IsEntAlive() )
+ return false
+
+ if ( titan.ContextAction_IsActive() )
+ return false
+
+ if ( !titan.IsInterruptable() )
+ return false
+
+ if ( IsValid( titan.GetParent() ) )
+ return false
+
+ if ( !HasSoul( titan ) )
+ return false
+
+ local soul = titan.GetTitanSoul()
+
+ if ( GetDoomedState( titan ) && !PROTO_AlternateDoomedState() )
+ return false
+
+ if ( soul.IsEjecting() )
+ return false
+
+ #if SERVER
+ // client doesn't know these things
+ if ( IsPlayerEmbarking( player ) )
+ return false
+
+ if ( IsPlayerDisembarking( player ) )
+ return false
+ #endif
+
+ if ( "disembarkTime" in titan.s )
+ {
+ if ( Time() - titan.s.disembarkTime < 1.65 )
+ return false
+ }
+
+ return true
+}
+
+function FindEmbarkActionForCriteria( criteria )
+{
+ local embarkAction
+ foreach ( option in level.titanEmbarkActions )
+ {
+ bool failed = false
+ foreach ( key, value in criteria )
+ {
+ if ( value != option[key] )
+ {
+ failed = true
+ break
+ }
+ }
+
+ if ( !failed )
+ {
+ embarkAction = option
+ break
+ }
+ }
+
+ return embarkAction
+
+}
+
+function GetRandomEmbarkAction()
+{
+ return level.titanEmbarkActions[ RandomInt( level.titanEmbarkActions.len() ) ]
+}
+
+function FindBestEmbark( entity player, entity titan, bool doDistCheck = true )
+{
+// if ( IsServer() )
+// printt( "finding best embark for " + player + " to " + titan )
+ vector playerPos = player.GetOrigin()
+ vector titanPos = titan.GetOrigin()
+
+ vector relTitanToPlayerDir = CalculateTitanToPlayerDir( titan, player )
+
+ local bestAction = null
+ float bestDot = -2
+ float dist = 0
+
+ if ( doDistCheck )
+ {
+ dist = Distance( playerPos, titanPos )
+ if ( dist > level.titanEmbarkFarthestDistance )
+ return null
+ }
+ //if ( IsServer() )
+ // printt( "dist: " + dist )
+
+ for ( int i = 0; i < 3; i++ )
+ {
+ bestAction = GetBestEmbarkAction( i, player, titan, dist, relTitanToPlayerDir )
+ if ( bestAction != null )
+ break
+ }
+
+ if ( bestAction == null )
+ return null
+
+ return GenerateEmbarkActionTable( player, titan, bestAction, relTitanToPlayerDir )
+}
+
+vector function CalculateTitanToPlayerDir( entity titan, entity player )
+{
+ vector playerPos = player.GetOrigin()
+ vector titanPos = titan.GetOrigin()
+
+ vector absTitanToPlayerDir
+ if ( playerPos == titanPos )
+ {
+ absTitanToPlayerDir = Vector( 1, 0, 0 )
+ }
+ else
+ {
+ vector angles = player.EyeAngles()
+ vector forward = AnglesToForward( angles )
+
+ absTitanToPlayerDir = ( playerPos - titanPos )
+
+
+ absTitanToPlayerDir.Norm()
+
+// not needed cause we can't get in without a legal use
+// // is the target in my fov?
+// if ( forward.Dot( absTitanToPlayerDir * -1 ) < 0.77 )
+// return null
+ }
+
+ vector titanAngles = titan.GetAngles()
+ titanAngles.x = 0
+ if ( titan.GetTitanSoul().GetStance() >= STANCE_STANDING )
+ titanAngles = AnglesCompose( titanAngles, Vector( 0, -30, 0 ) )
+
+ vector relTitanToPlayerDir = CalcRelativeVector( titanAngles, absTitanToPlayerDir )
+ return relTitanToPlayerDir
+}
+
+function GenerateEmbarkActionTable( entity player, entity titan, bestAction, var relTitanToPlayerDir = null )
+{
+ bool useFastAnims = player.IsPlayer() && player.HasPassive( ePassives.PAS_FAST_EMBARK )
+
+ if ( relTitanToPlayerDir == null )
+ {
+ relTitanToPlayerDir = CalculateTitanToPlayerDir( titan, player )
+ expect vector( relTitanToPlayerDir )
+ }
+ else
+ {
+ expect vector( relTitanToPlayerDir )
+ }
+
+ local Table = {}
+ Table.action <- bestAction
+
+ if ( "animSet" in bestAction )
+ {
+ Table.animSet <- bestAction.animSet
+ Table.audioSet <- bestAction.audioSet
+ }
+ else
+ {
+ local bestAnimSet
+ local bestAudioSet
+ local bestDot = -2
+ Assert( "animSets" in bestAction, "Table has no animSet and no animSets!" )
+ foreach ( animSet in bestAction.animSets )
+ {
+ local dot = relTitanToPlayerDir.Dot( animSet.direction )
+
+ if ( dot > bestDot )
+ {
+ bestAnimSet = animSet
+ bestAudioSet = animSet.audioSet
+ bestDot = dot
+ }
+ }
+
+ Table.animSet <- bestAnimSet
+ Table.audioSet <- bestAudioSet
+ }
+
+ if ( useFastAnims )
+ {
+ Table.animSet = clone Table.animSet
+
+ foreach ( string idx, item in Table.animSet )
+ {
+ if ( IsString( item ) )
+ Table.animSet[ idx ] = item + "_fast"
+ }
+ }
+
+ return Table
+}
+
+function GetBestEmbarkAction( int priority, entity player, entity titan, float dist, vector relTitanToPlayerDir )
+{
+ local bestAction = null
+ local bestDot = -2
+
+ foreach ( action in level.titanEmbarkActions )
+ {
+ if ( action.priority != priority )
+ continue
+
+ if ( dist > action.distance )
+ {
+ //if ( IsServer() )
+ //printt( "Failed: Action " + action.embark + " had dist " + action.distance + " vs actual dist " + dist )
+ continue
+ }
+
+ if ( action.lungeCheck )
+ {
+ if ( player.IsNPC() )
+ continue
+
+ if ( player.Lunge_IsActive() != action.lungeCheck )
+ continue
+ }
+
+ local dot = relTitanToPlayerDir.Dot( action.direction )
+
+ if ( dot < action.minDot )
+ {
+ //if ( IsServer() )
+ //printt( "Failed: Action " + action.embark + " had dot " + dot )
+ continue
+ }
+
+ if ( expect bool( action.titanCanStandRequired ) && !TitanCanStand( titan ) )
+ {
+ //if ( IsServer() )
+ //printt( "Failed: Action " + action.embark + " cant stand" )
+ continue
+ }
+
+ if ( dot > bestDot )
+ {
+ //if ( IsServer() )
+ //printt( "Action " + action.embark + " had dot " + dot )
+ bestAction = action
+ bestDot = dot
+ }
+ }
+
+ return bestAction
+}
+
+function FindBestEmbarkForNpcAnim( entity npc, entity titan )
+{
+ bool doDistCheck = false
+ return FindBestEmbark( npc, titan, doDistCheck )
+}
+
+
+
+
+bool function TitanCanStand( entity titan )
+{
+ #if SERVER
+ vector maxs = titan.GetBoundingMaxs()
+ vector mins = titan.GetBoundingMins()
+
+ vector start = titan.GetOrigin()
+ vector end = titan.GetOrigin()
+ entity soul = titan.GetTitanSoul()
+ entity ignoreEnt = null
+
+ if ( IsValid( soul.soul.bubbleShield ) )
+ ignoreEnt = soul.soul.bubbleShield
+ int mask = titan.GetPhysicsSolidMask()
+ //printt( "mask has " + MaskTester( mask ) )
+ TraceResults result = TraceHull( start, end, mins, maxs, ignoreEnt, mask, TRACE_COLLISION_GROUP_NONE )
+ //printt( "start " + start + " end " + end )
+
+ //DebugDrawLine( start, result.endPos, 0, 255, 0, true, 5.0 )
+ //DebugDrawLine( result.endPos, end, 255, 0, 0, true, 5.0 )
+
+ bool canStand = result.fraction >= 1.0
+ titan.SetCanStand( canStand )
+ return canStand
+ #else
+ return titan.GetCanStand() != 0
+ #endif
+}
+
+bool function PlayerCanEmbarkTitan( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function
+{
+ PerfStart( PerfIndexClient.PlayerCanEmbarkTitan1 )
+ if ( !TitanIsCurrentlyEmbarkableForPlayer( player, titan ) )
+ {
+ PerfEnd( PerfIndexClient.PlayerCanEmbarkTitan1 )
+ return false
+ }
+ PerfEnd( PerfIndexClient.PlayerCanEmbarkTitan1 )
+
+ PerfStart( PerfIndexClient.FindBestEmbark )
+ bool res = FindBestEmbark( player, titan ) != null
+ PerfEnd( PerfIndexClient.FindBestEmbark )
+
+ return res
+}
+
+bool function PlayerCanImmediatelyEmbarkTitan( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function
+{
+ if ( "embarkingPlayer" in titan.s )
+ return false
+
+ if ( player.IsNoclipping() )
+ return false
+
+ if ( !IsAlive( player ) || !IsAlive( titan ) )
+ return false
+
+ return FindBestEmbark( player, titan ) != null
+}
+
+#if SERVER
+function PlayerLungesToEmbark( entity player, entity ent )
+{
+ Assert( TitanIsCurrentlyEmbarkableForPlayer( player, ent ) )
+
+ if ( PlayerCanImmediatelyEmbarkTitan( player, ent ) )
+ {
+ table embarkDirection = expect table( FindBestEmbark( player, ent ) )
+ thread PlayerEmbarksTitan( player, ent, embarkDirection )
+ return
+ }
+
+ if ( player.IsNoclipping() )
+ return
+
+ // already lunging
+ if ( player.Lunge_IsActive() )
+ return
+
+ if ( ShouldStopLunging( player, ent ) )
+ return
+
+ player.Lunge_SetTargetEntity( ent, false )
+ player.Lunge_SetSmoothTime( 3.0 )
+}
+
+void function TitanBecomesPilot_UpdateRodeoRiderHud( entity playerTitan, entity npc_titan )
+{
+ entity rodeoPilot = GetRodeoPilot( npc_titan )
+ if ( !IsValid( rodeoPilot ) )
+ return
+
+ Remote_CallFunction_Replay( rodeoPilot, "ServerCallback_UpdateRodeoRiderHud" )
+}
+
+void function PilotBecomesTitan_UpdateRodeoRiderHud( entity playerTitan, entity npc_titan )
+{
+ entity rodeoPilot = GetRodeoPilot( playerTitan )
+ if ( !IsValid( rodeoPilot ) )
+ return
+
+ Remote_CallFunction_Replay( rodeoPilot, "ServerCallback_UpdateRodeoRiderHud" )
+
+}
+
+void function SetSmallDisembarkFailSafeTeleportVector( vector value ) //TODO: Re-examine this for next game, probably should have different values for SP versus MP
+{
+ file.smallDisembarkFailSafeTeleportVector = value
+}
+
+void function SetLargeDisembarkFailSafeTeleportVector( vector value ) //TODO: Re-examine this for next game, probably should have different values for SP versus MP
+{
+ file.largeDisembarkFailSafeTeleportVector = value
+}
+
+#endif // SERVER
+
+#if DEV
+void function SetEmbarkDebugPrint( bool value )
+{
+ file.embarkDebugPrint = value
+}
+#endif \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_peacekraber.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_peacekraber.nut
new file mode 100644
index 00000000..a9da541f
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_peacekraber.nut
@@ -0,0 +1,162 @@
+untyped
+
+// created by JustANormalUser#0001 on discord
+
+global function OnWeaponPrimaryAttack_peacekraber;
+global function OnWeaponDeactivate_peacekraber
+global function OnWeaponActivate_peacekraber
+
+#if SERVER
+global function OnWeaponNpcPrimaryAttack_peacekraber
+#endif // #if SERVER
+
+
+const PEACEKRABER_MAX_BOLTS = 11 // this is the code limit for bolts per frame... do not increase.
+bool isWeaponActive = false;
+entity clientWeapon = null;
+
+struct
+{
+ float[2][PEACEKRABER_MAX_BOLTS] boltOffsets = [
+ [0.0, 0.0], // center
+ [0.0, 1.0], // top
+ [-0.683, 0.327],
+ [0.683, 0.327],
+ [-0.636, -0.683],
+ [0.636, -0.683],
+ [0.0, 0.5],
+ [-0.342, 0.174],
+ [0.342, 0.174],
+ [-0.318, -0.342],
+ [0.318, -0.342],
+ ]
+
+ int maxAmmo
+ float ammoRegenTime
+} file
+// "OnWeaponActivate" "OnWeaponActivate_peacekraber"
+// "OnWeaponDeactivate" "OnWeaponDeactivate_peacekraber"
+void function OnWeaponActivate_peacekraber (entity weapon) {
+ #if CLIENT
+ if (!weapon.GetWeaponOwner().IsPlayer() || weapon.GetWeaponOwner() != GetLocalViewPlayer()) return;
+ isWeaponActive = true;
+ clientWeapon = weapon;
+ thread CrosshairCycle();
+ #endif
+}
+
+void function OnWeaponDeactivate_peacekraber (entity weapon) {
+ #if CLIENT
+ if (!weapon.GetWeaponOwner().IsPlayer() || weapon.GetWeaponOwner() != GetLocalViewPlayer()) return;
+ isWeaponActive = false;
+ #endif
+}
+#if CLIENT
+void function CrosshairCycle() {
+ var rui = RuiCreate( $"ui/crosshair_shotgun.rpak", clGlobal.topoCockpitHudPermanent, RUI_DRAW_COCKPIT, 0 )
+ RuiSetFloat(rui, "adjustedSpread", 0.1)
+ array<int> spreadFrac = [1, 0.65, 0.45, 0.2]
+ array<vector> colors = [<1, 1, 1>, <0.666, 1, 1>, <0.333, 1, 1>, <0, 1, 1>]
+ int chargeLevel;
+ float chargeFrac;
+ while (isWeaponActive) {
+ WaitFrame()
+ chargeLevel = clientWeapon.GetWeaponChargeLevel();
+ chargeFrac = clientWeapon.GetWeaponChargeFraction();
+ RuiSetFloat3(rui, "teamColor", colors[chargeLevel]);
+ switch (chargeLevel) {
+ case 0:
+ if (chargeFrac > 0.266) {
+ RuiSetFloat(rui, "adjustedSpread", Graph(chargeFrac, 0.266, 0.333, 0.1, 0.065))
+ }
+ else RuiSetFloat(rui, "adjustedSpread", 0.1)
+ break;
+ case 1:
+ if (chargeFrac > 0.6) {
+ RuiSetFloat(rui, "adjustedSpread", Graph(chargeFrac, 0.6, 0.666, 0.065, 0.045))
+ }
+ else RuiSetFloat(rui, "adjustedSpread", 0.065)
+ break;
+ case 2:
+ if (chargeFrac > 0.933) {
+ RuiSetFloat(rui, "adjustedSpread", Graph(chargeFrac, 0.933, 1, 0.045, 0.02))
+ }
+ else RuiSetFloat(rui, "adjustedSpread", 0.045)
+ break;
+ case 3:
+ RuiSetFloat(rui, "adjustedSpread", 0.025)
+ break;
+ default:
+ break;
+ }
+ }
+
+ RuiDestroy(rui);
+ clientWeapon = null;
+}
+#endif
+
+var function OnWeaponPrimaryAttack_peacekraber( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ #if CLIENT
+ weapon.EmitWeaponSound( "Weapon_Titan_Sniper_LevelTick_2" )
+ #endif
+
+ return FireWeaponPlayerAndNPC( attackParams, true, weapon )
+}
+
+#if SERVER
+var function OnWeaponNpcPrimaryAttack_peacekraber( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ return FireWeaponPlayerAndNPC( attackParams, false, weapon )
+}
+#endif // #if SERVER
+
+function FireWeaponPlayerAndNPC( WeaponPrimaryAttackParams attackParams, bool playerFired, entity weapon )
+{
+ entity owner = weapon.GetWeaponOwner()
+ bool shouldCreateProjectile = false
+ if ( IsServer() || weapon.ShouldPredictProjectiles() )
+ shouldCreateProjectile = true
+ #if CLIENT
+ if ( !playerFired )
+ shouldCreateProjectile = false
+ #endif
+
+ vector attackAngles = VectorToAngles( attackParams.dir )
+ vector baseUpVec = AnglesToUp( attackAngles )
+ vector baseRightVec = AnglesToRight( attackAngles )
+
+ if ( shouldCreateProjectile )
+ {
+ weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 )
+ array<int> spreadFrac = [1, 0.65, 0.45, 0.2]
+
+ for ( int index = 0; index < PEACEKRABER_MAX_BOLTS; index++ )
+ {
+ vector upVec = baseUpVec * file.boltOffsets[index][1] * 0.05 * spreadFrac[weapon.GetWeaponChargeLevel()]
+ vector rightVec = baseRightVec * file.boltOffsets[index][0] * 0.05 * spreadFrac[weapon.GetWeaponChargeLevel()]
+
+ vector attackDir = attackParams.dir + upVec + rightVec
+ float projectileSpeed = 2800
+
+ if ( weapon.GetWeaponClassName() == "mp_weapon_peacekraber" )
+ {
+ projectileSpeed = 6400
+ }
+
+ entity bolt = weapon.FireWeaponBolt( attackParams.pos, attackDir, projectileSpeed, damageTypes.largeCaliber | DF_SHOTGUN, damageTypes.largeCaliber | DF_SHOTGUN, playerFired, index )
+ if ( bolt )
+ {
+ bolt.kv.gravity = 0.4 // 0.09
+
+ if ( weapon.GetWeaponClassName() == "mp_weapon_peacekraber" )
+ bolt.SetProjectileLifetime( RandomFloatRange( 1.0, 1.3 ) )
+ else
+ bolt.SetProjectileLifetime( RandomFloatRange( 0.50, 0.65 ) )
+ }
+ }
+ }
+
+ return 1
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_toolgun.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_toolgun.nut
new file mode 100644
index 00000000..94bd7429
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_toolgun.nut
@@ -0,0 +1,39 @@
+untyped
+global function OnWeaponActivate_weapon_toolgun
+global function OnWeaponDeactivate_weapon_toolgun
+global function OnWeaponPrimaryAttack_weapon_toolgun
+global function OnWeaponStartZoomIn_weapon_toolgun
+global function OnWeaponStartZoomOut_weapon_toolgun
+#if SERVER
+global function OnWeaponNpcPrimaryAttack_weapon_toolgun
+#endif
+
+void function OnWeaponActivate_weapon_toolgun( entity weapon )
+{
+ CallToolOnEquipped( weapon.GetOwner(), weapon )
+}
+
+void function OnWeaponDeactivate_weapon_toolgun( entity weapon )
+{
+ CallToolOnUnequipped( weapon.GetOwner(), weapon )
+}
+
+var function OnWeaponPrimaryAttack_weapon_toolgun( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ CallToolOnFired( weapon.GetOwner(), weapon, attackParams )
+}
+
+void function OnWeaponStartZoomIn_weapon_toolgun( entity weapon )
+{
+ CallToolOnAds( weapon.GetOwner(), weapon )
+}
+
+void function OnWeaponStartZoomOut_weapon_toolgun( entity weapon )
+{
+ CallToolOnUnAds( weapon.GetOwner(), weapon )
+}
+
+var function OnWeaponNpcPrimaryAttack_weapon_toolgun( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ // do nothing for now, maybe make it launch nukes or something later that could be funny
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut
new file mode 100644
index 00000000..512c538c
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut
@@ -0,0 +1,30 @@
+global function ToolgunToolCreateExplosion_Init
+
+void function ToolgunToolCreateExplosion_Init()
+{
+ AddCallback_OnToolgunToolsInit( CreateToolgunToolCreateExplosion )
+}
+
+void function CreateToolgunToolCreateExplosion()
+{
+ ToolgunTool createExplosionTool
+ createExplosionTool.toolName = "Create explosion"
+ createExplosionTool.toolDescription = "Creates an explosion"
+
+ createExplosionTool.onFired = ToolgunToolCreateExplosion_Fire
+
+ RegisterTool( createExplosionTool )
+}
+
+void function ToolgunToolCreateExplosion_Fire( entity player, entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ #if SERVER
+ int dist = 55555 // should hit edge of map at all times ideally
+ vector forward = AnglesToForward( player.EyeAngles() )
+
+ // raycast to explosion position
+ TraceResults trace = TraceLine( player.EyePosition(), player.EyePosition() + ( dist * forward ), null, TRACE_MASK_NPCSOLID )
+ // make explosion
+ Explosion( trace.endPos, player, player, 90, 90, 100, 100, 0, trace.endPos, 5000, damageTypes.explosive, eDamageSourceId.burn, "exp_small" )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut
new file mode 100644
index 00000000..d6975c6d
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut
@@ -0,0 +1,24 @@
+global function ToolgunToolThrowEntity_Init
+
+void function ToolgunToolThrowEntity_Init()
+{
+ AddCallback_OnToolgunToolsInit( CreateToolgunToolThrow )
+}
+
+void function CreateToolgunToolThrow()
+{
+ ToolgunTool throwTool
+ throwTool.toolName = "Throw entity"
+ throwTool.toolDescription = "Spawns and throws the currently selected entity type"
+
+ throwTool.onFired = ToolgunToolThrow_OnFired
+
+ RegisterTool( throwTool )
+}
+
+void function ToolgunToolThrow_OnFired( entity player, entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ #if SERVER
+ ClientCommand( player, "ent_throw npc_frag_drone" )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut
new file mode 100644
index 00000000..4d7e9d89
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut
@@ -0,0 +1,196 @@
+untyped
+global function ToolgunTools_Init
+global function AddCallback_OnToolgunToolsInit
+
+global function RegisterTool
+global function CallToolOnEquipped
+global function CallToolOnUnequipped
+global function CallToolOnFired
+global function CallToolOnAds
+global function CallToolOnUnAds
+
+#if SERVER
+global function SetToolgunAmmoCount
+global function SetToolgunProscreen
+#endif // #if SERVER
+
+global struct ToolgunTool
+{
+ string toolName = ""
+ string toolDescription = ""
+
+ void functionref( entity, entity ) onEquipped
+ void functionref( entity, entity ) onUnequipped
+ void functionref( entity, entity, WeaponPrimaryAttackParams ) onFired
+ void functionref( entity, entity ) onAds
+ void functionref( entity, entity ) onUnAds
+}
+
+struct ToolgunPlayerSettings
+{
+ int selectedToolIndex
+ int ammoCount
+ int proscreenNumber
+}
+
+// doing preprocessor defs inside the struct seemed to cause compiler errors so we define them separately
+#if CLIENT
+struct {
+ array<void functionref()> onToolgunToolsInitCallbacks
+
+ array<ToolgunTool> tools
+ ToolgunPlayerSettings clientPlayerSettings
+} file
+#endif // #if CLIENT
+
+#if SERVER
+struct {
+ array<void functionref()> onToolgunToolsInitCallbacks
+
+ array<ToolgunTool> tools
+ // serverside playersettings
+ table<int, ToolgunPlayerSettings> playerSettings
+} file
+#endif // #if SERVER
+
+void function AddCallback_OnToolgunToolsInit( void functionref() callback )
+{
+ file.onToolgunToolsInitCallbacks.append( callback )
+}
+
+void function ToolgunTools_Init()
+{
+ //#if SERVER
+ //AddCallback_OnClientConnecting( InitialiseToolgunSettings )
+ //AddCallback_OnClientDisconnected( DestroyToolgunSettings )
+ //#endif // #if SERVER
+ //
+ //// need this threaded so we can wait a frame
+ //thread ToolgunTools_InitThreaded()
+}
+
+void function ToolgunTools_InitThreaded()
+{
+ // wait a frame for tools to all init and add their callbacks
+ WaitFrame()
+
+ // call callbacks
+ foreach ( void functionref() callback in file.onToolgunToolsInitCallbacks )
+ callback()
+}
+
+void function RegisterTool( ToolgunTool toolStruct )
+{
+ file.tools.append( toolStruct )
+}
+
+void function CallToolOnEquipped( entity player, entity weapon )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onEquipped != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onEquipped( player, weapon )
+ #endif // #if CLIENT
+
+ #if SERVER
+ // set ammo and proscreen numbers when equipped
+ weapon.SetProScreenIntValForIndex( PRO_SCREEN_INT_LIFETIME_KILLS, file.playerSettings[ player.GetPlayerIndex() ].proscreenNumber )
+ weapon.SetWeaponPrimaryClipCount( file.playerSettings[ player.GetPlayerIndex() ].ammoCount )
+
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onEquipped != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onEquipped( player, weapon )
+ #endif // #if SERVER
+}
+
+void function CallToolOnUnequipped( entity player, entity weapon )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnequipped != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnequipped( player, weapon )
+ #endif // #if CLIENT
+
+ #if SERVER
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped( player, weapon )
+ #endif // #if SERVER
+}
+
+void function CallToolOnFired( entity player, entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onFired != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onFired( player, weapon, attackParams )
+ #endif // #if CLIENT
+
+ #if SERVER
+ // ammocount needs to be +1 because we lose 1 ammo immediately after this function is run
+ weapon.SetWeaponPrimaryClipCount( file.playerSettings[ player.GetPlayerIndex() ].ammoCount + 1 )
+
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onFired != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onFired( player, weapon, attackParams )
+ #endif // #if SERVER
+}
+
+void function CallToolOnAds( entity player, entity weapon )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onAds != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onAds( player, weapon )
+ #endif // #if CLIENT
+
+ #if SERVER
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped( player, weapon )
+ #endif // #if SERVER
+}
+
+void function CallToolOnUnAds( entity player, entity weapon )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnAds != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnAds( player, weapon )
+ #endif // #if CLIENT
+
+ #if SERVER
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped( player, weapon )
+ #endif // #if SERVER
+}
+
+#if SERVER
+void function InitialiseToolgunSettings( entity player )
+{
+ print( "initialising toolgun settings for player " + player )
+
+ ToolgunPlayerSettings playerSettings
+ playerSettings.selectedToolIndex = 0
+
+ file.playerSettings[ player.GetPlayerIndex() ] <- playerSettings
+}
+
+void function DestroyToolgunSettings( entity player )
+{
+ delete file.playerSettings[ player.GetPlayerIndex() ]
+}
+
+void function SetToolgunAmmoCount( entity player, int ammoCount )
+{
+ entity currentWeapon = player.GetActiveWeapon()
+ if ( ammoCount > currentWeapon.GetWeaponPrimaryClipCountMax() ) // setting clipcount to over max clipcount crashes so we need to prevent that
+ return
+
+ ToolgunPlayerSettings playerSettings = file.playerSettings[ player.GetPlayerIndex() ]
+ playerSettings.ammoCount = ammoCount
+
+ if ( currentWeapon.GetWeaponClassName() == "mp_weapon_toolgun" ) // set ammocount immediately if we've got toolgun equipped already
+ currentWeapon.SetWeaponPrimaryClipCount( ammoCount )
+}
+
+void function SetToolgunProscreen( entity player, int proscreenNumber )
+{
+ ToolgunPlayerSettings playerSettings = file.playerSettings[ player.GetPlayerIndex() ]
+ playerSettings.proscreenNumber = proscreenNumber
+
+ if ( player.GetActiveWeapon().GetWeaponClassName() == "mp_weapon_toolgun" ) // set proscreen number immediately if we've got toolgun equipped already
+ player.GetActiveWeapon().SetProScreenIntValForIndex( PRO_SCREEN_INT_LIFETIME_KILLS, proscreenNumber )
+}
+#endif // #if SERVER \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/weapons/melee_pilot_emptyhanded.txt b/Northstar.Custom/mod/scripts/weapons/melee_pilot_emptyhanded.txt
new file mode 100644
index 00000000..032867af
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/weapons/melee_pilot_emptyhanded.txt
@@ -0,0 +1,149 @@
+WeaponData
+{
+ // General
+ "printname" "Melee"
+ "shortprintname" "Melee"
+ "description" "Description needed"
+ "longdesc" "Description needed"
+
+ "menu_icon" "rui/hud/common/melee_icon"
+ "hud_icon" "rui/hud/common/melee_icon"
+
+ "weaponClass" "human"
+ "weaponType" "melee"
+ "body_type" "battle_rifle"
+ "fire_mode" "offhand_melee"
+ "never_drop" "1"
+
+ // Models
+ "viewmodel" "models/weapons/empty_handed/ptpov_emptyhand.mdl"
+ "playermodel" "models/weapons/empty_handed/w_empty_handed_human.mdl"
+
+ // Melee
+ "melee_can_hit_humansized" "1"
+ "melee_can_hit_titans" "0"
+ "melee_raise_recovery_animtime_normal" "0.75"
+ "melee_raise_recovery_animtime_quick" "0.4"
+ "melee_range" "60"
+ "melee_rumble_on_hit" "pilot_melee_hit"
+ "melee_rumble_on_hit_partial" "pilot_melee_hit_partial"
+ "melee_freezelook_on_hit" "0.25"
+ //"melee_sound_attack_1p" "Player_Melee_Backhand_1P"
+ "melee_sound_attack_3p" "player_melee_kick_3p"
+ "melee_anim_3p" "ACT_MP_MELEE_KNIFE_FIRST"
+ "damage_flags" "DF_MELEE | DF_KNOCK_BACK"
+ "impact_effect_table" "melee_human"
+ "impulse_force" "35000"
+ "offhand_keep_primary_in_hand" "1"
+
+ "zoom_effects" "0"
+
+ MP_BASE
+ {
+ "melee_lunge_target_range" "110"
+ "melee_lunge_target_angle" "30"
+ "melee_damage" "100"
+ "melee_damage_heavyarmor" "0"
+ "melee_attack_animtime" "0"
+ "melee_lunge_time" "0.3"
+ "melee_anim_1p_number" "2"
+ }
+
+ SP_BASE
+ {
+ "melee_lunge_target_range" "130"
+ "melee_lunge_target_angle" "40"
+ "melee_damage" "125"
+ "melee_damage_heavyarmor" "125"
+ "melee_attack_animtime" "0.0"
+ "melee_lunge_time" "0.2"
+ "melee_anim_1p_number" "1" // ACT_VM_MELEE_ATTACK1, 2, or 3
+ }
+
+ //
+
+ "ammo_suck_behavior" "melee_weapons"
+ "viewkick_spring" "melee"
+
+ "viewkick_pitch_base" "-1.75"
+ "viewkick_pitch_random" "0.75"
+ "viewkick_pitch_softScale" "0.3"
+ "viewkick_pitch_hardScale" "5.5"
+
+ "viewkick_yaw_base" "1.0"
+ "viewkick_yaw_random" "0.38"
+ "viewkick_yaw_softScale" "0.38"
+ "viewkick_yaw_hardScale" "15.5"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.6"
+ "viewkick_roll_randomMax" "0.8"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "20.75"
+
+ //
+ "damage_rodeo" "100"
+ "aimassist_disable_hipfire" "1"
+ "aimassist_disable_ads" "1"
+
+ // Bob
+ //"bob_cycle_time" "0.45"
+ //"bob_vert_dist" "0.075"
+ //"bob_horz_dist" "0.05"
+ //"bob_max_speed" "150"
+ //"bob_pitch" "0.75"
+ //"bob_yaw" "1"
+ //"bob_roll" "-0.75"
+
+ // Sway
+ //"sway_rotate_attach" "SWAY_ROTATE"
+ //"sway_min_x" "-0.3"
+ //"sway_min_y" "-0.5"
+ //"sway_min_z" "-0.5"
+ //"sway_max_x" "0.3"
+ //"sway_max_y" "0.5"
+ //"sway_max_z" "0.1"
+ //"sway_min_pitch" "-3"
+ //"sway_min_yaw" "-3.5"
+ //"sway_min_roll" "-2"
+ //"sway_max_pitch" "3"
+ //"sway_max_yaw" "3.5"
+ //"sway_max_roll" "3"
+ //"sway_translate_gain" "10"
+ //"sway_rotate_gain" "12"
+ //"sway_move_forward_translate_x" "0"
+ //"sway_move_forward_translate_z" "-0.5"
+ //"sway_move_back_translate_x" "-2"
+ //"sway_move_back_translate_z" "-1"
+ //"sway_move_left_translate_y" "-1"
+ //"sway_move_left_translate_z" "-0.5"
+ //"sway_move_left_rotate_roll" "-2"
+ //"sway_move_right_translate_y" "1"
+ //"sway_move_right_translate_z" "-0.5"
+ //"sway_move_right_rotate_roll" "4"
+ //"sway_move_up_translate_z" "-1"
+ //"sway_move_down_translate_z" "1"
+ //"sway_turn_left_rotate_yaw" "-1"
+ //"sway_turn_right_rotate_yaw" "1"
+ //"sway_turn_up_rotate_pitch" "1"
+ //"sway_turn_down_rotate_pitch" "-1"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "deployfirst_time" "1.25"
+ "sprintcycle_time" ".55"
+
+ Mods
+ {
+ rocket_arena
+ {
+ "melee_damage" "33"
+ }
+
+ // mod for testing low damage, high knockback "shove" melee rebalance
+ test_push
+ {
+ "melee_damage" "5"
+ "impulse_force" "150000"
+ }
+ }
+}
diff --git a/Northstar.Custom/mod/scripts/weapons/melee_pilot_kunai.txt b/Northstar.Custom/mod/scripts/weapons/melee_pilot_kunai.txt
new file mode 100644
index 00000000..e8b7c181
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/weapons/melee_pilot_kunai.txt
@@ -0,0 +1,149 @@
+WeaponData
+{
+ // General
+ "printname" "Melee"
+ "shortprintname" "Melee"
+ "description" "Description needed"
+ "longdesc" "Description needed"
+
+ "menu_icon" "ui/temp"
+ "hud_icon" "ui/temp"
+
+ "weaponClass" "human"
+ "weaponType" "melee"
+ "body_type" "battle_rifle"
+ "fire_mode" "offhand_melee"
+ "never_drop" "1"
+
+ // Models
+ "viewmodel" "models/weapons/kunai/ptpov_kunai.mdl"
+ "playermodel" "models/weapons/kunai/w_kunai.mdl"
+
+ // Melee
+ "melee_can_hit_humansized" "1"
+ "melee_can_hit_titans" "0"
+ "melee_raise_recovery_animtime_normal" "0.25"
+ "melee_raise_recovery_animtime_quick" "0.01"
+ "melee_range" "40"
+ "melee_rumble_on_hit" "pilot_melee_hit"
+ "melee_freezelook_on_hit" "0.0"
+ "melee_sound_attack_1p" "Player_Melee_Backhand_1P"
+ "melee_sound_attack_3p" "player_melee_kick_3p"
+ "melee_anim_1p_number" "1" // ACT_VM_MELEE_ATTACK1, 2, or 3
+ "melee_anim_3p" "ACT_MP_MELEE_KNIFE_FIRST"
+ "damage_flags" "DF_MELEE | DF_KNOCK_BACK"
+ "impact_effect_table" "melee_human"
+ "impulse_force" "25000"
+
+ "zoom_effects" "0"
+
+ MP_BASE
+ {
+ "melee_lunge_target_range" "130"
+ "melee_lunge_target_angle" "30"
+ "melee_damage" "300"
+ "melee_damage_heavyarmor" "300"
+ "melee_attack_animtime" "1.0"
+ "melee_lunge_time" "0.125"
+ }
+
+ SP_BASE
+ {
+ "melee_lunge_target_range" "200"
+ "melee_lunge_target_angle" "40"
+ "melee_damage" "130"
+ "melee_damage_heavyarmor" "130"
+ "melee_attack_animtime" "0.0"
+ "melee_lunge_time" "0.2"
+ }
+
+ //
+ "ammo_suck_behavior" "melee_weapons"
+ "viewkick_spring" "melee"
+
+ "viewkick_pitch_base" "-1.75"
+ "viewkick_pitch_random" "0.75"
+ "viewkick_pitch_softScale" "0.3"
+ "viewkick_pitch_hardScale" "10.5"
+
+ "viewkick_yaw_base" "-1.0"
+ "viewkick_yaw_random" "0.38"
+ "viewkick_yaw_softScale" "0.38"
+ "viewkick_yaw_hardScale" "30.5"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.6"
+ "viewkick_roll_randomMax" "0.8"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "10.0"
+
+ //
+ "damage_rodeo" "100"
+ "aimassist_disable_hipfire" "1"
+ "aimassist_disable_ads" "1"
+
+ // Bob
+ "bob_cycle_time" "0.45"
+ "bob_vert_dist" "0.075"
+ "bob_horz_dist" "0.05"
+ "bob_max_speed" "150"
+ "bob_pitch" "0.75"
+ "bob_yaw" "1"
+ "bob_roll" "-0.75"
+
+ // View Drift
+ "viewdrift_hipfire_stand_scale_pitch" "0.325"
+ "viewdrift_hipfire_crouch_scale_pitch" "0.275"
+ "viewdrift_hipfire_air_scale_pitch" "0.5"
+ "viewdrift_hipfire_stand_scale_yaw" "0.12"
+ "viewdrift_hipfire_crouch_scale_yaw" "0.10"
+ "viewdrift_hipfire_air_scale_yaw" "0.22"
+ "viewdrift_hipfire_speed_pitch" "0.6"
+ "viewdrift_hipfire_speed_yaw" "1.22"
+
+ // Sway
+ "sway_rotate_attach" "SWAY_ROTATE"
+ "sway_min_x" "-0.3"
+ "sway_min_y" "-0.5"
+ "sway_min_z" "-0.5"
+ "sway_max_x" "0.3"
+ "sway_max_y" "0.5"
+ "sway_max_z" "0.1"
+ "sway_min_pitch" "-3"
+ "sway_min_yaw" "-3.5"
+ "sway_min_roll" "-2"
+ "sway_max_pitch" "3"
+ "sway_max_yaw" "3.5"
+ "sway_max_roll" "3"
+ "sway_translate_gain" "10"
+ "sway_rotate_gain" "12"
+ "sway_move_forward_translate_x" "0"
+ "sway_move_forward_translate_z" "-0.5"
+ "sway_move_back_translate_x" "-2"
+ "sway_move_back_translate_z" "-1"
+ "sway_move_left_translate_y" "-1"
+ "sway_move_left_translate_z" "-0.5"
+ "sway_move_left_rotate_roll" "-2"
+ "sway_move_right_translate_y" "1"
+ "sway_move_right_translate_z" "-0.5"
+ "sway_move_right_rotate_roll" "4"
+ "sway_move_up_translate_z" "-1"
+ "sway_move_down_translate_z" "1"
+ "sway_turn_left_rotate_yaw" "-1"
+ "sway_turn_right_rotate_yaw" "1"
+ "sway_turn_up_rotate_pitch" "1"
+ "sway_turn_down_rotate_pitch" "-1"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "deployfirst_time" "1.25"
+ "sprintcycle_time" ".55"
+
+ Mods
+ {
+ test_push
+ {
+ "melee_damage" "5"
+ "impulse_force" "50000"
+ }
+ }
+}
diff --git a/Northstar.Custom/mod/scripts/weapons/mp_weapon_peacekraber.txt b/Northstar.Custom/mod/scripts/weapons/mp_weapon_peacekraber.txt
new file mode 100644
index 00000000..c5e61f93
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/weapons/mp_weapon_peacekraber.txt
@@ -0,0 +1,347 @@
+WeaponData
+{
+ // General
+ "printname" "#WPN_PEACEKRABER"
+ "shortprintname" "#WPN_PEACEKRABER_SHORT"
+ "description" "#WPN_PEACEKRABER_DESC"
+ "longdesc" "#WPN_PEACEKRABER_LONGDESC"
+ "weaponClass" "human"
+ "weaponSubClass" "projectile_shotgun"
+ "body_type" "close_quarters"
+ "fire_mode" "semi-auto"
+ "pickup_hold_prompt" "Hold [USE] [WEAPONNAME]"
+ "pickup_press_prompt" "[USE] [WEAPONNAME]"
+ "aimassist_adspull_weaponclass" "broad"
+ "minimap_reveal_distance" "32000"
+ "leveled_pickup" "0"
+
+ "viewmodel" "models/weapons/at_rifle/ptpov_at_rifle.mdl"
+ "playermodel" "models/weapons/at_rifle/w_at_rifle.mdl"
+
+ "OnWeaponActivate" "OnWeaponActivate_peacekraber"
+ "OnWeaponDeactivate" "OnWeaponDeactivate_peacekraber"
+ "OnWeaponPrimaryAttack" "OnWeaponPrimaryAttack_peacekraber"
+ "OnWeaponChargeLevelIncreased" "OnWeaponChargeLevelIncreased_titanweapon_sniper"
+ "OnWeaponNpcPrimaryAttack" "OnWeaponNpcPrimaryAttack_peacekraber"
+
+ "projectilemodel" "models/dev/empty_model.mdl"
+ "tracer_effect" "weapon_tracers_shotgun"
+ "impact_effect_table" "inc_bullet"
+ "projectile_trail_effect_0" "P_plasma_proj_MD"
+ "vortex_absorb_effect" "wpn_vortex_projectile_shotgun_FP"
+ "vortex_absorb_effect_third_person" "wpn_vortex_projectile_shotgun"
+ "vortex_absorb_sound" "Vortex_Shield_AbsorbBulletSmall"
+ "vortex_absorb_sound_1P_VS_3P" "Vortex_Shield_AbsorbBulletSmall_1P_VS_3P"
+
+ // Menu
+ "menu_category" "special"
+ "menu_anim_class" "medium"
+ "stat_damage" "98"
+ "stat_range" "15"
+ "stat_accuracy" "15"
+ "stat_rof" "20"
+
+ "ammo_display" "bar"
+ "impulse_force" "10000"
+
+ "impact_effect_table" "titan_shotgun_bullet"
+
+ "charge_time" "1.5"
+ "charge_levels" "3"
+ "charge_cooldown_time" "1.5"
+ "charge_cooldown_delay" "1.25"
+ "charge_is_triggered_by_ADS" "1"
+ "charge_end_forces_fire" "0"
+ "charge_sound_1p" "Weapon_Titan_Sniper_WindUp"
+ "charge_sound_stop_when_full" "1"
+ "charge_sound_seek_to_charge_fraction" "1"
+ "charge_full_sound_1p" "Weapon_Titan_Sniper_SustainLoop"
+ "charge_drain_sound_1p" "Weapon_Titan_Sniper_WindDown"
+ "charge_drain_sound_stop_when_empty" "1"
+ "charge_drain_sound_seek_to_charge_fraction" "1"
+ // Spread
+ "spread_stand_hip" "0"
+ "spread_stand_hip_run" "0"
+ "spread_stand_hip_sprint" "0"
+ "spread_stand_ads" "0"
+ "spread_crouch_hip" "0"
+ "spread_crouch_ads" "0"
+ "spread_air_hip" "0"
+ "spread_air_ads" "0"
+
+ // Damage - When Used by Players
+ "damage_type" "bullet"
+ "damage_near_distance" "500"
+ "damage_far_distance" "1000"
+ "damage_near_value" "11"
+ "damage_far_value" "11"
+ "damage_near_value_titanarmor" "20"
+ "damage_far_value_titanarmor" "15"
+
+ // Ammo
+ "ammo_stockpile_max" "20"
+ "ammo_default_total" "25"
+ "ammo_clip_size" "5"
+ "ammo_size_segmented_reload" "5"
+ "ammo_display_as_clips" "0"
+ "reload_is_segmented" "0"
+
+ "reload_time" "2.45"
+ "reload_time_late1" "2.175"
+ "reload_time_late2" "0"
+ "reloadempty_time" "3.35"
+ "reloadempty_time_late1" "2.5"
+ "reloadempty_time_late2" "1.7"
+ "reloadempty_time_late2" "1.1"
+ "rechamber_time" "0.756"
+ "viewmodel_offset_ads" "0 0 0"
+
+
+ "vortex_absorb_effect" "wpn_vortex_projectile_40mm_FP"
+ "vortex_absorb_effect_third_person" "wpn_vortex_projectile_40mm"
+ "vortex_absorb_sound" "Vortex_Shield_AbsorbBulletLarge"
+ "vortex_absorb_sound_1p_vs_3p" "Vortex_Shield_AbsorbBulletLarge_1P_VS_3P"
+
+ "sound_dryfire" "shotgun_dryfire"
+ "fire_sound_1_player_1p" "Weapon_Leadwall_Fire_1P"
+ "fire_sound_1_player_3p" "Weapon_Leadwall_Fire_3P"
+ "fire_sound_1_npc" "Weapon_Leadwall_Fire_3P"
+ "sound_zoom_in" "Weapon_EVA8_ADS_In"
+ "sound_zoom_out" "Weapon_EVA8_ADS_Out"
+
+ "fx_shell_eject_view" "wpn_shelleject_shotshell_FP"
+ "fx_shell_eject_world" "wpn_shelleject_shotshell"
+ "fx_shell_eject_attach" "shell"
+
+ "fx_muzzle_flash_view" "wpn_muzzleflash_40mm_fp"
+ "fx_muzzle_flash_world" "wpn_muzzleflash_40mm"
+ "fx_muzzle_flash_attach" "muzzle_flash"
+
+ "critical_hit_damage_scale" "1.3"
+ "critical_hit" "1"
+ "projectile_inherit_owner_velocity_scale" "0.2"
+
+ "bolt_hitsize" "0.5"
+ "bolt_hitsize_grow1_time" "0.075"
+ "bolt_hitsize_grow1_size" "4.0"
+ "bolt_hitsize_grow2_time" "0.075"
+ "bolt_hitsize_grow2_size" "4.0"
+ "bolt_hitsize_growfinal_lerptime" "0.18"
+ "bolt_hitsize_growfinal_size" "6.0"
+ "bolt_bounce_frac" "1.0"
+
+
+ "bolt_gravity_enabled" "1"
+
+ // Behavior
+ "fire_rate" "1.33"
+ "zoom_time_in" "0.2"
+ "zoom_time_out" "0.2"
+ "zoom_fov" "50"
+ "holster_time" "0.5"
+ "deploy_time" "0.8"
+ "lower_time" "0.25"
+ "raise_time" "0.3"
+ "vortex_refire_behavior" "bullet"
+ "allow_empty_fire" "0"
+ "reload_enabled" "1"
+ "allow_empty_click" "1"
+ "empty_reload_only" "0"
+ "trigger_snipercam" "0"
+ "allow_headshots" "0"
+ "primary_fire_does_not_block_sprint" "1"
+ "ads_move_speed_scale" "0.5"
+ "aimassist_disable_hipfire" "0"
+ "aimassist_disable_ads" "0"
+ "aimassist_disable_hipfire_titansonly" "1"
+ "aimassist_disable_ads_titansonly" "1"
+
+ "ammo_suck_behavior" "primary_weapons"
+
+ // View Kick
+ "viewkick_spring" "shotgun"
+
+ "viewkick_pitch_base" "-2.25"
+ "viewkick_pitch_random" "1"
+ "viewkick_pitch_softScale" "0.4"
+ "viewkick_pitch_hardScale" "2.0"
+
+ "viewkick_yaw_base" "-0.95"
+ "viewkick_yaw_random" "0.5"
+ "viewkick_yaw_softScale" "0.5"
+ "viewkick_yaw_hardScale" "2.0"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.6"
+ "viewkick_roll_randomMax" "0.8"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "2.75"
+
+ "viewkick_hipfire_weaponFraction" "0.1"
+ "viewkick_hipfire_weaponFraction_vmScale" "0.0"
+ "viewkick_ads_weaponFraction" "1.0"
+ "viewkick_ads_weaponFraction_vmScale" "0.15"
+
+ "viewkick_perm_pitch_base" "0"
+ "viewkick_perm_pitch_random" "0.0"
+
+ //
+ "viewmodel_shake_forward" "0.5"
+ "viewmodel_shake_up" "0.2"
+ "viewmodel_shake_right" "0.0"
+
+ // Bob
+ //"bob_cycle_time" "0.45"
+ //"bob_vert_dist" "0.1"
+ //"bob_horz_dist" "0.1"
+ //"bob_max_speed" "150"
+ //"bob_pitch" "0.75"
+ //"bob_yaw" "0.5"
+ //"bob_roll" "-0.75"
+
+
+ // Rumble
+ "fire_rumble" "pilot_singleshot_strong_fire"
+
+ // Sway
+ "sway_rotate_attach" "SWAY_ROTATE"
+ "sway_min_x" "-0.5"
+ "sway_min_y" "-0.4"
+ "sway_min_z" "-0.6"
+ "sway_max_x" "0.5"
+ "sway_max_y" "0.4"
+ "sway_max_z" "0.6"
+ "sway_min_pitch" "-3"
+ "sway_min_yaw" "-2.5"
+ "sway_min_roll" "-4"
+ "sway_max_pitch" "3"
+ "sway_max_yaw" "2.5"
+ "sway_max_roll" "4"
+ "sway_translate_gain" "2.5"
+ "sway_rotate_gain" "7"
+ "sway_move_forward_translate_x" "-0.1"
+ "sway_move_forward_translate_z" "-0.5"
+ "sway_move_back_translate_x" "0.2"
+ "sway_move_back_translate_z" "-0.2"
+ "sway_move_left_translate_y" "-1"
+ "sway_move_left_translate_z" "-0.5"
+ "sway_move_left_rotate_roll" "-4"
+ "sway_move_right_translate_y" "1"
+ "sway_move_right_translate_z" "-0.5"
+ "sway_move_right_rotate_roll" "4"
+ "sway_move_up_translate_z" "-1"
+ "sway_move_down_translate_z" "1"
+ "sway_turn_left_rotate_yaw" "1"
+ "sway_turn_right_rotate_yaw" "-1"
+
+ "sway_turn_left_translate_y" "-.2"
+ "sway_turn_right_translate_y" ".2"
+ "sway_turn_up_translate_z" "-.2"
+ "sway_turn_down_translate_z" ".2"
+ "sway_turn_up_translate_x" "-.1"
+ "sway_turn_down_translate_x" ".1"
+
+ "sway_turn_left_rotate_roll" "-4"
+ "sway_turn_right_rotate_roll" "4"
+ "sway_turn_up_rotate_pitch" "-3"
+ "sway_turn_down_rotate_pitch" "3"
+ "sway_turn_up_rotate_roll" "0.8"
+ "sway_turn_down_rotate_roll" "-0.8"
+
+ // Zoomed Sway
+ "sway_rotate_attach_zoomed" "SWAY_ROTATE_ZOOMED"
+ "sway_rotate_attach_blend_time_zoomed" "0.2"
+ "sway_rotate_gain_zoomed" "5"
+
+ "sway_min_yaw_zoomed" "-0.04"
+ "sway_max_yaw_zoomed" "0.04"
+ "sway_turn_left_rotate_yaw_zoomed" "-0.085"
+ "sway_turn_right_rotate_yaw_zoomed" "0.085"
+
+ "sway_min_roll_zoomed" "-1"
+ "sway_max_roll_zoomed" "1"
+ "sway_turn_left_rotate_roll_zoomed" "-1"
+ "sway_turn_right_rotate_roll_zoomed" "1"
+
+ "sway_move_right_rotate_roll_zoomed" "0.2"
+ "sway_move_left_rotate_roll_zoomed" "-0.2"
+
+ "sway_min_pitch_zoomed" "-0.01"
+ "sway_max_pitch_zoomed" "0.01"
+ "sway_turn_up_rotate_pitch_zoomed" "0.09"
+ "sway_turn_down_rotate_pitch_zoomed" "-0.09"
+
+ // NPC
+ "proficiency_poor_spreadscale" "7.0"
+ "proficiency_average_spreadscale" "5.0"
+ "proficiency_good_spreadscale" "3.33333"
+ "proficiency_very_good_spreadscale" "3.66667"
+
+ "npc_min_range" "0"
+ "npc_max_range" "1250"
+
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "npc_rest_time_between_bursts_min" "1.25"
+ "npc_rest_time_between_bursts_max" "1.75"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "bob_tilt_angle" "0.5"
+ "sway_turn_angle_factor" "-0.5"
+ "sway_turn_origin_factor" "0"
+ "sway_turn_angle_factor_zoomed" "0"
+ "sway_turn_origin_factor_zoomed" "0.05"
+ "sway_move_angle_factor" "0.15"
+ "sway_move_origin_factor" "0.15"
+ "sway_move_angle_factor_zoomed" "0"
+ "sway_move_origin_factor_zoomed" "0.03"
+ "sway_gain" "10.0"
+ "deployfirst_time" "1.0"
+ "deploycatch_time" "1.33"
+ "sprintcycle_time" ".55"
+ "sprint_fractional_anims" "0"
+
+ "projectile_lifetime" "0.5"
+ "projectile_damage_reduction_per_bounce" "0.0"
+ "projectile_damages_owner" "0"
+
+ // Crosshair
+ "red_crosshair_range" "300"
+ active_crosshair_count "0"
+
+ "ui6_enable" "1"
+ UiData6
+ {
+ "ui" "ui/b3wing_ammo_counter"
+ "mesh" "models/weapons/attachments/sniper_scope_rui_upper"
+ Args
+ {
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ clipCount weapon_stockpileClipCount
+ }
+ }
+ RUI_CrosshairData
+ {
+ DefaultArgs
+ {
+ adjustedSpread weapon_spread
+ isSprinting player_is_sprinting
+ isReloading weapon_is_reloading
+ teamColor crosshair_team_color
+ isAmped weapon_is_amped
+ chargeFrac player_chargeFrac
+ crosshairMovementX weapon_is_reloading
+ crosshairMovementY weapon_is_reloading
+ }
+
+ Crosshair_2
+ {
+ "ui" "ui/crosshair_lstar"
+ "base_spread" "4"
+ Args
+ {
+
+ }
+ }
+} \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/weapons/mp_weapon_sniper.txt b/Northstar.Custom/mod/scripts/weapons/mp_weapon_sniper.txt
new file mode 100644
index 00000000..ede43d7d
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/weapons/mp_weapon_sniper.txt
@@ -0,0 +1,651 @@
+WeaponData
+{
+ // General
+ "printname" "#WPN_SNIPER"
+ "shortprintname" "#WPN_SNIPER_SHORT"
+ "description" "#WPN_SNIPER_DESC"
+ "longdesc" "#WPN_SNIPER_LONGDESC"
+
+ "menu_icon" "r2_ui/menus/loadout_icons/primary_weapon/primary_kraber"
+ "hud_icon" "r2_ui/menus/loadout_icons/primary_weapon/primary_kraber"
+
+ "weaponClass" "human"
+ "weaponSubClass" "sniper"
+ "body_type" "heavy"
+ "fire_mode" "semi-auto"
+ "pickup_hold_prompt" "Hold [USE] [WEAPONNAME]"
+ "pickup_press_prompt" "[USE] [WEAPONNAME]"
+ "minimap_reveal_distance" "32000"
+ "leveled_pickup" "1"
+
+ "OnWeaponActivate" "OnWeaponActivate_weapon_sniper"
+ "OnClientAnimEvent" "OnClientAnimEvent_weapon_sniper"
+ "OnWeaponPrimaryAttack" "OnWeaponPrimaryAttack_weapon_sniper"
+ "OnWeaponNpcPrimaryAttack" "OnWeaponNpcPrimaryAttack_weapon_sniper"
+ "OnProjectileCollision" "OnProjectileCollision_weapon_sniper"
+
+ "viewmodel_offset_ads" "0 0 0"
+
+ // Menu
+ "menu_category" "sniper"
+ "menu_anim_class" "large"
+ "stat_damage" "100"
+ "stat_range" "100"
+ "stat_accuracy" "60"
+ "stat_rof" "15"
+
+ "ads_dof_disable" "1"
+
+ // Models
+ "viewmodel" "models/weapons/at_rifle/ptpov_at_rifle.mdl"
+ "playermodel" "models/weapons/at_rifle/w_at_rifle.mdl"
+ "projectilemodel" "models/weapons/bullets/projectile_20mm.mdl"
+
+ // Effects
+ "impact_effect_table" "titan_bullet"
+ "projectile_trail_effect_0" "weapon_kraber_projectile"
+ "projectile_do_predict_impact_effects" "1"//0"
+ "vortex_absorb_effect" "wpn_vortex_projectile_20mm_FP"
+ "vortex_absorb_effect_third_person" "wpn_vortex_projectile_20mm"
+ "vortex_absorb_sound" "Vortex_Shield_AbsorbBulletLarge"
+ "vortex_absorb_sound_1p_vs_3p" "Vortex_Shield_AbsorbBulletLarge_1P_VS_3P"
+ "projectile_adjust_to_gun_barrel" "1"
+
+ // Sounds
+ "sound_dryfire" "rifle_dryfire"
+ "sound_pickup" "wpn_pickup_Rifle_1P"
+ "sound_zoom_in" "Weapon_Rangemaster_Kraber_ADS_In"
+ "sound_zoom_out" "Weapon_Rangemaster_Kraber_ADS_Out"
+ "fire_sound_1_player_1p" "large_shell_drop"
+ "fire_sound_1_player_3p" "large_shell_drop"
+ "fire_sound_1_npc" "large_shell_drop"
+ "fire_sound_2_player_1p" "Weapon_Kraber_Fire_1P"
+ "fire_sound_2_player_3p" "Weapon_Kraber_Fire_3P"
+ "fire_sound_2_npc" "Weapon_Kraber_Fire_npc"
+
+ "low_ammo_sound_name_1" "Kraber_LowAmmo_Shot1"
+ "low_ammo_sound_name_2" "Kraber_LowAmmo_Shot2"
+ "low_ammo_sound_name_3" "Kraber_LowAmmo_Shot3"
+
+ "fx_shell_eject_view" "wpn_shelleject_rifle_large_FP"
+ "fx_shell_eject_world" "wpn_shelleject_rifle_large"
+ "fx_shell_eject_attach" "shell"
+ "fx_shell_eject_attach_scoped" "shell_scoped"
+
+ "fx_muzzle_flash_view" "wpn_muzzleflash_snp_hmn_FP"
+ "fx_muzzle_flash_world" "wpn_muzzleflash_snp_hmn"
+ "fx_muzzle_flash_attach" "muzzle_flash"
+ "fx_muzzle_flash_attach_scoped" "muzzle_flash_scoped"
+
+ // Bolt info
+ "bolt_hitsize" "0.0"
+ "bolt_hitsize_grow1_time" "0.035"
+ "bolt_hitsize_grow1_size" "0.5"
+ "bolt_hitsize_grow2_time" "0.08"
+ "bolt_hitsize_grow2_size" "1.0"
+ "bolt_hitsize_growfinal_lerptime" "0.18"
+ "bolt_hitsize_growfinal_size" "2.0"
+
+ "bolt_gravity_enabled" "1"
+ "bolt_gravity_amount" "0.2500"//0.500"
+
+ "bolt_bounce_frac" "0.000"
+ "projectile_damage_reduction_per_bounce" "0.0"
+ "projectile_damages_owner" "0"
+ "projectile_ricochet_max_count" "0"
+
+ "pass_through_depth" "64"
+ "pass_through_damage_preserved_scale" "1"
+
+ "bolt_speed" "10000"
+
+ // Damage - When Used by Players
+ "damage_flags" "DF_BULLET | DF_KNOCK_BACK | DF_DISMEMBERMENT"
+ "damage_type" "bullet"
+
+ "damage_headshot_scale" "1.5"
+
+ "impulse_force" "10"
+
+ "ammo_clip_size" "4"
+
+ "titanarmor_critical_hit_required" "1"
+ "critical_hit" "1"
+
+
+ MP_BASE
+ {
+ "ammo_default_total" "40"
+ "ammo_stockpile_max" "40"
+ "ammo_no_remove_from_stockpile" "1"
+ "ammo_min_to_fire" "1"
+
+
+ "damage_near_value" "350"
+ "damage_far_value" "350"
+ "damage_near_value_titanarmor" "430"
+ "damage_far_value_titanarmor" "430"
+ "damage_rodeo" "900"
+ "damage_near_distance" "2000"
+ "damage_far_distance" "15000"
+
+ "red_crosshair_range" "15000"
+
+ "critical_hit_damage_scale" "1"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_value" "49"
+ "npc_damage_far_value" "49"
+ "npc_damage_near_value_titanarmor" "50"
+ "npc_damage_far_value_titanarmor" "25"
+
+ "npc_accuracy_multiplier_pilot" "1.0"
+
+ "npc_suppress_lsp_allowed" "0"
+
+ "enable_highlight_networking_on_creation" "<KEEP_DEFAULT>"
+
+ "damage_heavyarmor_nontitan_scale" "0.35"
+
+ "zoom_toggle_lerp_time" "<KEEP_DEFAULT>"
+ "zoom_toggle_fov" "<KEEP_DEFAULT>"
+ }
+
+ SP_BASE
+ {
+ "zoom_toggle_lerp_time" "0.2"
+ "zoom_toggle_fov" "7"
+
+ "ammo_default_total" "12"
+ "ammo_stockpile_max" "28"
+ "ammo_no_remove_from_stockpile" "0"
+ "ammo_min_to_fire" "1"
+
+
+ "damage_near_value" "200"
+ "damage_far_value" "200"
+ "damage_near_value_titanarmor" "500"
+ "damage_far_value_titanarmor" "500"
+ "damage_rodeo" "900"
+ "damage_near_distance" "2000"
+ "damage_far_distance" "15000"
+
+ "red_crosshair_range" "15000"
+
+ "critical_hit_damage_scale" "1.5"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_value" "49"
+ "npc_damage_far_value" "49"
+ "npc_damage_near_value_titanarmor" "50"
+ "npc_damage_far_value_titanarmor" "25"
+
+ "npc_accuracy_multiplier_pilot" "3.0"
+
+ "npc_suppress_lsp_allowed" "<KEEP_DEFAULT>"
+
+ "enable_highlight_networking_on_creation" "1"
+
+ "damage_heavyarmor_nontitan_scale" "1"
+ }
+
+ // NPC
+ "proficiency_poor_spreadscale" "3.0"
+ "proficiency_average_spreadscale" "2.2"
+ "proficiency_good_spreadscale" "2.0"
+ "proficiency_very_good_spreadscale" "1.5"
+
+ "npc_min_engage_range" "500"
+ "npc_max_engage_range" "8000"
+ "npc_min_engage_range_heavy_armor" "500"
+ "npc_max_engage_range_heavy_armor" "8000"
+ "npc_min_range" "0"
+ "npc_max_range" "8000"
+
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "npc_rest_time_between_bursts_min" "2.0"
+ "npc_rest_time_between_bursts_max" "3.0"
+
+ "dof_zoom_nearDepthStart" "0"
+ "dof_zoom_nearDepthEnd" "0"
+ "dof_nearDepthStart" "4.750"
+ "dof_nearDepthEnd" "9"
+
+ // Behavior
+ "fire_rate" "1.85"
+ "zoom_time_in" "0.4"//0.48"//1.1"//1.25"//0.45"
+ "zoom_time_out" "0.3"
+ "zoom_fov" "26.26" //3x zoom
+ "zoom_scope_frac_start" "0.2"//0.2"
+ "zoom_scope_frac_end" "0.7"//0.85"
+ "zoom_angle_shift_pitch" "0.3"
+ "zoom_angle_shift_yaw" "0.65"
+ "rechamber_time" "1.60"//1.30"
+ "reload_time" "2.5"
+ "reload_time_late1" "1.52"
+ "reload_time_late2" "0.63"
+ "reloadempty_time" "3.61"
+ "reloadempty_time_late1" "2.63"
+ "reloadempty_time_late2" "1.75"
+ "reloadempty_time_late3" "0.55"
+ "holster_time" "0.5"
+ "deploy_time" "0.8"
+ "lower_time" "0.25"
+ "raise_time" "0.3"
+ "vortex_refire_behavior" "bullet"
+ "allow_empty_fire" "0"
+ "reload_enabled" "1"
+ "allow_empty_click" "1"
+ "empty_reload_only" "0"
+ "trigger_snipercam" "1"
+ "allow_headshots" "1"
+ "ads_move_speed_scale" "0.5"//0.35"
+ "aimassist_disable_hipfire" "1"
+ "aimassist_disable_ads" "1"
+ "aimassist_disable_hipfire_titansonly" "1"
+ "aimassist_disable_ads_titansonly" "1"
+ "gamepad_use_yaw_speed_for_pitch_ads" "1"
+ "ads_fov_zoomfrac_start" "0.5"
+ "ads_fov_zoomfrac_end" "0.9"
+
+ "sprint_fractional_anims" "0"
+
+ // Spread
+ "spread_stand_hip" "10"
+ "spread_stand_hip_run" "12"
+ "spread_stand_ads" "0.0" //"0.1"
+ "spread_crouch_hip" "8"
+ "spread_crouch_ads" "0"
+ "spread_air_hip" "12"
+ "spread_air_ads" "0.0" //".15"
+
+ "ammo_suck_behavior" "primary_weapons"
+
+ // View Kick
+ "viewkick_spring" "sniper"
+
+ "viewkick_pitch_base" "-0.5625"//"-1.25"//
+ "viewkick_pitch_random" "0.0225"//"0.05"//
+ "viewkick_pitch_softScale" "0.8"
+ "viewkick_pitch_hardScale" "2"
+
+ "viewkick_yaw_base" "-0.135"//"-0.3"//
+ "viewkick_yaw_random" "0.045"//"0.1"//
+ "viewkick_yaw_softScale" "1.0"
+ "viewkick_yaw_hardScale" "1.5"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.5"
+ "viewkick_roll_randomMax" "0.5"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "3.0"
+
+ "viewkick_hipfire_weaponFraction" "0.4"
+ "viewkick_hipfire_weaponFraction_vmScale" "0.55"
+ "viewkick_ads_weaponFraction" "0.0"//"0.45"//
+ "viewkick_ads_weaponFraction_vmScale" "0.0"//"-0.3"//
+
+ "viewkick_perm_pitch_base" "0.0"
+ "viewkick_perm_pitch_random" "0.0"
+ "viewkick_perm_pitch_random_innerexclude" "0.0"
+ "viewkick_perm_yaw_base" "0.0"
+ "viewkick_perm_yaw_random" "0.0"
+ "viewkick_perm_yaw_random_innerexclude" "0.0"
+
+ //
+ "viewmodel_shake_forward" "0.5"
+ "viewmodel_shake_up" "0.1"
+ "viewmodel_shake_right" "0.0"
+
+ // Bob
+ "bob_cycle_time" "0.4"
+ "bob_vert_dist" "0.19"
+ "bob_horz_dist" "0.1"
+ "bob_max_speed" "150"
+ "bob_pitch" "0.75"
+ "bob_yaw" "-1.7"
+ "bob_roll" "1.2"
+
+ // Bob_Zoomed
+ "bob_cycle_time_zoomed" "0.4"
+ "bob_vert_dist_zoomed" "0.0025"
+ "bob_horz_dist_zoomed" "0.0025"
+ "bob_max_speed_zoomed" "150"
+ //"bob_pitch_zoomed" "0.002"
+ //"bob_yaw_zoomed" "-.002"
+ //"bob_roll_zoomed" ".002"
+
+ // Rumble
+ "fire_rumble" "rumble_sniper"
+
+ // Sway
+ "sway_rotate_attach" "SWAY_ROTATE"
+ "sway_min_x" "-0.5"
+ "sway_min_y" "-0.5"
+ "sway_min_z" "-0.6"
+ "sway_max_x" "0.5"
+ "sway_max_y" "0.5"
+ "sway_max_z" "0.6"
+ "sway_min_pitch" "-3"
+ "sway_min_yaw" "-2.5"
+ "sway_min_roll" "-4"
+ "sway_max_pitch" "3"
+ "sway_max_yaw" "2.5"
+ "sway_max_roll" "4"
+ "sway_translate_gain" "2.5"
+ "sway_rotate_gain" "7"
+ "sway_move_forward_translate_x" "-0.1"
+ "sway_move_forward_translate_z" "-0.5"
+ "sway_move_back_translate_x" "0.2"
+ "sway_move_back_translate_z" "-0.2"
+ "sway_move_left_translate_y" "-1"
+ "sway_move_left_translate_z" "-0.5"
+ "sway_move_left_rotate_roll" "-4"
+ "sway_move_right_translate_y" "1"
+ "sway_move_right_translate_z" "-0.5"
+ "sway_move_right_rotate_roll" "4"
+ "sway_move_up_translate_z" "-1"
+ "sway_move_down_translate_z" "1"
+ "sway_turn_left_rotate_yaw" "-2.5"
+ "sway_turn_right_rotate_yaw" "2.5"
+
+ "sway_turn_left_translate_y" ".5"
+ "sway_turn_right_translate_y" "-.5"
+ "sway_turn_up_translate_z" ".2"
+ "sway_turn_down_translate_z" "-.2"
+ "sway_turn_up_translate_x" ".1"
+ "sway_turn_down_translate_x" "-.1"
+
+ "sway_turn_left_rotate_roll" "4"
+ "sway_turn_right_rotate_roll" "-4"
+ "sway_turn_up_rotate_pitch" "3"
+ "sway_turn_down_rotate_pitch" "-3"
+ "sway_turn_up_rotate_roll" "-0.8"
+ "sway_turn_down_rotate_roll" "0.8"
+
+ // Zoomed Sway
+ "sway_rotate_attach_zoomed" "jx_c_pov"
+ "sway_rotate_attach_blend_time_zoomed" "0.2"
+ "sway_rotate_gain_zoomed" "5"
+
+ "sway_min_yaw_zoomed" "-0.085"
+ "sway_max_yaw_zoomed" "0.085"
+ "sway_turn_left_rotate_yaw_zoomed" "0.085"
+ "sway_turn_right_rotate_yaw_zoomed" "-0.085"
+
+ "sway_min_roll_zoomed" "-1"
+ "sway_max_roll_zoomed" "1"
+ "sway_turn_left_rotate_roll_zoomed" "-1"
+ "sway_turn_right_rotate_roll_zoomed" "1"
+
+ "sway_move_right_rotate_roll_zoomed" "0.2"
+ "sway_move_left_rotate_roll_zoomed" "-0.2"
+
+ "sway_min_pitch_zoomed" "-0.25"
+ "sway_max_pitch_zoomed" "0.25"
+ "sway_turn_up_rotate_pitch_zoomed" "-0.25"
+ "sway_turn_down_rotate_pitch_zoomed" "0.25"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "sprintcycle_time" ".55"
+ "is_sniper" "1"
+
+ // Bodygroups:
+ "bodygroup1_name" "scope_dcom"
+ "bodygroup1_set" "1"
+ "bodygroup2_name" "scope_zoom"
+ "bodygroup2_set" "0"
+ "bodygroup3_name" "ammo"
+ "bodygroup3_set" "1"
+ "bodygroup4_name" "scope_outline"
+ "bodygroup4_set" "0"
+ "bodygroup5_name" "scope_oracle"
+ "bodygroup5_set" "0"
+ "bodygroup6_name" "proscreen"
+ "bodygroup6_set" "0"
+ // "bodygroup7_name" "suppressor_sq_lg"
+ // "bodygroup7_set" "0"
+
+ "bodygroup_ads_scope_name" "ads_scopes"
+ "bodygroup_ads_scope_set" "3"
+
+
+ "anim_alt_idleAttack" "0"
+
+ "clip_bodygroup" "at_rifle_magazine"
+ "clip_bodygroup_index_shown" "0"
+ "clip_bodygroup_index_hidden" "1"
+ "clip_bodygroup_show_for_milestone_0" "1"
+ "clip_bodygroup_show_for_milestone_1" "0"
+ "clip_bodygroup_show_for_milestone_2" "1"
+ "clip_bodygroup_show_for_milestone_3" "1"
+
+ "bodygroup_ammo_index_count" "6"
+
+ Mods
+ {
+ iron_sights
+ {
+
+ }
+ scope_4x
+ {
+ //Use this for Variable Zoom
+ "ui7_enable" "1"
+
+ "bodygroup1_set" "0"
+ "bodygroup2_set" "0"
+ "bodygroup4_set" "1"
+ "bodygroup5_set" "0"
+ "bodygroup_ads_scope_set" "2"
+ "zoom_toggle_lerp_time" "0.2"
+ "zoom_toggle_fov" "7"
+ //"ui6_enable" "1"
+ }
+ extended_ammo
+ {
+ "ammo_stockpile_max" "90"
+ "ammo_clip_size" "7"
+ "ammo_default_total" "90"
+ }
+ stabilizer
+ {
+ "bodygroup1_set" "0"
+ "bodygroup2_set" "0"
+ "bodygroup4_set" "0"
+ "bodygroup5_set" "1"
+ "bodygroup_ads_scope_set" "0"
+
+ // "viewdrift_ads_stand_scale_pitch" "*0.5"
+ // "viewdrift_ads_crouch_scale_pitch" "*0.5"
+ // "viewdrift_ads_air_scale_pitch" "*0.5"
+ // "viewdrift_ads_air_scale_yaw" "*0.5"
+ // "viewdrift_ads_stand_scale_yaw" "*0.5"
+ // "viewdrift_ads_crouch_scale_yaw" "*0.5"
+ // "viewdrift_ads_speed_pitch" "*0.5"
+ // "viewdrift_ads_speed_yaw" "*0.5"
+
+ "viewmodel_offset_ads" "0 -2.95 0.31"
+ //"viewmodel_offset_lerp_endFrac" "1"
+
+ "ads_fov_zoomfrac_start" "0.3"
+ "ads_fov_zoomfrac_end" "0.8"
+
+ "dof_zoom_nearDepthStart" "7.0"
+ "dof_zoom_nearDepthEnd" "7.2"
+
+ "anim_alt_idleAttack" "1"
+ }
+ ricochet
+ {
+ "bolt_bounce_frac" "0.7"
+ "projectile_damage_reduction_per_bounce" "0.0"
+ "projectile_damages_owner" "0"
+ "projectile_ricochet_max_count" "2"
+ }
+ slammer
+ {
+ }
+ threat_scope
+ {
+ "bodygroup1_set" "0"
+ "bodygroup2_set" "1"
+ "bodygroup4_set" "0"
+ "bodygroup5_set" "0"
+ "bodygroup_ads_scope_set" "1"
+
+ "threat_scope_enabled" "1"
+ "threat_scope_bounds_width" "1.5"
+ "threat_scope_bounds_height" "1.1"
+ "threat_scope_zoomfrac_start" "0.85"
+ "viewmodel_offset_ads" "0 0.5 0"
+ dof_zoom_focusArea_horizontal 0.068
+ dof_zoom_focusArea_top 0.065
+ dof_zoom_focusArea_bottom -0.046
+ }
+ pro_screen
+ {
+ "ui8_enable" "1"
+ "bodygroup6_set" "1"
+ }
+ tactical_cdr_on_kill
+ {
+
+ }
+ pas_fast_reload
+ {
+ "reload_time" "*0.7"
+ "reload_time_late1" "*0.7"
+ "reloadempty_time" "*0.7"
+ "reloadempty_time_late1" "*0.7"
+ }
+ pas_fast_ads
+ {
+ //Fast ADS
+ "zoom_time_in" "*0.5"
+ "zoom_time_out" "*0.6"
+ }
+ pas_fast_swap
+ {
+ //Fast Swap
+ "fast_swap_to" "1"
+ }
+ burn_mod_sniper
+ {
+ "is_burn_mod" "1"
+ //FX
+ "fx_muzzle_flash_view" "wpn_muzzleflash_snp_hmn_FP_burn"
+ "fx_muzzle_flash_world" "wpn_muzzleflash_snp_hmn_burn"
+ "projectile_trail_effect_0" "weapon_kraber_projectile_burn"
+ //"impact_effect_table" "titan_bullet_elec"
+
+ "damage_near_value" "450"
+ "damage_far_value" "450"
+ "damage_near_value_titanarmor" "580"
+ "damage_far_value_titanarmor" "580"
+
+ // reimplement tf1 amped kraber
+ "explosion_damage" "150"
+ "explosion_damage_heavy_armor" "150"
+ "explosion_inner_radius" "25"
+ "explosionradius" "75"
+ "explosion_shake_radius" "250"
+ "explosion_shake_amplitude" "10"
+ "explosion_shake_frequency" "50"
+ "explosion_shake_duration" "0.6"
+ "impact_effect_table" "exp_small"
+ }
+ pve_elite
+ {
+ //"ammo_stockpile_max" "90"
+ //"ammo_clip_size" "7"
+ //"ammo_default_total" "90"
+ //"reload_time" "0.1"
+ //"fire_rate" "0.1"
+ "npc_damage_near_value" "70"
+ "npc_damage_far_value" "70"
+ }
+ }
+
+ "ui1_enable" "1"
+ UiData1
+ {
+ "ui" "ui/kraber_ammo_counter"
+ "mesh" "models/weapons/attachments/kraber_rui_lower"
+ Args
+ {
+ vis player_zoomfrac
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ }
+ }
+
+ "ui6_enable" "0"
+ UiData6
+ {
+ "ui" "ui/red_dot_basic"
+ "mesh" "models/weapons/attachments/sniper_scope_rui_upper"
+ Args
+ {
+ vis player_zoomfrac
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ clipCount weapon_stockpileClipCount
+ }
+ }
+
+ "ui7_enable" "0"
+ UiData7
+ {
+ "ui" "ui/variable_zoom_crosshair"
+ "mesh" "models/weapons/attachments/attach_scope_ads_2_crosshair"
+ Args
+ {
+ vis player_zoomfrac
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ clipCount weapon_stockpileClipCount
+ }
+ }
+
+ "ui8_enable" "0"
+ UiData8
+ {
+ "ui" "ui/pro_screen_panel"
+ "mesh" "models/weapons/attachments/pro_screen_rui_upper"
+ Args
+ {
+ proValue proscreen_int0
+ proOwnedByPlayer proscreen_owner_is_player
+ }
+ }
+
+ active_crosshair_count "1"
+ rui_crosshair_index "0"
+
+ RUI_CrosshairData
+ {
+ DefaultArgs
+ {
+ adjustedSpread weapon_spread
+ adsFrac player_zoomFrac
+ isSprinting player_is_sprinting
+ isReloading weapon_is_reloading
+ teamColor crosshair_team_color
+ isAmped weapon_is_amped
+ crosshairMovementX crosshair_movement_x
+ crosshairMovementY crosshair_movement_y
+ }
+
+ Crosshair_1
+ {
+ "ui" "ui/crosshair_sniper_amped"
+ "base_spread" "10"
+ Args
+ {
+ isFiring weapon_is_firing
+ }
+ }
+ }
+}
diff --git a/Northstar.Custom/mod/scripts/weapons/mp_weapon_toolgun.txt b/Northstar.Custom/mod/scripts/weapons/mp_weapon_toolgun.txt
new file mode 100644
index 00000000..37e9bc25
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/weapons/mp_weapon_toolgun.txt
@@ -0,0 +1,587 @@
+WeaponData
+{
+ // General
+ "printname" "#WPN_TOOLGUN"
+ "shortprintname" "#WPN_TOOLGUN_SHORT"
+ "description" "#WPN_TOOLGUN_DESC"
+ "longdesc" "#WPN_TOOLGUN_LONGDESC"
+
+ "fast_swap_to" "1"
+
+ "menu_icon" "r2_ui/menus/loadout_icons/secondary_weapon/secondary_mozambique"
+ "hud_icon" "r2_ui/menus/loadout_icons/secondary_weapon/secondary_mozambique"
+
+ "weaponClass" "human"
+ "weaponSubClass" "projectile_shotgun"
+ "body_type" "close_quarters"
+ "fire_mode" "auto"
+ "pickup_hold_prompt" "Hold [USE] [WEAPONNAME]"
+ "pickup_press_prompt" "[USE] [WEAPONNAME]"
+ "minimap_reveal_distance" "32000"
+ "leveled_pickup" "1"
+
+ "OnWeaponActivate" "OnWeaponActivate_weapon_toolgun"
+ "OnWeaponDeactivate" "OnWeaponDeactivate_weapon_toolgun"
+ "OnWeaponPrimaryAttack" "OnWeaponPrimaryAttack_weapon_toolgun"
+ "OnWeaponStartZoomIn" "OnWeaponStartZoomIn_weapon_toolgun"
+ "OnWeaponStartZoomOut" "OnWeaponStartZoomOut_weapon_toolgun"
+ "OnWeaponNpcPrimaryAttack" "OnWeaponNpcPrimaryAttack_weapon_toolgun"
+
+ // Menu
+ "menu_category" "handgun"
+ "menu_anim_class" "small"
+ "stat_damage" "57"
+ "stat_range" "40"
+ "stat_accuracy" "44"
+ "stat_rof" "27"
+
+ // Models
+ "viewmodel" "models/weapons/pstl_sa3/ptpov_pstl_sa3.mdl"
+ "playermodel" "models/weapons/pstl_sa3/w_pstl_sa3.mdl"
+ "activitymodifier" "pistol"
+ "holster_type" "pistol"
+
+ // Effects
+ "tracer_effect" "weapon_tracers_shotgun"
+ "impact_effect_table" "bullet_mastiff"
+ "vortex_impact_effect" "P_impact_xo_shield_cp"
+ "vortex_absorb_effect" "wpn_vortex_projectile_shotgun_FP"
+ "vortex_absorb_effect_third_person" "wpn_vortex_projectile_shotgun"
+ "vortex_absorb_sound" "Vortex_Shield_AbsorbBulletSmall"
+ "vortex_absorb_sound_1P_VS_3P" "Vortex_Shield_AbsorbBulletSmall_1P_VS_3P"
+ "projectile_adjust_to_gun_barrel" "1"
+
+ "projectilemodel" "models/dev/empty_model.mdl"
+ //"projectile_trail_effect_0" "P_dragonsbreath_trail"
+ "projectile_trail_effect_0" "P_mastiff_proj"
+
+ "sound_dryfire" "shotgun_dryfire"
+ "sound_pickup" "wpn_pickup_Pistol_1P"
+ "fire_sound_1_player_1p" "weapon_shotgunpistol_fire_suppressed_1p"
+ "fire_sound_1_player_3p" "weapon_shotgunpistol_fire_suppressed_3p"
+ "fire_sound_1_npc" "Weapon_ShotgunPistol_Fire_NPC"
+ "sound_zoom_in" "Weapon_EVA8_ADS_In"
+ "sound_zoom_out" "Weapon_EVA8_ADS_Out"
+
+ "low_ammo_sound_name_1" "ShotgunPistol_LowAmmo_Shot1"
+ "low_ammo_sound_name_2" "ShotgunPistol_LowAmmo_Shot2"
+
+ //"fx_shell_eject_view" "wpn_shelleject_shotshell_FP"
+ //"fx_shell_eject_world" "wpn_shelleject_shotshell"
+ //"fx_shell_eject_attach" "shell"
+
+ "fx_muzzle_flash_view" "P_wpn_muz_mastiff_FP"
+ "fx_muzzle_flash_world" "P_wpn_muz_mastiff"
+ "fx_muzzle_flash_attach" "muzzle_flash"
+
+ "damage_flags" "DF_SHOTGUN | DF_BULLET | DF_KNOCK_BACK | DF_DISMEMBERMENT"
+ "damage_type" "bullet"
+ "damage_headshot_scale" "1.5"
+
+ "explosion_inner_radius" "16"
+ "explosionradius" "32"
+ "impulse_force" "0"
+
+
+ "critical_hit_damage_scale" "1"
+
+ "projectile_inherit_owner_velocity_scale" "0.0"
+
+ "ammo_clip_size" "6" // arbitrarily large number
+ "titanarmor_critical_hit_required" "1"
+
+
+
+ MP_BASE
+ {
+ "ammo_default_total" "6"
+ "ammo_stockpile_max" "6"
+ "ammo_no_remove_from_stockpile" "1"
+ "ammo_min_to_fire" "0"
+
+ "aimassist_adspull_weaponclass" "broad"
+
+ "critical_hit" "1"
+
+ // Damage - When Used by Players
+ "damage_near_value" "30"
+ "damage_far_value" "25"
+ "damage_near_value_titanarmor" "30"
+ "damage_far_value_titanarmor" "20"
+ // "explosion_damage" "50" // 150
+ // "explosion_damage_heavy_armor" "50" // 150
+ "damage_near_distance" "750"
+ "damage_far_distance" "1000"
+
+ "red_crosshair_range" "750"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_value" "11"
+ "npc_damage_far_value" "5"
+ "npc_damage_near_value_titanarmor" "<KEEP_DEFAULT>"
+ "npc_damage_far_value_titanarmor" "<KEEP_DEFAULT>"
+ "npc_damage_near_distance" "500"
+ "npc_damage_far_distance" "1000"
+
+ // NPC
+ "npc_min_engage_range" "0"
+ "npc_max_engage_range" "1000"
+ "npc_min_engage_range_heavy_armor" "100"
+ "npc_max_engage_range_heavy_armor" "2000"
+ "npc_min_range" "0"
+ "npc_max_range" "8000"
+
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "npc_rest_time_between_bursts_min" "0.5"
+ "npc_rest_time_between_bursts_max" "0.5"
+
+ "enable_highlight_networking_on_creation" "<KEEP_DEFAULT>"
+
+ "damage_heavyarmor_nontitan_scale" "0.35"
+ }
+
+ SP_BASE
+ {
+ "ammo_default_total" "20"
+ "ammo_stockpile_max" "32"
+ "ammo_no_remove_from_stockpile" "0"
+ "ammo_min_to_fire" "1"
+
+ "aimassist_adspull_weaponclass" "broad_sp"
+
+ "critical_hit" "0"
+
+ // Damage - When Used by Players
+ "damage_near_value" "30"
+ "damage_far_value" "20"
+ "damage_near_value_titanarmor" "30"
+ "damage_far_value_titanarmor" "20"
+ // "explosion_damage" "50"
+ // "explosion_damage_heavy_armor" "50"
+ "damage_near_distance" "750"
+ "damage_far_distance" "850"
+
+ "red_crosshair_range" "750"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_value" "11"
+ "npc_damage_far_value" "5"
+ "npc_damage_near_value_titanarmor" "10"
+ "npc_damage_far_value_titanarmor" "8"
+ "npc_damage_near_distance" "500"
+ "npc_damage_far_distance" "1000"
+
+ // NPC
+ "npc_min_engage_range" "0"
+ "npc_max_engage_range" "1000"
+ "npc_min_engage_range_heavy_armor" "100"
+ "npc_max_engage_range_heavy_armor" "2000"
+ "npc_min_range" "0"
+ "npc_max_range" "8000"
+
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "npc_rest_time_between_bursts_min" "0.7"
+ "npc_rest_time_between_bursts_max" "1.0"
+
+ "enable_highlight_networking_on_creation" "1"
+
+ "damage_heavyarmor_nontitan_scale" "1"
+ }
+
+ "proficiency_poor_spreadscale" "5.0"
+ "proficiency_average_spreadscale" "5.0"
+ "proficiency_good_spreadscale" "3.66667"
+ "proficiency_very_good_spreadscale" "3.66667"
+
+ "viewmodel_offset_ads" "0 0 0"
+ "dof_zoom_nearDepthStart" "4.750"
+ "dof_zoom_nearDepthEnd" "11.00"
+ "dof_nearDepthStart" "0"
+ "dof_nearDepthEnd" "0"
+
+ //"bolt_hitsize" "20"
+
+ "bolt_hitsize" "0.5"
+ "bolt_hitsize_grow1_time" "0.055"
+ "bolt_hitsize_grow1_size" "5.0"
+ "bolt_hitsize_grow2_time" "0.18"
+ "bolt_hitsize_grow2_size" "7.5"
+ "bolt_hitsize_growfinal_lerptime" "0.18"
+ "bolt_hitsize_growfinal_size" "7.5"
+
+ "bolt_gravity_enabled" "1"
+
+ // Behavior
+ "fire_rate" "3.0"
+ "zoom_time_in" "0.2"
+ "zoom_time_out" "0.15"
+ "zoom_fov" "55"
+ "reload_time" "2.1"
+ "reload_time_late1" "1.05"
+ "reloadempty_time" "2.1"
+ "reloadempty_time_late1" "1.05"
+ "holster_time" "0.3"
+ "deploy_time" "0.4"
+ "lower_time" "0.2"
+ "raise_time" "0.2"
+ "vortex_refire_behavior" "bullet"
+ "allow_empty_fire" "0"
+ "reload_enabled" "0"
+ "allow_empty_click" "1"
+ "empty_reload_only" "0"
+ "trigger_snipercam" "0"
+ "allow_headshots" "1"
+ "headshot_distance" "1400"
+ "primary_fire_does_not_block_sprint" "0"
+ "ads_move_speed_scale" "0.5"
+ "aimassist_disable_hipfire" "1"
+ "aimassist_disable_ads" "1"
+ "aimassist_disable_hipfire_titansonly" "0"
+ "aimassist_disable_ads_titansonly" "0"
+
+ "sprint_fractional_anims" "0"
+
+ // Spread
+ "spread_stand_hip" "0"
+ "spread_stand_hip_run" "0"
+ "spread_stand_hip_sprint" "0"
+ "spread_stand_ads" "0"
+ "spread_crouch_hip" "0"
+ "spread_crouch_ads" "0"
+ "spread_air_hip" "0"
+ "spread_air_ads" "0"
+
+ // Spread on NPCs affects their initial shooting direction
+ // Don't make this a large number or the damage/tracers won't be even remotely parallel to their barrel
+
+ "ammo_suck_behavior" "primary_weapons"
+
+ // View Kick
+ "viewkick_spring" "shotgun"
+
+ "viewkick_pitch_base" "-2.25"
+ "viewkick_pitch_random" "1"
+ "viewkick_pitch_softScale" "1.4"
+ "viewkick_pitch_hardScale" "0.5"
+
+ "viewkick_yaw_base" "-0.95"
+ "viewkick_yaw_random" "0.5"
+ "viewkick_yaw_softScale" "1.5"
+ "viewkick_yaw_hardScale" "0.5"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.6"
+ "viewkick_roll_randomMax" "0.8"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "2.75"
+
+ "viewkick_hipfire_weaponFraction" "0.1"
+ "viewkick_hipfire_weaponFraction_vmScale" "0.0"
+ "viewkick_ads_weaponFraction" "1.0"
+ "viewkick_ads_weaponFraction_vmScale" "0.15"
+
+ "viewkick_perm_pitch_base" "0"
+ "viewkick_perm_pitch_random" "0.0"
+
+ //
+ "viewmodel_shake_forward" "0.5"
+ "viewmodel_shake_up" "0.2"
+ "viewmodel_shake_right" "0.0"
+
+ // Bob
+ "bob_cycle_time" "0.4"
+ "bob_vert_dist" "0.19"
+ "bob_horz_dist" "0.1"
+ "bob_max_speed" "150"
+ "bob_pitch" "0.75"
+ "bob_yaw" "-1.7"
+ "bob_roll" "1.2"
+
+ // Bob_Zoomed
+ "bob_cycle_time_zoomed" "0.4"
+ "bob_vert_dist_zoomed" "0.01"
+ "bob_horz_dist_zoomed" "0.01"
+ "bob_max_speed_zoomed" "150"
+ //"bob_pitch_zoomed" "0.002"
+ //"bob_yaw_zoomed" "-.002"
+ //"bob_roll_zoomed" ".002"
+
+ // Rumble
+ "fire_rumble" "rumble_pistol_heavy"
+
+ // Sway
+ "sway_rotate_attach" "SWAY_ROTATE"
+ "sway_min_x" "-0.5"
+ "sway_min_y" "-0.5"
+ "sway_min_z" "-0.6"
+ "sway_max_x" "0.5"
+ "sway_max_y" "0.5"
+ "sway_max_z" "0.6"
+ "sway_min_pitch" "-3"
+ "sway_min_yaw" "-2.5"
+ "sway_min_roll" "-4"
+ "sway_max_pitch" "3"
+ "sway_max_yaw" "2.5"
+ "sway_max_roll" "4"
+ "sway_translate_gain" "2.5"
+ "sway_rotate_gain" "7"
+ "sway_move_forward_translate_x" "-0.1"
+ "sway_move_forward_translate_z" "-0.5"
+ "sway_move_back_translate_x" "0.2"
+ "sway_move_back_translate_z" "-0.2"
+ "sway_move_left_translate_y" "-1"
+ "sway_move_left_translate_z" "-0.5"
+ "sway_move_left_rotate_roll" "-4"
+ "sway_move_right_translate_y" "1"
+ "sway_move_right_translate_z" "-0.5"
+ "sway_move_right_rotate_roll" "4"
+ "sway_move_up_translate_z" "-1"
+ "sway_move_down_translate_z" "1"
+ "sway_turn_left_rotate_yaw" "-2.5"
+ "sway_turn_right_rotate_yaw" "2.5"
+
+ "sway_turn_left_translate_y" ".5"
+ "sway_turn_right_translate_y" "-.5"
+ "sway_turn_up_translate_z" ".2"
+ "sway_turn_down_translate_z" "-.2"
+ "sway_turn_up_translate_x" ".1"
+ "sway_turn_down_translate_x" "-.1"
+
+ "sway_turn_left_rotate_roll" "4"
+ "sway_turn_right_rotate_roll" "-4"
+ "sway_turn_up_rotate_pitch" "3"
+ "sway_turn_down_rotate_pitch" "-3"
+ "sway_turn_up_rotate_roll" "-0.8"
+ "sway_turn_down_rotate_roll" "0.8"
+
+ // Zoomed Sway
+ "sway_rotate_attach_zoomed" "SWAY_ROTATE_ZOOMED"
+ "sway_rotate_attach_blend_time_zoomed" "0.2"
+ "sway_rotate_gain_zoomed" "5"
+
+ "sway_min_yaw_zoomed" "-0.073"
+ "sway_max_yaw_zoomed" "0.073"
+ "sway_turn_left_rotate_yaw_zoomed" "-0.085"
+ "sway_turn_right_rotate_yaw_zoomed" "0.085"
+
+ "sway_min_roll_zoomed" "-4"
+ "sway_max_roll_zoomed" "4"
+ "sway_turn_left_rotate_roll_zoomed" "0"
+ "sway_turn_right_rotate_roll_zoomed" "0"
+
+ "sway_move_right_rotate_roll_zoomed" "0.2"
+ "sway_move_left_rotate_roll_zoomed" "-0.2"
+
+ "sway_min_pitch_zoomed" "-0.03"
+ "sway_max_pitch_zoomed" "0.03"
+ "sway_turn_up_rotate_pitch_zoomed" "0.07"
+ "sway_turn_down_rotate_pitch_zoomed" "-0.07"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "bob_tilt_angle" "0.5"
+ "sway_turn_angle_factor" "-0.5"
+ "sway_turn_origin_factor" "0"
+ "sway_turn_angle_factor_zoomed" "0"
+ "sway_turn_origin_factor_zoomed" "0.05"
+ "sway_move_angle_factor" "0.15"
+ "sway_move_origin_factor" "0.15"
+ "sway_move_angle_factor_zoomed" "0"
+ "sway_move_origin_factor_zoomed" "0.03"
+ "sway_gain" "10.0"
+ "deployfirst_time" "1.0"
+ "deploycatch_time" "1.33"
+ "sprintcycle_time" ".55"
+
+ // Bodygroups:
+ "bodygroup1_name" "suppressor_cyl_med"
+ "bodygroup1_set" "0"
+ "bodygroup2_name" "suppressor_sq_med"
+ "bodygroup2_set" "1" // enable suppressor bodygroup by default
+ "bodygroup3_name" "sight_cqh"
+ "bodygroup3_set" "0"
+ "bodygroup4_name" "sight_acgs"
+ "bodygroup4_set" "0"
+ "bodygroup5_name" "sight_cro"
+ "bodygroup5_set" "0"
+ "bodygroup6_name" "proscreen"
+ "bodygroup6_set" "1" // enable proscreen bodygroup by default
+
+ "clip_bodygroup" "pstl_sa3_shell"
+ "clip_bodygroup_index_shown" "0"
+ "clip_bodygroup_index_hidden" "1"
+ "clip_bodygroup_show_for_milestone_0" "1"
+ "clip_bodygroup_show_for_milestone_1" "0"
+ "clip_bodygroup_show_for_milestone_2" "0"
+ "clip_bodygroup_show_for_milestone_3" "0"
+
+ Mods
+ {
+ iron_sights
+ {
+ }
+ extended_ammo
+ {
+ "ammo_stockpile_max" "160"
+ "ammo_clip_size" "8"
+ "ammo_default_total" "160"
+ }
+ silencer //HACK JFS: Doesn't get applied on amped weapons. See bug 170460
+ {
+ "bodygroup2_set" "1"
+ "silenced" "1"
+ "fire_sound_1_player_1p" "weapon_shotgunpistol_fire_suppressed_1p"
+ "fire_sound_1_player_3p" "weapon_shotgunpistol_fire_suppressed_3p"
+ "damage_near_value" "--10"
+ "damage_far_value" "--4"
+ //"bodygroup2_set" "1"
+
+ //"rumble" "4"
+ //"tracer_effect" "P_wpn_tracer_pistol"
+ "minimap_reveal_distance" "1"
+
+ "fx_muzzle_flash_view" "wpn_muzzleflash_pistol_sup_FP"
+ "fx_muzzle_flash_world" "wpn_muzzleflash_pistol_sup"
+ //"fx_muzzle_flash_attach" "muzzle_flash_suppressor_sq"
+ }
+ pas_run_and_gun
+ {
+ "primary_fire_does_not_block_sprint" "1"
+ "crosshair_force_sprint_fade_disabled" "1"
+ }
+ alt_spread
+ {
+ }
+ hcog
+ {
+ "bodygroup3_set" "1"
+ "bodygroup4_set" "0"
+ "bodygroup5_set" "0"
+ "viewmodel_offset_ads" "0 -6 -0.79"
+ "dof_zoom_nearDepthStart" "0.750"
+ "dof_zoom_nearDepthEnd" "8.000"
+ }
+ threat_scope
+ {
+ "bodygroup3_set" "0"
+ "bodygroup4_set" "0"
+ "bodygroup5_set" "1"
+ "threat_scope_enabled" "1"
+ "threat_scope_bounds_tagname1" "SCR_TR_CRO"
+ "threat_scope_bounds_tagname2" "SCR_BL_CRO"
+ "viewmodel_offset_ads" "0 -10 -0.83"
+ "dof_zoom_nearDepthStart" "5.040"
+ "dof_zoom_nearDepthEnd" "5.737"
+ }
+ pro_screen
+ {
+ "ui8_enable" "1"
+ "bodygroup6_set" "1"
+ }
+ pas_fast_reload
+ {
+ "reload_time" "*0.7"
+ "reload_time_late1" "*0.7"
+ "reloadempty_time" "*0.7"
+ "reloadempty_time_late1" "*0.7"
+ }
+ pas_fast_ads
+ {
+ //Fast ADS
+ "zoom_time_in" "*0.5"
+ "zoom_time_out" "*0.6"
+ }
+ pas_fast_swap
+ {
+ //Fast Swap
+ "fast_swap_to" "1"
+ }
+ tactical_cdr_on_kill
+ {
+
+ }
+ burn_mod_shotgun_pistol
+ {
+ "damage_near_value" "++5"
+ "damage_far_value" "++10"
+ "damage_near_value_titanarmor" "++30"
+ "damage_far_value_titanarmor" "++30"
+ "is_burn_mod" "1"
+
+ //Effects
+ "fx_muzzle_flash_view" "P_wpn_muz_SGPistol_amp_FP"
+ "fx_muzzle_flash_world" "P_wpn_muz_mastiff_amp"
+ "projectile_trail_effect_0" "P_mastiff_proj_amp"
+ "impact_effect_table" "titan_bullet_elec"
+ }
+
+ }
+
+
+ "ui1_enable" "1"
+ "ui1_draw_cloaked" "1"
+ UiData1
+ {
+ "ui" "ui/sa3_crosshair"
+ "mesh" "models/weapons/attachments/sa3_rui_upper"
+ Args
+ {
+ vis player_zoomfrac
+ }
+ }
+
+ "ui2_enable" "1"
+ UiData2
+ {
+ "ui" "ui/sa3_ammo_counter"
+ "mesh" "models/weapons/attachments/sa3_rui_lower"
+ Args
+ {
+ vis player_zoomfrac
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ clipCount weapon_stockpileClipCount
+ }
+ }
+
+ "ui8_enable" "1"
+ UiData8
+ {
+ "ui" "ui/pro_screen_panel"
+ "mesh" "models/weapons/attachments/pro_screen_rui_upper"
+ Args
+ {
+ proValue proscreen_int0
+ proOwnedByPlayer proscreen_owner_is_player
+ }
+ }
+
+ active_crosshair_count "1"
+ rui_crosshair_index "0"
+
+ RUI_CrosshairData
+ {
+ DefaultArgs
+ {
+ adjustedSpread weapon_spread
+ adsFrac player_zoomFrac
+ isSprinting player_is_sprinting
+ isReloading weapon_is_reloading
+ teamColor crosshair_team_color
+ isAmped weapon_is_amped
+ crosshairMovementX crosshair_movement_x
+ crosshairMovementY crosshair_movement_y
+ }
+
+ Crosshair_1
+ {
+ "ui" "ui/crosshair_mozambique"
+ "base_spread" "0.0"
+ Args
+ {
+ isFiring weapon_is_firing
+ }
+ }
+ }
+}