2040 lines
56 KiB
Ucode
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
|
|
}
|