aboutsummaryrefslogtreecommitdiff
path: root/Northstar.Custom/mod/scripts/vscripts/burnmeter
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.Custom/mod/scripts/vscripts/burnmeter')
-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
2 files changed, 1250 insertions, 0 deletions
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