From a0923c46ade1d30a9fb0949f9468255b9dd91c50 Mon Sep 17 00:00:00 2001 From: x3Karma Date: Thu, 30 Dec 2021 14:22:31 +0800 Subject: fixes map hack issues --- .../mod/scripts/vscripts/burnmeter/_burnmeter.gnut | 874 +++++++++++---------- 1 file 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 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 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 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 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 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 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 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 -- cgit v1.2.3