//============================================================================= // KFRepairableActor //============================================================================= // Class for any actor that can be repaired with the welder. //============================================================================= // Killing Floor 2 // Copyright (C) 2018 Tripwire Interactive LLC //============================================================================= class KFRepairableActor extends KFWeldableActor hidecategories(Collision, Physics, Object, Mobile) placeable native; var KFRepairableActorTrigger RepairTrigger; var() const StaticMesh RepairedMesh; var() const StaticMesh BrokenMesh; var() StaticMeshComponent RepairableActorMesh; var() StaticMeshComponent WeldComponent; var() FXTemplate BreakingEmitterTemplate; var() FXTemplate BrokenEmitterTemplate; var ParticleSystemComponent BrokenEmitter; var() array ActivationSoundEvents; var() array CompletionSoundEvents; var() array ResetSoundEvents; var() vector IconLocationOffset; var transient bool bRepairComplete; // bSkipActorPropertyReplication=true prevents bHidden from being replicated automatically, // so we'll do it ourselves anyway var transient repnotify bool bHidden_Replicated; delegate OnRepairCompelete(KFRepairableActor RepairedActor); cpptext { virtual void PostLoad(); virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent); } replication { if (bNetDirty) bHidden_Replicated; } simulated event ReplicatedEvent(name VarName) { switch (VarName) { case nameof(bHidden_Replicated): SetHidden(bHidden_Replicated); break; default: super.ReplicatedEvent(VarName); }; } simulated private native function SetupComponents(); simulated function PostBeginPlay() { local StaticMesh DefaultMesh; super.PostBeginPlay(); DefaultMesh = RepairableActorMesh.StaticMesh; SetupComponents(); InitializeIntegrityMIC(); Reset(); if (DefaultMesh != none) { // Reset() sets static mesh to RepairedMesh, but we don't want that initially SetStaticMesh(DefaultMesh); } } simulated function InitializeIntegrityMIC() { if (WeldComponent != none && WeldComponent.GetMaterial(0) != none) { IntegrityMIC = WeldComponent.CreateAndSetMaterialInstanceConstant(0); IntegrityMIC.SetScalarParameterValue('Weld', 0.f); UpdateIntegrityMIC(); } } simulated function InitializeRepairableActorMIC() { local int MaterialIndex; local MaterialInstanceConstant NewMIC; if (WorldInfo.NetMode == NM_DedicatedServer) { return; } if (RepairableActorMesh == none) { return; } for (MaterialIndex = 0; MaterialIndex < RepairableActorMesh.GetNumElements(); MaterialIndex++) { if(RepairableActorMesh.GetMaterial(MaterialIndex) != none) { NewMIC = new class'MaterialInstanceConstant'; NewMIC.SetParent(RepairableActorMesh.GetMaterial(MaterialIndex)); RepairableActorMesh.SetMaterial(MaterialIndex, NewMIC); } } } simulated function InitializeWeldableComponent() { WeldableComponent.SetOwner(self); WeldableComponent.MaxWeldIntegrity = MaxWeldIntegrity; WeldableComponent.bWeldable = true; WeldableComponent.Delegate_OnWeldIntegrityChanged = OnWeldCompWeldIntegrityChanged; // grab the weldintegrity from the weldable component in case it changed // before we were able to set the delegate above WeldIntegrity = WeldableComponent.WeldIntegrity; WeldableComponent.SetCollisionCylinderSize(200, 200); } simulated function OnWeldCompWeldIntegrityChanged(int Amount, KFPawn Welder) { if (Role == ROLE_Authority) { FastenWeld(Amount, Welder); } else { WeldIntegrity = WeldableComponent.WeldIntegrity; UpdateIntegrityMIC(); } } function Repair(float Amount, optional KFPawn Welder) { FastenWeld(Amount, Welder); } function FastenWeld(int Amount, optional KFPawn Welder) { // not allowed to unfasten object. if (Amount > 0 && WeldIntegrity < MaxWeldIntegrity) { UpdateWeldIntegrity(Amount); if (!BeingUnwelded()) { if (!BeingWelded()) { WelderPawn = Welder; } } if (WelderPawn == Welder) { LastWeldTime = WorldInfo.TimeSeconds; } } if (WeldIntegrity >= MaxWeldIntegrity) { CompleteRepair(); } } simulated function SetStaticMesh(StaticMesh NewMesh) { local int i; // Change the mesh on client and server so that collision is synced up for welding if(RepairableActorMesh != none) { RepairableActorMesh.SetStaticMesh(NewMesh); // Need to set materials to none so GetMaterial (called by InitializeRepairableActorMIC) // actually gets materials from static mesh instead of from materials array. Can't just empty // array because it's const. for (i = 0; i < RepairableActorMesh.Materials.Length; ++i) { RepairableActorMesh.SetMaterial(i, None); } // Re-initialize materials array InitializeRepairableActorMIC(); bForceNetUpdate = true; } } simulated function PlayDestroyed() { local AkEvent ActivationSound; if (Role == ROLE_Authority) { bIsDestroyed = true; bWasRepaired = false; bRepairComplete = false; UpdateWeldIntegrity(-WeldIntegrity); WeldableComponent.SetDestroyed(true); WeldableComponent.SetWeldIntegrity(0); foreach ActivationSoundEvents(ActivationSound) { PlaySoundBase(ActivationSound,, WorldInfo.NetMode == NM_DedicatedServer); } } SetStaticMesh(BrokenMesh); if(WorldInfo.NetMode != NM_DedicatedServer) { if (BreakingEmitterTemplate.ParticleTemplate != none) { WorldInfo.MyEmitterPool.SpawnEmitter(BreakingEmitterTemplate.ParticleTemplate, Location + BreakingEmitterTemplate.RelativeOffset, BreakingEmitterTemplate.RelativeRotation); } if (BrokenEmitterTemplate.ParticleTemplate != none) { BrokenEmitter = WorldInfo.MyEmitterPool.SpawnEmitter(BrokenEmitterTemplate.ParticleTemplate, Location + BrokenEmitterTemplate.RelativeOffset, BrokenEmitterTemplate.RelativeRotation); } } // activate kismet event OnKismetEvent(ERepairableActorEvent_Activated); } simulated function CompleteRepair() { if (Role == ROLE_Authority) { bRepairComplete = true; bForceNetUpdate = true; } Reset(); if (RepairFXTemplate.ParticleTemplate != none) { WorldInfo.MyEmitterPool.SpawnEmitter(RepairFXTemplate.ParticleTemplate, Location + RepairFXTemplate.RelativeOffset); } OnRepairCompelete(self); // repaired kismet event OnKismetEvent(ERepairableActorEvent_Repaired); } simulated function Reset() { local AkEvent CompletionSound, ResetSound; if (Role == ROLE_Authority) { WeldIntegrity = MaxWeldIntegrity; bIsDestroyed = false; bWasRepaired = true; WeldableComponent.SetWeldIntegrity(MaxWeldIntegrity); WeldableComponent.SetDestroyed(false); if (bRepairComplete) { foreach CompletionSoundEvents(CompletionSound) { PlaySoundBase(CompletionSound,, WorldInfo.NetMode == NM_DedicatedServer); } } else { foreach ResetSoundEvents(ResetSound) { PlaySoundBase(ResetSound,, WorldInfo.NetMode == NM_DedicatedServer); } } } if (BrokenEmitter != none) { BrokenEmitter.DeactivateSystem(); BrokenEmitter = none; } if (WorldInfo.NetMode != NM_DedicatedServer) { SetStaticMesh(RepairedMesh); } } simulated function UpdateIntegrityMIC() { local float IntegrityScalar; if (IntegrityMIC != none) { if (WeldIntegrity > 0) { IntegrityScalar = float(WeldIntegrity) / float(MaxWeldIntegrity); } else { IntegrityScalar = 0; } IntegrityMIC.SetScalarParameterValue('Weld', IntegrityScalar); if (WeldComponent != none) { if (WeldComponent.HiddenGame && WeldIntegrity > 0) { WeldComponent.SetHidden(false); } else { WeldComponent.SetHidden(true); } } } } /** Handler for SeqAct_ToggleHidden, just sets bHidden. */ simulated function OnToggleHidden(SeqAct_ToggleHidden Action) { super.OnToggleHidden(Action); // replicate bHidden here, because bSkipActorPropertyReplication=true prevents it from happening automatically if (Role == ROLE_Authority) { bHidden_Replicated = bHidden; } } simulated function OnKismetEvent(int EventType) { local int i; local array OutputLinksToActivate; local KFSeqEvent_RepairableActor ProgressEvent; OutputLinksToActivate.AddItem(EventType); for (i = 0; i < GeneratedEvents.Length; i++) { ProgressEvent = KFSeqEvent_RepairableActor(GeneratedEvents[i]); if (ProgressEvent != none) { ProgressEvent.Reset(); ProgressEvent.CheckActivate(self, self, , OutputLinksToActivate); } } } defaultproperties { Physics=PHYS_None bEdShouldSnap=true bStatic=false bNoDelete=true bTickIsDisabled=true RemoteRole = ROLE_SimulatedProxy bAlwaysRelevant=true bOnlyDirtyReplication=true bSkipActorPropertyReplication=true bIgnoreNetRelevancyCollision=true NetUpdateFrequency=0.1 bReplicateRigidBodyLocation=true bCollideActors=true bBlockActors=true bWorldGeometry=true bCollideWorld=false bNoEncroachCheck=false bProjTarget=false bCanStepUpOn=false bMovable=false MaxWeldIntegrity=1500 BreakingEmitterTemplate=(ParticleTemplate=ParticleSystem'FX_Gore_EMIT_THREE.FX_HeadExplode_dh_px') BrokenEmitterTemplate=(ParticleTemplate=ParticleSystem'FX_Environmental_EMIT_THREE.FX_Carnival_Electric_Malfunction_01') RepairFXTemplate=(ParticleTemplate=ParticleSystem'FX_Environmental_EMIT_THREE.FX_Carnival_Game_Entry_01') 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 RepairableActorMesh=StaticMeshComponent0 Begin Object Class=StaticMeshComponent Name=StaticMeshComponent1 bAllowApproximateOcclusion=TRUE bForceDirectLightMap=TRUE bUsePrecomputedShadows=TRUE LightingChannels=(Indoor=TRUE,Outdoor=TRUE,bInitialized=TRUE) CollideActors=FALSE BlockRigidBody=FALSE End Object WeldComponent=StaticMeshComponent1 SupportedEvents.Add(class'KFSeqEvent_RepairableActor') }