436 lines
12 KiB
Ucode
436 lines
12 KiB
Ucode
//=============================================================================
|
|
// KFSM_BloatKing_Gorge
|
|
//=============================================================================
|
|
// Bloat King gorge move. Pulls in friendly + enemy with different effects
|
|
// on contact with the Bloat.
|
|
//=============================================================================
|
|
// Killing Floor 2
|
|
// Copyright (C) 2017 Tripwire Interactive LLC
|
|
//=============================================================================
|
|
class KFSM_BloatKing_Gorge extends KFSM_PlaySingleAnim;
|
|
|
|
//Cached bloat reference
|
|
var KFPawn_ZedBloatKing BloatPawn;
|
|
|
|
//Ranges of the attack
|
|
var const float GorgeAttackRangeSq;
|
|
var const float GorgeHumanAttackRangeSq;
|
|
|
|
//Angle range that players/zeds have to be in for this to be a valid time to start
|
|
var const float GorgeMinAttackAngle;
|
|
|
|
//Amount of time to take before next attempted gorge after using the ability
|
|
var const Vector2D GorgeAttackCooldown[4];
|
|
|
|
//Amount of time to take between checks if starting the move was not valid
|
|
var const float GorgeAttackCheckDelay;
|
|
|
|
//amount of time to wait to start the pull when the special move starts
|
|
var const float GorgePullDelay;
|
|
|
|
//List of actors we're already pulling
|
|
var array<KFPawn> PullList;
|
|
var array<KFPawn> PulledList;
|
|
|
|
//Special move to do to the person being grabbed
|
|
var ESpecialMove FollowerSpecialMove;
|
|
var ESpecialMove ZedFollowerSpecialMove;
|
|
|
|
//Rate the pulled pawn moves towards the Bloat
|
|
var float GorgePullRate;
|
|
|
|
//Gorge release offset
|
|
var float GorgeReleaseOffset;
|
|
|
|
//Damage per-difficulty for human interaction
|
|
var float GorgeBaseDamage[4];
|
|
|
|
//Track whether pull is now valid
|
|
var bool bPullActive;
|
|
|
|
// List of pawns that need to be killed
|
|
var array<KFPawn> GorgeHitList;
|
|
|
|
// List of pawns that have to be removed as victims on next tick
|
|
// SpecialMoveEnded happens in async tick work, so we can't change physics in that time
|
|
var array<KFPawn> DeferredRemovalList;
|
|
|
|
/** Contains a list of particle systems spawned during the move */
|
|
var protected array<ParticleSystemComponent> AnimParticles;
|
|
|
|
static function float GetGorgeCooldown(KFPawn InPawn, int Difficulty)
|
|
{
|
|
return Lerp(default.GorgeAttackCooldown[Difficulty].X, default.GorgeAttackCooldown[Difficulty].Y, InPawn.GetHealthPercentage());
|
|
}
|
|
|
|
function SpecialMoveStarted(bool bForced, Name PrevMove)
|
|
{
|
|
super.SpecialMoveStarted(bForced, PrevMove);
|
|
|
|
BloatPawn = KFPawn_ZedBloatKing(KFPOwner);
|
|
PulledList.Length = 0;
|
|
KFPOwner.SetTimer(GorgePullDelay, false, 'StartGorgePull', self);
|
|
}
|
|
|
|
/** Called from KFPawn::OnAnimNotifyParticleSystemSpawned() */
|
|
function OnAnimNotifyParticleSystemSpawned( const AnimNotify_PlayParticleEffect AnimNotifyData, ParticleSystemComponent PSC )
|
|
{
|
|
local AnimSequence AnimSeq;
|
|
|
|
if (AnimNotifyData.Outer != none)
|
|
{
|
|
AnimSeq = AnimSequence(AnimNotifyData.Outer);
|
|
if (AnimSeq != none && string(AnimSeq.SequenceName) ~= string(AnimName)) // string conversion so we don't have case mismatches
|
|
{
|
|
AnimParticles.AddItem( PSC );
|
|
}
|
|
}
|
|
}
|
|
|
|
function StartGorgePull()
|
|
{
|
|
if (KFPOwner.Role == ROLE_Authority)
|
|
{
|
|
bPullActive = true;
|
|
}
|
|
}
|
|
|
|
function SpecialMoveEnded(Name PrevMove, Name NextMove)
|
|
{
|
|
local KFPawn PullPawn;
|
|
local int i;
|
|
|
|
bPullActive = false;
|
|
|
|
for (i = PullList.Length - 1; i >= 0; --i)
|
|
{
|
|
PullPawn = PullList[i];
|
|
if (KFPawn_Human(PullPawn) != none)
|
|
{
|
|
RemoveVictim(PullPawn, false);
|
|
}
|
|
else
|
|
{
|
|
DeferredRemovalList.AddItem(PullPawn);
|
|
}
|
|
}
|
|
PullList.Length = 0;
|
|
|
|
for (i = 0; i < AnimParticles.Length; ++i)
|
|
{
|
|
AnimParticles[i].DeactivateSystem();
|
|
}
|
|
AnimParticles.Length = 0;
|
|
|
|
Super.SpecialMoveEnded(PrevMove, NextMove);
|
|
}
|
|
|
|
function PlayAnimation()
|
|
{
|
|
local float Duration;
|
|
|
|
Duration = PlaySpecialMoveAnim(AnimName, AnimStance, BlendInTime, BlendOutTime, 1.f, bLoopAnim);
|
|
KFPOwner.SetTimer(Duration + 2.f, false, 'Timeout', self);
|
|
}
|
|
|
|
function Timeout()
|
|
{
|
|
BloatPawn.EndSpecialMove();
|
|
}
|
|
|
|
function Tick(float DeltaTime)
|
|
{
|
|
local KFPawn TestPawn;
|
|
local KFPawn_Monster MonsterToKill;
|
|
|
|
super.Tick(DeltaTime);
|
|
|
|
if(KFPOwner.Role == ROLE_Authority)
|
|
{
|
|
if (GorgeHitList.length > 0)
|
|
{
|
|
foreach GorgeHitList(TestPawn)
|
|
{
|
|
if (TestPawn == none || !TestPawn.IsAliveAndWell())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
MonsterToKill = KFPawn_Monster(TestPawn);
|
|
if (MonsterToKill != none)
|
|
{
|
|
MonsterToKill.KilledBy(KFPOwner);
|
|
}
|
|
else
|
|
{
|
|
TestPawn.TakeDamage(GorgeBaseDamage[KFPOwner.WorldInfo.Game.GetModifiedGameDifficulty()], KFPOwner.Controller, TestPawn.Location, vect(0, 0, 0), class'KFDT_Bludgeon_BloatKingGorge');
|
|
}
|
|
}
|
|
GorgeHitList.length = 0;
|
|
}
|
|
|
|
if (DeferredRemovalList.Length > 0)
|
|
{
|
|
foreach DeferredRemovalList(TestPawn)
|
|
{
|
|
RemoveVictim(TestPawn, false);
|
|
}
|
|
|
|
DeferredRemovalList.Length = 0;
|
|
}
|
|
|
|
if (bPullActive)
|
|
{
|
|
FindNewVictims();
|
|
}
|
|
}
|
|
UpdateVictims(DeltaTime);
|
|
}
|
|
|
|
function FindNewVictims()
|
|
{
|
|
local KFPawn PullPawn;
|
|
local vector ViewDirection, ToTarget;
|
|
local vector Extent, HitLocation, HitNormal;
|
|
local Actor HitActor;
|
|
local float DotAngle, ToTargetRange;
|
|
|
|
if (BloatPawn.GorgeTrigger != None)
|
|
{
|
|
ViewDirection = Vector(KFPOwner.Rotation);
|
|
foreach BloatPawn.GorgeTrigger.TouchingActors(class'KFPawn', PullPawn)
|
|
{
|
|
//Not currently being pulled, and not pulled within this cycle
|
|
if (PullList.Find(PullPawn) == INDEX_NONE && PulledList.Find(PullPawn) == INDEX_NONE)
|
|
{
|
|
//Can target enemy and friendly, but they must be alive and can be filtered by class type
|
|
//They must not already be grabbed
|
|
//They must not already be in the current intended follower move
|
|
if (!PullPawn.IsAliveAndWell() || !IsValidPullClass(PullPawn) || !PullPawn.CanBeGrabbed(KFPOwner, true, true) || PullPawn.IsDoingSpecialMove(FollowerSpecialMove))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ToTarget = PullPawn.Location - KFPOwner.Location;
|
|
ToTargetRange = VSizeSq(ToTarget);
|
|
|
|
//Check distance. If out of range, ignore this potential target.
|
|
if (PullPawn.IsHumanControlled() && ToTargetRange > default.GorgeHumanAttackRangeSq)
|
|
{
|
|
continue;
|
|
}
|
|
else if (ToTargetRange > default.GorgeAttackRangeSq)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!KFPOwner.IsHumanControlled())
|
|
{
|
|
// Set our extent
|
|
Extent.X = KFPOwner.GetCollisionRadius() * 0.5f;
|
|
Extent.Y = Extent.X;
|
|
Extent.Z = KFPOwner.GetCollisionHeight() * 0.5f;
|
|
|
|
// trace for obstructions (already checked if IsHumanControlled())
|
|
HitActor = KFPOwner.Trace(HitLocation, HitNormal, PullPawn.Location, KFPOwner.Location, true, Extent);
|
|
if (HitActor != None && HitActor != PullPawn)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Within field of view and not behind geometry
|
|
DotAngle = ViewDirection dot Normal(ToTarget);
|
|
if (DotAngle > default.GorgeMinAttackAngle && KFPOwner.FastTrace(PullPawn.Location, KFPOwner.Location))
|
|
{
|
|
AddNewVictim(PullPawn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static function bool IsValidPullClass(KFPawn PullPawn)
|
|
{
|
|
local KFPawn_Monster MonsterPawn;
|
|
|
|
MonsterPawn = KFPawn_Monster(PullPawn);
|
|
|
|
if (PullPawn.class == class'KFPawn_ZedBloatKingSubspawn' || MonsterPawn != none && MonsterPawn.IsABoss())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function UpdateVictims(float DeltaTime)
|
|
{
|
|
local KFPawn CurrentPawn;
|
|
local Vector MoveVector, SocketLocation;
|
|
local Rotator SocketRotation;
|
|
local int i;
|
|
local bool bIsHuman;
|
|
|
|
KFPOwner.Mesh.GetSocketWorldLocationAndRotation('PukeSocket', SocketLocation, SocketRotation);
|
|
for (i = 0; i < 11; ++i)
|
|
{
|
|
CurrentPawn = BloatPawn.PullVictims[i];
|
|
if (CurrentPawn != None)
|
|
{
|
|
bIsHuman = CurrentPawn.IsA('KFPawn_Human');
|
|
//Reached end, stop pull and do things
|
|
if (VSizeSq(CurrentPawn.Location - KFPOwner.Location) < GorgeReleaseOffset * GorgeReleaseOffset)
|
|
{
|
|
RemoveVictim(CurrentPawn, true);
|
|
}
|
|
//else keep pulling
|
|
else
|
|
{
|
|
//Scoot humans along the ground
|
|
if (bIsHuman)
|
|
{
|
|
MoveVector = (GorgePullRate * DeltaTime) * Normal(KFPOwner.Location - CurrentPawn.Location);
|
|
}
|
|
//Bring zeds towards mouth
|
|
else
|
|
{
|
|
MoveVector = (GorgePullRate * DeltaTime) * Normal(SocketLocation - CurrentPawn.Location);
|
|
}
|
|
CurrentPawn.SetLocation(CurrentPawn.Location + MoveVector);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function AddNewVictim(KFPawn NewVictim)
|
|
{
|
|
//Found a target, so we're good to start the move
|
|
PullList.AddItem(NewVictim);
|
|
PulledList.RemoveItem(NewVictim);
|
|
StartPullingPawn(NewVictim);
|
|
|
|
if (BloatPawn.Role == ROLE_Authority)
|
|
{
|
|
BloatPawn.AddGorgeVictim(NewVictim);
|
|
}
|
|
}
|
|
|
|
function RemoveVictim(KFPawn OldVictim, bool bReachedEnd = false)
|
|
{
|
|
PullList.RemoveItem(OldVictim);
|
|
PulledList.AddItem(OldVictim);
|
|
|
|
if (BloatPawn.Role == ROLE_Authority)
|
|
{
|
|
BloatPawn.RemoveGorgeVictim(OldVictim);
|
|
if (bReachedEnd && KFPawn_Monster(OldVictim) != none)
|
|
{
|
|
BloatPawn.SpawnPoopMonster();
|
|
}
|
|
}
|
|
StopPullingPawn(OldVictim, bReachedEnd);
|
|
}
|
|
|
|
function StartPullingPawn(KFPawn NewVictim)
|
|
{
|
|
if (NewVictim != None)
|
|
{
|
|
if (KFPOwner.Role == ROLE_Authority)
|
|
{
|
|
//Monster types use a different special move and also turn on explosive death from
|
|
// the Inflate weekly for special effects reasons.
|
|
if (KFPawn_Monster(NewVictim) != none)
|
|
{
|
|
NewVictim.DoSpecialMove(ZedFollowerSpecialMove, true, KFPOwner);
|
|
KFPawn_Monster(NewVictim).bUseExplosiveDeath = true;
|
|
}
|
|
else
|
|
{
|
|
NewVictim.DoSpecialMove(FollowerSpecialMove, true, KFPOwner);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function StopPullingPawn(KFPawn OldVictim, bool bReachedEnd = true)
|
|
{
|
|
local KFPawn_Monster MonsterVictim;
|
|
|
|
if (OldVictim != None)
|
|
{
|
|
MonsterVictim = KFPawn_Monster(OldVictim);
|
|
OldVictim.EndSpecialMove();
|
|
|
|
if (KFPOwner.Role == ROLE_Authority)
|
|
{
|
|
if (MonsterVictim != None)
|
|
{
|
|
//Kill if this is a gorge finisher
|
|
if (bReachedEnd)
|
|
{
|
|
GorgeHitList.AddItem(MonsterVictim);
|
|
}
|
|
//Otherwise turn off explosive death
|
|
else
|
|
{
|
|
MonsterVictim.bUseExplosiveDeath = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(bReachedEnd)
|
|
{
|
|
GorgeHitList.AddItem(OldVictim);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function bool CanOverrideMoveWith(Name NewMove)
|
|
{
|
|
if (NewMove == 'KFSM_Knockdown')
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
//Special Move
|
|
Handle=KFSM_BloatKing_Gorge
|
|
AnimName=Atk_Gorge
|
|
AnimStance=EAS_FullBody
|
|
bDisableSteering=true
|
|
bDisableMovement=true
|
|
bDisableTurnInPlace=true
|
|
bLockPawnRotation=true
|
|
bCanBeInterrupted=true
|
|
CustomRotationRate=(Pitch=0, Yaw=0, Roll=0)
|
|
|
|
//Gorge Specific
|
|
//These are (0% health, 100% health)
|
|
GorgeAttackCooldown[0]=(X=8.0,Y=11.0) //normal //x35 y45
|
|
GorgeAttackCooldown[1]=(X=8.0,Y=11.0) //hard //x25 y35
|
|
GorgeAttackCooldown[2]=(X=7.0,Y=10.0) //Suicidal //x15 y25
|
|
GorgeAttackCooldown[3]=(X=6.0,Y=9.0) //HOE //x10 y15
|
|
GorgeAttackCheckDelay=5.0
|
|
GorgePullDelay=0.01 //0.91 0.61
|
|
FollowerSpecialMove=SM_BloatKingGorgeVictim
|
|
ZedFollowerSpecialMove=SM_GorgeZedVictim,
|
|
GorgePullRate=300.0 //UU pur sec //1000 //375
|
|
GorgeReleaseOffset=135 //300
|
|
GorgeBaseDamage[0]=30 //normal
|
|
GorgeBaseDamage[1]=40 //hard
|
|
GorgeBaseDamage[2]=50 //Suicidal
|
|
GorgeBaseDamage[3]=50 //HOE
|
|
|
|
//1000 * 1000 - Squaring distance for performance
|
|
//Also update the value in KFTrigger_BloatKingGorge or there may be some issues with first-frame spawn collision
|
|
GorgeAttackRangeSq=490000 //490k = 700UU
|
|
GorgeHumanAttackRangeSq=450000 //250k = 500UU 250000
|
|
GorgeMinAttackAngle=0.77f
|
|
} |