1
0
KF2-Dev-Scripts/KFGameContent/Classes/KFSM_BloatKing_Gorge.uc
2020-12-13 18:01:13 +03:00

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
}