diff options
author | Masterliberty <94194459+Masterliberty@users.noreply.github.com> | 2023-07-16 17:08:43 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-16 23:08:43 +0200 |
commit | 4c5cd0bf73b4672b0a5682aa2039fa16d7341893 (patch) | |
tree | c9ac4283929489af49f09a1a8f485888c9e6300d /Northstar.Custom/mod/scripts/vscripts | |
parent | 6cd9b2312bca0f4568c47568962a0601f36c8fd7 (diff) | |
download | NorthstarMods-4c5cd0bf73b4672b0a5682aa2039fa16d7341893.tar.gz NorthstarMods-4c5cd0bf73b4672b0a5682aa2039fa16d7341893.zip |
Add Arc cannon (#600)v1.17.0-rc1
Adds the Arc Cannon from Titanfall1
Diffstat (limited to 'Northstar.Custom/mod/scripts/vscripts')
3 files changed, 729 insertions, 580 deletions
diff --git a/Northstar.Custom/mod/scripts/vscripts/sh_northstar_custom_precache.gnut b/Northstar.Custom/mod/scripts/vscripts/sh_northstar_custom_precache.gnut index 79e64684..b8d4b1ba 100644 --- a/Northstar.Custom/mod/scripts/vscripts/sh_northstar_custom_precache.gnut +++ b/Northstar.Custom/mod/scripts/vscripts/sh_northstar_custom_precache.gnut @@ -4,6 +4,7 @@ void function NorthstarCustomPrecache() { PrecacheWeapon( "mp_weapon_peacekraber" ) PrecacheWeapon( "mp_titanweapon_triplethreat" ) + PrecacheWeapon( "mp_titanweapon_arc_cannon" ) PrecacheWeapon( "melee_pilot_kunai" ) RegisterWeaponDamageSources( diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut index 4268422e..cd58ef06 100644 --- a/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut +++ b/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut @@ -1,257 +1,218 @@ +untyped + +global function ArcCannon_Init + +global function ArcCannon_PrecacheFX +global function ArcCannon_Start +global function ArcCannon_Stop +global function ArcCannon_ChargeBegin +global function ArcCannon_ChargeEnd +global function FireArcCannon +global function ArcCannon_HideIdleEffect +#if SERVER + global function AddToArcCannonTargets + global function RemoveArcCannonTarget + global function ConvertTitanShieldIntoBonusCharge +#endif +global function GetArcCannonChargeFraction + +global function IsEntANeutralMegaTurret +global function CreateArcCannonBeam + // Aiming & Range -const DEFAULT_ARC_CANNON_FOVDOT = 0.98 // First target must be within this dot to be zapped and start a chain -const DEFAULT_ARC_CANNON_FOVDOT_MISSILE = 0.95 // First target must be within this dot to be zapped and start a chain ( if it's a missile, we allow more leaniency ) -const ARC_CANNON_RANGE_CHAIN = 400 // Max distance we can arc from one target to another -const ARC_CANNON_TITAN_RANGE_CHAIN = 900 // Max distance we can arc from one target to another -const ARC_CANNON_CHAIN_COUNT_MIN = 5 // Max number of chains at no charge -const ARC_CANNON_CHAIN_COUNT_MAX = 5 // Max number of chains at full charge -const ARC_CANNON_CHAIN_COUNT_NPC = 2 // Number of chains when an NPC fires the weapon -const ARC_CANNON_FORK_COUNT_MAX = 1 // Number of forks that can come out of one target to other targets -const ARC_CANNON_FORK_DELAY = 0.1 - -const ARC_CANNON_RANGE_CHAIN_BURN = 400 -const ARC_CANNON_TITAN_RANGE_CHAIN_BURN = 900 -const ARC_CANNON_CHAIN_COUNT_MIN_BURN = 100 // Max number of chains at no charge -const ARC_CANNON_CHAIN_COUNT_MAX_BURN = 100 // Max number of chains at full charge -const ARC_CANNON_CHAIN_COUNT_NPC_BURN = 10 // Number of chains when an NPC fires the weapon -const ARC_CANNON_FORK_COUNT_MAX_BURN = 10 // Number of forks that can come out of one target to other targets -const ARC_CANNON_BEAM_LIFETIME_BURN = 1 +global const DEFAULT_ARC_CANNON_FOVDOT = 0.98 // First target must be within this dot to be zapped and start a chain +global const DEFAULT_ARC_CANNON_FOVDOT_MISSILE = 0.95 // First target must be within this dot to be zapped and start a chain ( if it's a missile, we allow more leaniency ) +global const ARC_CANNON_RANGE_CHAIN = 400 // Max distance we can arc from one target to another +global const ARC_CANNON_TITAN_RANGE_CHAIN = 900 // Max distance we can arc from one target to another +global const ARC_CANNON_CHAIN_COUNT_MIN = 5 // Max number of chains at no charge +global const ARC_CANNON_CHAIN_COUNT_MAX = 5 // Max number of chains at full charge +global const ARC_CANNON_CHAIN_COUNT_NPC = 2 // Number of chains when an NPC fires the weapon +global const ARC_CANNON_FORK_COUNT_MAX = 1 // Number of forks that can come out of one target to other targets +global const ARC_CANNON_FORK_DELAY = 0.1 + +global const ARC_CANNON_RANGE_CHAIN_BURN = 400 +global const ARC_CANNON_TITAN_RANGE_CHAIN_BURN = 900 +global const ARC_CANNON_CHAIN_COUNT_MIN_BURN = 100 // Max number of chains at no charge +global const ARC_CANNON_CHAIN_COUNT_MAX_BURN = 100 // Max number of chains at full charge +global const ARC_CANNON_CHAIN_COUNT_NPC_BURN = 10 // Number of chains when an NPC fires the weapon +global const ARC_CANNON_FORK_COUNT_MAX_BURN = 10 // Number of forks that can come out of one target to other targets +global const ARC_CANNON_BEAM_LIFETIME_BURN = 1 // Visual settings -const ARC_CANNON_BOLT_RADIUS_MIN = 32 // Bolt radius at no charge ( not actually sure what this does to the beam lol ) -const ARC_CANNON_BOLT_RADIUS_MAX = 640 // Bold radius at full charge ( not actually sure what this does to the beam lol ) -const ARC_CANNON_BOLT_WIDTH_MIN = 1 // Bolt width at no charge -const ARC_CANNON_BOLT_WIDTH_MAX = 26 // Bolt width at full charge -const ARC_CANNON_BOLT_WIDTH_NPC = 8 // Bolt width when used by NPC -const ARC_CANNON_BEAM_COLOR = "150 190 255" -const ARC_CANNON_BEAM_LIFETIME = 0.75 +global const ARC_CANNON_BOLT_RADIUS_MIN = 32 // Bolt radius at no charge ( not actually sure what this does to the beam lol ) +global const ARC_CANNON_BOLT_RADIUS_MAX = 640 // Bold radius at full charge ( not actually sure what this does to the beam lol ) +global const ARC_CANNON_BOLT_WIDTH_MIN = 1 // Bolt width at no charge +global const ARC_CANNON_BOLT_WIDTH_MAX = 26 // Bolt width at full charge +global const ARC_CANNON_BOLT_WIDTH_NPC = 8 // Bolt width when used by NPC +global const ARC_CANNON_BEAM_COLOR = "150 190 255" +global const ARC_CANNON_BEAM_LIFETIME = 0.75 // Player Effects -const ARC_CANNON_TITAN_SCREEN_SFX = "Weapon_R1_LaserMine.Activate" -const ARC_CANNON_PILOT_SCREEN_SFX = "Weapon_R1_LaserMine.Activate" -const ARC_CANNON_EMP_DURATION_MIN = 0.1 -const ARC_CANNON_EMP_DURATION_MAX = 1.8 -const ARC_CANNON_EMP_FADEOUT_DURATION = 0.4 -const ARC_CANNON_SCREEN_EFFECTS_MIN = 0.025 -const ARC_CANNON_SCREEN_EFFECTS_MAX = 0.075 -const ARC_CANNON_SCREEN_THRESHOLD = 0.3385 -const ARC_CANNON_SLOW_SCALE_MIN = 0.8 -const ARC_CANNON_SLOW_SCALE_MAX = 0.7 -const ARC_CANNON_3RD_PERSON_EFFECT_MIN_DURATION = 0.2 - -// Rumble -const ARC_CANNON_RUMBLE_CHARGE_MIN = 5 -const ARC_CANNON_RUMBLE_CHARGE_MAX = 50 -const ARC_CANNON_RUMBLE_TYPE_INDEX = 14 // These are defined in code, 14 = RUMBLE_FLAT_BOTH +global const ARC_CANNON_TITAN_SCREEN_SFX = "Null_Remove_SoundHook" +global const ARC_CANNON_PILOT_SCREEN_SFX = "Null_Remove_SoundHook" +global const ARC_CANNON_EMP_DURATION_MIN = 0.1 +global const ARC_CANNON_EMP_DURATION_MAX = 1.8 +global const ARC_CANNON_EMP_FADEOUT_DURATION = 0.4 +global const ARC_CANNON_SCREEN_EFFECTS_MIN = 0.01 +global const ARC_CANNON_SCREEN_EFFECTS_MAX = 0.02 +global const ARC_CANNON_SCREEN_THRESHOLD = 0.3385 +global const ARC_CANNON_3RD_PERSON_EFFECT_MIN_DURATION = 0.2 // Damage -const ARC_CANNON_DAMAGE_FALLOFF_SCALER = 0.75 // Amount of damage carried on to the next target in the chain lightning. If 0.75, then a target that would normally take 100 damage will take 75 damage if they are one chain deep, or 56 damage if 2 levels deep -const ARC_CANNON_DAMAGE_CHARGE_RATIO = 0.85 // What amount of charge is required for full damage. -const ARC_CANNON_DAMAGE_CHARGE_RATIO_BURN = 0.676 // What amount of charge is required for full damage. -const ARC_CANNON_CAPACITOR_CHARGE_RATIO = 1.0 +global const ARC_CANNON_DAMAGE_FALLOFF_SCALER = 0.75 // Amount of damage carried on to the next target in the chain lightning. If 0.75, then a target that would normally take 100 damage will take 75 damage if they are one chain deep, or 56 damage if 2 levels deep +global const ARC_CANNON_DAMAGE_CHARGE_RATIO = 0.85 // What amount of charge is required for full damage. +global const ARC_CANNON_DAMAGE_CHARGE_RATIO_BURN = 0.676 // What amount of charge is required for full damage. +global const ARC_CANNON_CAPACITOR_CHARGE_RATIO = 1.0 // Options -const ARC_CANNON_TARGETS_MISSILES = 1 // 1 = arc cannon zaps missiles that are active, 0 = missiles are ignored by arc cannon +global const ARC_CANNON_TARGETS_MISSILES = 1 // 1 = arc cannon zaps missiles that are active, 0 = missiles are ignored by arc cannon //Mods -const OVERCHARGE_MAX_SHIELD_DECAY = 0.2 -const OVERCHARGE_SHIELD_DECAY_MULTIPLIER = 0.04 -const OVERCHARGE_BONUS_CHARGE_FRACTION = 0.05 - -const SPLITTER_DAMAGE_FALLOFF_SCALER = 0.6 -const SPLITTER_FORK_COUNT_MAX = 10 - -const ARC_CANNON_SIGNAL_DEACTIVATED = "ArcCannonDeactivated" -RegisterSignal( ARC_CANNON_SIGNAL_DEACTIVATED ) - -const ARC_CANNON_SIGNAL_CHARGEEND = "ArcCannonChargeEnd" -RegisterSignal( ARC_CANNON_SIGNAL_CHARGEEND ) - -const ARC_CANNON_BEAM_EFFECT = "wpn_arc_cannon_beam" -PrecacheParticleSystem( ARC_CANNON_BEAM_EFFECT ) - -const ARC_CANNON_BEAM_EFFECT_MOD = "wpn_arc_cannon_beam_mod" -PrecacheParticleSystem( ARC_CANNON_BEAM_EFFECT_MOD ) +global const OVERCHARGE_MAX_SHIELD_DECAY = 0.2 +global const OVERCHARGE_SHIELD_DECAY_MULTIPLIER = 0.04 +global const OVERCHARGE_BONUS_CHARGE_FRACTION = 0.05 + +global const SPLITTER_DAMAGE_FALLOFF_SCALER = 0.6 +global const SPLITTER_FORK_COUNT_MAX = 10 + +global const ARC_CANNON_SIGNAL_DEACTIVATED = "ArcCannonDeactivated" +global const ARC_CANNON_SIGNAL_CHARGEEND = "ArcCannonChargeEnd" + +global const ARC_CANNON_BEAM_EFFECT = $"wpn_arc_cannon_beam" +global const ARC_CANNON_BEAM_EFFECT_MOD = $"wpn_arc_cannon_beam_mod" + +global const ARC_CANNON_FX_TABLE = "exp_arc_cannon" + +global const ArcCannonTargetClassnames = { + [ "npc_drone" ] = true, + [ "npc_dropship" ] = true, + [ "npc_marvin" ] = true, + [ "npc_prowler" ] = true, + [ "npc_soldier" ] = true, + [ "npc_soldier_heavy" ] = true, + [ "npc_soldier_shield" ] = true, + [ "npc_spectre" ] = true, + [ "npc_stalker" ] = true, + [ "npc_super_spectre" ] = true, + [ "npc_titan" ] = true, + [ "npc_turret_floor" ] = true, + [ "npc_turret_mega" ] = true, + [ "npc_turret_sentry" ] = true, + [ "npc_frag_drone" ] = true, + [ "player" ] = true, + [ "prop_dynamic" ] = true, + [ "prop_script" ] = true, + [ "grenade_frag" ] = true, + [ "rpg_missile" ] = true, + [ "script_mover" ] = true, + [ "turret" ] = true, +} -const ARC_CANNON_FX_TABLE = "exp_arc_cannon" -PrecacheImpactEffectTable( ARC_CANNON_FX_TABLE ) +struct { + array<string> missileCheckTargetnames = [ + // "Arc Pylon", + "Arc Ball" + ] +} file; -if ( !reloadingScripts ) +function ArcCannon_Init() { - // Valid Arc Cannon Target Classnames - level.arcCannonTargetClassnames <- {} - level.arcCannonTargetClassnames[ "npc_turret_floor" ] <- true - level.arcCannonTargetClassnames[ "npc_spectre" ] <- true - level.arcCannonTargetClassnames[ "npc_soldier_shield" ] <- true - level.arcCannonTargetClassnames[ "npc_soldier_heavy" ] <- true - level.arcCannonTargetClassnames[ "npc_soldier" ] <- true - level.arcCannonTargetClassnames[ "npc_cscanner" ] <- true - level.arcCannonTargetClassnames[ "npc_titan" ] <- true - level.arcCannonTargetClassnames[ "npc_marvin" ] <- true - level.arcCannonTargetClassnames[ "player" ] <- true - level.arcCannonTargetClassnames[ "script_mover" ] <- true - level.arcCannonTargetClassnames[ "npc_grenade_frag" ] <- true - level.arcCannonTargetClassnames[ "rpg_missile" ] <- true - level.arcCannonTargetClassnames[ "npc_turret_mega" ] <- true - level.arcCannonTargetClassnames[ "npc_turret_sentry" ] <- true - level.arcCannonTargetClassnames[ "npc_dropship" ] <- true - level.arcCannonTargetClassnames[ "prop_dynamic" ] <- true -} + RegisterSignal( ARC_CANNON_SIGNAL_DEACTIVATED ) + RegisterSignal( ARC_CANNON_SIGNAL_CHARGEEND ) + PrecacheParticleSystem( ARC_CANNON_BEAM_EFFECT ) + PrecacheParticleSystem( ARC_CANNON_BEAM_EFFECT_MOD ) + PrecacheImpactEffectTable( ARC_CANNON_FX_TABLE ) -function main() -{ - Globalize( ArcCannon_PrecacheFX ) - Globalize( ArcCannon_Start ) - Globalize( ArcCannon_Stop ) - Globalize( ArcCannon_ChargeBegin ) - Globalize( ArcCannon_ChargeEnd ) - Globalize( FireArcCannon ) - Globalize( ArcCannon_HideIdleEffect ) - Globalize( AddToArcCannonTargets ) - Globalize( ConvertTitanShieldIntoBonusCharge ) - Globalize( GetArcCannonChargeFraction ) - Globalize( StopChargeEffects ) - - if( IsClient() ) - { + #if CLIENT AddDestroyCallback( "mp_titanweapon_arc_cannon", ClientDestroyCallback_ArcCannon_Stop ) - } - else - { + #else level._arcCannonTargetsArrayID <- CreateScriptManagedEntArray() - } + #endif - PrecacheParticleSystem( "impact_arc_cannon_titan" ) + PrecacheParticleSystem( $"impact_arc_cannon_titan" ) } -function ArcCannon_PrecacheFX( weapon ) +function ArcCannon_PrecacheFX() { - if ( WeaponIsPrecached( weapon ) ) - return - - PrecacheParticleSystem( "wpn_arc_cannon_electricity_fp" ) - PrecacheParticleSystem( "wpn_arc_cannon_electricity" ) - - PrecacheParticleSystem( "wpn_ARC_knob_FP" ) - PrecacheParticleSystem( "wpn_ARC_knob" ) + PrecacheParticleSystem( $"wpn_arc_cannon_electricity_fp" ) + PrecacheParticleSystem( $"wpn_arc_cannon_electricity" ) - PrecacheParticleSystem( "wpn_arc_cannon_charge_fp" ) - PrecacheParticleSystem( "wpn_arc_cannon_charge" ) - - PrecacheParticleSystem( "wpn_arc_cannon_charge_fp" ) - PrecacheParticleSystem( "wpn_arc_cannon_charge" ) - - PrecacheParticleSystem( "wpn_muzzleflash_arc_cannon_fp" ) - PrecacheParticleSystem( "wpn_muzzleflash_arc_cannon" ) + PrecacheParticleSystem( $"wpn_muzzleflash_arc_cannon_fp" ) + PrecacheParticleSystem( $"wpn_muzzleflash_arc_cannon" ) } function ArcCannon_Start( weapon ) { - weapon.PlayWeaponEffectNoCull( "wpn_arc_cannon_electricity_fp", "wpn_arc_cannon_electricity", "muzzle_flash" ) - weapon.EmitWeaponSound( "arc_cannon_charged_loop" ) + expect entity( weapon ) + if ( !IsPilot( weapon.GetWeaponOwner() ) ) + { + weapon.PlayWeaponEffectNoCull( $"wpn_arc_cannon_electricity_fp", $"wpn_arc_cannon_electricity", "muzzle_flash" ) + weapon.EmitWeaponSound( "arc_cannon_charged_loop" ) + } + else + { + weapon.EmitWeaponSound_1p3p( "Arc_Rifle_charged_Loop_1P", "Arc_Rifle_charged_Loop_3P" ) + } } function ArcCannon_Stop( weapon, player = null ) { + expect entity( weapon ) weapon.Signal( ARC_CANNON_SIGNAL_DEACTIVATED ) - StopChargeEffects( weapon, player ) - weapon.StopWeaponEffect( "wpn_arc_cannon_electricity_fp", "wpn_arc_cannon_electricity" ) + weapon.StopWeaponEffect( $"wpn_arc_cannon_electricity_fp", $"wpn_arc_cannon_electricity" ) weapon.StopWeaponSound( "arc_cannon_charged_loop" ) - weapon.StopWeaponSound( "arc_cannon_charge" ) } -function ArcCannon_ChargeBegin( weapon ) +function ArcCannon_ChargeBegin( entity weapon ) { - local weaponOwner = weapon.GetWeaponOwner() - local weaponScriptScope = weapon.GetScriptScope() - local useNormalChargeSounds = true - if( weapon.HasMod( "overcharge" ) ) - { - if ( weaponOwner.IsTitan() ) + #if SERVER + if ( weapon.HasMod( "overcharge" ) ) { - local soul = weaponOwner.GetTitanSoul() - if ( soul.GetShieldHealth() > 0 ) + entity weaponOwner = weapon.GetWeaponOwner() + if ( weaponOwner.IsTitan() ) { - weapon.EmitWeaponSound( "arc_cannon_fastcharge" ) - useNormalChargeSounds = false - } - if ( IsServer() ) + entity soul = weaponOwner.GetTitanSoul() thread ConvertTitanShieldIntoBonusCharge( soul, weapon ) + } } - } + #endif - if ( useNormalChargeSounds ) - { - weapon.EmitWeaponSound( "arc_cannon_charge" ) - } - - if( !("maxChargeTime" in weapon.s) ) - weapon.s.maxChargeTime <- weapon.GetWeaponModSetting( "charge_time" ) - - weapon.PlayWeaponEffectNoCull( "wpn_arc_cannon_charge_fp", "wpn_arc_cannon_charge", "muzzle_flash" ) - local chargeTime = weapon.GetWeaponChargeTime() - - if ( IsClient() ) - { + #if CLIENT if ( !weapon.ShouldPredictProjectiles() ) return - if ( weaponOwner.IsPlayer() ) - weaponOwner.StartArcCannon(); - - local handle = weapon.AllocateHandleForViewmodelEffect( "wpn_arc_cannon_charge_fp" ) - if ( handle ) - EffectSkipForwardToTime( handle, chargeTime ) - - thread cl_ChargeRumble( weapon, ARC_CANNON_RUMBLE_TYPE_INDEX, ARC_CANNON_RUMBLE_CHARGE_MIN, ARC_CANNON_RUMBLE_CHARGE_MAX, ARC_CANNON_SIGNAL_CHARGEEND ) - } - thread ChargeEffects( weapon ) + entity weaponOwner = weapon.GetWeaponOwner() + Assert( weaponOwner.IsPlayer() ) + weaponOwner.StartArcCannon(); + #endif } -function ArcCannon_ChargeEnd( weapon, player = null ) +function ArcCannon_ChargeEnd( entity weapon, entity player = null ) { - if ( IsClient() && weapon.GetWeaponOwner() == GetLocalViewPlayer() ) - { - local weaponOwner - if ( player != null ) - weaponOwner = player - else - weaponOwner = weapon.GetWeaponOwner() - - if ( IsValid( weaponOwner ) && weaponOwner.IsPlayer() ) - weaponOwner.StopArcCannon() - } - if( IsValid( weapon ) ) - StopChargeEffects( weapon ) -} + #if SERVER + if ( IsValid( weapon ) ) + weapon.Signal( ARC_CANNON_SIGNAL_CHARGEEND ) + #endif -function StopChargeEffects( weapon, player = null ) -{ - weapon.Signal( ARC_CANNON_SIGNAL_CHARGEEND ) - - local weaponScriptScope = weapon.GetScriptScope() - weapon.StopWeaponSound( "arc_cannon_charge" ) - weapon.StopWeaponEffect( "wpn_arc_cannon_charge_fp", "wpn_arc_cannon_charge" ) - weapon.StopWeaponSound( "arc_cannon_fastcharge" ) - //weapon.StopWeaponEffect( "wpn_arc_cannon_charge_fp", "wpn_arc_cannon_charge" ) - weapon.StopWeaponEffect( "wpn_ARC_knob_FP", "wpn_ARC_knob" ) -} - -function ChargeEffects( weapon ) -{ - weapon.EndSignal( ARC_CANNON_SIGNAL_CHARGEEND ) - weapon.EndSignal( "OnDestroy" ) - - local player = weapon.GetWeaponOwner() - - wait ( weapon.s.maxChargeTime * GetArcCannonChargeFraction( weapon ) ) + #if CLIENT + if ( weapon.GetWeaponOwner() == GetLocalViewPlayer() ) + { + entity weaponOwner + if ( player != null ) + weaponOwner = player + else + weaponOwner = weapon.GetWeaponOwner() - weapon.PlayWeaponEffectNoCull( "wpn_ARC_knob_FP", "wpn_ARC_knob", "SPINNING_KNOB" ) + if ( IsValid( weaponOwner ) && weaponOwner.IsPlayer() ) + weaponOwner.StopArcCannon() + } + #endif } -function ConvertTitanShieldIntoBonusCharge( soul, weapon ) +#if SERVER +function ConvertTitanShieldIntoBonusCharge( entity soul, entity weapon ) { weapon.EndSignal( ARC_CANNON_SIGNAL_CHARGEEND ) weapon.EndSignal( "OnDestroy" ) @@ -259,19 +220,19 @@ function ConvertTitanShieldIntoBonusCharge( soul, weapon ) local maxShieldDecay = OVERCHARGE_MAX_SHIELD_DECAY local bonusChargeFraction = OVERCHARGE_BONUS_CHARGE_FRACTION local shieldDecayMultiplier = OVERCHARGE_SHIELD_DECAY_MULTIPLIER - local shieldHealthMax = soul.GetShieldHealthMax() + int shieldHealthMax = soul.GetShieldHealthMax() local chargeRatio = GetArcCannonChargeFraction( weapon ) while( 1 ) { - if( !IsValid( soul ) || !IsValid( weapon ) ) + if ( !IsValid( soul ) || !IsValid( weapon ) ) break - local baseCharge = weapon.GetWeaponChargeFraction() // + GetOverchargeBonusChargeFraction() + local baseCharge = GetWeaponChargeFrac( weapon ) // + GetOverchargeBonusChargeFraction() local charge = clamp ( baseCharge * ( 1 / chargeRatio ), 0.0, 1.0 ) - if( charge < 1.0 || maxShieldDecay > 0) + if ( charge < 1.0 || maxShieldDecay > 0) { - local shieldHealth = soul.GetShieldHealth() + int shieldHealth = soul.GetShieldHealth() //Slight inconsistency in server updates, this ensures it never takes too much. if ( shieldDecayMultiplier > maxShieldDecay ) @@ -281,42 +242,27 @@ function ConvertTitanShieldIntoBonusCharge( soul, weapon ) local shieldDecayAmount = shieldHealthMax * shieldDecayMultiplier local newShieldAmount = shieldHealth - shieldDecayAmount soul.SetShieldHealth( max( newShieldAmount, 0 ) ) - soul.s.nextRegenTime = Time() + TITAN_SHIELD_REGEN_DELAY + soul.nextRegenTime = Time() + GetShieldRegenTime( soul ) - if( shieldDecayAmount > shieldHealth ) + if ( shieldDecayAmount > shieldHealth ) bonusChargeFraction = bonusChargeFraction * ( shieldHealth / shieldDecayAmount ) weapon.SetWeaponChargeFraction( baseCharge + bonusChargeFraction ) } wait 0.1 } } +#endif -function FireArcCannon( weapon, attackParams ) +function FireArcCannon( entity weapon, WeaponPrimaryAttackParams attackParams ) { local weaponScriptScope = weapon.GetScriptScope() - local owner = weapon.GetWeaponOwner() - - local baseCharge = weapon.GetWeaponChargeFraction() // + GetOverchargeBonusChargeFraction() - local charge = clamp ( baseCharge * ( 1 / GetArcCannonChargeFraction( weapon ) ), 0.0, 1.0 ) - local newVolume = GraphCapped( charge, 0.25, 1.0, 0.0, 1.0 ) - if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) ) - { - weapon.EmitWeaponSound( "arc_cannon_fire_SmallShot_Amped" ) - weaponScriptScope.PlayWeaponSoundWithVolume( "arc_cannon_fire_BigShot_Amped", newVolume ) - } - else - { - weapon.EmitWeaponSound( "arc_cannon_fire_SmallShot" ) - weaponScriptScope.PlayWeaponSoundWithVolume( "arc_cannon_Fire_BigShot", newVolume ) - } - - weapon.StopWeaponSound( "arc_cannon_charged_loop" ) + local baseCharge = GetWeaponChargeFrac( weapon ) // + GetOverchargeBonusChargeFraction() + local charge = clamp( baseCharge * ( 1 / GetArcCannonChargeFraction( weapon ) ), 0.0, 1.0 ) + float newVolume = GraphCapped( charge, 0.25, 1.0, 0.0, 1.0 ) weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 ) - weapon.PlayWeaponEffect( "wpn_muzzleflash_arc_cannon_fp", "wpn_muzzleflash_arc_cannon", "muzzle_flash" ) - - StopChargeEffects( weapon ) + weapon.PlayWeaponEffect( $"wpn_muzzleflash_arc_cannon_fp", $"wpn_muzzleflash_arc_cannon", "muzzle_flash" ) local attachmentName = "muzzle_flash" local attachmentIndex = weapon.LookupAttachment( attachmentName ) @@ -325,7 +271,7 @@ function FireArcCannon( weapon, attackParams ) //printt( "-------- FIRING ARC CANNON --------" ) - local firstTargetInfo = GetFirstArcCannonTarget( weapon, attackParams ) + table firstTargetInfo = GetFirstArcCannonTarget( weapon, attackParams ) if ( !IsValid( firstTargetInfo.target ) ) FireArcNoTargets( weapon, attackParams, muzzleOrigin ) else @@ -334,63 +280,71 @@ function FireArcCannon( weapon, attackParams ) return 1 } -function GetFirstArcCannonTarget( weapon, attackParams ) +table function GetFirstArcCannonTarget( entity weapon, WeaponPrimaryAttackParams attackParams ) { - local owner = weapon.GetWeaponOwner() + entity owner = weapon.GetWeaponOwner() local coneHeight = weapon.GetMaxDamageFarDist() - local angleToAxis = 8 // set this too high and auto-titans using it will error on GetVisibleEntitiesInCone - local ignoredEntities = [ owner, weapon ] - local traceMask = TRACE_MASK_SHOT - local flags = VIS_CONE_ENTS_TEST_HITBOXES // | VIS_CONE_ENTS_IGNORE_VORTEX + local angleToAxis = 2 // set this too high and auto-titans using it will error on FindVisibleEntitiesInCone + array<entity> ignoredEntities = [ owner, weapon ] + int traceMask = TRACE_MASK_SHOT + int flags = VIS_CONE_ENTS_TEST_HITBOXES local antilagPlayer = null if ( owner.IsPlayer() ) { - angleToAxis = owner.GetAttackSpreadAngle() * 0.095 + angleToAxis = owner.GetAttackSpreadAngle() * 0.11 antilagPlayer = owner } - local results - local ownerTeam = owner.GetTeam() + int ownerTeam = owner.GetTeam() // Get a missile target and a non-missile target in the cone that the player can zap // We do this in a separate check so we can use a wider cone to be more forgiving for targeting missiles - local firstTargetInfo = {} + table firstTargetInfo = {} firstTargetInfo.target <- null firstTargetInfo.hitLocation <- null - for ( local i = 0 ; i < 2 ; i++ ) + + for ( int i = 0; i < 2; i++ ) { local missileCheck = i == 0 local coneAngle = angleToAxis - if ( missileCheck ) - coneAngle *= 3.0 + if ( missileCheck && owner.IsPlayer() ) // missile check only if owner is player + coneAngle *= 8.0 + + coneAngle = clamp( coneAngle, 0.1, 89.9 ) - results = GetVisibleEntitiesInCone( attackParams.pos, attackParams.dir, coneHeight, coneAngle, ignoredEntities, traceMask, flags, antilagPlayer, false, false ) - foreach( result in results ) + array<VisibleEntityInCone> results = FindVisibleEntitiesInCone( attackParams.pos, attackParams.dir, coneHeight, coneAngle, ignoredEntities, traceMask, flags, antilagPlayer ) + foreach ( result in results ) { - local visibleEnt = result.entity + entity visibleEnt = result.ent if ( !IsValid( visibleEnt ) ) continue - local classname = IsServer() ? visibleEnt.GetClassname() : visibleEnt.GetSignifierName() + if ( visibleEnt.IsPhaseShifted() ) + continue - if ( !( classname in level.arcCannonTargetClassnames ) ) + local classname = IsServer() ? visibleEnt.GetClassName() : visibleEnt.GetSignifierName() + + if ( !( classname in ArcCannonTargetClassnames ) ) continue if ( "GetTeam" in visibleEnt ) { - local visibleEntTeam = visibleEnt.GetTeam() + int visibleEntTeam = visibleEnt.GetTeam() if ( visibleEntTeam == ownerTeam ) continue if ( IsEntANeutralMegaTurret( visibleEnt, ownerTeam ) ) continue } - if ( missileCheck && classname != "rpg_missile" ) + expect string( classname ) + string targetname = visibleEnt.GetTargetName() + + if ( missileCheck && ( classname != "rpg_missile" && !file.missileCheckTargetnames.contains( targetname ) ) ) continue - if ( !missileCheck && classname == "rpg_missile" ) + if ( !missileCheck && ( classname == "rpg_missile" || file.missileCheckTargetnames.contains( targetname ) ) ) continue firstTargetInfo.target = visibleEnt @@ -399,50 +353,78 @@ function GetFirstArcCannonTarget( weapon, attackParams ) } } //Creating a whiz-by sound. - weapon.FireWeaponBullet_Special( attackParams.pos, attackParams.dir, 1, 0, true, true, true, true ) + weapon.FireWeaponBullet_Special( attackParams.pos, attackParams.dir, 1, 0, true, true, true, true, true, false, false ) return firstTargetInfo } -function FireArcNoTargets( weapon, attackParams, muzzleOrigin ) +function FireArcNoTargets( entity weapon, WeaponPrimaryAttackParams attackParams, muzzleOrigin ) { Assert( IsValid( weapon ) ) - local player = weapon.GetWeaponOwner() - local chargeFrac = weapon.GetWeaponChargeFraction() + entity player = weapon.GetWeaponOwner() + local chargeFrac = GetWeaponChargeFrac( weapon ) local beamVec = attackParams.dir * weapon.GetMaxDamageFarDist() local playerEyePos = player.EyePosition() - local traceResults = TraceLineHighDetail( playerEyePos, (playerEyePos + beamVec), weapon, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE ) + TraceResults traceResults = TraceLineHighDetail( playerEyePos, (playerEyePos + beamVec), weapon, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE ) local beamEnd = traceResults.endPos - local vortexHit = VortexBulletHitCheck( player, playerEyePos, beamEnd ) + VortexBulletHit ornull vortexHit = VortexBulletHitCheck( player, playerEyePos, beamEnd ) if ( vortexHit ) { - if( IsServer() ) - { - local vortexWeapon = vortexHit.vortex.GetOwnerWeapon() - if( vortexWeapon && vortexWeapon.GetClassname() == "mp_titanweapon_vortex_shield" ) - VortexDrainedByImpact( vortexWeapon, weapon, null, null ) - } + expect VortexBulletHit( vortexHit ) + #if SERVER + entity vortexWeapon = vortexHit.vortex.GetOwnerWeapon() + string className = IsValid( vortexWeapon ) ? vortexWeapon.GetWeaponClassName() : "" + if ( vortexWeapon && ( className == "mp_titanweapon_vortex_shield" || className == "mp_titanweapon_vortex_shield_ion" ) ) + { + float amount = expect float ( chargeFrac ) * weapon.GetWeaponSettingFloat( eWeaponVar.vortex_drain ) + if ( amount <= 0.0 ) + return + + if ( vortexWeapon.GetWeaponClassName() == "mp_titanweapon_vortex_shield_ion" ) + { + entity owner = vortexWeapon.GetWeaponOwner() + int totalEnergy = owner.GetSharedEnergyTotal() + owner.TakeSharedEnergy( int( float( totalEnergy ) * amount ) ) + } + else + { + float frac = min ( vortexWeapon.GetWeaponChargeFraction() + amount, 1.0 ) + vortexWeapon.SetWeaponChargeFraction( frac ) + } + } + else if ( IsVortexSphere( vortexHit.vortex ) ) + { + // do damage to vortex_sphere entities that isn't the titan "vortex shield" + local damageNear = weapon.GetWeaponInfoFileKeyField( "damage_near_value" ) + local damage = damageNear * GraphCapped( chargeFrac, 0, 1, 0.0, 1.0 ) * 10 // do more damage the more charged the weapon is. + VortexSphereDrainHealthForDamage( vortexHit.vortex, damage ) + if ( IsValid( player ) && player.IsPlayer() ) + player.NotifyDidDamage( vortexHit.vortex, 0, vortexHit.hitPos, 0, damage, DF_NO_HITBEEP, 0, null, 0 ) + } + #endif beamEnd = vortexHit.hitPos } - local radius = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_RADIUS_MIN, ARC_CANNON_BOLT_RADIUS_MAX ) + float radius = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_RADIUS_MIN, ARC_CANNON_BOLT_RADIUS_MAX ) local boltWidth = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_WIDTH_MIN, ARC_CANNON_BOLT_WIDTH_MAX ) if ( player.IsNPC() ) boltWidth = ARC_CANNON_BOLT_WIDTH_NPC thread CreateArcCannonBeam( weapon, null, muzzleOrigin, beamEnd, player, ARC_CANNON_BEAM_LIFETIME, radius, boltWidth, 2, false, true ) - if( IsServer() ) - CreateExplosion( beamEnd, 0, 0, 1, 1, player, 0, null, -1, false, ARC_CANNON_FX_TABLE ) + + #if SERVER + PlayImpactFXTable( expect vector( beamEnd ), player, ARC_CANNON_FX_TABLE, SF_ENVEXPLOSION_INCLUDE_ENTITIES ) + #endif } -function FireArcWithTargets( weapon, firstTargetInfo, attackParams, muzzleOrigin ) +function FireArcWithTargets( entity weapon, table firstTargetInfo, WeaponPrimaryAttackParams attackParams, muzzleOrigin ) { local beamStart = muzzleOrigin local beamEnd - local player = weapon.GetWeaponOwner() - local chargeFrac = weapon.GetWeaponChargeFraction() - local radius = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_RADIUS_MIN, ARC_CANNON_BOLT_RADIUS_MAX ) - local boltWidth = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_WIDTH_MIN, ARC_CANNON_BOLT_WIDTH_MAX ) + entity player = weapon.GetWeaponOwner() + local chargeFrac = GetWeaponChargeFrac( weapon ) + float radius = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_RADIUS_MIN, ARC_CANNON_BOLT_RADIUS_MAX ) + float boltWidth = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_WIDTH_MIN, ARC_CANNON_BOLT_WIDTH_MAX ) local maxChains local minChains @@ -468,7 +450,7 @@ function FireArcWithTargets( weapon, firstTargetInfo, attackParams, muzzleOrigin if ( !player.IsNPC() ) maxChains = Graph( chargeFrac, 0, 1, minChains, maxChains ) - local zapInfo = {} + table zapInfo = {} zapInfo.weapon <- weapon zapInfo.player <- player zapInfo.muzzleOrigin <- muzzleOrigin @@ -478,11 +460,12 @@ function FireArcWithTargets( weapon, firstTargetInfo, attackParams, muzzleOrigin zapInfo.chargeFrac <- chargeFrac zapInfo.zappedTargets <- {} zapInfo.zappedTargets[ firstTargetInfo.target ] <- true + zapInfo.dmgSourceID <- weapon.GetDamageSourceID() local chainNum = 1 - thread ZapTargetRecursive( firstTargetInfo.target, zapInfo, zapInfo.muzzleOrigin, firstTargetInfo.hitLocation, chainNum ) + thread ZapTargetRecursive( expect entity( firstTargetInfo.target), zapInfo, zapInfo.muzzleOrigin, expect vector( firstTargetInfo.hitLocation ), chainNum ) } -function ZapTargetRecursive( target, zapInfo, beamStartPos, firstTargetBeamEndPos = null, chainNum = 1 ) +function ZapTargetRecursive( entity target, table zapInfo, beamStartPos, vector ornull firstTargetBeamEndPos = null, chainNum = 1 ) { if ( !IsValid( target ) ) return @@ -493,28 +476,30 @@ function ZapTargetRecursive( target, zapInfo, beamStartPos, firstTargetBeamEndPo Assert( target in zapInfo.zappedTargets ) if ( chainNum > zapInfo.maxChains ) return - local beamEndPos + vector beamEndPos if ( firstTargetBeamEndPos == null ) beamEndPos = target.GetWorldSpaceCenter() else - beamEndPos = firstTargetBeamEndPos + beamEndPos = expect vector( firstTargetBeamEndPos ) waitthread ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum ) // Get other nearby targets we can chain to - if ( IsServer() ) - { - if ( !IsValid( target ) ) - return + #if SERVER + if ( !IsValid( zapInfo.weapon ) ) + return + + var noArcing = expect entity( zapInfo.weapon ).GetWeaponInfoFileKeyField( "disable_arc" ) - if ( !IsValid( zapInfo.weapon ) ) - return + if ( noArcing != null && noArcing == 1 ) + return // no chaining on new arc cannon - local chainTargets = GetArcCannonChainTargets( beamEndPos, target, zapInfo ) - foreach( chainTarget in chainTargets ) + // NOTE: 'target' could be invalid at this point (no corpse) + array<entity> chainTargets = GetArcCannonChainTargets( beamEndPos, target, zapInfo ) + foreach( entity chainTarget in chainTargets ) { local newChainNum = chainNum - if( !chainTarget.GetClassname() != "rpg_missile" ) + if ( chainTarget.GetClassName() != "rpg_missile" ) newChainNum++ zapInfo.zappedTargets[ chainTarget ] <- true thread ZapTargetRecursive( chainTarget, zapInfo, beamEndPos, null, newChainNum ) @@ -522,203 +507,257 @@ function ZapTargetRecursive( target, zapInfo, beamStartPos, firstTargetBeamEndPo if ( IsValid( zapInfo.player ) && zapInfo.player.IsPlayer() && zapInfo.zappedTargets.len() >= 5 ) { - if ( PlayerProgressionAllowed( zapInfo.player ) ) - zapInfo.player.SetPersistentVar( "ach_multikillArcRifle", true ) + #if HAS_STATS if ( chainNum == 5 ) - UpdatePlayerStat( zapInfo.player, "misc_stats", "arcCannonMultiKills", 1 ) + UpdatePlayerStat( expect entity( zapInfo.player ), "misc_stats", "arcCannonMultiKills", 1 ) + #endif } - } + #endif } function ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum = 1 ) { + expect entity( target ) + expect vector( beamStartPos ) + expect vector( beamEndPos ) + //DebugDrawLine( beamStartPos, beamEndPos, 255, 0, 0, true, 5.0 ) local boltWidth = zapInfo.boltWidth if ( zapInfo.player.IsNPC() ) boltWidth = ARC_CANNON_BOLT_WIDTH_NPC local firstBeam = ( chainNum == 1 ) - if( firstBeam && IsServer() ) - CreateExplosion( beamEndPos, 0, 0, 1, 1, zapInfo.player, 0, null, -1, false, ARC_CANNON_FX_TABLE ) + #if SERVER + if ( firstBeam ) + { + PlayImpactFXTable( beamEndPos, expect entity( zapInfo.player ), ARC_CANNON_FX_TABLE, SF_ENVEXPLOSION_INCLUDE_ENTITIES ) + } + #endif + thread CreateArcCannonBeam( zapInfo.weapon, target, beamStartPos, beamEndPos, zapInfo.player, ARC_CANNON_BEAM_LIFETIME, zapInfo.radius, boltWidth, 5, true, firstBeam ) - if ( IsClient() ) - return + #if SERVER + local isMissile = ( target.GetClassName() == "rpg_missile" ) + if ( !isMissile ) + wait ARC_CANNON_FORK_DELAY + else + wait 0.05 - local isMissile = ( target.GetClassname() == "rpg_missile" ) - if( !isMissile ) - wait ARC_CANNON_FORK_DELAY - else - wait 0.05 + local deathPackage = damageTypes.arcCannon - local deathPackage = damageTypes.ArcCannon + float damageAmount + int damageMin + int damageMax - local damageAmount - local damageMin - local damageMax + int damageFarValue = eWeaponVar.damage_far_value + int damageNearValue = eWeaponVar.damage_near_value + int damageFarValueTitanArmor = eWeaponVar.damage_far_value_titanarmor + int damageNearValueTitanArmor = eWeaponVar.damage_near_value_titanarmor + if ( zapInfo.player.IsNPC() ) + { + damageFarValue = eWeaponVar.npc_damage_far_value + damageNearValue = eWeaponVar.npc_damage_near_value + damageFarValueTitanArmor = eWeaponVar.npc_damage_far_value_titanarmor + damageNearValueTitanArmor = eWeaponVar.npc_damage_near_value_titanarmor + } - if ( IsValid( target ) && IsValid( zapInfo.player ) ) - { - if ( target.GetArmorType() == ARMOR_TYPE_HEAVY ) + if ( IsValid( target ) && IsValid( zapInfo.player ) ) { + bool hasFastPacitor = false + bool noArcing = false + if ( IsValid( zapInfo.weapon ) ) { - damageMin = zapInfo.weapon.GetWeaponModSetting( "damage_far_value_titanarmor" ) - damageMax = zapInfo.weapon.GetWeaponModSetting( "damage_near_value_titanarmor" ) + entity weap = expect entity( zapInfo.weapon ) + hasFastPacitor = weap.GetWeaponInfoFileKeyField( "push_apart" ) != null && weap.GetWeaponInfoFileKeyField( "push_apart" ) == 1 + noArcing = weap.GetWeaponInfoFileKeyField( "no_arcing" ) != null && weap.GetWeaponInfoFileKeyField( "no_arcing" ) == 1 } - else + + if ( target.GetArmorType() == ARMOR_TYPE_HEAVY ) { - damageMin = GetWeaponInfoFileKeyField_Global( "mp_titanweapon_arc_cannon", "damage_far_value_titanarmor" ) - damageMax = GetWeaponInfoFileKeyField_Global( "mp_titanweapon_arc_cannon", "damage_near_value_titanarmor" ) + if ( IsValid( zapInfo.weapon ) ) + { + entity weapon = expect entity( zapInfo.weapon ) + damageMin = weapon.GetWeaponSettingInt( damageFarValueTitanArmor ) + damageMax = weapon.GetWeaponSettingInt( damageNearValueTitanArmor ) + } + else + { + damageMin = 100 + damageMax = zapInfo.player.IsNPC() ? 1200 : 800 + } } - - // Due to auto-titans not charging, they do very little damage with this weapon against one another. - if ( zapInfo.player.IsNPC() ) + else { - damageMin *= 7.0 - damageMax *= 7.0 + if ( IsValid( zapInfo.weapon ) ) + { + entity weapon = expect entity( zapInfo.weapon ) + damageMin = weapon.GetWeaponSettingInt( damageFarValue ) + damageMax = weapon.GetWeaponSettingInt( damageNearValue ) + } + else + { + damageMin = 120 + damageMax = zapInfo.player.IsNPC() ? 140 : 275 + } + + if ( target.IsNPC() ) + { + damageMin *= 3 // more powerful against NPC humans so they die easy + damageMax *= 3 + } } - // HACK; temp fix for non titan heavy armor targets (e.g. mega turret) - } - else - { - if ( IsValid( zapInfo.weapon ) ) + + local chargeRatio = GetArcCannonChargeFraction( zapInfo.weapon ) + if ( IsValid( zapInfo.weapon ) && !zapInfo.weapon.GetWeaponSettingBool( eWeaponVar.charge_require_input ) ) { - damageMin = zapInfo.weapon.GetWeaponModSetting( "damage_far_value" ) - damageMax = zapInfo.weapon.GetWeaponModSetting( "damage_near_value" ) + // use distance for damage if the weapon auto-fires + entity weapon = expect entity( zapInfo.weapon ) + float nearDist = weapon.GetWeaponSettingFloat( eWeaponVar.damage_near_distance ) + float farDist = weapon.GetWeaponSettingFloat( eWeaponVar.damage_far_distance ) + + float dist = Distance( weapon.GetOrigin(), target.GetOrigin() ) + damageAmount = GraphCapped( dist, farDist, nearDist, damageMin, damageMax ) } else { - damageMin = GetWeaponInfoFileKeyField_Global( "mp_titanweapon_arc_cannon", "damage_far_value" ) - damageMax = GetWeaponInfoFileKeyField_Global( "mp_titanweapon_arc_cannon", "damage_near_value" ) + // Scale damage amount based on how many chains deep we are + damageAmount = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, damageMin, damageMax ) } + local damageFalloff = ARC_CANNON_DAMAGE_FALLOFF_SCALER + if ( IsValid( zapInfo.weapon ) && zapInfo.weapon.HasMod( "splitter" ) ) + damageFalloff = SPLITTER_DAMAGE_FALLOFF_SCALER + damageAmount *= pow( damageFalloff, chainNum - 1 ) + + local dmgSourceID = zapInfo.dmgSourceID - if ( target.IsNPC() ) + // Update Later - This shouldn't be done here, this is not where we determine if damage actually happened to the target + // move to Damaged callback instead + if ( damageAmount > 0 ) { - damageMin *= 3.0 // more powerful against NPC humans so they die easy - damageMax *= 3.0 - } - } + float empDuration = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, ARC_CANNON_EMP_DURATION_MIN, ARC_CANNON_EMP_DURATION_MAX ) + if ( target.IsPlayer() && target.IsTitan() && !hasFastPacitor && !noArcing ) + { + float empViewStrength = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, ARC_CANNON_SCREEN_EFFECTS_MIN, ARC_CANNON_SCREEN_EFFECTS_MAX ) + + if ( target.IsTitan() && zapInfo.chargeFrac >= ARC_CANNON_SCREEN_THRESHOLD ) + { + Remote_CallFunction_Replay( target, "ServerCallback_TitanEMP", empViewStrength, empDuration, ARC_CANNON_EMP_FADEOUT_DURATION ) + EmitSoundOnEntityOnlyToPlayer( target, target, ARC_CANNON_TITAN_SCREEN_SFX ) + } + else if ( zapInfo.chargeFrac >= ARC_CANNON_SCREEN_THRESHOLD ) + { + StatusEffect_AddTimed( target, eStatusEffect.emp, empViewStrength, empDuration, ARC_CANNON_EMP_FADEOUT_DURATION ) + EmitSoundOnEntityOnlyToPlayer( target, target, ARC_CANNON_PILOT_SCREEN_SFX ) + } + } - // Scale damage amount based on how many chains deep we are - local chargeRatio = GetArcCannonChargeFraction( zapInfo.weapon ) - damageAmount = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, damageMin, damageMax ) - local damageFalloff = ARC_CANNON_DAMAGE_FALLOFF_SCALER - if( IsValid( zapInfo.weapon ) && zapInfo.weapon.HasMod( "splitter" ) ) - damageFalloff = SPLITTER_DAMAGE_FALLOFF_SCALER - damageAmount *= pow( damageFalloff, chainNum - 1 ) + // Do 3rd person effect on the body + asset effect + string tag + target.TakeDamage( damageAmount, zapInfo.player, zapInfo.player, { origin = beamEndPos, force = Vector(0,0,0), scriptType = deathPackage, weapon = zapInfo.weapon, damageSourceId = dmgSourceID,criticalHitScale = zapInfo.weapon.GetWeaponSettingFloat( eWeaponVar.critical_hit_damage_scale ) } ) + //vector dir = Normalize( beamEndPos - beamStartPos ) + //vector velocity = dir * 600 + //PushPlayerAway( target, velocity ) + //PushPlayerAway( expect entity( zapInfo.player ), -velocity ) - local dmgSourceID = eDamageSourceId.mp_titanweapon_arc_cannon + if ( IsValid( zapInfo.weapon ) && hasFastPacitor ) + { + if ( IsAlive( target ) && IsAlive( expect entity( zapInfo.player ) ) && target.IsTitan() ) + { + float pushPercent = GraphCapped( damageAmount, damageMin, damageMax, 0.0, 1.0 ) - // Update Later - This shouldn't be done here, this is not where we determine if damage actually happened to the target - // move to Damaged callback instead - if( damageAmount > 0 ) - { - local empDuration = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, ARC_CANNON_EMP_DURATION_MIN, ARC_CANNON_EMP_DURATION_MAX ) + if ( pushPercent > 0.6 ) + PushPlayersApart( target, expect entity( zapInfo.player ), pushPercent * 400.0 ) + } + } - if ( target.IsPlayer() ) - { - local empViewStrength = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, ARC_CANNON_SCREEN_EFFECTS_MIN, ARC_CANNON_SCREEN_EFFECTS_MAX ) + if ( zapInfo.chargeFrac < ARC_CANNON_SCREEN_THRESHOLD ) + empDuration = ARC_CANNON_3RD_PERSON_EFFECT_MIN_DURATION + else + empDuration += ARC_CANNON_EMP_FADEOUT_DURATION - if ( target.IsTitan() && zapInfo.chargeFrac >= ARC_CANNON_SCREEN_THRESHOLD ) + if ( target.GetArmorType() == ARMOR_TYPE_HEAVY ) { - Remote.CallFunction_Replay( target, "ServerCallback_TitanEMP", empViewStrength, empDuration, ARC_CANNON_EMP_FADEOUT_DURATION ) - EmitSoundOnEntityOnlyToPlayer( target, target, ARC_CANNON_TITAN_SCREEN_SFX ) - - local scale = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, ARC_CANNON_SLOW_SCALE_MIN, ARC_CANNON_SLOW_SCALE_MAX ) - thread EMP_SlowPlayer( target, scale, empDuration ) + effect = $"impact_arc_cannon_titan" + tag = "exp_torso_front" } - else if ( zapInfo.chargeFrac >= ARC_CANNON_SCREEN_THRESHOLD ) + else { - Remote.CallFunction_Replay( target, "ServerCallback_PilotEMP", empViewStrength, empDuration, ARC_CANNON_EMP_FADEOUT_DURATION ) - EmitSoundOnEntityOnlyToPlayer( target, target, ARC_CANNON_PILOT_SCREEN_SFX ) + effect = $"P_emp_body_human" + tag = "CHESTFOCUS" } - } - // Do 3rd person effect on the body - local effect = null - local tag = null - target.TakeDamage( damageAmount, zapInfo.player, zapInfo.player, { origin = zapInfo.player.GetOrigin(), force = Vector(0,0,0), scriptType = deathPackage, weapon = zapInfo.weapon, damageSourceId = dmgSourceID } ) + if ( target.IsPlayer() ) + { + if ( target.LookupAttachment( tag ) != 0 ) + ClientStylePlayFXOnEntity( effect, target, tag, empDuration ) + } - if ( zapInfo.chargeFrac < ARC_CANNON_SCREEN_THRESHOLD ) - empDuration = ARC_CANNON_3RD_PERSON_EFFECT_MIN_DURATION - else - empDuration += ARC_CANNON_EMP_FADEOUT_DURATION + if ( target.IsPlayer() ) + EmitSoundOnEntityExceptToPlayer( target, target, "Titan_Blue_Electricity_Cloud" ) + else + EmitSoundOnEntity( target, "Titan_Blue_Electricity_Cloud" ) - if ( target.GetArmorType() == ARMOR_TYPE_HEAVY ) - { - effect = "impact_arc_cannon_titan" - tag = "exp_torso_front" + thread FadeOutSoundOnEntityAfterDelay( target, "Titan_Blue_Electricity_Cloud", empDuration * 0.6666, empDuration * 0.3333 ) } else { - effect = "P_emp_body_human" - tag = "CHESTFOCUS" + //Don't bounce if the beam is set to do 0 damage. + chainNum = zapInfo.maxChains } - if ( target.IsPlayer() && effect != null && tag != null ) + if ( isMissile ) { - if ( target.LookupAttachment( tag ) != 0 ) - ClientStylePlayFXOnEntity( effect, target, tag, empDuration ) + if ( IsValid( zapInfo.player ) ) + target.SetOwner( zapInfo.player ) + target.MissileExplode() } - - if ( target.IsPlayer() ) - EmitSoundOnEntityExceptToPlayer( target, target, "Titan_Blue_Electricity_Cloud" ) - else - EmitSoundOnEntity( target, "Titan_Blue_Electricity_Cloud" ) - - thread FadeOutSoundOnEntityAfterDelay( target, "Titan_Blue_Electricity_Cloud", empDuration * 0.6666, empDuration * 0.3333 ) - } - else - { - //Don't bounce if the beam is set to do 0 damage. - chainNum = zapInfo.maxChains } - - if ( isMissile ) - { - if ( IsValid ( zapInfo.player ) ) - target.SetOwner( zapInfo.player ) - target.Explode() - } - } + #endif // SERVER } -function FadeOutSoundOnEntityAfterDelay( entity, soundAlias, delay, fadeTime ) -{ - - if ( !IsValid( entity ) ) - return +#if SERVER - entity.EndSignal( "OnDestroy" ) - wait delay - FadeOutSoundOnEntity( entity, soundAlias, fadeTime ) +void function PushEntForTime( entity ent, vector velocity, float time ) +{ + ent.EndSignal( "OnDeath" ) + float endTime = Time() + time + float startTime = Time() + for ( ;; ) + { + if ( Time() >= endTime ) + break + float multiplier = Graph( Time(), startTime, endTime, 1.0, 0.0 ) + vector currentVel = ent.GetVelocity() + currentVel += velocity * multiplier + ent.SetVelocity( currentVel ) + WaitFrame() + } } - -function GetArcCannonChainTargets( fromOrigin, fromTarget, zapInfo ) +array<entity> function GetArcCannonChainTargets( vector fromOrigin, entity fromTarget, table zapInfo ) { - Assert( IsServer() ) - - local results = [] + // NOTE: fromTarget could be null/invalid if it was a drone + array<entity> results = [] if ( !IsValid( zapInfo.player ) ) return results - local playerTeam = zapInfo.player.GetTeam() - local allTargets = GetArcCannonTargetsInRange( fromOrigin, playerTeam, zapInfo.weapon ) + int playerTeam = expect entity( zapInfo.player ).GetTeam() + array<entity> allTargets = GetArcCannonTargetsInRange( fromOrigin, playerTeam, expect entity( zapInfo.weapon ) ) allTargets = ArrayClosest( allTargets, fromOrigin ) local viewVector if ( zapInfo.player.IsPlayer() ) viewVector = zapInfo.player.GetViewVector() else - viewVector = zapInfo.player.EyeAngles().AnglesToForward() + viewVector = AnglesToForward( zapInfo.player.EyeAngles() ) local eyePosition = zapInfo.player.EyePosition() - foreach( ent in allTargets ) + foreach ( ent in allTargets ) { local forkCount = ARC_CANNON_FORK_COUNT_MAX if ( zapInfo.weapon.HasMod( "splitter" ) ) @@ -729,21 +768,22 @@ function GetArcCannonChainTargets( fromOrigin, fromTarget, zapInfo ) if ( results.len() >= forkCount ) break + if ( ent.IsPhaseShifted() ) + continue + if ( ent.IsPlayer() ) { - if ( ent.GetPlayerClass() == "operator" ) - continue - - if ( ent.GetPlayerClass() == "dronecontroller" ) - continue - // Ignore players that are passing damage to their parent. This is to address zapping a friendly rodeo player local entParent = ent.GetParent() if ( IsValid( entParent ) && ent.kv.PassDamageToParent.tointeger() ) continue + + // only chains to other titan players for now + if ( !ent.IsTitan() ) + continue } - if ( ent.GetClassname() == "script_mover" ) + if ( ent.GetClassName() == "script_mover" ) continue if ( IsEntANeutralMegaTurret( ent, playerTeam ) ) @@ -759,7 +799,7 @@ function GetArcCannonChainTargets( fromOrigin, fromTarget, zapInfo ) //Preventing the arc-cannon from firing behind. local vecToEnt = ( ent.GetWorldSpaceCenter() - eyePosition ) vecToEnt.Norm() - local dotVal = vecToEnt.Dot( viewVector ) + local dotVal = DotProduct( vecToEnt, viewVector ) if ( dotVal < 0 ) continue @@ -774,17 +814,21 @@ function GetArcCannonChainTargets( fromOrigin, fromTarget, zapInfo ) ignoreEnts.append( zappedTarget ) } - local traceResult = TraceLineHighDetail( fromOrigin, ent.GetWorldSpaceCenter(), ignoreEnts, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE ) + TraceResults traceResult = TraceLineHighDetail( fromOrigin, ent.GetWorldSpaceCenter(), ignoreEnts, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE ) // Trace failed, lets try an eye to eye trace - if ( traceResult.fraction < 1 && IsValid( fromTarget ) ) - traceResult = TraceLineHighDetail( fromTarget.EyePosition(), ent.EyePosition(), ignoreEnts, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE ) + if ( traceResult.fraction < 1 ) + { + // 'fromTarget' may be invalid + if ( IsValid( fromTarget ) ) + traceResult = TraceLineHighDetail( fromTarget.EyePosition(), ent.EyePosition(), ignoreEnts, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE ) + } if ( traceResult.fraction < 1 ) continue // Enemy is in visible, and within range. - if ( !IsValueInArray( results, ent ) ) + if ( !results.contains( ent ) ) results.append( ent ) } @@ -792,72 +836,85 @@ function GetArcCannonChainTargets( fromOrigin, fromTarget, zapInfo ) return results } -Globalize( GetArcCannonChainTargets ) - +#endif // SERVER -function IsEntANeutralMegaTurret( ent, playerTeam ) +bool function IsEntANeutralMegaTurret( ent, int playerTeam ) { - if ( ent.GetClassname() != "npc_turret_mega" ) + expect entity( ent ) + + if ( ent.GetClassName() != "npc_turret_mega" ) return false - local entTeam = ent.GetTeam() + int entTeam = ent.GetTeam() if ( entTeam == playerTeam ) return false - if ( entTeam != GetOtherTeam( playerTeam ) ) + if ( !IsEnemyTeam( playerTeam, entTeam ) ) return true return false } -Globalize( IsEntANeutralMegaTurret ) -function ArcCannon_HideIdleEffect( weapon, delay ) +function ArcCannon_HideIdleEffect( entity weapon, delay ) { - //printt( "HideIdleEffect" ) + bool weaponOwnerIsPilot = IsPilot( weapon.GetWeaponOwner() ) weapon.EndSignal( ARC_CANNON_SIGNAL_DEACTIVATED ) - weapon.StopWeaponEffect( "wpn_arc_cannon_electricity_fp", "wpn_arc_cannon_electricity" ) + if ( weaponOwnerIsPilot == false ) + { + weapon.StopWeaponEffect( $"wpn_arc_cannon_electricity_fp", $"wpn_arc_cannon_electricity" ) + weapon.StopWeaponSound( "arc_cannon_charged_loop" ) + } wait delay - if( !IsValid( weapon ) ) + if ( !IsValid( weapon ) ) return - local weaponOwner = weapon.GetWeaponOwner() + entity weaponOwner = weapon.GetWeaponOwner() //The weapon can be valid, but the player isn't a Titan during melee execute. // JFS: threads with waits should just end on "OnDestroy" - if ( !IsValid(weaponOwner) ) + if ( !IsValid( weaponOwner ) ) return - if ( !weapon.GetWeaponOwner().IsTitan() || weapon != weaponOwner.GetActiveWeapon() ) + if ( weapon != weaponOwner.GetActiveWeapon() ) return - weapon.PlayWeaponEffectNoCull( "wpn_arc_cannon_electricity_fp", "wpn_arc_cannon_electricity", "muzzle_flash" ) - weapon.EmitWeaponSound( "arc_cannon_charged_loop" ) + if ( weaponOwnerIsPilot == false ) + { + weapon.PlayWeaponEffectNoCull( $"wpn_arc_cannon_electricity_fp", $"wpn_arc_cannon_electricity", "muzzle_flash" ) + weapon.EmitWeaponSound( "arc_cannon_charged_loop" ) + } + else + { + weapon.EmitWeaponSound_1p3p( "Arc_Rifle_charged_Loop_1P", "Arc_Rifle_charged_Loop_3P" ) + } } -function AddToArcCannonTargets( ent ) +#if SERVER +void function AddToArcCannonTargets( entity ent ) { AddToScriptManagedEntArray( level._arcCannonTargetsArrayID, ent ); } -function GetArcCannonTargets( origin, team ) +function RemoveArcCannonTarget( ent ) { - local targets = GetScriptManagedEntArrayWithinCenter( level._arcCannonTargetsArrayID, team, origin, ARC_CANNON_TITAN_RANGE_CHAIN ) + RemoveFromScriptManagedEntArray( level._arcCannonTargetsArrayID, ent ) +} - if ( ARC_CANNON_TARGETS_MISSILES ) - { - local enemyTeam = GetEnemyTeam( team ) - targets.extend( GetProjectileArrayEx( "rpg_missile", enemyTeam, origin, ARC_CANNON_TITAN_RANGE_CHAIN ) ) - } +array<entity> function GetArcCannonTargets( vector origin, int team, entity weapon ) +{ + array<entity> targets = GetScriptManagedEntArrayWithinCenter( level._arcCannonTargetsArrayID, team, origin, ARC_CANNON_TITAN_RANGE_CHAIN ) + + if ( ARC_CANNON_TARGETS_MISSILES && weapon.GetWeaponChargeFraction() == 1.0 ) + targets.extend( GetProjectileArrayEx( "rpg_missile", TEAM_ANY, team, origin, ARC_CANNON_TITAN_RANGE_CHAIN ) ) return targets } -Globalize( GetArcCannonTargets ) -function GetArcCannonTargetsInRange( origin, team, weapon ) +array<entity> function GetArcCannonTargetsInRange( vector origin, int team, entity weapon ) { - local allTargets = GetArcCannonTargets( origin, team ) - local targetsInRange = [] + array<entity> allTargets = GetArcCannonTargets( origin, team, weapon ) + array<entity> targetsInRange - local titanDistSq - local distSq + float titanDistSq + float distSq if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) ) { titanDistSq = ARC_CANNON_TITAN_RANGE_CHAIN_BURN * ARC_CANNON_TITAN_RANGE_CHAIN_BURN @@ -871,45 +928,15 @@ function GetArcCannonTargetsInRange( origin, team, weapon ) foreach( target in allTargets ) { - local d = DistanceSqr( target.GetOrigin(), origin ) - local validDist = target.IsTitan() ? titanDistSq : distSq + float d = DistanceSqr( target.GetOrigin(), origin ) + float validDist = target.IsTitan() ? titanDistSq : distSq if ( d <= validDist ) targetsInRange.append( target ) } return targetsInRange } - -function SortArcCannonTargets( weapon, targets ) -{ - Assert( targets.len() > 0 ) - local originalTargetCount = targets.len() - //printt( " sorting", originalTargetCount, "targets" ) - - local sortedTargets = [] - local lastEnt = weapon.GetWeaponOwner() - local closestIndex = null - local closestEnt = null - - while( targets.len() > 0 ) - { - closestEnt = null - closestIndex = null - - closestIndex = GetClosestIndex( targets, lastEnt.GetOrigin() ) - Assert( closestIndex != null ) - closestEnt = targets[ closestIndex ] - Assert( closestEnt != null ) - - sortedTargets.append( closestEnt ) - targets.remove( closestIndex ) - - lastEnt = closestEnt - } - - Assert( sortedTargets.len() == originalTargetCount ) - return sortedTargets -} +#endif // SERVER function CreateArcCannonBeam( weapon, target, startPos, endPos, player, lifeDuration = ARC_CANNON_BEAM_LIFETIME, radius = 256, boltWidth = 4, noiseAmplitude = 5, hasTarget = true, firstBeam = false ) { @@ -922,37 +949,38 @@ function CreateArcCannonBeam( weapon, target, startPos, endPos, player, lifeDura if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) ) lifeDuration = ARC_CANNON_BEAM_LIFETIME_BURN // If it's the first beam and on client we do a special beam so it's lined up with the muzzle origin - if ( IsClient() && firstBeam ) - thread CreateClientArcBeam( weapon, endPos, lifeDuration, target ) - - if ( IsClient() ) - return - - // Control point sets the end position of the effect - local cpEnd = CreateEntity( "info_placement_helper" ) - cpEnd.SetName( UniqueString( "arc_cannon_beam_cpEnd" ) ) - cpEnd.SetOrigin( endPos ) - DispatchSpawn( cpEnd, false ) - - local zapBeam = CreateEntity( "info_particle_system" ) - zapBeam.kv.cpoint1 = cpEnd.GetName() - - zapBeam.kv.effect_name = GetBeamEffect( weapon ) - - zapBeam.kv.start_active = 0 - zapBeam.SetOwner( player ) - zapBeam.SetOrigin( startPos ) - if ( firstBeam ) - { - zapBeam.kv.VisibilityFlags = 6 // everyone but owner - zapBeam.SetParent( player.GetActiveWeapon(), "muzzle_flash", false, 0.0 ) - } - DispatchSpawn( zapBeam ) + #if CLIENT + if ( firstBeam ) + thread CreateClientArcBeam( weapon, endPos, lifeDuration, target ) + #endif + + #if SERVER + // Control point sets the end position of the effect + entity cpEnd = CreateEntity( "info_placement_helper" ) + SetTargetName( cpEnd, UniqueString( "arc_cannon_beam_cpEnd" ) ) + cpEnd.SetOrigin( endPos ) + DispatchSpawn( cpEnd ) + + entity zapBeam = CreateEntity( "info_particle_system" ) + zapBeam.kv.cpoint1 = cpEnd.GetTargetName() + + zapBeam.SetValueForEffectNameKey( GetBeamEffect( weapon ) ) + + zapBeam.kv.start_active = 0 + zapBeam.SetOwner( player ) + zapBeam.SetOrigin( startPos ) + if ( firstBeam ) + { + zapBeam.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // everyone but owner + zapBeam.SetParent( player.GetActiveWeapon(), "muzzle_flash", false, 0.0 ) + } + DispatchSpawn( zapBeam ) - zapBeam.Fire( "Start" ) - zapBeam.Fire( "StopPlayEndCap", "", lifeDuration ) - zapBeam.Kill( lifeDuration ) - cpEnd.Kill( lifeDuration ) + zapBeam.Fire( "Start" ) + zapBeam.Fire( "StopPlayEndCap", "", lifeDuration ) + zapBeam.Kill_Deprecated_UseDestroyInstead( lifeDuration ) + cpEnd.Kill_Deprecated_UseDestroyInstead( lifeDuration ) + #endif } function GetBeamEffect( weapon ) @@ -963,14 +991,19 @@ function GetBeamEffect( weapon ) return ARC_CANNON_BEAM_EFFECT } +#if CLIENT function CreateClientArcBeam( weapon, endPos, lifeDuration, target ) { Assert( IsClient() ) local beamEffect = GetBeamEffect( weapon ) - weapon.PlayWeaponEffect( beamEffect, null, "muzzle_flash" ) - local handle = weapon.AllocateHandleForViewmodelEffect( beamEffect ) + // HACK HACK HACK HACK + string tag = "muzzle_flash" + if ( weapon.GetWeaponInfoFileKeyField( "client_tag_override" ) != null ) + tag = expect string( weapon.GetWeaponInfoFileKeyField( "client_tag_override" ) ) + + local handle = weapon.PlayWeaponEffectReturnViewEffectHandle( beamEffect, $"", tag ) if ( !EffectDoesExist( handle ) ) return @@ -982,25 +1015,33 @@ function CreateClientArcBeam( weapon, endPos, lifeDuration, target ) wait( lifeDuration ) if ( IsValid( weapon ) ) - weapon.StopWeaponEffect( beamEffect, null ) + weapon.StopWeaponEffect( beamEffect, $"" ) } -function ClientDestroyCallback_ArcCannon_Stop( entity ) +void function ClientDestroyCallback_ArcCannon_Stop( entity ent ) { - ArcCannon_Stop( entity ) + ArcCannon_Stop( ent ) } +#endif // CLIENT function GetArcCannonChargeFraction( weapon ) { if ( IsValid( weapon ) ) { local chargeRatio = ARC_CANNON_DAMAGE_CHARGE_RATIO - if( weapon.HasModDefined( "capacitor" ) && weapon.HasMod( "capacitor" ) ) + if ( weapon.HasMod( "capacitor" ) ) chargeRatio = ARC_CANNON_CAPACITOR_CHARGE_RATIO - if( weapon.GetWeaponModSetting( "is_burn_mod" ) ) + if ( weapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) ) chargeRatio = ARC_CANNON_DAMAGE_CHARGE_RATIO_BURN return chargeRatio } return 0 +} + +function GetWeaponChargeFrac( weapon ) +{ + if ( weapon.IsChargeWeapon() ) + return weapon.GetWeaponChargeFraction() + return 1.0 }
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/mp_titanweapon_arc_cannon.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_titanweapon_arc_cannon.nut index 09faafa5..78879393 100644 --- a/Northstar.Custom/mod/scripts/vscripts/weapons/mp_titanweapon_arc_cannon.nut +++ b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_titanweapon_arc_cannon.nut @@ -1,24 +1,50 @@ -ArcCannon_PrecacheFX( self ) +untyped -function OnWeaponActivate( activateParams ) +global function MpTitanweaponArcCannon_Init + +global function OnWeaponActivate_titanweapon_arc_cannon +global function OnWeaponDeactivate_titanweapon_arc_cannon +global function OnWeaponReload_titanweapon_arc_cannon +global function OnWeaponOwnerChanged_titanweapon_arc_cannon +global function OnWeaponChargeBegin_titanweapon_arc_cannon +global function OnWeaponChargeEnd_titanweapon_arc_cannon +global function OnWeaponPrimaryAttack_titanweapon_arc_cannon + +const FX_EMP_BODY_HUMAN = $"P_emp_body_human" +const FX_EMP_BODY_TITAN = $"P_emp_body_titan" + +#if SERVER +global function OnWeaponNpcPrimaryAttack_titanweapon_arc_cannon +#endif // #if SERVER + +void function MpTitanweaponArcCannon_Init() +{ + ArcCannon_PrecacheFX() + + #if SERVER + AddDamageCallbackSourceID( eDamageSourceId.mp_titanweapon_arc_cannon, ArcRifleOnDamage ) + #endif +} + +void function OnWeaponActivate_titanweapon_arc_cannon( entity weapon ) { - local weaponOwner = self.GetWeaponOwner() - thread DelayedArcCannonStart( self, weaponOwner ) - if( !("weaponOwner" in self.s) ) - self.s.weaponOwner <- weaponOwner + entity weaponOwner = weapon.GetWeaponOwner() + thread DelayedArcCannonStart( weapon, weaponOwner ) + if( !("weaponOwner" in weapon.s) ) + weapon.s.weaponOwner <- weaponOwner } -function DelayedArcCannonStart( weapon, weaponOwner ) +function DelayedArcCannonStart( entity weapon, entity weaponOwner ) { weapon.EndSignal( "WeaponDeactivateEvent" ) - wait 0 + WaitFrame() if ( IsValid( weapon ) && IsValid( weaponOwner ) && weapon == weaponOwner.GetActiveWeapon() ) { if( weaponOwner.IsPlayer() ) { - local modelEnt = weaponOwner.GetViewModelEntity() + entity modelEnt = weaponOwner.GetViewModelEntity() if( IsValid( modelEnt ) && EntHasModelSet( modelEnt ) ) ArcCannon_Start( weapon ) } @@ -29,91 +55,172 @@ function DelayedArcCannonStart( weapon, weaponOwner ) } } -function OnWeaponDeactivate( deactivateParams ) +void function OnWeaponDeactivate_titanweapon_arc_cannon( entity weapon ) { - ArcCannon_ChargeEnd( self, self.s.weaponOwner ) - ArcCannon_Stop( self ) + ArcCannon_ChargeEnd( weapon, expect entity( weapon.s.weaponOwner ) ) + ArcCannon_Stop( weapon ) } -function OnWeaponReload( reloadParams ) +void function OnWeaponReload_titanweapon_arc_cannon( entity weapon, int milestoneIndex ) { - local reloadTime = self.GetWeaponInfoFileKeyField( "reload_time" ) - thread ArcCannon_HideIdleEffect( self, reloadTime ) //constant seems to help it sync up better + local reloadTime = weapon.GetWeaponInfoFileKeyField( "reload_time" ) + thread ArcCannon_HideIdleEffect( weapon, reloadTime ) //constant seems to help it sync up better } -function OnWeaponOwnerChanged( changeParams ) +void function OnWeaponOwnerChanged_titanweapon_arc_cannon( entity weapon, WeaponOwnerChangedParams changeParams ) { - if ( IsClient() ) - { - local viewPlayer = GetLocalViewPlayer() + #if CLIENT + entity viewPlayer = GetLocalViewPlayer() if ( changeParams.oldOwner != null && changeParams.oldOwner == viewPlayer ) { - ArcCannon_ChargeEnd( self, changeParams.oldOwner ) - ArcCannon_Stop( self, changeParams.oldOwner ) + ArcCannon_ChargeEnd( weapon, changeParams.oldOwner ) + ArcCannon_Stop( weapon) } + if ( changeParams.newOwner != null && changeParams.newOwner == viewPlayer ) - thread ArcCannon_HideIdleEffect( self, 0.25 ) - } - else - { + thread ArcCannon_HideIdleEffect( weapon, 0.25 ) + #else if ( changeParams.oldOwner != null ) { - ArcCannon_ChargeEnd( self, changeParams.oldOwner ) - ArcCannon_Stop( self, changeParams.oldOwner ) + ArcCannon_ChargeEnd( weapon, changeParams.oldOwner ) + ArcCannon_Stop( weapon ) } + if ( changeParams.newOwner != null ) - thread ArcCannon_HideIdleEffect( self, 0.25 ) - } + thread ArcCannon_HideIdleEffect( weapon, 0.25 ) + #endif } -function OnWeaponChargeBegin( chargeParams ) +bool function OnWeaponChargeBegin_titanweapon_arc_cannon( entity weapon ) { - ArcCannon_ChargeBegin( self ) + local stub = "this is here to suppress the untyped message. This can go away when the .s. usage is removed from this file." + #if SERVER + //if ( weapon.HasMod( "fastpacitor_push_apart" ) ) + // weapon.GetWeaponOwner().StunMovementBegin( weapon.GetWeaponSettingFloat( eWeaponVar.charge_time ) ) + #endif + + ArcCannon_ChargeBegin( weapon ) + + return true } -function OnWeaponChargeEnd( chargeParams ) +void function OnWeaponChargeEnd_titanweapon_arc_cannon( entity weapon ) { - ArcCannon_ChargeEnd( self ) + ArcCannon_ChargeEnd( weapon, weapon ) } -function OnWeaponPrimaryAttack( attackParams ) +var function OnWeaponPrimaryAttack_titanweapon_arc_cannon( entity weapon, WeaponPrimaryAttackParams attackParams ) { - if ( self.HasMod( "capacitor" ) && self.GetWeaponChargeFraction() < GetArcCannonChargeFraction( self ) ) + if ( weapon.HasMod( "capacitor" ) && weapon.GetWeaponChargeFraction() < GetArcCannonChargeFraction( weapon ) ) return 0 if ( !attackParams.firstTimePredicted ) return - local fireRate = self.GetWeaponInfoFileKeyField( "fire_rate" ) - thread ArcCannon_HideIdleEffect( self, (1 / fireRate) ) - return FireArcCannon( self, attackParams ) -} + local fireRate = weapon.GetWeaponInfoFileKeyField( "fire_rate" ) + thread ArcCannon_HideIdleEffect( weapon, (1 / fireRate) ) -function OnWeaponNpcPrimaryAttack( attackParams ) -{ - local fireRate = self.GetWeaponInfoFileKeyField( "fire_rate" ) - thread ArcCannon_HideIdleEffect( self, fireRate ) - return FireArcCannon( self, attackParams ) + return FireArcCannon( weapon, attackParams ) } -function OnWeaponStartZoomIn() +#if SERVER +var function OnWeaponNpcPrimaryAttack_titanweapon_arc_cannon( entity weapon, WeaponPrimaryAttackParams attackParams ) { - HandleWeaponSoundZoomIn( self, "Weapon_Titan_ArcCannon.ADS_In" ) -} + local fireRate = weapon.GetWeaponInfoFileKeyField( "fire_rate" ) + thread ArcCannon_HideIdleEffect( weapon, fireRate ) -function OnWeaponStartZoomOut() -{ - HandleWeaponSoundZoomOut( self, "Weapon_Titan_ArcCannon.ADS_Out" ) + return FireArcCannon( weapon, attackParams ) } -/* -function OnWeaponPrimaryAttackVMActivityToUse() +void function ArcRifleOnDamage( entity ent, var damageInfo ) { - local baseCharge = self.GetWeaponChargeFraction() - local charge = clamp ( baseCharge * ( 1 / 0.7 ), 0.0, 1.0 ) + vector pos = DamageInfo_GetDamagePosition( damageInfo ) + entity attacker = DamageInfo_GetAttacker( damageInfo ) - if ( charge > 0.25 ) - return 1 + EmitSoundOnEntity( ent, ARC_CANNON_TITAN_SCREEN_SFX ) + + if ( ent.IsPlayer() || ent.IsNPC() ) + { + entity entToSlow = ent + entity soul = ent.GetTitanSoul() + + if ( soul != null ) + entToSlow = soul + + StatusEffect_AddTimed( entToSlow, eStatusEffect.move_slow, 0.5, 2.0, 1.0 ) + StatusEffect_AddTimed( entToSlow, eStatusEffect.dodge_speed_slow, 0.5, 2.0, 1.0 ) + } + + string tag = "" + asset effect + + if ( ent.IsTitan() ) + { + tag = "exp_torso_front" + effect = FX_EMP_BODY_TITAN + } + else if ( ChestFocusTarget( ent ) ) + { + tag = "CHESTFOCUS" + effect = FX_EMP_BODY_HUMAN + } + else if ( IsAirDrone( ent ) ) + { + tag = "HEADSHOT" + effect = FX_EMP_BODY_HUMAN + } + else if ( IsGunship( ent ) ) + { + tag = "ORIGIN" + effect = FX_EMP_BODY_TITAN + } + + if ( tag != "" ) + { + float duration = 2.0 + //thread EMP_FX( effect, ent, tag, duration ) + } + + if ( ent.IsTitan() ) + { + if ( ent.IsPlayer() ) + { + EmitSoundOnEntityOnlyToPlayer( ent, ent, "titan_energy_bulletimpact_3p_vs_1p" ) + EmitSoundOnEntityExceptToPlayer( ent, ent, "titan_energy_bulletimpact_3p_vs_3p" ) + } + else + { + EmitSoundOnEntity( ent, "titan_energy_bulletimpact_3p_vs_3p" ) + } + } else - return 0 -}*/ + { + if ( ent.IsPlayer() ) + { + EmitSoundOnEntityOnlyToPlayer( ent, ent, "flesh_lavafog_deathzap_3p" ) + EmitSoundOnEntityExceptToPlayer( ent, ent, "flesh_lavafog_deathzap_1p" ) + } + else + { + EmitSoundOnEntity( ent, "flesh_lavafog_deathzap_1p" ) + } + } + +} + +bool function ChestFocusTarget( entity ent ) +{ + if ( IsSpectre( ent ) ) + return true + if ( IsStalker( ent ) ) + return true + if ( IsSuperSpectre( ent ) ) + return true + if ( IsGrunt( ent ) ) + return true + if ( IsPilot( ent ) ) + return true + + return false +} +#endif // #if SERVER |