/** 
 *  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<Actor> OldAttached;
	local array<vector> 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')
}

