2020-12-13 15:01:13 +00:00
//=============================================================================
// 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 \K FGameAnalytics.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 ) ;
/ * * T h e S k e l e t a l M e s h C o m p o n e n t s A n i m a t i o n s a r e b e i n g i n s t a n c e d f r o m A n i m T r e e T e m p l a t e
* 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.25 f * 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 */
2024-01-23 16:25:12 +00:00
simulated function CloseDoor ( )
2020-12-13 15:01:13 +00:00
{
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.1 f , 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.15 f , 0.2 f , 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.25 f )
{
UpdateHealthScalars ( 'doorHealthB' , ( HealthScaler - 0.25 f ) * 1.32 f ) ;
}
if ( HealthScaler >= 0.5 f )
{
UpdateHealthScalars ( 'doorHealthC' , ( HealthScaler - 0.5 f ) * 2. f ) ;
}
if ( HealthScaler >= 0.75 f )
{
UpdateHealthScalars ( 'doorHealthD' , ( HealthScaler - 0.75 f ) * 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.5 f )
{
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.1 f , 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.5 f
HingedRotation = 90
SlideTranslation = - 100
LiftTranslation = 245
// actor
Physics = PHYS _None
bEdShouldSnap = true
bStatic = false
bNoDelete = true
bTickIsDisabled = true
CooldownTime = 0.5 f
// 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
}