From 6b07fb30fb7380b1d61af6a620ec72c9066753e2 Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Sat, 16 Oct 2021 01:27:00 +0100 Subject: add burncard support --- .../mod/scripts/vscripts/burnmeter/_burnmeter.gnut | 270 ++++++++++++++++++++- .../mod/scripts/vscripts/mp/_base_gametype_mp.gnut | 2 +- 2 files changed, 263 insertions(+), 9 deletions(-) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut index 8e1cb71ff..fa6867b8a 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut @@ -1,19 +1,175 @@ +untyped + global function BurnMeter_Init -global function InitBurnMeterPersistentData -global function BurnMeter_GiveRewardDirect 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 InitBurnMeterPersistentData(entity 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 ) @@ -21,22 +177,120 @@ void function BurnMeter_GiveRewardDirect( entity player, string itemRef ) } -void function RunBurnCardUseFunc(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 UseBurnCardWeapon( entity weapon, entity ownerPlayer ) +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 UseBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer ) +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 ) } -int function GetBurnCardWeaponSkin(entity weapon) +void function PlayerUsesRodeoGrenadeBurncard( entity player ) { - return 0 + player.SetPlayerNetInt( "numSuperRodeoGrenades", 7player.GetPlayerNetInt( "numSuperRodeoGrenades" ) + 1 ) } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut index 38803e04c..c42899e3f 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut @@ -286,7 +286,7 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga // do some pre-replay stuff if we're gonna do a replay float replayLength = CalculateLengthOfKillReplay( player, methodOfDeath ) - bool shouldDoReplay = Replay_IsEnabled() && KillcamsEnabled() && ShouldDoReplay( player, attacker, replayLength, methodOfDeath ) + bool shouldDoReplay = Replay_IsEnabled() && KillcamsEnabled() && IsValid( attacker ) && ShouldDoReplay( player, attacker, replayLength, methodOfDeath ) table replayTracker = { validTime = null } if ( shouldDoReplay ) thread TrackDestroyTimeForReplay( attacker, replayTracker ) -- cgit v1.2.3