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

505 lines
13 KiB
Ucode

//=============================================================================
// KFProjectileStickHelper
//=============================================================================
// Manages projectile sticking and pinning functionality
//=============================================================================
// Killing Floor 2
// Copyright (C) 2019 Tripwire Interactive LLC
//=============================================================================
class KFProjectileStickHelper extends Object within KFProjectile;
var transient Pawn PinPawn;
var transient name PinBoneName;
var transient RB_ConstraintActorSpawnable PinConstraint;
var transient vector PinLocation;
var transient Actor PinHitActor;
var AkEvent StickAkEvent;
/** Tries to stick projectile to hit actor. alternatively, tries to bounce it off of hit actor. */
simulated function TryStick(vector HitNormal, optional vector HitLocation, optional Actor HitActor)
{
local TraceHitInfo HitInfo;
if (Instigator == None || !Instigator.IsLocallyControlled() || (Physics == PHYS_None && StuckToActor != none))
{
return;
}
if (HitActor != none && (HitActor == StuckToActor || HitActor == PinPawn))
{
return;
}
GetImpactInfo(Velocity, HitLocation, HitNormal, HitInfo);
if (HitInfo.HitComponent != none && GetImpactResult(HitActor, HitInfo.HitComponent))
{
Stick(HitActor, HitLocation, HitNormal, HitInfo);
}
}
/** Get all relevant impact info (called after collision occurs to fill in details that we don't get in HitWall or ProcessTouch) */
simulated function GetImpactInfo(vector in_Velocity, out vector out_HitLocation, out vector out_HitNormal, out TraceHitInfo out_HitInfo)
{
local vector VelNorm;
local vector VelScaled;
VelNorm = Normal(in_Velocity);
VelScaled = VelNorm * 30;
Trace(out_HitLocation, out_HitNormal, out_HitLocation + VelScaled, out_HitLocation - VelScaled,,,
out_HitInfo, TRACEFLAG_Bullet /*for complex collision*/ );
}
/** Returns appropriate interaction with HitActor (stick or ignore, for now. add bounce later?) */
simulated function bool GetImpactResult(Actor HitActor, PrimitiveComponent HitComp)
{
local KFPawn_Human KFP;
local KFDestructibleActor D;
local StaticMeshComponent StaticMeshComp;
if (HitActor == none)
{
return true;
}
if (HitActor.RemoteRole == ROLE_None && !HitActor.bWorldGeometry)
{
return false;
}
// if we've already been dislodged from an actor, don't keep trying to stick to it while falling
if (HitActor.bTearOff || HitActor.bDeleteMe || HitActor.bPendingDelete || HitActor == PrevStuckToActor)
{
return false;
}
StaticMeshComp = StaticMeshComponent(HitComp);
if (StaticMeshComp != none)
{
// NOTE: Door actors fall into this category!
// pass through meshes that can move
return !StaticMeshComp.CanBecomeDynamic();
}
KFP = KFPawn_Human(HitActor);
if (KFP != none)
{
// bounce off of player pawns, stick to other pawns
return false;
}
D = KFDestructibleActor(HitActor);
if (D != none)
{
// don't react to client-side-only destructibles, stick to others
return D.ReplicationMode != RT_ClientSide;
}
return true;
}
/** Stops movement of projectile and calculates orientation to surface */
simulated function Stick(Actor HitActor, vector HitLocation, vector HitNormal, const out TraceHitInfo HitInfo)
{
local int BoneIdx;
local KFPawn_Monster HitMonster;
local array<ImpactInfo> HitZoneImpactList;
local vector StartTrace, EndTrace, Direction, ClosestBoneLocation;
local name BoneName;
BoneName = HitInfo.BoneName;
HitMonster = KFPawn_Monster(HitActor);
if (HitMonster != none)
{
// get injury hit zone
StartTrace = HitLocation;
Direction = Normal(Velocity);
EndTrace = StartTrace + Direction * (HitMonster.CylinderComponent.CollisionRadius * 6.0);
TraceProjHitZones(HitMonster, EndTrace, StartTrace, HitZoneImpactList);
if (BoneName == '')
{
// get the best bone to attach to
ClosestBoneLocation = HitMonster.Mesh.GetClosestCollidingBoneLocation(HitLocation, true, false);
BoneName = HitMonster.Mesh.FindClosestBone(ClosestBoneLocation, ClosestBoneLocation);
}
// do impact damage
if (KFWeapon(Owner) != none)
{
HitZoneImpactList[0].RayDir = Normal(EndTrace - StartTrace); // add a raydir here since TraceProjHitZones doesn't fill this out (so certain afflictions apply)
KFWeapon(Owner).HandleProjectileImpact(WeaponFireMode, HitZoneImpactList[0], PenetrationPower);
}
}
if (!IsZero(HitLocation))
{
SetLocation(HitLocation);
}
SetStickOrientation(HitNormal);
BoneIdx = INDEX_NONE;
if (BoneName != '')
{
BoneIdx = GetBoneIndexFromActor(HitActor, BoneName);
}
StickToActor(HitActor, HitInfo.HitComponent, BoneIdx, true);
if (Role < ROLE_Authority)
{
Outer.ServerStick(HitActor, BoneIdx, StuckToLocation, StuckToRotation);
}
if (WorldInfo.NetMode != NM_DedicatedServer && StickAkEvent != none)
{
PlaySoundBase(StickAkEvent);
}
}
/** Changes the base of the charge to the stick actor and sets its relative loc/rot */
simulated function StickToActor(Actor StickTo, PrimitiveComponent HitComp, int BoneIdx, optional bool bCalculateRelativeLocRot)
{
local SkeletalMeshComponent SkelMeshComp;
local Name BoneName;
local vector RelStuckToLocation;
local rotator RelStuckToRotation;
local KFPawn StickToPawn;
StickToPawn = KFPawn(StickTo);
if (bCanPin && (StickToPawn == none || StickToPawn.bCanBePinned))
{
// if StickTo pawn is dead, pin it and keep flying
if (Role == ROLE_Authority)
{
if (StickToPawn != none && !StickToPawn.IsAliveAndWell())
{
if (PinPawn == none)
{
Pin(StickTo, BoneIdx);
}
return;
}
}
if (WorldInfo.NetMode != NM_DedicatedServer && PinPawn != none)
{
if (StickToPawn == none)
{
// Pin pinned pawn to StickTo actor
//PinPawn.Mesh.RetardRBLinearVelocity(vector(Rotation), 0.75);
PinPawn.Mesh.SetRBPosition(Location, PinBoneName);
PinConstraint = Spawn(class'RB_ConstraintActorSpawnable',,,Location);
PinConstraint.InitConstraint(PinPawn, none, PinBoneName, '');
}
PinPawn = none;
}
}
else if (StickToPawn != none && !StickToPawn.IsAliveAndWell())
{
return;
}
SetPhysics(PHYS_None);
PrevStuckToActor = StuckToActor;
StuckToActor = StickTo;
StuckToBoneIdx = BoneIdx;
// if we found a skel mesh, set our base to it and set relative loc/rot
if (BoneIdx != INDEX_NONE)
{
SkelMeshComp = SkeletalMeshComponent(HitComp);
BoneName = SkelMeshComp.GetBoneName(BoneIdx);
if (bCalculateRelativeLocRot)
{
StuckToLocation = Location;
StuckToRotation = Rotation;
}
SkelMeshComp.TransformToBoneSpace(BoneName, StuckToLocation, StuckToRotation, RelStuckToLocation, RelStuckToRotation);
SetBase(StickTo,, SkelMeshComp, BoneName);
SetRelativeLocation(RelStuckToLocation);
SetRelativeRotation(RelStuckToRotation);
}
// otherwise, just set our base
else
{
if (bCalculateRelativeLocRot)
{
// set replicated loc/rot
StuckToLocation = Location;
StuckToRotation = Rotation;
}
else
{
// set loc/rot to replicated loc/rot
SetLocation(StuckToLocation);
SetRotation(StuckToRotation);
}
SetBase(StickTo);
}
}
simulated function Pin(Actor PinTo, int BoneIdx)
{
if (Role == ROLE_Authority)
{
bUpdateSimulatedPosition = false;
PinActor = PinTo;
PinBoneIdx = BoneIdx;
}
PinPawn = Pawn(PinTo);
PinBoneName = PinPawn.Mesh.GetBoneName(BoneIdx);
StuckToActor = none;
StuckToBoneIdx = INDEX_None;
SetBase(none);
SetPhysics(PHYS_Falling);
if (WorldInfo.NetMode != NM_Standalone)
{
SetLocation(StuckToLocation);
SetRotation(StuckToRotation);
}
Velocity = Speed * vector(Rotation);
}
/** Attempts to retrieve skeletal mesh from actor */
simulated function SkeletalMeshComponent GetActorSkeletalMesh(Actor StickActor)
{
local Pawn P;
local SkeletalMeshActor SM;
P = Pawn(StickActor);
if (P != none)
{
return P.Mesh;
}
SM = SkeletalMeshActor(StickActor);
if (SM != none)
{
return SM.SkeletalMeshComponent;
}
return none;
}
/** Replicates stick to server from client */
function ServerStick(Actor StickTo, int BoneIdx, vector StickLoc, rotator StickRot)
{
bUpdateSimulatedPosition = true;
StuckToLocation = StickLoc;
StuckToRotation = StickRot;
bForceNetUpdate = true;
if (PinPawn != none)
{
PinPawn = none;
}
ReplicatedStick(StickTo, BoneIdx);
}
/** Calls "Stick" with replicated info */
simulated function ReplicatedStick(Actor StickTo, int BoneIdx)
{
StickToActor(StickTo, GetActorSkeletalMesh(StickTo), BoneIdx);
}
/** Gets index for passed-in bone name for different kinds of actors that have differently-named skeletalmeshcomponents */
simulated function int GetBoneIndexFromActor(Actor HitActor, Name BoneName)
{
local Pawn P;
local SkeletalMeshActor SM;
P = Pawn(HitActor);
if (P != none)
{
return P.Mesh.MatchRefBone(BoneName);
}
SM = SkeletalMeshActor(HitActor);
if (SM != none)
{
return SM.SkeletalMeshComponent.MatchRefBone(BoneName);
}
return INDEX_NONE;
}
/** Resets physics/collision vars to defaults */
simulated function UnStick()
{
PrevStuckToActor = StuckToActor;
StuckToActor = none;
StuckToBoneIdx = INDEX_NONE;
StuckToLocation = vect(0,0,0);
StuckToRotation = rot(0,0,0);
SetBase(none);
SetPhysics(default.Physics);
}
simulated function UnPin()
{
if (PinConstraint != none)
{
PinConstraint.TermConstraint();
}
PinConstraint = none;
PinPawn = none;
}
simulated function Tick(float DeltaTime)
{
local int i;
local Pawn P;
local KFFracturedMeshActor FracMesh;
local KFDoorActor Door;
local KFDestructibleActor Destructible;
local Actor StuckTo;
local vector HitLocation, HitNormal;
local TraceHitInfo HitInfo;
local Actor HitActor;
local float PinRad;
local vector VFront;
local float StickPct;
// @todo jdr: REMOVE THESE WHEN WE HAVE A BETTER SYSTEM
local float PinRadMagicNumber, StickPctMagicNumber;
if (PinPawn != none)
{
// @todo jdr: REMOVE MAGIC NUMBERS FROM COMMON CODE
// Ideally, this code retrieves values from the projectile, or we could add tuneables to
// the helper than can be set in each instance of the helper
VFront = Normal(Velocity);
StickPct = 1.0;
PinRadMagicNumber = 3.0;
StickPctMagicNumber = 0.3;
if (IsZero(PinLocation))
{
PinRad = PinPawn.CylinderComponent.CollisionRadius * PinRadMagicNumber;
HitActor = Trace(HitLocation, HitNormal, Location, Location + VFront * PinRad,,, HitInfo, TRACEFLAG_Bullet);
if (HitActor != none && HitActor != PinActor && Pawn(HitActor) == none && GetImpactResult(HitActor, HitInfo.HitComponent))
{
GravityScale = 0.0;
PinLocation = HitLocation;
PinHitActor = HitActor;
}
}
if (!IsZero(PinLocation))
{
PinRad = PinPawn.CylinderComponent.CollisionRadius * PinRadMagicNumber;
StickPct = VSize(PinLocation - Location) / PinRad;
Velocity = VFront * MaxSpeed * StickPct * StickPct;
}
if (WorldInfo.NetMode != NM_DedicatedServer)
{
PinPawn.Mesh.SetRBLinearVelocity(Velocity);
PinPawn.Mesh.SetRBPosition(Location, PinBoneName);
if (Instigator != none && Instigator.IsLocallyControlled() && StickPct < StickPctMagicNumber)
{
Stick(PinHitActor, Location, HitNormal, HitInfo);
}
}
}
StuckTo = StuckToActor;
if (StuckTo != none)
{
// always restart movement if torn off
if (StuckTo.bTearOff && PinPawn == none)
{
UnStick();
return;
}
// if the bone we're stuck to is hidden (just head, probably), detatch
P = Pawn(StuckTo);
if (P != none)
{
if (P.Mesh.IsBoneHidden(StuckToBoneIdx))
{
UnStick();
}
return;
}
// if the non-pawn actor we're stuck to is going away (could be due to non-relevancy, in which case they will not be torn off), detatch
if (StuckTo.bDeleteMe || StuckTo.bPendingDelete)
{
UnStick();
return;
}
// if the glass we're stuck to is fractured, detatch
FracMesh = KFFracturedMeshActor(StuckTo);
if (FracMesh != none && FracMesh.bHasLostChunk)
{
UnStick();
return;
}
// if the door we're stuck to is moving, detatch
// (we can't set our base to doors because they're world geometry, so we won't follow when they move)
Door = KFDoorActor(StuckTo);
if (Door != none && (!Door.bDoorMoveCompleted || Door.bIsDestroyed))
{
UnStick();
return;
}
if (LastTouchComponent != none)
{
// if the replicated destructible we're stuck to is destroyed, detatch
Destructible = KFDestructibleActor(StuckTo);
if (Destructible != none)
{
for (i = 0; i < Destructible.SubObjects.Length; ++i)
{
if (Destructible.SubObjects[i].Mesh == LastTouchComponent && Destructible.SubObjects[i].Health <= 0)
{
UnStick();
return;
}
}
}
}
}
}