//============================================================================= // 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 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 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; /** 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 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 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 DestroyedEmitters; /** Physics actors spawned when a door is broken */ var transient array 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, optional TraceHitInfo HitInfo, optional Actor DamageCauser) { local KFPawn_Monster KFPM; local KFCharacterInfo_Monster MonsterInfo; local class 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( DamageType ); if( KFDT != none && KFDT.default.bAllowAIDoorDestruction ) { IncrementHitCount( EventInstigator.Pawn ); DestroyDoor( EventInstigator ); } else if( class(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) { // Human team (0) can only do explosive damage if ( EventInstigator == none || (EventInstigator.GetTeamNum() == 0 && class(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 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 }