//============================================================================= // KFWeap_AutoTurret //============================================================================= //============================================================================= // Killing Floor 2 // Copyright (C) 2022 Tripwire Interactive LLC //============================================================================= class KFWeap_AutoTurret extends KFWeap_ThrownBase; `define AUTOTURRET_MIC_LED_INDEX 2 const DETONATE_FIREMODE = 5; // NEW - IronSights Key var(Animations) const editconst name DetonateAnim; var(Animations) const editconst name DetonateLastAnim; /** Sound to play upon successful detonation */ var() AkEvent DetonateAkEvent; /** Strenght applied to forward dir to get the throwing velocity */ var const float ThrowStrength; /** Max num of turrets that can be deployed */ var const byte MaxTurretsDeployed; /** Offset to spawn the dron (screen coordinates) */ var const vector TurretSpawnOffset; var transient byte NumDeployedTurrets; var transient KFPlayerController KFPC; /** If the turret is in a state available for throw another to fix some animation issues. */ var transient bool bTurretReadyToUse; var repnotify float CurrentAmmoPercentage; const TransitionParamName = 'transition_full_to_empty'; const EmptyParamName = 'Blinking_0_off___1_on'; var transient bool bDetonateLocked; replication { if( bNetDirty ) NumDeployedTurrets, CurrentAmmoPercentage, bTurretReadyToUse; } simulated event ReplicatedEvent(name VarName) { if (VarName == nameof(CurrentAmmoPercentage)) { UpdateMaterialColor(CurrentAmmoPercentage); } else { Super.ReplicatedEvent(VarName); } } simulated event PreBeginPlay() { local class WeaponClass; super.PreBeginPlay(); WeaponClass = class (DynamicLoadObject(class'KFPawn_AutoTurret'.default.WeaponDefinition.default.WeaponClassPath, class'Class')); WeaponClass.static.TriggerAsyncContentLoad(WeaponClass); } simulated event PostBeginPlay() { super.PostBeginPlay(); if (Role == ROLE_Authority) { KFPC = KFPlayerController(Instigator.Controller); NumDeployedTurrets = KFPC.DeployedTurrets.Length; } } /** Route ironsight player input to detonate */ simulated function SetIronSights(bool bNewIronSights) { if ( !Instigator.IsLocallyControlled() ) { return; } if ( bNewIronSights ) { StartFire(DETONATE_FIREMODE); } else { StopFire(DETONATE_FIREMODE); } } /** Overridded to add spawned turret to list of spawned turrets */ simulated function Projectile ProjectileFire() { local vector SpawnLocation, SpawnDirection; local KFPawn_AutoTurret SpawnedActor; if (Role == ROLE_Authority && CurrentFireMode == DEFAULT_FIREMODE) { GetTurretSpawnLocationAndDir(SpawnLocation, SpawnDirection); SpawnedActor = Spawn(class'KFPawn_AutoTurret', self,, SpawnLocation + (TurretSpawnOffset >> Rotation), Rotation,,true); if( SpawnedActor != none ) { SpawnedActor.OwnerWeapon = self; SpawnedActor.SetPhysics(PHYS_Falling); SpawnedActor.Velocity = SpawnDirection * ThrowStrength; SpawnedActor.UpdateInstigator(Instigator); SpawnedActor.UpdateWeaponUpgrade(CurrentWeaponUpgradeIndex); SpawnedActor.SetTurretState(ETS_Throw); KFPC.DeployedTurrets.AddItem( SpawnedActor ); NumDeployedTurrets = KFPC.DeployedTurrets.Length; bTurretReadyToUse = false; bForceNetUpdate = true; } return none; } else { return super.ProjectileFire(); } return none; } simulated function GetTurretSpawnLocationAndDir(out vector SpawnLocation, out vector SpawnDirection) { local vector StartTrace, EndTrace, RealStartLoc, AimDir; local ImpactInfo TestImpact; local vector DirA, DirB; local Quat Q; // This is where we would start an instant trace. (what CalcWeaponFire uses) StartTrace = GetSafeStartTraceLocation(); AimDir = Vector(GetAdjustedAim( StartTrace )); // this is the location where the projectile is spawned. RealStartLoc = GetPhysicalFireStartLoc(AimDir); if( StartTrace != RealStartLoc ) { // if projectile is spawned at different location of crosshair, // then simulate an instant trace where crosshair is aiming at, Get hit info. EndTrace = StartTrace + AimDir * GetTraceRange(); TestImpact = CalcWeaponFire( StartTrace, EndTrace ); // Store the original aim direction without correction DirB = AimDir; // Then we realign projectile aim direction to match where the crosshair did hit. AimDir = Normal(TestImpact.HitLocation - RealStartLoc); // Store the desired corrected aim direction DirA = AimDir; // Clamp the maximum aim adjustment for the AimDir so you don't get wierd // cases where the projectiles velocity is going WAY off of where you // are aiming. This can happen if you are really close to what you are // shooting - Ramm if ( (DirA dot DirB) < MaxAimAdjust_Cos ) { Q = QuatFromAxisAndAngle(Normal(DirB cross DirA), MaxAimAdjust_Angle); AimDir = QuatRotateVector(Q,DirB); } } SpawnDirection = AimDir; SpawnLocation = RealStartLoc; } /** Detonates the oldest turret */ simulated function Detonate(optional bool bKeepTurret = false) { local int i; local array TurretsCopy; // auto switch weapon when out of ammo and after detonating the last deployed turret if( Role == ROLE_Authority ) { TurretsCopy = KFPC.DeployedTurrets; for (i = 0; i < TurretsCopy.Length; i++) { if (bKeepTurret && i == 0) { continue; } KFPawn_AutoTurret(TurretsCopy[i]).SetTurretState(ETS_Detonate); } KFPC.DeployedTurrets.Remove(bKeepTurret ? 1 : 0, KFPC.DeployedTurrets.Length); SetReadyToUse(true); if( !HasAnyAmmo() && NumDeployedTurrets == 0 ) { if( CanSwitchWeapons() ) { Instigator.Controller.ClientSwitchToBestWeapon(false); } } } } /** Removes a turret from the list using either an index or an actor and updates NumDeployedTurrets */ function RemoveDeployedTurret( optional int Index = INDEX_NONE, optional Actor TurretActor ) { if( Index == INDEX_NONE ) { if( TurretActor != none ) { Index = KFPC.DeployedTurrets.Find( TurretActor ); } } if( Index != INDEX_NONE ) { KFPC.DeployedTurrets.Remove( Index, 1 ); NumDeployedTurrets = KFPC.DeployedTurrets.Length; bForceNetUpdate = true; } } function SetOriginalValuesFromPickup( KFWeapon PickedUpWeapon ) { local int i; super.SetOriginalValuesFromPickup( PickedUpWeapon ); if (PickedUpWeapon.KFPlayer != none && PickedUpWeapon.KFPlayer != KFPC) { for (i = 0; i < PickedUpWeapon.KFPlayer.DeployedTurrets.Length; i++) { KFPC.DeployedTurrets.AddItem(PickedUpWeapon.KFPlayer.DeployedTurrets[i]); } PickedUpWeapon.KFPlayer.DeployedTurrets.Remove(0, PickedUpWeapon.KFPlayer.DeployedTurrets.Length); } if (KFPC.DeployedTurrets.Length > 1) { Detonate(true); } PickedUpWeapon.KFPlayer = none; NumDeployedTurrets = KFPC.DeployedTurrets.Length; bForceNetUpdate = true; for( i = 0; i < NumDeployedTurrets; ++i ) { // charge alerts (beep, light) need current instigator KFPC.DeployedTurrets[i].Instigator = Instigator; KFPC.DeployedTurrets[i].SetOwner(self); if( Instigator.Controller != none ) { KFPawn_AutoTurret(KFPC.DeployedTurrets[i]).InstigatorController = Instigator.Controller; } } } /** * Drop this item out in to the world */ function DropFrom(vector StartLocation, vector StartVelocity) { local DroppedPickup P; // Offset spawn closer to eye location StartLocation.Z += Instigator.BaseEyeHeight / 2; // for some reason, Inventory::DropFrom removes weapon from inventory whether it was able to spawn the pickup or not. // we only want the weapon removed from inventory if pickup was successfully spawned, so instead of calling the supers, // do all the super functionality here. if( !CanThrow() ) { return; } if( DroppedPickupClass == None || DroppedPickupMesh == None ) { Destroy(); return; } // the last bool param is to prevent collision from preventing spawns P = Spawn(DroppedPickupClass,,, StartLocation,,,true); if( P == None ) { // if we can't spawn the pickup (likely for collision reasons), // just return without removing from inventory or destroying, which removes from inventory PlayerController(Instigator.Controller).ReceiveLocalizedMessage( class'KFLocalMessage_Game', GMT_FailedDropInventory ); return; } if( Instigator != None && Instigator.InvManager != None ) { Instigator.InvManager.RemoveFromInventory(Self); if( Instigator.IsAliveAndWell() && !Instigator.InvManager.bPendingDelete ) { `DialogManager.PlayDropWeaponDialog( KFPawn(Instigator) ); } } SetupDroppedPickup( P, StartVelocity ); KFDroppedPickup(P).PreviousOwner = KFPlayerController(Instigator.Controller); Instigator = None; GotoState(''); AIController = None; } /** * Returns true if this weapon uses a secondary ammo pool */ static simulated event bool UsesAmmo() { return true; } simulated function bool HasAmmo( byte FireModeNum, optional int Amount ) { if( FireModeNum == DETONATE_FIREMODE ) { return NumDeployedTurrets > 0; } return super.HasAmmo( FireModeNum, Amount ); } simulated function BeginFire( byte FireModeNum ) { // Clear any pending detonate if we pressed the main fire // That prevents strange holding right click behaviour and sound issues if (FireModeNum == DEFAULT_FIREMODE) { ClearPendingFire(DETONATE_FIREMODE); } if (FireModeNum == DETONATE_FIREMODE ) { if (bDetonateLocked) { return; } if (NumDeployedTurrets > 0 && bTurretReadyToUse) { PrepareAndDetonate(); } } else { if (FireModeNum == DEFAULT_FIREMODE && NumDeployedTurrets >= MaxTurretsDeployed && HasAnyAmmo()) { if (!bTurretReadyToUse) { return; } PrepareAndDetonate(); } super.BeginFire( FireModeNum ); } } simulated function PrepareAndDetonate() { local name DetonateAnimName; local float AnimDuration; local bool bInSprintState; DetonateAnimName = ShouldPlayLastAnims() ? DetonateLastAnim : DetonateAnim; AnimDuration = MySkelMesh.GetAnimLength( DetonateAnimName ); bInSprintState = IsInState( 'WeaponSprinting' ); if( WorldInfo.NetMode != NM_DedicatedServer ) { if( NumDeployedTurrets > 0 ) { PlaySoundBase( DetonateAkEvent, true ); } if( bInSprintState ) { AnimDuration *= 0.25f; PlayAnimation( DetonateAnimName, AnimDuration ); } else { PlayAnimation( DetonateAnimName ); } } if( Role == ROLE_Authority ) { Detonate(); } IncrementFlashCount(); if( bInSprintState ) { SetTimer( AnimDuration * 0.8f, false, nameof(PlaySprintStart) ); } else { SetTimer( AnimDuration * 0.5f, false, nameof(GotoActiveState) ); } } // do nothing, as we have no alt fire mode simulated function AltFireMode(); /** Allow weapons with abnormal state transitions to always use zed time resist*/ simulated function bool HasAlwaysOnZedTimeResist() { return true; } /********************************************************************************************* * State Active * A Weapon this is being held by a pawn should be in the active state. In this state, * a weapon should loop any number of idle animations, as well as check the PendingFire flags * to see if a shot has been fired. *********************************************************************************************/ simulated state Active { /** Overridden to prevent playing fidget if play has no more ammo */ simulated function bool CanPlayIdleFidget(optional bool bOnReload) { if( !HasAmmo(0) ) { return false; } return super.CanPlayIdleFidget( bOnReload ); } } /********************************************************************************************* * State WeaponDetonating * The weapon is in this state while detonating a charge *********************************************************************************************/ simulated function GotoActiveState(); simulated state WeaponDetonating { ignores AllowSprinting; simulated event BeginState( name PreviousStateName ) { PrepareAndDetonate(); } simulated function GotoActiveState() { GotoState('Active'); } } /********************************************************************************************* * State WeaponThrowing * Handles throwing weapon (similar to state GrenadeFiring) *********************************************************************************************/ simulated state WeaponThrowing { /** Never refires. Must re-enter this state instead. */ simulated function bool ShouldRefire() { return false; } simulated function EndState(Name NextStateName) { local KFPerk InstigatorPerk; Super.EndState(NextStateName); //Targeted fix for Demolitionist w/ the C4. It should remain in zed time while waiting on // the fake reload to be triggered. This will return 0 for other perks. InstigatorPerk = GetPerk(); if( InstigatorPerk != none ) { SetZedTimeResist( InstigatorPerk.GetZedTimeModifier(self) ); } } } /********************************************************************************************* * State WeaponEquipping * The Weapon is in this state while transitioning from Inactive to Active state. * Typically, the weapon will remain in this state while its selection animation is being played. * While in this state, the weapon cannot be fired. *********************************************************************************************/ simulated state WeaponEquipping { simulated event BeginState( name PreviousStateName ) { super.BeginState( PreviousStateName ); // perform a "reload" if we refilled our ammo from empty while it was unequipped if( !HasAmmo(THROW_FIREMODE) && HasSpareAmmo() ) { PerformArtificialReload(); } StopFire(DETONATE_FIREMODE); } } /********************************************************************************************* * State WeaponPuttingDown * Putting down weapon in favor of a new one. * Weapon is transitioning to the Inactive state. * * Detonating while putting down causes C4 not to be put down, which causes problems, so let's * just ignore SetIronSights, which causes detonation *********************************************************************************************/ simulated state WeaponPuttingDown { ignores SetIronSights; simulated event BeginState( name PreviousStateName ) { super.BeginState( PreviousStateName ); StopFire(DETONATE_FIREMODE); } } /********************************************************************************************* * @name Trader *********************************************************************************************/ /** Returns trader filter index based on weapon type */ static simulated event EFilterTypeUI GetTraderFilter() { return FT_Explosive; } function CheckTurretAmmo() { local float Percentage; local KFWeapon Weapon; local KFPawn KFP; if (Role == Role_Authority) { if (KFPC == none) { return; } if (KFPC.DeployedTurrets.Length > 0) { Weapon = KFWeapon(KFPawn_AutoTurret(KFPC.DeployedTurrets[0]).Weapon); if (Weapon != none) { Percentage = float(Weapon.AmmoCount[0]) / Weapon.MagazineCapacity[0]; if (Percentage != CurrentAmmoPercentage) { CurrentAmmoPercentage = Percentage; bNetDirty = true; if (WorldInfo.NetMode == NM_Standalone) { UpdateMaterialColor(CurrentAmmoPercentage); } else { KFP = KFPawn(Instigator); if (KFP != none) { KFP.OnWeaponSpecialAction( 1 + (CurrentAmmoPercentage * 100) ); } } } } } } } function SetReadyToUse(bool bReady) { if (bTurretReadyToUse != bReady) { bTurretReadyToUse = bReady; bNetDirty = true; } } simulated event Tick(float DeltaTime) { super.Tick(DeltaTime); if (Role == Role_Authority) { CheckTurretAmmo(); } } simulated function UpdateMaterialColor(float Percentage) { if (NumDeployedTurrets == 0) { WeaponMICs[`AUTOTURRET_MIC_LED_INDEX].SetScalarParameterValue(EmptyParamName, 0); } else if (Percentage >= 0) { WeaponMICs[`AUTOTURRET_MIC_LED_INDEX].SetScalarParameterValue(TransitionParamName, 1.0f - Percentage); WeaponMICs[`AUTOTURRET_MIC_LED_INDEX].SetScalarParameterValue(EmptyParamName, Percentage == 0 ? 1 : 0); } } simulated function SetWeaponUpgradeLevel(int WeaponUpgradeLevel) { local Actor Turret; local KFPawn_AutoTurret TurretPawn; super.SetWeaponUpgradeLevel(WeaponUpgradeLevel); if (KFPC != none) { foreach KFPC.DeployedTurrets(Turret) { TurretPawn = KFPawn_AutoTurret(Turret); if (TurretPawn != none) { TurretPawn.UpdateWeaponUpgrade(WeaponUpgradeLevel); } } } } /** * GRENADE FIRING * There's a bug that alt fire interrupts the grenade anim at any moment, * This avoids being able to altfire until the throw animation ends or the * interrupt notify is reached. */ simulated state GrenadeFiring { simulated function EndState(Name NextStateName) { ClearDetonateLock(); Super.EndState(NextStateName); } } /** Play animation at the start of the GrenadeFiring state */ simulated function PlayGrenadeThrow() { local name WeaponFireAnimName; local float InterruptTime; PlayFiringSound(CurrentFireMode); if( Instigator != none && Instigator.IsFirstPerson() ) { WeaponFireAnimName = GetGrenadeThrowAnim(); if ( WeaponFireAnimName != '' ) { InterruptTime = MySkelMesh.GetAnimInterruptTime(WeaponFireAnimName); PlayAnimation(WeaponFireAnimName, MySkelMesh.GetAnimLength(WeaponFireAnimName),,FireTweenTime); bDetonateLocked = true; SetTimer(InterruptTime, false, nameof(ClearDetonateLock)); } } } simulated function ClearDetonateLock() { bDetonateLocked = false; ClearTimer(nameof(ClearDetonateLock)); } /***/ /////////////////////////////////////////////////////////////////////////////////////////// // // Trader // /////////////////////////////////////////////////////////////////////////////////////////// /** Allows weapon to calculate its own stats for display in trader */ static simulated event SetTraderWeaponStats( out array WeaponStats ) { super.SetTraderWeaponStats(WeaponStats); WeaponStats.Length = 4; WeaponStats[0].StatType = TWS_Damage; WeaponStats[0].StatValue = class'KFWeap_AutoTurretWeapon'.static.CalculateTraderWeaponStatDamage(); WeaponStats[1].StatType = TWS_Penetration; WeaponStats[1].StatValue = class'KFWeap_AutoTurretWeapon'.default.PenetrationPower[DEFAULT_FIREMODE]; WeaponStats[2].StatType = TWS_Range; // This is now set in native since EffectiveRange has been moved to KFWeaponDefinition // WeaponStats[2].StatValue = CalculateTraderWeaponStatRange(); WeaponStats[3].StatType = TWS_RateOfFire; WeaponStats[3].StatValue = class'KFWeap_AutoTurretWeapon'.static.CalculateTraderWeaponStatFireRate(); } defaultproperties { // start in detonate mode so that an attempt to detonate before any charges are thrown results in // the proper third-person anim CurrentFireMode=DETONATE_FIREMODE // Zooming/Position PlayerViewOffset=(X=6.0,Y=2,Z=-4) FireOffset=(X=0,Y=0) TurretSpawnOffset=(X=0, Y=15, Z=-50) // Content PackageKey="AutoTurret" FirstPersonMeshName="Wep_1P_AutoTurret_MESH.Wep_1stP_AutoTurret_Rig" FirstPersonAnimSetNames(0)="Wep_1P_AutoTurret_ANIM.Wep_1P_AutoTurret_ANIM" PickupMeshName="wep_3p_autoturret_mesh.Wep_AutoTurret_Pickup" AttachmentArchetypeName="WEP_AutoTurret_ARCH.Wep_AutoTurret_3P" // Anim FireAnim=C4_Throw FireLastAnim=C4_Throw_Last DetonateAnim=Detonate DetonateLastAnim=Detonate_Last // Ammo MagazineCapacity[0]=1 SpareAmmoCapacity[0]=3 InitialSpareMags[0]=1 AmmoPickupScale[0]=1.0 // THROW_FIREMODE FireInterval(THROW_FIREMODE)=0.25 FireModeIconPaths(THROW_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_Drone' // DETONATE_FIREMODE FiringStatesArray(DETONATE_FIREMODE)=WeaponDetonating WeaponFireTypes(DETONATE_FIREMODE)=EWFT_Custom AmmoCost(DETONATE_FIREMODE)=0 // BASH_FIREMODE InstantHitDamageTypes(BASH_FIREMODE)=class'KFDT_Bludgeon_AutoTurret' InstantHitDamage(BASH_FIREMODE)=26 // Inventory / Grouping InventoryGroup=IG_Equipment GroupPriority=11 WeaponSelectTexture=Texture2D'WEP_UI_AutoTurret_TEX.UI_WeaponSelect_AutoTurret' InventorySize=3 DetonateAkEvent=AkEvent'ww_wep_autoturret.Play_WEP_AutoTurret_Detonate_Trigger' // Weapon Upgrade stat boosts //WeaponUpgrades[1]=(IncrementDamage=1.05f,IncrementWeight=1) //WeaponUpgrades[2]=(IncrementDamage=1.1f,IncrementWeight=2) //WeaponUpgrades[3]=(IncrementDamage=1.15f,IncrementWeight=3) AssociatedPerkClasses(0)=class'KFPerk_Commando' MaxTurretsDeployed=1 NumDeployedTurrets=0 ThrowStrength=1350.0f bTurretReadyToUse=true WeaponUpgrades[1]=(Stats=((Stat=EWUS_Damage0, Scale=1.15f), (Stat=EWUS_Damage1, Scale=1.15f), (Stat=EWUS_Weight, Add=1))) WeaponUpgrades[2]=(Stats=((Stat=EWUS_Damage0, Scale=1.3f), (Stat=EWUS_Damage1, Scale=1.3f), (Stat=EWUS_Weight, Add=2))) NumBloodMapMaterials=3 bDetonateLocked=false }