diff options
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/weapons')
10 files changed, 0 insertions, 8895 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_arc_cannon.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_arc_cannon.nut deleted file mode 100644 index 1601330c..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_arc_cannon.nut +++ /dev/null @@ -1,1032 +0,0 @@ -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 -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 -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 -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 -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 -global const ARC_CANNON_TARGETS_MISSILES = 1 // 1 = arc cannon zaps missiles that are active, 0 = missiles are ignored by arc cannon - -//Mods -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, -} - -struct { - array<string> missileCheckTargetnames = [ - // "Arc Pylon", - "Arc Ball" - ] -} file; - -function ArcCannon_Init() -{ - 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 ) - - #if CLIENT - AddDestroyCallback( "mp_titanweapon_arc_cannon", ClientDestroyCallback_ArcCannon_Stop ) - #else - level._arcCannonTargetsArrayID <- CreateScriptManagedEntArray() - #endif - - PrecacheParticleSystem( $"impact_arc_cannon_titan" ) -} - -function ArcCannon_PrecacheFX() -{ - PrecacheParticleSystem( $"wpn_arc_cannon_electricity_fp" ) - PrecacheParticleSystem( $"wpn_arc_cannon_electricity" ) - - PrecacheParticleSystem( $"wpn_muzzleflash_arc_cannon_fp" ) - PrecacheParticleSystem( $"wpn_muzzleflash_arc_cannon" ) -} - -function ArcCannon_Start( weapon ) -{ - 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 ) - - weapon.StopWeaponEffect( $"wpn_arc_cannon_electricity_fp", $"wpn_arc_cannon_electricity" ) - weapon.StopWeaponSound( "arc_cannon_charged_loop" ) -} - -function ArcCannon_ChargeBegin( entity weapon ) -{ - #if SERVER - if ( weapon.HasMod( "overcharge" ) ) - { - entity weaponOwner = weapon.GetWeaponOwner() - if ( weaponOwner.IsTitan() ) - { - entity soul = weaponOwner.GetTitanSoul() - thread ConvertTitanShieldIntoBonusCharge( soul, weapon ) - } - } - #endif - - #if CLIENT - if ( !weapon.ShouldPredictProjectiles() ) - return - - entity weaponOwner = weapon.GetWeaponOwner() - Assert( weaponOwner.IsPlayer() ) - weaponOwner.StartArcCannon(); - #endif -} - -function ArcCannon_ChargeEnd( entity weapon, entity player = null ) -{ - #if SERVER - if ( IsValid( weapon ) ) - weapon.Signal( ARC_CANNON_SIGNAL_CHARGEEND ) - #endif - - #if CLIENT - if ( weapon.GetWeaponOwner() == GetLocalViewPlayer() ) - { - entity weaponOwner - if ( player != null ) - weaponOwner = player - else - weaponOwner = weapon.GetWeaponOwner() - - if ( IsValid( weaponOwner ) && weaponOwner.IsPlayer() ) - weaponOwner.StopArcCannon() - } - #endif -} - -#if SERVER -function ConvertTitanShieldIntoBonusCharge( entity soul, entity weapon ) -{ - weapon.EndSignal( ARC_CANNON_SIGNAL_CHARGEEND ) - weapon.EndSignal( "OnDestroy" ) - - local maxShieldDecay = OVERCHARGE_MAX_SHIELD_DECAY - local bonusChargeFraction = OVERCHARGE_BONUS_CHARGE_FRACTION - local shieldDecayMultiplier = OVERCHARGE_SHIELD_DECAY_MULTIPLIER - int shieldHealthMax = soul.GetShieldHealthMax() - local chargeRatio = GetArcCannonChargeFraction( weapon ) - - while( 1 ) - { - if ( !IsValid( soul ) || !IsValid( weapon ) ) - break - - local baseCharge = GetWeaponChargeFrac( weapon ) // + GetOverchargeBonusChargeFraction() - local charge = clamp ( baseCharge * ( 1 / chargeRatio ), 0.0, 1.0 ) - if ( charge < 1.0 || maxShieldDecay > 0) - { - int shieldHealth = soul.GetShieldHealth() - - //Slight inconsistency in server updates, this ensures it never takes too much. - if ( shieldDecayMultiplier > maxShieldDecay ) - shieldDecayMultiplier = maxShieldDecay - maxShieldDecay -= shieldDecayMultiplier - - local shieldDecayAmount = shieldHealthMax * shieldDecayMultiplier - local newShieldAmount = shieldHealth - shieldDecayAmount - soul.SetShieldHealth( max( newShieldAmount, 0 ) ) - soul.nextRegenTime = Time() + GetShieldRegenTime( soul ) - - if ( shieldDecayAmount > shieldHealth ) - bonusChargeFraction = bonusChargeFraction * ( shieldHealth / shieldDecayAmount ) - weapon.SetWeaponChargeFraction( baseCharge + bonusChargeFraction ) - } - wait 0.1 - } -} -#endif - -function FireArcCannon( entity weapon, WeaponPrimaryAttackParams attackParams ) -{ - local weaponScriptScope = weapon.GetScriptScope() - 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" ) - - local attachmentName = "muzzle_flash" - local attachmentIndex = weapon.LookupAttachment( attachmentName ) - Assert( attachmentIndex >= 0 ) - local muzzleOrigin = weapon.GetAttachmentOrigin( attachmentIndex ) - - //printt( "-------- FIRING ARC CANNON --------" ) - - table firstTargetInfo = GetFirstArcCannonTarget( weapon, attackParams ) - if ( !IsValid( firstTargetInfo.target ) ) - FireArcNoTargets( weapon, attackParams, muzzleOrigin ) - else - FireArcWithTargets( weapon, firstTargetInfo, attackParams, muzzleOrigin ) - - return 1 -} - -table function GetFirstArcCannonTarget( entity weapon, WeaponPrimaryAttackParams attackParams ) -{ - entity owner = weapon.GetWeaponOwner() - local coneHeight = weapon.GetMaxDamageFarDist() - - 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.11 - antilagPlayer = owner - } - - 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 - table firstTargetInfo = {} - firstTargetInfo.target <- null - firstTargetInfo.hitLocation <- null - - for ( int i = 0; i < 2; i++ ) - { - local missileCheck = i == 0 - local coneAngle = angleToAxis - if ( missileCheck && owner.IsPlayer() ) // missile check only if owner is player - coneAngle *= 8.0 - - coneAngle = clamp( coneAngle, 0.1, 89.9 ) - - array<VisibleEntityInCone> results = FindVisibleEntitiesInCone( attackParams.pos, attackParams.dir, coneHeight, coneAngle, ignoredEntities, traceMask, flags, antilagPlayer ) - foreach ( result in results ) - { - entity visibleEnt = result.ent - - if ( !IsValid( visibleEnt ) ) - continue - - if ( visibleEnt.IsPhaseShifted() ) - continue - - local classname = IsServer() ? visibleEnt.GetClassName() : visibleEnt.GetSignifierName() - - if ( !( classname in ArcCannonTargetClassnames ) ) - continue - - if ( "GetTeam" in visibleEnt ) - { - int visibleEntTeam = visibleEnt.GetTeam() - if ( visibleEntTeam == ownerTeam ) - continue - if ( IsEntANeutralMegaTurret( visibleEnt, ownerTeam ) ) - continue - } - - expect string( classname ) - string targetname = visibleEnt.GetTargetName() - - if ( missileCheck && ( classname != "rpg_missile" && !file.missileCheckTargetnames.contains( targetname ) ) ) - continue - - if ( !missileCheck && ( classname == "rpg_missile" || file.missileCheckTargetnames.contains( targetname ) ) ) - continue - - firstTargetInfo.target = visibleEnt - firstTargetInfo.hitLocation = result.visiblePosition - break - } - } - //Creating a whiz-by sound. - weapon.FireWeaponBullet_Special( attackParams.pos, attackParams.dir, 1, 0, true, true, true, true, true, false, false ) - - return firstTargetInfo -} - -function FireArcNoTargets( entity weapon, WeaponPrimaryAttackParams attackParams, muzzleOrigin ) -{ - Assert( IsValid( weapon ) ) - entity player = weapon.GetWeaponOwner() - local chargeFrac = GetWeaponChargeFrac( weapon ) - local beamVec = attackParams.dir * weapon.GetMaxDamageFarDist() - local playerEyePos = player.EyePosition() - TraceResults traceResults = TraceLineHighDetail( playerEyePos, (playerEyePos + beamVec), weapon, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE ) - local beamEnd = traceResults.endPos - - VortexBulletHit ornull vortexHit = VortexBulletHitCheck( player, playerEyePos, beamEnd ) - if ( vortexHit ) - { - expect VortexBulletHit( vortexHit ) - #if SERVER - entity vortexWeapon = vortexHit.vortex.GetOwnerWeapon() - string className = vortexWeapon.GetWeaponClassName() - if ( vortexWeapon && ( className == "mp_titanweapon_vortex_shield" || className == "mp_titanweapon_vortex_shield_ion" ) ) - { - // drain the vortex shield - VortexDrainedByImpact( vortexWeapon, weapon, null, null ) - } - 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, 0.5, 0.0, 1.0 ) * 10 // do more damage the more charged the weapon is. - VortexSphereDrainHealthForDamage( vortexHit.vortex, damage ) - } - #endif - beamEnd = vortexHit.hitPos - } - - 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 SERVER - PlayImpactFXTable( expect vector( beamEnd ), player, ARC_CANNON_FX_TABLE, SF_ENVEXPLOSION_INCLUDE_ENTITIES ) - #endif -} - -function FireArcWithTargets( entity weapon, table firstTargetInfo, WeaponPrimaryAttackParams attackParams, muzzleOrigin ) -{ - local beamStart = muzzleOrigin - local beamEnd - 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 - - if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) ) - { - if ( player.IsNPC() ) - maxChains = ARC_CANNON_CHAIN_COUNT_NPC_BURN - else - maxChains = ARC_CANNON_CHAIN_COUNT_MAX_BURN - - minChains = ARC_CANNON_CHAIN_COUNT_MIN_BURN - } - else - { - if ( player.IsNPC() ) - maxChains = ARC_CANNON_CHAIN_COUNT_NPC - else - maxChains = ARC_CANNON_CHAIN_COUNT_MAX - - minChains = ARC_CANNON_CHAIN_COUNT_MIN - } - - if ( !player.IsNPC() ) - maxChains = Graph( chargeFrac, 0, 1, minChains, maxChains ) - - table zapInfo = {} - zapInfo.weapon <- weapon - zapInfo.player <- player - zapInfo.muzzleOrigin <- muzzleOrigin - zapInfo.radius <- radius - zapInfo.boltWidth <- boltWidth - zapInfo.maxChains <- maxChains - zapInfo.chargeFrac <- chargeFrac - zapInfo.zappedTargets <- {} - zapInfo.zappedTargets[ firstTargetInfo.target ] <- true - zapInfo.dmgSourceID <- weapon.GetDamageSourceID() - local chainNum = 1 - thread ZapTargetRecursive( expect entity( firstTargetInfo.target), zapInfo, zapInfo.muzzleOrigin, expect vector( firstTargetInfo.hitLocation ), chainNum ) -} - -function ZapTargetRecursive( entity target, table zapInfo, beamStartPos, vector ornull firstTargetBeamEndPos = null, chainNum = 1 ) -{ - if ( !IsValid( target ) ) - return - - if ( !IsValid( zapInfo.weapon ) ) - return - - Assert( target in zapInfo.zappedTargets ) - if ( chainNum > zapInfo.maxChains ) - return - vector beamEndPos - if ( firstTargetBeamEndPos == null ) - beamEndPos = target.GetWorldSpaceCenter() - else - beamEndPos = expect vector( firstTargetBeamEndPos ) - - waitthread ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum ) - - // Get other nearby targets we can chain to - #if SERVER - if ( !IsValid( zapInfo.weapon ) ) - return - - var noArcing = expect entity( zapInfo.weapon ).GetWeaponInfoFileKeyField( "disable_arc" ) - - if ( noArcing != null && noArcing == 1 ) - return // no chaining on new arc cannon - - // 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" ) - newChainNum++ - zapInfo.zappedTargets[ chainTarget ] <- true - thread ZapTargetRecursive( chainTarget, zapInfo, beamEndPos, null, newChainNum ) - } - - if ( IsValid( zapInfo.player ) && zapInfo.player.IsPlayer() && zapInfo.zappedTargets.len() >= 5 ) - { - #if HAS_STATS - if ( chainNum == 5 ) - 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 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 SERVER - local isMissile = ( target.GetClassName() == "rpg_missile" ) - if ( !isMissile ) - wait ARC_CANNON_FORK_DELAY - else - wait 0.05 - - local deathPackage = damageTypes.arcCannon - - float damageAmount - int damageMin - int 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 ) ) - { - bool hasFastPacitor = false - bool noArcing = false - - if ( IsValid( zapInfo.weapon ) ) - { - 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 - } - - if ( target.GetArmorType() == ARMOR_TYPE_HEAVY ) - { - 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 - } - } - else - { - 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 - } - } - - - local chargeRatio = GetArcCannonChargeFraction( zapInfo.weapon ) - if ( IsValid( zapInfo.weapon ) && !zapInfo.weapon.GetWeaponSettingBool( eWeaponVar.charge_require_input ) ) - { - // 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 - { - // 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 - - // 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 ) - { - 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 ) - } - } - - // 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 } ) - //vector dir = Normalize( beamEndPos - beamStartPos ) - //vector velocity = dir * 600 - //PushPlayerAway( target, velocity ) - //PushPlayerAway( expect entity( zapInfo.player ), -velocity ) - - 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 ) - - if ( pushPercent > 0.6 ) - PushPlayersApart( target, expect entity( zapInfo.player ), pushPercent * 400.0 ) - } - } - - if ( zapInfo.chargeFrac < ARC_CANNON_SCREEN_THRESHOLD ) - empDuration = ARC_CANNON_3RD_PERSON_EFFECT_MIN_DURATION - else - empDuration += ARC_CANNON_EMP_FADEOUT_DURATION - - if ( target.GetArmorType() == ARMOR_TYPE_HEAVY ) - { - effect = $"impact_arc_cannon_titan" - tag = "exp_torso_front" - } - else - { - effect = $"P_emp_body_human" - tag = "CHESTFOCUS" - } - - if ( target.IsPlayer() ) - { - if ( target.LookupAttachment( tag ) != 0 ) - ClientStylePlayFXOnEntity( effect, target, tag, empDuration ) - } - - 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.MissileExplode() - } - } - #endif // SERVER -} - - -#if SERVER - -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() - } -} - -array<entity> function GetArcCannonChainTargets( vector fromOrigin, entity fromTarget, table zapInfo ) -{ - // NOTE: fromTarget could be null/invalid if it was a drone - array<entity> results = [] - if ( !IsValid( zapInfo.player ) ) - return results - - 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 = AnglesToForward( zapInfo.player.EyeAngles() ) - - local eyePosition = zapInfo.player.EyePosition() - - foreach ( ent in allTargets ) - { - local forkCount = ARC_CANNON_FORK_COUNT_MAX - if ( zapInfo.weapon.HasMod( "splitter" ) ) - forkCount = SPLITTER_FORK_COUNT_MAX - else if ( zapInfo.weapon.HasMod( "burn_mod_titan_arc_cannon" ) ) - forkCount = ARC_CANNON_FORK_COUNT_MAX_BURN - - if ( results.len() >= forkCount ) - break - - if ( ent.IsPhaseShifted() ) - continue - - if ( ent.IsPlayer() ) - { - // 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" ) - continue - - if ( IsEntANeutralMegaTurret( ent, playerTeam ) ) - continue - - if ( !IsAlive( ent ) ) - continue - - // Don't consider targets that already got zapped - if ( ent in zapInfo.zappedTargets ) - continue - - //Preventing the arc-cannon from firing behind. - local vecToEnt = ( ent.GetWorldSpaceCenter() - eyePosition ) - vecToEnt.Norm() - local dotVal = DotProduct( vecToEnt, viewVector ) - if ( dotVal < 0 ) - continue - - // Check if we can see them, they aren't behind a wall or something - local ignoreEnts = [] - ignoreEnts.append( zapInfo.player ) - ignoreEnts.append( ent ) - - foreach( zappedTarget, val in zapInfo.zappedTargets ) - { - if ( IsValid( zappedTarget ) ) - ignoreEnts.append( zappedTarget ) - } - - 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 ) - { - // '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 ( !results.contains( ent ) ) - results.append( ent ) - } - - //printt( "NEARBY TARGETS VALID AND VISIBLE:", results.len() ) - - return results -} -#endif // SERVER - -bool function IsEntANeutralMegaTurret( ent, int playerTeam ) -{ - expect entity( ent ) - - if ( ent.GetClassName() != "npc_turret_mega" ) - return false - int entTeam = ent.GetTeam() - if ( entTeam == playerTeam ) - return false - if ( !IsEnemyTeam( playerTeam, entTeam ) ) - return true - - return false -} - -function ArcCannon_HideIdleEffect( entity weapon, delay ) -{ - bool weaponOwnerIsPilot = IsPilot( weapon.GetWeaponOwner() ) - weapon.EndSignal( ARC_CANNON_SIGNAL_DEACTIVATED ) - 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 ) ) - return - - 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 ) ) - return - - if ( weapon != weaponOwner.GetActiveWeapon() ) - return - - 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" ) - } -} - -#if SERVER -void function AddToArcCannonTargets( entity ent ) -{ - AddToScriptManagedEntArray( level._arcCannonTargetsArrayID, ent ); -} - -function RemoveArcCannonTarget( ent ) -{ - RemoveFromScriptManagedEntArray( level._arcCannonTargetsArrayID, ent ) -} - -array<entity> function GetArcCannonTargets( vector origin, int team ) -{ - array<entity> targets = GetScriptManagedEntArrayWithinCenter( level._arcCannonTargetsArrayID, team, origin, ARC_CANNON_TITAN_RANGE_CHAIN ) - - if ( ARC_CANNON_TARGETS_MISSILES ) - targets.extend( GetProjectileArrayEx( "rpg_missile", TEAM_ANY, team, origin, ARC_CANNON_TITAN_RANGE_CHAIN ) ) - - return targets -} - -array<entity> function GetArcCannonTargetsInRange( vector origin, int team, entity weapon ) -{ - array<entity> allTargets = GetArcCannonTargets( origin, team ) - array<entity> targetsInRange - - 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 - distSq = ARC_CANNON_RANGE_CHAIN_BURN * ARC_CANNON_RANGE_CHAIN_BURN - } - else - { - titanDistSq = ARC_CANNON_TITAN_RANGE_CHAIN * ARC_CANNON_TITAN_RANGE_CHAIN - distSq = ARC_CANNON_RANGE_CHAIN * ARC_CANNON_RANGE_CHAIN - } - - foreach( target in allTargets ) - { - float d = DistanceSqr( target.GetOrigin(), origin ) - float validDist = target.IsTitan() ? titanDistSq : distSq - if ( d <= validDist ) - targetsInRange.append( target ) - } - - return targetsInRange -} -#endif // SERVER - -function CreateArcCannonBeam( weapon, target, startPos, endPos, player, lifeDuration = ARC_CANNON_BEAM_LIFETIME, radius = 256, boltWidth = 4, noiseAmplitude = 5, hasTarget = true, firstBeam = false ) -{ - Assert( startPos ) - Assert( endPos ) - - //************************** - // LIGHTNING BEAM EFFECT - //************************** - 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 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_Deprecated_UseDestroyInstead( lifeDuration ) - cpEnd.Kill_Deprecated_UseDestroyInstead( lifeDuration ) - #endif -} - -function GetBeamEffect( weapon ) -{ - if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) ) - return ARC_CANNON_BEAM_EFFECT_MOD - - return ARC_CANNON_BEAM_EFFECT -} - -#if CLIENT -function CreateClientArcBeam( weapon, endPos, lifeDuration, target ) -{ - Assert( IsClient() ) - - local beamEffect = GetBeamEffect( weapon ) - - // 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 - - EffectSetControlPointVector( handle, 1, endPos ) - - if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) ) - lifeDuration = ARC_CANNON_BEAM_LIFETIME_BURN - - wait( lifeDuration ) - - if ( IsValid( weapon ) ) - weapon.StopWeaponEffect( beamEffect, $"" ) -} - -void function ClientDestroyCallback_ArcCannon_Stop( entity ent ) -{ - ArcCannon_Stop( ent ) -} -#endif // CLIENT - -function GetArcCannonChargeFraction( weapon ) -{ - if ( IsValid( weapon ) ) - { - local chargeRatio = ARC_CANNON_DAMAGE_CHARGE_RATIO - if ( weapon.HasMod( "capacitor" ) ) - chargeRatio = ARC_CANNON_CAPACITOR_CHARGE_RATIO - 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.CustomServers/scripts/vscripts/weapons/_at_turrets.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_at_turrets.gnut deleted file mode 100644 index b061c182..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_at_turrets.gnut +++ /dev/null @@ -1,284 +0,0 @@ -untyped - -global function ATTurrets_Init -global function CreateATTurret -global function ATTurretSettings -//global function SetDriverOnTurret -global function PROTO_ATTurretsEnabled -global function PROTO_Simulate_Turret_Use - -const USE_DEBOUNCE_TIME = 0.3 -const FX_ANTI_TITAN_SHIELD_WALL = $"P_anti_titan_shield_3P" -const vector AT_TURRET_SHIELD_COLOR = Vector( 115, 247, 255 ) - -void function ATTurrets_Init() -{ - AddSpawnCallbackEditorClass( "turret", "turret_pilot_at", ATTurretSettings ) - RegisterSignal( "ClearDriver" ) - RegisterSignal( "DismebarkATTurret" ) -} - -void function CreateATTurret( vector origin, vector angles ) -{ - entity turret = CreateEntity( "turret" ) - turret.kv.editorclass = "turret_pilot_at" - turret.kv.settings = "PROTO_at_turret" - turret.kv.teamnumber = 0 - turret.SetValueForModelKey( $"models/weapons/sentry_turret/sentry_turret.mdl" ) - turret.kv.origin = origin - turret.kv.angles = angles - DispatchSpawn( turret ) - ATTurretSettings( turret ) -} - -void function ATTurretSettings( entity turret ) -{ - if ( PROTO_ATTurretsEnabled() ) - { - turret.SetUsable() - turret.SetUsableByGroup( "pilot" ) - turret.SetUsePrompts( "Hold %use% to use AT-Turret", "Press %use% to use AT-Turret" ) - //AddCallback_OnUseEntity( turret, SetDriverOnTurret ) - AddCallback_OnUseEntity( turret, PROTO_Simulate_Turret_Use ) - - local attachmentID = turret.LookupAttachment( "muzzle_flash" ) - local origin = turret.GetAttachmentOrigin( attachmentID ) - local angles = turret.GetAttachmentAngles( attachmentID ) - - entity cpoint = CreateEntity( "info_placement_helper" ) - SetTargetName( cpoint, UniqueString( "shield_wall_controlpoint" ) ) - DispatchSpawn( cpoint ) - - turret.e.shieldWallFX = CreateEntity( "info_particle_system" ) - entity shieldWallFX = turret.e.shieldWallFX - shieldWallFX.SetValueForEffectNameKey( FX_ANTI_TITAN_SHIELD_WALL ) - shieldWallFX.kv.start_active = 1 - SetShieldWallCPoint( shieldWallFX, cpoint ) - shieldWallFX.SetOwner( turret ) - shieldWallFX.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // not owner only - shieldWallFX.kv.cpoint1 = cpoint.GetTargetName() - shieldWallFX.SetStopType( "destroyImmediately" ) - shieldWallFX.SetOrigin( origin ) - shieldWallFX.SetAngles( angles - Vector(0,0,90) ) - shieldWallFX.SetParent( turret, "muzzle_flash", true, 0.0 ) - DispatchSpawn( shieldWallFX ) - SetShieldWallCPointOrigin( shieldWallFX, AT_TURRET_SHIELD_COLOR ) - } - else - { - turret.DisableDraw() - turret.NotSolid() - } -} - -bool function PROTO_ATTurretsEnabled() -{ - return ( GetCurrentPlaylistVarInt( "at_turrets_enabled", 0 ) == 1 ) -} - -/*///////////////////////////////////////////////////////////////// - WEAPON PROTOTYPE -///////////////////////////////////////////////////////////////////*/ - -function PROTO_Simulate_Turret_Use( turret, player ) -{ - expect entity( turret ) - expect entity( player ) - - if ( Time() < player.p.PROTO_UseDebounceEndTime ) - return - - PROTO_ActivateTurret( turret, player ) -} - -const array<int> TURRET_CANCEL_BUTTONS = -[ - IN_USE, - IN_DUCK, - IN_DUCKTOGGLE, - IN_WEAPON_CYCLE, - IN_MELEE, - IN_OFFHAND0, - IN_OFFHAND1, - IN_OFFHAND2, - IN_OFFHAND3, - IN_OFFHAND4, - IN_JUMP -] - -void function PROTO_ActivateTurret( entity turret, entity player ) -{ - if ( turret.GetOwner() == player ) - { - player.Signal( "DismebarkATTurret" ) - } - else - { - if ( turret.GetOwner() == null ) - { - turret.DisableDraw() - turret.NotSolid() - SetShieldWallCPointOrigin( turret.e.shieldWallFX, < 0, 0, 0 > ) - turret.SetOwner( player ) - player.p.PROTO_UseDebounceEndTime = Time() + USE_DEBOUNCE_TIME - foreach( int button in TURRET_CANCEL_BUTTONS ) - AddButtonPressedPlayerInputCallback( player, button, PROTO_DisembarkATTurret ) - AddEntityCallback_OnDamaged( player, PlayerDamagedWhileOnTurret ) - thread MonitorPilot( turret, player ) - } - else - { - SendHudMessage( player, "Turret in use.", -1, 0.4, 255, 255, 0, 255, 0.0, 0.5, 0.0 ) - } - } -} - -void function PlayerDamagedWhileOnTurret( entity player, var damageInfo ) -{ - if ( Time() < player.p.PROTO_UseDebounceEndTime ) - return - - player.Signal( "DismebarkATTurret" ) -} - -function MonitorPilot( entity turret, entity player ) -{ - player.EndSignal( "OnDestroy" ) - player.EndSignal( "DismebarkATTurret") - turret.EndSignal( "OnDestroy" ) - - player.ForceStand() - entity playerMover = CreateOwnedScriptMover( player ) - player.SetParent( playerMover, "ref", true ) - vector forward = turret.GetForwardVector() - vector basePos = turret.GetOrigin() + forward * -25 - vector startOrigin = player.GetOrigin() - float moveTime = 0.1 - playerMover.NonPhysicsMoveTo( basePos, moveTime, 0.0, 0.0 ) - playerMover.NonPhysicsRotateTo( turret.GetAngles(), moveTime, 0, 0 ) - player.FreezeControlsOnServer() - - StorePilotWeapons( player ) - - OnThreadEnd( - function() : ( turret, player, playerMover, startOrigin ) - { - if ( IsValid( player ) ) - { - player.ClearParent() - player.UnforceStand() - ClearPlayerAnimViewEntity( player ) - player.UnfreezeControlsOnServer() - RetrievePilotWeapons( player ) - ViewConeZeroInstant( player ) - foreach( int button in TURRET_CANCEL_BUTTONS ) - RemoveButtonPressedPlayerInputCallback( player, button, PROTO_DisembarkATTurret ) - RemoveEntityCallback_OnDamaged( player, PlayerDamagedWhileOnTurret ) - player.p.PROTO_UseDebounceEndTime = Time() + USE_DEBOUNCE_TIME - PutEntityInSafeSpot( player, turret, null, startOrigin, player.GetOrigin() ) - } - - if ( IsValid( turret ) ) - { - turret.EnableDraw() - turret.Solid() - SetShieldWallCPointOrigin( turret.e.shieldWallFX, AT_TURRET_SHIELD_COLOR ) - turret.SetOwner( null ) - } - - playerMover.Destroy() - } - ) - - wait moveTime - - player.PlayerCone_SetSpecific( forward ) - ViewConeZeroInstant( player ) - - // PROTO: Supporting ability to pick different turret weapons for turrets in LevelEd and the legacy Defender prototype turret - // We need a predator cannon style turret in SP. - if ( IsMultiplayer() ) - { - //player.GiveWeapon( "mp_weapon_smr", [ "PROTO_at_turret" ] ) - //player.SetActiveWeaponByName( "mp_weapon_smr" ) - - // modded code: smr's at turret mod does exist in release tf2 - player.GiveWeapon( "mp_weapon_defender", [ "PROTO_at_turret" ] ) - player.SetActiveWeaponByName( "mp_weapon_defender" ) - } - else if ( turret.HasKey( "weaponsettings" ) ) - { - // See if we have any special turret mods on this weapon - array<string> turretMods = [] - array<string> mods = GetWeaponMods_Global( turret.kv.weaponsettings ) - foreach ( mod in mods ) - { - if ( mod.find( "PROTO_at_turret" ) == 0 ) - turretMods.append( "PROTO_at_turret" ) - } - - player.GiveWeapon( turret.kv.weaponsettings, turretMods ) - player.SetActiveWeaponByName( turret.kv.weaponsettings ) - } - - wait 0.1 - - player.UnfreezeControlsOnServer() - - ViewConeLockedForward( player ) - - player.WaitSignal( "OnDeath" ) -} - -void function PROTO_DisembarkATTurret( entity player ) -{ - if ( Time() < player.p.PROTO_UseDebounceEndTime ) - return - - player.Signal( "DismebarkATTurret" ) -} - -/*///////////////////////////////////////////////////////////////// - TURRET ENTITY PROTOTYPE -///////////////////////////////////////////////////////////////////*/ -// -//function SetDriverOnTurret( turret, player ) -//{ -// if ( turret.GetOwner() == player ) -// { -// turret.SetOwner( null ) -// turret.ClearDriver() -// player.Signal( "ClearDriver" ) -// } -// else -// { -// entity oldOwner = expect entity( turret.GetOwner() ) -// if ( oldOwner != null ) -// { -// oldOwner.Signal( "ClearDriver" ) -// turret.ClearDriver() -// } -// turret.SetOwner( player ) -// turret.SetDriver( player ) -// thread ClearDriverOnDeath( turret, player ) -// } -// //turret.SetUsePrompts( "DEACTIVATE", "DEACTIVATE" ) -// //turret.SetOwner( player ) -// //turret.SetBossPlayer( player ) -// //turret.SetUsableByGroup( "owner pilot" ) -//} -// -//function ClearDriverOnDeath( turret, player ) -//{ -// player.EndSignal( "ClearDriver" ) -// player.EndSignal( "OnDestroy" ) -// turret.EndSignal( "OnDestroy" ) -// -// player.WaitSignal( "OnDeath" ) -// -// if ( IsValid( turret ) ) -// turret.ClearDriver() -//} -////TODO: Handle death and handle deactivate. -// -//
\ No newline at end of file diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_ball_lightning.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_ball_lightning.gnut deleted file mode 100644 index 9aae59e5..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_ball_lightning.gnut +++ /dev/null @@ -1,363 +0,0 @@ -untyped - -global function BallLightning_Init -global function AttachBallLightning -global function AttachBallLightningToProp -global function CreateBallLightning -global function DestroyBallLightningOnEnt -global function GetBallLightningFromEnt - -global function RegisterBallLightningDamage - -global function BallLightningZapFX -global function BallLightningZapTargets -global function BallLightningZapConnectionFX - -struct { - table< string, float > uniqueStrings -} file - -function BallLightning_Init() -{ - PrecacheParticleSystem( BALL_LIGHTNING_ZAP_FX ) - - if ( BALL_LIGHTNING_FX_TABLE != "" ) - PrecacheImpactEffectTable( BALL_LIGHTNING_FX_TABLE ) - - RegisterBallLightningDamage( eDamageSourceId.mp_weapon_arc_launcher ) - RegisterBallLightningDamage( eDamageSourceId.mp_titanweapon_arc_ball ) - RegisterBallLightningDamage( eDamageSourceId.mp_weapon_arc_trap ) -} - -function AttachBallLightning( entity weapon, entity projectile ) -{ - Assert( !( "ballLightning" in projectile.s ) ) - - int damageSourceId - entity owner - - if ( weapon.IsProjectile() ) - { - owner = weapon.GetOwner() - damageSourceId = weapon.ProjectileGetDamageSourceID() - } - else - { - owner = weapon.GetWeaponOwner() - damageSourceId = weapon.GetDamageSourceID() - } - - - entity ball = CreateBallLightning( owner, damageSourceId, projectile.GetOrigin(), projectile.GetAngles() ) - ball.SetParent( projectile ) - projectile.s.ballLightning <- ball -} - -void function DestroyBallLightningOnEnt( entity prop ) -{ - if ( "ballLightning" in prop.s ) - { - prop.s.ballLightning.Destroy() - delete prop.s.ballLightning - } -} - - -entity function AttachBallLightningToProp( entity prop, entity owner, int damageSourceId ) -{ - entity ball = CreateBallLightning( owner, damageSourceId, prop.GetOrigin(), prop.GetAngles() ) - ball.SetParent( prop ) - prop.s.ballLightning <- ball - return ball -} - -entity function CreateBallLightning( entity owner, int damageSourceId, vector origin, vector angles ) -{ - entity ballLightning = CreateScriptMover( origin, angles ) - ballLightning.SetOwner( owner ) - SetTeam( ballLightning, owner.GetTeam() ) - - thread BallLightningThink( ballLightning, damageSourceId ) - return ballLightning -} - -void function RegisterBallLightningDamage( int damageSourceId ) -{ - AddDamageCallbackSourceID( damageSourceId, OnBallLightningDamage ) -} - -void function OnBallLightningDamage( entity victim, var damageInfo ) -{ - float damage = DamageInfo_GetDamage( damageInfo ) - - if ( damage <= 0 ) - return - - if ( victim.IsWorld() ) - return - - if ( victim.IsProjectile() ) - return - - if ( DamageInfo_GetCustomDamageType( damageInfo ) & (DF_EXPLOSION | DF_IMPACT) ) - return - - // if ( IsHumanSized( victim ) ) - // { - // DamageInfo_SetDamage( damageInfo, 0 ) - // return - // } - - entity ballLightning = DamageInfo_GetInflictor( damageInfo ) - - if ( victim == ballLightning ) - return - - if ( victim.GetParent() == ballLightning ) - return - - if ( !IsTargetEntValid( ballLightning, victim, ballLightning.e.ballLightningData ) ) - { - DamageInfo_SetDamage( damageInfo, 0 ) - return - } - - vector origin = DamageInfo_GetDamagePosition( damageInfo ) - int hitBox = DamageInfo_GetHitBox( damageInfo ) - - string tag = GetEntityCenterTag( victim ) - thread BallLightningZapConnectionFX( ballLightning, victim, tag, ballLightning.e.ballLightningData ) - BallLightningZapFX( ballLightning, victim, tag, ballLightning.e.ballLightningData ) -} - -void function BallLightningThink( entity ballLightning, int damageSourceId ) -{ - ballLightning.EndSignal( "OnDestroy" ) - - EmitSoundOnEntity( ballLightning, "Weapon_Arc_Ball_Loop" ) - - local data = {} - - OnThreadEnd( - function() : ( ballLightning, data ) - { - if ( IsValid( ballLightning ) ) - StopSoundOnEntity( ballLightning, "Weapon_Arc_Ball_Loop" ) - } - ) - - int inflictorTeam = ballLightning.GetTeam() - ballLightning.e.ballLightningTargetsIdx = CreateScriptManagedEntArray() - - WaitEndFrame() - - while( 1 ) - { - for( int i=0; i<BALL_LIGHTNING_BURST_NUM; i++ ) - { - if ( BALL_LIGHTNING_BURST_NUM > 1 ) - wait BALL_LIGHTNING_BURST_PAUSE - - vector origin = ballLightning.GetOrigin() - BallLightningZapTargets( ballLightning, origin, inflictorTeam, damageSourceId, ballLightning.e.ballLightningData, false ) - } - wait BALL_LIGHTNING_BURST_DELAY - } -} - -void function BallLightningZapTargets( entity ballLightning, vector origin, int inflictorTeam, int damageSourceId, BallLightningData fxData, bool single ) -{ - RadiusDamage( - origin, // origin - ballLightning.GetOwner(), // owner - ballLightning, // inflictor - fxData.damageToPilots, // normal damage - fxData.damage, // heavy armor damage - fxData.radius, // inner radius - fxData.radius, // outer radius - SF_ENVEXPLOSION_NO_DAMAGEOWNER, // explosion flags - 0, // distanceFromAttacker - 0, // explosionForce - fxData.deathPackage, // damage flags - damageSourceId // damage source id - ) -} - -string function GetEntityCenterTag( entity target ) -{ - string tag = "center" - - if ( IsHumanSized( target ) ) - tag = "CHESTFOCUS" - else if ( target.IsTitan() ) - tag = "HIJACK" - else if ( IsSuperSpectre( target ) || IsAirDrone( target ) ) - tag = "CHESTFOCUS" - else if ( IsDropship( target ) ) - tag = "ORIGIN" - else if ( target.GetClassName() == "npc_turret_mega" ) - tag = "ATTACH" - - return tag -} - -bool function IsTargetEntValid( entity ballLightning, entity target, BallLightningData fxData ) -{ - if ( !IsValid( target ) ) - return false - - vector origin = ballLightning.GetOrigin() - - if ( target == ballLightning ) - return false - - if ( target == ballLightning.GetParent() ) - return false - - if ( target.GetParent() == ballLightning.GetParent() ) - return false - - // if ( target.IsPlayer() && !target.IsTitan() ) - // return false - - if ( fabs( origin.z - target.GetOrigin().z ) > fxData.height ) - return false - - if ( GetBugReproNum() != 131703 ) - { - if ( target.GetModelName() == $"" ) - return false - } - - if ( !( target.GetClassName() in ArcCannonTargetClassnames ) ) - return false - - vector entityCenter = target.GetWorldSpaceCenter() - - if ( target.GetModelName() != $"" ) - { - string tag = GetEntityCenterTag( target ) - int index = target.LookupAttachment( tag ) - - if ( index == 0 ) - return false - - entityCenter = target.GetAttachmentOrigin( index ) - } - - vector fwd = AnglesToForward( ballLightning.GetAngles() ) - vector fwdToEnemy = Normalize( entityCenter - ballLightning.GetOrigin() ) - - float dot = DotProduct( fwd, fwdToEnemy ) - - if ( dot < fxData.minDot ) - return false - - - if ( IsHumanSized( target ) ) - { - float maxDist = fxData.humanRadius - if ( Distance( entityCenter, ballLightning.GetOrigin() ) > maxDist ) - return false - } - - // array<entity> ignoreEnts = [ target, ballLightning ] - // if ( ballLightning.GetParent() != null ) - // ignoreEnts.append( ballLightning.GetParent() ) - - // TraceResults trace = TraceLine( ballLightning.GetOrigin(), entityCenter, ignoreEnts, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS ) - - // if ( trace.fraction < 1 ) - // return false - - VortexBulletHit ornull vortexHit = VortexBulletHitCheck( ballLightning.GetOwner(), ballLightning.GetOrigin(), entityCenter ) - - if ( vortexHit ) - return false - - return true -} - -void function BallLightningZapConnectionFX( entity ballLightning, entity target, string tag, BallLightningData fxData ) -{ - if ( fxData.zapFx != $"" ) - { - // Control point sets the end position of the effect - entity cpEnd = CreateEntity( "info_placement_helper" ) - SetTargetName( cpEnd, GetUniqueCpString() ) - cpEnd.SetParent( target, tag, false, 0.0 ) - DispatchSpawn( cpEnd ) - - entity zapBeam = CreateEntity( "info_particle_system" ) - zapBeam.kv.cpoint1 = cpEnd.GetTargetName() - - zapBeam.SetValueForEffectNameKey( fxData.zapFx ) - zapBeam.kv.start_active = 0 - zapBeam.SetOwner( ballLightning ) - zapBeam.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE - zapBeam.SetParent( ballLightning, "", false, 0.0 ) - DispatchSpawn( zapBeam ) - - zapBeam.Fire( "Start" ) - - OnThreadEnd( - function() : ( zapBeam, cpEnd ) - { - if ( IsValid( zapBeam ) ) - zapBeam.Destroy() - if ( IsValid( cpEnd ) ) - cpEnd.Destroy() - } - ) - - ballLightning.EndSignal( "OnDestroy" ) - target.EndSignal( "OnDestroy" ) - target.EndSignal( "OnDeath" ) - - if ( fxData.zapLifetime > 0 ) - { - wait fxData.zapLifetime - } - } -} - -void function BallLightningZapFX( entity ballLightning, entity target, string tag, BallLightningData fxData ) -{ - int index = target.LookupAttachment( tag ) - - vector entityCenter = target.GetAttachmentOrigin( index ) - - if ( fxData.zapImpactTable != "" ) - PlayImpactFXTable( entityCenter, ballLightning.GetOwner(), fxData.zapImpactTable, SF_ENVEXPLOSION_INCLUDE_ENTITIES ) - - EmitSoundOnEntity( ballLightning, fxData.zapSound ) - thread FadeOutSoundOnEntityAfterDelay( ballLightning, fxData.zapSound, 0.2, 0.2 ) -} - -// This is to minimize creation of new Unique Strings -string function GetUniqueCpString() -{ - foreach ( string uString, float useTime in file.uniqueStrings ) - { - if ( useTime + BALL_LIGHTNING_ZAP_LIFETIME*2 > Time() ) - continue - - file.uniqueStrings[ uString ] = Time() - return uString - } - - string newString = UniqueString( "ball_lightning_cpEnd" ) - - // printt( "Generated new string " + newString ) - - file.uniqueStrings[ newString ] <- Time() - return newString -} - -entity function GetBallLightningFromEnt( entity ent ) -{ - if ( "ballLightning" in ent.s ) - return expect entity( ent.s.ballLightning ) - - return null -}
\ No newline at end of file diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_cloaker.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_cloaker.gnut deleted file mode 100644 index 6ec0bc0a..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_cloaker.gnut +++ /dev/null @@ -1,121 +0,0 @@ -untyped - -global function CloakerThink -global function CloakerShouldCloakGuy - -global function CloakerCloaksGuy -global function CloakerDeCloaksGuy - -function CloakerThink( entity cloaker, float radius, array<string> ents = [ "any" ], vector offset = Vector(0,0,0), var shouldCloakGuyFunc = null, float waitTime = 0.25 ) -{ - OnThreadEnd( - function() : ( cloaker ) - { - local cloakList = clone cloaker.s.cloakList - foreach ( entity guy, value in cloakList ) - { - if ( !IsAlive( guy ) ) - continue - - CloakerDeCloaksGuy( guy ) - } - } - ) - - cloaker.s.cloakList <- {} - cloaker.s.decloakList <- {} - - while( 1 ) - { - vector origin = cloaker.GetOrigin() + offset - array<entity> guys - - foreach ( entType in ents ) - { - switch ( entType ) - { - case "player": - case "players": - guys.extend( GetPlayerArrayEx( "any", cloaker.GetTeam(), TEAM_ANY, origin, radius ) ) - break; - default: - guys.extend( GetNPCArrayEx( entType, cloaker.GetTeam(), TEAM_ANY, origin, radius ) ) - break - } - } - int index = 0 - - float startTime = Time() - - table cloakList = expect table( cloaker.s.cloakList ) - cloaker.s.decloakList = clone cloakList - - foreach ( guy in guys ) - { - //only do 5 distanceSqr / cansee checks per frame - if ( index++ > 5 ) - { - wait 0.1 - index = 0 - origin = cloaker.GetOrigin() + offset - } - - bool shouldCloakGuy = CloakerShouldCloakGuy( cloaker, guy ) - - if ( shouldCloakGuy ) - shouldCloakGuy = expect bool( shouldCloakGuyFunc( cloaker, guy ) ) - - if ( shouldCloakGuy ) - { - if ( guy in cloaker.s.decloakList ) - delete cloaker.s.decloakList[ guy ] - - if ( IsCloaked( guy ) ) - continue - - cloakList[ guy ] <- true - CloakerCloaksGuy( guy ) - } - } - - foreach ( entity guy, value in cloaker.s.decloakList ) - { - // any guys still in the decloakList shouldn't be decloaked ... if alive. - Assert( guy in cloakList ) - delete cloakList[ guy ] - - if ( IsAlive( guy ) ) - CloakerDeCloaksGuy( guy ) - } - - float endTime = Time() - float elapsedTime = endTime - startTime - if ( elapsedTime < waitTime ) - wait waitTime - elapsedTime - } -} - -void function CloakerCloaksGuy( guy ) -{ - guy.SetCloakDuration( 2.0, -1, 0 ) - EmitSoundOnEntity( guy, CLOAKED_DRONE_CLOAK_START_SFX ) - EmitSoundOnEntity( guy, CLOAKED_DRONE_CLOAK_LOOP_SFX ) - guy.Minimap_Hide( TEAM_IMC, null ) - guy.Minimap_Hide( TEAM_MILITIA, null ) -} - -void function CloakerDeCloaksGuy( guy ) -{ - guy.SetCloakDuration( 0, 0, 1.5 ) - StopSoundOnEntity( guy, CLOAKED_DRONE_CLOAK_LOOP_SFX ) - guy.Minimap_AlwaysShow( TEAM_IMC, null ) - guy.Minimap_AlwaysShow( TEAM_MILITIA, null ) -} - -bool function CloakerShouldCloakGuy( entity cloaker, entity guy ) -{ - if ( !IsAlive( guy ) ) - return false - - return true -}
\ No newline at end of file diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut deleted file mode 100644 index c2036e85..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut +++ /dev/null @@ -1,604 +0,0 @@ -untyped - -global function Grenade_FileInit -global function GetGrenadeThrowSound_1p -global function GetGrenadeDeploySound_1p -global function GetGrenadeThrowSound_3p -global function GetGrenadeDeploySound_3p -global function GetGrenadeProjectileSound - -const DEFAULT_FUSE_TIME = 2.25 -const DEFAULT_WARNING_TIME = 1.0 -global const float DEFAULT_MAX_COOK_TIME = 99999.9 //Longer than an entire day. Really just an arbitrarily large number - -global function Grenade_OnWeaponTossReleaseAnimEvent -global function Grenade_OnWeaponTossCancelDrop -global function Grenade_OnWeaponDeactivate -global function Grenade_OnWeaponTossPrep -global function Grenade_OnProjectileIgnite - -#if SERVER - global function Grenade_OnPlayerNPCTossGrenade_Common - global function ProxMine_Triggered - global function EnableTrapWarningSound - global function AddToProximityTargets - global function ProximityMineThink -#endif -global function Grenade_Init - -const GRENADE_EXPLOSIVE_WARNING_SFX_LOOP = "Weapon_Vortex_Gun.ExplosiveWarningBeep" -const EMP_MAGNETIC_FORCE = 1600 -const MAG_FLIGHT_SFX_LOOP = "Explo_MGL_MagneticAttract" - -//Proximity Mine Settings -global const PROXIMITY_MINE_EXPLOSION_DELAY = 1.2 -global const PROXIMITY_MINE_ARMING_DELAY = 1.0 -const TRIGGERED_ALARM_SFX = "Weapon_ProximityMine_CloseWarning" -global const THERMITE_GRENADE_FX = $"P_grenade_thermite" -global const CLUSTER_BASE_FX = $"P_wpn_meteor_exp" - -global const ProximityTargetClassnames = { - [ "npc_soldier_shield" ] = true, - [ "npc_soldier_heavy" ] = true, - [ "npc_soldier" ] = true, - [ "npc_spectre" ] = true, - [ "npc_drone" ] = true, - [ "npc_titan" ] = true, - [ "npc_marvin" ] = true, - [ "player" ] = true, - [ "npc_turret_mega" ] = true, - [ "npc_turret_sentry" ] = true, - [ "npc_dropship" ] = true, -} - -const SOLDIER_ARC_STUN_ANIMS = [ - "pt_react_ARC_fall", - "pt_react_ARC_kneefall", - "pt_react_ARC_sidefall", - "pt_react_ARC_slowfall", - "pt_react_ARC_scream", - "pt_react_ARC_stumble_F", - "pt_react_ARC_stumble_R" ] - -function Grenade_FileInit() -{ - PrecacheParticleSystem( CLUSTER_BASE_FX ) - - RegisterSignal( "ThrowGrenade" ) - RegisterSignal( "WeaponDeactivateEvent" ) - RegisterSignal( "OnEMPPilotHit" ) - RegisterSignal( "StopGrenadeClientEffects" ) - RegisterSignal( "DisableTrapWarningSound" ) - - //Globalize( MagneticFlight ) - - #if CLIENT - AddDestroyCallback( "grenade_frag", ClientDestroyCallback_GrenadeDestroyed ) - #endif - - #if SERVER - level._empForcedCallbacks <- {} - level._proximityTargetArrayID <- CreateScriptManagedEntArray() - - AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_proximity_mine, ProxMine_Triggered ) - AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_thermite_grenade, Thermite_DamagedPlayerOrNPC ) - AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_frag_grenade, Frag_DamagedPlayerOrNPC ) - - level._empForcedCallbacks[eDamageSourceId.mp_weapon_grenade_emp] <- true - level._empForcedCallbacks[eDamageSourceId.mp_weapon_proximity_mine] <- true - - PrecacheParticleSystem( THERMITE_GRENADE_FX ) - #endif -} - -void function Grenade_OnWeaponTossPrep( entity weapon, WeaponTossPrepParams prepParams ) -{ - weapon.w.startChargeTime = Time() - - entity weaponOwner = weapon.GetWeaponOwner() - weapon.EmitWeaponSound_1p3p( GetGrenadeDeploySound_1p( weapon ), GetGrenadeDeploySound_3p( weapon ) ) - - #if SERVER - thread HACK_CookGrenade( weapon, weaponOwner ) - thread HACK_DropGrenadeOnDeath( weapon, weaponOwner ) - #elseif CLIENT - if ( weaponOwner.IsPlayer() ) - { - weaponOwner.p.grenadePulloutTime = Time() - } - #endif -} - -void function Grenade_OnWeaponDeactivate( entity weapon ) -{ - StopSoundOnEntity( weapon, GRENADE_EXPLOSIVE_WARNING_SFX_LOOP ) - weapon.Signal( "WeaponDeactivateEvent" ) -} - -void function Grenade_OnProjectileIgnite( entity weapon ) -{ - printt( "Grenade_OnProjectileIgnite() callback." ) -} - -function Grenade_Init( entity grenade, entity weapon ) -{ - entity weaponOwner = weapon.GetOwner() - if ( IsValid( weaponOwner ) ) - SetTeam( grenade, weaponOwner.GetTeam() ) - - // JFS: this is because I don't know if the above line should be - // weapon.GetOwner() or it's a typo and should really be weapon.GetWeaponOwner() - // and it's too close to ship and who knows what effect that will have - entity owner = weapon.GetWeaponOwner() - if ( IsMultiplayer() && IsValid( owner ) ) - { - if ( owner.IsNPC() ) - { - SetTeam( grenade, owner.GetTeam() ) - } - } - - #if SERVER - bool smartPistolVisible = weapon.GetWeaponSettingBool( eWeaponVar.projectile_visible_to_smart_ammo ) - if ( smartPistolVisible ) - { - grenade.SetDamageNotifications( true ) - grenade.SetTakeDamageType( DAMAGE_EVENTS_ONLY ) - grenade.proj.onlyAllowSmartPistolDamage = true - - if ( !grenade.GetProjectileWeaponSettingBool( eWeaponVar.projectile_damages_owner ) && !grenade.GetProjectileWeaponSettingBool( eWeaponVar.explosion_damages_owner ) ) - SetCustomSmartAmmoTarget( grenade, true ) // prevent friendly target lockon - } - else - { - grenade.SetTakeDamageType( DAMAGE_NO ) - } - #endif - if ( IsValid( weaponOwner ) ) - grenade.s.originalOwner <- weaponOwner // for later in damage callbacks, to skip damage vs friendlies but not for og owner or his enemies -} - - -int function Grenade_OnWeaponToss_( entity weapon, WeaponPrimaryAttackParams attackParams, float directionScale ) -{ - weapon.EmitWeaponSound_1p3p( GetGrenadeThrowSound_1p( weapon ), GetGrenadeThrowSound_3p( weapon ) ) - bool projectilePredicted = PROJECTILE_PREDICTED - bool projectileLagCompensated = PROJECTILE_LAG_COMPENSATED -#if SERVER - if ( weapon.IsForceReleaseFromServer() ) - { - projectilePredicted = false - projectileLagCompensated = false - } -#endif - entity grenade = Grenade_Launch( weapon, attackParams.pos, (attackParams.dir * directionScale), projectilePredicted, projectileLagCompensated ) - entity weaponOwner = weapon.GetWeaponOwner() - weaponOwner.Signal( "ThrowGrenade" ) - - PlayerUsedOffhand( weaponOwner, weapon ) // intentionally here and in Hack_DropGrenadeOnDeath - accurate for when cooldown actually begins - -#if SERVER - - #if BATTLECHATTER_ENABLED - TryPlayWeaponBattleChatterLine( weaponOwner, weapon ) - #endif - -#endif - - return weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot ) -} - -var function Grenade_OnWeaponTossReleaseAnimEvent( entity weapon, WeaponPrimaryAttackParams attackParams ) -{ - var result = Grenade_OnWeaponToss_( weapon, attackParams, 1.0 ) - return result -} - -var function Grenade_OnWeaponTossCancelDrop( entity weapon, WeaponPrimaryAttackParams attackParams ) -{ - var result = Grenade_OnWeaponToss_( weapon, attackParams, 0.2 ) - return result -} - -// Can return entity or nothing -entity function Grenade_Launch( entity weapon, vector attackPos, vector throwVelocity, bool isPredicted, bool isLagCompensated ) -{ - #if CLIENT - if ( !weapon.ShouldPredictProjectiles() ) - return null - #endif - - //TEMP FIX while Deploy anim is added to sprint - float currentTime = Time() - if ( weapon.w.startChargeTime == 0.0 ) - weapon.w.startChargeTime = currentTime - - entity weaponOwner = weapon.GetWeaponOwner() - - var discThrow = weapon.GetWeaponInfoFileKeyField( "grenade_disc_throw" ) - - vector angularVelocity = Vector( 3600, RandomFloatRange( -1200, 1200 ), 0 ) - - if ( discThrow == 1 ) - angularVelocity = Vector( 100, 100, RandomFloatRange( 1200, 2200 ) ) - - - float fuseTime - - float baseFuseTime = weapon.GetGrenadeFuseTime() //Note that fuse time of 0 means the grenade won't explode on its own, instead it depends on OnProjectileCollision() functions to be defined and explode there. Arguably in this case grenade_fuse_time shouldn't be 0, but an arbitrarily large number instead. - if ( baseFuseTime > 0.0 ) - { - fuseTime = baseFuseTime - ( currentTime - weapon.w.startChargeTime ) - if ( fuseTime <= 0 ) - fuseTime = 0.001 - } - else - { - fuseTime = baseFuseTime - } - - int damageFlags = weapon.GetWeaponDamageFlags() - entity frag = weapon.FireWeaponGrenade( attackPos, throwVelocity, angularVelocity, fuseTime, damageFlags, damageFlags, isPredicted, isLagCompensated, true ) - if ( frag == null ) - return null - - if ( discThrow == 1 ) // add wobble by pitching it slightly - { - Assert( !frag.IsMarkedForDeletion(), "Frag before .SetAngles() is marked for deletion." ) - frag.SetAngles( frag.GetAngles() + < RandomFloatRange( 7,11 ),0,0 > ) - //Assert( !frag.IsMarkedForDeletion(), "Frag after .SetAngles() is marked for deletion." ) - if ( frag.IsMarkedForDeletion() ) - { - CodeWarning( "Frag after .SetAngles() was marked for deletion." ) - return null - } - } - - Grenade_OnPlayerNPCTossGrenade_Common( weapon, frag ) - - return frag -} - -void function Grenade_OnPlayerNPCTossGrenade_Common( entity weapon, entity frag ) -{ - Grenade_Init( frag, weapon ) - #if SERVER - thread TrapExplodeOnDamage( frag, 20, 0.0, 0.0 ) - - string projectileSound = GetGrenadeProjectileSound( weapon ) - if ( projectileSound != "" ) - EmitSoundOnEntity( frag, projectileSound ) - #endif - - if( weapon.HasMod( "burn_mod_emp_grenade" ) ) - frag.InitMagnetic( EMP_MAGNETIC_FORCE, MAG_FLIGHT_SFX_LOOP ) -} - -struct CookGrenadeStruct //Really just a convenience struct so we can read the changed value of a bool in an OnThreadEnd -{ - bool shouldOverrideFuseTime = false -} - -void function HACK_CookGrenade( entity weapon, entity weaponOwner ) -{ - float maxCookTime = GetMaxCookTime( weapon ) - if ( maxCookTime >= DEFAULT_MAX_COOK_TIME ) - return - - weaponOwner.EndSignal( "OnDeath" ) - weaponOwner.EndSignal( "ThrowGrenade" ) - weapon.EndSignal( "WeaponDeactivateEvent" ) - weapon.EndSignal( "OnDestroy" ) - - /*CookGrenadeStruct grenadeStruct - - OnThreadEnd( - function() : ( weapon, grenadeStruct ) - { - if ( grenadeStruct.shouldOverrideFuseTime ) - { - var minFuseTime = weapon.GetWeaponInfoFileKeyField( "min_fuse_time" ) - printt( "minFuseTime: " + minFuseTime ) - if ( minFuseTime != null ) - { - expect float( minFuseTime ) - printt( "Setting overrideFuseTime to : " + weapon.GetWeaponInfoFileKeyField( "min_fuse_time" ) ) - weapon.w.overrideFuseTime = minFuseTime - } - } - } - ) -*/ - if ( maxCookTime - DEFAULT_WARNING_TIME <= 0 ) - { - EmitSoundOnEntity( weapon, GRENADE_EXPLOSIVE_WARNING_SFX_LOOP ) - wait maxCookTime - } - else - { - wait( maxCookTime - DEFAULT_WARNING_TIME ) - - EmitSoundOnEntity( weapon, GRENADE_EXPLOSIVE_WARNING_SFX_LOOP ) - - wait( DEFAULT_WARNING_TIME ) - } - - if ( !IsValid( weapon.GetWeaponOwner() ) ) - return - - weapon.ForceReleaseFromServer() // Will eventually result in Grenade_OnWeaponToss_() or equivalent function - - // JFS: prevent grenade cook exploit in coliseum - if ( GameRules_GetGameMode() == COLISEUM ) - { - #if SERVER - int damageSource = weapon.GetDamageSourceID() - - if ( damageSource == eDamageSourceId.mp_weapon_frag_grenade ) - { - var impact_effect_table = weapon.GetWeaponInfoFileKeyField( "impact_effect_table" ) - if ( impact_effect_table != null ) - { - string fx = expect string( impact_effect_table ) - PlayImpactFXTable( weaponOwner.EyePosition(), weaponOwner, fx ) - } - weaponOwner.Die( weaponOwner, weapon, { damageSourceId = damageSource } ) - } - #endif - } - - weaponOwner.Signal( "ThrowGrenade" ) // Only necessary to end HACK_DropGrenadeOnDeath -} - - -void function HACK_WaitForGrenadeDropEvent( weapon, entity weaponOwner ) -{ - weapon.EndSignal( "WeaponDeactivateEvent" ) - - weaponOwner.WaitSignal( "OnDeath" ) -} - - -void function HACK_DropGrenadeOnDeath( entity weapon, entity weaponOwner ) -{ - if ( weapon.HasMod( "burn_card_weapon_mod" ) ) //JFS: Primarily to stop boost grenade weapons (e.g. frag_drone ) not doing TryUsingBurnCardWeapon() when dropped through this function. - return - - weaponOwner.EndSignal( "ThrowGrenade" ) - weaponOwner.EndSignal( "OnDestroy" ) - - waitthread HACK_WaitForGrenadeDropEvent( weapon, weaponOwner ) - - if( !IsValid( weaponOwner ) || !IsValid( weapon ) || IsAlive( weaponOwner ) ) - return - - float elapsedTime = Time() - weapon.w.startChargeTime - float baseFuseTime = weapon.GetGrenadeFuseTime() - float fuseDelta = (baseFuseTime - elapsedTime) - - if ( (baseFuseTime == 0.0) || (fuseDelta > -0.1) ) - { - float forwardScale = weapon.GetWeaponSettingFloat( eWeaponVar.grenade_death_drop_velocity_scale ) - vector velocity = weaponOwner.GetForwardVector() * forwardScale - velocity.z += weapon.GetWeaponSettingFloat( eWeaponVar.grenade_death_drop_velocity_extraUp ) - vector angularVelocity = Vector( 0, 0, 0 ) - float fuseTime = baseFuseTime ? baseFuseTime - elapsedTime : baseFuseTime - - int primaryClipCount = weapon.GetWeaponPrimaryClipCount() - int ammoPerShot = weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot ) - weapon.SetWeaponPrimaryClipCountAbsolute( maxint( 0, primaryClipCount - ammoPerShot ) ) - - PlayerUsedOffhand( weaponOwner, weapon ) // intentionally here and in ReleaseAnimEvent - for cases where grenade is dropped on death - - entity grenade = Grenade_Launch( weapon, weaponOwner.GetOrigin(), velocity, PROJECTILE_NOT_PREDICTED, PROJECTILE_NOT_LAG_COMPENSATED ) - } -} - - -#if SERVER -void function ProxMine_Triggered( entity ent, var damageInfo ) -{ - if ( !IsValid( ent ) ) - return - - if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS ) - return - - entity attacker = DamageInfo_GetAttacker( damageInfo ) - - if ( !IsValid( attacker ) ) - return - - if ( attacker == ent ) - return - - if ( ent.IsPlayer() || ent.IsNPC() ) - thread ShowProxMineTriggeredIcon( ent ) - - //If this feature is good, we should add this to NPCs as well. Currently script errors if applied to an NPC. - //if ( ent.IsPlayer() ) - // thread ProxMine_ShowOnMinimapTimed( ent, GetOtherTeam( ent.GetTeam() ), PROX_MINE_MARKER_TIME ) -} - -/* -function ProxMine_ShowOnMinimapTimed( ent, teamToDisplayEntTo, duration ) -{ - ent.Minimap_AlwaysShow( teamToDisplayEntTo, null ) - Minimap_CreatePingForTeam( teamToDisplayEntTo, ent.GetOrigin(), $"vgui/HUD/titanFiringPing", 1.0 ) - - wait duration - - if ( IsValid( ent ) && ent.IsPlayer() ) - ent.Minimap_DisplayDefault( teamToDisplayEntTo, ent ) -} -*/ - -void function Thermite_DamagedPlayerOrNPC( entity ent, var damageInfo ) -{ - if ( !IsValid( ent ) ) - return - - Thermite_DamagePlayerOrNPCSounds( ent ) -} - -void function Frag_DamagedPlayerOrNPC( entity ent, var damageInfo ) -{ - #if MP - if ( !IsValid( ent ) || ent.IsPlayer() || ent.IsTitan() ) - return - - if ( ent.IsMechanical() ) - DamageInfo_ScaleDamage( damageInfo, 0.5 ) - #endif -} - -#endif // SERVER - - -#if CLIENT -void function ClientDestroyCallback_GrenadeDestroyed( entity grenade ) -{ -} -#endif // CLIENT - -#if SERVER -function EnableTrapWarningSound( entity trap, delay = 0, warningSound = DEFAULT_WARNING_SFX ) -{ - trap.EndSignal( "OnDestroy" ) - trap.EndSignal( "DisableTrapWarningSound" ) - - if ( delay > 0 ) - wait delay - - while ( IsValid( trap ) ) - { - EmitSoundOnEntity( trap, warningSound ) - wait 1.0 - } -} - -void function AddToProximityTargets( entity ent ) -{ - AddToScriptManagedEntArray( level._proximityTargetArrayID, ent ); -} - -function ProximityMineThink( entity proximityMine, entity owner ) -{ - proximityMine.EndSignal( "OnDestroy" ) - - OnThreadEnd( - function() : ( proximityMine ) - { - if ( IsValid( proximityMine ) ) - proximityMine.Destroy() - } - ) - thread TrapExplodeOnDamage( proximityMine, 50 ) - - wait PROXIMITY_MINE_ARMING_DELAY - - int teamNum = proximityMine.GetTeam() - float explodeRadius = proximityMine.GetDamageRadius() - float triggerRadius = ( ( explodeRadius * 0.75 ) + 0.5 ) - local lastTimeNPCsChecked = 0 - local NPCTickRate = 0.5 - local PlayerTickRate = 0.2 - - // Wait for someone to enter proximity - while( IsValid( proximityMine ) && IsValid( owner ) ) - { - if ( lastTimeNPCsChecked + NPCTickRate <= Time() ) - { - array<entity> nearbyNPCs = GetNPCArrayEx( "any", TEAM_ANY, teamNum, proximityMine.GetOrigin(), triggerRadius ) - foreach( ent in nearbyNPCs ) - { - if ( ShouldSetOffProximityMine( proximityMine, ent ) ) - { - ProximityMine_Explode( proximityMine ) - return - } - } - lastTimeNPCsChecked = Time() - } - - array<entity> nearbyPlayers = GetPlayerArrayEx( "any", TEAM_ANY, teamNum, proximityMine.GetOrigin(), triggerRadius ) - foreach( ent in nearbyPlayers ) - { - if ( ShouldSetOffProximityMine( proximityMine, ent ) ) - { - ProximityMine_Explode( proximityMine ) - return - } - } - - wait PlayerTickRate - } -} - -function ProximityMine_Explode( proximityMine ) -{ - local explodeTime = Time() + PROXIMITY_MINE_EXPLOSION_DELAY - EmitSoundOnEntity( proximityMine, TRIGGERED_ALARM_SFX ) - - wait PROXIMITY_MINE_EXPLOSION_DELAY - - if ( IsValid( proximityMine ) ) - proximityMine.GrenadeExplode( proximityMine.GetForwardVector() ) -} - -bool function ShouldSetOffProximityMine( entity proximityMine, entity ent ) -{ - if ( !IsAlive( ent ) ) - return false - - if ( ent.IsPhaseShifted() ) - return false - - TraceResults results = TraceLine( proximityMine.GetOrigin(), ent.EyePosition(), proximityMine, (TRACE_MASK_SHOT | CONTENTS_BLOCKLOS), TRACE_COLLISION_GROUP_NONE ) - if ( results.fraction >= 1 || results.hitEnt == ent ) - return true - - return false -} - -#endif // SERVER - - - -float function GetMaxCookTime( entity weapon ) -{ - var cookTime = weapon.GetWeaponInfoFileKeyField( "max_cook_time" ) - if (cookTime == null ) - return DEFAULT_MAX_COOK_TIME - - expect float ( cookTime ) - return cookTime -} - -function GetGrenadeThrowSound_1p( weapon ) -{ - return weapon.GetWeaponInfoFileKeyField( "sound_throw_1p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_throw_1p" ) : "" -} - - -function GetGrenadeDeploySound_1p( weapon ) -{ - return weapon.GetWeaponInfoFileKeyField( "sound_deploy_1p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_deploy_1p" ) : "" -} - - -function GetGrenadeThrowSound_3p( weapon ) -{ - return weapon.GetWeaponInfoFileKeyField( "sound_throw_3p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_throw_3p" ) : "" -} - - -function GetGrenadeDeploySound_3p( weapon ) -{ - return weapon.GetWeaponInfoFileKeyField( "sound_deploy_3p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_deploy_3p" ) : "" -} - -string function GetGrenadeProjectileSound( weapon ) -{ - return expect string( weapon.GetWeaponInfoFileKeyField( "sound_grenade_projectile" ) ? weapon.GetWeaponInfoFileKeyField( "sound_grenade_projectile" ) : "" ) -} diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_particle_wall.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_particle_wall.gnut deleted file mode 100644 index a46bfff8..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_particle_wall.gnut +++ /dev/null @@ -1,460 +0,0 @@ -untyped - -global function ParticleWall_Init - -global function CreateTurretParticleWall -global function CreateParticleWallFromOwner -global function CreateShieldWithSettings -global function DrainHealthOverTime - -global function CreateAmpedWallFromOwner - -global function CreateParticleWallForOwnerFromDirection - -global const SHIELD_WALL_COL_MODEL = $"models/fx/xo_shield_wall.mdl" -global const SHIELD_WALL_FX = $"P_xo_shield_wall" - -global const TURRET_SHIELD_WALL_COL_MODEL = $"models/fx/turret_shield_wall.mdl" -global const TURRET_SHIELD_WALL_FX = $"P_turret_shield_wall" - -global const AMPED_WALL_FX = $"P_xo_amped_wall" -#if MP -global const SHIELD_WALL_HEALTH = 2000 -global const TURRET_SHIELD_WALL_HEALTH = 3500//1750 -#else -global const SHIELD_WALL_HEALTH = 1750 -global const TURRET_SHIELD_WALL_HEALTH = 1750 -#endif -global const PAS_TONE_WALL_HEALTH = 3000 -global const PAS_TONE_WALL_DURATION_MULTIPLIER = 1.5 -global const SHIELD_WALL_DURATION = 8.0 -global const SHIELD_WALL_RADIUS = 180 -global const SHIELD_WALL_FOV = 120 -global const SHIELD_WALL_WIDTH = 156.0 // SHIELD_WALL_RADIUS * cos( SHIELD_WALL_FOV/2 ) - -global function UpdateShieldWallColorForFrac -global function PlayEffectOnVortexSphere -global function SetVortexSphereShieldWallCPoint -global function SetShieldWallCPoint -global function StopShieldWallFX -global function StopShieldWallFXOverTime -global function SetShieldWallCPointOrigin - -function ParticleWall_Init() -{ - PrecacheParticleSystem( SHIELD_WALL_FX ) - PrecacheModel( SHIELD_WALL_COL_MODEL ) - - PrecacheParticleSystem( TURRET_SHIELD_WALL_FX ) - PrecacheModel( TURRET_SHIELD_WALL_COL_MODEL ) - - PrecacheParticleSystem( AMPED_WALL_FX ) -} - -void function CreateParticleWallFromOwner( entity weaponOwner, float duration, WeaponPrimaryAttackParams attackParams ) -{ - vector dir = GetParticleWallAttackAnglesFromOwner( weaponOwner, attackParams ) - CreateParticleWallForOwnerFromDirection( weaponOwner, duration, dir ) -} - -vector function GetParticleWallAttackAnglesFromOwner( entity weaponOwner, WeaponPrimaryAttackParams attackParams ) -{ - if ( weaponOwner.IsNPC() ) - return attackParams.dir - - vector angles = weaponOwner.CameraAngles() - angles.x = 0 - return AnglesToForward( angles ) -} - -void function CreateParticleWallForOwnerFromDirection( entity weaponOwner, float duration, vector dir ) -{ - Assert( IsServer() ) - - entity titanSoul = weaponOwner.GetTitanSoul() - - // JFS the weapon owner should always have a soul, at least on the server - if ( !IsValid( titanSoul ) ) - return - - vector origin = weaponOwner.GetOrigin() - vector safeSpot = origin - vector angles = VectorToAngles( dir ) - - if ( weaponOwner.IsNPC() ) - { - // spawn in front of npc a bit - origin += dir * 100 - } - - float endTime = Time() + duration - titanSoul.SetDefensivePlacement( endTime, SHIELD_WALL_WIDTH, 0, true, safeSpot, dir ) - - Assert( weaponOwner.IsTitan() ) - Assert( titanSoul ) - - int health - if ( SoulHasPassive( titanSoul, ePassives.PAS_TONE_WALL ) ) - { - health = PAS_TONE_WALL_HEALTH - duration *= PAS_TONE_WALL_DURATION_MULTIPLIER - } - else - { - health = SHIELD_WALL_HEALTH - } - entity vortexSphere = CreateShieldWithSettings( origin + < 0, 0, -64 >, angles, SHIELD_WALL_RADIUS, SHIELD_WALL_RADIUS * 2, SHIELD_WALL_FOV, duration, health, SHIELD_WALL_FX ) - thread DrainHealthOverTime( vortexSphere, vortexSphere.e.shieldWallFX, duration ) - - entity groundEntity = weaponOwner.GetGroundEntity() - if ( groundEntity != null && groundEntity.HasPusherRootParent() ) - vortexSphere.SetParent( groundEntity, "", true, 0 ) -} - -entity function CreateTurretParticleWall( vector origin, vector angles, float duration ) -{ - Assert( IsServer() ) - - entity vortexSphere = CreateTurretShieldWithSettings( origin + < 0, 0, -64 >, angles, SHIELD_WALL_RADIUS, int( SHIELD_WALL_RADIUS * 1.65 ), 270, duration, TURRET_SHIELD_WALL_HEALTH, TURRET_SHIELD_WALL_FX ) - thread DrainHealthOverTime( vortexSphere, vortexSphere.e.shieldWallFX, duration ) - - return vortexSphere -} - -entity function CreateShieldWithSettings( vector origin, vector angles, int radius, int height, int fov, float duration, int health, asset effectName ) -{ - entity vortexSphere = CreateEntity( "vortex_sphere" ) - - vortexSphere.kv.spawnflags = SF_ABSORB_BULLETS | SF_BLOCK_OWNER_WEAPON | SF_BLOCK_NPC_WEAPON_LOF | SF_ABSORB_CYLINDER - vortexSphere.kv.enabled = 0 - vortexSphere.kv.radius = radius - vortexSphere.kv.height = height - vortexSphere.kv.bullet_fov = fov - vortexSphere.kv.physics_pull_strength = 25 - vortexSphere.kv.physics_side_dampening = 6 - vortexSphere.kv.physics_fov = 360 - vortexSphere.kv.physics_max_mass = 2 - vortexSphere.kv.physics_max_size = 6 - - vortexSphere.SetAngles( angles ) // viewvec? - vortexSphere.SetOrigin( origin ) - vortexSphere.SetMaxHealth( health ) - vortexSphere.SetHealth( health ) - vortexSphere.SetTakeDamageType( DAMAGE_YES ) - - DispatchSpawn( vortexSphere ) - - vortexSphere.Fire( "Enable" ) - vortexSphere.Fire( "Kill", "", duration ) - - // Shield wall fx control point - entity cpoint = CreateEntity( "info_placement_helper" ) - SetTargetName( cpoint, UniqueString( "shield_wall_controlpoint" ) ) - DispatchSpawn( cpoint ) - - // Shield wall fx - entity shieldWallFX = PlayFXWithControlPoint( effectName, origin, cpoint, -1, null, angles, C_PLAYFX_LOOP ) - vortexSphere.e.shieldWallFX = shieldWallFX - shieldWallFX.SetParent( vortexSphere ) - SetVortexSphereShieldWallCPoint( vortexSphere, cpoint ) - StopShieldWallFXOverTime( vortexSphere, duration ) - - - thread StopFXOnDestroy( vortexSphere, shieldWallFX, duration ) - return vortexSphere -} - -//Turret Shields do not block npc line of fire. -entity function CreateTurretShieldWithSettings( vector origin, vector angles, int radius, int height, int fov, float duration, int health, asset effectName ) -{ - entity vortexSphere = CreateEntity( "vortex_sphere" ) - - vortexSphere.kv.spawnflags = SF_ABSORB_BULLETS | SF_BLOCK_OWNER_WEAPON | SF_ABSORB_CYLINDER - vortexSphere.kv.enabled = 0 - vortexSphere.kv.radius = radius - vortexSphere.kv.height = height - vortexSphere.kv.bullet_fov = fov - vortexSphere.kv.physics_pull_strength = 25 - vortexSphere.kv.physics_side_dampening = 6 - vortexSphere.kv.physics_fov = 360 - vortexSphere.kv.physics_max_mass = 2 - vortexSphere.kv.physics_max_size = 6 - - vortexSphere.SetAngles( angles ) // viewvec? - vortexSphere.SetOrigin( origin ) - vortexSphere.SetMaxHealth( health ) - vortexSphere.SetHealth( health ) - vortexSphere.SetTakeDamageType( DAMAGE_YES ) - - DispatchSpawn( vortexSphere ) - - vortexSphere.Fire( "Enable" ) - vortexSphere.Fire( "Kill", "", duration ) - - // Shield wall fx control point - entity cpoint = CreateEntity( "info_placement_helper" ) - SetTargetName( cpoint, UniqueString( "shield_wall_controlpoint" ) ) - DispatchSpawn( cpoint ) - - // Shield wall fx - entity shieldWallFX = PlayFXWithControlPoint( effectName, origin, cpoint, -1, null, angles, C_PLAYFX_LOOP ) - vortexSphere.e.shieldWallFX = shieldWallFX - shieldWallFX.SetParent( vortexSphere ) - SetVortexSphereShieldWallCPoint( vortexSphere, cpoint ) - StopShieldWallFXOverTime( vortexSphere, duration ) - - - thread StopFXOnDestroy( vortexSphere, shieldWallFX, duration ) - return vortexSphere -} - -function StopFXOnDestroy( entity vortexSphere, entity shieldWallFX, float duration ) -{ - vortexSphere.EndSignal( "OnDestroy" ) - shieldWallFX.EndSignal( "OnDestroy" ) - - OnThreadEnd( - function() : ( vortexSphere ) - { - StopShieldWallFX( vortexSphere ) - } - ) - - wait duration * 1.5 -} - -void function CreateAmpedWallFromOwner( entity weaponOwner, float duration, WeaponPrimaryAttackParams attackParams ) -{ - Assert( IsNewThread(), "Must be threaded off" ) - Assert( IsServer() ) - entity titanSoul = weaponOwner.GetTitanSoul() - - // JFS the weapon owner should always have a soul, at least on the server - if ( !IsValid( titanSoul ) ) - return - Assert( weaponOwner.IsTitan() ) - - vector dir = GetParticleWallAttackAnglesFromOwner( weaponOwner, attackParams ) - vector origin = weaponOwner.GetOrigin() - vector safeSpot = origin - vector angles = VectorToAngles( dir ) - vector forward = AnglesToForward( angles ) - angles = AnglesCompose( angles, <0,180,0> ) - - if ( weaponOwner.IsNPC() ) - { - // spawn in front of npc a bit - origin += dir * 100 - } - - origin += dir * 500 - origin += Vector(0,0,-64) - - float endTime = Time() + duration - titanSoul.SetDefensivePlacement( endTime, SHIELD_WALL_WIDTH, 0, true, safeSpot, dir ) - - entity vortexSphere = CreateShieldWithSettings( origin, angles, SHIELD_WALL_RADIUS, SHIELD_WALL_RADIUS * 2, SHIELD_WALL_FOV, duration, SHIELD_WALL_HEALTH, AMPED_WALL_FX ) - vortexSphere.EndSignal( "OnDestroy" ) - entity shieldWallFX = vortexSphere.e.shieldWallFX - shieldWallFX.EndSignal( "OnDestroy" ) - SetTargetName( vortexSphere, PROTO_AMPED_WALL ) // so projectiles pass through - - SetShieldWallCPointOrigin( shieldWallFX, < BURN_CARD_WEAPON_HUD_COLOR[0], BURN_CARD_WEAPON_HUD_COLOR[1], BURN_CARD_WEAPON_HUD_COLOR[2] > ) - - float tickRate = 0.1 - float dps = vortexSphere.GetMaxHealth() / duration - float dmgAmount = dps * tickRate - - EmitSoundOnEntity( vortexSphere, "ShieldWall_Loop" ) - - float endSoundTime = endTime - 3.0 // so magic - thread PlayDelayedVortexEndSound( endSoundTime, vortexSphere ) - bool playedEndSound = false - vector vortexOrigin = vortexSphere.GetOrigin() - entity mover = CreateScriptMover() - - int weaponOwnerTeam = weaponOwner.GetTeam(); - - OnThreadEnd( - function() : ( vortexSphere, vortexOrigin, endTime, mover, weaponOwnerTeam ) - { - if ( IsValid( vortexSphere ) ) - { - StopSoundOnEntity( vortexSphere, "ShieldWall_Loop" ) - StopSoundOnEntity( vortexSphere, "ShieldWall_End" ) - } - - if ( IsValid( mover ) ) - mover.Destroy() - - if ( endTime - Time() >= 1.0 ) - EmitSoundAtPosition( weaponOwnerTeam, vortexOrigin, "ShieldWall_Destroyed" ) - } - ) - - int rampOuts = 3 - float rampOutTime = 0.75 - float rampOutFinalFade = 1.0 - float finalFadeExtraBuffer = 0.45 - - wait duration - ( rampOutTime * rampOuts + rampOutFinalFade + finalFadeExtraBuffer ) - EmitSoundOnEntity( vortexSphere, "ShieldWall_End" ) - - entity cpoint = GetShieldWallFXCPoint( shieldWallFX ) - - vector cpointOrigin = cpoint.GetOrigin() - mover.SetOrigin( cpointOrigin ) - cpoint.SetParent( mover ) - float rampTime1 = rampOutTime * 0.75 - float rampTime2 = rampOutTime - rampTime1 - for ( int i = 0; i < rampOuts; i++ ) - { - mover.NonPhysicsMoveTo( <100,0,0>, rampTime1, rampTime1, 0.0 ) - wait rampTime1 - mover.NonPhysicsMoveTo( cpointOrigin, rampTime2, 0.0, rampTime2 ) - wait rampTime2 - } - - mover.NonPhysicsMoveTo( <0,0,0>, rampOutFinalFade, 0.0, 0.0 ) - wait rampOutFinalFade + finalFadeExtraBuffer -} - -void function PlayDelayedVortexEndSound( float delay, entity vortexSphere ) -{ - vortexSphere.EndSignal( "OnDestroy" ) - wait delay - EmitSoundOnEntity( vortexSphere, "ShieldWall_End" ) -} - - -function DrainHealthOverTime( entity vortexSphere, entity shieldWallFX, float duration ) -{ - vortexSphere.EndSignal( "OnDestroy" ) - shieldWallFX.EndSignal( "OnDestroy" ) - - float startTime = Time() - float endTime = startTime + duration - - float tickRate = 0.1 - float dps = vortexSphere.GetMaxHealth() / duration - float dmgAmount = dps * tickRate - - EmitSoundOnEntity( vortexSphere, "ShieldWall_Loop" ) - - float endSoundTime = endTime - 3.0 - bool playedEndSound = false - vector vortexOrigin = vortexSphere.GetOrigin() - - OnThreadEnd( - function() : ( vortexSphere, vortexOrigin, endTime ) - { - if ( endTime - Time() < 1.0 ) - return - - int teamNum = TEAM_UNASSIGNED - - if ( IsValid( vortexSphere ) ) - { - StopSoundOnEntity( vortexSphere, "ShieldWall_Loop" ) - StopSoundOnEntity( vortexSphere, "ShieldWall_End" ) - - teamNum = vortexSphere.GetTeam() - } - - EmitSoundAtPosition( teamNum, vortexOrigin, "ShieldWall_Destroyed" ) - } - ) - - while ( Time() < endTime ) - { - if ( Time() > endSoundTime && !playedEndSound ) - { - EmitSoundOnEntity( vortexSphere, "ShieldWall_End" ) - playedEndSound = true - } - - //vortexSphere.SetHealth( vortexSphere.GetHealth() - dmgAmount ) - UpdateShieldWallColorForFrac( shieldWallFX, GetHealthFrac( vortexSphere ) ) - wait tickRate - } - - StopSoundOnEntity( vortexSphere, "ShieldWall_Loop" ) -} - -function UpdateShieldWallColorForFrac( entity shieldWallFX, float colorFrac ) -{ - vector color = GetShieldTriLerpColor( 1 - colorFrac ) - - if ( IsValid( shieldWallFX ) ) - SetShieldWallCPointOrigin( shieldWallFX, color ) -} - -//////////////////////////////////////////////////////////////////////////////////////////////// -// -// All functions that care about to-be-deprecated cpoint are below here: -// -//////////////////////////////////////////////////////////////////////////////////////////////// - -void function PlayEffectOnVortexSphere( int fx, vector origin, vector angles, entity vortexSphere ) -{ - if ( !IsValid( vortexSphere ) ) - return - if ( !IsValid( vortexSphere.e.shieldWallFX ) ) - return - entity cpoint = vortexSphere.e.shieldWallFX.e.cpoint - if ( !IsValid( cpoint ) ) - return - StartParticleEffectInWorldWithControlPoint( fx, origin, angles, cpoint.GetOrigin() ) -} - -void function SetVortexSphereShieldWallCPoint( entity vortexSphere, entity cpoint ) -{ - Assert( IsValid( vortexSphere ) ) - Assert( IsValid( vortexSphere.e.shieldWallFX ) ) - SetShieldWallCPoint( vortexSphere.e.shieldWallFX, cpoint ) -} - -void function SetShieldWallCPoint( entity shieldWallFX, entity cpoint ) -{ - Assert( IsValid( shieldWallFX ) ) - Assert( IsValid( cpoint ) ) - shieldWallFX.e.cpoint = cpoint -} - -void function StopShieldWallFX( entity vortexSphere ) -{ - entity shieldWallFX = vortexSphere.e.shieldWallFX - vortexSphere.e.shieldWallFX = null - - if ( !IsValid( shieldWallFX ) ) - return - - shieldWallFX.Fire( "StopPlayEndCap" ) - shieldWallFX.Fire( "Kill", "", 1.0 ) - - if ( IsValid( shieldWallFX.e.cpoint ) ) - shieldWallFX.e.cpoint.Fire( "Kill", "", 1.0 ) - EffectStop( shieldWallFX ) -} - -void function StopShieldWallFXOverTime( entity vortexSphere, float duration ) -{ - entity shieldWallFX = vortexSphere.e.shieldWallFX - shieldWallFX.Fire( "StopPlayEndCap", "", duration ) - shieldWallFX.Fire( "Kill", "", duration ) - shieldWallFX.e.cpoint.Fire( "Kill", "", duration ) -} - -void function SetShieldWallCPointOrigin( entity shieldWallFX, vector AT_TURRET_SHIELD_COLOR ) -{ - Assert( IsValid( shieldWallFX ) ) - if ( !IsValid( shieldWallFX.e.cpoint ) ) - return - shieldWallFX.e.cpoint.SetOrigin( AT_TURRET_SHIELD_COLOR ) -} - -entity function GetShieldWallFXCPoint( entity shieldWallFX ) -{ - Assert( IsValid( shieldWallFX.e.cpoint ) ) - return shieldWallFX.e.cpoint -}
\ No newline at end of file diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_team_emp.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_team_emp.gnut deleted file mode 100644 index 41d42848..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_team_emp.gnut +++ /dev/null @@ -1,38 +0,0 @@ -global function TeamEMP_Init -global function EMPEffects - -void function TeamEMP_Init() -{ - RegisterSignal( "PlayerEMPed" ) -} - -void function EMPEffects( entity player, float time ) -{ - player.nv.empEndTime = Time() + time - - player.Signal( "PlayerEMPed" ) - - // remember this is a stack so you need to enable as many times as you disable - DisableOffhandWeapons( player ) - - thread RecoverFromEMP( player, time ) -} - -void function RecoverFromEMP( entity player, float time ) -{ - svGlobal.levelEnt.EndSignal( "BurnMeter_PreMatchEnter" ) - player.EndSignal( "OnDestroy" ) - - OnThreadEnd( - function() : ( player ) - { - if ( IsValid( player ) ) - { - // remember this is a stack so you need to enable as many times as you disable - EnableOffhandWeapons( player ) - } - } - ) - - wait time + 0.1 -}
\ No newline at end of file diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_vortex.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_vortex.nut deleted file mode 100644 index f1e46a53..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_vortex.nut +++ /dev/null @@ -1,1983 +0,0 @@ -untyped - -global function Vortex_Init - -global function CreateVortexSphere -global function DestroyVortexSphereFromVortexWeapon -global function EnableVortexSphere -#if SERVER -global function ValidateVortexImpact -global function TryVortexAbsorb -global function SetVortexSphereBulletHitRules -global function SetVortexSphereProjectileHitRules -#endif -global function VortexDrainedByImpact -global function VortexPrimaryAttack -global function GetVortexSphereCurrentColor -global function GetShieldTriLerpColor -global function IsVortexing -#if SERVER -global function Vortex_HandleElectricDamage -global function VortexSphereDrainHealthForDamage -global function Vortex_CreateImpactEventData -global function Vortex_SpawnHeatShieldPingFX -#endif - -global function Vortex_SetTagName -global function Vortex_SetBulletCollectionOffset - -global function CodeCallback_OnVortexHitBullet -global function CodeCallback_OnVortexHitProjectile - -const AMPED_WALL_IMPACT_FX = $"P_impact_xo_shield_cp" - -global const PROTO_AMPED_WALL = "proto_amped_wall" -global const GUN_SHIELD_WALL = "gun_shield_wall" -const PROX_MINE_MODEL = $"models/weapons/caber_shot/caber_shot_thrown.mdl" - -const VORTEX_SPHERE_COLOR_CHARGE_FULL = <115, 247, 255> // blue -const VORTEX_SPHERE_COLOR_CHARGE_MED = <200, 128, 80> // orange -const VORTEX_SPHERE_COLOR_CHARGE_EMPTY = <200, 80, 80> // red -const VORTEX_SPHERE_COLOR_PAS_ION_VORTEX = <115, 174, 255> // blue -const AMPED_DAMAGE_SCALAR = 1.5 - -const VORTEX_SPHERE_COLOR_CROSSOVERFRAC_FULL2MED = 0.75 // from zero to this fraction, fade between full and medium charge colors -const VORTEX_SPHERE_COLOR_CROSSOVERFRAC_MED2EMPTY = 0.95 // from "full2med" to this fraction, fade between medium and empty charge colors - -const VORTEX_BULLET_ABSORB_COUNT_MAX = 32 -const VORTEX_PROJECTILE_ABSORB_COUNT_MAX = 32 - -const VORTEX_TIMED_EXPLOSIVE_FUSETIME = 2.75 // fuse time for absorbed projectiles -const VORTEX_TIMED_EXPLOSIVE_FUSETIME_WARNINGFRAC = 0.75 // wait this fraction of the fuse time before warning the player it's about to explode - -const VORTEX_EXP_ROUNDS_RETURN_SPREAD_XY = 0.15 -const VORTEX_EXP_ROUNDS_RETURN_SPREAD_Z = 0.075 - -const VORTEX_ELECTRIC_DAMAGE_CHARGE_DRAIN_MIN = 0.1 // fraction of charge time -const VORTEX_ELECTRIC_DAMAGE_CHARGE_DRAIN_MAX = 0.3 - -//The shotgun spams a lot of pellets that deal too much damage if they return full damage. -const VORTEX_SHOTGUN_DAMAGE_RATIO = 0.25 - - -const SHIELD_WALL_BULLET_FX = $"P_impact_xo_shield_cp" -const SHIELD_WALL_EXPMED_FX = $"P_impact_exp_med_xo_shield_CP" - -const SIGNAL_ID_BULLET_HIT_THINK = "signal_id_bullet_hit_think" - -const VORTEX_EXPLOSIVE_WARNING_SFX_LOOP = "Weapon_Vortex_Gun.ExplosiveWarningBeep" - -const VORTEX_PILOT_WEAPON_WEAKNESS_DAMAGESCALE = 6.0 - -// These match the strings in the WeaponEd dropdown box for vortex_refire_behavior -global const VORTEX_REFIRE_NONE = "" -global const VORTEX_REFIRE_ABSORB = "absorb" -global const VORTEX_REFIRE_BULLET = "bullet" -global const VORTEX_REFIRE_EXPLOSIVE_ROUND = "explosive_round" -global const VORTEX_REFIRE_ROCKET = "rocket" -global const VORTEX_REFIRE_GRENADE = "grenade" -global const VORTEX_REFIRE_GRENADE_LONG_FUSE = "grenade_long_fuse" - -const VortexIgnoreClassnames = { - ["mp_titancore_flame_wave"] = true, - ["mp_ability_grapple"] = true, - ["mp_ability_shifter"] = true, -} - -table vortexImpactWeaponInfo - -const DEG_COS_60 = cos( 60 * DEG_TO_RAD ) - -function Vortex_Init() -{ - PrecacheParticleSystem( SHIELD_WALL_BULLET_FX ) - GetParticleSystemIndex( SHIELD_WALL_BULLET_FX ) - PrecacheParticleSystem( SHIELD_WALL_EXPMED_FX ) - GetParticleSystemIndex( SHIELD_WALL_EXPMED_FX ) - PrecacheParticleSystem( AMPED_WALL_IMPACT_FX ) - GetParticleSystemIndex( AMPED_WALL_IMPACT_FX ) - - RegisterSignal( SIGNAL_ID_BULLET_HIT_THINK ) - RegisterSignal( "VortexStopping" ) - - RegisterSignal( "VortexAbsorbed" ) - RegisterSignal( "VortexFired" ) - RegisterSignal( "Script_OnDamaged" ) -} - -#if SERVER -var function VortexBulletHitRules_Default( entity vortexSphere, var damageInfo ) -{ - return damageInfo -} - -bool function VortexProjectileHitRules_Default( entity vortexSphere, entity attacker, bool takesDamageByDefault ) -{ - return takesDamageByDefault -} - -void function SetVortexSphereBulletHitRules( entity vortexSphere, var functionref( entity, var ) customRules ) -{ - vortexSphere.e.BulletHitRules = customRules -} - -void function SetVortexSphereProjectileHitRules( entity vortexSphere, bool functionref( entity, entity, bool ) customRules ) -{ - vortexSphere.e.ProjectileHitRules = customRules -} -#endif -function CreateVortexSphere( entity vortexWeapon, bool useCylinderCheck, bool blockOwnerWeapon, int sphereRadius = 40, int bulletFOV = 180 ) -{ - entity owner = vortexWeapon.GetWeaponOwner() - Assert( owner ) - - #if SERVER - //printt( "util ent:", vortexWeapon.GetWeaponUtilityEntity() ) - Assert ( !vortexWeapon.GetWeaponUtilityEntity(), "Tried to create more than one vortex sphere on a vortex weapon!" ) - - entity vortexSphere = CreateEntity( "vortex_sphere" ) - Assert( vortexSphere ) - - int spawnFlags = SF_ABSORB_BULLETS | SF_BLOCK_NPC_WEAPON_LOF - - if ( useCylinderCheck ) - { - spawnFlags = spawnFlags | SF_ABSORB_CYLINDER - vortexSphere.kv.height = sphereRadius * 2 - } - - if ( blockOwnerWeapon ) - spawnFlags = spawnFlags | SF_BLOCK_OWNER_WEAPON - - vortexSphere.kv.spawnflags = spawnFlags - - vortexSphere.kv.enabled = 0 - vortexSphere.kv.radius = sphereRadius - vortexSphere.kv.bullet_fov = bulletFOV - vortexSphere.kv.physics_pull_strength = 25 - vortexSphere.kv.physics_side_dampening = 6 - vortexSphere.kv.physics_fov = 360 - vortexSphere.kv.physics_max_mass = 2 - vortexSphere.kv.physics_max_size = 6 - Assert( owner.IsNPC() || owner.IsPlayer(), "Vortex script expects the weapon owner to be a player or NPC." ) - - SetVortexSphereBulletHitRules( vortexSphere, VortexBulletHitRules_Default ) - SetVortexSphereProjectileHitRules( vortexSphere, VortexProjectileHitRules_Default ) - - DispatchSpawn( vortexSphere ) - - vortexSphere.SetOwner( owner ) - - if ( owner.IsNPC() ) - { - vortexSphere.SetParent( owner, "PROPGUN" ) - vortexSphere.SetLocalOrigin( Vector( 0, 35, 0 ) ) - } - else - { - vortexSphere.SetParent( owner ) - vortexSphere.SetLocalOrigin( Vector( 0, 10, -30 ) ) - } - vortexSphere.SetAbsAngles( Vector( 0, 0, 0 ) ) //Setting local angles on a parented object is not supported - - vortexSphere.SetOwnerWeapon( vortexWeapon ) - vortexWeapon.SetWeaponUtilityEntity( vortexSphere ) - #endif - - SetVortexAmmo( vortexWeapon, 0 ) -} - - -function EnableVortexSphere( entity vortexWeapon ) -{ - string tagname = GetVortexTagName( vortexWeapon ) - entity weaponOwner = vortexWeapon.GetWeaponOwner() - local hasBurnMod = vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) - - #if SERVER - entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity() - Assert( vortexSphere ) - vortexSphere.FireNow( "Enable" ) - - thread SetPlayerUsingVortex( weaponOwner, vortexWeapon ) - - Vortex_CreateAbsorbFX_ControlPoints( vortexWeapon ) - - // world (3P) version of the vortex sphere FX - vortexSphere.s.worldFX <- CreateEntity( "info_particle_system" ) - - if ( hasBurnMod ) - { - if ( "fxChargingControlPointBurn" in vortexWeapon.s ) - vortexSphere.s.worldFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxChargingControlPointBurn ) ) - } - else - { - if ( "fxChargingControlPoint" in vortexWeapon.s ) - vortexSphere.s.worldFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxChargingControlPoint ) ) - } - - vortexSphere.s.worldFX.kv.start_active = 1 - vortexSphere.s.worldFX.SetOwner( weaponOwner ) - vortexSphere.s.worldFX.SetParent( vortexWeapon, tagname ) - vortexSphere.s.worldFX.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // not owner only - vortexSphere.s.worldFX.kv.cpoint1 = vortexWeapon.s.vortexSphereColorCP.GetTargetName() - vortexSphere.s.worldFX.SetStopType( "destroyImmediately" ) - - DispatchSpawn( vortexSphere.s.worldFX ) - #endif - - SetVortexAmmo( vortexWeapon, 0 ) - - #if CLIENT - if ( IsLocalViewPlayer( weaponOwner ) ) - { - local fxAlias = null - - if ( hasBurnMod ) - { - if ( "fxChargingFPControlPointBurn" in vortexWeapon.s ) - fxAlias = vortexWeapon.s.fxChargingFPControlPointBurn - } - else - { - if ( "fxChargingFPControlPoint" in vortexWeapon.s ) - fxAlias = vortexWeapon.s.fxChargingFPControlPoint - } - - if ( fxAlias ) - { - int sphereClientFXHandle = vortexWeapon.PlayWeaponEffectReturnViewEffectHandle( fxAlias, $"", tagname ) - thread VortexSphereColorUpdate( vortexWeapon, sphereClientFXHandle ) - } - } - #elseif SERVER - asset fxAlias = $"" - - if ( hasBurnMod ) - { - if ( "fxChargingFPControlPointReplayBurn" in vortexWeapon.s ) - fxAlias = expect asset( vortexWeapon.s.fxChargingFPControlPointReplayBurn ) - } - else - { - if ( "fxChargingFPControlPointReplay" in vortexWeapon.s ) - fxAlias = expect asset( vortexWeapon.s.fxChargingFPControlPointReplay ) - } - - if ( fxAlias != $"" ) - vortexWeapon.PlayWeaponEffect( fxAlias, $"", tagname ) - - thread VortexSphereColorUpdate( vortexWeapon ) - #endif -} - - -function DestroyVortexSphereFromVortexWeapon( entity vortexWeapon ) -{ - DisableVortexSphereFromVortexWeapon( vortexWeapon ) - - #if SERVER - DestroyVortexSphere( vortexWeapon.GetWeaponUtilityEntity() ) - vortexWeapon.SetWeaponUtilityEntity( null ) - #endif -} - -void function DestroyVortexSphere( entity vortexSphere ) -{ - if ( IsValid( vortexSphere ) ) - { - vortexSphere.s.worldFX.Destroy() - vortexSphere.Destroy() - } -} - - -function DisableVortexSphereFromVortexWeapon( entity vortexWeapon ) -{ - vortexWeapon.Signal( "VortexStopping" ) - - // server cleanup - #if SERVER - DisableVortexSphere( vortexWeapon.GetWeaponUtilityEntity() ) - Vortex_CleanupAllEffects( vortexWeapon ) - Vortex_ClearImpactEventData( vortexWeapon ) - #endif - - // client & server cleanup - - if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) ) - { - if ( "fxChargingFPControlPointBurn" in vortexWeapon.s ) - vortexWeapon.StopWeaponEffect( expect asset( vortexWeapon.s.fxChargingFPControlPointBurn ), $"" ) - if ( "fxChargingFPControlPointReplayBurn" in vortexWeapon.s ) - vortexWeapon.StopWeaponEffect( expect asset( vortexWeapon.s.fxChargingFPControlPointReplayBurn ), $"" ) - } - else - { - if ( "fxChargingFPControlPoint" in vortexWeapon.s ) - vortexWeapon.StopWeaponEffect( expect asset( vortexWeapon.s.fxChargingFPControlPoint ), $"" ) - if ( "fxChargingFPControlPointReplay" in vortexWeapon.s ) - vortexWeapon.StopWeaponEffect( expect asset( vortexWeapon.s.fxChargingFPControlPointReplay ), $"" ) - } -} - -void function DisableVortexSphere( entity vortexSphere ) -{ - if ( IsValid( vortexSphere ) ) - { - vortexSphere.FireNow( "Disable" ) - vortexSphere.Signal( SIGNAL_ID_BULLET_HIT_THINK ) - } - -} - - -#if SERVER -function Vortex_CreateAbsorbFX_ControlPoints( entity vortexWeapon ) -{ - entity player = vortexWeapon.GetWeaponOwner() - Assert( player ) - - // vortex swirling incoming rounds FX location control point - if ( !( "vortexBulletEffectCP" in vortexWeapon.s ) ) - vortexWeapon.s.vortexBulletEffectCP <- null - vortexWeapon.s.vortexBulletEffectCP = CreateEntity( "info_placement_helper" ) - SetTargetName( expect entity( vortexWeapon.s.vortexBulletEffectCP ), UniqueString( "vortexBulletEffectCP" ) ) - vortexWeapon.s.vortexBulletEffectCP.kv.start_active = 1 - - DispatchSpawn( vortexWeapon.s.vortexBulletEffectCP ) - - vector offset = GetBulletCollectionOffset( vortexWeapon ) - vector origin = player.OffsetPositionFromView( player.EyePosition(), offset ) - - vortexWeapon.s.vortexBulletEffectCP.SetOrigin( origin ) - vortexWeapon.s.vortexBulletEffectCP.SetParent( player ) - - // vortex sphere color control point - if ( !( "vortexSphereColorCP" in vortexWeapon.s ) ) - vortexWeapon.s.vortexSphereColorCP <- null - vortexWeapon.s.vortexSphereColorCP = CreateEntity( "info_placement_helper" ) - SetTargetName( expect entity( vortexWeapon.s.vortexSphereColorCP ), UniqueString( "vortexSphereColorCP" ) ) - vortexWeapon.s.vortexSphereColorCP.kv.start_active = 1 - - DispatchSpawn( vortexWeapon.s.vortexSphereColorCP ) -} - - -function Vortex_CleanupAllEffects( entity vortexWeapon ) -{ - Assert( IsServer() ) - - Vortex_CleanupImpactAbsorbFX( vortexWeapon ) - - if ( ( "vortexBulletEffectCP" in vortexWeapon.s ) && IsValid_ThisFrame( expect entity( vortexWeapon.s.vortexBulletEffectCP ) ) ) - vortexWeapon.s.vortexBulletEffectCP.Destroy() - - if ( ( "vortexSphereColorCP" in vortexWeapon.s ) && IsValid_ThisFrame( expect entity( vortexWeapon.s.vortexSphereColorCP ) ) ) - vortexWeapon.s.vortexSphereColorCP.Destroy() -} -#endif // SERVER - - -function SetPlayerUsingVortex( entity weaponOwner, entity vortexWeapon ) -{ - weaponOwner.EndSignal( "OnDeath" ) - - weaponOwner.s.isVortexing <- true - - vortexWeapon.WaitSignal( "VortexStopping" ) - - OnThreadEnd - ( - function() : ( weaponOwner ) - { - if ( IsValid_ThisFrame( weaponOwner ) && "isVortexing" in weaponOwner.s ) - { - delete weaponOwner.s.isVortexing - } - } - ) -} - - -function IsVortexing( entity ent ) -{ - Assert( IsServer() ) - - if ( "isVortexing" in ent.s ) - return true -} - - -#if SERVER -function Vortex_HandleElectricDamage( entity ent, entity attacker, damage, entity weapon ) -{ - if ( !IsValid( ent ) ) - return damage - - if ( !ent.IsTitan() ) - return damage - - if ( !ent.IsPlayer() && !ent.IsNPC() ) - return damage - - if ( !IsVortexing( ent ) ) - return damage - - entity vortexWeapon = ent.GetActiveWeapon() - if ( !IsValid( vortexWeapon ) ) - return damage - - entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity() - if ( !IsValid( vortexSphere ) ) - return damage - - if ( !IsValid( vortexWeapon ) || !IsValid( vortexSphere ) ) - return damage - - // vortex FOV check - //printt( "sphere FOV:", vortexSphere.kv.bullet_fov ) - local sphereFOV = vortexSphere.kv.bullet_fov.tointeger() - entity attackerWeapon = attacker.GetActiveWeapon() - int attachIdx = attackerWeapon.LookupAttachment( "muzzle_flash" ) - vector beamOrg = attackerWeapon.GetAttachmentOrigin( attachIdx ) - vector firingDir = beamOrg - vortexSphere.GetOrigin() - firingDir = Normalize( firingDir ) - vector vortexDir = AnglesToForward( vortexSphere.GetAngles() ) - - float dot = DotProduct( vortexDir, firingDir ) - - float degCos = DEG_COS_60 - if ( sphereFOV != 120 ) - deg_cos( sphereFOV * 0.5 ) - - // not in the vortex cone - if ( dot < degCos ) - return damage - - if ( "fxElectricalExplosion" in vortexWeapon.s ) - { - entity fxRef = CreateEntity( "info_particle_system" ) - fxRef.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxElectricalExplosion ) ) - fxRef.kv.start_active = 1 - fxRef.SetStopType( "destroyImmediately" ) - //fxRef.kv.VisibilityFlags = ENTITY_VISIBLE_TO_OWNER // HACK this turns on owner only visibility. Uncomment when we hook up dedicated 3P effects - fxRef.SetOwner( ent ) - fxRef.SetOrigin( vortexSphere.GetOrigin() ) - fxRef.SetParent( ent ) - - DispatchSpawn( fxRef ) - fxRef.Kill_Deprecated_UseDestroyInstead( 1 ) - } - - return 0 -} - -// this function handles all incoming vortex impact events -bool function TryVortexAbsorb( entity vortexSphere, entity attacker, vector origin, int damageSourceID, entity weapon, string weaponName, string impactType, entity projectile = null, damageType = null, reflect = false ) -{ - if ( weaponName in VortexIgnoreClassnames ) - return false - - entity vortexWeapon = vortexSphere.GetOwnerWeapon() - entity owner = vortexWeapon.GetWeaponOwner() - - // keep cycling the oldest hitscan bullets out - if( !reflect ) - { - if ( impactType == "hitscan" ) - Vortex_ClampAbsorbedBulletCount( vortexWeapon ) - else - Vortex_ClampAbsorbedProjectileCount( vortexWeapon ) - } - - // vortex spheres tag refired projectiles with info about the original projectile for accurate duplication when re-absorbed - if ( projectile ) - { - - // specifically for tether, since it gets moved to the vortex area and can get absorbed in the process, then destroyed - if ( !IsValid( projectile ) ) - return false - - entity projOwner = projectile.GetOwner() - if ( IsValid( projOwner ) && projOwner.GetTeam() == owner.GetTeam() ) - return false - - if ( projectile.proj.hasBouncedOffVortex ) - return false - - if ( projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_ignores_vortex" ) == "fall_vortex" ) - { - vector velocity = projectile.GetVelocity() - vector multiplier = < -0.25, -0.25, -0.25 > - velocity = < velocity.x * multiplier.x, velocity.y * multiplier.y, velocity.z * multiplier.z > - projectile.SetVelocity( velocity ) - projectile.proj.hasBouncedOffVortex = true - return false - } - - // if ( projectile.GetParent() == owner ) - // return false - - if ( "originalDamageSource" in projectile.s ) - { - damageSourceID = expect int( projectile.s.originalDamageSource ) - - // Vortex Volley Achievement - if ( IsValid( owner ) && owner.IsPlayer() ) - { - //if ( PlayerProgressionAllowed( owner ) ) - // SetAchievement( owner, "ach_vortexVolley", true ) - } - } - - // Max projectile stat tracking - int projectilesInVortex = 1 - projectilesInVortex += vortexWeapon.w.vortexImpactData.len() - - if ( IsValid( owner ) && owner.IsPlayer() ) - { - if ( PlayerProgressionAllowed( owner ) ) - { - int record = owner.GetPersistentVarAsInt( "mostProjectilesCollectedInVortex" ) - if ( projectilesInVortex > record ) - owner.SetPersistentVar( "mostProjectilesCollectedInVortex", projectilesInVortex ) - } - - var impact_sound_1p = projectile.ProjectileGetWeaponInfoFileKeyField( "vortex_impact_sound_1p" ) - if ( impact_sound_1p != null ) - EmitSoundOnEntityOnlyToPlayer( vortexSphere, owner, impact_sound_1p ) - } - - var impact_sound_3p = projectile.ProjectileGetWeaponInfoFileKeyField( "vortex_impact_sound_3p" ) - if ( impact_sound_3p != null ) - EmitSoundAtPosition( TEAM_UNASSIGNED, origin, impact_sound_3p ) - } - else - { - if ( IsValid( owner ) && owner.IsPlayer() ) - { - var impact_sound_1p = GetWeaponInfoFileKeyField_Global( weaponName, "vortex_impact_sound_1p" ) - if ( impact_sound_1p != null ) - EmitSoundOnEntityOnlyToPlayer( vortexSphere, owner, impact_sound_1p ) - } - - var impact_sound_3p = GetWeaponInfoFileKeyField_Global( weaponName, "vortex_impact_sound_3p" ) - if ( impact_sound_3p != null ) - EmitSoundAtPosition( TEAM_UNASSIGNED, origin, impact_sound_3p ) - } - - local impactData = Vortex_CreateImpactEventData( vortexWeapon, attacker, origin, damageSourceID, weaponName, impactType ) - - VortexDrainedByImpact( vortexWeapon, weapon, projectile, damageType ) - Vortex_NotifyAttackerDidDamage( expect entity( impactData.attacker ), owner, impactData.origin ) - - if ( impactData.refireBehavior == VORTEX_REFIRE_ABSORB ) - return true - - if ( vortexWeapon.GetWeaponClassName() == "mp_titanweapon_heat_shield" ) - return true - - if ( !Vortex_ScriptCanHandleImpactEvent( impactData ) ) - return false - - Vortex_StoreImpactEvent( vortexWeapon, impactData ) - - VortexImpact_PlayAbsorbedFX( vortexWeapon, impactData ) - - if ( impactType == "hitscan" ) - vortexSphere.AddBulletToSphere(); - else - vortexSphere.AddProjectileToSphere(); - - local maxShotgunPelletsToIgnore = VORTEX_BULLET_ABSORB_COUNT_MAX * ( 1 - VORTEX_SHOTGUN_DAMAGE_RATIO ) - if ( IsPilotShotgunWeapon( weaponName ) && ( vortexWeapon.s.shotgunPelletsToIgnore + 1 ) < maxShotgunPelletsToIgnore ) - vortexWeapon.s.shotgunPelletsToIgnore += ( 1 - VORTEX_SHOTGUN_DAMAGE_RATIO ) - - if ( reflect ) - { - local attackParams = {} - attackParams.pos <- owner.EyePosition() - attackParams.dir <- owner.GetPlayerOrNPCViewVector() - - int bulletsFired = VortexReflectAttack( vortexWeapon, attackParams, expect vector( impactData.origin ) ) - - Vortex_CleanupImpactAbsorbFX( vortexWeapon ) - Vortex_ClearImpactEventData( vortexWeapon ) - - while ( vortexSphere.GetBulletAbsorbedCount() > 0 ) - vortexSphere.RemoveBulletFromSphere(); - - while ( vortexSphere.GetProjectileAbsorbedCount() > 0 ) - vortexSphere.RemoveProjectileFromSphere(); - } - - return true -} -#endif // SERVER - -function VortexDrainedByImpact( entity vortexWeapon, entity weapon, entity projectile, damageType ) -{ - if ( vortexWeapon.HasMod( "unlimited_charge_time" ) ) - return - if ( vortexWeapon.HasMod( "vortex_extended_effect_and_no_use_penalty" ) ) - return - - float amount - if ( projectile ) - amount = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.vortex_drain ) - else - amount = 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 ) - } -} - - -function VortexSlowOwnerFromAttacker( entity player, entity attacker, vector velocity, float multiplier ) -{ - vector damageForward = player.GetOrigin() - attacker.GetOrigin() - damageForward.z = 0 - damageForward.Norm() - - vector velForward = player.GetVelocity() - velForward.z = 0 - velForward.Norm() - - float dot = DotProduct( velForward, damageForward ) - if ( dot >= -0.5 ) - return - - dot += 0.5 - dot *= -2.0 - - vector negateVelocity = velocity * -multiplier - negateVelocity *= dot - - velocity += negateVelocity - player.SetVelocity( velocity ) -} - - -#if SERVER -function Vortex_ClampAbsorbedBulletCount( entity vortexWeapon ) -{ - if ( GetBulletsAbsorbedCount( vortexWeapon ) >= ( VORTEX_BULLET_ABSORB_COUNT_MAX - 1 ) ) - Vortex_RemoveOldestAbsorbedBullet( vortexWeapon ) -} - -function Vortex_ClampAbsorbedProjectileCount( entity vortexWeapon ) -{ - if ( GetProjectilesAbsorbedCount( vortexWeapon ) >= ( VORTEX_PROJECTILE_ABSORB_COUNT_MAX - 1 ) ) - Vortex_RemoveOldestAbsorbedProjectile( vortexWeapon ) -} - -function Vortex_RemoveOldestAbsorbedBullet( entity vortexWeapon ) -{ - entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity() - - local bulletImpacts = Vortex_GetHitscanBulletImpacts( vortexWeapon ) - local impactDataToRemove = bulletImpacts[ 0 ] // since it's an array, the first one will be the oldest - - Vortex_RemoveImpactEvent( vortexWeapon, impactDataToRemove ) - - vortexSphere.RemoveBulletFromSphere() -} - -function Vortex_RemoveOldestAbsorbedProjectile( entity vortexWeapon ) -{ - entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity() - - local projImpacts = Vortex_GetProjectileImpacts( vortexWeapon ) - local impactDataToRemove = projImpacts[ 0 ] // since it's an array, the first one will be the oldest - - Vortex_RemoveImpactEvent( vortexWeapon, impactDataToRemove ) - - vortexSphere.RemoveProjectileFromSphere() -} - -function Vortex_CreateImpactEventData( entity vortexWeapon, entity attacker, vector origin, int damageSourceID, string weaponName, string impactType ) -{ - entity player = vortexWeapon.GetWeaponOwner() - local impactData = {} - - impactData.attacker <- attacker - impactData.origin <- origin - impactData.damageSourceID <- damageSourceID - impactData.weaponName <- weaponName - impactData.impactType <- impactType - - impactData.refireBehavior <- VORTEX_REFIRE_NONE - impactData.absorbSFX <- "Vortex_Shield_AbsorbBulletSmall" - impactData.absorbSFX_1p_vs_3p <- null - - impactData.team <- null - // sets a team even if the attacker disconnected - if ( IsValid_ThisFrame( attacker ) ) - { - impactData.team = attacker.GetTeam() - } - else - { - // default to opposite team - if ( player.GetTeam() == TEAM_IMC ) - impactData.team = TEAM_MILITIA - else - impactData.team = TEAM_IMC - } - - impactData.absorbFX <- null - impactData.absorbFX_3p <- null - impactData.fxEnt_absorb <- null - - impactData.explosionradius <- null - impactData.explosion_damage <- null - impactData.impact_effect_table <- -1 - // -- everything from here down relies on being able to read a megaweapon file - if ( !( impactData.weaponName in vortexImpactWeaponInfo ) ) - { - vortexImpactWeaponInfo[ impactData.weaponName ] <- {} - vortexImpactWeaponInfo[ impactData.weaponName ].absorbFX <- GetWeaponInfoFileKeyFieldAsset_Global( impactData.weaponName, "vortex_absorb_effect" ) - vortexImpactWeaponInfo[ impactData.weaponName ].absorbFX_3p <- GetWeaponInfoFileKeyFieldAsset_Global( impactData.weaponName, "vortex_absorb_effect_third_person" ) - vortexImpactWeaponInfo[ impactData.weaponName ].refireBehavior <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "vortex_refire_behavior" ) - vortexImpactWeaponInfo[ impactData.weaponName ].absorbSound <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "vortex_absorb_sound" ) - vortexImpactWeaponInfo[ impactData.weaponName ].absorbSound_1p_vs_3p <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "vortex_absorb_sound_1p_vs_3p" ) - vortexImpactWeaponInfo[ impactData.weaponName ].explosionradius <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "explosionradius" ) - vortexImpactWeaponInfo[ impactData.weaponName ].explosion_damage_heavy_armor <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "explosion_damage_heavy_armor" ) - vortexImpactWeaponInfo[ impactData.weaponName ].explosion_damage <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "explosion_damage" ) - vortexImpactWeaponInfo[ impactData.weaponName ].impact_effect_table <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "impact_effect_table" ) - vortexImpactWeaponInfo[ impactData.weaponName ].grenade_ignition_time <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "grenade_ignition_time" ) - vortexImpactWeaponInfo[ impactData.weaponName ].grenade_fuse_time <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "grenade_fuse_time" ) - } - - impactData.absorbFX = vortexImpactWeaponInfo[ impactData.weaponName ].absorbFX - impactData.absorbFX_3p = vortexImpactWeaponInfo[ impactData.weaponName ].absorbFX_3p - if ( impactData.absorbFX ) - Assert( impactData.absorbFX_3p, "Missing 3rd person absorb effect for " + impactData.weaponName ) - impactData.refireBehavior = vortexImpactWeaponInfo[ impactData.weaponName ].refireBehavior - - local absorbSound = vortexImpactWeaponInfo[ impactData.weaponName ].absorbSound - if ( absorbSound ) - impactData.absorbSFX = absorbSound - - local absorbSound_1p_vs_3p = vortexImpactWeaponInfo[ impactData.weaponName ].absorbSound_1p_vs_3p - if ( absorbSound_1p_vs_3p ) - impactData.absorbSFX_1p_vs_3p = absorbSound_1p_vs_3p - - // info we need for refiring (some types of) impacts - impactData.explosionradius = vortexImpactWeaponInfo[ impactData.weaponName ].explosionradius - impactData.explosion_damage = vortexImpactWeaponInfo[ impactData.weaponName ].explosion_damage_heavy_armor - if ( impactData.explosion_damage == null ) - impactData.explosion_damage = vortexImpactWeaponInfo[ impactData.weaponName ].explosion_damage - impactData.impact_effect_table = vortexImpactWeaponInfo[ impactData.weaponName ].impact_effect_table - - return impactData -} - -function Vortex_ScriptCanHandleImpactEvent( impactData ) -{ - if ( impactData.refireBehavior == VORTEX_REFIRE_NONE ) - return false - - if ( !impactData.absorbFX ) - return false - - if ( impactData.impactType == "projectile" && !impactData.impact_effect_table ) - return false - - return true -} - -function Vortex_StoreImpactEvent( entity vortexWeapon, impactData ) -{ - vortexWeapon.w.vortexImpactData.append( impactData ) -} - -// safely removes data for a single impact event -function Vortex_RemoveImpactEvent( entity vortexWeapon, impactData ) -{ - Vortex_ImpactData_KillAbsorbFX( impactData ) - - vortexWeapon.w.vortexImpactData.fastremovebyvalue( impactData ) -} - -function Vortex_GetAllImpactEvents( entity vortexWeapon ) -{ - return vortexWeapon.w.vortexImpactData -} - -function Vortex_ClearImpactEventData( entity vortexWeapon ) -{ - vortexWeapon.w.vortexImpactData = [] -} - -function VortexImpact_PlayAbsorbedFX( entity vortexWeapon, impactData ) -{ - // generic shield ping FX - Vortex_SpawnShieldPingFX( vortexWeapon, impactData ) - - // specific absorb FX - impactData.fxEnt_absorb = Vortex_SpawnImpactAbsorbFX( vortexWeapon, impactData ) -} - -// FX played when something first enters the vortex sphere -function Vortex_SpawnShieldPingFX( entity vortexWeapon, impactData ) -{ - entity player = vortexWeapon.GetWeaponOwner() - Assert( player ) - - local absorbSFX = impactData.absorbSFX - //printt( "SFX absorb sound:", absorbSFX ) - if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) ) - EmitSoundOnEntity( vortexWeapon, "Vortex_Shield_Deflect_Amped" ) - else - { - EmitSoundOnEntity( vortexWeapon, absorbSFX ) - if ( impactData.absorbSFX_1p_vs_3p != null ) - { - if ( IsValid( impactData.attacker ) && impactData.attacker.IsPlayer() ) - { - EmitSoundOnEntityOnlyToPlayer( vortexWeapon, impactData.attacker, impactData.absorbSFX_1p_vs_3p ) - } - } - } - - entity pingFX = CreateEntity( "info_particle_system" ) - - if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) ) - { - if ( "fxBulletHitBurn" in vortexWeapon.s ) - pingFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxBulletHitBurn ) ) - } - else - { - if ( "fxBulletHit" in vortexWeapon.s ) - pingFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxBulletHit ) ) - } - - pingFX.kv.start_active = 1 - - DispatchSpawn( pingFX ) - - pingFX.SetOrigin( impactData.origin ) - pingFX.SetParent( player ) - pingFX.Kill_Deprecated_UseDestroyInstead( 0.25 ) -} - -function Vortex_SpawnHeatShieldPingFX( entity vortexWeapon, impactData, bool impactTypeIsBullet ) -{ - entity player = vortexWeapon.GetWeaponOwner() - Assert( player ) - - if ( impactTypeIsBullet ) - EmitSoundOnEntity( vortexWeapon, "heat_shield_stop_bullet" ) - else - EmitSoundOnEntity( vortexWeapon, "heat_shield_stop_projectile" ) - - entity pingFX = CreateEntity( "info_particle_system" ) - - if ( "fxBulletHit" in vortexWeapon.s ) - pingFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxBulletHit ) ) - - pingFX.kv.start_active = 1 - - DispatchSpawn( pingFX ) - - pingFX.SetOrigin( impactData.origin ) - pingFX.SetParent( player ) - pingFX.Kill_Deprecated_UseDestroyInstead( 0.25 ) -} - -function Vortex_SpawnImpactAbsorbFX( entity vortexWeapon, impactData ) -{ - // in case we're in the middle of cleaning the weapon up - if ( !IsValid( vortexWeapon.s.vortexBulletEffectCP ) ) - return - - entity owner = vortexWeapon.GetWeaponOwner() - Assert( owner ) - - local fxRefs = [] - - // owner - { - entity fxRef = CreateEntity( "info_particle_system" ) - - fxRef.SetValueForEffectNameKey( expect asset( impactData.absorbFX ) ) - fxRef.kv.start_active = 1 - fxRef.SetStopType( "destroyImmediately" ) - fxRef.kv.VisibilityFlags = ENTITY_VISIBLE_TO_OWNER - fxRef.kv.cpoint1 = vortexWeapon.s.vortexBulletEffectCP.GetTargetName() - - DispatchSpawn( fxRef ) - - fxRef.SetOwner( owner ) - fxRef.SetOrigin( impactData.origin ) - fxRef.SetParent( owner ) - - fxRefs.append( fxRef ) - } - - // everyone else - { - entity fxRef = CreateEntity( "info_particle_system" ) - - fxRef.SetValueForEffectNameKey( expect asset( impactData.absorbFX_3p ) ) - fxRef.kv.start_active = 1 - fxRef.SetStopType( "destroyImmediately" ) - fxRef.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // other only visibility - fxRef.kv.cpoint1 = vortexWeapon.s.vortexBulletEffectCP.GetTargetName() - - DispatchSpawn( fxRef ) - - fxRef.SetOwner( owner ) - fxRef.SetOrigin( impactData.origin ) - fxRef.SetParent( owner ) - - fxRefs.append( fxRef ) - } - - return fxRefs -} - -function Vortex_CleanupImpactAbsorbFX( entity vortexWeapon ) -{ - foreach ( impactData in Vortex_GetAllImpactEvents( vortexWeapon ) ) - { - Vortex_ImpactData_KillAbsorbFX( impactData ) - } -} - -function Vortex_ImpactData_KillAbsorbFX( impactData ) -{ - foreach ( fxRef in impactData.fxEnt_absorb ) - { - if ( !IsValid( fxRef ) ) - continue - - fxRef.Fire( "DestroyImmediately" ) - fxRef.Kill_Deprecated_UseDestroyInstead() - } -} - -bool function PlayerDiedOrDisconnected( entity player ) -{ - if ( !IsValid( player ) ) - return true - - if ( !IsAlive( player ) ) - return true - - if ( IsDisconnected( player ) ) - return true - - return false -} - -#endif // SERVER - -int function VortexPrimaryAttack( entity vortexWeapon, WeaponPrimaryAttackParams attackParams ) -{ - entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity() - if ( !vortexSphere ) - return 0 - - #if SERVER - Assert( vortexSphere ) - #endif - - int totalfired = 0 - int totalAttempts = 0 - - bool forceReleased = false - // in this case, it's also considered "force released" if the charge time runs out - if ( vortexWeapon.IsForceRelease() || vortexWeapon.GetWeaponChargeFraction() == 1 ) - forceReleased = true - - // PREDICTED REFIRES - // bullet impact events don't individually fire back per event because we aggregate and then shotgun blast them - int bulletsFired = Vortex_FireBackBullets( vortexWeapon, attackParams ) - totalfired += bulletsFired - - // UNPREDICTED REFIRES - #if SERVER - //printt( "server: force released?", forceReleased ) - - local unpredictedRefires = Vortex_GetProjectileImpacts( vortexWeapon ) - - // HACK we don't actually want to refire them with a spiral but - // this is to temporarily ensure compatibility with the Titan rocket launcher - if ( !( "spiralMissileIdx" in vortexWeapon.s ) ) - vortexWeapon.s.spiralMissileIdx <- null - vortexWeapon.s.spiralMissileIdx = 0 - - foreach ( impactData in unpredictedRefires ) - { - table fakeAttackParams = {pos = attackParams.pos, dir = attackParams.dir, firstTimePredicted = attackParams.firstTimePredicted, burstIndex = attackParams.burstIndex} - bool didFire = DoVortexAttackForImpactData( vortexWeapon, fakeAttackParams, impactData, totalAttempts ) - if ( didFire ) - totalfired++ - totalAttempts++ - } - //printt( "totalfired", totalfired ) - #else - totalfired += GetProjectilesAbsorbedCount( vortexWeapon ) - #endif - - SetVortexAmmo( vortexWeapon, 0 ) - - vortexWeapon.Signal( "VortexFired" ) - - if ( forceReleased ) - DestroyVortexSphereFromVortexWeapon( vortexWeapon ) - else - DisableVortexSphereFromVortexWeapon( vortexWeapon ) - - return totalfired -} - -int function Vortex_FireBackBullets( entity vortexWeapon, WeaponPrimaryAttackParams attackParams ) -{ - int bulletCount = GetBulletsAbsorbedCount( vortexWeapon ) - //Defensive Check - Couldn't repro error. - if ( "shotgunPelletsToIgnore" in vortexWeapon.s ) - bulletCount = int( ceil( bulletCount - vortexWeapon.s.shotgunPelletsToIgnore ) ) - - if ( bulletCount ) - { - bulletCount = minint( bulletCount, MAX_BULLET_PER_SHOT ) - - //if ( IsClient() && GetLocalViewPlayer() == vortexWeapon.GetWeaponOwner() ) - // printt( "vortex firing", bulletCount, "bullets" ) - - float radius = LOUD_WEAPON_AI_SOUND_RADIUS_MP; - vortexWeapon.EmitWeaponNpcSound( radius, 0.2 ) - int damageType = damageTypes.shotgun | DF_VORTEX_REFIRE - if ( bulletCount == 1 ) - vortexWeapon.FireWeaponBullet( attackParams.pos, attackParams.dir, bulletCount, damageType ) - else - ShotgunBlast( vortexWeapon, attackParams.pos, attackParams.dir, bulletCount, damageType ) - } - - return bulletCount -} - -#if SERVER -bool function Vortex_FireBackExplosiveRound( vortexWeapon, attackParams, impactData, sequenceID ) -{ - expect entity( vortexWeapon ) - - // common projectile data - float projSpeed = 8000.0 - int damageType = damageTypes.explosive | DF_VORTEX_REFIRE - - vortexWeapon.EmitWeaponSound( "Weapon.Explosion_Med" ) - - vector attackPos - //Requires code feature to properly fire tracers from offset positions. - //if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) ) - // attackPos = impactData.origin - //else - attackPos = Vortex_GenerateRandomRefireOrigin( vortexWeapon ) - - vector fireVec = Vortex_GenerateRandomRefireVector( vortexWeapon, VORTEX_EXP_ROUNDS_RETURN_SPREAD_XY, VORTEX_EXP_ROUNDS_RETURN_SPREAD_Z ) - - // fire off the bolt - entity bolt = vortexWeapon.FireWeaponBolt( attackPos, fireVec, projSpeed, damageType, damageType, PROJECTILE_NOT_PREDICTED, sequenceID ) - if ( bolt ) - { - bolt.kv.gravity = 0.3 - - Vortex_ProjectileCommonSetup( bolt, impactData ) - } - - return true -} - -bool function Vortex_FireBackProjectileBullet( vortexWeapon, attackParams, impactData, sequenceID ) -{ - expect entity( vortexWeapon ) - - // common projectile data - float projSpeed = 12000.0 - int damageType = damageTypes.bullet | DF_VORTEX_REFIRE - - vortexWeapon.EmitWeaponSound( "Weapon.Explosion_Med" ) - - vector attackPos - //Requires code feature to properly fire tracers from offset positions. - //if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) ) - // attackPos = impactData.origin - //else - attackPos = Vortex_GenerateRandomRefireOrigin( vortexWeapon ) - - vector fireVec = Vortex_GenerateRandomRefireVector( vortexWeapon, 0.15, 0.1 ) - //printt( Time(), fireVec ) // print for bug with random - - // fire off the bolt - entity bolt = vortexWeapon.FireWeaponBolt( attackPos, fireVec, projSpeed, damageType, damageType, PROJECTILE_NOT_PREDICTED, sequenceID ) - if ( bolt ) - { - bolt.kv.gravity = 0.0 - - Vortex_ProjectileCommonSetup( bolt, impactData ) - } - - return true -} - -vector function Vortex_GenerateRandomRefireOrigin( entity vortexWeapon, float distFromCenter = 3.0 ) -{ - float distFromCenter_neg = distFromCenter * -1 - - vector attackPos = expect vector( vortexWeapon.s.vortexBulletEffectCP.GetOrigin() ) - - float x = RandomFloatRange( distFromCenter_neg, distFromCenter ) - float y = RandomFloatRange( distFromCenter_neg, distFromCenter ) - float z = RandomFloatRange( distFromCenter_neg, distFromCenter ) - - attackPos = attackPos + Vector( x, y, z ) - - return attackPos -} - -vector function Vortex_GenerateRandomRefireVector( entity vortexWeapon, float vecSpread, float vecSpreadZ ) -{ - float x = RandomFloatRange( vecSpread * -1, vecSpread ) - float y = RandomFloatRange( vecSpread * -1, vecSpread ) - float z = RandomFloatRange( vecSpreadZ * -1, vecSpreadZ ) - - vector fireVec = vortexWeapon.GetWeaponOwner().GetPlayerOrNPCViewVector() + Vector( x, y, z ) - return fireVec -} - -bool function Vortex_FireBackRocket( vortexWeapon, attackParams, impactData, sequenceID ) -{ - expect entity( vortexWeapon ) - - // TODO prediction for clients - Assert( IsServer() ) - - entity rocket = vortexWeapon.FireWeaponMissile( attackParams.pos, attackParams.dir, 1800.0, damageTypes.largeCaliberExp | DF_VORTEX_REFIRE, damageTypes.largeCaliberExp | DF_VORTEX_REFIRE, false, PROJECTILE_NOT_PREDICTED ) - - if ( rocket ) - { - rocket.kv.lifetime = RandomFloatRange( 2.6, 3.5 ) - - InitMissileForRandomDriftForVortexLow( rocket, expect vector( attackParams.pos ), expect vector( attackParams.dir ) ) - - Vortex_ProjectileCommonSetup( rocket, impactData ) - } - - return true -} - -bool function Vortex_FireBackGrenade( entity vortexWeapon, attackParams, impactData, int attackSeedCount, float baseFuseTime ) -{ - float x = RandomFloatRange( -0.2, 0.2 ) - float y = RandomFloatRange( -0.2, 0.2 ) - float z = RandomFloatRange( -0.2, 0.2 ) - - vector velocity = ( expect vector( attackParams.dir ) + Vector( x, y, z ) ) * 1500 - vector angularVelocity = Vector( RandomFloatRange( -1200, 1200 ), 100, 0 ) - - bool hasIgnitionTime = vortexImpactWeaponInfo[ impactData.weaponName ].grenade_ignition_time > 0 - float fuseTime = hasIgnitionTime ? 0.0 : baseFuseTime - const int HARDCODED_DAMAGE_TYPE = (damageTypes.explosive | DF_VORTEX_REFIRE) - - entity grenade = vortexWeapon.FireWeaponGrenade( attackParams.pos, velocity, angularVelocity, fuseTime, HARDCODED_DAMAGE_TYPE, HARDCODED_DAMAGE_TYPE, PROJECTILE_NOT_PREDICTED, true, true ) - if ( grenade ) - { - Grenade_Init( grenade, vortexWeapon ) - Vortex_ProjectileCommonSetup( grenade, impactData ) - if ( hasIgnitionTime ) - grenade.SetGrenadeIgnitionDuration( vortexImpactWeaponInfo[ impactData.weaponName ].grenade_ignition_time ) - } - - return (grenade ? true : false) -} - -bool function DoVortexAttackForImpactData( entity vortexWeapon, attackParams, impactData, int attackSeedCount ) -{ - bool didFire = false - switch ( impactData.refireBehavior ) - { - case VORTEX_REFIRE_EXPLOSIVE_ROUND: - didFire = Vortex_FireBackExplosiveRound( vortexWeapon, attackParams, impactData, attackSeedCount ) - break - - case VORTEX_REFIRE_ROCKET: - didFire = Vortex_FireBackRocket( vortexWeapon, attackParams, impactData, attackSeedCount ) - break - - case VORTEX_REFIRE_GRENADE: - didFire = Vortex_FireBackGrenade( vortexWeapon, attackParams, impactData, attackSeedCount, 1.25 ) - break - - case VORTEX_REFIRE_GRENADE_LONG_FUSE: - didFire = Vortex_FireBackGrenade( vortexWeapon, attackParams, impactData, attackSeedCount, 10.0 ) - break - - case VORTEX_REFIRE_BULLET: - didFire = Vortex_FireBackProjectileBullet( vortexWeapon, attackParams, impactData, attackSeedCount ) - break - - case VORTEX_REFIRE_NONE: - break - } - - return didFire -} - -function Vortex_ProjectileCommonSetup( entity projectile, impactData ) -{ - // custom tag it so it shows up correctly if it hits another vortex sphere - projectile.s.originalDamageSource <- impactData.damageSourceID - - Vortex_SetImpactEffectTable_OnProjectile( projectile, impactData ) // set the correct impact effect table - - projectile.SetVortexRefired( true ) // This tells code the projectile was refired from the vortex so that it uses "projectile_vortex_vscript" - projectile.SetModel( GetWeaponInfoFileKeyFieldAsset_Global( impactData.weaponName, "projectilemodel" ) ) - projectile.SetWeaponClassName( impactData.weaponName ) // causes the projectile to use its normal trail FX - - projectile.ProjectileSetDamageSourceID( impactData.damageSourceID ) // obit will show the owner weapon -} - -// gives a refired projectile the correct impact effect table -function Vortex_SetImpactEffectTable_OnProjectile( projectile, impactData ) -{ - //Getting more info for bug 207595, don't check into Staging. - #if DEV - printt( "impactData.impact_effect_table ", impactData.impact_effect_table ) - if ( impactData.impact_effect_table == "" ) - PrintTable( impactData ) - #endif - - local fxTableHandle = GetImpactEffectTable( impactData.impact_effect_table ) - - projectile.SetImpactEffectTable( fxTableHandle ) -} -#endif // SERVER - -// absorbed bullets are tracked with a special networked kv variable because clients need to know how many bullets to fire as well, when they are doing the client version of FireWeaponBullet -int function GetBulletsAbsorbedCount( entity vortexWeapon ) -{ - if ( !vortexWeapon ) - return 0 - - entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity() - if ( !vortexSphere ) - return 0 - - return vortexSphere.GetBulletAbsorbedCount() -} - -int function GetProjectilesAbsorbedCount( entity vortexWeapon ) -{ - if ( !vortexWeapon ) - return 0 - - entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity() - if ( !vortexSphere ) - return 0 - - return vortexSphere.GetProjectileAbsorbedCount() -} - -#if SERVER -function Vortex_GetProjectileImpacts( entity vortexWeapon ) -{ - local impacts = [] - foreach ( impactData in Vortex_GetAllImpactEvents( vortexWeapon ) ) - { - if ( impactData.impactType == "projectile" ) - impacts.append( impactData ) - } - - return impacts -} - -function Vortex_GetHitscanBulletImpacts( entity vortexWeapon ) -{ - local impacts = [] - foreach ( impactData in Vortex_GetAllImpactEvents( vortexWeapon ) ) - { - if ( impactData.impactType == "hitscan" ) - impacts.append( impactData ) - } - - return impacts -} - -int function GetHitscanBulletImpactCount( entity vortexWeapon ) -{ - int count = 0 - foreach ( impactData in Vortex_GetAllImpactEvents( vortexWeapon ) ) - { - if ( impactData.impactType == "hitscan" ) - count++ - } - - return count -} -#endif // SERVER - -// // lets the damage callback communicate to the attacker that he hit a vortex shield -function Vortex_NotifyAttackerDidDamage( entity attacker, entity vortexOwner, hitPos ) -{ - if ( !IsValid( attacker ) || !attacker.IsPlayer() ) - return - - if ( !IsValid( vortexOwner ) ) - return - - Assert( hitPos ) - - attacker.NotifyDidDamage( vortexOwner, 0, hitPos, 0, 0, DAMAGEFLAG_VICTIM_HAS_VORTEX, 0, null, 0 ) -} - -function SetVortexAmmo( entity vortexWeapon, count ) -{ - entity owner = vortexWeapon.GetWeaponOwner() - if ( !IsValid_ThisFrame( owner ) ) - return - #if CLIENT - if ( !IsLocalViewPlayer( owner ) ) - return - #endif - - vortexWeapon.SetWeaponPrimaryAmmoCount( count ) -} - - -// sets the RGB color value for the vortex sphere FX based on current charge fraction -function VortexSphereColorUpdate( entity weapon, sphereClientFXHandle = null ) -{ - weapon.EndSignal( "VortexStopping" ) - - #if CLIENT - Assert( sphereClientFXHandle != null ) - #endif - bool isIonVortex = weapon.GetWeaponClassName() == "mp_titanweapon_vortex_shield_ion" - entity weaponOwner = weapon.GetWeaponOwner() - float energyTotal = float ( weaponOwner.GetSharedEnergyTotal() ) - while( IsValid( weapon ) && IsValid( weaponOwner ) ) - { - vector colorVec - if ( isIonVortex ) - { - float energyFrac = 1.0 - float( weaponOwner.GetSharedEnergyCount() ) / energyTotal - if ( weapon.HasMod( "pas_ion_vortex" ) ) - colorVec = GetVortexSphereCurrentColor( energyFrac, VORTEX_SPHERE_COLOR_PAS_ION_VORTEX ) - else - colorVec = GetVortexSphereCurrentColor( energyFrac ) - } - else - { - colorVec = GetVortexSphereCurrentColor( weapon.GetWeaponChargeFraction() ) - } - - - // update the world entity that is linked to the world FX playing on the server - #if SERVER - weapon.s.vortexSphereColorCP.SetOrigin( colorVec ) - #else - // handles the server killing the vortex sphere without the client knowing right away, - // for example if an explosive goes off and we short circuit the charge timer - if ( !EffectDoesExist( sphereClientFXHandle ) ) - break - - EffectSetControlPointVector( sphereClientFXHandle, 1, colorVec ) - #endif - - WaitFrame() - } -} - -vector function GetVortexSphereCurrentColor( float chargeFrac, vector fullHealthColor = VORTEX_SPHERE_COLOR_CHARGE_FULL ) -{ - return GetTriLerpColor( chargeFrac, fullHealthColor, VORTEX_SPHERE_COLOR_CHARGE_MED, VORTEX_SPHERE_COLOR_CHARGE_EMPTY ) -} - -vector function GetShieldTriLerpColor( float frac ) -{ - return GetTriLerpColor( frac, VORTEX_SPHERE_COLOR_CHARGE_FULL, VORTEX_SPHERE_COLOR_CHARGE_MED, VORTEX_SPHERE_COLOR_CHARGE_EMPTY ) -} - -vector function GetTriLerpColor( float fraction, vector color1, vector color2, vector color3 ) -{ - float crossover1 = VORTEX_SPHERE_COLOR_CROSSOVERFRAC_FULL2MED // from zero to this fraction, fade between color1 and color2 - float crossover2 = VORTEX_SPHERE_COLOR_CROSSOVERFRAC_MED2EMPTY // from crossover1 to this fraction, fade between color2 and color3 - - float r, g, b - - // 0 = full charge, 1 = no charge remaining - if ( fraction < crossover1 ) - { - r = Graph( fraction, 0, crossover1, color1.x, color2.x ) - g = Graph( fraction, 0, crossover1, color1.y, color2.y ) - b = Graph( fraction, 0, crossover1, color1.z, color2.z ) - return <r, g, b> - } - else if ( fraction < crossover2 ) - { - r = Graph( fraction, crossover1, crossover2, color2.x, color3.x ) - g = Graph( fraction, crossover1, crossover2, color2.y, color3.y ) - b = Graph( fraction, crossover1, crossover2, color2.z, color3.z ) - return <r, g, b> - } - else - { - // for the last bit of overload timer, keep it max danger color - r = color3.x - g = color3.y - b = color3.z - return <r, g, b> - } - - unreachable -} - -// generic impact validation -#if SERVER -bool function ValidateVortexImpact( entity vortexSphere, entity projectile = null ) -{ - Assert( IsServer() ) - - if ( !IsValid( vortexSphere ) ) - return false - - if ( !vortexSphere.GetOwnerWeapon() ) - return false - - entity vortexWeapon = vortexSphere.GetOwnerWeapon() - if ( !IsValid( vortexWeapon ) ) - return false - - if ( projectile ) - { - if ( !IsValid_ThisFrame( projectile ) ) - return false - - if ( projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_ignores_vortex" ) == 1 ) - return false - - if ( projectile.ProjectileGetWeaponClassName() == "" ) - return false - - // TEMP HACK - if ( projectile.ProjectileGetWeaponClassName() == "mp_weapon_tether" ) - return false - } - - return true -} -#endif - -/********************************/ -/* Setting override functions */ -/********************************/ - -function Vortex_SetTagName( entity weapon, string tagName ) -{ - Vortex_SetWeaponSettingOverride( weapon, "vortexTagName", tagName ) -} - -function Vortex_SetBulletCollectionOffset( entity weapon, vector offset ) -{ - Vortex_SetWeaponSettingOverride( weapon, "bulletCollectionOffset", offset ) -} - -function Vortex_SetWeaponSettingOverride( entity weapon, string setting, value ) -{ - if ( !( setting in weapon.s ) ) - weapon.s[ setting ] <- null - weapon.s[ setting ] = value -} - -string function GetVortexTagName( entity weapon ) -{ - if ( "vortexTagName" in weapon.s ) - return expect string( weapon.s.vortexTagName ) - - return "vortex_center" -} - -vector function GetBulletCollectionOffset( entity weapon ) -{ - if ( "bulletCollectionOffset" in weapon.s ) - return expect vector( weapon.s.bulletCollectionOffset ) - - entity owner = weapon.GetWeaponOwner() - if ( owner.IsTitan() ) - return Vector( 300.0, -90.0, -70.0 ) - else - return Vector( 80.0, 17.0, -11.0 ) - - unreachable -} - - -#if SERVER -function VortexSphereDrainHealthForDamage( entity vortexSphere, damage ) -{ - // don't drain the health of vortex_spheres that are set to be invulnerable. This is the case for the Particle Wall - if ( vortexSphere.IsInvulnerable() ) - return - - local result = {} - result.damage <- damage - vortexSphere.Signal( "Script_OnDamaged", result ) - - int currentHealth = vortexSphere.GetHealth() - Assert( damage >= 0 ) - // JFS to fix phone home bug; we never hit the assert above locally... - damage = max( damage, 0 ) - vortexSphere.SetHealth( currentHealth - damage ) - - entity vortexWeapon = vortexSphere.GetOwnerWeapon() - if ( IsValid( vortexWeapon ) && vortexWeapon.HasMod( "fd_gun_shield_redirect" ) ) - { - entity owner = vortexWeapon.GetWeaponOwner() - if ( IsValid( owner ) && owner.IsTitan() ) - { - entity soul = owner.GetTitanSoul() - if ( IsValid( soul ) ) - { - int shieldRestoreAmount = int( damage ) //Might need tuning - soul.SetShieldHealth( min( soul.GetShieldHealth() + shieldRestoreAmount, soul.GetShieldHealthMax() ) ) - } - } - } - - UpdateShieldWallColorForFrac( vortexSphere.e.shieldWallFX, GetHealthFrac( vortexSphere ) ) -} -#endif - - -bool function CodeCallback_OnVortexHitBullet( entity weapon, entity vortexSphere, var damageInfo ) -{ - bool isAmpedWall = vortexSphere.GetTargetName() == PROTO_AMPED_WALL - bool takesDamage = !isAmpedWall - bool adjustImpactAngles = !(vortexSphere.GetTargetName() == GUN_SHIELD_WALL) - - #if SERVER - if ( vortexSphere.e.BulletHitRules != null ) - { - vortexSphere.e.BulletHitRules( vortexSphere, damageInfo ) - takesDamage = takesDamage && (DamageInfo_GetDamage( damageInfo ) > 0) - } - #endif - - vector damageAngles = vortexSphere.GetAngles() - - if ( adjustImpactAngles ) - damageAngles = AnglesCompose( damageAngles, Vector( 90, 0, 0 ) ) - - int teamNum = vortexSphere.GetTeam() - - #if CLIENT - vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo ) - if ( !isAmpedWall ) - { - // TODO: slightly change angles to match radius rotation of vortex cylinder - int effectHandle = StartParticleEffectInWorldWithHandle( GetParticleSystemIndex( SHIELD_WALL_BULLET_FX ), damageOrigin, damageAngles ) - //local color = GetShieldTriLerpColor( 1 - GetHealthFrac( vortexSphere ) ) - vector color = GetShieldTriLerpColor( 0.0 ) - EffectSetControlPointVector( effectHandle, 1, color ) - } - - if ( takesDamage ) - { - float damage = ceil( DamageInfo_GetDamage( damageInfo ) ) - int damageType = DamageInfo_GetCustomDamageType( damageInfo ) - DamageFlyout( damage, damageOrigin, vortexSphere, false, false ) - } - - if ( DamageInfo_GetAttacker( damageInfo ) && DamageInfo_GetAttacker( damageInfo ).IsTitan() ) - EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Heavy.BulletImpact_1P_vs_3P" ) - else - EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Light.BulletImpact_1P_vs_3P" ) - #else - if ( !isAmpedWall ) - { - int fxId = GetParticleSystemIndex( SHIELD_WALL_BULLET_FX ) - PlayEffectOnVortexSphere( fxId, DamageInfo_GetDamagePosition( damageInfo ), damageAngles, vortexSphere ) - } - - entity weapon = DamageInfo_GetWeapon( damageInfo ) - float damage = ceil( DamageInfo_GetDamage( damageInfo ) ) - - Assert( damage >= 0, "Bug 159851 - Damage should be greater than or equal to 0.") - damage = max( 0.0, damage ) - - if ( IsValid( weapon ) ) - damage = HandleWeakToPilotWeapons( vortexSphere, weapon.GetWeaponClassName(), damage ) - - if ( takesDamage ) - { - //JFS - Arc Round bug fix for Monarch. Projectiles vortex callback doesn't even have damageInfo, so the shield modifier here doesn't exist in VortexSphereDrainHealthForDamage like it should. - ShieldDamageModifier damageModifier = GetShieldDamageModifier( damageInfo ) - damage *= damageModifier.damageScale - VortexSphereDrainHealthForDamage( vortexSphere, damage ) - } - - if ( DamageInfo_GetAttacker( damageInfo ) && DamageInfo_GetAttacker( damageInfo ).IsTitan() ) - EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Heavy.BulletImpact_3P_vs_3P" ) - else - EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Light.BulletImpact_3P_vs_3P" ) - #endif - - if ( isAmpedWall ) - { - #if SERVER - DamageInfo_ScaleDamage( damageInfo, AMPED_DAMAGE_SCALAR ) - #endif - return false - } - - return true -} - -bool function OnVortexHitBullet_BubbleShieldNPC( entity vortexSphere, var damageInfo ) -{ - vector vortexOrigin = vortexSphere.GetOrigin() - vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo ) - - float distSq = DistanceSqr( vortexOrigin, damageOrigin ) - if ( distSq < MINION_BUBBLE_SHIELD_RADIUS_SQR ) - return false//the damage is coming from INSIDE the sphere - - vector damageVec = damageOrigin - vortexOrigin - vector damageAngles = VectorToAngles( damageVec ) - damageAngles = AnglesCompose( damageAngles, Vector( 90, 0, 0 ) ) - - int teamNum = vortexSphere.GetTeam() - - #if CLIENT - int effectHandle = StartParticleEffectInWorldWithHandle( GetParticleSystemIndex( SHIELD_WALL_BULLET_FX ), damageOrigin, damageAngles ) - - vector color = GetShieldTriLerpColor( 0.9 ) - EffectSetControlPointVector( effectHandle, 1, color ) - - if ( DamageInfo_GetAttacker( damageInfo ) && DamageInfo_GetAttacker( damageInfo ).IsTitan() ) - EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Heavy.BulletImpact_1P_vs_3P" ) - else - EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Light.BulletImpact_1P_vs_3P" ) - #else - int fxId = GetParticleSystemIndex( SHIELD_WALL_BULLET_FX ) - PlayEffectOnVortexSphere( fxId, DamageInfo_GetDamagePosition( damageInfo ), damageAngles, vortexSphere ) - //VortexSphereDrainHealthForDamage( vortexSphere, DamageInfo_GetWeapon( damageInfo ), null ) - - if ( DamageInfo_GetAttacker( damageInfo ) && DamageInfo_GetAttacker( damageInfo ).IsTitan() ) - EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Heavy.BulletImpact_3P_vs_3P" ) - else - EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Light.BulletImpact_3P_vs_3P" ) - #endif - return true -} - -bool function CodeCallback_OnVortexHitProjectile( entity weapon, entity vortexSphere, entity attacker, entity projectile, vector contactPos ) -{ - // code shouldn't call this on an invalid vortexsphere! - if ( !IsValid( vortexSphere ) ) - return false - - var ignoreVortex = projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_ignores_vortex" ) - if ( ignoreVortex != null ) - { - #if SERVER - if ( projectile.proj.hasBouncedOffVortex ) - return false - - vector velocity = projectile.GetVelocity() - vector multiplier - - switch ( ignoreVortex ) - { - case "drop": - multiplier = < -0.25, -0.25, 0.0 > - break - - case "fall_vortex": - case "fall": - multiplier = < -0.25, -0.25, -0.25 > - break - - case "mirror": - // bounce back, assume along xy axis - multiplier = < -1.0, -1.0, 1.0 > - break - - default: - CodeWarning( "Unknown projectile_ignores_vortex " + ignoreVortex ) - break - } - - velocity = < velocity.x * multiplier.x, velocity.y * multiplier.y, velocity.z * multiplier.z > - projectile.proj.hasBouncedOffVortex = true - projectile.SetVelocity( velocity ) - #endif - return false - } - - bool adjustImpactAngles = !(vortexSphere.GetTargetName() == GUN_SHIELD_WALL) - - vector damageAngles = vortexSphere.GetAngles() - - if ( adjustImpactAngles ) - damageAngles = AnglesCompose( damageAngles, Vector( 90, 0, 0 ) ) - - asset projectileSettingFX = projectile.GetProjectileWeaponSettingAsset( eWeaponVar.vortex_impact_effect ) - asset impactFX = (projectileSettingFX != $"") ? projectileSettingFX : SHIELD_WALL_EXPMED_FX - - bool isAmpedWall = vortexSphere.GetTargetName() == PROTO_AMPED_WALL - bool takesDamage = !isAmpedWall - - #if SERVER - if ( vortexSphere.e.ProjectileHitRules != null ) - takesDamage = vortexSphere.e.ProjectileHitRules( vortexSphere, attacker, takesDamage ) - #endif - // hack to let client know about amped wall, and to amp the shot - if ( isAmpedWall ) - impactFX = AMPED_WALL_IMPACT_FX - - int teamNum = vortexSphere.GetTeam() - - #if CLIENT - if ( !isAmpedWall ) - { - int effectHandle = StartParticleEffectInWorldWithHandle( GetParticleSystemIndex( impactFX ), contactPos, damageAngles ) - //local color = GetShieldTriLerpColor( 1 - GetHealthFrac( vortexSphere ) ) - vector color = GetShieldTriLerpColor( 0.0 ) - EffectSetControlPointVector( effectHandle, 1, color ) - } - - var impact_sound_1p = projectile.ProjectileGetWeaponInfoFileKeyField( "vortex_impact_sound_1p" ) - if ( impact_sound_1p == null ) - impact_sound_1p = "TitanShieldWall.Explosive.BulletImpact_1P_vs_3P" - - EmitSoundAtPosition( teamNum, contactPos, impact_sound_1p ) - #else - if ( !isAmpedWall ) - { - int fxId = GetParticleSystemIndex( impactFX ) - PlayEffectOnVortexSphere( fxId, contactPos, damageAngles, vortexSphere ) - } - - float damage = float( projectile.GetProjectileWeaponSettingInt( eWeaponVar.damage_near_value ) ) - // once damageInfo is passed correctly we'll use that instead of looking up the values from the weapon .txt file. - // local damage = ceil( DamageInfo_GetDamage( damageInfo ) ) - - damage = HandleWeakToPilotWeapons( vortexSphere, projectile.ProjectileGetWeaponClassName(), damage ) - damage = damage + CalculateTitanSniperExtraDamage( projectile, vortexSphere ) - - if ( takesDamage ) - { - VortexSphereDrainHealthForDamage( vortexSphere, damage ) - if ( IsValid( attacker ) && attacker.IsPlayer() ) - attacker.NotifyDidDamage( vortexSphere, 0, contactPos, 0, damage, DF_NO_HITBEEP, 0, null, 0 ) - } - - var impact_sound_3p = projectile.ProjectileGetWeaponInfoFileKeyField( "vortex_impact_sound_3p" ) - - if ( impact_sound_3p == null ) - impact_sound_3p = "TitanShieldWall.Explosive.BulletImpact_3P_vs_3P" - - EmitSoundAtPosition( teamNum, contactPos, impact_sound_3p ) - - int damageSourceID = projectile.ProjectileGetDamageSourceID() - switch ( damageSourceID ) - { - case eDamageSourceId.mp_titanweapon_dumbfire_rockets: - vector normal = projectile.GetVelocity() * -1 - normal = Normalize( normal ) - ClusterRocket_Detonate( projectile, normal ) - CreateNoSpawnArea( TEAM_INVALID, TEAM_INVALID, contactPos, ( CLUSTER_ROCKET_BURST_COUNT / 5.0 ) * 0.5 + 1.0, CLUSTER_ROCKET_BURST_RANGE + 100 ) - break - - case eDamageSourceId.mp_weapon_grenade_electric_smoke: - ElectricGrenadeSmokescreen( projectile, FX_ELECTRIC_SMOKESCREEN_PILOT_AIR ) - break - - case eDamageSourceId.mp_weapon_grenade_emp: - - if ( StatusEffect_Get( vortexSphere, eStatusEffect.destroyed_by_emp ) ) - VortexSphereDrainHealthForDamage( vortexSphere, vortexSphere.GetHealth() ) - break - - case eDamageSourceId.mp_titanability_sonar_pulse: - if ( IsValid( attacker ) && attacker.IsTitan() ) - { - int team = attacker.GetTeam() - PulseLocation( attacker, team, contactPos, false, false ) - array<string> mods = projectile.ProjectileGetMods() - if ( mods.contains( "pas_tone_sonar" ) ) - thread DelayedPulseLocation( attacker, team, contactPos, false, false ) - } - break - - } - #endif - - // hack to let client know about amped wall, and to amp the shot - if ( isAmpedWall ) - { - #if SERVER - projectile.proj.damageScale = AMPED_DAMAGE_SCALAR - #endif - - return false - } - - return true -} - -bool function OnVortexHitProjectile_BubbleShieldNPC( entity vortexSphere, entity attacker, entity projectile, vector contactPos ) -{ - vector vortexOrigin = vortexSphere.GetOrigin() - - float dist = DistanceSqr( vortexOrigin, contactPos ) - if ( dist < MINION_BUBBLE_SHIELD_RADIUS_SQR ) - return false // the damage is coming from INSIDE THE SPHERE - - vector damageVec = Normalize( contactPos - vortexOrigin ) - vector damageAngles = VectorToAngles( damageVec ) - damageAngles = AnglesCompose( damageAngles, Vector( 90, 0, 0 ) ) - - asset projectileSettingFX = projectile.GetProjectileWeaponSettingAsset( eWeaponVar.vortex_impact_effect ) - asset impactFX = (projectileSettingFX != $"") ? projectileSettingFX : SHIELD_WALL_EXPMED_FX - - int teamNum = vortexSphere.GetTeam() - - #if CLIENT - int effectHandle = StartParticleEffectInWorldWithHandle( GetParticleSystemIndex( impactFX ), contactPos, damageAngles ) - - vector color = GetShieldTriLerpColor( 0.9 ) - EffectSetControlPointVector( effectHandle, 1, color ) - - EmitSoundAtPosition( teamNum, contactPos, "TitanShieldWall.Explosive.BulletImpact_1P_vs_3P" ) - #else - int fxId = GetParticleSystemIndex( impactFX ) - PlayEffectOnVortexSphere( fxId, contactPos, damageAngles, vortexSphere ) -// VortexSphereDrainHealthForDamage( vortexSphere, null, projectile ) - - EmitSoundAtPosition( teamNum, contactPos, "TitanShieldWall.Explosive.BulletImpact_3P_vs_3P" ) - - if ( projectile.ProjectileGetDamageSourceID() == eDamageSourceId.mp_titanweapon_dumbfire_rockets ) - { - vector normal = projectile.GetVelocity() * -1 - normal = Normalize( normal ) - ClusterRocket_Detonate( projectile, normal ) - CreateNoSpawnArea( TEAM_INVALID, TEAM_INVALID, contactPos, ( CLUSTER_ROCKET_BURST_COUNT / 5.0 ) * 0.5 + 1.0, CLUSTER_ROCKET_BURST_RANGE + 100 ) - } - #endif - return true -} - -#if SERVER -float function HandleWeakToPilotWeapons( entity vortexSphere, string weaponName, float damage ) -{ - if ( vortexSphere.e.proto_weakToPilotWeapons ) //needs code for real, but this is fine for prototyping - { - // is weapon a pilot weapon? - local refType = GetWeaponInfoFileKeyField_Global( weaponName, "weaponClass" ) - if ( refType == "human" ) - { - damage *= VORTEX_PILOT_WEAPON_WEAKNESS_DAMAGESCALE - } - } - - return damage -} -#endif - -// ???: reflectOrigin not used -int function VortexReflectAttack( entity vortexWeapon, attackParams, vector reflectOrigin ) -{ - entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity() - if ( !vortexSphere ) - return 0 - - #if SERVER - Assert( vortexSphere ) - #endif - - int totalfired = 0 - int totalAttempts = 0 - - bool forceReleased = false - // in this case, it's also considered "force released" if the charge time runs out - if ( vortexWeapon.IsForceRelease() || vortexWeapon.GetWeaponChargeFraction() == 1 ) - forceReleased = true - - //Requires code feature to properly fire tracers from offset positions. - //if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) ) - // attackParams.pos = reflectOrigin - - // PREDICTED REFIRES - // bullet impact events don't individually fire back per event because we aggregate and then shotgun blast them - - //Remove the below script after FireWeaponBulletBroadcast - //local bulletsFired = Vortex_FireBackBullets( vortexWeapon, attackParams ) - //totalfired += bulletsFired - int bulletCount = GetBulletsAbsorbedCount( vortexWeapon ) - if ( bulletCount > 0 ) - { - if ( "ampedBulletCount" in vortexWeapon.s ) - vortexWeapon.s.ampedBulletCount++ - else - vortexWeapon.s.ampedBulletCount <- 1 - vortexWeapon.Signal( "FireAmpedVortexBullet" ) - totalfired += 1 - } - - // UNPREDICTED REFIRES - #if SERVER - //printt( "server: force released?", forceReleased ) - - local unpredictedRefires = Vortex_GetProjectileImpacts( vortexWeapon ) - - // HACK we don't actually want to refire them with a spiral but - // this is to temporarily ensure compatibility with the Titan rocket launcher - if ( !( "spiralMissileIdx" in vortexWeapon.s ) ) - vortexWeapon.s.spiralMissileIdx <- null - vortexWeapon.s.spiralMissileIdx = 0 - foreach ( impactData in unpredictedRefires ) - { - bool didFire = DoVortexAttackForImpactData( vortexWeapon, attackParams, impactData, totalAttempts ) - if ( didFire ) - totalfired++ - totalAttempts++ - } - #endif - - SetVortexAmmo( vortexWeapon, 0 ) - vortexWeapon.Signal( "VortexFired" ) - -#if SERVER - vortexSphere.ClearAllBulletsFromSphere() -#endif - - /* - if ( forceReleased ) - DestroyVortexSphereFromVortexWeapon( vortexWeapon ) - else - DisableVortexSphereFromVortexWeapon( vortexWeapon ) - */ - - return totalfired -}
\ No newline at end of file diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_dialogue.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_dialogue.nut deleted file mode 100644 index 04fd24d3..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_dialogue.nut +++ /dev/null @@ -1,44 +0,0 @@ -untyped - -globalize_all_functions - -function Weapon_Dialogue_Init() -{ - RegisterConversation( "CoopTD_TurretAvailable", VO_PRIORITY_PLAYERSTATE ) // player turret becomes available - RegisterConversation( "CoopTD_TurretAvailableNag", VO_PRIORITY_PLAYERSTATE ) // player turret available nag - RegisterConversation( "CoopTD_TurretDestroyed", VO_PRIORITY_PLAYERSTATE ) // player turret destroyed - RegisterConversation( "CoopTD_TurretDeadAndReady", VO_PRIORITY_PLAYERSTATE ) // player turret destroyed, and one ready - RegisterConversation( "CoopTD_TurretKillstreak", VO_PRIORITY_PLAYERSTATE ) // player turret has lots of kills - RegisterConversation( "CoopTD_TurretKilledTitan", VO_PRIORITY_PLAYERSTATE ) // player turret killed a titan - RegisterConversation( "CoopTD_TurretKilledTitan_Multi", VO_PRIORITY_PLAYERSTATE ) // player turret killed multiple titans - - #if CLIENT - - AddVDULineForSarah( "CoopTD_TurretAvailable", "diag_gm_coop_playerTurretEarned_mcor_Sarah" ) - AddVDULineForSpyglass( "CoopTD_TurretAvailable", "diag_gm_coop_playerTurretEarned_mcor_Sarah" ) - - // player turret available nag - AddVDULineForSarah( "CoopTD_TurretAvailableNag", "diag_gm_coop_playerTurretNag_mcor_Sarah" ) - AddVDULineForSpyglass( "CoopTD_TurretAvailableNag", "diag_gm_coop_playerTurretNag_mcor_Sarah" ) - - // player turret destroyed - AddVDULineForSarah( "CoopTD_TurretDestroyed", "diag_gm_coop_playerTurretDestro_mcor_Sarah" ) - AddVDULineForSpyglass( "CoopTD_TurretDestroyed", "diag_gm_coop_playerTurretDestro_mcor_Sarah" ) - - // player turret destroyed and another one ready - AddVDULineForSarah( "CoopTD_TurretDeadAndReady", "diag_gm_coop_playerTurretDeadAndReady_mcor_Sarah" ) - AddVDULineForSpyglass( "CoopTD_TurretDeadAndReady", "diag_gm_coop_playerTurretDeadAndReady_mcor_Sarah" ) - - // player turret has lots of kills - AddVDULineForSarah( "CoopTD_TurretKillstreak", "diag_gm_coop_playerTurretHighKills_mcor_Sarah" ) - AddVDULineForSpyglass( "CoopTD_TurretKillstreak", "diag_gm_coop_playerTurretHighKills_mcor_Sarah" ) - - // player turret killed a titan - AddVDULineForSarah( "CoopTD_TurretKilledTitan", "diag_gm_coop_playerTurretKilledTitan_mcor_Sarah" ) - AddVDULineForSpyglass( "CoopTD_TurretKilledTitan", "diag_gm_coop_playerTurretKilledTitan_mcor_Sarah" ) - - // player turret killed multiple titans - AddVDULineForSarah( "CoopTD_TurretKilledTitan_Multi", "diag_gm_coop_playerTurretKilledTitanAgain_mcor_Sarah" ) - AddVDULineForSpyglass( "CoopTD_TurretKilledTitan_Multi", "diag_gm_coop_playerTurretKilledTitanAgain_mcor_Sarah" ) - #endif -} diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_utility.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_utility.nut deleted file mode 100644 index b3e5f5a3..00000000 --- a/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_utility.nut +++ /dev/null @@ -1,3966 +0,0 @@ -untyped - -//TODO: Should split this up into server, client and shared versions and just globalize_all_functions -global function WeaponUtility_Init - -global function ApplyVectorSpread -global function DebugDrawMissilePath -global function DegreesToTarget -global function DetonateAllPlantedExplosives -global function EntityCanHaveStickyEnts -global function EntityShouldStick -global function FireExpandContractMissiles -global function FireExpandContractMissiles_S2S -global function GetVectorFromPositionToCrosshair -global function GetVelocityForDestOverTime -global function GetPlayerVelocityForDestOverTime -global function GetWeaponBurnMods -global function InitMissileForRandomDriftForVortexLow -global function IsPilotShotgunWeapon -global function PlantStickyEntity -global function PlantStickyEntityThatBouncesOffWalls -global function PlantStickyEntityOnWorldThatBouncesOffWalls -global function PlantStickyGrenade -global function PlantSuperStickyGrenade -global function Player_DetonateSatchels -global function PROTO_CanPlayerDeployWeapon -global function ProximityCharge_PostFired_Init -global function RegenerateOffhandAmmoOverTime -global function ShotgunBlast -global function FireGenericBoltWithDrop -global function OnWeaponPrimaryAttack_GenericBoltWithDrop_Player -global function OnWeaponPrimaryAttack_GenericMissile_Player -global function OnWeaponActivate_updateViewmodelAmmo -global function TEMP_GetDamageFlagsFromProjectile -global function WeaponCanCrit -global function GiveEMPStunStatusEffects -global function GetPrimaryWeapons -global function GetSidearmWeapons -global function GetATWeapons -global function GetPlayerFromTitanWeapon -global function ChargeBall_Precache -global function ChargeBall_FireProjectile -global function ChargeBall_ChargeBegin -global function ChargeBall_ChargeEnd -global function ChargeBall_StopChargeEffects -global function ChargeBall_GetChargeTime - -global function PlayerUsedOffhand -#if SERVER -global function SetPlayerCooldowns -global function ResetPlayerCooldowns -global function StoreOffhandData -#endif - -global function GetRadiusDamageDataFromProjectile - -#if DEV -global function DevPrintAllStatusEffectsOnEnt -#endif // #if DEV - -#if SERVER - global function ClusterRocket_Detonate - global function PassThroughDamage - global function PROTO_CleanupTrackedProjectiles - global function PROTO_InitTrackedProjectile - global function PROTO_PlayTrapLightEffect - global function Satchel_PostFired_Init - global function StartClusterExplosions - global function TrapDestroyOnRoundEnd - global function TrapExplodeOnDamage - global function PROTO_DelayCooldown - global function PROTO_FlakCannonMissiles - global function GetBulletPassThroughTargets - global function IsValidPassThroughTarget - global function GivePlayerAmpedWeapon - global function GivePlayerAmpedWeaponAndSetAsActive - global function ReplacePlayerOffhand - global function ReplacePlayerOrdnance - global function DisableWeapons - global function EnableWeapons - global function WeaponAttackWave - global function AddActiveThermiteBurn - global function GetActiveThermiteBurnsWithinRadius - global function OnWeaponPrimaryAttack_GenericBoltWithDrop_NPC - global function OnWeaponPrimaryAttack_GenericMissile_NPC - global function EMP_DamagedPlayerOrNPC - global function EMP_FX - global function GetWeaponDPS - global function GetTTK - global function GetWeaponModsFromDamageInfo - global function Thermite_DamagePlayerOrNPCSounds - global function AddThreatScopeColorStatusEffect - global function RemoveThreatScopeColorStatusEffect -#endif //SERVER -#if CLIENT - global function GlobalClientEventHandler - global function UpdateViewmodelAmmo - global function ServerCallback_AirburstIconUpdate - global function ServerCallback_GuidedMissileDestroyed - global function IsOwnerViewPlayerFullyADSed -#endif //CLIENT - -global const PROJECTILE_PREDICTED = true -global const PROJECTILE_NOT_PREDICTED = false - -global const PROJECTILE_LAG_COMPENSATED = true -global const PROJECTILE_NOT_LAG_COMPENSATED = false - -const float EMP_SEVERITY_SLOWTURN = 0.35 -const float EMP_SEVERITY_SLOWMOVE = 0.50 -const float LASER_STUN_SEVERITY_SLOWTURN = 0.20 -const float LASER_STUN_SEVERITY_SLOWMOVE = 0.30 - -const asset FX_EMP_BODY_HUMAN = $"P_emp_body_human" -const asset FX_EMP_BODY_TITAN = $"P_emp_body_titan" -const asset FX_VANGUARD_ENERGY_BODY_HUMAN = $"P_monarchBeam_body_human" -const asset FX_VANGUARD_ENERGY_BODY_TITAN = $"P_monarchBeam_body_titan" -const SOUND_EMP_REBOOT_SPARKS = "marvin_weld" -const FX_EMP_REBOOT_SPARKS = $"weld_spark_01_sparksfly" -const EMP_GRENADE_BEAM_EFFECT = $"wpn_arc_cannon_beam" -const DRONE_REBOOT_TIME = 5.0 -const GUNSHIP_REBOOT_TIME = 5.0 - -global struct RadiusDamageData -{ - int explosionDamage - int explosionDamageHeavyArmor - float explosionRadius - float explosionInnerRadius -} - -#if SERVER - -global struct PopcornInfo -{ - string weaponName - array weaponMods // could be array< string > - int damageSourceId - int count - float delay - float offset - float range - vector normal - float duration - int groupSize - bool hasBase -} - -struct ColorSwapStruct -{ - int statusEffectId - entity weaponOwner -} - -struct -{ - float titanRocketLauncherTitanDamageRadius - float titanRocketLauncherOtherDamageRadius - - int activeThermiteBurnsManagedEnts - array<ColorSwapStruct> colorSwapStatusEffects -} file - -global int HOLO_PILOT_TRAIL_FX - -global struct HoverSounds -{ - string liftoff_1p - string liftoff_3p - string hover_1p - string hover_3p - string descent_1p - string descent_3p - string landing_1p - string landing_3p -} - -#endif - -function WeaponUtility_Init() -{ - level.weaponsPrecached <- {} - - // what classes can sticky thrown entities stick to? - level.stickyClasses <- {} - level.stickyClasses[ "worldspawn" ] <- true - level.stickyClasses[ "player" ] <- true - level.stickyClasses[ "prop_dynamic" ] <- true - level.stickyClasses[ "prop_script" ] <- true - level.stickyClasses[ "func_brush" ] <- true - level.stickyClasses[ "func_brush_lightweight" ] <- true - level.stickyClasses[ "phys_bone_follower" ] <- true - - level.trapChainReactClasses <- {} - level.trapChainReactClasses[ "mp_weapon_frag_grenade" ] <- true - level.trapChainReactClasses[ "mp_weapon_satchel" ] <- true - level.trapChainReactClasses[ "mp_weapon_proximity_mine" ] <- true - level.trapChainReactClasses[ "mp_weapon_laser_mine" ] <- true - - RegisterSignal( "Planted" ) - RegisterSignal( "EMP_FX" ) - RegisterSignal( "ArcStunned" ) - - PrecacheParticleSystem( EMP_GRENADE_BEAM_EFFECT ) - PrecacheParticleSystem( FX_EMP_BODY_TITAN ) - PrecacheParticleSystem( FX_EMP_BODY_HUMAN ) - PrecacheParticleSystem( FX_VANGUARD_ENERGY_BODY_HUMAN ) - PrecacheParticleSystem( FX_VANGUARD_ENERGY_BODY_TITAN ) - PrecacheParticleSystem( FX_EMP_REBOOT_SPARKS ) - - PrecacheImpactEffectTable( CLUSTER_ROCKET_FX_TABLE ) - - #if SERVER - AddDamageCallbackSourceID( eDamageSourceId.mp_titanweapon_triple_threat, TripleThreatGrenade_DamagedPlayerOrNPC ) - AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_defender, Defender_DamagedPlayerOrNPC ) - //AddDamageCallbackSourceID( eDamageSourceId.mp_titanweapon_rocketeer_rocketstream, TitanRocketLauncher_DamagedPlayerOrNPC ) - AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_smr, SMR_DamagedPlayerOrNPC ) - AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_flak_rifle, PROTO_Flak_Rifle_DamagedPlayerOrNPC ) - AddDamageCallbackSourceID( eDamageSourceId.mp_titanweapon_stun_laser, VanguardEnergySiphon_DamagedPlayerOrNPC ) - AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_grenade_emp, EMP_DamagedPlayerOrNPC ) - AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_proximity_mine, EMP_DamagedPlayerOrNPC ) - AddDamageCallbackSourceID( eDamageSourceId[ CHARGE_TOOL ], EMP_DamagedPlayerOrNPC ) - if ( IsMultiplayer() ) - AddCallback_OnPlayerRespawned( PROTO_TrackedProjectile_OnPlayerRespawned ) - AddCallback_OnPlayerKilled( PAS_CooldownReduction_OnKill ) - AddCallback_OnPlayerGetsNewPilotLoadout( OnPlayerGetsNewPilotLoadout ) - AddCallback_OnPlayerKilled( OnPlayerKilled ) - - file.activeThermiteBurnsManagedEnts = CreateScriptManagedEntArray() - - AddCallback_EntitiesDidLoad( EntitiesDidLoad ) - - HOLO_PILOT_TRAIL_FX = PrecacheParticleSystem( $"P_ar_holopilot_trail" ) - - PrecacheParticleSystem( $"wpn_laser_blink" ) - PrecacheParticleSystem( $"wpn_laser_blink_fast" ) - PrecacheParticleSystem( $"P_ordinance_icon_owner" ) - #endif -} - -#if SERVER -void function EntitiesDidLoad() -{ -#if SP - // if we are going to do this, it should happen in the weapon, not globally - //float titanRocketLauncherInnerRadius = expect float( GetWeaponInfoFileKeyField_Global( "mp_titanweapon_rocketeer_rocketstream", "explosion_inner_radius" ) ) - //float titanRocketLauncherOuterRadius = expect float( GetWeaponInfoFileKeyField_Global( "mp_titanweapon_rocketeer_rocketstream", "explosionradius" ) ) - //file.titanRocketLauncherTitanDamageRadius = titanRocketLauncherInnerRadius + ( ( titanRocketLauncherOuterRadius - titanRocketLauncherInnerRadius ) * 0.4 ) - //file.titanRocketLauncherOtherDamageRadius = titanRocketLauncherInnerRadius + ( ( titanRocketLauncherOuterRadius - titanRocketLauncherInnerRadius ) * 0.1 ) -#endif -} -#endif - -//////////////////////////////////////////////////////////////////// - -#if CLIENT -void function GlobalClientEventHandler( entity weapon, string name ) -{ - if ( name == "ammo_update" ) - UpdateViewmodelAmmo( false, weapon ) - - if ( name == "ammo_full" ) - UpdateViewmodelAmmo( true, weapon ) -} - -function UpdateViewmodelAmmo( bool forceFull, entity weapon ) -{ - Assert( weapon != null ) // used to be: if ( weapon == null ) weapon = this.self - - if ( !IsValid( weapon ) ) - return - if ( !IsLocalViewPlayer( weapon.GetWeaponOwner() ) ) - return - - int bodyGroupCount = weapon.GetWeaponSettingInt( eWeaponVar.bodygroup_ammo_index_count ) - if ( bodyGroupCount <= 0 ) - return - - int rounds = weapon.GetWeaponPrimaryClipCount() - int maxRoundsForClipSize = weapon.GetWeaponPrimaryClipCountMax() - int maxRoundsForBodyGroup = (bodyGroupCount - 1) - int maxRounds = minint( maxRoundsForClipSize, maxRoundsForBodyGroup ) - - if ( forceFull || (rounds > maxRounds) ) - rounds = maxRounds - - //printt( "ROUNDS:", rounds, "/", maxRounds ) - weapon.SetViewmodelAmmoModelIndex( rounds ) -} -#endif // #if CLIENT - -void function OnWeaponActivate_updateViewmodelAmmo( entity weapon ) -{ -#if CLIENT - UpdateViewmodelAmmo( false, weapon ) -#endif // #if CLIENT -} - -#if SERVER -//////////////////WEAPON DAMAGE CALLBACKS///////////////////////////////////////// -void function TripleThreatGrenade_DamagedPlayerOrNPC( entity ent, var damageInfo ) -{ - if ( !IsValid( ent ) ) - return - - if ( ent.GetClassName() == "grenade_frag" ) - return - - if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS ) - return - - vector damagePosition = DamageInfo_GetDamagePosition( damageInfo ) - - vector entOrigin = ent.GetOrigin() - vector entCenter = ent.GetWorldSpaceCenter() - float distanceToOrigin = Distance( entOrigin, damagePosition ) - float distanceToCenter = Distance( entCenter, damagePosition ) - - vector normal = Vector( 0, 0, 1 ) - entity inflictor = DamageInfo_GetInflictor( damageInfo ) - if ( IsValid( inflictor.s ) ) - { - if ( "collisionNormal" in inflictor.s ) - normal = expect vector( inflictor.s.collisionNormal ) - } - - local zDifferenceOrigin = deg_cos( DegreesToTarget( entOrigin, normal, damagePosition ) ) * distanceToOrigin - local zDifferenceTop = deg_cos( DegreesToTarget( entCenter, normal, damagePosition ) ) * distanceToCenter - (entCenter.z - entOrigin.z) - - float zDamageDiff - //Full damage if explosion is between Origin or Center. - if ( zDifferenceOrigin > 0 && zDifferenceTop < 0 ) - zDamageDiff = 1.0 - else if ( zDifferenceTop > 0 ) - zDamageDiff = GraphCapped( zDifferenceTop, 0.0, 32.0, 1.0, 0.0 ) - else - zDamageDiff = GraphCapped( zDifferenceOrigin, 0.0, -32.0, 1.0, 0.0 ) - - DamageInfo_ScaleDamage( damageInfo, zDamageDiff ) -} - -void function Defender_DamagedPlayerOrNPC( entity ent, var damageInfo ) -{ - if ( !IsValid( ent ) ) - return - - if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS ) - return - - local damage = Vortex_HandleElectricDamage( ent, DamageInfo_GetAttacker( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetWeapon( damageInfo ) ) - DamageInfo_SetDamage( damageInfo, damage ) -} - -/* -void function TitanRocketLauncher_DamagedPlayerOrNPC( entity ent, var damageInfo ) -{ - Assert( IsSingleplayer() ) - - if ( !IsValid( ent ) ) - return - - if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS ) - return - - vector damagePosition = DamageInfo_GetDamagePosition( damageInfo ) - - if ( ent == DamageInfo_GetAttacker( damageInfo ) ) - return - - if ( ent.IsTitan() ) - { - vector entOrigin = ent.GetOrigin() - if ( Distance( damagePosition, entOrigin ) > file.titanRocketLauncherTitanDamageRadius ) - DamageInfo_SetDamage( damageInfo, 0 ) - } - else if ( IsHumanSized( ent ) ) - { - if ( Distance( damagePosition, ent.GetOrigin() ) > file.titanRocketLauncherOtherDamageRadius ) - DamageInfo_SetDamage( damageInfo, 0 ) - } -} -*/ - -void function SMR_DamagedPlayerOrNPC( entity ent, var damageInfo ) -{ - //Hack - JFS ( The explosion radius is too small on the SMR to deal splash damage to pilots on a Titan. ) - if ( !IsValid( ent ) ) - return - - if ( !ent.IsTitan() ) - return - - if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS ) - return - - entity attacker = DamageInfo_GetAttacker( damageInfo ) - - if ( IsValid( attacker ) && attacker.IsPlayer() && attacker.GetTitanSoulBeingRodeoed() == ent.GetTitanSoul() ) - attacker.TakeDamage( 30, attacker, attacker, { scriptType = DF_GIB | DF_EXPLOSION, damageSourceId = eDamageSourceId.mp_weapon_smr, weapon = DamageInfo_GetWeapon( damageInfo ) } ) -} - - -void function PROTO_Flak_Rifle_DamagedPlayerOrNPC( entity ent, var damageInfo ) -{ - entity attacker = DamageInfo_GetAttacker( damageInfo ) - - if ( !IsValid( ent ) || !IsValid( attacker ) ) - return - - if ( attacker == ent ) - DamageInfo_ScaleDamage( damageInfo, 0.5 ) -} - -function EngineerRocket_DamagedPlayerOrNPC( ent, damageInfo ) -{ - expect entity( ent ) - - entity attacker = DamageInfo_GetAttacker( damageInfo ) - - if ( !IsValid( ent ) || !IsValid( attacker ) ) - return - - if ( attacker == ent ) - DamageInfo_SetDamage( damageInfo, 10 ) -} -/////////////////////////////////////////////////////////////////////// - -#endif // SERVER - -vector function ApplyVectorSpread( vector vecShotDirection, float spreadDegrees, float bias = 1.0 ) -{ - vector angles = VectorToAngles( vecShotDirection ) - vector vecUp = AnglesToUp( angles ) - vector vecRight = AnglesToRight( angles ) - - float sinDeg = deg_sin( spreadDegrees / 2.0 ) - - // get circular gaussian spread - float x - float y - float z - - if ( bias > 1.0 ) - bias = 1.0 - else if ( bias < 0.0 ) - bias = 0.0 - - // code gets these values from cvars ai_shot_bias_min & ai_shot_bias_max - float shotBiasMin = -1.0 - float shotBiasMax = 1.0 - - // 1.0 gaussian, 0.0 is flat, -1.0 is inverse gaussian - float shotBias = ( ( shotBiasMax - shotBiasMin ) * bias ) + shotBiasMin - float flatness = ( fabs(shotBias) * 0.5 ) - - while ( true ) - { - x = RandomFloatRange( -1.0, 1.0 ) * flatness + RandomFloatRange( -1.0, 1.0 ) * (1 - flatness) - y = RandomFloatRange( -1.0, 1.0 ) * flatness + RandomFloatRange( -1.0, 1.0 ) * (1 - flatness) - if ( shotBias < 0 ) - { - x = ( x >= 0 ) ? 1.0 - x : -1.0 - x - y = ( y >= 0 ) ? 1.0 - y : -1.0 - y - } - z = x * x + y * y - - if ( z <= 1 ) - break - } - - vector addX = vecRight * ( x * sinDeg ) - vector addY = vecUp * ( y * sinDeg ) - vector m_vecResult = vecShotDirection + addX + addY - - return m_vecResult -} - - -float function DegreesToTarget( vector origin, vector forward, vector targetPos ) -{ - vector dirToTarget = targetPos - origin - dirToTarget = Normalize( dirToTarget ) - float dot = DotProduct( forward, dirToTarget ) - float degToTarget = (acos( dot ) * 180 / PI) - - return degToTarget -} - -function ShotgunBlast( entity weapon, vector pos, vector dir, int numBlasts, int damageType, float damageScaler = 1.0, float ornull maxAngle = null, float ornull maxDistance = null ) -{ - Assert( numBlasts > 0 ) - int numBlastsOriginal = numBlasts - entity owner = weapon.GetWeaponOwner() - - /* - Debug ConVars: - visible_ent_cone_debug_duration_client - Set to non-zero to see debug output - visible_ent_cone_debug_duration_server - Set to non-zero to see debug output - visible_ent_cone_debug_draw_radius - Size of trace endpoint debug draw - */ - - if ( maxDistance == null ) - maxDistance = weapon.GetMaxDamageFarDist() - expect float( maxDistance ) - - if ( maxAngle == null ) - maxAngle = owner.GetAttackSpreadAngle() * 0.5 - expect float( maxAngle ) - - array<entity> ignoredEntities = [ owner ] - int traceMask = TRACE_MASK_SHOT - int visConeFlags = VIS_CONE_ENTS_TEST_HITBOXES | VIS_CONE_ENTS_CHECK_SOLID_BODY_HIT | VIS_CONE_ENTS_APPOX_CLOSEST_HITBOX | VIS_CONE_RETURN_HIT_VORTEX - - entity antilagPlayer - if ( owner.IsPlayer() ) - { - if ( owner.IsPhaseShifted() ) - return; - - antilagPlayer = owner - } - - //JFS - Bug 198500 - Assert( maxAngle > 0.0, "JFS returning out at this instance. We need to investigate when a valid mp_titanweapon_laser_lite weapon returns 0 spread") - if ( maxAngle == 0.0 ) - return - - array<VisibleEntityInCone> results = FindVisibleEntitiesInCone( pos, dir, maxDistance, (maxAngle * 1.1), ignoredEntities, traceMask, visConeFlags, antilagPlayer, weapon ) - foreach ( result in results ) - { - float angleToHitbox = 0.0 - if ( !result.solidBodyHit ) - angleToHitbox = DegreesToTarget( pos, dir, result.approxClosestHitboxPos ) - - numBlasts -= ShotgunBlastDamageEntity( weapon, pos, dir, result, angleToHitbox, maxAngle, numBlasts, damageType, damageScaler ) - if ( numBlasts <= 0 ) - break - } - - //Something in the TakeDamage above is triggering the weapon owner to become invalid. - owner = weapon.GetWeaponOwner() - if ( !IsValid( owner ) ) - return - - // maxTracer limit set in /r1dev/src/game/client/c_player.h - const int MAX_TRACERS = 16 - bool didHitAnything = ((numBlastsOriginal - numBlasts) != 0) - bool doTraceBrushOnly = (!didHitAnything) - if ( numBlasts > 0 ) - weapon.FireWeaponBullet_Special( pos, dir, minint( numBlasts, MAX_TRACERS ), damageType, false, false, true, false, false, false, doTraceBrushOnly ) -} - - -const SHOTGUN_ANGLE_MIN_FRACTION = 0.1; -const SHOTGUN_ANGLE_MAX_FRACTION = 1.0; -const SHOTGUN_DAMAGE_SCALE_AT_MIN_ANGLE = 0.8; -const SHOTGUN_DAMAGE_SCALE_AT_MAX_ANGLE = 0.1; - -int function ShotgunBlastDamageEntity( entity weapon, vector barrelPos, vector barrelVec, VisibleEntityInCone result, float angle, float maxAngle, int numPellets, int damageType, float damageScaler ) -{ - entity target = result.ent - - //The damage scaler is currently only > 1 for the Titan Shotgun alt fire. - if ( !target.IsTitan() && damageScaler > 1 ) - damageScaler = max( damageScaler * 0.4, 1.5 ) - - entity owner = weapon.GetWeaponOwner() - // Ent in cone not valid - if ( !IsValid( target ) || !IsValid( owner ) ) - return 0 - - // Fire fake bullet towards entity for visual purposes only - vector hitLocation = result.visiblePosition - vector vecToEnt = ( hitLocation - barrelPos ) - vecToEnt.Norm() - if ( Length( vecToEnt ) == 0 ) - vecToEnt = barrelVec - - // This fires a fake bullet that doesn't do any damage. Currently it triggeres a damage callback with 0 damage which is bad. - weapon.FireWeaponBullet_Special( barrelPos, vecToEnt, 1, damageType, true, true, true, false, false, false, false ) // fires perfect bullet with no antilag and no spread - -#if SERVER - // Determine how much damage to do based on distance - float distanceToTarget = Distance( barrelPos, hitLocation ) - - if ( !result.solidBodyHit ) // non solid hits take 1 blast more - distanceToTarget += 130 - - int extraMods = result.extraMods - float damageAmount = CalcWeaponDamage( owner, target, weapon, distanceToTarget, extraMods ) - - // vortex needs to scale damage based on number of rounds absorbed - string className = weapon.GetWeaponClassName() - if ( (className == "mp_titanweapon_vortex_shield") || (className == "mp_titanweapon_vortex_shield_ion") || (className == "mp_titanweapon_heat_shield") ) - { - damageAmount *= numPellets - //printt( "scaling vortex hitscan output damage by", numPellets, "pellets for", weaponNearDamageTitan, "damage vs titans" ) - } - - float coneScaler = 1.0 - //if ( angle > 0 ) - // coneScaler = GraphCapped( angle, (maxAngle * SHOTGUN_ANGLE_MIN_FRACTION), (maxAngle * SHOTGUN_ANGLE_MAX_FRACTION), SHOTGUN_DAMAGE_SCALE_AT_MIN_ANGLE, SHOTGUN_DAMAGE_SCALE_AT_MAX_ANGLE ) - - // Calculate the final damage abount to inflict on the target. Also scale it by damageScaler which may have been passed in by script ( used by alt fire mode on titan shotgun to fire multiple shells ) - float finalDamageAmount = damageAmount * coneScaler * damageScaler - //printt( "angle:", angle, "- coneScaler:", coneScaler, "- damageAmount:", damageAmount, "- damageScaler:", damageScaler, " = finalDamageAmount:", finalDamageAmount ) - - // Calculate impulse force to apply based on damage - int maxImpulseForce = expect int( weapon.GetWeaponInfoFileKeyField( "impulse_force" ) ) - float impulseForce = float( maxImpulseForce ) * coneScaler * damageScaler - vector impulseVec = barrelVec * impulseForce - - int damageSourceID = weapon.GetDamageSourceID() - - // - float critScale = weapon.GetWeaponSettingFloat( eWeaponVar.critical_hit_damage_scale ) - target.TakeDamage( finalDamageAmount, owner, weapon, { origin = hitLocation, force = impulseVec, scriptType = damageType, damageSourceId = damageSourceID, weapon = weapon, hitbox = result.visibleHitbox, criticalHitScale = critScale } ) - - //printt( "-----------" ) - //printt( " distanceToTarget:", distanceToTarget ) - //printt( " damageAmount:", damageAmount ) - //printt( " coneScaler:", coneScaler ) - //printt( " impulseForce:", impulseForce ) - //printt( " impulseVec:", impulseVec.x + ", " + impulseVec.y + ", " + impulseVec.z ) - //printt( " finalDamageAmount:", finalDamageAmount ) - //PrintTable( result ) -#endif // #if SERVER - - return 1 -} - -int function FireGenericBoltWithDrop( entity weapon, WeaponPrimaryAttackParams attackParams, bool isPlayerFired ) -{ -#if CLIENT - if ( !weapon.ShouldPredictProjectiles() ) - return 1 -#endif // #if CLIENT - - weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 ) - - const float PROJ_SPEED_SCALE = 1 - const float PROJ_GRAVITY = 1 - int damageFlags = weapon.GetWeaponDamageFlags() - entity bolt = weapon.FireWeaponBolt( attackParams.pos, attackParams.dir, PROJ_SPEED_SCALE, damageFlags, damageFlags, isPlayerFired, 0 ) - if ( bolt != null ) - { - bolt.kv.gravity = PROJ_GRAVITY - bolt.kv.rendercolor = "0 0 0" - bolt.kv.renderamt = 0 - bolt.kv.fadedist = 1 - } - - return 1 -} -var function OnWeaponPrimaryAttack_GenericBoltWithDrop_Player( entity weapon, WeaponPrimaryAttackParams attackParams ) -{ - return FireGenericBoltWithDrop( weapon, attackParams, true ) -} - -var function OnWeaponPrimaryAttack_EPG( entity weapon, WeaponPrimaryAttackParams attackParams ) -{ - entity missile = weapon.FireWeaponMissile( attackParams.pos, attackParams.dir, 1, damageTypes.largeCaliberExp, damageTypes.largeCaliberExp, false, PROJECTILE_NOT_PREDICTED ) - if ( missile ) - { - EmitSoundOnEntity( missile, "Weapon_Sidwinder_Projectile" ) - missile.InitMissileForRandomDriftFromWeaponSettings( attackParams.pos, attackParams.dir ) - } - - return missile -} - -#if SERVER -var function OnWeaponPrimaryAttack_GenericBoltWithDrop_NPC( entity weapon, WeaponPrimaryAttackParams attackParams ) -{ - return FireGenericBoltWithDrop( weapon, attackParams, false ) -} -#endif // #if SERVER - - -var function OnWeaponPrimaryAttack_GenericMissile_Player( entity weapon, WeaponPrimaryAttackParams attackParams ) -{ - weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 ) - - entity weaponOwner = weapon.GetWeaponOwner() - vector bulletVec = ApplyVectorSpread( attackParams.dir, weaponOwner.GetAttackSpreadAngle() - 1.0 ) - attackParams.dir = bulletVec - - if ( IsServer() || weapon.ShouldPredictProjectiles() ) - { - entity missile = weapon.FireWeaponMissile( attackParams.pos, attackParams.dir, 1.0, weapon.GetWeaponDamageFlags(), weapon.GetWeaponDamageFlags(), false, PROJECTILE_PREDICTED ) - if ( missile ) - { - missile.InitMissileForRandomDriftFromWeaponSettings( attackParams.pos, attackParams.dir ) - } - } -} - -#if SERVER -var function OnWeaponPrimaryAttack_GenericMissile_NPC( entity weapon, WeaponPrimaryAttackParams attackParams ) -{ - weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 ) - - entity missile = weapon.FireWeaponMissile( attackParams.pos, attackParams.dir, 1.0, weapon.GetWeaponDamageFlags(), weapon.GetWeaponDamageFlags(), true, PROJECTILE_NOT_PREDICTED ) - if ( missile ) - { - missile.InitMissileForRandomDriftFromWeaponSettings( attackParams.pos, attackParams.dir ) - } -} -#endif // #if SERVER - -bool function PlantStickyEntityOnWorldThatBouncesOffWalls( entity ent, table collisionParams, float bounceDot ) -{ - entity hitEnt = expect entity( collisionParams.hitEnt ) - if ( hitEnt && ( hitEnt.IsWorld() || hitEnt.HasPusherRootParent() ) ) - { - float dot = expect vector( collisionParams.normal ).Dot( Vector( 0, 0, 1 ) ) - - if ( dot < bounceDot ) - return false - - return PlantStickyEntity( ent, collisionParams ) - } - - return false -} - -bool function PlantStickyEntityThatBouncesOffWalls( entity ent, table collisionParams, float bounceDot ) -{ - if ( expect entity( collisionParams.hitEnt ) == GetEntByIndex( 0 ) ) - { - // Satchel hit the world - float dot = expect vector( collisionParams.normal ).Dot( Vector( 0, 0, 1 ) ) - - if ( dot < bounceDot ) - return false - } - - return PlantStickyEntity( ent, collisionParams ) -} - - -bool function PlantStickyEntity( entity ent, table collisionParams, vector angleOffset = <0.0, 0.0, 0.0> ) -{ - if ( !EntityShouldStick( ent, expect entity( collisionParams.hitEnt ) ) ) - return false - - // Don't allow parenting to another "sticky" entity to prevent them parenting onto each other - if ( collisionParams.hitEnt.IsProjectile() ) - return false - - // Update normal from last bouce so when it explodes it can orient the effect properly - - vector plantAngles = AnglesCompose( VectorToAngles( collisionParams.normal ), angleOffset ) - vector plantPosition = expect vector( collisionParams.pos ) - - if ( !LegalOrigin( plantPosition ) ) - return false - - #if SERVER - ent.SetAbsOrigin( plantPosition ) - ent.SetAbsAngles( plantAngles ) - ent.proj.isPlanted = true - #else - ent.SetOrigin( plantPosition ) - ent.SetAngles( plantAngles ) - #endif - ent.SetVelocity( Vector( 0, 0, 0 ) ) - - //printt( " - Hitbox is:", collisionParams.hitbox, " IsWorld:", collisionParams.hitEnt ) - if ( !collisionParams.hitEnt.IsWorld() ) - { - if ( !ent.IsMarkedForDeletion() && !collisionParams.hitEnt.IsMarkedForDeletion() ) - { - if ( collisionParams.hitbox > 0 ) - ent.SetParentWithHitbox( collisionParams.hitEnt, collisionParams.hitbox, true ) - - // Hit a func_brush - else - ent.SetParent( collisionParams.hitEnt ) - - if ( collisionParams.hitEnt.IsPlayer() ) - { - thread HandleDisappearingParent( ent, expect entity( collisionParams.hitEnt ) ) - } - } - } - else - { - ent.SetVelocity( Vector( 0, 0, 0 ) ) - ent.StopPhysics() - } - #if CLIENT - if ( ent instanceof C_BaseGrenade ) - #else - if ( ent instanceof CBaseGrenade ) - #endif - ent.MarkAsAttached() - - ent.Signal( "Planted" ) - - return true -} - -bool function PlantStickyGrenade( entity ent, vector pos, vector normal, entity hitEnt, int hitbox, float depth = 0.0, bool allowBounce = true, bool allowEntityStick = true ) -{ - if ( ent.GetTeam() == hitEnt.GetTeam() ) - return false - - if ( ent.IsMarkedForDeletion() || hitEnt.IsMarkedForDeletion() ) - return false - - vector plantAngles = VectorToAngles( normal ) - vector plantPosition = pos + normal * -depth - - if ( !allowBounce ) - ent.SetVelocity( Vector( 0, 0, 0 ) ) - - if ( !LegalOrigin( plantPosition ) ) - return false - - #if SERVER - ent.SetAbsOrigin( plantPosition ) - ent.SetAbsAngles( plantAngles ) - ent.proj.isPlanted = true - #else - ent.SetOrigin( plantPosition ) - ent.SetAngles( plantAngles ) - #endif - - if ( !hitEnt.IsWorld() && (!hitEnt.IsTitan() || !allowEntityStick) ) - return false - - // SetOrigin might be causing the ent to get markedForDeletion. - if ( ent.IsMarkedForDeletion() ) - return false - - ent.SetVelocity( Vector( 0, 0, 0 ) ) - - if ( hitEnt.IsWorld() ) - { - ent.SetParent( hitEnt, "", true ) - ent.StopPhysics() - } - else - { - if ( hitbox > 0 ) - ent.SetParentWithHitbox( hitEnt, hitbox, true ) - else // Hit a func_brush - ent.SetParent( hitEnt ) - - if ( hitEnt.IsPlayer() ) - { - thread HandleDisappearingParent( ent, hitEnt ) - } - } - - #if CLIENT - if ( ent instanceof C_BaseGrenade ) - ent.MarkAsAttached() - #else - if ( ent instanceof CBaseGrenade ) - ent.MarkAsAttached() - #endif - - return true -} - - -bool function PlantSuperStickyGrenade( entity ent, vector pos, vector normal, entity hitEnt, int hitbox ) -{ - if ( ent.GetTeam() == hitEnt.GetTeam() ) - return false - - vector plantAngles = VectorToAngles( normal ) - vector plantPosition = pos - - if ( !LegalOrigin( plantPosition ) ) - return false - - #if SERVER - ent.SetAbsOrigin( plantPosition ) - ent.SetAbsAngles( plantAngles ) - ent.proj.isPlanted = true - #else - ent.SetOrigin( plantPosition ) - ent.SetAngles( plantAngles ) - #endif - - if ( !hitEnt.IsWorld() && !hitEnt.IsPlayer() && !hitEnt.IsNPC() ) - return false - - ent.SetVelocity( Vector( 0, 0, 0 ) ) - - if ( hitEnt.IsWorld() ) - { - ent.StopPhysics() - } - else - { - if ( !ent.IsMarkedForDeletion() && !hitEnt.IsMarkedForDeletion() ) - { - if ( hitbox > 0 ) - ent.SetParentWithHitbox( hitEnt, hitbox, true ) - else // Hit a func_brush - ent.SetParent( hitEnt ) - - if ( hitEnt.IsPlayer() ) - { - thread HandleDisappearingParent( ent, hitEnt ) - } - } - } - - #if CLIENT - if ( ent instanceof C_BaseGrenade ) - ent.MarkAsAttached() - #else - if ( ent instanceof CBaseGrenade ) - ent.MarkAsAttached() - #endif - - return true -} - -#if SERVER -void function HandleDisappearingParent( entity ent, entity parentEnt ) -{ - parentEnt.EndSignal( "OnDeath" ) - ent.EndSignal( "OnDestroy" ) - - OnThreadEnd( - function() : ( ent ) - { - ent.ClearParent() - } - ) - - parentEnt.WaitSignal( "StartPhaseShift" ) -} -#else -void function HandleDisappearingParent( entity ent, entity parentEnt ) -{ - parentEnt.EndSignal( "OnDeath" ) - ent.EndSignal( "OnDestroy" ) - - parentEnt.WaitSignal( "StartPhaseShift" ) - - ent.ClearParent() -} -#endif - -bool function EntityShouldStick( entity stickyEnt, entity hitent ) -{ - if ( !EntityCanHaveStickyEnts( stickyEnt, hitent ) ) - return false - - if ( hitent == stickyEnt ) - return false - - return true -} - -bool function EntityCanHaveStickyEnts( entity stickyEnt, entity ent ) -{ - if ( !IsValid( ent ) ) - return false - - if ( ent.GetModelName() == $"" ) // valid case, other projectiles bullets, etc.. sometimes have no model - return false; - - local entClassname - if ( IsServer() ) - entClassname = ent.GetClassName() - else - entClassname = ent.GetSignifierName() // Can return null - - if ( !( entClassname in level.stickyClasses ) && !ent.IsNPC() ) - return false - - #if CLIENT - if ( stickyEnt instanceof C_Projectile ) - #else - if ( stickyEnt instanceof CProjectile ) - #endif - { - string weaponClassName = stickyEnt.ProjectileGetWeaponClassName() - local stickPlayer = GetWeaponInfoFileKeyField_Global( weaponClassName, "stick_pilot" ) - local stickTitan = GetWeaponInfoFileKeyField_Global( weaponClassName, "stick_titan" ) - local stickNPC = GetWeaponInfoFileKeyField_Global( weaponClassName, "stick_npc" ) - - if ( ent.IsTitan() && stickTitan ) - return true - else if ( ent.IsPlayer() && stickPlayer ) - return true - else if ( ent.IsNPC() && stickNPC ) - return true - - // not pilots - if ( ent.IsPlayer() && !ent.IsTitan() ) - return false - } - - return true -} - -#if SERVER -// shared with the vortex script which also needs to create satchels -function Satchel_PostFired_Init( entity satchel, entity player ) -{ - satchel.proj.onlyAllowSmartPistolDamage = false - thread SatchelThink( satchel, player ) -} - -function SatchelThink( entity satchel, entity player ) -{ - player.EndSignal("OnDestroy") - satchel.EndSignal("OnDestroy") - - int satchelHealth = 15 - thread TrapExplodeOnDamage( satchel, satchelHealth ) - - #if DEV - // temp HACK for FX to use to figure out the size of the particle to play - if ( Flag( "ShowExplosionRadius" ) ) - thread ShowExplosionRadiusOnExplode( satchel ) - #endif - - player.EndSignal( "OnDeath" ) - - OnThreadEnd( - function() : ( satchel ) - { - if ( IsValid( satchel ) ) - { - satchel.Destroy() - } - } - ) - - WaitForever() -} - -#endif // SERVER - -function ProximityCharge_PostFired_Init( entity proximityMine, entity player ) -{ - #if SERVER - proximityMine.proj.onlyAllowSmartPistolDamage = false - #endif -} - -function DetonateAllPlantedExplosives( entity player ) -{ - // ToDo: Could use Player_DetonateSatchels but it only tracks satchels, not laser mines. - - // Detonate all explosives - satchels and laser mines are also frag grenades in disguise - array<entity> grenades = GetProjectileArrayEx( "grenade_frag", TEAM_ANY, TEAM_ANY, Vector( 0, 0, 0 ), -1 ) - foreach( grenade in grenades ) - { - if ( grenade.GetOwner() != player ) - continue - - if ( grenade.ProjectileGetDamageSourceID() != eDamageSourceId.mp_weapon_satchel && grenade.ProjectileGetDamageSourceID() != eDamageSourceId.mp_weapon_proximity_mine ) - continue - - thread ExplodePlantedGrenadeAfterDelay( grenade, RandomFloatRange( 0.75, 0.95 ) ) - } -} - -function ExplodePlantedGrenadeAfterDelay( entity grenade, float delay ) -{ - grenade.EndSignal( "OnDeath" ) - grenade.EndSignal( "OnDestroy" ) - - float endTime = Time() + delay - - while ( Time() < endTime ) - { - EmitSoundOnEntity( grenade, DEFAULT_WARNING_SFX ) - wait 0.1 - } - - grenade.GrenadeExplode( grenade.GetForwardVector() ) -} - -function Player_DetonateSatchels( entity player ) -{ - #if SERVER - Assert( IsServer() ) - - array<entity> traps = GetScriptManagedEntArray( player.s.activeTrapArrayId ) - traps.sort( CompareCreationReverse ) - foreach ( index, satchel in traps ) - { - if ( IsValidSatchel( satchel ) ) - { - - thread PROTO_ExplodeAfterDelay( satchel, index * 0.25 ) - } - } - #endif -} - -function IsValidSatchel( entity satchel ) -{ - #if SERVER - if ( satchel.ProjectileGetWeaponClassName() != "mp_weapon_satchel" ) - return false - - if ( satchel.e.isDisabled == true ) - return false - - return true - #endif -} - -#if SERVER -function PROTO_ExplodeAfterDelay( entity satchel, float delay ) -{ - satchel.EndSignal( "OnDestroy" ) - - #if MP - while ( !satchel.proj.isPlanted ) - { - WaitFrame() - } - #endif - - wait delay - - satchel.GrenadeExplode( satchel.GetForwardVector() ) -} -#endif - - -#if DEV -function ShowExplosionRadiusOnExplode( entity ent ) -{ - ent.WaitSignal( "OnDestroy" ) - - float innerRadius = expect float( ent.GetWeaponInfoFileKeyField( "explosion_inner_radius" ) ) - float outerRadius = expect float( ent.GetWeaponInfoFileKeyField( "explosionradius" ) ) - - vector org = ent.GetOrigin() - vector angles = Vector( 0, 0, 0 ) - thread DebugDrawCircle( org, angles, innerRadius, 255, 255, 51, true, 3.0 ) - thread DebugDrawCircle( org, angles, outerRadius, 255, 255, 255, true, 3.0 ) -} -#endif // DEV - -#if SERVER -// shared between nades, satchels and laser mines -void function TrapExplodeOnDamage( entity trapEnt, int trapEntHealth = 50, float waitMin = 0.0, float waitMax = 0.0 ) -{ - Assert( IsValid( trapEnt ), "Given trapEnt entity is not valid, fired from: " + trapEnt.ProjectileGetWeaponClassName() ) - EndSignal( trapEnt, "OnDestroy" ) - - trapEnt.SetDamageNotifications( true ) - var results //Really should be a struct - entity attacker - entity inflictor - - while ( true ) - { - if ( !IsValid( trapEnt ) ) - return - - results = WaitSignal( trapEnt, "OnDamaged" ) - attacker = expect entity( results.activator ) - inflictor = expect entity( results.inflictor ) - - if ( IsValid( inflictor ) && inflictor == trapEnt ) - continue - - bool shouldDamageTrap = false - if ( IsValid( attacker ) ) - { - if ( trapEnt.proj.onlyAllowSmartPistolDamage ) - { - if ( attacker.IsNPC() || attacker.IsPlayer() ) - { - entity attackerWeapon = attacker.GetActiveWeapon() - if ( IsValid( attackerWeapon ) && WeaponIsSmartPistolVariant( attackerWeapon ) ) - shouldDamageTrap = true - } - } - else - { - if ( trapEnt.GetTeam() == attacker.GetTeam() ) - { - if ( trapEnt.GetOwner() != attacker ) - shouldDamageTrap = false - else - shouldDamageTrap = !ProjectileIgnoresOwnerDamage( trapEnt ) - } - else - { - shouldDamageTrap = true - } - } - } - - if ( shouldDamageTrap ) - trapEntHealth -= int ( results.value ) //TODO: This returns float even though it feels like it should return int - - if ( trapEntHealth <= 0 ) - break - } - - if ( !IsValid( trapEnt ) ) - return - - inflictor = expect entity( results.inflictor ) // waiting on code feature to pass inflictor with OnDamaged signal results table - - if ( waitMin >= 0 && waitMax > 0 ) - { - float waitTime = RandomFloatRange( waitMin, waitMax ) - - if ( waitTime > 0 ) - wait waitTime - } - else if ( IsValid( inflictor ) && (inflictor.IsProjectile() || (inflictor instanceof CWeaponX)) ) - { - int dmgSourceID - if ( inflictor.IsProjectile() ) - dmgSourceID = inflictor.ProjectileGetDamageSourceID() - else - dmgSourceID = inflictor.GetDamageSourceID() - - string inflictorClass = GetObitFromDamageSourceID( dmgSourceID ) - - if ( inflictorClass in level.trapChainReactClasses ) - { - // chain reaction delay - Wait( RandomFloatRange( 0.2, 0.275 ) ) - } - } - - if ( !IsValid( trapEnt ) ) - return - - if ( IsValid( attacker ) ) - { - if ( attacker.IsPlayer() ) - { - AddPlayerScoreForTrapDestruction( attacker, trapEnt ) - trapEnt.SetOwner( attacker ) - } - else - { - entity lastAttacker = GetLastAttacker( attacker ) - if ( IsValid( lastAttacker ) ) - { - // for chain explosions, figure out the attacking player that started the chain - trapEnt.SetOwner( lastAttacker ) - } - } - } - - trapEnt.GrenadeExplode( trapEnt.GetForwardVector() ) -} - -bool function ProjectileIgnoresOwnerDamage( entity projectile ) -{ - var ignoreOwnerDamage = projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_ignore_owner_damage" ) - - if ( ignoreOwnerDamage == null ) - return false - - return ignoreOwnerDamage == 1 -} - -bool function WeaponIsSmartPistolVariant( entity weapon ) -{ - var isSP = weapon.GetWeaponInfoFileKeyField( "is_smart_pistol" ) - - //printt( isSP ) - - if ( isSP == null ) - return false - - return ( isSP == 1 ) -} - -// NOTE: we should stop using this -function TrapDestroyOnRoundEnd( entity player, entity trapEnt ) -{ - trapEnt.EndSignal( "OnDestroy" ) - - svGlobal.levelEnt.WaitSignal( "ClearedPlayers" ) - - if ( IsValid( trapEnt ) ) - trapEnt.Destroy() -} - -function AddPlayerScoreForTrapDestruction( entity player, entity trapEnt ) -{ - // don't get score for killing your own trap - if ( "originalOwner" in trapEnt.s && trapEnt.s.originalOwner == player ) - return - - string trapClass = trapEnt.ProjectileGetWeaponClassName() - if ( trapClass == "" ) - return - - string scoreEvent - if ( trapClass == "mp_weapon_satchel" ) - scoreEvent = "Destroyed_Satchel" - else if ( trapClass == "mp_weapon_proximity_mine" ) - scoreEvent = "Destored_Proximity_Mine" - - if ( scoreEvent == "" ) - return - - AddPlayerScore( player, scoreEvent, trapEnt ) -} - -table function GetBulletPassThroughTargets( entity attacker, WeaponBulletHitParams hitParams ) -{ - //HACK requires code later - table passThroughInfo = { - endPos = null - targetArray = [] - } - - TraceResults result - array<entity> ignoreEnts = [ attacker, hitParams.hitEnt ] - - while ( true ) - { - vector vec = ( hitParams.hitPos - hitParams.startPos ) * 1000 - ArrayRemoveInvalid( ignoreEnts ) - result = TraceLine( hitParams.startPos, vec, ignoreEnts, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - - if ( result.hitEnt == svGlobal.worldspawn ) - break - - ignoreEnts.append( result.hitEnt ) - - if ( IsValidPassThroughTarget( result.hitEnt, attacker ) ) - passThroughInfo.targetArray.append( result.hitEnt ) - } - passThroughInfo.endPos = result.endPos - - return passThroughInfo -} -#endif // SERVER - -bool function WeaponCanCrit( entity weapon ) -{ - // player sometimes has no weapon during titan exit, mantle, etc... - if ( !weapon ) - return false - - return weapon.GetWeaponSettingBool( eWeaponVar.critical_hit ) -} - - -#if SERVER -bool function IsValidPassThroughTarget( entity target, entity attacker ) -{ - //Tied to PassThroughHack function remove when supported by code. - if ( target == svGlobal.worldspawn ) - return false - - if ( !IsValid( target ) ) - return false - - if ( target.GetTeam() == attacker.GetTeam() ) - return false - - if ( target.GetTeam() != TEAM_IMC && target.GetTeam() != TEAM_MILITIA ) - return false - - return true -} - -function PassThroughDamage( entity weapon, targetArray ) -{ - //Tied to PassThroughHack function remove when supported by code. - - int damageSourceID = weapon.GetDamageSourceID() - entity owner = weapon.GetWeaponOwner() - - foreach ( ent in targetArray ) - { - expect entity( ent ) - - float distanceToTarget = Distance( weapon.GetOrigin(), ent.GetOrigin() ) - float damageToDeal = CalcWeaponDamage( owner, ent, weapon, distanceToTarget, 0 ) - - ent.TakeDamage( damageToDeal, owner, weapon.GetWeaponOwner(), { damageSourceId = damageSourceID } ) - } -} -#endif // SERVER - -vector function GetVectorFromPositionToCrosshair( entity player, vector startPos ) -{ - Assert( IsValid( player ) ) - - // See where we're looking - vector traceStart = player.EyePosition() - vector traceEnd = traceStart + ( player.GetViewVector() * 20000 ) - local ignoreEnts = [ player ] - TraceResults traceResult = TraceLine( traceStart, traceEnd, ignoreEnts, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - - // Return vec from startPos to where we are looking - vector vec = traceResult.endPos - startPos - vec = Normalize( vec ) - return vec -} - -/* -function InitMissileForRandomDriftBasic( missile, startPos, startDir ) -{ - missile.s.RandomFloatRange <- RandomFloat( 1.0 ) - missile.s.startPos <- startPos - missile.s.startDir <- startDir -} -*/ - -function InitMissileForRandomDriftForVortexHigh( entity missile, vector startPos, vector startDir ) -{ - missile.InitMissileForRandomDrift( startPos, startDir, 8, 2.5, 0, 0, 100, 100 ) -} - -function InitMissileForRandomDriftForVortexLow( entity missile, vector startPos, vector startDir ) -{ - missile.InitMissileForRandomDrift( startPos, startDir, 0.3, 0.085, 0, 0, 0.5, 0.5 ) -} - -/* -function InitMissileForRandomDrift( missile, startPos, startDir ) -{ - InitMissileForRandomDriftBasic( missile, startPos, startDir ) - - missile.s.drift_windiness <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_drift_windiness" ) - missile.s.drift_intensity <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_drift_intensity" ) - - missile.s.straight_time_min <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_straight_time_min" ) - missile.s.straight_time_max <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_straight_time_max" ) - - missile.s.straight_radius_min <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_straight_radius_min" ) - if ( missile.s.straight_radius_min < 1 ) - missile.s.straight_radius_min = 1 - missile.s.straight_radius_max <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_straight_radius_max" ) - if ( missile.s.straight_radius_max < 1 ) - missile.s.straight_radius_max = 1 -} - -function SmoothRandom( x ) -{ - return 0.25 * (sin(x) + sin(x * 0.762) + sin(x * 0.363) + sin(x * 0.084)) -} - -function MissileRandomDrift( timeElapsed, timeStep, windiness, intensity ) -{ - // This function makes the missile go in a random direction. - // Windiness is how frequently the missile changes direction. - // Intensity is how strongly the missile steers in the direction it has chosen. - - local sampleTime = timeElapsed - timeStep * 0.5 - - intensity *= timeStep - - local offset = self.s.RandomFloatRange * 1000 - - local offsetx = intensity * SmoothRandom( offset + sampleTime * windiness ) - local offsety = intensity * SmoothRandom( offset * 2 + 100 + sampleTime * windiness ) - - local right = self.GetRightVector() - local up = self.GetUpVector() - - //DebugDrawLine( self.GetOrigin(), self.GetOrigin() + right * 100, 255,255,255, true, 0 ) - //DebugDrawLine( self.GetOrigin(), self.GetOrigin() + up * 100, 255,128,255, true, 0 ) - - local dir = self.GetVelocity() - local speed = Length( dir ) - dir = Normalize( dir ) - dir += right * offsetx - dir += up * offsety - dir = Normalize( dir ) - dir *= speed - - return dir -} - -// designed to be called every frame (GetProjectileVelocity callback) on projectiles that are flying through the air -function ApplyMissileControlledDrift( missile, timeElapsed, timeStep ) -{ - // If we have a target, don't do anything fancy; just let code do the homing behavior - if ( missile.GetMissileTarget() ) - return missile.GetVelocity() - - local s = missile.s - return MissileControlledDrift( timeElapsed, timeStep, s.drift_windiness, s.drift_intensity, s.straight_time_min, s.straight_time_max, s.straight_radius_min, s.straight_radius_max ) -} - -function MissileControlledDrift( timeElapsed, timeStep, windiness, intensity, pathTimeMin, pathTimeMax, pathRadiusMin, pathRadiusMax ) -{ - // Start with random drift. - local vel = MissileRandomDrift( timeElapsed, timeStep, windiness, intensity ) - - // Straighten our velocity back along our original path if we're below pathTimeMax. - // Path time is how long it tries to stay on a straight path. - // Path radius is how far it can get from its straight path. - if ( timeElapsed < pathTimeMax ) - { - local org = self.GetOrigin() - local alongPathLen = self.s.startDir.Dot( org - self.s.startPos ) - local alongPathPos = self.s.startPos + self.s.startDir * alongPathLen - local offPathOffset = org - alongPathPos - local pathDist = Length( offPathOffset ) - - local speed = Length( vel ) - - local lerp = 1 - if ( timeElapsed > pathTimeMin ) - lerp = 1.0 - (timeElapsed - pathTimeMin) / (pathTimeMax - pathTimeMin) - - local pathRadius = pathRadiusMax + (pathRadiusMin - pathRadiusMax) * lerp - - // This circle shows the radius the missile is allowed to be in. - //if ( IsServer() ) - // DebugDrawCircle( alongPathPos, VectorToAngles( AnglesToUp( VectorToAngles( self.s.startDir ) ) ), pathRadius, 255,255,255, true, 0.0 ) - - local backToPathVel = offPathOffset * -1 - // Cap backToPathVel at speed - if ( pathDist > pathRadius ) - backToPathVel *= speed / pathDist - else - backToPathVel *= speed / pathRadius - - if ( pathDist < pathRadius ) - { - backToPathVel += self.s.startDir * (speed * (1.0 - pathDist / pathRadius)) - } - - //DebugDrawLine( org, org + vel * 0.1, 255,255,255, true, 0 ) - //DebugDrawLine( org, org + backToPathVel * intensity * lerp * 0.1, 128,255,128, true, 0 ) - - vel += backToPathVel * (intensity * timeStep) - vel = Normalize( vel ) - vel *= speed - } - - return vel -} -*/ - -#if SERVER -function ClusterRocket_Detonate( entity rocket, vector normal ) -{ - entity owner = rocket.GetOwner() - if ( !IsValid( owner ) ) - return - - int count - float duration - float range - - array mods = rocket.ProjectileGetMods() - if ( mods.contains( "pas_northstar_cluster" ) ) - { - count = CLUSTER_ROCKET_BURST_COUNT_BURN - duration = PAS_NORTHSTAR_CLUSTER_ROCKET_DURATION - range = CLUSTER_ROCKET_BURST_RANGE * 1.5 - } - else - { - count = CLUSTER_ROCKET_BURST_COUNT - duration = CLUSTER_ROCKET_DURATION - range = CLUSTER_ROCKET_BURST_RANGE - } - - if ( mods.contains( "fd_twin_cluster" ) ) - { - count = int( count * 0.7 ) - duration *= 0.7 - } - PopcornInfo popcornInfo - - popcornInfo.weaponName = "mp_titanweapon_dumbfire_rockets" - popcornInfo.weaponMods = mods - popcornInfo.damageSourceId = eDamageSourceId.mp_titanweapon_dumbfire_rockets - popcornInfo.count = count - popcornInfo.delay = CLUSTER_ROCKET_BURST_DELAY - popcornInfo.offset = CLUSTER_ROCKET_BURST_OFFSET - popcornInfo.range = range - popcornInfo.normal = normal - popcornInfo.duration = duration - popcornInfo.groupSize = CLUSTER_ROCKET_BURST_GROUP_SIZE - popcornInfo.hasBase = true - - thread StartClusterExplosions( rocket, owner, popcornInfo, CLUSTER_ROCKET_FX_TABLE ) -} - - -function StartClusterExplosions( entity projectile, entity owner, PopcornInfo popcornInfo, customFxTable = null ) -{ - Assert( IsValid( owner ) ) - owner.EndSignal( "OnDestroy" ) - - string weaponName = popcornInfo.weaponName - float innerRadius - float outerRadius - int explosionDamage - int explosionDamageHeavyArmor - - innerRadius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosion_inner_radius ) - outerRadius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosionradius ) - if ( owner.IsPlayer() ) - { - explosionDamage = projectile.GetProjectileWeaponSettingInt( eWeaponVar.explosion_damage ) - explosionDamageHeavyArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.explosion_damage_heavy_armor ) - } - else - { - explosionDamage = projectile.GetProjectileWeaponSettingInt( eWeaponVar.npc_explosion_damage ) - explosionDamageHeavyArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.npc_explosion_damage_heavy_armor ) - } - - local explosionDelay = projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_explosion_delay" ) - - if ( owner.IsPlayer() ) - owner.EndSignal( "OnDestroy" ) - - vector origin = projectile.GetOrigin() - - vector rotateFX = Vector( 90,0,0 ) - entity placementHelper = CreateScriptMover() - placementHelper.SetOrigin( origin ) - placementHelper.SetAngles( VectorToAngles( popcornInfo.normal ) ) - SetTeam( placementHelper, owner.GetTeam() ) - - array<entity> players = GetPlayerArray() - foreach ( player in players ) - { - Remote_CallFunction_NonReplay( player, "SCB_AddGrenadeIndicatorForEntity", owner.GetTeam(), owner.GetEncodedEHandle(), placementHelper.GetEncodedEHandle(), outerRadius ) - } - - int particleSystemIndex = GetParticleSystemIndex( CLUSTER_BASE_FX ) - int attachId = placementHelper.LookupAttachment( "REF" ) - entity fx - - if ( popcornInfo.hasBase ) - { - fx = StartParticleEffectOnEntity_ReturnEntity( placementHelper, particleSystemIndex, FX_PATTACH_POINT_FOLLOW, attachId ) - EmitSoundOnEntity( placementHelper, "Explo_ThermiteGrenade_Impact_3P" ) // TODO: wants a custom sound - } - - OnThreadEnd( - function() : ( fx, placementHelper ) - { - if ( IsValid( fx ) ) - EffectStop( fx ) - placementHelper.Destroy() - } - ) - - if ( explosionDelay ) - wait explosionDelay - - waitthread ClusterRocketBursts( origin, explosionDamage, explosionDamageHeavyArmor, innerRadius, outerRadius, owner, popcornInfo, customFxTable ) - - if ( IsValid( projectile ) ) - projectile.Destroy() -} - - -//------------------------------------------------------------ -// ClusterRocketBurst() - does a "popcorn airburst" explosion effect over time around the origin. Total distance is based on popRangeBase -// - returns the entity in case you want to parent it -//------------------------------------------------------------ -function ClusterRocketBursts( vector origin, int damage, int damageHeavyArmor, float innerRadius, float outerRadius, entity owner, PopcornInfo popcornInfo, customFxTable = null ) -{ - owner.EndSignal( "OnDestroy" ) - - // this ent remembers the weapon mods - entity clusterExplosionEnt = CreateEntity( "info_target" ) - DispatchSpawn( clusterExplosionEnt ) - - if ( popcornInfo.weaponMods.len() > 0 ) - clusterExplosionEnt.s.weaponMods <- popcornInfo.weaponMods - - clusterExplosionEnt.SetOwner( owner ) - clusterExplosionEnt.SetOrigin( origin ) - - AI_CreateDangerousArea_Static( clusterExplosionEnt, null, outerRadius, TEAM_INVALID, true, true, origin ) - - OnThreadEnd( - function() : ( clusterExplosionEnt ) - { - clusterExplosionEnt.Destroy() - } - ) - - // No Damage - Only Force - // Push players - // Test LOS before pushing - int flags = 11 - // create a blast that knocks pilots out of the way - CreatePhysExplosion( origin, outerRadius, PHYS_EXPLOSION_LARGE, flags ) - - int count = popcornInfo.groupSize - for ( int index = 0; index < count; index++ ) - { - thread ClusterRocketBurst( clusterExplosionEnt, origin, damage, damageHeavyArmor, innerRadius, outerRadius, owner, popcornInfo, customFxTable ) - WaitFrame() - } - - wait CLUSTER_ROCKET_DURATION -} - -function ClusterRocketBurst( entity clusterExplosionEnt, vector origin, damage, damageHeavyArmor, innerRadius, outerRadius, entity owner, PopcornInfo popcornInfo, customFxTable = null ) -{ - clusterExplosionEnt.EndSignal( "OnDestroy" ) - Assert( IsValid( owner ), "ClusterRocketBurst had invalid owner" ) - - // first explosion always happens where you fired - //int eDamageSource = popcornInfo.damageSourceId - int numBursts = popcornInfo.count - float popRangeBase = popcornInfo.range - float popDelayBase = popcornInfo.delay - float popDelayRandRange = popcornInfo.offset - float duration = popcornInfo.duration - int groupSize = popcornInfo.groupSize - - int counter = 0 - vector randVec - float randRangeMod - float popRange - vector popVec - vector popOri = origin - float popDelay - float colTrace - - float burstDelay = duration / ( numBursts / groupSize ) - - vector clusterBurstOrigin = origin + (popcornInfo.normal * 8.0) - entity clusterBurstEnt = CreateClusterBurst( clusterBurstOrigin ) - - OnThreadEnd( - function() : ( clusterBurstEnt ) - { - if ( IsValid( clusterBurstEnt ) ) - { - foreach ( fx in clusterBurstEnt.e.fxArray ) - { - if ( IsValid( fx ) ) - fx.Destroy() - } - clusterBurstEnt.Destroy() - } - } - ) - - while ( IsValid( clusterBurstEnt ) && counter <= numBursts / popcornInfo.groupSize ) - { - randVec = RandomVecInDome( popcornInfo.normal ) - randRangeMod = RandomFloat( 1.0 ) - popRange = popRangeBase * randRangeMod - popVec = randVec * popRange - popOri = origin + popVec - popDelay = popDelayBase + RandomFloatRange( -popDelayRandRange, popDelayRandRange ) - - colTrace = TraceLineSimple( origin, popOri, null ) - if ( colTrace < 1 ) - { - popVec = popVec * colTrace - popOri = origin + popVec - } - - clusterBurstEnt.SetOrigin( clusterBurstOrigin ) - - vector velocity = GetVelocityForDestOverTime( clusterBurstEnt.GetOrigin(), popOri, burstDelay - popDelay ) - clusterBurstEnt.SetVelocity( velocity ) - - clusterBurstOrigin = popOri - - counter++ - - wait burstDelay - popDelay - - Explosion( - clusterBurstOrigin, - owner, - clusterExplosionEnt, - damage, - damageHeavyArmor, - innerRadius, - outerRadius, - SF_ENVEXPLOSION_NOSOUND_FOR_ALLIES, - clusterBurstOrigin, - damage, - damageTypes.explosive, - popcornInfo.damageSourceId, - customFxTable ) - } -} - - -entity function CreateClusterBurst( vector origin ) -{ - entity prop_physics = CreateEntity( "prop_physics" ) - prop_physics.SetValueForModelKey( $"models/weapons/bullets/projectile_rocket.mdl" ) - prop_physics.kv.spawnflags = 4 // 4 = SF_PHYSPROP_DEBRIS - prop_physics.kv.fadedist = 2000 - prop_physics.kv.renderamt = 255 - prop_physics.kv.rendercolor = "255 255 255" - prop_physics.kv.CollisionGroup = TRACE_COLLISION_GROUP_DEBRIS - - prop_physics.kv.minhealthdmg = 9999 - prop_physics.kv.nodamageforces = 1 - prop_physics.kv.inertiaScale = 1.0 - - prop_physics.SetOrigin( origin ) - DispatchSpawn( prop_physics ) - prop_physics.SetModel( $"models/weapons/grenades/m20_f_grenade.mdl" ) - - entity fx = PlayFXOnEntity( $"P_wpn_dumbfire_burst_trail", prop_physics ) - prop_physics.e.fxArray.append( fx ) - - return prop_physics -} -#endif // SERVER - -vector function GetVelocityForDestOverTime( vector startPoint, vector endPoint, float duration ) -{ - const GRAVITY = 750 - - float Vox = (endPoint.x - startPoint.x) / duration - float Voy = (endPoint.y - startPoint.y) / duration - float Voz = (endPoint.z + 0.5 * GRAVITY * duration * duration - startPoint.z) / duration - - return Vector( Vox, Voy, Voz ) -} - -vector function GetPlayerVelocityForDestOverTime( vector startPoint, vector endPoint, float duration ) -{ - // Same as above but accounts for player gravity setting not being 1.0 - - float gravityScale = expect float( GetPlayerSettingsFieldForClassName( DEFAULT_PILOT_SETTINGS, "gravityscale" ) ) - float GRAVITY = 750 * gravityScale // adjusted for new gravity scale - - float Vox = (endPoint.x - startPoint.x) / duration - float Voy = (endPoint.y - startPoint.y) / duration - float Voz = (endPoint.z + 0.5 * GRAVITY * duration * duration - startPoint.z) / duration - - return Vector( Vox, Voy, Voz ) -} - -bool function HasLockedTarget( weapon ) -{ - if ( weapon.SmartAmmo_IsEnabled() ) - { - local targets = weapon.SmartAmmo_GetTargets() - if ( targets.len() > 0 ) - { - foreach ( target in targets ) - { - if ( target.fraction == 1 ) - return true - } - } - } - return false -} - -function CanWeaponShootWhileRunning( entity weapon ) -{ - if ( "primary_fire_does_not_block_sprint" in weapon.s ) - return weapon.s.primary_fire_does_not_block_sprint - - if ( weapon.GetWeaponInfoFileKeyField( "primary_fire_does_not_block_sprint" ) == 1 ) - { - weapon.s.primary_fire_does_not_block_sprint <- true - return true - } - - weapon.s.primary_fire_does_not_block_sprint <- false - return false -} - -#if CLIENT -function ServerCallback_GuidedMissileDestroyed() -{ - entity player = GetLocalViewPlayer() - - // guided missiles has not been updated to work with replays. added this if statement defensively just in case. - Roger - if ( !( "missileInFlight" in player.s ) ) - return - - player.s.missileInFlight = false -} - -function ServerCallback_AirburstIconUpdate( toggle ) -{ - entity player = GetLocalViewPlayer() - entity cockpit = player.GetCockpit() - if ( cockpit ) - { - entity mainVGUI = cockpit.e.mainVGUI - if ( mainVGUI ) - { - if ( toggle ) - cockpit.s.offhandHud[OFFHAND_RIGHT].icon.SetImage( $"vgui/HUD/dpad_airburst_activate" ) - else - cockpit.s.offhandHud[OFFHAND_RIGHT].icon.SetImage( $"vgui/HUD/dpad_airburst" ) - } - } -} - -bool function IsOwnerViewPlayerFullyADSed( entity weapon ) -{ - entity owner = weapon.GetOwner() - if ( !IsValid( owner ) ) - return false - - if( !owner.IsPlayer() ) - return false - - if ( owner != GetLocalViewPlayer() ) - return false - - float zoomFrac = owner.GetZoomFrac() - if ( zoomFrac < 1.0 ) - return false - - return true - -} -#endif // CLIENT - -array<entity> function FireExpandContractMissiles( entity weapon, WeaponPrimaryAttackParams attackParams, vector attackPos, vector attackDir, int damageType, int explosionDamageType, shouldPredict, int rocketsPerShot, missileSpeed, launchOutAng, launchOutTime, launchInAng, launchInTime, launchInLerpTime, launchStraightLerpTime, applyRandSpread, int burstFireCountOverride = -1, debugDrawPath = false ) -{ - local missileVecs = GetExpandContractRocketTrajectories( weapon, attackParams.burstIndex, attackPos, attackDir, rocketsPerShot, launchOutAng, launchInAng, burstFireCountOverride ) - entity owner = weapon.GetWeaponOwner() - array<entity> firedMissiles - - vector missileEndPos = owner.EyePosition() + ( attackDir * 5000 ) - - for ( int i = 0; i < rocketsPerShot; i++ ) - { - entity missile = weapon.FireWeaponMissile( attackPos, attackDir, missileSpeed, damageType, explosionDamageType, false, shouldPredict ) - - if ( missile ) - { - /* - missile.s.flightData <- { - launchOutVec = missileVecs[i].outward, - launchOutTime = launchOutTime, - launchInLerpTime = launchInLerpTime, - launchInVec = missileVecs[i].inward, - launchInTime = launchInTime, - launchStraightLerpTime = launchStraightLerpTime, - endPos = missileEndPos, - applyRandSpread = applyRandSpread - } - */ - - missile.InitMissileExpandContract( missileVecs[i].outward, missileVecs[i].inward, launchOutTime, launchInLerpTime, launchInTime, launchStraightLerpTime, missileEndPos, applyRandSpread ) - - if ( IsServer() && debugDrawPath ) - thread DebugDrawMissilePath( missile ) - - //InitMissileForRandomDrift( missile, attackPos, attackDir ) - missile.InitMissileForRandomDriftFromWeaponSettings( attackPos, attackDir ) - - firedMissiles.append( missile ) - } - } - - return firedMissiles -} - -array<entity> function FireExpandContractMissiles_S2S( entity weapon, WeaponPrimaryAttackParams attackParams, vector attackPos, vector attackDir, shouldPredict, int rocketsPerShot, missileSpeed, launchOutAng, launchOutTime, launchInAng, launchInTime, launchInLerpTime, launchStraightLerpTime, applyRandSpread, int burstFireCountOverride = -1, debugDrawPath = false ) -{ - local missileVecs = GetExpandContractRocketTrajectories( weapon, attackParams.burstIndex, attackPos, attackDir, rocketsPerShot, launchOutAng, launchInAng, burstFireCountOverride ) - entity owner = weapon.GetWeaponOwner() - array<entity> firedMissiles - - vector missileEndPos = attackPos + ( attackDir * 5000 ) - - for ( int i = 0; i < rocketsPerShot; i++ ) - { - entity missile = weapon.FireWeaponMissile( attackPos, attackDir, missileSpeed, DF_GIB | DF_IMPACT, damageTypes.explosive, false, shouldPredict ) - missile.SetOrigin( attackPos )//HACK why do I have to do this? - if ( missile ) - { - /* - missile.s.flightData <- { - launchOutVec = missileVecs[i].outward, - launchOutTime = launchOutTime, - launchInLerpTime = launchInLerpTime, - launchInVec = missileVecs[i].inward, - launchInTime = launchInTime, - launchStraightLerpTime = launchStraightLerpTime, - endPos = missileEndPos, - applyRandSpread = applyRandSpread - } - */ - - missile.InitMissileExpandContract( missileVecs[i].outward, missileVecs[i].inward, launchOutTime, launchInLerpTime, launchInTime, launchStraightLerpTime, missileEndPos, applyRandSpread ) - - if ( IsServer() && debugDrawPath ) - thread DebugDrawMissilePath( missile ) - - //InitMissileForRandomDrift( missile, attackPos, attackDir ) - missile.InitMissileForRandomDriftFromWeaponSettings( attackPos, attackDir ) - - firedMissiles.append( missile ) - } - } - - return firedMissiles -} - -function GetExpandContractRocketTrajectories( entity weapon, int burstIndex, vector attackPos, vector attackDir, int rocketsPerShot, launchOutAng, launchInAng, int burstFireCount = -1 ) -{ - bool DEBUG_DRAW_MATH = false - - if ( burstFireCount == -1 ) - burstFireCount = weapon.GetWeaponBurstFireCount() - - local additionalRotation = ( ( 360.0 / rocketsPerShot ) / burstFireCount ) * burstIndex - //printt( "burstIndex:", burstIndex ) - //printt( "rocketsPerShot:", rocketsPerShot ) - //printt( "burstFireCount:", burstFireCount ) - - vector ang = VectorToAngles( attackDir ) - vector forward = AnglesToForward( ang ) - vector right = AnglesToRight( ang ) - vector up = AnglesToUp( ang ) - - if ( DEBUG_DRAW_MATH ) - DebugDrawLine( attackPos, attackPos + ( forward * 1000 ), 255, 0, 0, true, 30.0 ) - - // Create points on circle - float offsetAng = 360.0 / rocketsPerShot - for ( int i = 0; i < rocketsPerShot; i++ ) - { - local a = offsetAng * i + additionalRotation - vector vec = Vector( 0, 0, 0 ) - vec += up * deg_sin( a ) - vec += right * deg_cos( a ) - - if ( DEBUG_DRAW_MATH ) - DebugDrawLine( attackPos, attackPos + ( vec * 50 ), 10, 10, 10, true, 30.0 ) - } - - // Create missile points - vector x = right * deg_sin( launchOutAng ) - vector y = up * deg_sin( launchOutAng ) - vector z = forward * deg_cos( launchOutAng ) - vector rx = right * deg_sin( launchInAng ) - vector ry = up * deg_sin( launchInAng ) - vector rz = forward * deg_cos( launchInAng ) - local missilePoints = [] - for ( int i = 0; i < rocketsPerShot; i++ ) - { - local points = {} - - // Outward vec - local a = offsetAng * i + additionalRotation - float s = deg_sin( a ) - float c = deg_cos( a ) - vector vecOut = z + x * c + y * s - vecOut = Normalize( vecOut ) - points.outward <- vecOut - - // Inward vec - vector vecIn = rz + rx * c + ry * s - points.inward <- vecIn - - // Add to array - missilePoints.append( points ) - - if ( DEBUG_DRAW_MATH ) - { - DebugDrawLine( attackPos, attackPos + ( vecOut * 50 ), 255, 255, 0, true, 30.0 ) - DebugDrawLine( attackPos + vecOut * 50, attackPos + vecOut * 50 + ( vecIn * 50 ), 255, 0, 255, true, 30.0 ) - } - } - - return missilePoints -} - -function DebugDrawMissilePath( entity missile ) -{ - EndSignal( missile, "OnDestroy" ) - vector lastPos = missile.GetOrigin() - while ( true ) - { - WaitFrame() - if ( !IsValid( missile ) ) - return - DebugDrawLine( lastPos, missile.GetOrigin(), 0, 255, 0, true, 20.0 ) - lastPos = missile.GetOrigin() - } -} - - -function RegenerateOffhandAmmoOverTime( entity weapon, float rechargeTime, int maxAmmo, int offhandIndex ) -{ - weapon.Signal( "RegenAmmo" ) - weapon.EndSignal( "RegenAmmo" ) - weapon.EndSignal( "OnDestroy" ) - - #if CLIENT - entity weaponOwner = weapon.GetWeaponOwner() - if ( IsValid( weaponOwner ) && weaponOwner.IsPlayer() ) - { - entity cockpit = weaponOwner.GetCockpit() - if ( IsValid( cockpit ) ) - { - cockpit.s.offhandHud[offhandIndex].bar.SetBarProgressSource( ProgressSource.PROGRESS_SOURCE_SCRIPTED ) - cockpit.s.offhandHud[offhandIndex].bar.SetBarProgressRemap( 0.0, 1.0, 0.0, 1.0 ) - cockpit.s.offhandHud[offhandIndex].bar.SetBarProgressAndRate( 1.0 / maxAmmo , 1 / ( rechargeTime * maxAmmo ) ) - } - } - #endif - - if ( !( "totalChargeTime" in weapon.s ) ) - weapon.s.totalChargeTime <- rechargeTime - - if ( !( "nextChargeTime" in weapon.s ) ) - weapon.s.nextChargeTime <- null - - for ( ;; ) - { - weapon.s.nextChargeTime = rechargeTime + Time() - - wait rechargeTime - - if ( IsServer() ) - { - int max = maxAmmo - int weaponMax = weapon.GetWeaponPrimaryClipCountMax() - if ( weaponMax < max ) - max = weaponMax - - int ammo = weapon.GetWeaponPrimaryClipCount() - if ( ammo < max ) - weapon.SetWeaponPrimaryClipCount( ammo + 1 ) - } - } -} - -bool function IsPilotShotgunWeapon( string weaponName ) -{ - return GetWeaponInfoFileKeyField_Global( weaponName, "weaponSubClass" ) == "shotgun" -} - -array<string> function GetWeaponBurnMods( string weaponClassName ) -{ - array<string> burnMods = [] - array<string> mods = GetWeaponMods_Global( weaponClassName ) - string prefix = "burn_mod" - foreach ( mod in mods ) - { - if ( mod.find( prefix ) == 0 ) - burnMods.append( mod ) - } - - return burnMods -} - -int function TEMP_GetDamageFlagsFromProjectile( entity projectile ) -{ - var damageFlagsString = projectile.ProjectileGetWeaponInfoFileKeyField( "damage_flags" ) - if ( damageFlagsString == null ) - return 0 - expect string( damageFlagsString ) - - return TEMP_GetDamageFlagsFromString( damageFlagsString ) -} - -int function TEMP_GetDamageFlagsFromString( string damageFlagsString ) -{ - int damageFlags = 0 - - array<string> damageFlagTokens = split( damageFlagsString, "|" ) - foreach ( token in damageFlagTokens ) - { - damageFlags = damageFlags | getconsttable()[strip(token)] - } - - return damageFlags -} - -#if SERVER -function PROTO_InitTrackedProjectile( entity projectile ) -{ - // HACK: accessing ProjectileGetWeaponInfoFileKeyField or ProjectileGetWeaponClassName during CodeCallback_OnSpawned causes a code assert - projectile.EndSignal( "OnDestroy" ) - WaitFrame() - - entity owner = projectile.GetOwner() - - if ( !IsValid( owner ) || !owner.IsPlayer() ) - return - - int maxDeployed = projectile.GetProjectileWeaponSettingInt( eWeaponVar.projectile_max_deployed ) - if ( maxDeployed != 0 ) - { - AddToScriptManagedEntArray( owner.s.activeTrapArrayId, projectile ) - - array<entity> traps = GetScriptManagedEntArray( owner.s.activeTrapArrayId ) - array<entity> sameTypeTrapEnts - foreach ( ent in traps ) - { - if ( ent.ProjectileGetWeaponClassName() != projectile.ProjectileGetWeaponClassName() ) - continue - - sameTypeTrapEnts.append( ent ) - } - - int numToDestroy = sameTypeTrapEnts.len() - maxDeployed - if ( numToDestroy > 0 ) - { - sameTypeTrapEnts.sort( CompareCreation ) - foreach ( ent in sameTypeTrapEnts ) - { - ent.Destroy() - numToDestroy-- - - if ( !numToDestroy ) - break - } - } - } -} - - -function PROTO_CleanupTrackedProjectiles( entity player ) -{ - array<entity> traps = GetScriptManagedEntArray( player.s.activeTrapArrayId ) - foreach ( ent in traps ) - { - ent.Destroy() - } -} - -int function CompareCreation( entity a, entity b ) -{ - if ( a.GetProjectileCreationTime() > b.GetProjectileCreationTime() ) - return 1 - - return -1 -} - -int function CompareCreationReverse( entity a, entity b ) -{ - if ( a.GetProjectileCreationTime() > b.GetProjectileCreationTime() ) - return 1 - - return -1 -} - -void function PROTO_TrackedProjectile_OnPlayerRespawned( entity player ) -{ - thread PROTO_TrackedProjectile_OnPlayerRespawned_Internal( player ) -} - -void function PROTO_TrackedProjectile_OnPlayerRespawned_Internal( entity player ) -{ - player.EndSignal( "OnDeath" ) - - if ( player.s.inGracePeriod ) - player.WaitSignal( "GracePeriodDone" ) - - entity ordnance = player.GetOffhandWeapon( OFFHAND_ORDNANCE ) - - array<entity> traps = GetScriptManagedEntArray( player.s.activeTrapArrayId ) - foreach ( ent in traps ) - { - if ( ordnance && ent.ProjectileGetWeaponClassName() == ordnance.GetWeaponClassName() ) - continue - - ent.Destroy() - } -} - -function PROTO_PlayTrapLightEffect( entity ent, string tag, int team ) -{ - asset ownerFx = ent.ProjectileGetWeaponInfoFileKeyFieldAsset( "trap_warning_owner_fx" ) - if ( ownerFx != $"" ) - { - entity ownerFxEnt = CreateServerEffect_Owner( ownerFx, ent.GetOwner() ) - SetServerEffectControlPoint( ownerFxEnt, 0, FRIENDLY_COLOR ) - StartServerEffectOnEntity( ownerFxEnt, ent, tag ) - } - - asset friendlyFx = ent.ProjectileGetWeaponInfoFileKeyFieldAsset( "trap_warning_friendly_fx" ) - if ( friendlyFx != $"" ) - { - entity friendlyFxEnt = CreateServerEffect_Friendly( friendlyFx, team ) - SetServerEffectControlPoint( friendlyFxEnt, 0, FRIENDLY_COLOR_FX ) - StartServerEffectOnEntity( friendlyFxEnt, ent, tag ) - } - - asset enemyFx = ent.ProjectileGetWeaponInfoFileKeyFieldAsset( "trap_warning_enemy_fx" ) - if ( enemyFx != $"" ) - { - entity enemyFxEnt = CreateServerEffect_Enemy( enemyFx, team ) - SetServerEffectControlPoint( enemyFxEnt, 0, ENEMY_COLOR_FX ) - StartServerEffectOnEntity( enemyFxEnt, ent, tag ) - } -} - -string ornull function GetCooldownBeepPrefix( weapon ) -{ - var reloadBeepPrefix = weapon.GetWeaponInfoFileKeyField( "cooldown_sound_prefix" ) - if ( reloadBeepPrefix == null ) - return null - - expect string( reloadBeepPrefix ) - - return reloadBeepPrefix -} - -void function PROTO_DelayCooldown( entity weapon ) -{ - weapon.s.nextCooldownTime = Time() + weapon.s.cooldownDelay -} - -string function GetBeepSuffixForAmmo( int currentAmmo, int maxAmmo ) -{ - float frac = float( currentAmmo ) / float( maxAmmo ) - - if ( frac >= 1.0 ) - return "_full" - - if ( frac >= 0.25 ) - return "" - - return "_low" -} - -#endif //SERVER - -bool function PROTO_CanPlayerDeployWeapon( entity player ) -{ - if ( player.IsPhaseShifted() ) - return false - - if ( player.ContextAction_IsActive() == true ) - { - if ( player.IsZiplining() ) - return true - else - return false - } - - return true -} - -#if SERVER -void function PROTO_FlakCannonMissiles( entity projectile, float speed ) -{ - projectile.EndSignal( "OnDestroy" ) - - float radius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosionradius ) - vector velocity = projectile.GetVelocity() - vector currentPos = projectile.GetOrigin() - int team = projectile.GetTeam() - - float waitTime = 0.1 - float distanceInterval = speed * waitTime - int forwardDistanceChecks = int( ceil( distanceInterval / radius ) ) - bool forceExplosion = false - while ( forceExplosion == false ) - { - currentPos = projectile.GetOrigin() - for ( int i = 0; i < forwardDistanceChecks; i++ ) - { - float frac = float( i ) / float (forwardDistanceChecks ) - if ( PROTO_FlakCannon_HasNearbyEnemies( currentPos + velocity * waitTime * frac , team, radius ) ) - { - if ( i == 0 ) - { - forceExplosion = true - break - } - else - { - projectile.SetVelocity( velocity * ( frac - 0.05 ) ) - break - } - } - } - - if ( forceExplosion == false ) - wait waitTime - } - - projectile.MissileExplode() -} - -bool function PROTO_FlakCannon_HasNearbyEnemies( vector origin, int team, float radius ) -{ - float worldSpaceCenterBuffer = 200 - - array<entity> guys = GetPlayerArrayEx( "any", TEAM_ANY, team, origin, radius + worldSpaceCenterBuffer ) - foreach ( guy in guys ) - { - if ( IsAlive( guy ) && Distance( origin, guy.GetWorldSpaceCenter() ) < radius ) - return true - } - - array<entity> ai = GetNPCArrayEx( "any", TEAM_ANY, team, origin, radius + worldSpaceCenterBuffer ) - foreach ( guy in ai ) - { - if ( IsAlive( guy ) && Distance( origin, guy.GetWorldSpaceCenter() ) < radius ) - return true - } - - return false -} -#endif // #if SERVER - -void function GiveEMPStunStatusEffects( entity ent, float duration, float fadeoutDuration = 0.5, float slowTurn = EMP_SEVERITY_SLOWTURN, float slowMove = EMP_SEVERITY_SLOWMOVE) -{ - entity target = ent.IsTitan() ? ent.GetTitanSoul() : ent - int slowEffect = StatusEffect_AddTimed( target, eStatusEffect.turn_slow, slowTurn, duration, fadeoutDuration ) - int turnEffect = StatusEffect_AddTimed( target, eStatusEffect.move_slow, slowMove, duration, fadeoutDuration ) - - #if SERVER - if ( ent.IsPlayer() ) - { - ent.p.empStatusEffectsToClearForPhaseShift.append( slowEffect ) - ent.p.empStatusEffectsToClearForPhaseShift.append( turnEffect ) - } - #endif -} - -#if DEV -string ornull function FindEnumNameForValue( table searchTable, int searchVal ) -{ - foreach( string keyname, int value in searchTable ) - { - if ( value == searchVal ) - return keyname; - } - return null -} - -void function DevPrintAllStatusEffectsOnEnt( entity ent ) -{ - printt( "Effects:", ent ) - array<float> effects = StatusEffect_GetAll( ent ) - int length = effects.len() - int found = 0; - for ( int idx = 0; idx < length; idx++ ) - { - float severity = effects[idx]; - if ( severity <= 0.0 ) - continue - string ornull name = FindEnumNameForValue( eStatusEffect, idx ) - Assert( name ) - expect string( name ) - printt( " eStatusEffect." + name + ": " + severity ) - found++; - } - printt( found + " effects active.\n" ); -} -#endif // #if DEV - -array<entity> function GetPrimaryWeapons( entity player ) -{ - array<entity> primaryWeapons - array<entity> weapons = player.GetMainWeapons() - foreach ( weaponEnt in weapons ) - { - int weaponType = weaponEnt.GetWeaponType() - if ( weaponType == WT_SIDEARM || weaponType == WT_ANTITITAN ) - continue; - - primaryWeapons.append( weaponEnt ) - } - return primaryWeapons -} - -array<entity> function GetSidearmWeapons( entity player ) -{ - array<entity> sidearmWeapons - array<entity> weapons = player.GetMainWeapons() - foreach ( weaponEnt in weapons ) - { - if ( weaponEnt.GetWeaponType() != WT_SIDEARM ) - continue - - sidearmWeapons.append( weaponEnt ) - } - return sidearmWeapons -} - -array<entity> function GetATWeapons( entity player ) -{ - array<entity> atWeapons - array<entity> weapons = player.GetMainWeapons() - foreach ( weaponEnt in weapons ) - { - if ( weaponEnt.GetWeaponType() != WT_ANTITITAN ) - continue - - atWeapons.append( weaponEnt ) - } - return atWeapons -} - -entity function GetPlayerFromTitanWeapon( entity weapon ) -{ - entity titan = weapon.GetWeaponOwner() - entity player - - if ( titan == null ) - return null - - if ( !titan.IsPlayer() ) - player = titan.GetBossPlayer() - else - player = titan - - return player -} - - -const asset CHARGE_SHOT_PROJECTILE = $"models/weapons/bullets/temp_triple_threat_projectile_large.mdl" - -const asset CHARGE_EFFECT_1P = $"P_ordnance_charge_st_FP" // $"P_wpn_defender_charge_FP" -const asset CHARGE_EFFECT_3P = $"P_ordnance_charge_st" // $"P_wpn_defender_charge" -const asset CHARGE_EFFECT_DLIGHT = $"defender_charge_CH_dlight" - -const string CHARGE_SOUND_WINDUP_1P = "Weapon_ChargeRifle_WindUp_1P" -const string CHARGE_SOUND_WINDUP_3P = "Weapon_ChargeRifle_WindUp_3P" -const string CHARGE_SOUND_WINDDOWN_1P = "Weapon_ChargeRifle_WindDown_1P" -const string CHARGE_SOUND_WINDDOWN_3P = "Weapon_ChargeRifle_WindDown_3P" - -void function ChargeBall_Precache() -{ -#if SERVER - PrecacheModel( CHARGE_SHOT_PROJECTILE ) - PrecacheEffect( CHARGE_EFFECT_1P ) - PrecacheEffect( CHARGE_EFFECT_3P ) -#endif // #if SERVER -} - -void function ChargeBall_FireProjectile( entity weapon, vector position, vector direction, bool shouldPredict ) -{ - weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 ) - - entity owner = weapon.GetWeaponOwner() - const float MISSILE_SPEED = 1200.0 - const int CONTACT_DAMAGE_TYPES = (damageTypes.projectileImpact | DF_DOOM_FATALITY) - const int EXPLOSION_DAMAGE_TYPES = damageTypes.explosive - const bool DO_POPUP = false - - if ( shouldPredict ) - { - entity missile = weapon.FireWeaponMissile( position, direction, MISSILE_SPEED, CONTACT_DAMAGE_TYPES, EXPLOSION_DAMAGE_TYPES, DO_POPUP, shouldPredict ) - if ( missile ) - { - EmitSoundOnEntity( owner, "ShoulderRocket_Cluster_Fire_3P" ) - missile.SetModel( CHARGE_SHOT_PROJECTILE ) -#if CLIENT - const ROCKETEER_MISSILE_EXPLOSION = $"xo_exp_death" - const ROCKETEER_MISSILE_SHOULDER_FX = $"wpn_mflash_xo_rocket_shoulder_FP" - entity owner = weapon.GetWeaponOwner() - vector origin = owner.OffsetPositionFromView( Vector(0, 0, 0), Vector(25, -25, 15) ) - vector angles = owner.CameraAngles() - StartParticleEffectOnEntityWithPos( owner, GetParticleSystemIndex( ROCKETEER_MISSILE_SHOULDER_FX ), FX_PATTACH_EYES_FOLLOW, -1, origin, angles ) -#else // #if CLIENT - missile.SetProjectileImpactDamageOverride( 1440 ) - missile.kv.damageSourceId = eDamageSourceId.charge_ball -#endif // #else // #if CLIENT - } - } -} - -bool function ChargeBall_ChargeBegin( entity weapon, string tagName ) -{ -#if CLIENT - if ( InPrediction() && !IsFirstTimePredicted() ) - return true -#endif // #if CLIENT - - weapon.w.statusEffects.append( StatusEffect_AddEndless( weapon.GetWeaponOwner(), eStatusEffect.move_slow, 0.6 ) ) - weapon.w.statusEffects.append( StatusEffect_AddEndless( weapon.GetWeaponOwner(), eStatusEffect.turn_slow, 0.35 ) ) - - weapon.PlayWeaponEffect( CHARGE_EFFECT_1P, CHARGE_EFFECT_3P, tagName ) - weapon.PlayWeaponEffect( $"", CHARGE_EFFECT_DLIGHT, tagName ) - -#if SERVER - StopSoundOnEntity( weapon, CHARGE_SOUND_WINDDOWN_3P ) - entity weaponOwner = weapon.GetWeaponOwner() - if ( IsValid( weaponOwner ) ) - { - if ( weaponOwner.IsPlayer() ) - EmitSoundOnEntityExceptToPlayer( weapon, weaponOwner, CHARGE_SOUND_WINDUP_3P ) - else - EmitSoundOnEntity( weapon, CHARGE_SOUND_WINDUP_3P ) - } -#else - StopSoundOnEntity( weapon, CHARGE_SOUND_WINDDOWN_1P ) - EmitSoundOnEntity( weapon, CHARGE_SOUND_WINDUP_1P ) -#endif - - return true -} - -void function ChargeBall_ChargeEnd( entity weapon ) -{ -#if CLIENT - if ( InPrediction() && !IsFirstTimePredicted() ) - return -#endif - - if ( IsValid( weapon.GetWeaponOwner() ) ) - { - #if CLIENT - if ( InPrediction() && IsFirstTimePredicted() ) - { - #endif - - foreach ( effect in weapon.w.statusEffects ) - { - StatusEffect_Stop( weapon.GetWeaponOwner(), effect ) - } - - #if CLIENT - } - #endif - } - -#if SERVER - StopSoundOnEntity( weapon, CHARGE_SOUND_WINDUP_3P ) - entity weaponOwner = weapon.GetWeaponOwner() - if ( IsValid( weaponOwner ) ) - { - if ( weaponOwner.IsPlayer() ) - EmitSoundOnEntityExceptToPlayer( weapon, weaponOwner, CHARGE_SOUND_WINDDOWN_3P ) - else - EmitSoundOnEntity( weapon, CHARGE_SOUND_WINDDOWN_3P ) - } -#else - StopSoundOnEntity( weapon, CHARGE_SOUND_WINDUP_1P ) - EmitSoundOnEntity( weapon, CHARGE_SOUND_WINDDOWN_1P ) -#endif - - ChargeBall_StopChargeEffects( weapon ) -} - -void function ChargeBall_StopChargeEffects( entity weapon ) -{ - Assert( IsValid( weapon ) ) - // weapon.StopWeaponEffect( CHARGE_EFFECT_1P, CHARGE_EFFECT_3P ) - // weapon.StopWeaponEffect( CHARGE_EFFECT_3P, CHARGE_EFFECT_1P ) - // weapon.StopWeaponEffect( CHARGE_EFFECT_DLIGHT, CHARGE_EFFECT_DLIGHT ) - thread HACK_Deplayed_ChargeBall_StopChargeEffects( weapon ) -} - -void function HACK_Deplayed_ChargeBall_StopChargeEffects( entity weapon ) -{ - weapon.EndSignal( "OnDestroy" ) - wait 0.2 - weapon.StopWeaponEffect( CHARGE_EFFECT_1P, CHARGE_EFFECT_3P ) - weapon.StopWeaponEffect( CHARGE_EFFECT_3P, CHARGE_EFFECT_1P ) - weapon.StopWeaponEffect( CHARGE_EFFECT_DLIGHT, CHARGE_EFFECT_DLIGHT ) -} - -float function ChargeBall_GetChargeTime() -{ - return 1.05 -} - -#if SERVER -void function GivePlayerAmpedWeapon( entity player, string weaponName ) -{ - array<entity> weapons = player.GetMainWeapons() - int numWeapons = weapons.len() - if ( numWeapons == 0 ) - return - - //Figure out what weapon to take away. - //This is more complicated than it should be because of rules of what weapons can be in what slots, e.g. your anti-titan weapon can't be replaced by non anti-titan weapons - if ( HasWeapon( player, weaponName ) ) - { - //Simplest case: - //Take away the currently existing version of the weapon you already have. - player.TakeWeaponNow( weaponName ) - } - else - { - bool ampedWeaponIsAntiTitan = GetWeaponInfoFileKeyField_Global( weaponName, "weaponType" ) == "anti_titan" - if ( ampedWeaponIsAntiTitan ) - { - foreach( weapon in weapons ) - { - string currentWeaponClassName = weapon.GetWeaponClassName() - if ( GetWeaponInfoFileKeyField_Global( currentWeaponClassName, "weaponType" ) == "anti_titan" ) - { - player.TakeWeaponNow( currentWeaponClassName ) - break - } - } - - unreachable //We had no anti-titan weapon? Shouldn't ever be possible - - } - else - { - string currentActiveWeaponClassName = player.GetActiveWeapon().GetWeaponClassName() - if ( ShouldReplaceWeaponInFirstSlot( player, currentActiveWeaponClassName ) ) - { - //Current weapon is anti_titan, but amped weapon we are trying to give is not. Just replace the weapon that is in the first slot. - //Assumes that weapon in first slot is not an anti-titan weapon - //We could get even fancier and look to see if the amped weapon is a primary weapon or a sidearm and replace the slot accordingly, but - //that makes it more complicated, plus there are cases where you can have no primary weapons/no side arms etc - string firstWeaponClassName = weapons[ 0 ].GetWeaponClassName() - Assert( GetWeaponInfoFileKeyField_Global( firstWeaponClassName, "weaponType" ) != "anti_titan" ) - player.TakeWeaponNow( firstWeaponClassName ) - } - else - { - player.TakeWeaponNow( currentActiveWeaponClassName ) - } - } - } - - array<string> burnMods = GetWeaponBurnMods( weaponName ) - entity ampedWeapon = player.GiveWeapon( weaponName, burnMods ) - ampedWeapon.SetWeaponPrimaryClipCount( ampedWeapon.GetWeaponPrimaryClipCountMax() ) //Needed for weapons that give a mod with extra clip size -} - -bool function ShouldReplaceWeaponInFirstSlot( entity player, string currentActiveWeaponClassName ) -{ - if ( GetWeaponInfoFileKeyField_Global( currentActiveWeaponClassName, "weaponType" ) == "anti_titan" ) //Active weapon is anti-titan weapon. Can't replace anti-titan weapon slot with non-anti-titan weapon - return true - - if ( currentActiveWeaponClassName == player.GetOffhandWeapon( OFFHAND_ORDNANCE ).GetWeaponClassName() ) - return true - - return false - -} - -void function GivePlayerAmpedWeaponAndSetAsActive( entity player, string weaponName ) -{ - GivePlayerAmpedWeapon( player, weaponName ) - player.SetActiveWeaponByName( weaponName ) -} - -void function ReplacePlayerOffhand( entity player, string offhandName, array<string> mods = [] ) -{ - player.TakeOffhandWeapon( OFFHAND_SPECIAL ) - player.GiveOffhandWeapon( offhandName, OFFHAND_SPECIAL, mods ) -} - -void function ReplacePlayerOrdnance( entity player, string ordnanceName, array<string> mods = [] ) -{ - player.TakeOffhandWeapon( OFFHAND_ORDNANCE ) - player.GiveOffhandWeapon( ordnanceName, OFFHAND_ORDNANCE, mods ) -} - -void function PAS_CooldownReduction_OnKill( entity victim, entity attacker, var damageInfo ) -{ - if ( !IsAlive( attacker ) || !IsPilot( attacker ) ) - return - - array<string> weaponMods = GetWeaponModsFromDamageInfo( damageInfo ) - - if ( GetCurrentPlaylistVarInt( "featured_mode_tactikill", 0 ) > 0 ) - { - entity weapon = attacker.GetOffhandWeapon( OFFHAND_LEFT ) - - switch ( GetWeaponInfoFileKeyField_Global( weapon.GetWeaponClassName(), "cooldown_type" ) ) - { - case "grapple": - attacker.SetSuitGrapplePower( attacker.GetSuitGrapplePower() + 100 ) - break - - case "ammo": - case "ammo_instant": - case "ammo_deployed": - case "ammo_timed": - int maxAmmo = weapon.GetWeaponPrimaryClipCountMax() - weapon.SetWeaponPrimaryClipCountNoRegenReset( maxAmmo ) - break - - case "chargeFrac": - weapon.SetWeaponChargeFraction( 0 ) - break - - // case "mp_ability_ground_slam": - // break - - default: - Assert( false, weapon.GetWeaponClassName() + " needs to be updated to support cooldown_type setting" ) - break - } - } - else - { - if ( !PlayerHasPassive( attacker, ePassives.PAS_CDR_ON_KILL ) && !weaponMods.contains( "tactical_cdr_on_kill" ) ) - return - - entity weapon = attacker.GetOffhandWeapon( OFFHAND_LEFT ) - - switch ( GetWeaponInfoFileKeyField_Global( weapon.GetWeaponClassName(), "cooldown_type" ) ) - { - case "grapple": - attacker.SetSuitGrapplePower( attacker.GetSuitGrapplePower() + 25 ) - break - - case "ammo": - case "ammo_instant": - case "ammo_deployed": - case "ammo_timed": - int maxAmmo = weapon.GetWeaponPrimaryClipCountMax() - weapon.SetWeaponPrimaryClipCountNoRegenReset( min( maxAmmo, weapon.GetWeaponPrimaryClipCount() + ( maxAmmo / 4 ) ) ) - break - - case "chargeFrac": - weapon.SetWeaponChargeFraction( max( 0, weapon.GetWeaponChargeFraction() - 0.25 ) ) - break - - // case "mp_ability_ground_slam": - // break - - default: - Assert( false, weapon.GetWeaponClassName() + " needs to be updated to support cooldown_type setting" ) - break - } - } -} - -void function DisableWeapons( entity player, array<string> excludeNames ) -{ - array<entity> weapons = GetPlayerWeapons( player, excludeNames ) - foreach ( weapon in weapons ) - weapon.AllowUse( false ) -} - -void function EnableWeapons( entity player, array<string> excludeNames ) -{ - array<entity> weapons = GetPlayerWeapons( player, excludeNames ) - foreach ( weapon in weapons ) - weapon.AllowUse( true ) -} - -array<entity> function GetPlayerWeapons( entity player, array<string> excludeNames ) -{ - array<entity> weapons = player.GetMainWeapons() - weapons.extend( player.GetOffhandWeapons() ) - - for ( int idx = weapons.len() - 1; idx > 0; idx-- ) - { - foreach ( excludeName in excludeNames ) - { - if ( weapons[idx].GetWeaponClassName() == excludeName ) - weapons.remove( idx ) - } - } - - return weapons -} - -void function WeaponAttackWave( entity ent, int projectileCount, entity inflictor, vector pos, vector dir, bool functionref( entity, int, entity, entity, vector, vector, int ) waveFunc ) -{ - ent.EndSignal( "OnDestroy" ) - - entity weapon - entity projectile - int maxCount - float step - entity owner - int damageNearValueTitanArmor - int count = 0 - array<vector> positions = [] - vector lastDownPos - bool firstTrace = true - - dir = <dir.x, dir.y, 0.0> - dir = Normalize( dir ) - vector angles = VectorToAngles( dir ) - - if ( ent.IsProjectile() ) - { - projectile = ent - string chargedPrefix = "" - if ( ent.proj.isChargedShot ) - chargedPrefix = "charge_" - - maxCount = expect int( ent.ProjectileGetWeaponInfoFileKeyField( chargedPrefix + "wave_max_count" ) ) - step = expect float( ent.ProjectileGetWeaponInfoFileKeyField( chargedPrefix + "wave_step_dist" ) ) - owner = ent.GetOwner() - damageNearValueTitanArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.damage_near_value_titanarmor ) - } - else - { - weapon = ent - maxCount = expect int( ent.GetWeaponInfoFileKeyField( "wave_max_count" ) ) - step = expect float( ent.GetWeaponInfoFileKeyField( "wave_step_dist" ) ) - owner = ent.GetWeaponOwner() - damageNearValueTitanArmor = weapon.GetWeaponSettingInt( eWeaponVar.damage_near_value_titanarmor ) - } - - owner.EndSignal( "OnDestroy" ) - - for ( int i = 0; i < maxCount; i++ ) - { - vector newPos = pos + dir * step - - vector traceStart = pos - vector traceEndUnder = newPos - vector traceEndOver = newPos - - if ( !firstTrace ) - { - traceStart = lastDownPos + <0.0, 0.0, 80.0 > - traceEndUnder = <newPos.x, newPos.y, traceStart.z - 40.0 > - traceEndOver = <newPos.x, newPos.y, traceStart.z + step * 0.57735056839> // The over height is to cover the case of a sheer surface that then continues gradually upwards (like mp_box) - } - firstTrace = false - - VortexBulletHit ornull vortexHit = VortexBulletHitCheck( owner, traceStart, traceEndOver ) - if ( vortexHit ) - { - expect VortexBulletHit( vortexHit ) - entity vortexWeapon = vortexHit.vortex.GetOwnerWeapon() - - if ( vortexWeapon && vortexWeapon.GetWeaponClassName() == "mp_titanweapon_vortex_shield" ) - VortexDrainedByImpact( vortexWeapon, weapon, projectile, null ) // drain the vortex shield - else if ( IsVortexSphere( vortexHit.vortex ) ) - VortexSphereDrainHealthForDamage( vortexHit.vortex, damageNearValueTitanArmor ) - - WaitFrame() - continue - } - - //DebugDrawLine( traceStart, traceEndUnder, 0, 255, 0, true, 25.0 ) - array ignoreArray = [] - if ( IsValid( inflictor ) && inflictor.GetOwner() != null ) - ignoreArray.append( inflictor.GetOwner() ) - - TraceResults forwardTrace = TraceLine( traceStart, traceEndUnder, ignoreArray, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS ) - if ( forwardTrace.fraction == 1.0 ) - { - //DebugDrawLine( forwardTrace.endPos, forwardTrace.endPos + <0.0, 0.0, -1000.0>, 255, 0, 0, true, 25.0 ) - TraceResults downTrace = TraceLine( forwardTrace.endPos, forwardTrace.endPos + <0.0, 0.0, -1000.0>, ignoreArray, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS ) - if ( downTrace.fraction == 1.0 ) - break - - entity movingGeo = null - if ( downTrace.hitEnt && downTrace.hitEnt.HasPusherRootParent() && !downTrace.hitEnt.IsMarkedForDeletion() ) - movingGeo = downTrace.hitEnt - - if ( !waveFunc( ent, projectileCount, inflictor, movingGeo, downTrace.endPos, angles, i ) ) - return - - lastDownPos = downTrace.endPos - pos = forwardTrace.endPos - - WaitFrame() - continue - } - else - { - if ( IsValid( forwardTrace.hitEnt ) && (StatusEffect_Get( forwardTrace.hitEnt, eStatusEffect.pass_through_amps_weapon ) > 0) && !CheckPassThroughDir( forwardTrace.hitEnt, forwardTrace.surfaceNormal, forwardTrace.endPos ) ) - break; - } - - TraceResults upwardTrace = TraceLine( traceStart, traceEndOver, ignoreArray, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS ) - //DebugDrawLine( traceStart, traceEndOver, 0, 0, 255, true, 25.0 ) - if ( upwardTrace.fraction < 1.0 ) - { - if ( IsValid( upwardTrace.hitEnt ) ) - { - if ( upwardTrace.hitEnt.IsWorld() || upwardTrace.hitEnt.IsPlayer() || upwardTrace.hitEnt.IsNPC() ) - break - } - } - else - { - TraceResults downTrace = TraceLine( upwardTrace.endPos, upwardTrace.endPos + <0.0, 0.0, -1000.0>, ignoreArray, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS ) - if ( downTrace.fraction == 1.0 ) - break - - entity movingGeo = null - if ( downTrace.hitEnt && downTrace.hitEnt.HasPusherRootParent() && !downTrace.hitEnt.IsMarkedForDeletion() ) - movingGeo = downTrace.hitEnt - - if ( !waveFunc( ent, projectileCount, inflictor, movingGeo, downTrace.endPos, angles, i ) ) - return - - lastDownPos = downTrace.endPos - pos = forwardTrace.endPos - } - - WaitFrame() - } -} - -void function AddActiveThermiteBurn( entity ent ) -{ - AddToScriptManagedEntArray( file.activeThermiteBurnsManagedEnts, ent ) -} - -array<entity> function GetActiveThermiteBurnsWithinRadius( vector origin, float dist, team = TEAM_ANY ) -{ - return GetScriptManagedEntArrayWithinCenter( file.activeThermiteBurnsManagedEnts, team, origin, dist ) -} - -void function EMP_DamagedPlayerOrNPC( entity ent, var damageInfo ) -{ - Elecriticy_DamagedPlayerOrNPC( ent, damageInfo, FX_EMP_BODY_HUMAN, FX_EMP_BODY_TITAN, EMP_SEVERITY_SLOWTURN, EMP_SEVERITY_SLOWMOVE ) -} - -void function VanguardEnergySiphon_DamagedPlayerOrNPC( entity ent, var damageInfo ) -{ - entity attacker = DamageInfo_GetAttacker( damageInfo ) - if ( IsValid( attacker ) && attacker.GetTeam() == ent.GetTeam() ) - return - - Elecriticy_DamagedPlayerOrNPC( ent, damageInfo, FX_VANGUARD_ENERGY_BODY_HUMAN, FX_VANGUARD_ENERGY_BODY_TITAN, LASER_STUN_SEVERITY_SLOWTURN, LASER_STUN_SEVERITY_SLOWMOVE ) -} - -void function Elecriticy_DamagedPlayerOrNPC( entity ent, var damageInfo, asset humanFx, asset titanFx, float slowTurn, float slowMove ) -{ - if ( !IsValid( ent ) ) - return - - if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS ) - return - - local inflictor = DamageInfo_GetInflictor( damageInfo ) - if( !IsValid( inflictor ) ) - return - - // Do electrical effect on this ent that everyone can see if they are a titan - string tag = "" - asset effect - - if ( ent.IsTitan() ) - { - tag = "exp_torso_front" - effect = titanFx - } - else if ( IsStalker( ent ) || IsSpectre( ent ) ) - { - tag = "CHESTFOCUS" - effect = humanFx - if ( !ent.ContextAction_IsActive() && IsAlive( ent ) && ent.IsInterruptable() ) - { - ent.Anim_ScriptedPlayActivityByName( "ACT_STUNNED", true, 0.1 ) - } - } - else if ( IsSuperSpectre( ent ) ) - { - tag = "CHESTFOCUS" - effect = humanFx - - if ( ent.GetParent() == null && !ent.ContextAction_IsActive() && IsAlive( ent ) && ent.IsInterruptable() ) - { - ent.Anim_ScriptedPlayActivityByName( "ACT_STUNNED", true, 0.1 ) - } - } - else if ( IsGrunt( ent ) ) - { - tag = "CHESTFOCUS" - effect = humanFx - if ( !ent.ContextAction_IsActive() && IsAlive( ent ) && ent.IsInterruptable() ) - { - ent.Anim_ScriptedPlayActivityByName( "ACT_STUNNED", true, 0.1 ) - ent.EnableNPCFlag( NPC_PAIN_IN_SCRIPTED_ANIM ) - } - } - else if ( IsPilot( ent ) ) - { - tag = "CHESTFOCUS" - effect = humanFx - } - else if ( IsAirDrone( ent ) ) - { - if ( GetDroneType( ent ) == "drone_type_marvin" ) - return - tag = "HEADSHOT" - effect = humanFx - thread NpcEmpRebootPrototype( ent, damageInfo, humanFx, titanFx ) - } - else if ( IsGunship( ent ) ) - { - tag = "ORIGIN" - effect = titanFx - thread NpcEmpRebootPrototype( ent, damageInfo, humanFx, titanFx ) - } - - ent.Signal( "ArcStunned" ) - - if ( tag != "" ) - { - local inflictor = DamageInfo_GetInflictor( damageInfo ) - Assert( !(inflictor instanceof CEnvExplosion) ) - if ( IsValid( inflictor ) ) - { - float duration = EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MAX - if ( inflictor instanceof CBaseGrenade ) - { - local entCenter = ent.GetWorldSpaceCenter() - local dist = Distance( DamageInfo_GetDamagePosition( damageInfo ), entCenter ) - local damageRadius = inflictor.GetDamageRadius() - duration = GraphCapped( dist, damageRadius * 0.5, damageRadius, EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MIN, EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MAX ) - } - thread EMP_FX( effect, ent, tag, duration ) - } - } - - if ( StatusEffect_Get( ent, eStatusEffect.destroyed_by_emp ) ) - DamageInfo_SetDamage( damageInfo, ent.GetHealth() ) - - // Don't do arc beams to entities that are on the same team... except the owner - entity attacker = DamageInfo_GetAttacker( damageInfo ) - if ( IsValid( attacker ) && attacker.GetTeam() == ent.GetTeam() && attacker != ent ) - return - - if ( ent.IsPlayer() ) - { - thread EMPGrenade_EffectsPlayer( ent, damageInfo ) - } - else if ( ent.IsTitan() ) - { - EMPGrenade_AffectsShield( ent, damageInfo ) - #if MP - GiveEMPStunStatusEffects( ent, 2.5, 1.0, slowTurn, slowMove ) - #endif - thread EMPGrenade_AffectsAccuracy( ent ) - } - else if ( ent.IsMechanical() ) - { - #if MP - GiveEMPStunStatusEffects( ent, 2.5, 1.0, slowTurn, slowMove ) - DamageInfo_ScaleDamage( damageInfo, 2.05 ) - #endif - } - else if ( ent.IsHuman() ) - { - #if MP - DamageInfo_ScaleDamage( damageInfo, 0.99 ) - #endif - } - - if ( inflictor instanceof CBaseGrenade ) - { - if ( !ent.IsPlayer() || ent.IsTitan() ) //Beam should hit cloaked targets, when cloak is updated make IsCloaked() function. - EMPGrenade_ArcBeam( DamageInfo_GetDamagePosition( damageInfo ), ent ) - } -} - -////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// HACK: might make sense to move this to code -void function NpcEmpRebootPrototype( entity npc, var damageInfo, asset humanFx, asset titanFx ) -{ - if ( !IsValid( npc ) ) - return - - npc.EndSignal( "OnDeath" ) - npc.EndSignal( "OnDestroy" ) - - if ( !( "rebooting" in npc.s ) ) - npc.s.rebooting <- null - - if ( npc.s.rebooting ) // npc already knocked down and in rebooting process - return - - float rebootTime - vector groundPos - local nearestNode - local neighborNodes - local groundNodePos - local origin = npc.GetOrigin() - local startOrigin = origin - local classname = npc.GetClassName() - local soundPowerDown - local soundPowerUp - - //------------------------------------------------------ - // Custom stuff depending on AI type - //------------------------------------------------------ - switch ( classname ) - { - case "npc_drone": - soundPowerDown = "Drone_Power_Down" - soundPowerUp = "Drone_Power_On" - rebootTime = DRONE_REBOOT_TIME - break - case "npc_gunship": - soundPowerDown = "Gunship_Power_Down" - soundPowerUp = "Gunship_Power_On" - rebootTime = GUNSHIP_REBOOT_TIME - break - default: - Assert( 0, "Unhandled npc type: " + classname ) - - } - - //------------------------------------------------------ - // NPC stunned and is rebooting - //------------------------------------------------------ - npc.Signal( "OnStunned" ) - npc.s.rebooting = true - - - //TODO: make drone/gunship slowly drift to the ground while rebooting - /* - groundPos = OriginToGround( origin ) - groundPos += Vector( 0, 0, 32 ) - - - //DebugDrawLine(origin, groundPos, 255, 0, 0, true, 15 ) - - //thread AssaultOrigin( drone, groundPos, 16 ) - //thread PlayAnim( drone, "idle" ) - */ - - - thread EmpRebootFxPrototype( npc, humanFx, titanFx ) - npc.EnableNPCFlag( NPC_IGNORE_ALL ) - npc.SetNoTarget( true ) - npc.EnableNPCFlag( NPC_DISABLE_SENSING ) // don't do traces to look for enemies or players - - if ( IsAttackDrone( npc ) ) - npc.SetAttackMode( false ) - - EmitSoundOnEntity( npc, soundPowerDown ) - - wait rebootTime - - EmitSoundOnEntity( npc, soundPowerUp ) - npc.DisableNPCFlag( NPC_IGNORE_ALL ) - npc.SetNoTarget( false ) - npc.DisableNPCFlag( NPC_DISABLE_SENSING ) // don't do traces to look for enemies or players - - if ( IsAttackDrone( npc ) ) - npc.SetAttackMode( true ) - - npc.s.rebooting = false -} - -////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// HACK: might make sense to move this to code -function EmpRebootFxPrototype( npc, asset humanFx, asset titanFx ) -{ - expect entity( npc ) - - if ( !IsValid( npc ) ) - return - - npc.EndSignal( "OnDeath" ) - npc.EndSignal( "OnDestroy" ) - - string classname = npc.GetClassName() - vector origin - float delayDuration - entity fxHandle - asset fxEMPdamage - string fxTag - float rebootTime - string soundEMPdamage - - //------------------------------------------------------ - // Custom stuff depending on AI type - //------------------------------------------------------ - switch ( classname ) - { - case "npc_drone": - if ( GetDroneType( npc ) == "drone_type_marvin" ) - return - fxEMPdamage = humanFx - fxTag = "HEADSHOT" - rebootTime = DRONE_REBOOT_TIME - soundEMPdamage = "Titan_Blue_Electricity_Cloud" - break - case "npc_gunship": - fxEMPdamage = titanFx - fxTag = "ORIGIN" - rebootTime = GUNSHIP_REBOOT_TIME - soundEMPdamage = "Titan_Blue_Electricity_Cloud" - break - default: - Assert( 0, "Unhandled npc type: " + classname ) - - } - - //------------------------------------------------------ - // Play Fx/Sound till reboot finishes - //------------------------------------------------------ - fxHandle = ClientStylePlayFXOnEntity( fxEMPdamage, npc, fxTag, rebootTime ) - EmitSoundOnEntity( npc, soundEMPdamage ) - - while ( npc.s.rebooting == true ) - { - delayDuration = RandomFloatRange( 0.4, 1.2 ) - origin = npc.GetOrigin() - - - EmitSoundAtPosition( npc.GetTeam(), origin, SOUND_EMP_REBOOT_SPARKS ) - PlayFX( FX_EMP_REBOOT_SPARKS, origin ) - PlayFX( FX_EMP_REBOOT_SPARKS, origin ) - - OnThreadEnd( - function() : ( fxHandle, npc, soundEMPdamage ) - { - if ( IsValid( fxHandle ) ) - fxHandle.Fire( "StopPlayEndCap" ) - if ( IsValid( npc ) ) - StopSoundOnEntity( npc, soundEMPdamage ) - } - ) - - wait ( delayDuration ) - } -} - -function EMP_FX( asset effect, entity ent, string tag, float duration ) -{ - if ( !IsAlive( ent ) ) - return - - ent.Signal( "EMP_FX" ) - ent.EndSignal( "OnDestroy" ) - ent.EndSignal( "OnDeath" ) - ent.EndSignal( "StartPhaseShift" ) - ent.EndSignal( "EMP_FX" ) - - bool isPlayer = ent.IsPlayer() - - int fxId = GetParticleSystemIndex( effect ) - int attachId = ent.LookupAttachment( tag ) - - entity fxHandle = StartParticleEffectOnEntity_ReturnEntity( ent, fxId, FX_PATTACH_POINT_FOLLOW, attachId ) - fxHandle.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY - fxHandle.SetOwner( ent ) - - OnThreadEnd( - function() : ( fxHandle, ent ) - { - if ( IsValid( fxHandle ) ) - { - EffectStop( fxHandle ) - } - - if ( IsValid( ent ) ) - StopSoundOnEntity( ent, "Titan_Blue_Electricity_Cloud" ) - } - ) - - if ( !isPlayer ) - { - EmitSoundOnEntity( ent, "Titan_Blue_Electricity_Cloud" ) - wait duration - } - else - { - EmitSoundOnEntityExceptToPlayer( ent, ent, "Titan_Blue_Electricity_Cloud" ) - - var endTime = Time() + duration - bool effectsActive = true - while( endTime > Time() ) - { - if ( ent.IsPhaseShifted() ) - { - if ( effectsActive ) - { - effectsActive = false - if ( IsValid( fxHandle ) ) - EffectSleep( fxHandle ) - - if ( IsValid( ent ) ) - StopSoundOnEntity( ent, "Titan_Blue_Electricity_Cloud" ) - } - } - else if ( effectsActive == false ) - { - EffectWake( fxHandle ) - EmitSoundOnEntityExceptToPlayer( ent, ent, "Titan_Blue_Electricity_Cloud" ) - effectsActive = true - } - - WaitFrame() - } - } -} - -function EMPGrenade_AffectsShield( entity titan, damageInfo ) -{ - int shieldHealth = titan.GetTitanSoul().GetShieldHealth() - int shieldDamage = int( titan.GetTitanSoul().GetShieldHealthMax() * 0.5 ) - - titan.GetTitanSoul().SetShieldHealth( maxint( 0, shieldHealth - shieldDamage ) ) - - // attacker took down titan shields - if ( shieldHealth && !titan.GetTitanSoul().GetShieldHealth() ) - { - entity attacker = DamageInfo_GetAttacker( damageInfo ) - if ( attacker && attacker.IsPlayer() ) - EmitSoundOnEntityOnlyToPlayer( attacker, attacker, "titan_energyshield_down" ) - } -} - -function EMPGrenade_AffectsAccuracy( npcTitan ) -{ - npcTitan.EndSignal( "OnDestroy" ) - - npcTitan.kv.AccuracyMultiplier = 0.5 - wait EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MAX - npcTitan.kv.AccuracyMultiplier = 1.0 -} - - -function EMPGrenade_EffectsPlayer( entity player, damageInfo ) -{ - player.Signal( "OnEMPPilotHit" ) - player.EndSignal( "OnEMPPilotHit" ) - - if ( player.IsPhaseShifted() ) - return - - entity inflictor = DamageInfo_GetInflictor( damageInfo ) - local dist = Distance( DamageInfo_GetDamagePosition( damageInfo ), player.GetWorldSpaceCenter() ) - local damageRadius = 128 - if ( inflictor instanceof CBaseGrenade ) - damageRadius = inflictor.GetDamageRadius() - float frac = GraphCapped( dist, damageRadius * 0.5, damageRadius, 1.0, 0.0 ) - local strength = EMP_GRENADE_PILOT_SCREEN_EFFECTS_MIN + ( ( EMP_GRENADE_PILOT_SCREEN_EFFECTS_MAX - EMP_GRENADE_PILOT_SCREEN_EFFECTS_MIN ) * frac ) - float fadeoutDuration = EMP_GRENADE_PILOT_SCREEN_EFFECTS_FADE * frac - float duration = EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MIN + ( ( EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MAX - EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MIN ) * frac ) - fadeoutDuration - local origin = inflictor.GetOrigin() - - int dmgSource = DamageInfo_GetDamageSourceIdentifier( damageInfo ) - if ( dmgSource == eDamageSourceId.mp_weapon_proximity_mine || dmgSource == eDamageSourceId.mp_titanweapon_stun_laser ) - { - strength *= 0.1 - } - - if ( player.IsTitan() ) - { - // Hit player should do EMP screen effects locally - Remote_CallFunction_Replay( player, "ServerCallback_TitanCockpitEMP", duration ) - - EMPGrenade_AffectsShield( player, damageInfo ) - - Remote_CallFunction_Replay( player, "ServerCallback_TitanEMP", strength, duration, fadeoutDuration ) - } - else - { - if ( IsCloaked( player ) ) - player.SetCloakFlicker( 0.5, duration ) - - // duration = 0 - // fadeoutDuration = 0 - - StatusEffect_AddTimed( player, eStatusEffect.emp, strength, duration, fadeoutDuration ) - //DamageInfo_SetDamage( damageInfo, 0 ) - } - - GiveEMPStunStatusEffects( player, (duration + fadeoutDuration), fadeoutDuration) -} - -function EMPGrenade_ArcBeam( grenadePos, ent ) -{ - if ( !ent.IsPlayer() && !ent.IsNPC() ) - return - - Assert( IsValid( ent ) ) - local lifeDuration = 0.5 - - // Control point sets the end position of the effect - entity cpEnd = CreateEntity( "info_placement_helper" ) - SetTargetName( cpEnd, UniqueString( "emp_grenade_beam_cpEnd" ) ) - cpEnd.SetOrigin( grenadePos ) - DispatchSpawn( cpEnd ) - - entity zapBeam = CreateEntity( "info_particle_system" ) - zapBeam.kv.cpoint1 = cpEnd.GetTargetName() - zapBeam.SetValueForEffectNameKey( EMP_GRENADE_BEAM_EFFECT ) - zapBeam.kv.start_active = 0 - zapBeam.SetOrigin( ent.GetWorldSpaceCenter() ) - if ( !ent.IsMarkedForDeletion() ) // TODO: This is a hack for shipping. Should not be parenting to deleted entities - { - zapBeam.SetParent( ent, "", true, 0.0 ) - } - - DispatchSpawn( zapBeam ) - - zapBeam.Fire( "Start" ) - zapBeam.Fire( "StopPlayEndCap", "", lifeDuration ) - zapBeam.Kill_Deprecated_UseDestroyInstead( lifeDuration ) - cpEnd.Kill_Deprecated_UseDestroyInstead( lifeDuration ) -} - -void function GetWeaponDPS( bool vsTitan = false ) -{ - entity player = GetPlayerArray()[0] - entity weapon = player.GetActiveWeapon() - - local fire_rate = weapon.GetWeaponInfoFileKeyField( "fire_rate" ) - local burst_fire_count = weapon.GetWeaponInfoFileKeyField( "burst_fire_count" ) - local burst_fire_delay = weapon.GetWeaponInfoFileKeyField( "burst_fire_delay" ) - - local damage_near_value = weapon.GetWeaponInfoFileKeyField( "damage_near_value" ) - local damage_far_value = weapon.GetWeaponInfoFileKeyField( "damage_far_value" ) - - if ( vsTitan ) - { - damage_near_value = weapon.GetWeaponInfoFileKeyField( "damage_near_value_titanarmor" ) - damage_far_value = weapon.GetWeaponInfoFileKeyField( "damage_far_value_titanarmor" ) - } - - if ( burst_fire_count ) - { - local timePerShot = 1 / fire_rate - local timePerBurst = (timePerShot * burst_fire_count) + burst_fire_delay - local burstPerSecond = 1 / timePerBurst - - printt( timePerBurst ) - - printt( "DPS Near", (burstPerSecond * burst_fire_count) * damage_near_value ) - printt( "DPS Far ", (burstPerSecond * burst_fire_count) * damage_far_value ) - } - else - { - printt( "DPS Near", fire_rate * damage_near_value ) - printt( "DPS Far ", fire_rate * damage_far_value ) - } -} - - -void function GetTTK( string weaponRef, float health = 100.0 ) -{ - local fire_rate = GetWeaponInfoFileKeyField_Global( weaponRef, "fire_rate" ).tofloat() - local burst_fire_count = GetWeaponInfoFileKeyField_Global( weaponRef, "burst_fire_count" ) - if ( burst_fire_count != null ) - burst_fire_count = burst_fire_count.tofloat() - - local burst_fire_delay = GetWeaponInfoFileKeyField_Global( weaponRef, "burst_fire_delay" ) - if ( burst_fire_delay != null ) - burst_fire_delay = burst_fire_delay.tofloat() - - local damage_near_value = GetWeaponInfoFileKeyField_Global( weaponRef, "damage_near_value" ).tointeger() - local damage_far_value = GetWeaponInfoFileKeyField_Global( weaponRef, "damage_far_value" ).tointeger() - - local nearBodyShots = ceil( health / damage_near_value ) - 1 - local farBodyShots = ceil( health / damage_far_value ) - 1 - - local delayAdd = 0 - if ( burst_fire_count && burst_fire_count < nearBodyShots ) - delayAdd += burst_fire_delay - - printt( "TTK Near", (nearBodyShots * (1 / fire_rate)) + delayAdd, " (" + (nearBodyShots + 1) + ")" ) - - - delayAdd = 0 - if ( burst_fire_count && burst_fire_count < farBodyShots ) - delayAdd += burst_fire_delay - - printt( "TTK Far ", (farBodyShots * (1 / fire_rate)) + delayAdd, " (" + (farBodyShots + 1) + ")" ) -} - -array<string> function GetWeaponModsFromDamageInfo( var damageInfo ) -{ - entity weapon = DamageInfo_GetWeapon( damageInfo ) - entity inflictor = DamageInfo_GetInflictor( damageInfo ) - int damageType = DamageInfo_GetCustomDamageType( damageInfo ) - - if ( IsValid( weapon ) ) - { - return weapon.GetMods() - } - else if ( IsValid( inflictor ) ) - { - if ( "weaponMods" in inflictor.s && inflictor.s.weaponMods ) - { - array<string> temp - foreach ( string mod in inflictor.s.weaponMods ) - { - temp.append( mod ) - } - - return temp - } - else if( inflictor.IsProjectile() ) - return inflictor.ProjectileGetMods() - else if ( damageType & DF_EXPLOSION && inflictor.IsPlayer() && IsValid( inflictor.GetActiveWeapon() ) ) - return inflictor.GetActiveWeapon().GetMods() - //Hack - Splash damage doesn't pass mod weapon through. This only works under the assumption that offhand weapons don't have mods. - } - return [] -} - -void function OnPlayerGetsNewPilotLoadout( entity player, PilotLoadoutDef loadout ) -{ - if ( GetCurrentPlaylistVarInt( "featured_mode_amped_tacticals", 0 ) >= 1 ) - { - player.GiveExtraWeaponMod( "amped_tacticals" ) - } - - if ( GetCurrentPlaylistVarInt( "featured_mode_all_grapple", 0 ) >= 1 ) - { - player.GiveExtraWeaponMod( "all_grapple" ) - } - - if ( GetCurrentPlaylistVarInt( "featured_mode_all_phase", 0 ) >= 1 ) - { - player.GiveExtraWeaponMod( "all_phase" ) - } - - SetPlayerCooldowns( player ) -} - -void function SetPlayerCooldowns( entity player ) -{ - if ( player.IsTitan() ) - return - - array<int> offhandIndices = [ OFFHAND_LEFT, OFFHAND_RIGHT ] - - foreach ( index in offhandIndices ) - { - float lastUseTime = player.p.lastPilotOffhandUseTime[ index ] - float lastChargeFrac = player.p.lastPilotOffhandChargeFrac[ index ] - float lastClipFrac = player.p.lastPilotClipFrac[ index ] - - if ( lastUseTime >= 0.0 ) - { - entity weapon = player.GetOffhandWeapon( index ) - if ( !IsValid( weapon ) ) - continue - - string weaponClassName = weapon.GetWeaponClassName() - - switch ( GetWeaponInfoFileKeyField_Global( weaponClassName, "cooldown_type" ) ) - { - case "grapple": - // GetPlayerSettingsField isn't working for moddable fields? - Bug 129567 - float powerRequired = 100.0 // GetPlayerSettingsField( "grapple_power_required" ) - float regenRefillDelay = 3.0 // GetPlayerSettingsField( "grapple_power_regen_delay" ) - float regenRefillRate = 5.0 // GetPlayerSettingsField( "grapple_power_regen_rate" ) - float suitPowerToRestore = powerRequired - player.p.lastSuitPower - float regenRefillTime = suitPowerToRestore / regenRefillRate - - float regenStartTime = lastUseTime + regenRefillDelay - - float newSuitPower = GraphCapped( Time() - regenStartTime, 0.0, regenRefillTime, player.p.lastSuitPower, powerRequired ) - - player.SetSuitGrapplePower( newSuitPower ) - break - - case "ammo": - case "ammo_instant": - case "ammo_deployed": - case "ammo_timed": - int maxAmmo = weapon.GetWeaponPrimaryClipCountMax() - float fireDuration = weapon.GetWeaponSettingFloat( eWeaponVar.fire_duration ) - float regenRefillDelay = weapon.GetWeaponSettingFloat( eWeaponVar.regen_ammo_refill_start_delay ) - float regenRefillRate = weapon.GetWeaponSettingFloat( eWeaponVar.regen_ammo_refill_rate ) - int startingClipCount = int( lastClipFrac * maxAmmo ) - int ammoToRestore = maxAmmo - startingClipCount - float regenRefillTime = ammoToRestore / regenRefillRate - - float regenStartTime = lastUseTime + fireDuration + regenRefillDelay - - int newAmmo = int( GraphCapped( Time() - regenStartTime, 0.0, regenRefillTime, startingClipCount, maxAmmo ) ) - - weapon.SetWeaponPrimaryClipCountAbsolute( newAmmo ) - break - - case "chargeFrac": - float chargeCooldownDelay = weapon.GetWeaponSettingFloat( eWeaponVar.charge_cooldown_delay ) - float chargeCooldownTime = weapon.GetWeaponSettingFloat( eWeaponVar.charge_cooldown_time ) - float regenRefillTime = lastChargeFrac * chargeCooldownTime - float regenStartTime = lastUseTime + chargeCooldownDelay - - float newCharge = GraphCapped( Time() - regenStartTime, 0.0, regenRefillTime, lastChargeFrac, 0.0 ) - - weapon.SetWeaponChargeFraction( newCharge ) - break - - default: - printt( weaponClassName + " needs to be updated to support cooldown_type setting" ) - break - } - } - } -} - -void function ResetPlayerCooldowns( entity player ) -{ - if ( player.IsTitan() ) - return - - array<int> offhandIndices = [ OFFHAND_LEFT, OFFHAND_RIGHT ] - - foreach ( index in offhandIndices ) - { - float lastUseTime = -99.0//player.p.lastPilotOffhandUseTime[ index ] - float lastChargeFrac = -1.0//player.p.lastPilotOffhandChargeFrac[ index ] - float lastClipFrac = 1.0//player.p.lastPilotClipFrac[ index ] - - entity weapon = player.GetOffhandWeapon( index ) - if ( !IsValid( weapon ) ) - continue - - string weaponClassName = weapon.GetWeaponClassName() - - switch ( GetWeaponInfoFileKeyField_Global( weaponClassName, "cooldown_type" ) ) - { - case "grapple": - // GetPlayerSettingsField isn't working for moddable fields? - Bug 129567 - float powerRequired = 100.0 // GetPlayerSettingsField( "grapple_power_required" ) - player.SetSuitGrapplePower( powerRequired ) - break - - case "ammo": - case "ammo_instant": - case "ammo_deployed": - case "ammo_timed": - int maxAmmo = weapon.GetWeaponPrimaryClipCountMax() - weapon.SetWeaponPrimaryClipCountAbsolute( maxAmmo ) - break - - case "chargeFrac": - weapon.SetWeaponChargeFraction( 1.0 ) - break - - default: - printt( weaponClassName + " needs to be updated to support cooldown_type setting" ) - break - } - } -} - -void function OnPlayerKilled( entity player, entity attacker, var damageInfo ) -{ - StoreOffhandData( player ) -} - -void function StoreOffhandData( entity player, bool waitEndFrame = true ) -{ - thread StoreOffhandDataThread( player, waitEndFrame ) -} - -void function StoreOffhandDataThread( entity player, bool waitEndFrame ) -{ - if ( !IsValid( player ) ) - return - - player.EndSignal( "OnDestroy" ) - - if ( waitEndFrame ) - WaitEndFrame() // Need to WaitEndFrame so clip counts can be updated if player is dying the same frame - - array<int> offhandIndices = [ OFFHAND_LEFT, OFFHAND_RIGHT ] - - // Reset all values for full cooldown - player.p.lastSuitPower = 0.0 - - foreach ( index in offhandIndices ) - { - player.p.lastPilotOffhandChargeFrac[ index ] = 1.0 - player.p.lastPilotClipFrac[ index ] = 1.0 - - player.p.lastTitanOffhandChargeFrac[ index ] = 1.0 - player.p.lastTitanClipFrac[ index ] = 1.0 - } - - if ( player.IsTitan() ) - return - - foreach ( index in offhandIndices ) - { - entity weapon = player.GetOffhandWeapon( index ) - if ( !IsValid( weapon ) ) - continue - - string weaponClassName = weapon.GetWeaponClassName() - - switch ( GetWeaponInfoFileKeyField_Global( weaponClassName, "cooldown_type" ) ) - { - case "grapple": - player.p.lastSuitPower = player.GetSuitGrapplePower() - break - - case "ammo": - case "ammo_instant": - case "ammo_deployed": - case "ammo_timed": - - if ( player.IsTitan() ) - { - if ( !weapon.IsWeaponRegenDraining() ) - player.p.lastTitanClipFrac[ index ] = min( 1.0, weapon.GetWeaponPrimaryClipCount() / float( weapon.GetWeaponPrimaryClipCountMax() ) ) //Was returning greater than one with extraweaponmod timing. - else - player.p.lastTitanClipFrac[ index ] = 0.0 - } - else - { - if ( !weapon.IsWeaponRegenDraining() ) - player.p.lastPilotClipFrac[ index ] = min( 1.0, weapon.GetWeaponPrimaryClipCount() / float( weapon.GetWeaponPrimaryClipCountMax() ) ) //Was returning greater than one with extraweaponmod timing. - else - player.p.lastPilotClipFrac[ index ] = 0.0 - } - break - - case "chargeFrac": - if ( player.IsTitan() ) - player.p.lastTitanOffhandChargeFrac[ index ] = weapon.GetWeaponChargeFraction() - else - player.p.lastPilotOffhandChargeFrac[ index ] = weapon.GetWeaponChargeFraction() - break - - default: - printt( weaponClassName + " needs to be updated to support cooldown_type setting" ) - break - } - } -} -#endif // #if SERVER - -void function PlayerUsedOffhand( entity player, entity offhandWeapon ) -{ - array<int> offhandIndices = [ OFFHAND_LEFT, OFFHAND_RIGHT, OFFHAND_ANTIRODEO, OFFHAND_EQUIPMENT ] - - foreach ( index in offhandIndices ) - { - entity weapon = player.GetOffhandWeapon( index ) - if ( !IsValid( weapon ) ) - continue - - if ( weapon != offhandWeapon ) - continue - - #if SERVER - if ( player.IsTitan() ) - player.p.lastTitanOffhandUseTime[ index ] = Time() - else - player.p.lastPilotOffhandUseTime[ index ] = Time() - - #if MP - string weaponName = offhandWeapon.GetWeaponClassName() - if ( weaponName != "mp_ability_grapple" ) // handled in CodeCallback_OnGrapple // nope, it's not (?) - { - string category - float duration - if ( index == OFFHAND_EQUIPMENT && player.IsTitan() ) - { - category = "core" - duration = -1 - } - else - { - category = "" - duration = Time() - offhandWeapon.GetNextAttackAllowedTimeRaw() - } - PIN_PlayerAbility( player, category, weaponName, {}, duration ) - } - #endif - #endif // SERVER - - #if HAS_TITAN_TELEMETRY && CLIENT - ClTitanHints_ClearOffhandHint( index ) - #endif - - #if HAS_TITAN_TELEMETRY && SERVER - TitanHints_NotifyUsedOffhand( index ) - #endif - - return - } -} - -RadiusDamageData function GetRadiusDamageDataFromProjectile( entity projectile, entity owner ) -{ - RadiusDamageData radiusDamageData - - radiusDamageData.explosionDamage = -1 - radiusDamageData.explosionDamageHeavyArmor = -1 - - if ( owner.IsNPC() ) - { - radiusDamageData.explosionDamage = projectile.GetProjectileWeaponSettingInt( eWeaponVar.npc_explosion_damage ) - radiusDamageData.explosionDamageHeavyArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.npc_explosion_damage_heavy_armor ) - } - - if ( radiusDamageData.explosionDamage == -1 ) - radiusDamageData.explosionDamage = projectile.GetProjectileWeaponSettingInt( eWeaponVar.explosion_damage ) - - if ( radiusDamageData.explosionDamageHeavyArmor == -1 ) - radiusDamageData.explosionDamageHeavyArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.explosion_damage_heavy_armor ) - - radiusDamageData.explosionRadius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosionradius ) - radiusDamageData.explosionInnerRadius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosion_inner_radius ) - - Assert( radiusDamageData.explosionRadius > 0, "Created RadiusDamageData with 0 radius" ) - Assert( radiusDamageData.explosionDamage > 0 || radiusDamageData.explosionDamageHeavyArmor > 0, "Created RadiusDamageData with 0 damage" ) - return radiusDamageData -} - -#if SERVER -void function Thermite_DamagePlayerOrNPCSounds( entity ent ) -{ - if ( ent.IsTitan() ) - { - if ( ent.IsPlayer() ) - { - EmitSoundOnEntityOnlyToPlayer( ent, ent, "titan_thermiteburn_3p_vs_1p" ) - EmitSoundOnEntityExceptToPlayer( ent, ent, "titan_thermiteburn_1p_vs_3p" ) - } - else - { - EmitSoundOnEntity( ent, "titan_thermiteburn_1p_vs_3p" ) - } - } - else - { - if ( ent.IsPlayer() ) - { - EmitSoundOnEntityOnlyToPlayer( ent, ent, "flesh_thermiteburn_3p_vs_1p" ) - EmitSoundOnEntityExceptToPlayer( ent, ent, "flesh_thermiteburn_1p_vs_3p" ) - } - else - { - EmitSoundOnEntity( ent, "flesh_thermiteburn_1p_vs_3p" ) - } - } -} -#endif - -#if SERVER -void function RemoveThreatScopeColorStatusEffect( entity player ) -{ - for ( int i = file.colorSwapStatusEffects.len() - 1; i >= 0; i-- ) - { - entity owner = file.colorSwapStatusEffects[i].weaponOwner - if ( !IsValid( owner ) ) - { - file.colorSwapStatusEffects.remove( i ) - continue - } - if ( owner == player ) - { - StatusEffect_Stop( player, file.colorSwapStatusEffects[i].statusEffectId ) - file.colorSwapStatusEffects.remove( i ) - } - } -} - -void function AddThreatScopeColorStatusEffect( entity player ) -{ - ColorSwapStruct info - info.weaponOwner = player - info.statusEffectId = StatusEffect_AddTimed( player, eStatusEffect.cockpitColor, COCKPIT_COLOR_THREAT, 100000, 0 ) - file.colorSwapStatusEffects.append( info ) -} -#endif |