1167 lines
32 KiB
Ucode
1167 lines
32 KiB
Ucode
//=============================================================================
|
|
// KFPawn_AutoTurret
|
|
//=============================================================================
|
|
// Base pawn class for autoturret
|
|
//=============================================================================
|
|
// 2022!
|
|
//=============================================================================
|
|
class KFPawn_AutoTurret extends KFPawn
|
|
notplaceable;
|
|
|
|
`define AUTOTURRET_MIC_INDEX 0
|
|
|
|
enum ETurretState
|
|
{
|
|
ETS_None,
|
|
ETS_Throw,
|
|
ETS_Deploy,
|
|
ETS_TargetSearch,
|
|
ETS_Combat,
|
|
ETS_Detonate,
|
|
ETS_Empty
|
|
};
|
|
|
|
var SkeletalMeshComponent TurretMesh;
|
|
var Controller InstigatorController;
|
|
|
|
/** Speed to rise the drone in Z axis after thrown */
|
|
var const float DeployZSpeed;
|
|
/** Height (Z axis) the drone should position for deployment */
|
|
var const float DeployHeight;
|
|
/** Radius to detect enemies */
|
|
var const float EffectiveRadius;
|
|
/** Radius to trigger explosion */
|
|
var const float ExplosiveRadius;
|
|
/** Rotation Vel for aiming targets */
|
|
var const rotator AimRotationVel;
|
|
/** Rotation Vel for aiming targets */
|
|
var const rotator CombatRotationVel;
|
|
/** Time before refreshing targets */
|
|
var const float TimeBeforeRefreshingTargets;
|
|
/** Time to idle after rotating to a random position (search) */
|
|
var const float TimeIdlingWhenSearching;
|
|
/** Min Value to apply randomness to idling */
|
|
var const float MinIdlingVariation;
|
|
/** Max Value to apply randomness to idling */
|
|
var const float MaxIdlingVariation;
|
|
/** Pitch requested when out of ammo */
|
|
var const int EmptyPitch;
|
|
/** Turret Explosion */
|
|
var GameExplosion ExplosionTemplate;
|
|
/** Weapon the turret carries */
|
|
var class<KFWeaponDefinition> WeaponDefinition;
|
|
/** WIf explodes when an enemy gets nearby */
|
|
var const bool bCanDetonateOnProximityWithAmmo;
|
|
|
|
var repnotify ETurretState CurrentState;
|
|
var repnotify rotator ReplicatedRotation;
|
|
var repnotify float CurrentAmmoPercentage;
|
|
var repnotify AKEvent TurretWeaponAmbientSound;
|
|
var repnotify int WeaponSkinID;
|
|
var repnotify int AutoTurretFlashCount;
|
|
|
|
var transient rotator DeployRotation;
|
|
|
|
/** Rotation */
|
|
var transient rotator RotationStart;
|
|
var transient rotator TargetRotation;
|
|
var transient float RotationAlpha;
|
|
var transient float RotationTime;
|
|
var transient bool bRotating;
|
|
|
|
var transient KFWeap_AutoTurretWeapon TurretWeapon;
|
|
var transient KFWeapAttach_AutoTurretWeap TurretWeaponAttachment;
|
|
|
|
var transient KFWeap_Autoturret OwnerWeapon;
|
|
|
|
var bool deployUsingOffsetFromPlayerLocation;
|
|
var float deployUsingOffsetZ;
|
|
|
|
var transient vector ThrowInstigatorLocation;
|
|
var transient vector GroundLocation;
|
|
var transient vector DeployedLocation;
|
|
var transient float RandomSearchLocation;
|
|
var transient KFPawn_Monster EnemyTarget;
|
|
var transient bool bHasSearchRandomLocation;
|
|
var transient byte TeamNum;
|
|
var transient bool bRotatingByTime;
|
|
var transient MaterialInstanceConstant TurretAttachMIC;
|
|
|
|
/** Socket name to attach weapon */
|
|
const WeaponSocketName = 'WeaponSocket';
|
|
const WeaponAttachmentSocketName = 'WeaponAttachment';
|
|
|
|
const TransitionParamName = 'transition_full_to_empty';
|
|
const EmptyParamName = 'Blinking_0_off___1_on';
|
|
|
|
const DroneFlyingStartAudio = AkEvent'WW_WEP_Autoturret.Play_WEP_AutoTurret_IdleFly';
|
|
const DroneFlyingStopAudio = AkEvent'WW_WEP_Autoturret.Stop_WEP_AutoTurret_IdleFly';
|
|
|
|
var AkComponent FlyAkComponent;
|
|
|
|
const DroneDryFire = AkEvent'WW_WEP_Autoturret.Play_WEP_AutoTurret_Dron_Dry_Fire';
|
|
|
|
const NoAmmoSocketName = 'malfunction';
|
|
const NoAmmoFXTemplate = ParticleSystem'WEP_AutoTurret_EMIT.FX_NoAmmo_Sparks';
|
|
var transient ParticleSystemComponent NoAmmoFX;
|
|
|
|
replication
|
|
{
|
|
if( bNetDirty )
|
|
CurrentState, ReplicatedRotation, CurrentAmmoPercentage, TurretWeaponAmbientSound, EnemyTarget, WeaponSkinID, AutoTurretFlashCount;
|
|
}
|
|
|
|
simulated event ReplicatedEvent(name VarName)
|
|
{
|
|
if( VarName == nameof(CurrentState) )
|
|
{
|
|
ChangeState(CurrentState);
|
|
}
|
|
else if (VarName == nameof(ReplicatedRotation))
|
|
{
|
|
RotateBySpeed(ReplicatedRotation);
|
|
}
|
|
else if (VarName == nameof(CurrentAmmoPercentage))
|
|
{
|
|
UpdateTurretMeshMaterialColor(CurrentAmmoPercentage);
|
|
}
|
|
else if (VarName == nameof(TurretWeaponAmbientSound))
|
|
{
|
|
SetWeaponAmbientSound(TurretWeaponAmbientSound);
|
|
}
|
|
else if (VarName == nameof(WeaponSkinID))
|
|
{
|
|
SetWeaponSkin(WeaponSkinID);
|
|
}
|
|
else if (VarName == nameof(AutoTurretFlashCount))
|
|
{
|
|
FlashCountUpdated(Weapon, AutoTurretFlashCount, TRUE);
|
|
}
|
|
else if (VarName == nameof(FlashCount))
|
|
{
|
|
// Intercept Flash Count: do nothing
|
|
}
|
|
else
|
|
{
|
|
super.ReplicatedEvent(VarName);
|
|
}
|
|
}
|
|
|
|
simulated event PreBeginPlay()
|
|
{
|
|
local class<KFWeapon> WeaponClass;
|
|
local rotator ZeroRotator;
|
|
|
|
super.PreBeginPlay();
|
|
|
|
WeaponClass = class<KFWeap_AutoTurretWeapon> (DynamicLoadObject(WeaponDefinition.default.WeaponClassPath, class'Class'));
|
|
WeaponClassForAttachmentTemplate = WeaponClass;
|
|
|
|
SetMeshVisibility(false);
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
Weapon = Spawn(WeaponClass, self);
|
|
TurretWeapon = KFWeap_AutoTurretWeapon(Weapon);
|
|
MyKFWeapon = TurretWeapon;
|
|
|
|
if (Weapon != none)
|
|
{
|
|
Weapon.bReplicateInstigator=true;
|
|
Weapon.bReplicateMovement=true;
|
|
Weapon.Instigator = Instigator;
|
|
TurretWeapon.InstigatorDrone = self;
|
|
Weapon.SetCollision(false, false);
|
|
Weapon.SetBase(self,, TurretMesh, WeaponSocketName);
|
|
TurretMesh.AttachComponentToSocket(Weapon.Mesh, WeaponSocketName);
|
|
|
|
Weapon.SetRelativeLocation(vect(0,0,0));
|
|
Weapon.SetRelativeRotation(ZeroRotator);
|
|
Weapon.Mesh.SetOnlyOwnerSee(true);
|
|
MyKFWeapon.Mesh.SetHidden(true);
|
|
}
|
|
}
|
|
if (WorldInfo.NetMode == NM_Client || WorldInfo.NetMode == NM_Standalone)
|
|
{
|
|
/** If this fails, the AutoTurret is still loading from KFWeap_Autoturret. */
|
|
if (WeaponClass.default.AttachmentArchetype != none)
|
|
{
|
|
SetTurretWeaponAttachment(WeaponClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function SetTurretWeaponAttachment(class<KFWeapon> WeaponClass)
|
|
{
|
|
local KFWeaponAttachment AttachmentTemplate;
|
|
local rotator ZeroRotator;
|
|
|
|
if (WeaponAttachment != none)
|
|
return;
|
|
|
|
// Create the new Attachment.
|
|
AttachmentTemplate = WeaponClass.default.AttachmentArchetype;
|
|
WeaponAttachment = Spawn(AttachmentTemplate.Class, self,,,, AttachmentTemplate);
|
|
|
|
if (WeaponAttachment != None)
|
|
{
|
|
WeaponAttachment.SetCollision(false, false);
|
|
WeaponAttachment.Instigator = Instigator;
|
|
|
|
TurretMesh.AttachComponentToSocket(WeaponAttachment.WeapMesh, WeaponAttachmentSocketName);
|
|
WeaponAttachment.SetRelativeLocation(vect(0,0,0));
|
|
WeaponAttachment.SetRelativeRotation(ZeroRotator);
|
|
WeaponAttachment.WeapMesh.SetOnlyOwnerSee(false);
|
|
WeaponAttachment.WeapMesh.SetOwnerNoSee(false);
|
|
WeaponAttachment.ChangeVisibility(true);
|
|
|
|
WeaponAttachment.AttachLaserSight();
|
|
|
|
TurretWeaponAttachment = KFWeapAttach_AutoTurretWeap(WeaponAttachment);
|
|
TurretWeaponAttachment.PlayCloseAnim();
|
|
}
|
|
}
|
|
|
|
simulated function UpdateInstigator(Pawn NewInstigator)
|
|
{
|
|
local KFPawn_Human KFPH;
|
|
|
|
Instigator = NewInstigator;
|
|
|
|
TeamNum = Instigator.GetTeamNum();
|
|
|
|
if (Weapon != none)
|
|
{
|
|
Weapon.Instigator = NewInstigator;
|
|
}
|
|
|
|
KFPH = KFPawn_Human(NewInstigator);
|
|
if (KFPH != none && KFPH.WeaponSkinItemId > 0)
|
|
{
|
|
SetWeaponSkin(KFPH.WeaponSkinItemId);
|
|
}
|
|
}
|
|
|
|
simulated function SetWeaponSkin(int SkinID)
|
|
{
|
|
if (Role == Role_Authority)
|
|
{
|
|
WeaponSkinID = SkinID;
|
|
bForceNetUpdate = true;
|
|
}
|
|
|
|
if (WeaponAttachment != none)
|
|
{
|
|
WeaponAttachment.SetWeaponSkin(SkinID);
|
|
}
|
|
}
|
|
|
|
simulated function UpdateWeaponUpgrade(int UpgradeIndex)
|
|
{
|
|
if (Weapon != none)
|
|
{
|
|
TurretWeapon.SetWeaponUpgradeLevel(UpgradeIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Object states
|
|
*/
|
|
function SetTurretState(ETurretState State)
|
|
{
|
|
if (CurrentState == State)
|
|
return;
|
|
|
|
ChangeState(State);
|
|
|
|
CurrentState = State;
|
|
bForceNetUpdate = true;
|
|
}
|
|
|
|
function UpdateReadyToUse(bool bReady)
|
|
{
|
|
if (OwnerWeapon != none)
|
|
{
|
|
OwnerWeapon.SetReadyToUse(bReady);
|
|
}
|
|
}
|
|
|
|
simulated function ChangeState(ETurretState State)
|
|
{
|
|
switch(State)
|
|
{
|
|
case ETS_None:
|
|
break;
|
|
case ETS_Throw:
|
|
GotoState('Throw');
|
|
break;
|
|
case ETS_Deploy:
|
|
GotoState('Deploy');
|
|
break;
|
|
case ETS_TargetSearch:
|
|
GotoState('TargetSearch');
|
|
break;
|
|
case ETS_Combat:
|
|
GotoState('Combat');
|
|
break;
|
|
case ETS_Detonate:
|
|
GotoState('Detonate');
|
|
break;
|
|
case ETS_Empty:
|
|
GotoState('Empty');
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto simulated state Throw
|
|
{
|
|
simulated function BeginState(name PreviousStateName)
|
|
{
|
|
super.BeginState(PreviousStateName);
|
|
|
|
ThrowInstigatorLocation = Instigator.Location;
|
|
|
|
if (Role == Role_Authority)
|
|
{
|
|
UpdateReadyToUse(false);
|
|
}
|
|
}
|
|
|
|
simulated event Landed(vector HitNormal, actor FloorActor)
|
|
{
|
|
super.Landed(HitNormal, FloorActor);
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
GroundLocation = Location;
|
|
DeployRotation = Rotation;
|
|
|
|
SetTurretState(ETS_Deploy);
|
|
}
|
|
}
|
|
|
|
simulated event Tick(float DeltaTime)
|
|
{
|
|
if (deployUsingOffsetFromPlayerLocation)
|
|
{
|
|
// If the drone goes below the offset, prevent from falling more
|
|
if (Location.Z < (ThrowInstigatorLocation.z - deployUsingOffsetZ))
|
|
{
|
|
GroundLocation = Location;
|
|
DeployRotation = Rotation;
|
|
|
|
SetTurretState(ETS_Deploy);
|
|
}
|
|
}
|
|
|
|
super.Tick(DeltaTime);
|
|
}
|
|
}
|
|
|
|
simulated state Deploy
|
|
{
|
|
simulated function BeginState(name PreviousStateName)
|
|
{
|
|
local float AnimDuration;
|
|
|
|
super.BeginState(PreviousStateName);
|
|
|
|
if (TurretWeaponAttachment != none)
|
|
{
|
|
AnimDuration = TurretWeaponAttachment.PlayDeployAnim();
|
|
SetTimer(AnimDuration, false, nameof(StartIdleAnim));
|
|
}
|
|
|
|
SetPhysics(PHYS_FLYING);
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
Velocity = vect(0,0,1) * DeployZSpeed;
|
|
UpdateReadyToUse(false);
|
|
}
|
|
}
|
|
|
|
simulated event Tick(float DeltaTime)
|
|
{
|
|
local float CurrentHeight;
|
|
local vector LocationNext;
|
|
|
|
super.Tick(DeltaTime);
|
|
|
|
LocationNext = Location;
|
|
LocationNext.z += Velocity.z * DeltaTime;
|
|
|
|
// If there's little to no movement or we are going to collide
|
|
if (Velocity.z <= 0.01f || !FastTrace(LocationNext, Location, vect(25,25,25)))
|
|
{
|
|
SetTurretState(ETS_TargetSearch);
|
|
}
|
|
else
|
|
{
|
|
// Check height to change state
|
|
CurrentHeight = Location.Z - GroundLocation.Z;
|
|
if (CurrentHeight >= DeployHeight)
|
|
{
|
|
SetTurretState(ETS_TargetSearch);
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function EndState(name NextStateName)
|
|
{
|
|
super.EndState(NextStateName);
|
|
DeployedLocation = Location;
|
|
|
|
Velocity = vect(0,0,0);
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
UpdateReadyToUse(true);
|
|
|
|
if (bCanDetonateOnProximityWithAmmo)
|
|
{
|
|
SetTimer(0.25f, true, nameof(CheckEnemiesWithinExplosionRadius));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated state TargetSearch
|
|
{
|
|
simulated function BeginState(name PreviousStateName)
|
|
{
|
|
super.BeginState(PreviousStateName);
|
|
|
|
bHasSearchRandomLocation = false;
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
GetRandomSearchLocation();
|
|
// bHasSearchRandomLocation=true;
|
|
|
|
SetTimer(TimeBeforeRefreshingTargets, true, nameof(CheckForTargets));
|
|
}
|
|
}
|
|
|
|
simulated function EndState(name EndStateName)
|
|
{
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
ClearTimer(nameof(CheckForTargets));
|
|
ClearTimer(nameof(GetRandomSearchLocation));
|
|
}
|
|
|
|
super.EndState(EndStateName);
|
|
}
|
|
|
|
simulated event Tick( float DeltaTime )
|
|
{
|
|
super.Tick(DeltaTime);
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
if (ReachedRotation() && !bHasSearchRandomLocation)
|
|
{
|
|
SetTimer( TimeIdlingWhenSearching + RandRange(MinIdlingVariation, MaxIdlingVariation), false, nameof(GetRandomSearchLocation));
|
|
bHasSearchRandomLocation = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function GetRandomSearchLocation()
|
|
{
|
|
local rotator NewRotation;
|
|
|
|
NewRotation.Yaw += RandRange(0,65536);
|
|
bHasSearchRandomLocation = false;
|
|
|
|
RequestRotation(NewRotation);
|
|
}
|
|
}
|
|
|
|
simulated state Combat
|
|
{
|
|
simulated function BeginState(name PreviousStateName)
|
|
{
|
|
super.BeginState(PreviousStateName);
|
|
|
|
/*if (Role == ROLE_Authority)
|
|
{
|
|
SetTimer(TimeBeforeRefreshingTargets, true, nameof(CheckForTargets));
|
|
}*/
|
|
|
|
if (WorldInfo.NetMode == NM_Client || WorldInfo.NetMode == NM_Standalone)
|
|
{
|
|
if (TurretWeaponAttachment != none)
|
|
{
|
|
TurretWeaponAttachment.UpdateLaserColor(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function EndState(name NextStateName)
|
|
{
|
|
ClearTimer(nameof(CheckForTargets));
|
|
|
|
if (Role == ROLE_Authority && TurretWeapon != none)
|
|
{
|
|
TurretWeapon.StopFire(0);
|
|
}
|
|
|
|
if (WorldInfo.NetMode == NM_Client || WorldInfo.NetMode == NM_Standalone)
|
|
{
|
|
if (TurretWeaponAttachment != none)
|
|
{
|
|
TurretWeaponAttachment.UpdateLaserColor(false);
|
|
}
|
|
}
|
|
|
|
super.EndState(NextStateName);
|
|
}
|
|
|
|
simulated event Tick( float DeltaTime )
|
|
{
|
|
local vector MuzzleLoc;
|
|
local rotator MuzzleRot;
|
|
local rotator DesiredRotationRot;
|
|
|
|
local vector HitLocation, HitNormal;
|
|
local TraceHitInfo HitInfo;
|
|
local Actor HitActor;
|
|
|
|
local float NewAmmoPercentage;
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
TurretWeapon.GetMuzzleLocAndRot(MuzzleLoc, MuzzleRot);
|
|
|
|
NewAmmoPercentage = float(TurretWeapon.AmmoCount[0]) / TurretWeapon.MagazineCapacity[0];
|
|
|
|
if (NewAmmoPercentage != CurrentAmmoPercentage)
|
|
{
|
|
CurrentAmmoPercentage = NewAmmoPercentage;
|
|
|
|
if (WorldInfo.NetMode == NM_Standalone)
|
|
{
|
|
UpdateTurretMeshMaterialColor(CurrentAmmoPercentage);
|
|
}
|
|
else
|
|
{
|
|
bNetDirty = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WeaponAttachment.WeapMesh.GetSocketWorldLocationAndRotation('MuzzleFlash', MuzzleLoc, MuzzleRot);
|
|
}
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
if (EnemyTarget != none)
|
|
{
|
|
// Trace from the Target reference to MuzzleLoc, because MuzzleLoc could be already inside physics, as it's outside the collider of the Drone!
|
|
HitActor = Trace(HitLocation, HitNormal, EnemyTarget.Mesh.GetBoneLocation('Spine1'), MuzzleLoc,,,,TRACEFLAG_Bullet);
|
|
|
|
/** Search for new enemies if current is dead, cloaked or too far, or something between the drone and the target except a player */
|
|
if (!EnemyTarget.IsAliveAndWell()
|
|
|| EnemyTarget.bIsCloaking
|
|
|| VSizeSq(EnemyTarget.Location - Location) > EffectiveRadius * EffectiveRadius
|
|
|| (HitActor != none && KFPawn_Monster(HitActor) == none && KFPawn_Human(HitActor) == none))
|
|
{
|
|
EnemyTarget = none;
|
|
CheckForTargets();
|
|
|
|
if (EnemyTarget == none)
|
|
{
|
|
SetTurretState(ETS_TargetSearch);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (EnemyTarget != none)
|
|
{
|
|
DesiredRotationRot = rotator(Normal(EnemyTarget.Mesh.GetBoneLocation('Spine1') - MuzzleLoc));
|
|
DesiredRotationRot.Roll = 0;
|
|
|
|
RotateBySpeed(DesiredRotationRot);
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
HitActor = Trace(HitLocation, HitNormal, MuzzleLoc + vector(Rotation) * EffectiveRadius, MuzzleLoc, , , HitInfo, TRACEFLAG_Bullet);
|
|
|
|
if (TurretWeapon != none)
|
|
{
|
|
if (KFPawn_Monster(HitActor) != none)
|
|
{
|
|
TurretWeapon.Fire();
|
|
|
|
if (!TurretWeapon.HasAmmo(0))
|
|
{
|
|
SetTurretState(ETS_Empty);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TurretWeapon.StopFire(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
super.Tick(DeltaTime);
|
|
}
|
|
}
|
|
|
|
simulated state Detonate
|
|
{
|
|
function BeginState(name PreviousStateName)
|
|
{
|
|
super.BeginState(PreviousStateName);
|
|
|
|
StopIdleSound();
|
|
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
TriggerExplosion();
|
|
}
|
|
}
|
|
|
|
function TriggerExplosion()
|
|
{
|
|
local KFExplosionActorReplicated ExploActor;
|
|
|
|
// explode using the given template
|
|
ExploActor = Spawn(class'KFExplosionActorReplicated', TurretWeapon,, Location, Rotation,, true);
|
|
if (ExploActor != None)
|
|
{
|
|
ExploActor.InstigatorController = Instigator.Controller;
|
|
ExploActor.Instigator = Instigator;
|
|
|
|
ExploActor.Explode(ExplosionTemplate);
|
|
}
|
|
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
simulated state Empty
|
|
{
|
|
simulated function BeginState(name PreviousStateName)
|
|
{
|
|
super.BeginState(PreviousStateName);
|
|
|
|
if (WorldInfo.NetMode == NM_Client || WorldInfo.NetMode == NM_Standalone)
|
|
{
|
|
// Attach No Ammo VFX
|
|
if (NoAmmoFX == none)
|
|
{
|
|
NoAmmoFX = WorldInfo.MyEmitterPool.SpawnEmitter(NoAmmoFXTemplate, Location);
|
|
}
|
|
|
|
// Play dry sound 2 or 3 times
|
|
SetTimer(0.5f, false, 'PlayEmptySound1');
|
|
SetTimer(1.f, false, 'PlayEmptySound2');
|
|
SetTimer(1.5f, false, 'PlayEmptySound3');
|
|
|
|
// When sound finish play animation
|
|
SetTimer(2.f, false, 'PlayEmptyState');
|
|
|
|
UpdateTurretMeshMaterialColor(0.0f);
|
|
}
|
|
|
|
if (Role == ROLE_Authority && !bCanDetonateOnProximityWithAmmo)
|
|
{
|
|
SetTimer(0.25f, true, nameof(CheckEnemiesWithinExplosionRadius));
|
|
}
|
|
}
|
|
|
|
simulated function EndState(name NextStateName)
|
|
{
|
|
super.EndState(NextStateName);
|
|
}
|
|
}
|
|
|
|
// We can't use the same delegate, hence we create 3 functions..
|
|
|
|
simulated function PlayEmptySound1()
|
|
{
|
|
PlaySoundBase(DroneDryFire);
|
|
}
|
|
|
|
simulated function PlayEmptySound2()
|
|
{
|
|
PlaySoundBase(DroneDryFire);
|
|
}
|
|
|
|
simulated function PlayEmptySound3()
|
|
{
|
|
PlaySoundBase(DroneDryFire);
|
|
}
|
|
|
|
simulated function PlayEmptyState()
|
|
{
|
|
if (TurretWeaponAttachment != none)
|
|
{
|
|
StopIdleSound();
|
|
|
|
TurretWeaponAttachment.PlayEmptyState();
|
|
}
|
|
}
|
|
|
|
simulated function StopIdleSound()
|
|
{
|
|
FlyAkComponent.StopEvents();
|
|
}
|
|
|
|
function CheckForTargets()
|
|
{
|
|
local KFPawn_Monster CurrentTarget;
|
|
local TraceHitInfo HitInfo;
|
|
|
|
local float CurrentDistance;
|
|
local float Distance;
|
|
|
|
local vector MuzzleLoc;
|
|
local rotator MuzzleRot;
|
|
|
|
local vector HitLocation, HitNormal;
|
|
local Actor HitActor;
|
|
|
|
if (EnemyTarget != none)
|
|
{
|
|
CurrentDistance = VSizeSq(Location - EnemyTarget.Location);
|
|
}
|
|
else
|
|
{
|
|
CurrentDistance = 9999.f;
|
|
}
|
|
|
|
TurretWeapon.GetMuzzleLocAndRot(MuzzleLoc, MuzzleRot);
|
|
|
|
foreach CollidingActors(class'KFPawn_Monster', CurrentTarget, EffectiveRadius, Location, true,, HitInfo)
|
|
{
|
|
// Trace from the Target reference to MuzzleLoc, because MuzzleLoc could be already inside physics, as it's outside the collider of the Drone!
|
|
HitActor = Trace(HitLocation, HitNormal, CurrentTarget.Mesh.GetBoneLocation('Spine1'), MuzzleLoc,,,,TRACEFLAG_Bullet);
|
|
|
|
if (!CurrentTarget.IsAliveAndWell() || CurrentTarget.bIsCloaking || HitActor == none || KFPawn_Monster(HitActor) == none)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Distance = VSizeSq(Location - CurrentTarget.Location);
|
|
|
|
if (EnemyTarget == none)
|
|
{
|
|
EnemyTarget = CurrentTarget;
|
|
CurrentDistance = Distance;
|
|
}
|
|
else if (CurrentDistance > Distance)
|
|
{
|
|
EnemyTarget = CurrentTarget;
|
|
CurrentDistance = Distance;
|
|
}
|
|
}
|
|
|
|
if (EnemyTarget != none)
|
|
{
|
|
SetTurretState(ETS_Combat);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
simulated event Destroyed()
|
|
{
|
|
local KFWeap_AutoTurret WeapOwner;
|
|
|
|
StopIdleSound();
|
|
|
|
WeapOwner = KFWeap_AutoTurret(Owner);
|
|
|
|
if (WeapOwner != none)
|
|
{
|
|
WeapOwner.RemoveDeployedTurret(,self);
|
|
}
|
|
|
|
ClearTimer(nameof(CheckEnemiesWithinExplosionRadius));
|
|
|
|
if (NoAmmoFX != none)
|
|
{
|
|
NoAmmoFX.KillParticlesForced();
|
|
WorldInfo.MyEmitterPool.OnParticleSystemFinished(NoAmmoFX);
|
|
NoAmmoFX = none;
|
|
}
|
|
|
|
super.Destroyed();
|
|
}
|
|
|
|
simulated function CheckEnemiesWithinExplosionRadius()
|
|
{
|
|
local KFPawn_Monster KFPM;
|
|
local Vector CheckExplosionLocation;
|
|
|
|
CheckExplosionLocation = Location;
|
|
CheckExplosionLocation.z -= DeployHeight * 0.5f;
|
|
|
|
if (CheckExplosionLocation.z < GroundLocation.z)
|
|
{
|
|
CheckExplosionLocation.z = GroundLocation.z;
|
|
}
|
|
|
|
//DrawDebugSphere(CheckExplosionLocation, ExplosiveRadius, 10, 255, 255, 0 );
|
|
|
|
foreach CollidingActors(class'KFPawn_Monster', KFPM, ExplosiveRadius, CheckExplosionLocation, true,,)
|
|
{
|
|
if(KFPM != none && KFPM.IsAliveAndWell())
|
|
{
|
|
SetTurretState(ETS_Detonate);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function StartIdleAnim()
|
|
{
|
|
if (TurretWeaponAttachment != none)
|
|
{
|
|
TurretWeaponAttachment.PlayIdleAnim();
|
|
|
|
FlyAkComponent.PlayEvent(DroneFlyingStartAudio, true, true);
|
|
}
|
|
}
|
|
|
|
simulated event Tick(float DeltaTime)
|
|
{
|
|
local rotator NewRotationRate;
|
|
|
|
/** This gets reset somehow and causes issues with the rotation */
|
|
RotationRate = NewRotationRate;
|
|
|
|
if (bRotating)
|
|
{
|
|
UpdateRotation(DeltaTime);
|
|
}
|
|
|
|
super.Tick(DeltaTime);
|
|
}
|
|
|
|
simulated function UpdateRotation(float DeltaTime)
|
|
{
|
|
local rotator RotationDiff;
|
|
local rotator RotationStep;
|
|
local rotator NewRotation;
|
|
local rotator RotationSpeed;
|
|
local int Sign;
|
|
|
|
if (bRotating)
|
|
{
|
|
if (bRotatingByTime) /** Rotate in an amount of time */
|
|
{
|
|
if(RotationAlpha < RotationTime)
|
|
{
|
|
RotationAlpha += DeltaTime;
|
|
|
|
RotationStep = RLerp(RotationStart, TargetRotation, FMin(RotationAlpha / RotationTime, 1.0f), true);
|
|
RotationStep.Roll = 0.0f;
|
|
|
|
RotationStep.Yaw = RotationStep.Yaw % 65536;
|
|
RotationStep.Pitch = RotationStep.Pitch % 65536;
|
|
|
|
if (RotationStep.Yaw < 0)
|
|
RotationStep.Yaw += 65536;
|
|
|
|
if (RotationStep.Pitch < 0)
|
|
RotationStep.Pitch += 65536;
|
|
|
|
SetRotation(RotationStep);
|
|
}
|
|
else
|
|
{
|
|
bRotating = false;
|
|
}
|
|
}
|
|
else /** Rotate By Speed */
|
|
{
|
|
RotationSpeed = CurrentState == ETS_Combat ? CombatRotationVel : AimRotationVel;
|
|
|
|
RotationDiff = TargetRotation - Rotation;
|
|
|
|
RotationDiff.Yaw = NormalizeRotAxis(RotationDiff.Yaw);
|
|
RotationDiff.Pitch = NormalizeRotAxis(RotationDiff.Pitch);
|
|
RotationDiff.Roll = NormalizeRotAxis(RotationDiff.Roll);
|
|
|
|
Sign = RotationDiff.Yaw >= 0? 1 : -1;
|
|
RotationStep.Yaw = RotationSpeed.Yaw * DeltaTime * Sign;
|
|
if (Abs(RotationStep.Yaw) > Abs(RotationDiff.Yaw))
|
|
{
|
|
RotationStep.Yaw = RotationDiff.Yaw;
|
|
}
|
|
|
|
Sign = RotationDiff.Pitch >= 0? 1 : -1;
|
|
RotationStep.Pitch = RotationSpeed.Pitch * DeltaTime * Sign;
|
|
if (Abs(RotationStep.Pitch) > Abs(RotationDiff.Pitch))
|
|
{
|
|
RotationStep.Pitch = RotationDiff.Pitch;
|
|
}
|
|
|
|
RotationStep.Roll = 0.0f;
|
|
|
|
NewRotation = Rotation + RotationStep;
|
|
NewRotation.Yaw = NewRotation.Yaw % 65536;
|
|
NewRotation.Pitch = NewRotation.Pitch % 65536;
|
|
|
|
if (NewRotation.Yaw < 0)
|
|
NewRotation.Yaw += 65536;
|
|
|
|
if (NewRotation.Pitch < 0)
|
|
NewRotation.Pitch += 65536;
|
|
|
|
SetRotation(NewRotation);
|
|
|
|
if (ReachedRotation())
|
|
{
|
|
bRotating = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated function RotateByTime(rotator NewRotation, float Time)
|
|
{
|
|
if (NewRotation != Rotation)
|
|
{
|
|
RotationStart = Rotation;
|
|
TargetRotation = NewRotation;
|
|
RotationAlpha = 0.0;
|
|
RotationTime = Time;
|
|
bRotating = true;
|
|
bRotatingByTime = true;
|
|
}
|
|
}
|
|
|
|
simulated function RotateBySpeed(rotator NewRotation)
|
|
{
|
|
TargetRotation = NewRotation;
|
|
RotationAlpha = 0.0f;
|
|
RotationTime = 0.0f;
|
|
bRotating = true;
|
|
bRotatingByTime = false;
|
|
}
|
|
|
|
simulated event byte ScriptGetTeamNum()
|
|
{
|
|
return TeamNum;
|
|
}
|
|
|
|
simulated function RequestRotation(rotator NewRotation)
|
|
{
|
|
if (Role == ROLE_Authority)
|
|
{
|
|
/** Set rotation the same as its going to be replicated **/
|
|
ReplicatedRotation.Yaw = ((NewRotation.Yaw >> 8) & 255) << 8;
|
|
ReplicatedRotation.Pitch = ((NewRotation.Pitch >> 8) & 255) << 8;
|
|
|
|
bForceNetUpdate = true;
|
|
}
|
|
|
|
RotateBySpeed(ReplicatedRotation);
|
|
}
|
|
|
|
simulated function bool ReachedRotation(int DeltaError = 2000)
|
|
{
|
|
local int YawDiff;
|
|
YawDiff = Abs((TargetRotation.Yaw & 65535) - (Rotation.Yaw & 65535));
|
|
return (YawDiff < DeltaError) || (YawDiff > 65535 - DeltaError);
|
|
}
|
|
|
|
event TakeDamage(int Damage, Controller InstigatedBy, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
|
|
{
|
|
}
|
|
|
|
simulated function TakeRadiusDamage(
|
|
Controller InstigatedBy,
|
|
float BaseDamage,
|
|
float DamageRadius,
|
|
class<DamageType> DamageType,
|
|
float Momentum,
|
|
vector HurtOrigin,
|
|
bool bFullDamage,
|
|
Actor DamageCauser,
|
|
optional float DamageFalloffExponent=1.f
|
|
)
|
|
{}
|
|
|
|
function bool CanAITargetThisPawn(Controller TargetingController)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
simulated function UpdateTurretMeshMaterialColor(float Value)
|
|
{
|
|
if (TurretWeaponAttachment == none)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (TurretAttachMIC == none)
|
|
{
|
|
TurretAttachMIC = TurretWeaponAttachment.WeapMesh.CreateAndSetMaterialInstanceConstant(`AUTOTURRET_MIC_INDEX);
|
|
}
|
|
|
|
if (TurretAttachMIC != none)
|
|
{
|
|
TurretAttachMIC.SetScalarParameterValue(TransitionParamName, 1.0f - Value);
|
|
TurretAttachMIC.SetScalarParameterValue(EmptyParamName, Value == 0.0f ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
/** No AutoAim */
|
|
simulated function bool GetAutoTargetBones(out array<name> WeakBones, out array<name> NormalBones)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
simulated function SetWeaponAmbientSound(AkEvent NewAmbientSound, optional AkEvent FirstPersonAmbientSound)
|
|
{
|
|
super.SetWeaponAmbientSound(NewAmbientSound, FirstPersonAmbientSound);
|
|
|
|
if (WorldInfo.NetMode == NM_DedicatedServer)
|
|
{
|
|
TurretWeaponAmbientSound = NewAmbientSound;
|
|
bNetDirty = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function's responsibility is to signal clients that non-instant hit shot
|
|
* has been fired. Call this on the server and local player.
|
|
*
|
|
* Network: Server and Local Player
|
|
*/
|
|
simulated function IncrementFlashCount(Weapon InWeapon, byte InFiringMode)
|
|
{
|
|
Super.IncrementFlashCount(InWeapon, InFiringMode);
|
|
AutoTurretFlashCount = FlashCount;
|
|
// bNetDirty = true;
|
|
bForceNetUpdate = true;
|
|
}
|
|
|
|
simulated function ClearFlashCount(Weapon InWeapon)
|
|
{
|
|
Super.ClearFlashCount(InWeapon);
|
|
|
|
AutoTurretFlashCount = FlashCount;
|
|
bForceNetUpdate=true;
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
bCollideComplex=TRUE
|
|
Begin Object Name=CollisionCylinder
|
|
CollisionRadius=40
|
|
CollisionHeight=30
|
|
bIgnoreRadialImpulse=true
|
|
BlockNonZeroExtent=true
|
|
CollideActors=true
|
|
BlockActors=false
|
|
End Object
|
|
CylinderComponent=CollisionCylinder
|
|
|
|
Begin Object Class=SkeletalMeshComponent Name=TurretMesh0
|
|
SkeletalMesh=SkeletalMesh'WEP_3P_AutoTurret_MESH.drone_SM'
|
|
PhysicsAsset=PhysicsAsset'WEP_3P_AutoTurret_MESH.drone_SM_Physics'
|
|
BlockNonZeroExtent=false
|
|
CastShadow=false
|
|
bIgnoreRadialImpulse=true
|
|
|
|
End Object
|
|
Components.Add(TurretMesh0)
|
|
TurretMesh=TurretMesh0
|
|
Mesh=TurretMesh0
|
|
|
|
Begin Object Class=KFGameExplosion Name=ExploTemplate0 // @todo: change me
|
|
Damage=200
|
|
DamageRadius=300
|
|
DamageFalloffExponent=0.5f
|
|
DamageDelay=0.f
|
|
|
|
// Damage Effects
|
|
MyDamageType=class'KFDT_Explosive_AutoTurret'
|
|
KnockDownStrength=0
|
|
FractureMeshRadius=200.0
|
|
FracturePartVel=500.0
|
|
ExplosionEffects=KFImpactEffectInfo'wep_autoturret_arch.AutoTurret_Explosion' // Original : KFImpactEffectInfo'WEP_Seal_Squeal_ARCH.SealSquealHarpoon_Explosion'
|
|
ExplosionSound=AkEvent'ww_wep_autoturret.Play_WEP_AutoTurret_Alt_Fire_3P'
|
|
|
|
// Camera Shake
|
|
CamShake=CameraShake'FX_CameraShake_Arch.Misc_Explosions.Light_Explosion_Rumble'
|
|
CamShakeInnerRadius=200
|
|
CamShakeOuterRadius=900
|
|
CamShakeFalloff=1.5f
|
|
bOrientCameraShakeTowardsEpicenter=true
|
|
End Object
|
|
ExplosionTemplate=ExploTemplate0
|
|
|
|
// sounds
|
|
Begin Object Class=AkComponent name=FlyAkComponent0
|
|
BoneName=dummy
|
|
bStopWhenOwnerDestroyed=true
|
|
bForceOcclusionUpdateInterval=true
|
|
OcclusionUpdateInterval=0.2f
|
|
End Object
|
|
FlyAkComponent=FlyAkComponent0
|
|
Components.Add(FlyAkComponent0)
|
|
|
|
CurrentState=ETS_None
|
|
DeployZSpeed=800.0f
|
|
DeployHeight=200.0f
|
|
|
|
deployUsingOffsetFromPlayerLocation=true
|
|
deployUsingOffsetZ = 100.f
|
|
|
|
EffectiveRadius=1500.0f
|
|
ExplosiveRadius=100.0f
|
|
|
|
WeaponDefinition=class'KFGame.KFWeapDef_AutoTurretWeapon'
|
|
|
|
TimeBeforeRefreshingTargets=0.5f
|
|
TimeIdlingWhenSearching = 1.0f
|
|
MinIdlingVariation = -0.5f
|
|
MaxIdlingVariation = 1.0f
|
|
|
|
// Rotation speed per second
|
|
AimRotationVel =(Pitch=16384,Yaw=32768,Roll=0)
|
|
CombatRotationVel =(Pitch=16384,Yaw=32768,Roll=0)
|
|
|
|
RotationAlpha = 0.0f
|
|
RotationTime = 0.0f
|
|
bRotating = false
|
|
bHasSearchRandomLocation = false
|
|
|
|
EmptyPitch=0 //-10000
|
|
|
|
bRunPhysicsWithNoController=true;
|
|
|
|
bCanBeDamaged=false
|
|
TeamNum=0
|
|
|
|
bRotatingByTime=false
|
|
RemoteRole = ROLE_SimulatedProxy
|
|
|
|
bCanDetonateOnProximityWithAmmo=false
|
|
bIgnoreForces=true
|
|
|
|
bIsTurret=true
|
|
|
|
NoAmmoFX=none
|
|
|
|
bAlwaysRelevant=true
|
|
|
|
AutoTurretFlashCount=0
|
|
}
|