/** * Dynamic static mesh actor intended to be used with Matinee replaces movers * * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved. */ class InterpActor extends DynamicSMActor native ClassGroup(Common) placeable; cpptext { UBOOL ShouldTrace(UPrimitiveComponent* Primitive, AActor *SourceActor, DWORD TraceFlags); virtual void TickSpecial(FLOAT DeltaSeconds); virtual FLOAT GetNetPriority(const FVector& ViewPos, const FVector& ViewDir, APlayerController* Viewer, UActorChannel* InChannel, FLOAT Time, UBOOL bLowBandwidth); /** * Function that gets called from within Map_Check to allow this actor to check itself * for any potential errors and register them with map check dialog. */ #if WITH_EDITOR virtual void CheckForErrors(); #endif } /** Data relevant to checkpoint save/load, see CreateCheckpointRecord/ApplyCheckpointRecord below */ struct CheckpointRecord { var vector Location; var rotator Rotation; var ECollisionType CollisionType; var bool bHidden; var bool bIsShutdown; var bool bNeedsPositionReplication; }; /** whether this should be saved in checkpoints */ var bool bShouldSaveForCheckpoint; /** 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 bMonitorMover; /** if true, call MoverFinished() event on all Controllers with us as their PendingMover when we reach peak Z velocity */ var bool bMonitorZVelocity; /** set while monitoring lift movement */ var float MaxZVelocity; /** delay after mover finishes interpolating before it notifies any mover events */ var float StayOpenTime; /** sound played when the mover is interpolated forward */ var() AkBaseSoundObject OpenSound; /** looping sound while opening */ var() SoundCue OpeningAmbientSound; /** sound played when mover finished moving forward */ var() AkBaseSoundObject OpenedSound; /** sound played when the mover is interpolated in reverse */ var() AkBaseSoundObject CloseSound; /** looping sound while closing */ var() SoundCue ClosingAmbientSound; /** sound played when mover finished moving backward */ var() AkBaseSoundObject ClosedSound; /** component for looping sounds */ var AudioComponent AmbientSoundComponent; /** if set this mover blows up projectiles when it encroaches them */ var() bool bDestroyProjectilesOnEncroach; /** if set, this mover keeps going if it encroaches an Actor in PHYS_RigidBody. */ var() bool bContinueOnEncroachPhysicsObject; /** true by default, prevents mover from completing the movement that would leave it encroaching another actor */ var() bool bStopOnEncroach; /** * This is used for having the Actor ShadowParent all of the components that are "SetBased" onto it. This allows LDs to * take InterpActors in the level and then SetBase a ton of other meshes to them and not incur multiple shadow casters. **/ var() bool bShouldShadowParentAllAttachedActors; /** If true, have a liftcenter associated with this interpactor, so it is being used as a lift */ var bool bIsLift; simulated event PostBeginPlay() { Super.PostBeginPlay(); if (bShouldShadowParentAllAttachedActors) { SetShadowParentOnAllAttachedComponents(StaticMeshComponent, LightEnvironment); } // create ambient sound component if needed if (OpeningAmbientSound != None || ClosingAmbientSound != None) { AmbientSoundComponent = new(self) class'AudioComponent'; AttachComponent(AmbientSoundComponent); } // by default don't save InterpActors that are based on a skeletal mesh bone if (Base != None && (bHardAttach || (BaseSkelComponent != None && BaseBoneName != 'None'))) { bShouldSaveForCheckpoint = false; } } event bool EncroachingOn(Actor Other) { local int i; local SeqEvent_Mover MoverEvent; local Pawn P; local vector Height, HitLocation, HitNormal; local bool bLandingPawn; // Allow move into rigid bodies - should just push them out of the way. if(bContinueOnEncroachPhysicsObject && (Other.Physics == PHYS_RigidBody)) { return FALSE; } // Check if this is something that should be destroyed when mover runs into it if(Other.bDestroyedByInterpActor) { Other.Destroy(); return FALSE; } // if we're moving towards the actor if ( (Other.Base == self) || (Normal(Velocity) Dot Normal(Other.Location - Location) >= 0.f) ) { // if we're moving up into a pawn, ignore it so it can land on us instead P = Pawn(Other); if (P != None) { if (P.Physics == PHYS_Falling && Velocity.Z > 0.f) { Height = P.GetCollisionHeight() * vect(0,0,1); // @note: only checking against our StaticMeshComponent, assumes we have no other colliding components if (TraceComponent(HitLocation, HitNormal, StaticMeshComponent, P.Location - Height, P.Location + Height, P.GetCollisionExtent())) { // make sure the pawn doesn't fall through us if (P.Location.Z < Location.Z) { P.SetLocation(HitLocation + Height); } bLandingPawn = true; } } else if (P.Base != self && P.Controller != None && P.Controller.PendingMover != None && P.Controller.PendingMover == self) { P.Controller.UnderLift(LiftCenter(MyMarker)); } } else if (bDestroyProjectilesOnEncroach && Other.IsA('Projectile')) { Projectile(Other).Explode(Other.Location, -Normal(Velocity)); return false; } if ( !bLandingPawn ) { // search for any mover events for (i = 0; i < GeneratedEvents.Length; i++) { MoverEvent = SeqEvent_Mover(GeneratedEvents[i]); if (MoverEvent != None) { // notify the event that we encroached something MoverEvent.NotifyEncroachingOn(Other); } } return bStopOnEncroach; } } return false; } /* * called for encroaching actors which successfully moved the other actor out of the way */ event RanInto( Actor Other ) { local int i; local SeqEvent_Mover MoverEvent; if (bDestroyProjectilesOnEncroach && Other.IsA('Projectile')) { Projectile(Other).Explode(Other.Location, -Normal(Velocity)); } // Check if this is something that should be destroyed when mover runs into it else if(Other.bDestroyedByInterpActor) { Other.Destroy(); } else if ( bIsLift ) { // no encroach event if have liftcenter based on me // keeps lifts from returning when object/player based on them bounces/jumps and then runs into lift return; } else { // search for any mover events for (i = 0; i < GeneratedEvents.Length; i++) { MoverEvent = SeqEvent_Mover(GeneratedEvents[i]); if (MoverEvent != None) { // notify the event that we encroached something MoverEvent.NotifyEncroachingOn(Other); } } } } event Attach(Actor Other) { local int i; local SeqEvent_Mover MoverEvent; if (!IsTimerActive('FinishedOpen')) { // search for any mover events for (i = 0; i < GeneratedEvents.Length; i++) { MoverEvent = SeqEvent_Mover(GeneratedEvents[i]); if (MoverEvent != None) { // notify the event that an Actor has been attached MoverEvent.NotifyAttached(Other); } } } } event Detach(Actor Other) { local int i; local SeqEvent_Mover MoverEvent; // search for any mover events for (i = 0; i < GeneratedEvents.Length; i++) { MoverEvent = SeqEvent_Mover(GeneratedEvents[i]); if (MoverEvent != None) { // notify the event that an Actor has been detached MoverEvent.NotifyDetached(Other); } } } /** checks if anything is still attached to the mover, and if so notifies Kismet so that it may restart it if desired */ function Restart() { local Actor A; foreach BasedActors(class'Actor', A) { Attach(A); } } /** called on a timer StayOpenTime seconds after the mover has finished opening (forward matinee playback) */ function FinishedOpen() { local int i; local SeqEvent_Mover MoverEvent; // search for any mover events for (i = 0; i < GeneratedEvents.Length; i++) { MoverEvent = SeqEvent_Mover(GeneratedEvents[i]); if (MoverEvent != None) { // notify the event that all opening and associated delays are finished and it may now reverse our direction // (or do any other actions as set up in Kismet) MoverEvent.NotifyFinishedOpen(); } } } simulated function PlayMovingSound(bool bClosing) { local AkBaseSoundObject SoundToPlay; local SoundCue AmbientToPlay; if (bClosing) { SoundToPlay = CloseSound; AmbientToPlay = OpeningAmbientSound; } else { SoundToPlay = OpenSound; AmbientToPlay = ClosingAmbientSound; } if (SoundToPlay != None) { PlaySoundBase(SoundToPlay, true); } if (AmbientToPlay != None) { AmbientSoundComponent.Stop(); AmbientSoundComponent.SoundCue = AmbientToPlay; AmbientSoundComponent.Play(); } } simulated event InterpolationStarted(SeqAct_Interp InterpAction, InterpGroupInst GroupInst) { ClearTimer('Restart'); ClearTimer('FinishedOpen'); PlayMovingSound(InterpAction.bReversePlayback); // we need to save it if it's affected by a matinee bShouldSaveForCheckpoint = true; } simulated event InterpolationFinished(SeqAct_Interp InterpAction) { local DoorMarker DoorNav; local Controller C; local AkBaseSoundObject StoppedSound; if (AmbientSoundComponent != None) { AmbientSoundComponent.Stop(); } StoppedSound = InterpAction.bReversePlayback ? ClosedSound : OpenedSound; if (StoppedSound != None) { PlaySoundBase(StoppedSound, true); } DoorNav = DoorMarker(MyMarker); if (InterpAction.bReversePlayback) { // we are done; if something is still attached, set timer to try restart if (Attached.length > 0) { SetTimer( StayOpenTime, false, nameof(Restart) ); } if (DoorNav != None) { DoorNav.MoverClosed(); } } else { // set timer to notify any mover events SetTimer( StayOpenTime, false, nameof(FinishedOpen) ); if (DoorNav != None) { DoorNav.MoverOpened(); } } if (bMonitorMover) { // notify any Controllers with us as PendingMover that we have finished moving foreach WorldInfo.AllControllers(class'Controller', C) { if (C.PendingMover == self) { C.MoverFinished(); } } } //@hack: force location update on clients if future matinee actions rely on it if (InterpAction.bNoResetOnRewind && InterpAction.bRewindOnPlay) { ForceNetRelevant(); bUpdateSimulatedPosition = true; bReplicateMovement = true; } } simulated event InterpolationChanged(SeqAct_Interp InterpAction) { PlayMovingSound(InterpAction.bReversePlayback); } simulated function ShutDown() { Super.ShutDown(); // safe to save regardless of other factors because it's going to be invisible/uncollidable on load bShouldSaveForCheckpoint = true; } function bool ShouldSaveForCheckpoint() { return bShouldSaveForCheckpoint || RemoteRole == ROLE_SimulatedProxy; } /** Called when this actor is being saved in a checkpoint, records pertinent information for restoration via ApplyCheckpointRecord. */ function CreateCheckpointRecord(out CheckpointRecord Record) { Record.Location = Location; Record.Rotation = Rotation; Record.bHidden = bHidden; Record.CollisionType = ReplicatedCollisionType; Record.bNeedsPositionReplication = (RemoteRole == ROLE_SimulatedProxy && bUpdateSimulatedPosition); //@fixme - is there a more reliable way to detect this? maybe add a bIsShutDown flag to actor? Record.bIsShutdown = (Physics == PHYS_None && bHidden); } function ApplyCheckpointRecord(const out CheckpointRecord Record) { local Actor OldBase; local SkeletalMeshComponent OldBaseComp; local name OldBaseBoneName; local array OldAttached; local array OldLocations; local int i; if (Record.bIsShutdown) { ShutDown(); } else { // store and recover the location of other checkpoint saved actors // as they may have already been processed // otherwise their post-checkpoint location will be based on our pre-checkpoint location // which will put them out of position OldAttached = Attached; while (i < OldAttached.length) { // checkpoint code clears bJustTeleported, so checking it only gets actors teleported by checkpoint loading if (OldAttached[i] != None && OldAttached[i].bJustTeleported) { OldLocations[i] = OldAttached[i].Location; i++; } else { OldAttached.Remove(i, 1); } } // SetLocation() will clear our base, so we need to restore it OldBase = Base; OldBaseComp = BaseSkelComponent; OldBaseBoneName = BaseBoneName; SetLocation(Record.Location); SetRotation(Record.Rotation); SetBase(OldBase,, OldBaseComp, OldBaseBoneName); // restore attached actors for (i = 0; i < OldAttached.length; i++) { if (OldAttached[i] != None) { OldAttached[i].SetLocation(OldLocations[i]); OldAttached[i].SetBase(self); } } if (Record.CollisionType != ReplicatedCollisionType) { SetCollisionType(Record.CollisionType); ForceNetRelevant(); } if (Record.bHidden != bHidden) { SetHidden(Record.bHidden); SetForcedInitialReplicatedProperty(Property'Engine.Actor.bHidden', (bHidden == default.bHidden)); ForceNetRelevant(); } if (Record.bNeedsPositionReplication) { bUpdateSimulatedPosition = true; bReplicateMovement = true; ForceNetRelevant(); } } bShouldSaveForCheckpoint = true; } defaultproperties { bShouldShadowParentAllAttachedActors=TRUE Begin Object Name=StaticMeshComponent0 WireframeColor=(R=255,G=0,B=255,A=255) AlwaysLoadOnClient=true AlwaysLoadOnServer=true RBCollideWithChannels=(Default=TRUE,BlockingVolume=TRUE) End Object bStatic=false bWorldGeometry=false Physics=PHYS_Interpolating bNoDelete=true bAlwaysRelevant=true bSkipActorPropertyReplication=false bUpdateSimulatedPosition=false bOnlyDirtyReplication=true RemoteRole=ROLE_None NetPriority=2.7 NetUpdateFrequency=1.0 bDestroyProjectilesOnEncroach=true bStopOnEncroach=true bContinueOnEncroachPhysicsObject=TRUE bCollideWhenPlacing=FALSE bBlocksTeleport=true bShouldSaveForCheckpoint=true SupportedEvents.Add(class'SeqEvent_Mover') }