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

2040 lines
56 KiB
Ucode

//=============================================================================
// KFDoorActor
//=============================================================================
// Class for placeable doors. Used intead of the InterpActor method so that
// we can easily replicate damage states and use skeletal animation.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Andrew "Strago" Ladenberger
//=============================================================================
class KFDoorActor extends KFWeldableActor
hidecategories(Movement, Collision, Physics, Object, Mobile)
placeable
native
nativeReplication;
`include(KFGame\KFGameAnalytics.uci);
/** The base width of a doors SkeletalMeshComponent */
const SkeletalMesh_Width = 256;
const KActorOffset = 25;
/** Reference to the door trigger */
var KFDoorTrigger DoorTrigger;
var bool bIsInteractive;
/*********************************************************************************************
* @name Meshes / Materials / Particles
********************************************************************************************* */
struct native DoorMeshAttachment
{
var() StaticMeshComponent Component; // Component which needs to be attached
var() name AttachTo; // Bone or socket name to which the attachment should be attached
var() bool bSocketAttach; // Whether to attach it to a socket or a bone
structdefaultproperties
{
AttachTo=DoorLeft
}
};
/** Information of all the mesh attachments for the vehicle */
var() array<DoorMeshAttachment> MeshAttachments;
/** Mesh for the weld that goes between the center of two doors or the end of the left door */
var() StaticMeshComponent CenterWeldComponent;
/** skeletal mesh used for open/close animations */
var() const editconst SkeletalMeshComponent SkeletalMeshComp;
var array<MaterialInstanceConstant> HealthMICs;
enum EDoorMaterialType
{
EDMT_Metal,
EDMT_Wood
};
var() EDoorMaterialType DoorMaterial;
/*********************************************************************************************
* @name Open / Close
********************************************************************************************* */
enum EDoorMechanism
{
EDM_Hinge,
EDM_Slide,
EDM_Lift,
};
/** type of networking to use */
var(Opening) EDoorMechanism DoorMechanism;
/** The amount of time before a door can be used again */
var(Opening) float CooldownTime;
/** The time it takes to open / close a door */
var(Opening) float OpenBlendTime;
/** ONLY FOR HINGED DOORS Defines how far we want to open our hinged doors in degrees */
var(Opening) int HingedRotation;
/** ONLY FOR SLIDING DOORS Defines how far we want sliding doors to move */
var(Opening) int SlideTranslation;
/** ONLY FOR LIFT DOORS Defines how far we want lift doors to raise */
var(Opening) int LiftTranslation;
/** If set, door opens and closes automatically */
var(Opening) const editconst bool bAutomaticDoor;
/** Has the last door move (open/shut) completed? */
var transient bool bDoorMoveCompleted;
/** The time when the door was last used */
var transient float LastUsedTime;
/** current state of the door if it hasn't been destroyed - note it's possible for bIsDoorOpen to be false and bIsDoorDestroyed to be true so check both */
var () bool bStartDoorOpen;
var repnotify transient bool bIsDoorOpen;
var transient bool bLocalIsDoorOpen;
var transient bool bReverseHinge;
var transient bool bCanCloseDoor;
/** When true, the door can be processed for resets */
var transient bool bHasBeenDirtied;
/*********************************************************************************************
* @name Health
********************************************************************************************* */
enum EDoorFastening
{
EDF_ArcWelding,
EDF_Rivets
};
var EDoorFastening FastenerType;
/** Starting health for a door */
var() int MaxHealth;
/** Current door health for damage/destruction */
var repnotify transient int Health;
/** Should this door explode? */
var repnotify transient bool bShouldExplode;
var transient KFPlayerController ExplosionInstigatorController;
/** While the door is being attacked it takes longer to weld */
var() float CombatWeldModifier<ClampMin=0.0 | ClampMax=1.0>;
/** The amount of time a door is considered in combat after being hit */
var float CombatLength;
/** True if this projectile has already blown up, used to ensure only a single explosion. */
var repnotify transient byte HitCount;
const HIT_DIRECTION_FLAG = 0x80;
/** Last time the door took damage */
var transient float LastHitTime;
/** Cached SkelControlSingleBone */
var transient SkelControlSingleBone MovementControl;
/** Cached AnimNodeSlot */
var transient AnimNodeSlot BashSlot;
/** A scaler to fit a door into its proper frame. Double the frame size if we are only using one door for this actor */
var() float FrameSizeOfTwoDoors;
/*********************************************************************************************
* @name AI
********************************************************************************************* */
/** NavigationPoint associated with this actor for sending AI related notifications (could be a LiftCenter or DoorMarker) */
var NavigationPoint MyMarker;
/** true when AI is waiting for us to finish moving */
var bool bMonitorDoor;
//var() KFNavMeshObstacle_Door MyNavMeshObstacle;
/** Navigation handle used for pathing when using NavMesh */
//var class<KFNavigationHandle> NavigationHandleClass;
//var KFNavigationHandle MyKFNavigationHandle;
/*********************************************************************************************
* @name Physics
********************************************************************************************* */
/** The impulse to provide the doors when they are destroyed */
var() float BrokenDoorImpulse;
/** The maximum angular velocity applied to a broken hinged door*/
var() float MaxAngularVelocity;
/** settings for TryPushPawns */
const DoorWidth = 200;
const HumanPushDistance = 40;
const SlidingPushForce = 750;
const VerticalPushForce = 100;
/** Adjusts the "push" plane of the door (the threshold for pushing forward or backward) along its X-axis */
//var() float PushOriginOffset;
/** Cache of physical broken door pieces, needed so they can be deleted when the door is restored through a non-reset method */
var array<KActor> BrokenDoorPhysicsActors;
/*********************************************************************************************
* @name Effects
********************************************************************************************* */
const BashHingedAnim_F = 'DoorBash_A';
const BashHingedAnim_B = 'DoorBash_B';
const BashSlidingAnim_F = 'DoorBashSliding_A';
const BashSlidingAnim_B = 'DoorBashSliding_B';
/** sound played when the mover is interpolated forward */
var(Sound) AkBaseSoundObject OpenSound;
/** looping sound while opening */
var(Sound) AkBaseSoundObject OpeningAmbientSound;
/** sound played when mover finished moving forward */
var(Sound) AkBaseSoundObject OpenedSound;
/** sound played when the mover is interpolated in reverse */
var(Sound) AkBaseSoundObject CloseSound;
/** looping sound while closing */
var(Sound) AkBaseSoundObject ClosingAmbientSound;
/** sound played when mover finished moving backward */
var(Sound) AkBaseSoundObject ClosedSound;
/** sound played when door is destroyed */
var(Sound) AkBaseSoundObject DestroyedSound;
/** component for looping sounds */
var AkComponent AmbientSoundComponent;
/** used for playing sounds (set to center of doorway in PostBeginPlay because sometimes the actor location is under the floor and sounds don't play right) */
var transient vector SoundOrigin;
/** played when the melee attack hits world geometry */
var() DestroyedEffectParams DamageEmitter;
/** played when the melee attack hits world geometry and the door has no health */
var() array<DestroyedEffectParams> DestroyedEmitters;
/** Physics actors spawned when a door is broken */
var transient array<ParticleSystemComponent> BrokenDoorParticleEffects;
var() FXTemplate OnDoorOpenEmitterTemplate;
var ParticleSystemComponent OnDoorOpenEmitter;
/*********************************************************************************************
* @name UI
********************************************************************************************* */
/** Offset from door location (bottom) to closer to eye level for UI, FX, sounds, etc */
var transient vector VisualDoorLocation;
/** Localized strings */
var localized string ExplosiveString;
cpptext
{
virtual void TickSpecial( FLOAT DeltaSeconds );
INT* GetOptimizedRepList( BYTE* InDefault, FPropertyRetirement* Retire, INT* Ptr, UPackageMap* Map, UActorChannel* Channel );
// Ensure the SkeletalMeshComp has no pre-existing animations
virtual void PostLoad();
// Attach static meshes in PostEditChangeProperty
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent);
virtual UBOOL ShouldTrace(UPrimitiveComponent* Primitive, AActor *SourceActor, DWORD TraceFlags);
#if WITH_EDITOR
virtual INT AddMyMarker(AActor *S);
#endif
}
replication
{
if ( bNetDirty )
HitCount, bIsDoorOpen, Health, bShouldExplode, bIsInteractive;
if ( bNetDirty && DoorMechanism == EDM_Hinge )
bReverseHinge;
}
simulated event ReplicatedEvent(name VarName)
{
if (VarName == nameof(bIsDoorOpen))
{
if ( bIsDoorOpen )
{
// not already open (see InitSkelControl)
if ( MovementControl.StrengthTarget == 0 )
{
OpenDoor(None);
}
}
else
{
CloseDoor();
}
}
else if ( VarName == nameof(Health) )
{
UpdateHealthMICs();
// In case the door was destroyed and it was still welded
if( Health <= 0 )
{
UpdateIntegrityMIC();
}
}
else if ( VarName == nameof(bShouldExplode) )
{
UpdateIntegrityMIC();
}
else if ( VarName == nameof(HitCount) )
{
PlayTakeHitEffects();
}
else
{
super.ReplicatedEvent(VarName);
}
}
/*********************************************************************************************
* @name Initialization
********************************************************************************************* */
native function InitBrokenAttachment( StaticMeshComponent Attachment, KFKActorSpawnable SpawnedKActor );
/** The SkeletalMeshComponents Animations are being instanced from AnimTreeTemplate
* before PostInitAnimTree. Be sure to never set the Mesh's animations directly through
* the package */
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
MovementControl = SkelControlSingleBone(SkelComp.FindSkelControl('MovementControl'));
BashSlot = AnimNodeSlot(SkeletalMeshComp.FindAnimNode('Slot_Bash'));
// set open animation based on door mechanism
InitSkelControl();
}
simulated event PostBeginPlay()
{
local int i, NumActualDoors;
local float MyRadius, MyHeight;
local vector X,Y,Z;
Super.PostBeginPlay();
Health = MaxHealth;
LastUsedTime = WorldInfo.TimeSeconds - CooldownTime; // Ensures doors can be used immediately
// create ambient sound component if needed
if (OpeningAmbientSound != None || ClosingAmbientSound != None)
{
AmbientSoundComponent = new(self) class'AkComponent';
AttachComponent(AmbientSoundComponent);
}
InitializeDoorMIC();
if (CenterWeldComponent != none)
{
CenterWeldComponent.SetHidden(bStartDoorOpen);
}
if( class'KFAIController'.default.bUseNavMesh )
{
//InitNavigationHandle();
}
for( i = 0; i < MeshAttachments.length; ++i )
{
if( MeshAttachments[i].Component.StaticMesh != none )
{
SoundOrigin += MeshAttachments[i].Component.Bounds.Origin;
NumActualDoors++;
}
}
if( NumActualDoors > 0 )
{
SoundOrigin /= NumActualDoors;
}
else
{
SoundOrigin = Location;
}
if( NumActualDoors > 1 )
{
// With double doors, the weld UI location is in the center, so this is good for visual testing
VisualDoorLocation = WeldUILocation;
}
else
{
// With single doors, the weld UI location is at the doorstop, which isn't ideal for visual testing
// and can often intersect with the doorframe geometry. Offset visual test location to center of door instead.
// Since there are always 2 door components whether or not there are 2 actual doors, we multiply the radius by
// 0.25 instead of 0.5 to get the center (Radius / 4). -MattF
GetAxes( Rotation, X,Y,Z );
GetBoundingCylinder( MyRadius, MyHeight );
VisualDoorLocation = WeldUILocation - (MyRadius * 0.25f * Y);
}
}
/** Grab the doors materials and create MICs to visualize door damage */
simulated function InitializeDoorMIC()
{
local MaterialInstanceConstant NewMIC, AltMIC;
local byte i, MaterialIndex;
if (HealthMICs.Length <= 0 && WorldInfo.NetMode != NM_DedicatedServer)
{
if ( MeshAttachments.Length > 0 )
{
for( MaterialIndex = 0; MaterialIndex < MeshAttachments[0].Component.GetNumElements(); MaterialIndex++ )
{
if( MeshAttachments[0].Component.GetMaterial(MaterialIndex) != none )
{
// We are not using "CreateAndSetMaterialInstanceConstant" because
// a single MIC will be created for each MeshAttachment
NewMIC = new class'MaterialInstanceConstant';
NewMIC.SetParent( MeshAttachments[0].Component.GetMaterial(MaterialIndex) );
HealthMICs.AddItem(NewMIC);
// Apply the MIC to our door components
for ( i = 0; i < MeshAttachments.length; i++ )
{
// Only apply MIC to components that share the same material
if( MeshAttachments[i].Component.GetMaterial(MaterialIndex) == NewMIC.Parent )
{
MeshAttachments[i].Component.SetMaterial(MaterialIndex, NewMIC);
}
else
{
// If this mesh component had a custom material, allow it to use it instead of our mirrored MIC
AltMIC = new class'MaterialInstanceConstant';
AltMIC.SetParent( MeshAttachments[i].Component.GetMaterial(MaterialIndex) );
HealthMICs.AddItem( AltMIC );
MeshAttachments[i].Component.SetMaterial( MaterialIndex, AltMIC );
}
}
}
}
}
if ( CenterWeldComponent != none && CenterWeldComponent.GetMaterial(0) != none )
{
IntegrityMIC = CenterWeldComponent.CreateAndSetMaterialInstanceConstant( 0 );
IntegrityMIC.SetScalarParameterValue('doorWeld', 0.f);
UpdateIntegrityMIC();
}
}
}
/** Set up skel control for open/close */
simulated function InitSkelControl()
{
MovementControl.BlendInTime = OpenBlendTime;
MovementControl.BlendOutTime = OpenBlendTime;
switch (DoorMechanism)
{
case EDM_Hinge:
MovementControl.bApplyTranslation = false;
MovementControl.bApplyRotation = true;
MovementControl.BoneRotation.Yaw = HingedRotation * DegToUnrRot;
break;
case EDM_Slide:
MovementControl.bApplyTranslation = true;
MovementControl.bApplyRotation = false;
MovementControl.BoneTranslation.Y = SlideTranslation;
MovementControl.BoneTranslation.Z = 0;
break;
case EDM_Lift:
MovementControl.bApplyTranslation = true;
MovementControl.bApplyRotation = false;
MovementControl.BoneTranslation.Y = 0;
MovementControl.BoneTranslation.Z = LiftTranslation;
break;
}
// start in level-set state. default is open.
if (Role == ROLE_Authority)
{
MovementControl.SetSkelControlStrength(bStartDoorOpen ? 1.f : 0.f, 0.f);
bIsDoorOpen = bStartDoorOpen;
bLocalIsDoorOpen = bStartDoorOpen;
WeldIntegrity = (bStartWelded && !bStartDoorOpen) ? MaxWeldIntegrity : 0;
}
}
/*********************************************************************************************
* @name Open / Close
********************************************************************************************* */
/** Returns true if door is open or destroyed */
native function bool IsCompletelyOpen() const;
/** Handling Toggle event from Kismet. */
simulated function OnToggle(SeqAct_Toggle action)
{
if ( bAutomaticDoor )
return;
if (action.InputLinks[0].bHasImpulse && WeldIntegrity <= 0)
{
OpenDoor(None);
}
else if (action.InputLinks[1].bHasImpulse)
{
CloseDoor();
}
else if (action.InputLinks[2].bHasImpulse)
{
UseDoor(None);
}
}
/** Server only */
function UseDoor(Pawn P)
{
// Doors cannot be used until the cooldown time has passed
if ( `TimeSince(LastUsedTime) < CooldownTime)
{
return;
}
if ( bAutomaticDoor )
return;
if ( bIsDoorOpen )
{
CloseDoor();
}
else if ( WeldIntegrity <= 0 )
{
OpenDoor(P);
}
LastUsedTime = WorldInfo.TimeSeconds;
}
/** automatic doors open when bumped */
event Bump( Actor Other, PrimitiveComponent OtherComp, Vector HitNormal )
{
local Pawn P;
if ( bAutomaticDoor && !bIsDoorOpen && Role == ROLE_Authority )
{
P = Pawn(Other);
if ( P != None && WeldIntegrity <= 0 )
{
OpenDoor(P);
SetTimer(3.f, false, nameof(CloseDoor));
}
}
`if(`__TW_PATHFINDING_)
if( KFPawn(Other) != none && !KFPawn(Other).IsHumanControlled() )
{
`AILog_Ext( GetFuncName()$" "$self$" by "$KFPawn(Other).MyKFAIC, 'Doors', KFPawn(Other).MyKFAIC );
}
`endif
}
/** Initiate door opening animation */
simulated protected function OpenDoor(Pawn P)
{
local Vector Loc;
local Rotator Rot;
if ( bIsDestroyed || bLocalIsDoorOpen || WeldIntegrity > 0)
{
return;
}
bIsDoorOpen = true;
bForceNetUpdate = true;
bDoorMoveCompleted = false;
// Local (non-replicated) open flag
bLocalIsDoorOpen = true;
if ( DoorMechanism == EDM_Hinge )
{
OpenSwingingDoor(P);
}
//if( MyNavMeshObstacle != none )
//{
// MyNavMeshObstacle.SetEnabled(false);
//}
SetTickIsDisabled(false);
PlayMovingSound(false);
MovementControl.SetSkelControlActive(true);
if (OnDoorOpenEmitterTemplate.ParticleTemplate != none && (OnDoorOpenEmitter == none || !OnDoorOpenEmitter.bIsActive))
{
if (OnDoorOpenEmitter != none)
{
OnDoorOpenEmitter.DeactivateSystem();
}
Loc = Location + OnDoorOpenEmitterTemplate.RelativeOffset;
Rot = Rotation + OnDoorOpenEmitterTemplate.RelativeRotation;
OnDoorOpenEmitter = WorldInfo.MyEmitterPool.SpawnEmitter(OnDoorOpenEmitterTemplate.ParticleTemplate, Loc, Rot);
}
}
/** Determine direction of hinge and push pawns back */
simulated private function OpenSwingingDoor(Pawn P)
{
local vector X, Y, Z;
local int DefaultYaw;
if ( Role == ROLE_Authority )
{
GetAxes(Rotation, X, Y, Z);
// (Server Only) if the animation is not playing update hinge direction
if ( P != None && MovementControl.BlendTimeToGo <= 0 )
{
bReverseHinge = (X dot (P.Location - Location) > 0.f) ? false : true;
}
}
DefaultYaw = Abs(MovementControl.BoneRotation.Yaw);
MovementControl.BoneRotation.Yaw = ( bReverseHinge ) ? -DefaultYaw : DefaultYaw;
}
/** To close the door, just reverse the animation */
simulated private function CloseDoor()
{
if( bIsDestroyed || !bLocalIsDoorOpen || !bCanCloseDoor)
{
return;
}
// If door has been closed, it's dirty
bHasBeenDirtied = true;
bIsDoorOpen = false;
bForceNetUpdate = true;
bDoorMoveCompleted = false;
// Local (non-replicated) open flag
bLocalIsDoorOpen = false;
SetTickIsDisabled(false);
PlayMovingSound(true);
//if( MyNavMeshObstacle != none )
//{
// MyNavMeshObstacle.SetEnabled(true);
//}
MovementControl.SetSkelControlActive(false);
if ( DoorMechanism == EDM_Hinge )
{
SetTimer(OpenBlendTime * 0.65, false, nameof(TryPushPawns));
}
else
{
SetTimer(OpenBlendTime * 0.5, false, nameof(TryPushPawns));
}
}
/** Notification when a door finishes opening or closing */
event NotifyDoorMoveCompleted( bool bOpened )
{
if( bOpened )
{
OnOpenFinish();
}
else
{
OnCloseFinish();
}
SetRBCollideWithDeadPawn(!bOpened);
}
/** Called once we've finished opening the door */
function OnOpenFinish()
{
local DoorMarker DoorNav;
if (AmbientSoundComponent != None)
{
AmbientSoundComponent.StopEvents();
}
DoorNav = DoorMarker(MyMarker);
if (DoorNav != None)
{
if( bMonitorDoor )
{
NotifyAIDoorOpened();
}
DoorNav.MoverOpened();
}
if (OpenedSound != None)
{
PlaySoundBase(OpenedSound,,,, SoundOrigin);
}
}
/** Called once we've finished closing the door */
function OnCloseFinish()
{
local DoorMarker DoorNav;
if (AmbientSoundComponent != None)
{
AmbientSoundComponent.StopEvents();
}
DoorNav = DoorMarker(MyMarker);
if (DoorNav != None)
{
DoorNav.MoverClosed();
}
if (ClosedSound != None)
{
PlaySoundBase(ClosedSound,,,, SoundOrigin);
}
}
/*********************************************************************************************
* @name Momentum Push
********************************************************************************************* */
// Used when a pawn is standing ontop of a door
function vector GetPushDirection( Vector PawnLocation )
{
local bool bInFrontOfDoor;
local vector DoorX, DoorY, DoorZ, PushDir;
GetAxes(Rotation, DoorX, DoorY, DoorZ);
bInFrontOfDoor = ( DoorX dot ( PawnLocation - Location ) > 0.f );
PushDir = SlidingPushForce * DoorX;
PushDir *= ( bInFrontOfDoor ) ? 1 : -1;
PushDir.z = 50;
return PushDir;
}
/** Called only if a a door closes on a pawn*/
private function TryPushPawns()
{
local Pawn P;
local bool bInFrontOfDoor;
local vector DoorX, DoorY, DoorZ, PushDir, OffsetLocation;
local rotator DoorYRot;
GetAxes(Rotation, DoorX, DoorY, DoorZ);
DoorYRot = rotator(DoorY);
OffsetLocation = Location + DoorX;// * PushOriginOffset;
foreach WorldInfo.AllPawns( class'Pawn', P, Location, DoorWidth )
{
if ( !P.IsAliveAndWell() )
continue;
if( !bIsDoorOpen && P.IsPlayerOwned() )
{
if( PointDistToPlane( OffsetLocation, DoorYRot, P.Location ) < HumanPushDistance )
{
bInFrontOfDoor = (DoorX dot (P.Location - OffsetLocation) > 0.f);
PushDir = SlidingPushForce * DoorX;
if( DoorMechanism == EDoorMechanism.EDM_Hinge )
{
if ( bReverseHinge )
{
PushDir *= -1;
}
}
else
{
PushDir *= ( bInFrontOfDoor ) ? 1 : -1;
}
PushDir.Z += VerticalPushForce; //Small vertical fix to help the player avoid tripping over mesh edges in door frames
P.AddVelocity( PushDir, P.Location, class'KFDT_Bludgeon');
}
}
// prevent closing on an AI
else if ( !bIsDoorOpen )
{
if( PointDistToPlane( Location, DoorYRot, P.Location ) < (0.85 * P.CylinderComponent.default.CollisionRadius) )
{
OpenDoor(None);
}
}
}
}
/*********************************************************************************************
* @name Damage and Welding
********************************************************************************************* */
simulated function InitializeWeldableComponent()
{
WeldableComponent.SetOwner(self);
WeldableComponent.WeldIntegrity = (bStartWelded && !bStartDoorOpen) ? MaxWeldIntegrity : 0;
WeldableComponent.MaxWeldIntegrity = MaxWeldIntegrity;
WeldableComponent.DemoWeldRequired = DemoWeldRequired;
WeldableComponent.bWeldable = true;
WeldableComponent.bUnweldable = true;
WeldableComponent.bRepairable = true;
WeldableComponent.Delegate_AdjustWeldAmount = AdjustWeldCompWeldAmount;
WeldableComponent.Delegate_OnWeldIntegrityChanged = OnWeldCompWeldIntegrityChanged;
WeldableComponent.Delegate_OnRepairProgressChanged = OnWeldCompRepairProgressChanged;
WeldableComponent.SetCollisionCylinderSize(200, 200);
}
function AdjustWeldCompWeldAmount(out int Amount)
{
// reduce weld strength if it's being attacked
if ( UnderAttack() )
{
Amount *= CombatWeldModifier;
}
}
simulated function OnWeldCompWeldIntegrityChanged(int Amount, KFPawn Welder)
{
if (Role == ROLE_Authority)
{
FastenWeld(Amount, Welder);
WeldableComponent.SetWeldIntegrity(WeldIntegrity);
}
else
{
WeldIntegrity = WeldableComponent.WeldIntegrity;
DemoWeld = WeldableComponent.DemoWeld;
UpdateIntegrityMIC();
}
}
simulated function OnWeldCompRepairProgressChanged(float Amount, KFPawn Welder)
{
if (Role == ROLE_Authority)
{
Repair(Amount, Welder);
}
else
{
RepairProgress = WeldableComponent.RepairProgress;
}
}
/** When welded shut, do damage to the door */
event TakeDamage(int Damage, Controller EventInstigator, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
local KFPawn_Monster KFPM;
local KFCharacterInfo_Monster MonsterInfo;
local class<KFDamageType> KFDT;
if ( Role < ROLE_Authority )
{
return;
}
if (!bIsInteractive)
{
return;
}
// call Actor's version to handle any SeqEvent_TakeDamage for scripting
Super.TakeDamage(Damage, EventInstigator, HitLocation, Momentum, DamageType, HitInfo, DamageCauser);
// check can be damaged
if ( bIsDestroyed || !AllowDamageFrom(EventInstigator, DamageType) )
{
return;
}
// If the door is welded damage the health and the weld integrity
if ( WeldIntegrity > 0 )
{
KFPM = KFPawn_Monster(DamageCauser);
if( KFPM != none )
{
MonsterInfo = KFPM.GetCharacterMonsterInfo();
if( MonsterInfo != none )
{
PlaySoundBase(GetSoundEffectFromType(MonsterInfo),,,, SoundOrigin);
}
}
if ( Health > 0 )
{
Health = Max(Health - Damage, 0 );
UpdateHealthMICs();
}
UpdateWeldIntegrity(-Damage);
WeldableComponent.UpdateWeldIntegrity(-Damage);
// if weld is broken
if ( WeldIntegrity <= 0 || Health <= 0 )
{
DestroyDoor( EventInstigator );
}
if ( !bIsDestroyed )
{
IncrementHitCount( EventInstigator.Pawn );
PlayTakeHitEffects();
}
`DialogManager.PlayDoorTakeDamageDialog( self );
LastHitTime = WorldInfo.TimeSeconds;
bForceNetUpdate = true;
}
// failsafe for AI deciding to attack the door instead of opening it
else if( !bIsDoorOpen && EventInstigator != none && EventInstigator.Pawn != none && EventInstigator.GetTeamNum() == 255 )
{
KFDT = class<KFDamageType>( DamageType );
if( KFDT != none && KFDT.default.bAllowAIDoorDestruction )
{
IncrementHitCount( EventInstigator.Pawn );
DestroyDoor( EventInstigator );
}
else if( class<KFDT_Ballistic>(DamageType) == none )
{
OpenDoor( EventInstigator.Pawn );
}
}
}
/** Destroys this door */
function DestroyDoor( optional Controller DestructionInstigator )
{
WeldIntegrity = 0;
DemoWeld = 0;
Health = 0;
WeldableComponent.SetWeldIntegrity(0);
WeldableComponent.SetDemoWeld(0);
//UpdateHealthMICs();
UpdateIntegrityMIC();
// Weld and health are gone, destroy the door!
PlayDestroyed();
if( MyMarker != none && bMonitorDoor )
{
`RecordAIDestroyedDoor( KFAIController(DestructionInstigator), self, "Health:"$Health );
NotifyAIDoorOpened();
}
}
/** Returns true if the specificed damage causer can damage this door */
function bool AllowDamageFrom(Controller EventInstigator, class<DamageType> DamageType)
{
// Human team (0) can only do explosive damage
if ( EventInstigator == none || (EventInstigator.GetTeamNum() == 0 && class<KFDT_Explosive>(DamageType) == none) )
{
return false;
}
return true;
}
/** Increase the weld integrity - Network: Server only */
function FastenWeld(int Amount, optional KFPawn Welder)
{
local KFPlayerController PC;
local KFPerk WelderPerk;
if ( bIsDestroyed )
{
return;
}
// handle unfasten
if ( Amount < 0 )
{
if ( WeldIntegrity > 0 )
{
UpdateWeldIntegrity(Amount);
if( !BeingWelded() )
{
if( !BeingUnwelded() )
{
WelderPawn = Welder;
}
}
`DialogManager.PlayUnweldDialog( Welder, self, WelderPawn );
if( WelderPawn == Welder )
{
LastUnweldTime = WorldInfo.TimeSeconds;
}
}
if( WeldIntegrity <= 0 )
{
bShouldExplode = false;
DemoWeld = 0;
ExplosionInstigatorController = none;
WeldableComponent.SetDemoWeld(0);
}
}
// handle fasten
else if ( !bIsDoorOpen && WeldIntegrity < MaxWeldIntegrity )
{
PC = KFPlayerController(Welder.Controller);
if ( PC != None )
{
PC.AddWeldPoints(Amount);
}
// reduce weld strength if it's being attacked
if ( UnderAttack() )
{
Amount *= CombatWeldModifier;
}
WelderPerk = PC.GetPerk();
if( WelderPerk != none && WelderPerk.CanExplosiveWeld() )
{
AddExplosiveWeld( Amount, PC );
}
// If weld integrity has increased from 0, it's dirty
bHasBeenDirtied = true;
UpdateWeldIntegrity(Amount);
if( WeldIntegrity == MaxWeldIntegrity )
{
PC.UnlockHoldOut();
}
if( !BeingUnwelded() )
{
if( !BeingWelded() )
{
WelderPawn = Welder;
}
}
`DialogManager.PlayWeldDialog( Welder, self, WelderPawn );
if( WelderPawn == Welder )
{
LastWeldTime = WorldInfo.TimeSeconds;
}
}
else if( !bIsDoorOpen && WeldIntegrity >= MaxWeldIntegrity &&
DemoWeld < DemoWeldRequired )
{
PC = KFPlayerController(Welder.Controller);
if ( PC != None )
{
WelderPerk = PC.GetPerk();
if( WelderPerk != none && WelderPerk.CanExplosiveWeld() )
{
AddExplosiveWeld( Amount, PC );
UpdateIntegrityMIC();
bForceNetUpdate = true;
}
}
}
else if ( bIsDoorOpen )
{
CloseDoor();
}
}
simulated function CompleteRepair()
{
ResetDoor(true);
}
/** Increase the weld integrity - Network: Server only */
function Repair(float Amount, optional KFPawn Welder)
{
local byte ByteAmount;
local KFPlayerController KFPC;
if ( bIsDestroyed )
{
ByteAmount = FloatToByte(Amount);
if ( RepairProgress + ByteAmount >= 255 )
{
CompleteRepair();
if( WorldInfo.NetMode != NM_StandAlone )
{
bWasRepaired = true;
SetTimer( 0.1f, false, nameOf(Timer_ResetRepairFlag) );
}
if (Welder != none)
{
KFPC = KFPlayerController(Welder.Controller);
if (KFPC != none)
{
KFPC.DoorRepaired();
}
}
}
else
{
RepairProgress += ByteAmount;
}
bForceNetUpdate = true;
}
}
/** Restores repair flag to FALSE so door can be repaired later */
function Timer_ResetRepairFlag()
{
bWasRepaired = false;
}
function AddExplosiveWeld( int Amount, KFPlayerController PC )
{
super.AddExplosiveWeld(Amount, PC);
if( DemoWeld >= DemoWeldRequired && !bShouldExplode )
{
ExplosionInstigatorController = PC;
bShouldExplode = true;
bForceNetUpdate = true;
}
}
function bool CanExplosiveWeld()
{
return true;
}
/*********************************************************************************************
* @name Animation & FX
********************************************************************************************* */
/**
* This function's responsibility is to signal clients to play hit effects on the door
*
* Network: Server
*/
function IncrementHitCount( Pawn P )
{
local vector X, Y, Z;
if ( !bIsDoorOpen && P != none )
{
bForceNetUpdate = true;
GetAxes(Rotation, X, Y, Z);
HitCount++;
if (X dot (P.Location - Location) > 0.f)
{
// Set the last bit to indicate the hit direction
HitCount = HitCount | HIT_DIRECTION_FLAG;
}
else
{
// Clear out the last bit to indicate the hit direction
HitCount = HitCount & byte(~HIT_DIRECTION_FLAG);
}
}
}
/** Play bash animations, particle effects, and destruction effects */
simulated function PlayTakeHitEffects()
{
local name AnimName;
local bool bReverseDir; // if true rotate the direction of the particle effect and bash anim
if ( bIsDestroyed || bIsDoorOpen || WorldInfo.NetMode == NM_DedicatedServer )
{
return;
}
SetTickIsDisabled(false);
if ( `TimeSince(CreationTime) > 1.0f && ActorEffectIsRelevant(Instigator, false) )
{
bReverseDir = (HitCount & HIT_DIRECTION_FLAG) > 0;
AnimName = GetBashAnimName(bReverseDir);
BashSlot.PlayCustomAnim( AnimName, 1.f );
SpawnParticlesFromEffectParam( DamageEmitter, bReverseDir );
}
}
/** Returns name of animation to play when hit by zed */
simulated function name GetBashAnimName(bool bReverse)
{
if ( DoorMechanism == EDM_Hinge )
{
return (bReverse) ? BashHingedAnim_F : BashHingedAnim_B;
}
return (bReverse) ? BashSlidingAnim_F : BashSlidingAnim_B;
}
/** Spawn and offset particle effects according to how the mapper set it up */
simulated function SpawnParticlesFromEffectParam( DestroyedEffectParams EffectParam, optional bool bReverseDir )
{
local Vector Loc;
local Rotator Rot;
if( WorldInfo.NetMode == NM_DedicatedServer )
{
return;
}
if ( EffectParam.ParticleEffect != none )
{
Loc = Location + EffectParam.RelativeOffset;
Rot = Rotation + EffectParam.RelativeRotation;
Rot.Yaw += ( bReverseDir ) ? 32768 : 0;
BrokenDoorParticleEffects.AddItem( WorldInfo.MyEmitterPool.SpawnEmitter(EffectParam.ParticleEffect, Loc, Rot) );
}
}
/** Activates the doors physics assets and particles once a door is destroyed */
simulated function PlayDestroyed()
{
bForceNetUpdate = true;
SetTickIsDisabled(false);
bIsDestroyed = true;
RepairProgress = 0;
WeldIntegrity = 0;
WeldableComponent.SetDestroyed(true);
WeldableComponent.SetRepairProgress(0);
WeldableComponent.SetWeldIntegrity(0);
// If door is destroyed, it's dirty
bHasBeenDirtied = true;
if ( DestroyedSound != None )
{
PlaySoundBase(DestroyedSound,,,, SoundOrigin);
}
if ( DoorMechanism == EDM_Hinge )
{
DestroyHingedDoor();
}
else
{
DestroyNonPhysicsDoor();
}
if( bShouldExplode )
{
//blowup
PlayExplosion();
bShouldExplode = false;
}
// notify owning trigger
DoorTrigger.OnDestroyOrReset();
}
simulated function PlayExplosion()
{
local KFExplosionActor ExploActor;
local GameExplosion ExplosionTemplate;
local KFPawn ExplosionInstigator;
local KFPerk InstigatorPerk;
local class<KFExplosionActor> KFEAR;
if ( Role < ROLE_Authority )
{
return;
}
ExplosionInstigator = KFPawn(ExplosionInstigatorController.Pawn);
InstigatorPerk = ExplosionInstigator.GetPerk();
if( ExplosionInstigator != none && InstigatorPerk != none )
{
KFEAR = InstigatorPerk.DoorShouldNuke() ? class'KFPerk_Demolitionist'.static.GetNukeExplosionActorClass() : class'KFExplosionActorReplicated';
ExploActor = Spawn(KFEAR, self,, DoorTrigger.Location,,, true);
if( ExploActor != None )
{
ExploActor.InstigatorController = ExplosionInstigatorController;
ExploActor.Instigator = ExplosionInstigator;
ExplosionTemplate = InstigatorPerk.DoorShouldNuke() ? class'KFPerk_Demolitionist'.static.GetNukeExplosionTemplate() : class'KFPerk_Demolitionist'.static.GetDoorTrapsExplosionTemplate();
ExplosionTemplate.Damage = class'KFPerk_Demolitionist'.static.GetDoorTrapsExplosionTemplate().Damage;
ExploActor.Explode( ExplosionTemplate );
}
}
}
/** If the door is sliding or lifting then only play particle effects and open the door */
simulated function DestroyNonPhysicsDoor()
{
local byte i;
for( i = 0; i < DestroyedEmitters.Length; i++ )
{
SpawnParticlesFromEffectParam( DestroyedEmitters[i] );
}
// @todo: move to destroyed (use blend in time)
if ( DoorMechanism == EDM_Slide )
{
BashSlot.PlayCustomAnim('Door_Slide_broken', 1.f, 0.15f, 0.2f, true, true);
}
}
/** Restores this door to the way it was at the start of a map */
simulated function ResetDoor( optional bool bRepaired )
{
local int i;
local DoorMarker DoorNav;
// Reset server-controlled variables and force an update
if( Role == ROLE_Authority )
{
Health = MaxHealth;
bIsDestroyed = false;
bShouldExplode = false;
WeldIntegrity = 0;
DemoWeld = 0;
WeldableComponent.SetDestroyed(false);
WeldableComponent.SetWeldIntegrity(0);
WeldableComponent.SetDemoWeld(0);
// Only open door if this wasn't a repair
if( bRepaired )
{
bIsDoorOpen = false;
}
else
{
bIsDoorOpen = true;
}
// Update clients immediately
bNetDirty = true;
bForceNetUpdate = true;
}
// Reset non-replicated variables
ExplosionInstigatorController = none;
bDoorMoveCompleted = true;
bHasBeenDirtied = false;
// Door has not been dirtied and does not need to be ticked yet
SetTickIsDisabled( true );
// Reset hinged doors a little differently
if( DoorMechanism == EDM_Hinge )
{
ResetHingedDoor( bRepaired );
}
else
{
BashSlot.StopCustomAnim( 0 );
}
// Restore door to closed position if this was a repair
if( bRepaired )
{
bLocalIsDoorOpen = false;
// Set rigid body collision params
SetRBCollideWithDeadPawn( true );
MovementControl.SetSkelControlStrength( 0.f, 0.f );
// Need to push any pawns out of door collision (they could get stuck!)
TryPushPawns();
}
else
{
bLocalIsDoorOpen = true;
// Otherwise it stays open
MovementControl.SetSkelControlStrength( 1.f, 0.f );
// Set rigid body collision params
SetRBCollideWithDeadPawn( false );
}
SkeletalMeshComp.ForceSkelUpdate();
// Make sure door is considered open by AI (if not a repair)
if( bMonitorDoor && !bRepaired )
{
DoorNav = DoorMarker(MyMarker);
if (DoorNav != None)
{
DoorNav.MoverOpened();
}
bMonitorDoor = false;
}
else if( bRepaired )
{
// We need to notify AI that this door is no longer open
DoorNav = DoorMarker(MyMarker);
if (DoorNav != None)
{
DoorNav.MoverClosed();
}
}
// Update health MIC scalars
UpdateHealthScalars( 'doorHealthA', 0 );
UpdateHealthScalars( 'doorHealthB', 0 );
UpdateHealthScalars( 'doorHealthC', 0 );
UpdateHealthScalars( 'doorHealthD', 0 );
// Stop any sounds that might have been playing
if( AmbientSoundComponent != none && AmbientSoundComponent.IsPlaying() )
{
AmbientSoundComponent.StopEvents();
}
// Delete broken door particle effects and empty cache
for( i = 0; i < BrokenDoorParticleEffects.Length; ++i )
{
if( BrokenDoorParticleEffects[i] != none && BrokenDoorParticleEffects[i].bIsActive )
{
BrokenDoorParticleEffects[i].DeactivateSystem();
}
}
BrokenDoorParticleEffects.Length = 0;
// Delete broken door particle effects and empty cache
for( i = 0; i < BrokenDoorPhysicsActors.Length; ++i )
{
BrokenDoorPhysicsActors[i].Destroy();
}
BrokenDoorPhysicsActors.Length = 0;
// When repaired, spawn an effect and play a sound
if( bRepaired && RepairFXTemplate.ParticleTemplate != none && WorldInfo.MyEmitterPool != none )
{
WorldInfo.MyEmitterPool.SpawnEmitter( RepairFXTemplate.ParticleTemplate, VisualDoorLocation + RepairFXTemplate.RelativeOffset, rotator(vect(0,0,1)) + RepairFXTemplate.RelativeRotation, self );
PlaySoundBase( RepairSound,,,, VisualDoorLocation );
}
// notify owning trigger
DoorTrigger.OnDestroyOrReset();
}
/** As the door takes damage, scale its damage paramaters to make it look more beaten */
simulated function UpdateHealthMICs()
{
local float HealthScaler;
// Leave the MIC alone when destroyed. Otherwise, when health is automatically
// zeroed because of a broken weld there will be a pop (bug #24270)
if ( Health <= 0 )
{
return;
}
// If health has dropped, it's dirty
if( Health < MaxHealth )
{
bHasBeenDirtied = true;
}
if ( HealthMICs.Length > 0 )
{
HealthScaler = 1.f - (float(Health) / float(MaxHealth));
// Stagger the door damage parameters and scale them so they will reach 1.f at the same time
UpdateHealthScalars('doorHealthA', HealthScaler);
if ( HealthScaler >= 0.25f )
{
UpdateHealthScalars('doorHealthB', ( HealthScaler - 0.25f ) * 1.32f);
}
if ( HealthScaler >= 0.5f )
{
UpdateHealthScalars('doorHealthC', ( HealthScaler - 0.5f ) * 2.f);
}
if ( HealthScaler >= 0.75f )
{
UpdateHealthScalars('doorHealthD', ( HealthScaler - 0.75f ) * 4.f);
}
}
}
/** Iterates over all stored health mics and updates their health scalars */
simulated function UpdateHealthScalars(name ScalarName, float Value)
{
local byte i;
for( i = 0; i < HealthMICs.Length; i++ )
{
HealthMICs[i].SetScalarParameterValue(ScalarName, Value);
}
}
/** As the door is welded, update the integrityMic to visualize how strong the weld is */
simulated function UpdateIntegrityMIC()
{
local float IntegrityScaler, ExplosiveScaler;
local KFDoorMarker DoorMarker;
local int AttackerCount, QueuedCount;
if ( IntegrityMIC != none )
{
// Have a minimum IntegrityScalar if we have any weld integrity so that
// the weld will always be at least partially visible on the door
if(WeldIntegrity > 0)
{
// If weld integrity has increased from 0, it's dirty
bHasBeenDirtied = true;
IntegrityScaler = FMax(float(WeldIntegrity) / float(MaxWeldIntegrity), MinWeldScalar);
}
else
{
IntegrityScaler = 0;
}
IntegrityMIC.SetScalarParameterValue('doorWeld', IntegrityScaler);
ExplosiveScaler = bShouldExplode ? 1.f : 0.f;
IntegrityMIC.SetScalarParameterValue( 'scalar_explosive', ExplosiveScaler );
if (CenterWeldComponent != none)
{
if (CenterWeldComponent.HiddenGame && WeldIntegrity > 0)
{
CenterWeldComponent.SetHidden(false);
}
else if (!CenterWeldComponent.HiddenGame && WeldIntegrity <= 0)
{
CenterWeldComponent.SetHidden(true);
}
}
}
if( WorldInfo.NetMode != NM_Client && WeldIntegrity > 0 && MyMarker != none )
{
DoorMarker = KFDoorMarker( MyMarker );
if( DoorMarker != none )
{
GetQueuedDoorAICounts( AttackerCount, QueuedCount );
DoorMarker.UpdatePathingCost( AttackerCount, QueuedCount );
}
}
}
/*********************************************************************************************
* @name RB Physics
********************************************************************************************* */
/** Since zeds can move through open doors we need to also allow their corpses to do the same */
simulated function SetRBCollideWithDeadPawn(bool bEnabled)
{
local int i;
// Apply the MIC to our door components
for ( i = 0; i < MeshAttachments.length; i++ )
{
MeshAttachments[i].Component.SetRBCollidesWithChannel(RBCC_DeadPawn, bEnabled);
}
}
/** Hinged doors are special and need to physics assets */
simulated function DestroyHingedDoor()
{
local bool bReverseDir; // if true rotate the direction of the particle effect and impulse
local byte i;
SkeletalMeshComp.bComponentUseFixedSkelBounds = false;
// If the map was just loaded skip physics and go straight to hide
if ( `TimeSince(CreationTime) > 1.0f )
{
bCallRigidBodyWakeEvents = true;
bReverseDir = (HitCount & HIT_DIRECTION_FLAG) > 0;
// Play the destroyed particle effect
for( i = 0; i < DestroyedEmitters.Length; i++ )
{
SpawnParticlesFromEffectParam( DestroyedEmitters[i], bReverseDir );
}
SpawnBrokenDoors( bReverseDir );
}
else
{
// If we are joining a game with broken doors, make them invisible
for( i = 0; i < MeshAttachments.Length; i++ )
{
MeshAttachments[i].Component.SetBlockRigidBody(false);
MeshAttachments[i].Component.SetActorCollision(false, false);
MeshAttachments[i].Component.SetHidden( true );
}
}
}
simulated function SpawnBrokenDoors( bool bReverseDir )
{
local KFKActorSpawnable_Door SpawnedKActor;
local Name BoneName;
local vector DoorX, DoorY, DoorZ, InitAngVel;
local Vector AttachmentLocation;
local Rotator AttachmentRotation;
local byte i;
GetAxes(Rotation, DoorX, DoorY, DoorZ);
DoorX *= ( bReverseDir ) ? -1 : 1;
for( i = 0; i < MeshAttachments.Length; i++ )
{
BoneName = MeshAttachments[i].AttachTo;
AttachmentLocation = SkeletalMeshComp.GetBoneLocation( BoneName ) + ( DoorX * KActorOffset );
AttachmentRotation = QuatToRotator( SkeletalMeshComp.GetBoneQuaternion( BoneName ) ) + MeshAttachments[i].Component.Rotation;
if( WorldInfo.NetMode != NM_DedicatedServer )
{
SpawnedKActor = Spawn(class'KFKActorSpawnable_Door', self,, AttachmentLocation, AttachmentRotation );
if( SpawnedKActor != none && MeshAttachments[i].Component != none )
{
InitBrokenAttachment( MeshAttachments[i].Component, SpawnedKActor );
SpawnedKActor.StaticMeshComponent.SetRBLinearVelocity( DoorX * BrokenDoorImpulse );
// Set initial angular velocity
InitAngVel.X = MaxAngularVelocity * FRand();
InitAngVel.Y = MaxAngularVelocity * FRand();
InitAngVel.Z = MaxAngularVelocity * FRand();
SpawnedKActor.StaticMeshComponent.SetRBAngularVelocity( InitAngVel );
// Start awake
SpawnedKActor.StaticMeshComponent.WakeRigidBody();
// Add to array so we can delete these when the door needs to be repaired
BrokenDoorPhysicsActors.AddItem( SpawnedKActor );
}
}
if( MeshAttachments[i].Component != none )
{
// Hide the original attachment
MeshAttachments[i].Component.SetHidden( true );
MeshAttachments[i].Component.SetBlockRigidBody(false);
MeshAttachments[i].Component.SetActorCollision(false, false);
}
}
}
/** Hinged doors are special and need to reset physics pieces and collision */
simulated function ResetHingedDoor( optional bool bRepaired )
{
local byte i;
for( i = 0; i < MeshAttachments.Length; i++ )
{
MeshAttachments[i].Component.SetBlockRigidBody(true);
MeshAttachments[i].Component.SetActorCollision(true, true);
MeshAttachments[i].Component.SetHidden( false );
}
// Repaired doors start closed
if( !bRepaired )
{
OpenSwingingDoor( none );
}
SkeletalMeshComp.bComponentUseFixedSkelBounds = TRUE;
}
/*********************************************************************************************
* @name Dialog & Sound
********************************************************************************************* */
/** Based on InterpActor::PlayMovingSound */
simulated private function PlayMovingSound(bool bClosing)
{
local AkBaseSoundObject SoundToPlay;
local AkBaseSoundObject AmbientToPlay;
if (bClosing)
{
SoundToPlay = CloseSound;
AmbientToPlay = OpeningAmbientSound;
}
else
{
SoundToPlay = OpenSound;
AmbientToPlay = ClosingAmbientSound;
}
if (SoundToPlay != None)
{
PlaySoundBase(SoundToPlay, true,,, SoundOrigin);
}
if (AkEvent(AmbientToPlay) != None)
{
AmbientSoundComponent.StopEvents();
AmbientSoundComponent.PlayEvent( AkEvent(AmbientToPlay) );
}
}
function AkEvent GetSoundEffectFromType(KFCharacterInfo_Monster DamagingMonsterInfo)
{
switch (DoorMaterial)
{
case EDMT_Metal:
return DamagingMonsterInfo.DoorHitSound.Metal;
break;
case EDMT_Wood:
return DamagingMonsterInfo.DoorHitSound.Wood;
break;
}
}
function bool UnderAttack()
{
return `TimeSince(LastHitTime) < CombatLength;
}
/*********************************************************************************************
* @name AI Behavior & Navigation
********************************************************************************************* */
/** Door is welded shut*/
function bool WeldedShut()
{
return !IsCompletelyOpen() && WeldIntegrity > 0;
}
/** Alerts NPCs that door they're waiting for has been destroyed or opened */
function NotifyAIDoorOpened()
{
local DoorMarker DoorNav;
local KFAIController KFAIC;
DoorNav = DoorMarker( MyMarker );
if( DoorNav != None )
{
if( bMonitorDoor )
{
// notify any Controllers with us as PendingDoor that we have finished moving
foreach WorldInfo.AllControllers(class'KFAIController', KFAIC)
{
if( KFAIC.PendingDoor == self )
{
KFAIC.DoorFinished();
}
// Notify other NPCs who might have been waiting around for another door [WIP]
// else if( C.PendingDoor != none && C.PendingDoor != self && C.PendingDoor.WeldIntegrity > 0 )
// {
// MTG = AICommand_MoveToGoal( C.GetActiveCommand() );
// if( MTG != none )
// {
// MTG.bValidRouteCache = false;
// C.bReevaluatePath = true;
// MTG.CreateTemporaryBlockedPath( C.PendingDoor.MyMarker );
// C.ForcePauseAndRepath();
// }
// }
}
}
DoorNav.MoverOpened();
}
}
/*********************************************************************************************
* @name HUD
********************************************************************************************* */
simulated event DrawDoorHUD( HUD HUD, Canvas C )
{
local PlayerController PC;
local Vector CameraLoc, ScreenLoc;
local Rotator CameraRot;
local float X, Y;
local float DOT;
PC = HUD.PlayerOwner;
C.SetDrawColor(255,255,255);
C.Font = class'KFGameEngine'.Static.GetKFCanvasFont();
// project location onto the hud
PC.GetPlayerViewPoint( CameraLoc, CameraRot );
Dot = vector(CameraRot) dot (Location - CameraLoc);
if( Dot < 0.5f )
{
return;
}
ScreenLoc = C.Project( WeldUILocation );
if( ScreenLoc.X < 0 || ScreenLoc.X + WelderIcon.SizeX * 3 >= C.ClipX || ScreenLoc.Y < 0 && ScreenLoc.Y >= C.ClipY)
{
return;
}
C.SetPos(ScreenLoc.X - WelderIcon.SizeX / 2, ScreenLoc.Y - WelderIcon.SizeY / 2, ScreenLoc.Z);
C.DrawTexture( WelderIcon, 1.f );
X = ScreenLoc.X + WelderIcon.SizeX/2 + 5;
Y = ScreenLoc.Y - WelderIcon.SizeY/2;
C.SetPos( X, Y );
if ( bIsDestroyed )
{
DrawRepairHUD( C, HUD );
}
else
{
DrawWeldHUD( C, HUD, X, Y );
}
}
simulated function DrawWeldHUD( Canvas C, HUD HUD, float PosX, float PosY )
{
local float WeldPercentageFloat;
local int WeldPercentage;
local float FontScale;
local FontRenderInfo FRI;
local String Str;
FRI.bClipText = true;
// Display weld integrity as a percentage
FontScale = class'KFGameEngine'.Static.GetKFFontScale();
WeldPercentageFloat = (float(WeldIntegrity) / float(MaxWeldIntegrity)) * 100.0;
if( WeldPercentageFloat < 1.f && WeldPercentageFloat > 0.f )
{
WeldPercentageFloat = 1.f;
}
else if( WeldPercentageFloat > 99.f && WeldPercentageFloat < 100.f )
{
WeldPercentageFloat = 99.f;
}
WeldPercentage = WeldPercentageFloat;
Str = WeldIntegrityString@WeldPercentage$"%";
C.DrawText( Str, TRUE, FontScale, FontScale, FRI );
if( DemoWeld > 0 && !bShouldExplode )
{
C.SetPos( PosX, PosY + 20.f );
WeldPercentage = float(DemoWeld) / float(DemoWeldRequired) * 100.0;
Str = ExplosiveString@WeldPercentage$"%";
C.DrawText( Str, TRUE, FontScale, FontScale, FRI );
}
}
simulated function DrawRepairHUD( Canvas C, HUD HUD )
{
local float RepairPercentageFloat;
local int RepairPercentage;
local float FontScale;
local FontRenderInfo FRI;
local String Str;
FRI.bClipText = true;
// Display weld integrity as a percentage
FontScale = class'KFGameEngine'.Static.GetKFFontScale();
RepairPercentageFloat = ( float(RepairProgress) / 255.f ) * 100.0;
if( RepairPercentageFloat > 99.f && RepairPercentageFloat < 100.f )
{
RepairPercentageFloat = 99.f;
}
RepairPercentage = int( RepairPercentageFloat );
Str = RepairProgressString @ RepairPercentage $ "%" ;
C.DrawText( Str, TRUE, FontScale, FontScale, FRI );
}
/** Outputs the number of AI attacking and/or queued for this door */
function GetQueuedDoorAICounts( out int DoorAttackers, out int DoorQueuers )
{
local KFAIController KFAIC;
foreach WorldInfo.AllControllers( class'KFAIController', KFAIC )
{
if( KFAIC.DoorEnemy == self )
{
++DoorAttackers;
}
else if( KFAIC.PendingDoor == self )
{
++DoorQueuers;
}
}
}
/** Scalar to use in KFMeleeHelperAI to break doors faster when more zeds are piled on it */
function float GetAIDoorDamageScale()
{
local int AttackerCount, QueuedCount;
GetQueuedDoorAICounts( AttackerCount, QueuedCount );
return 1.f + fMin( float(AttackerCount) * 0.1f, 1.f );
}
/**
* Level was reset without reloading
* Network: ALL. Called on clients to avoid issues that could arise from players joining in progress
* and thinking they need to reset this actor when they don't.
*/
simulated function Reset()
{
if( bHasBeenDirtied )
{
ResetDoor();
}
}
simulated function SetInteractive(bool InInteractive)
{
if (Role == ROLE_Authority)
{
bIsInteractive = InInteractive;
DoorTrigger.SetCollision(InInteractive, DoorTrigger.bBlockActors);
}
}
defaultproperties
{
Begin Object Class=SpriteComponent Name=Sprite
Sprite=Texture2D'EditorResources.door'
HiddenGame=true
AlwaysLoadOnClient=False
AlwaysLoadOnServer=False
Translation=(X=40,Z=40)
End Object
Components.Add(Sprite)
// UI
WelderIcon=Texture2D'UI_World_TEX.welder_door_icon'
bIsInteractive=true
bDoorMoveCompleted=true
bStartDoorOpen=true
bStartWelded=false
OpenBlendTime=0.5f
HingedRotation=90
SlideTranslation=-100
LiftTranslation=245
// actor
Physics=PHYS_None
bEdShouldSnap=true
bStatic=false
bNoDelete=true
bTickIsDisabled=true
CooldownTime=0.5f
// network
RemoteRole=ROLE_SimulatedProxy
bAlwaysRelevant=true
bOnlyDirtyReplication=true
bSkipActorPropertyReplication=true
bIgnoreNetRelevancyCollision=true
NetUpdateFrequency=0.1 // uses bForceNetUpdate
bReplicateRigidBodyLocation=true
// collision
bCollideActors=true
bBlockActors=true
bWorldGeometry=true
bCollideWorld=false
bNoEncroachCheck=false
bProjTarget=true
bCanStepUpOn=false
// health
MaxHealth=4000
MaxWeldIntegrity=1500
CombatWeldModifier=0.6
bCanBeDamaged=true
CombatLength=1.25
MinWeldScalar=0.08
// Explosive weld
DemoWeldRequired=500
// forces
BrokenDoorImpulse=750
MaxAngularVelocity=2
// ---------------------------------------------
// Door Meshes
FrameSizeOfTwoDoors=SkeletalMesh_Width
Begin Object Class=SkeletalMeshComponent Name=SkeletalMeshComponent0
bUseTickOptimization=FALSE // need to turn this off because there is no skeletal geometry
SkeletalMesh=SkeletalMesh'ENV_Doors_Mesh.ENV_Door_Base'
AnimSets(0)=AnimSet'ENV_Doors_ANIM.Door_Base_Animation'
AnimTreeTemplate=AnimTree'ENV_Doors_ANIM.ENV_Doors_AnimTree'
Animations=none
BlockRigidBody=false
HiddenGame=true
HiddenEditor=true
CollideActors=TRUE
End Object
CollisionComponent=SkeletalMeshComponent0
SkeletalMeshComp=SkeletalMeshComponent0
Components.Add(SkeletalMeshComponent0)
Begin Object Class=StaticMeshComponent Name=StaticMeshComponent0
bAllowApproximateOcclusion=TRUE
bForceDirectLightMap=TRUE
bUsePrecomputedShadows=TRUE
LightingChannels=(Indoor=TRUE,Outdoor=TRUE,bInitialized=TRUE)
// Collision
CollideActors=TRUE
BlockActors=TRUE
BlockRigidBody=TRUE
RBChannel=RBCC_GameplayPhysics
RBCollideWithChannels=(Default=TRUE,GameplayPhysics=TRUE,EffectPhysics=TRUE,DeadPawn=FALSE,Pickup=TRUE,FlexAsset=FALSE)
End Object
Begin Object Class=StaticMeshComponent Name=StaticMeshComponent1
bAllowApproximateOcclusion=TRUE
bForceDirectLightMap=TRUE
bUsePrecomputedShadows=TRUE
LightingChannels=(Indoor=TRUE,Outdoor=TRUE,bInitialized=TRUE)
Rotation=(Pitch=32768) // compensate for bone orientation (for uniform SkelControl)
// Collision
CollideActors=TRUE
BlockActors=TRUE
BlockRigidBody=TRUE
RBChannel=RBCC_GameplayPhysics
RBCollideWithChannels=(Default=TRUE,GameplayPhysics=TRUE,EffectPhysics=TRUE,DeadPawn=FALSE,Pickup=TRUE,FlexAsset=FALSE)
End Object
Begin Object Class=StaticMeshComponent Name=StaticMeshComponent2
bAllowApproximateOcclusion=TRUE
bForceDirectLightMap=TRUE
bUsePrecomputedShadows=TRUE
LightingChannels=(Indoor=TRUE,Outdoor=TRUE,bInitialized=TRUE)
CollideActors=FALSE
BlockRigidBody=FALSE
End Object
CenterWeldComponent=StaticMeshComponent2
MeshAttachments.Add((Component=StaticMeshComponent0, AttachTo=DoorLeft))
MeshAttachments.Add((Component=StaticMeshComponent1, AttachTo=DoorRight))
RepairFXTemplate=(ParticleTemplate=ParticleSystem'FX_Gameplay_EMIT_TWO.FX_Door_Repair_Complete_01')
RepairSound=AkEvent'WW_ENV_Destruction.Play_Door_Heal'
//NavigationHandleClass=class'KFNavigationHandle'
bCanCloseDoor=true
}