aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers
diff options
context:
space:
mode:
authorx3Karma <juliuslimck@gmail.com>2021-12-30 14:22:31 +0800
committerGitHub <noreply@github.com>2021-12-30 14:22:31 +0800
commita0923c46ade1d30a9fb0949f9468255b9dd91c50 (patch)
treea47f6442a4ced0a2148c1cb16fb7a4b88994b96b /Northstar.CustomServers
parent0c3e9a7e4e99b506aa960348beb44eca1c0a02ed (diff)
downloadNorthstarMods-a0923c46ade1d30a9fb0949f9468255b9dd91c50.tar.gz
NorthstarMods-a0923c46ade1d30a9fb0949f9468255b9dd91c50.zip
fixes map hack issues
Diffstat (limited to 'Northstar.CustomServers')
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut874
1 files changed, 440 insertions, 434 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
index 3151a0f2c..3eb6bda6c 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
@@ -1,435 +1,441 @@
-untyped
-
-global function BurnMeter_Init
-global function ForceSetGlobalBurncardOverride
-global function GetSelectedBurncardRefFromWeaponOrPlayer
-global function RunBurnCardUseFunc
-global function UseBurnCardWeapon
-global function UseBurnCardWeaponInCriticalSection
-global function BurnMeter_GiveRewardDirect
-global function GetBurnCardWeaponSkin
-global function InitBurnMeterPersistentData
-
-const float PHASE_REWIND_LENGTH = 2.0
-// taken from wraith portal in apex, assuming it's the same as tf2's
-const float PHASE_REWIND_PATH_SNAPSHOT_INTERVAL = 0.1
-const int PHASE_REWIND_MAX_SNAPSHOTS = int( PHASE_REWIND_LENGTH / PHASE_REWIND_PATH_SNAPSHOT_INTERVAL )
-
-const float AMPED_WEAPONS_LENGTH = 30.0
-
-const int MAPHACK_PULSE_COUNT = 4
-const float MAPHACK_PULSE_DELAY = 2.0
-
-struct {
- string forcedGlobalBurncardOverride = ""
-} file
-
-void function BurnMeter_Init()
-{
- // turret precaches
- // do we have to cache these on client? release builds sure don't
- PrecacheModel( Dev_GetAISettingAssetByKeyField_Global( "npc_turret_sentry_burn_card_ap", "DefaultModelName" ) )
- PrecacheModel( Dev_GetAISettingAssetByKeyField_Global( "npc_turret_sentry_burn_card_at", "DefaultModelName" ) )
-
- // setup burncard use funcs
- BurnReward_GetByRef( "burnmeter_amped_weapons" ).rewardAvailableCallback = PlayerUsesAmpedWeaponsBurncard
- BurnReward_GetByRef( "burnmeter_smart_pistol" ).rewardAvailableCallback = PlayerUsesSmartPistolBurncard
- BurnReward_GetByRef( "burnmeter_emergency_battery" ).rewardAvailableCallback = PlayerUsesBatteryBurncard
- BurnReward_GetByRef( "burnmeter_radar_jammer" ).rewardAvailableCallback = PlayerUsesRadarJammerBurncard
- BurnReward_GetByRef( "burnmeter_maphack" ).rewardAvailableCallback = PlayerUsesMaphackBurncard
- BurnReward_GetByRef( "burnmeter_phase_rewind" ).rewardAvailableCallback = PlayerUsesPhaseRewindBurncard
-
- // these ones aren't so important, they're either for fd ( unsupported rn ) or unused
- //BurnReward_GetByRef( "burnmeter_harvester_shield" ).rewardAvailableCallback =
- BurnReward_GetByRef( "burnmeter_rodeo_grenade" ).rewardAvailableCallback = PlayerUsesRodeoGrenadeBurncard
- BurnReward_GetByRef( "burnmeter_nuke_titan" ).rewardAvailableCallback = PlayerUsesNukeTitanBurncard // unused in vanilla, fun though
-
- // setup player callbacks
- AddCallback_GameStateEnter( eGameState.Playing, InitBurncardsForIntroPlayers )
- AddCallback_OnClientConnected( InitBurncardsForLateJoiner )
-
- AddCallback_OnPlayerRespawned( StartPhaseRewindLifetime )
- AddCallback_OnTitanBecomesPilot( RemoveAmpedWeaponsForTitanPilot )
-
- // necessary signals
- RegisterSignal( "StopAmpedWeapons" )
-}
-
-void function ForceSetGlobalBurncardOverride( string ref )
-{
- file.forcedGlobalBurncardOverride = ref
-}
-
-string function GetSelectedBurncardRefFromWeaponOrPlayer( entity weapon, entity player )
-{
- // determine the burncard we're using
- // in actual gameplay, this will always be the player's selected burncard
- // however, if we want to manually give burncards and such, we want to make sure they'll still work
- // so some extra work goes into this
-
- string ref = GetSelectedBurnCardRef( player )
-
- if ( file.forcedGlobalBurncardOverride.len() > 0 )
- ref = file.forcedGlobalBurncardOverride
-
- if ( IsValid( weapon ) )
- {
- // determine via weapon mods, this assumes weapon mod names are the same as burn refs, which works in practice but is a bit weird
- // this does crash with the burnmeter_doublexp mod, but who cares, it doesn't get hit normally
- if ( weapon.GetWeaponClassName() == "mp_ability_burncardweapon" )
- {
- foreach ( string mod in weapon.GetMods() )
- if ( mod.find( "burnmeter_" ) == 0 )
- return mod
- }
- // determine via weapon name in the case of stuff like holopilot etc
- else
- {
- // unfortunately, we have to hardcode this, we don't have a way of getting refs directly from weapons other than the burncard weapon
- // this should be modular at some point, wish we could just iterate over burncards and find ones with the current weapon, but this isn't possible
- switch ( weapon.GetWeaponClassName() )
- {
- case "mp_ability_holopilot_nova":
- return "burnmeter_holopilot_nova"
-
- case "mp_weapon_arc_trap":
- return "burnmeter_arc_trap"
-
- case "mp_weapon_frag_drone":
- return "burnmeter_ticks"
-
- case "mp_weapon_hard_cover":
- return "burnmeter_hard_cover"
-
- case "mp_ability_turretweapon":
- // turret has 2 burncards, antititan and antipilot
- if( weapon.HasMod( "burnmeter_at_turret_weapon" ) || weapon.HasMod( "burnmeter_at_turret_weapon_inf" ) )
- return "burnmeter_at_turret_weapon"
- else
- return "burnmeter_ap_turret_weapon"
-
- // note: cloak and stim both have burn_card_weapon_mod mods, but they aren't used and don't call burncard code at all, likely for tf1 infinite stim/cloak burncards?
-
- default:
- print( "tried to use unknown burncard weapon " + weapon.GetWeaponClassName() )
- return "burnmeter_amped_weapons"
- }
- }
- }
-
- return ref
-}
-
-void function InitPlayerBurncards( entity player )
-{
- string ref = GetSelectedBurncardRefFromWeaponOrPlayer( null, player )
- BurnReward reward = BurnReward_GetByRef( ref )
- player.SetPlayerNetInt( TOP_INVENTORY_ITEM_BURN_CARD_ID, reward.id )
-
- if ( IsAlive( player ) )
- thread PhaseRewindLifetime( player )
-}
-
-void function InitBurncardsForIntroPlayers()
-{
- // gotta do this, since sh_burnmeter uses this netint
- foreach ( entity player in GetPlayerArray() )
- InitPlayerBurncards( player )
-}
-
-void function InitBurncardsForLateJoiner( entity player )
-{
- // gotta do this, since sh_burnmeter uses this netint
- if ( GetGameState() > eGameState.Prematch )
- InitPlayerBurncards( player )
-}
-
-void function StartPhaseRewindLifetime( entity player )
-{
- thread PhaseRewindLifetime( player )
-}
-
-void function PhaseRewindLifetime( entity player )
-{
- player.EndSignal( "OnDestroy" )
- player.EndSignal( "OnDeath" )
-
- OnThreadEnd( function() : ( player )
- {
- player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions.clear()
- })
-
- while ( true )
- {
- PhaseRewindData rewindData
- rewindData.origin = player.GetOrigin()
- rewindData.angles = player.GetAngles()
- rewindData.velocity = player.GetVelocity()
- rewindData.wasInContextAction = player.ContextAction_IsActive()
- rewindData.wasCrouched = player.IsCrouched()
-
- if ( player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions.len() >= PHASE_REWIND_MAX_SNAPSHOTS )
- {
- // shift all snapshots left
- for ( int i = 0; i < PHASE_REWIND_MAX_SNAPSHOTS - 1; i++ )
- player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ i ] = player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ i + 1 ]
-
- player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ PHASE_REWIND_MAX_SNAPSHOTS - 1 ] = rewindData
- }
- else
- player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions.append( rewindData )
-
- wait PHASE_REWIND_PATH_SNAPSHOT_INTERVAL
- }
-}
-
-void function RunBurnCardUseFunc( entity player, string itemRef )
-{
- void functionref( entity ) ornull func = BurnReward_GetByRef( itemRef ).rewardAvailableCallback
- if ( func != null )
- ( expect void functionref( entity ) ( func ) )( player )
-}
-
-void function UseBurnCardWeapon( entity weapon, entity player )
-{
- string ref = GetSelectedBurncardRefFromWeaponOrPlayer( weapon, player )
-
- Remote_CallFunction_Replay( player, "ServerCallback_RewardUsed", BurnReward_GetByRef( ref ).id )
- RunBurnCardUseFunc( player, ref )
-
- // dont remove in RunBurnCardUseFunc because it can be called in non-burn_card_weapon_mod contexts
- // TODO: currently not sure how burncards can be stacked ( max clipcount for all burncards is 1, so can't just set that )
- // if this gets figured out, add a conditional check here to prevent removes if they've got burncards left
- if ( PlayerEarnMeter_IsRewardAvailable( player ) )
- PlayerEarnMeter_SetRewardUsed( player )
-
- player.TakeWeapon( BurnReward_GetByRef( ref ).weaponName )
-}
-
-void function UseBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer )
-{
- // ignoring critical section stuff, assuming it was necessary in tf1 where burncards were part of inventory, but not here
- UseBurnCardWeapon( weapon, ownerPlayer )
-}
-
-void function BurnMeter_GiveRewardDirect( entity player, string itemRef )
-{
-
-}
-
-int function GetBurnCardWeaponSkin( entity weapon )
-{
- return GetBoostSkin( GetSelectedBurncardRefFromWeaponOrPlayer( weapon, weapon.GetOwner() ) )
-}
-
-// stub
-void function InitBurnMeterPersistentData( entity player )
-{}
-
-
-// burncard use funcs
-
-void function PlayerUsesAmpedWeaponsBurncard( entity player )
-{
- thread PlayerUsesAmpedWeaponsBurncardThreaded( player )
-}
-
-void function PlayerUsesAmpedWeaponsBurncardThreaded( entity player )
-{
- array<entity> weapons = player.GetMainWeapons()
- //weapons.extend( player.GetOffhandWeapons() ) // idk? unsure of vanilla behaviour here
- foreach ( entity weapon in weapons )
- {
- weapon.RemoveMod( "silencer" ) // both this and the burnmod will override firing fx, if a second one overrides this we crash
- foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
- {
- // catch incompatibilities just in case
- try
- {
- weapon.AddMod( mod )
- }
- catch( ex )
- {
- weapons.removebyvalue( weapon )
- }
- }
-
- // needed to display amped weapon time left
- weapon.SetScriptFlags0( weapon.GetScriptFlags0() | WEAPONFLAG_AMPED )
- weapon.SetScriptTime0( Time() + AMPED_WEAPONS_LENGTH )
- }
-
- wait AMPED_WEAPONS_LENGTH
-
- // note: weapons may have been destroyed or picked up by other people by this point, so need to verify this
- foreach ( entity weapon in weapons )
- {
- if ( !IsValid( weapon ) )
- continue
-
- foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
- weapon.RemoveMod( mod )
-
- weapon.SetScriptFlags0( weapon.GetScriptFlags0() & ~WEAPONFLAG_AMPED )
- }
-}
-
-void function RemoveAmpedWeaponsForTitanPilot( entity player, entity titan )
-{
- foreach ( entity weapon in player.GetMainWeapons() )
- foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
- weapon.RemoveMod( mod )
-}
-
-void function PlayerUsesSmartPistolBurncard( entity player )
-{
- // take secondary weapon
- array<entity> sidearms = player.GetMainWeapons()
- if ( sidearms.len() > 1 )
- player.TakeWeaponNow( sidearms[ 1 ].GetWeaponClassName() ) // take secondary weapon
-
- player.GiveWeapon( "mp_weapon_smart_pistol" )
- player.SetActiveWeaponByName( "mp_weapon_smart_pistol" )
-
- // do we need to track the player losing smart pistol, then give their old weapon back? idk not implementing for now, check later
-}
-
-void function PlayerUsesBatteryBurncard( entity player )
-{
- Rodeo_GiveBatteryToPlayer( player )
-}
-
-void function PlayerUsesRadarJammerBurncard( entity player )
-{
- foreach ( entity otherPlayer in GetPlayerArray() )
- {
- MessageToPlayer( otherPlayer, eEventNotifications.BurnMeter_RadarJammerUsed, player )
-
- if ( otherPlayer.GetTeam() != player.GetTeam() )
- StatusEffect_AddTimed( otherPlayer, eStatusEffect.minimap_jammed, 1.0, RADAR_JAM_TIME, RADAR_JAM_TIME )
- }
-}
-
-void function PlayerUsesMaphackBurncard( entity player )
-{
- thread PlayerUsesMaphackBurncardThreaded( player )
-}
-
-void function PlayerUsesMaphackBurncardThreaded( entity player )
-{
- player.EndSignal( "OnDestroy" )
- player.EndSignal( "OnDeath" )
-
- // todo: potentially look into ScanMinimap in _passives for doing this better? boost is pretty likely based off it pretty heavily
- for ( int i = 0; i < MAPHACK_PULSE_COUNT; i++ )
- {
- EmitSoundOnEntityOnlyToPlayer( player, player, "Burn_Card_Map_Hack_Radar_Pulse_V1_1P" )
-
- foreach ( entity otherPlayer in GetPlayerArray() )
- {
- Remote_CallFunction_Replay( otherPlayer, "ServerCallback_SonarPulseFromPosition", player.GetOrigin().x, player.GetOrigin().y, player.GetOrigin().z, SONAR_GRENADE_RADIUS )
-
- if ( otherPlayer.GetTeam() != player.GetTeam() )
- {
- StatusEffect_AddTimed( player, eStatusEffect.maphack_detected, 1.0, MAPHACK_PULSE_DELAY / 2, 0.0 )
- SonarStart( otherPlayer, player.GetOrigin(), player.GetTeam(), player )
- }
- }
-
- wait MAPHACK_PULSE_DELAY
- }
-}
-
-void function PlayerUsesPhaseRewindBurncard( entity player )
-{
- thread PlayerUsesPhaseRewindBurncardThreaded( player )
-}
-
-void function PlayerUsesPhaseRewindBurncardThreaded( entity player )
-{
- player.EndSignal( "OnDestroy" )
- player.EndSignal( "OnDeath" )
-
- entity mover = CreateScriptMover( player.GetOrigin(), player.GetAngles() )
- player.SetParent( mover, "REF" )
-
- OnThreadEnd( function() : ( player, mover )
- {
- CancelPhaseShift( player )
- player.DeployWeapon()
- player.SetPredictionEnabled( true )
- player.ClearParent()
- ViewConeFree( player )
- mover.Destroy()
- })
-
- array<PhaseRewindData> positions = clone player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions
-
- ViewConeZero( player )
- player.HolsterWeapon()
- player.SetPredictionEnabled( false )
- PhaseShift( player, 0.0, positions.len() * PHASE_REWIND_PATH_SNAPSHOT_INTERVAL * 1.5 )
-
- for ( int i = positions.len() - 1; i > -1; i-- )
- {
- mover.NonPhysicsMoveTo( positions[ i ].origin, PHASE_REWIND_PATH_SNAPSHOT_INTERVAL, 0, 0 )
- mover.NonPhysicsRotateTo( positions[ i ].angles, PHASE_REWIND_PATH_SNAPSHOT_INTERVAL, 0, 0 )
- wait PHASE_REWIND_PATH_SNAPSHOT_INTERVAL
- }
-
- // this isn't vanilla but it's cool lol, should prolly remove it tho
- player.SetVelocity( -positions[ positions.len() - 1 ].velocity )
-}
-
-void function PlayerUsesNukeTitanBurncard( entity player )
-{
- thread PlayerUsesNukeBurncardThreaded( player )
-}
-
-void function PlayerUsesNukeBurncardThreaded( entity player )
-{
- // if this is given manually ( i.e. not the equipped burnreward in inventory ), this will run at bad times
- // so do this check here, yes, this will cause people to lose their cards and get nothing, but better than free titan regens
- if ( !BurnMeterPlayer_CanUseReward( player, BurnReward_GetByRef( "burnmeter_nuke_titan" ) ) )
- return
-
- float ownedFrac = PlayerEarnMeter_GetOwnedFrac( player )
-
- // use player's titan loadout, but with warpfall so faster and no dome
- TitanLoadoutDef titanLoadout = GetTitanLoadoutForPlayer( player )
- titanLoadout.passive3 = "pas_warpfall"
-
- thread CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) )
-
- entity titan = player.GetPetTitan()
- SetTeam( titan, TEAM_UNASSIGNED ) // make it so you can kill yourself lol
- DoomTitan( titan )
- NPC_SetNuclearPayload( titan )
- // this should get run after the vanilla set_usable's event, so titan is never embarkable
- // embarking a titan in this state WILL kill the server so uhh, pretty bad
- AddAnimEvent( titan, "set_usable", void function( entity titan ) { titan.UnsetUsable() } )
-
- titan.WaitSignal( "TitanHotDropComplete" )
- AutoTitan_SelfDestruct( titan )
-
- while ( PlayerEarnMeter_GetMode( player ) == eEarnMeterMode.PET )
- WaitFrame()
-
- // restore original earnmeter values, no way to set earned that's exposed unfortunately
- PlayerEarnMeter_SetOwnedFrac( player, ownedFrac )
-}
-
-void function PlayerUsesRodeoGrenadeBurncard( entity player )
-{
- player.SetPlayerNetInt( "numSuperRodeoGrenades", player.GetPlayerNetInt( "numSuperRodeoGrenades" ) + 1 )
-}
-
-// unused burncard that's mentioned in a few areas and has a validiation function in sh_burnmeter ( BurnMeter_SummonReaperCanUse ), thought it'd be neat to add it
-void function PlayerUsesReaperfallBurncard( entity player )
-{
- Point spawnpoint = GetTitanReplacementPoint( player, false )
- entity reaper = CreateSuperSpectre( player.GetTeam(), spawnpoint.origin, spawnpoint.angles )
- DispatchSpawn( reaper )
-
- thread SuperSpectre_WarpFall( reaper )
+untyped
+
+global function BurnMeter_Init
+global function ForceSetGlobalBurncardOverride
+global function GetSelectedBurncardRefFromWeaponOrPlayer
+global function RunBurnCardUseFunc
+global function UseBurnCardWeapon
+global function UseBurnCardWeaponInCriticalSection
+global function BurnMeter_GiveRewardDirect
+global function GetBurnCardWeaponSkin
+global function InitBurnMeterPersistentData
+
+const float PHASE_REWIND_LENGTH = 2.0
+// taken from wraith portal in apex, assuming it's the same as tf2's
+const float PHASE_REWIND_PATH_SNAPSHOT_INTERVAL = 0.1
+const int PHASE_REWIND_MAX_SNAPSHOTS = int( PHASE_REWIND_LENGTH / PHASE_REWIND_PATH_SNAPSHOT_INTERVAL )
+
+const float AMPED_WEAPONS_LENGTH = 30.0
+
+const int MAPHACK_PULSE_COUNT = 4
+const float MAPHACK_PULSE_DELAY = 2.0
+
+struct {
+ string forcedGlobalBurncardOverride = ""
+} file
+
+void function BurnMeter_Init()
+{
+ // turret precaches
+ // do we have to cache these on client? release builds sure don't
+ PrecacheModel( Dev_GetAISettingAssetByKeyField_Global( "npc_turret_sentry_burn_card_ap", "DefaultModelName" ) )
+ PrecacheModel( Dev_GetAISettingAssetByKeyField_Global( "npc_turret_sentry_burn_card_at", "DefaultModelName" ) )
+
+ // setup burncard use funcs
+ BurnReward_GetByRef( "burnmeter_amped_weapons" ).rewardAvailableCallback = PlayerUsesAmpedWeaponsBurncard
+ BurnReward_GetByRef( "burnmeter_smart_pistol" ).rewardAvailableCallback = PlayerUsesSmartPistolBurncard
+ BurnReward_GetByRef( "burnmeter_emergency_battery" ).rewardAvailableCallback = PlayerUsesBatteryBurncard
+ BurnReward_GetByRef( "burnmeter_radar_jammer" ).rewardAvailableCallback = PlayerUsesRadarJammerBurncard
+ BurnReward_GetByRef( "burnmeter_maphack" ).rewardAvailableCallback = PlayerUsesMaphackBurncard
+ BurnReward_GetByRef( "burnmeter_phase_rewind" ).rewardAvailableCallback = PlayerUsesPhaseRewindBurncard
+
+ // these ones aren't so important, they're either for fd ( unsupported rn ) or unused
+ //BurnReward_GetByRef( "burnmeter_harvester_shield" ).rewardAvailableCallback =
+ BurnReward_GetByRef( "burnmeter_rodeo_grenade" ).rewardAvailableCallback = PlayerUsesRodeoGrenadeBurncard
+ BurnReward_GetByRef( "burnmeter_nuke_titan" ).rewardAvailableCallback = PlayerUsesNukeTitanBurncard // unused in vanilla, fun though
+
+ // setup player callbacks
+ AddCallback_GameStateEnter( eGameState.Playing, InitBurncardsForIntroPlayers )
+ AddCallback_OnClientConnected( InitBurncardsForLateJoiner )
+
+ AddCallback_OnPlayerRespawned( StartPhaseRewindLifetime )
+ AddCallback_OnTitanBecomesPilot( RemoveAmpedWeaponsForTitanPilot )
+
+ // necessary signals
+ RegisterSignal( "StopAmpedWeapons" )
+}
+
+void function ForceSetGlobalBurncardOverride( string ref )
+{
+ file.forcedGlobalBurncardOverride = ref
+}
+
+string function GetSelectedBurncardRefFromWeaponOrPlayer( entity weapon, entity player )
+{
+ // determine the burncard we're using
+ // in actual gameplay, this will always be the player's selected burncard
+ // however, if we want to manually give burncards and such, we want to make sure they'll still work
+ // so some extra work goes into this
+
+ string ref = GetSelectedBurnCardRef( player )
+
+ if ( file.forcedGlobalBurncardOverride.len() > 0 )
+ ref = file.forcedGlobalBurncardOverride
+
+ if ( IsValid( weapon ) )
+ {
+ // determine via weapon mods, this assumes weapon mod names are the same as burn refs, which works in practice but is a bit weird
+ // this does crash with the burnmeter_doublexp mod, but who cares, it doesn't get hit normally
+ if ( weapon.GetWeaponClassName() == "mp_ability_burncardweapon" )
+ {
+ foreach ( string mod in weapon.GetMods() )
+ if ( mod.find( "burnmeter_" ) == 0 )
+ return mod
+ }
+ // determine via weapon name in the case of stuff like holopilot etc
+ else
+ {
+ // unfortunately, we have to hardcode this, we don't have a way of getting refs directly from weapons other than the burncard weapon
+ // this should be modular at some point, wish we could just iterate over burncards and find ones with the current weapon, but this isn't possible
+ switch ( weapon.GetWeaponClassName() )
+ {
+ case "mp_ability_holopilot_nova":
+ return "burnmeter_holopilot_nova"
+
+ case "mp_weapon_arc_trap":
+ return "burnmeter_arc_trap"
+
+ case "mp_weapon_frag_drone":
+ return "burnmeter_ticks"
+
+ case "mp_weapon_hard_cover":
+ return "burnmeter_hard_cover"
+
+ case "mp_ability_turretweapon":
+ // turret has 2 burncards, antititan and antipilot
+ if( weapon.HasMod( "burnmeter_at_turret_weapon" ) || weapon.HasMod( "burnmeter_at_turret_weapon_inf" ) )
+ return "burnmeter_at_turret_weapon"
+ else
+ return "burnmeter_ap_turret_weapon"
+
+ // note: cloak and stim both have burn_card_weapon_mod mods, but they aren't used and don't call burncard code at all, likely for tf1 infinite stim/cloak burncards?
+
+ default:
+ print( "tried to use unknown burncard weapon " + weapon.GetWeaponClassName() )
+ return "burnmeter_amped_weapons"
+ }
+ }
+ }
+
+ return ref
+}
+
+void function InitPlayerBurncards( entity player )
+{
+ string ref = GetSelectedBurncardRefFromWeaponOrPlayer( null, player )
+ BurnReward reward = BurnReward_GetByRef( ref )
+ player.SetPlayerNetInt( TOP_INVENTORY_ITEM_BURN_CARD_ID, reward.id )
+
+ if ( IsAlive( player ) )
+ thread PhaseRewindLifetime( player )
+}
+
+void function InitBurncardsForIntroPlayers()
+{
+ // gotta do this, since sh_burnmeter uses this netint
+ foreach ( entity player in GetPlayerArray() )
+ InitPlayerBurncards( player )
+}
+
+void function InitBurncardsForLateJoiner( entity player )
+{
+ // gotta do this, since sh_burnmeter uses this netint
+ if ( GetGameState() > eGameState.Prematch )
+ InitPlayerBurncards( player )
+}
+
+void function StartPhaseRewindLifetime( entity player )
+{
+ thread PhaseRewindLifetime( player )
+}
+
+void function PhaseRewindLifetime( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+
+ OnThreadEnd( function() : ( player )
+ {
+ player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions.clear()
+ })
+
+ while ( true )
+ {
+ PhaseRewindData rewindData
+ rewindData.origin = player.GetOrigin()
+ rewindData.angles = player.GetAngles()
+ rewindData.velocity = player.GetVelocity()
+ rewindData.wasInContextAction = player.ContextAction_IsActive()
+ rewindData.wasCrouched = player.IsCrouched()
+
+ if ( player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions.len() >= PHASE_REWIND_MAX_SNAPSHOTS )
+ {
+ // shift all snapshots left
+ for ( int i = 0; i < PHASE_REWIND_MAX_SNAPSHOTS - 1; i++ )
+ player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ i ] = player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ i + 1 ]
+
+ player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ PHASE_REWIND_MAX_SNAPSHOTS - 1 ] = rewindData
+ }
+ else
+ player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions.append( rewindData )
+
+ wait PHASE_REWIND_PATH_SNAPSHOT_INTERVAL
+ }
+}
+
+void function RunBurnCardUseFunc( entity player, string itemRef )
+{
+ void functionref( entity ) ornull func = BurnReward_GetByRef( itemRef ).rewardAvailableCallback
+ if ( func != null )
+ ( expect void functionref( entity ) ( func ) )( player )
+}
+
+void function UseBurnCardWeapon( entity weapon, entity player )
+{
+ string ref = GetSelectedBurncardRefFromWeaponOrPlayer( weapon, player )
+
+ Remote_CallFunction_Replay( player, "ServerCallback_RewardUsed", BurnReward_GetByRef( ref ).id )
+ RunBurnCardUseFunc( player, ref )
+
+ // dont remove in RunBurnCardUseFunc because it can be called in non-burn_card_weapon_mod contexts
+ // TODO: currently not sure how burncards can be stacked ( max clipcount for all burncards is 1, so can't just set that )
+ // if this gets figured out, add a conditional check here to prevent removes if they've got burncards left
+ if ( PlayerEarnMeter_IsRewardAvailable( player ) )
+ PlayerEarnMeter_SetRewardUsed( player )
+
+ player.TakeWeapon( BurnReward_GetByRef( ref ).weaponName )
+}
+
+void function UseBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer )
+{
+ // ignoring critical section stuff, assuming it was necessary in tf1 where burncards were part of inventory, but not here
+ UseBurnCardWeapon( weapon, ownerPlayer )
+}
+
+void function BurnMeter_GiveRewardDirect( entity player, string itemRef )
+{
+
+}
+
+int function GetBurnCardWeaponSkin( entity weapon )
+{
+ return GetBoostSkin( GetSelectedBurncardRefFromWeaponOrPlayer( weapon, weapon.GetOwner() ) )
+}
+
+// stub
+void function InitBurnMeterPersistentData( entity player )
+{}
+
+
+// burncard use funcs
+
+void function PlayerUsesAmpedWeaponsBurncard( entity player )
+{
+ thread PlayerUsesAmpedWeaponsBurncardThreaded( player )
+}
+
+void function PlayerUsesAmpedWeaponsBurncardThreaded( entity player )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ //weapons.extend( player.GetOffhandWeapons() ) // idk? unsure of vanilla behaviour here
+ foreach ( entity weapon in weapons )
+ {
+ weapon.RemoveMod( "silencer" ) // both this and the burnmod will override firing fx, if a second one overrides this we crash
+ foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
+ {
+ // catch incompatibilities just in case
+ try
+ {
+ weapon.AddMod( mod )
+ }
+ catch( ex )
+ {
+ weapons.removebyvalue( weapon )
+ }
+ }
+
+ // needed to display amped weapon time left
+ weapon.SetScriptFlags0( weapon.GetScriptFlags0() | WEAPONFLAG_AMPED )
+ weapon.SetScriptTime0( Time() + AMPED_WEAPONS_LENGTH )
+ }
+
+ wait AMPED_WEAPONS_LENGTH
+
+ // note: weapons may have been destroyed or picked up by other people by this point, so need to verify this
+ foreach ( entity weapon in weapons )
+ {
+ if ( !IsValid( weapon ) )
+ continue
+
+ foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
+ weapon.RemoveMod( mod )
+
+ weapon.SetScriptFlags0( weapon.GetScriptFlags0() & ~WEAPONFLAG_AMPED )
+ }
+}
+
+void function RemoveAmpedWeaponsForTitanPilot( entity player, entity titan )
+{
+ foreach ( entity weapon in player.GetMainWeapons() )
+ foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
+ weapon.RemoveMod( mod )
+}
+
+void function PlayerUsesSmartPistolBurncard( entity player )
+{
+ // take secondary weapon
+ array<entity> sidearms = player.GetMainWeapons()
+ if ( sidearms.len() > 1 )
+ player.TakeWeaponNow( sidearms[ 1 ].GetWeaponClassName() ) // take secondary weapon
+
+ player.GiveWeapon( "mp_weapon_smart_pistol" )
+ player.SetActiveWeaponByName( "mp_weapon_smart_pistol" )
+
+ // do we need to track the player losing smart pistol, then give their old weapon back? idk not implementing for now, check later
+}
+
+void function PlayerUsesBatteryBurncard( entity player )
+{
+ Rodeo_GiveBatteryToPlayer( player )
+}
+
+void function PlayerUsesRadarJammerBurncard( entity player )
+{
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ {
+ MessageToPlayer( otherPlayer, eEventNotifications.BurnMeter_RadarJammerUsed, player )
+
+ if ( otherPlayer.GetTeam() != player.GetTeam() )
+ StatusEffect_AddTimed( otherPlayer, eStatusEffect.minimap_jammed, 1.0, RADAR_JAM_TIME, RADAR_JAM_TIME )
+ }
+}
+
+void function PlayerUsesMaphackBurncard( entity player )
+{
+ thread PlayerUsesMaphackBurncardThreaded( player )
+}
+
+void function PlayerUsesMaphackBurncardThreaded( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+
+ // todo: potentially look into ScanMinimap in _passives for doing this better? boost is pretty likely based off it pretty heavily
+ for ( int i = 0; i < MAPHACK_PULSE_COUNT; i++ )
+ {
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Burn_Card_Map_Hack_Radar_Pulse_V1_1P" )
+ array<entity> aliveplayers = GetPlayerArray()
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ {
+ Remote_CallFunction_Replay( otherPlayer, "ServerCallback_SonarPulseFromPosition", player.GetOrigin().x, player.GetOrigin().y, player.GetOrigin().z, SONAR_GRENADE_RADIUS )
+
+ if ( otherPlayer.GetTeam() != player.GetTeam() && aliveplayers.find(otherPlayer) != -1 && aliveplayers.find(player) != -1 )
+ {
+ StatusEffect_AddTimed( otherPlayer, eStatusEffect.maphack_detected, 1.0, MAPHACK_PULSE_DELAY / 2, 0.0 )
+ SonarStart( otherPlayer, player.GetOrigin(), player.GetTeam(), player )
+ IncrementSonarPerTeam( player.GetTeam() )
+ }
+ }
+ wait MAPHACK_PULSE_DELAY
+ foreach ( entity otherPlayer in GetPlayerArray() ) {
+ if ( otherPlayer.GetTeam() != player.GetTeam() && aliveplayers.find(otherPlayer) != -1 && aliveplayers.find(player) != -1 ) {
+ SonarEnd (otherPlayer, player.GetTeam() )
+ DecrementSonarPerTeam( player.GetTeam() )
+ }
+ }
+ }
+}
+
+void function PlayerUsesPhaseRewindBurncard( entity player )
+{
+ thread PlayerUsesPhaseRewindBurncardThreaded( player )
+}
+
+void function PlayerUsesPhaseRewindBurncardThreaded( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+
+ entity mover = CreateScriptMover( player.GetOrigin(), player.GetAngles() )
+ player.SetParent( mover, "REF" )
+
+ OnThreadEnd( function() : ( player, mover )
+ {
+ CancelPhaseShift( player )
+ player.DeployWeapon()
+ player.SetPredictionEnabled( true )
+ player.ClearParent()
+ ViewConeFree( player )
+ mover.Destroy()
+ })
+
+ array<PhaseRewindData> positions = clone player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions
+
+ ViewConeZero( player )
+ player.HolsterWeapon()
+ player.SetPredictionEnabled( false )
+ PhaseShift( player, 0.0, positions.len() * PHASE_REWIND_PATH_SNAPSHOT_INTERVAL * 1.5 )
+
+ for ( int i = positions.len() - 1; i > -1; i-- )
+ {
+ mover.NonPhysicsMoveTo( positions[ i ].origin, PHASE_REWIND_PATH_SNAPSHOT_INTERVAL, 0, 0 )
+ mover.NonPhysicsRotateTo( positions[ i ].angles, PHASE_REWIND_PATH_SNAPSHOT_INTERVAL, 0, 0 )
+ wait PHASE_REWIND_PATH_SNAPSHOT_INTERVAL
+ }
+
+ // this isn't vanilla but it's cool lol, should prolly remove it tho
+ player.SetVelocity( -positions[ positions.len() - 1 ].velocity )
+}
+
+void function PlayerUsesNukeTitanBurncard( entity player )
+{
+ thread PlayerUsesNukeBurncardThreaded( player )
+}
+
+void function PlayerUsesNukeBurncardThreaded( entity player )
+{
+ // if this is given manually ( i.e. not the equipped burnreward in inventory ), this will run at bad times
+ // so do this check here, yes, this will cause people to lose their cards and get nothing, but better than free titan regens
+ if ( !BurnMeterPlayer_CanUseReward( player, BurnReward_GetByRef( "burnmeter_nuke_titan" ) ) )
+ return
+
+ float ownedFrac = PlayerEarnMeter_GetOwnedFrac( player )
+
+ // use player's titan loadout, but with warpfall so faster and no dome
+ TitanLoadoutDef titanLoadout = GetTitanLoadoutForPlayer( player )
+ titanLoadout.passive3 = "pas_warpfall"
+
+ thread CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) )
+
+ entity titan = player.GetPetTitan()
+ SetTeam( titan, TEAM_UNASSIGNED ) // make it so you can kill yourself lol
+ DoomTitan( titan )
+ NPC_SetNuclearPayload( titan )
+ // this should get run after the vanilla set_usable's event, so titan is never embarkable
+ // embarking a titan in this state WILL kill the server so uhh, pretty bad
+ AddAnimEvent( titan, "set_usable", void function( entity titan ) { titan.UnsetUsable() } )
+
+ titan.WaitSignal( "TitanHotDropComplete" )
+ AutoTitan_SelfDestruct( titan )
+
+ while ( PlayerEarnMeter_GetMode( player ) == eEarnMeterMode.PET )
+ WaitFrame()
+
+ // restore original earnmeter values, no way to set earned that's exposed unfortunately
+ PlayerEarnMeter_SetOwnedFrac( player, ownedFrac )
+}
+
+void function PlayerUsesRodeoGrenadeBurncard( entity player )
+{
+ player.SetPlayerNetInt( "numSuperRodeoGrenades", player.GetPlayerNetInt( "numSuperRodeoGrenades" ) + 1 )
+}
+
+// unused burncard that's mentioned in a few areas and has a validiation function in sh_burnmeter ( BurnMeter_SummonReaperCanUse ), thought it'd be neat to add it
+void function PlayerUsesReaperfallBurncard( entity player )
+{
+ Point spawnpoint = GetTitanReplacementPoint( player, false )
+ entity reaper = CreateSuperSpectre( player.GetTeam(), spawnpoint.origin, spawnpoint.angles )
+ DispatchSpawn( reaper )
+
+ thread SuperSpectre_WarpFall( reaper )
} \ No newline at end of file