untyped global function BurnMeter_Init global function RunBurnCardUseFunc global function UseBurnCardWeapon global function UseBurnCardWeaponInCriticalSection global function BurnMeter_GiveRewardDirect global function GetBurnCardWeaponSkin global function InitBurnMeterPersistentData // taken from wraith portal in apex, assuming it's the same as tf2's const float PHASE_REWIND_PATH_SNAPSHOT_INTERVAL = 0.1 const float AMPED_WEAPONS_LENGTH = 30.0 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 = //BurnReward_GetByRef( "burnmeter_phase_rewind" ).rewardAvailableCallback = // these ones aren't so important BurnReward_GetByRef( "burnmeter_nuke_titan" ).rewardAvailableCallback = PlayerUsesNukeTitanBurncard // unused in vanilla, fun though //BurnReward_GetByRef( "burnmeter_harvester_shield" ).rewardAvailableCallback = BurnReward_GetByRef( "burnmeter_rodeo_grenade" ).rewardAvailableCallback = PlayerUsesRodeoGrenadeBurncard // setup player callbacks AddCallback_GameStateEnter( eGameState.Playing, InitBurncardsForIntroPlayers ) AddCallback_OnClientConnected( InitBurncardsForLateJoiner ) AddCallback_OnPlayerRespawned( StartPhaseRewindLifetime ) } void function InitPlayerBurncards( entity player ) { int burnmeterSlot = player.GetPersistentVarAsInt( "burnmeterSlot" ) player.SetPlayerNetInt( TOP_INVENTORY_ITEM_BURN_CARD_ID, burnmeterSlot ) if ( BurnReward_GetById( burnmeterSlot ).ref == "burnmeter_phase_rewind" ) { player.s.hasPhaseRewind <- true if ( IsAlive( player ) ) thread PhaseRewindLifetime( player ) } else player.s.hasPhaseRewind <- false } 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 ) { if ( "hasPhaseRewind" in player.s && player.s.hasPhaseRewind ) thread PhaseRewindLifetime( player ) } void function PhaseRewindLifetime( entity player ) { player.EndSignal( "OnDestroy" ) player.EndSignal( "OnDeath" ) OnThreadEnd( function() : ( player ) { }) while ( true ) { 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 ) } string function GetBurncardRefFromWeaponOrPlayer( 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 ) // determine via weapon mods, this assumes weapon mod names are the same as burn refs, which works in practice but is a bit weird // though, this does crash with the burnmeter_doublexp mod 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, likely for infinite stim/cloak burncards? default: print( "tried to use unknown burncard weapon " + weapon.GetWeaponClassName() ) return "burnmeter_amped_weapons" // shouldn't hit this } } return ref } void function UseBurnCardWeapon( entity weapon, entity player ) { string ref = GetBurncardRefFromWeaponOrPlayer( 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 player.TakeOffhandWeapon( 4 ) } void function UseBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer ) { // ignoring critical section stuff, assuming it was necessary in tf1 where burncards were part of inventory UseBurnCardWeapon( weapon, ownerPlayer ) } void function BurnMeter_GiveRewardDirect( entity player, string itemRef ) { } int function GetBurnCardWeaponSkin( entity weapon ) { return GetBoostSkin( GetBurncardRefFromWeaponOrPlayer( 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 weapons = player.GetMainWeapons() //weapons.extend( player.GetOffhandWeapons() ) // idk? unsure of vanilla behaviour here foreach ( entity weapon in weapons ) { foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) ) weapon.AddMod( mod ) // 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 PlayerUsesSmartPistolBurncard( entity player ) { // take secondary weapon array 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 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", 7player.GetPlayerNetInt( "numSuperRodeoGrenades" ) + 1 ) }