
//=============================================================================
// PlayerController
//
// PlayerControllers are used by human players to control pawns.
//
// This is a built-in Unreal class and it shouldn't be modified.
// for the change in Possess().
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//=============================================================================
class PlayerController extends Controller
	config(Game)
	native(Controller)
	nativereplication
	dependson(OnlineSubsystem,OnlineGameSearch,SeqAct_ControlMovieTexture);

var const				Player		Player;						// Player info
var(Camera) editinline	Camera		PlayerCamera;				// Camera associated with this Player Controller
var const class<Camera>				CameraClass;				// Camera class to use for the PlayerCamera

// Player control flags
var					bool			bFrozen;					// Set when game ends or player dies to temporarily prevent player from restarting (until cleared by timer)
var					bool			bPressedJump;
var					bool			bDoubleJump;
var					bool			bUpdatePosition;
var					bool			bUpdating;
var globalconfig	bool			bNeverSwitchOnPickup;		// If true, don't automatically switch to picked up weapon
var					bool			bCheatFlying;				// instantly stop in flying mode
var					bool			bCameraPositionLocked;

var bool	bShortConnectTimeOut;	// when true, reduces connect timeout to 15 seconds
var const bool	bPendingDestroy;		// when true, playercontroller is being destroyed
var bool bWasSpeedHack;
var const bool bWasSaturated;		// used by servers to identify saturated client connections
var globalconfig bool bAimingHelp;

var float MaxResponseTime;		 // how long server will wait for client move update before setting position
var					float			WaitDelay;					// Delay time until can restart
var					pawn			AcknowledgedPawn;			// Used in net games so client can acknowledge it possessed a pawn

var					eDoubleClickDir	DoubleClickDir;				// direction of movement key double click (for special moves)

// Camera info.
var const			actor			ViewTarget;
var PlayerReplicationInfo			RealViewTarget;
var transient bool					bCameraCut;					// Whether we did a camera cut this frame. Automatically reset to FALSE every frame.

/** True if clients are handling setting their own viewtarget and the server should not replicate it (e.g. during certain matinees) */
var bool							bClientSimulatingViewTarget;

/** Director track that's currently possessing this player controller, or none if not possessed. */
var transient InterpTrackInstDirector ControllingDirTrackInst;

/** field of view angle in degrees */
var	protected float		FOVAngle;
var 	float			DesiredFOV;
var 	float			DefaultFOV;

/** last used FOV based multiplier to distance to an object when determining if it exceeds the object's cull distance
 * @note: only valid on client
 */
var const float LODDistanceFactor;

// Remote Pawn ViewTargets
var					rotator			TargetViewRotation;
var					float			TargetEyeHeight;

/** used for smoothing the viewrotation of spectated players */
var rotator BlendedTargetViewRotation;

var					HUD				myHUD;						// heads up display info
var					HUD				mySecondaryHUD;				// secondary heads up display info

// Move buffering for network games.  Clients save their un-acknowledged moves in order to replay them
// when they get position updates from the server.

/** SavedMoveClass should be changed for network player move replication where different properties need to be replicated from the base engine implementation.*/
var					class<SavedMove> SavedMoveClass;

var					SavedMove		SavedMoves;					// buffered moves pending position updates
var					SavedMove		FreeMoves;					// freed moves, available for buffering
var					SavedMove		PendingMove;				// PendingMove already processed on client - waiting to combine with next movement to reduce client to server bandwidth
var					vector			LastAckedAccel;				// last acknowledged sent acceleration
var					float			CurrentTimeStamp;			// Timestamp on server of most recent servermove() processed for this playercontroller
var					float			LastUpdateTime;				// Last time server updated client with a move correction or confirmation
var					float			ServerTimeStamp;			// Server clock time when last servermove was received
var					float			TimeMargin;					// Difference between server's clock and time of received servermoves - used for speedhack detection
var					float			ClientUpdateTime;			// Client timestamp of last time it sent a servermove() to the server.  Used for holding off on sending movement updates to save bandwidth.
var 				float			MaxTimeMargin;				// Max time difference before detecting speedhack. . Calculated from GameInfo speedhack detection configurable settings.
var					float			LastActiveTime;				// used to kick idlers

/** Cap set by server on bandwidth from client to server in bytes/sec (only has impact if >=2600) */
var int ClientCap;

/** ping replication and netspeed adjustment based on ping */
var deprecated float DynamicPingThreshold;

/** Client time when last ping update was sent to server. */
var					float			LastPingUpdate;

/** Last time possible speedhack was logged in server log. */
var float LastSpeedHackLog;

/** MAXPOSITIONERRORSQUARED is the square of the max position error that is accepted (not corrected) in net play */
const MAXPOSITIONERRORSQUARED = 3.0;

/** MAXNEARZEROVELOCITYSQUARED is the square of the max velocity that is considered zero (not corrected) in net play */
const MAXNEARZEROVELOCITYSQUARED = 9.0;

/** MAXVEHICLEPOSITIONERRORSQUARED is the square of the max position error that is accepted (not corrected) in net play when driving a vehicle*/
const MAXVEHICLEPOSITIONERRORSQUARED = 900.0;

/** CLIENTADJUSTUPDATECOST is the bandwidth cost in bytes of sending a client adjustment update. 180 is greater than the actual cost, but represents a tweaked value reserving enough bandwidth for
other updates sent to the client.  Increase this value to reduce client adjustment update frequency, or if the amount of data sent in the clientadjustment() call increases */
const CLIENTADJUSTUPDATECOST=180.0;

/** MAXCLIENTUPDATEINTERVAL is the maximum time between movement updates from the client before the server forces an update. */
const MAXCLIENTUPDATEINTERVAL=0.25;

// ClientAdjustPosition replication (event called at end of frame)
struct native ClientAdjustment
{
    var float TimeStamp;
    var EPhysics newPhysics;
    var vector NewLoc;
    var vector NewVel;
    var actor NewBase;
    var vector NewFloor;
	var byte bAckGoodMove;
};
var ClientAdjustment PendingAdjustment;

var					int				GroundPitch;

// Components ( inner classes )
var transient	CheatManager			CheatManager;		// Object within playercontroller that manages "cheat" commands
var						class<CheatManager>		CheatClass;			// class of my CheatManager

var()	transient	editinline	PlayerInput			PlayerInput;		// Object within playercontroller that manages player input.
var						class<PlayerInput>		InputClass;			// class of my PlayerInput

var const				vector					FailedPathStart;
var						CylinderComponent		CylinderComponent;

// Manages gamepad rumble (within)
var config string ForceFeedbackManagerClassName;
var transient ForceFeedbackManager ForceFeedbackManager;

// Interactions.
var	transient			array<interaction>		Interactions;

/** Indicates that the server and client */
var bool bHasVoiceHandshakeCompleted;

/** List of players that are explicitly muted (outside of gameplay) */
var array<UniqueNetId> VoiceMuteList;

/** List of players muted via gameplay */
var array<UniqueNetId> GameplayVoiceMuteList;

/** The list of combined players to filter voice packets for */
var array<UniqueNetId> VoicePacketFilter;

/** Info about a connected peer relative to this player */
struct native ConnectedPeerInfo
{
	/** Unique net id for the remote peer player */
	var UniqueNetId PlayerId;
	/** NAT type of remote peer connection */
	var ENATType NATType;
	/** TRUE if remote peer has lost connecttion to the game host */
	var bool bLostConnectionToHost;
};
/** List of net ids for peer connections relative to this player. If a player is missing a connected peer then voice packets are routed through server. */
var array<ConnectedPeerInfo> ConnectedPeers;
/** Sorted list of peers to be the next host. Client peers will determine if they are the new host by looking at this list. */
var array<UniqueNetId> BestNextHostPeers;
/** Holds the migrated session sent by the new host. Client peers will join this migrated session. */
var OnlineGameSearch MigratedSearchToJoin;

/** Cached online subsystem variable */
var OnlineSubsystem OnlineSub;

//@HSL_BEGIN - BWJ - 8-4-16 - Playfab support
var PlayfabInterface PlayfabInter;
//@HSL_END

/** Cached online voice interface variable */
var OnlineVoiceInterface VoiceInterface;

/** The data store that holds any online player data */
var UIDataStore_OnlinePlayerData OnlinePlayerData;

//@HSL_BEGIN_XBOX
/** Cached result for CanPlayOnline */
var bool bCanPlayOnline;

//@SABER_EGS_BEGIN Crossplay support
var bool bIsEosPlayer;
//@SABER_EGS_END

/** Cached result for CanShareUserCreatedContent */
var bool bCanShareUserCreatedContent;

/** Cached result for CanCommunicateVoice */
var bool bCanCommunicateVoice;

/** bool to use before checking privileges */
var bool bPrivilegesInitialized;
//@HSL_END_XBOX

//@HSL_BEGIN - JRO - 6/17/2015 - Keep this around so we can still join the game after leaving the current one
/** Save the invite result so a later delegate can use it to join the online game */
var OnlineGameSearchResult CachedInviteResult;
//@HLS_END

/** Ignores movement input. Stacked state storage, Use accessor function IgnoreMoveInput() */
var byte	bIgnoreMoveInput;

/** Ignores look input. Stacked state storage, use accessor function IgnoreLookInput(). */
var byte	bIgnoreLookInput;

/** Maximum distance to search for interactable actors */
var config float InteractDistance;

/** Is this player currently in cinematic mode?  Prevents rotation/movement/firing/etc */
var		bool		bCinematicMode;

/**
 * Whether this player is currently in an interactive sequence.
 * This is set to false if there is any matinee active with a director track and a camera cut.
 */
var bool bInteractiveMode;

/** The state of the inputs from cinematic mode */
var bool bCinemaDisableInputMove, bCinemaDisableInputLook;

//@HSL_BEGIN_XBOX
/** Whether or not to render the UI within the cinematic black bars */
var bool bRenderHUDFullScreen;
//@HSL_END_XBOX

/** Used to cache the session name to join until the timer fires */
var name DelayedJoinSessionName;

/** Whether to ignore network error messages from now on */
var bool bIgnoreNetworkMessages;

/** Used to toggle pushing DrawText Kismet events to the Hud's KismetTextInfo */
var config  bool    bShowKismetDrawText;

// PLAYER INPUT MATCHING =============================================================

/** Type of inputs the matching code recognizes */
enum EInputTypes
{
	IT_XAxis,
	IT_YAxis,
};

/** How to match an input action */
enum EInputMatchAction
{
	IMA_GreaterThan,
	IMA_LessThan
};

/** Individual entry to input matching sequences */
struct native InputEntry
{
	/** Type of input to match */
	var EInputTypes Type;

	/** Min value required to consider as a valid match */
	var	float Value;

	/** Max amount of time since last match before sequence resets */
	var	float TimeDelta;

	/** What type of match is this? */
	var EInputMatchAction Action;
};

/**
 * Contains information to match a series of a inputs and call the given
 * function upon a match.  Processed by PlayerInput, defined in the
 * PlayerController.
 */
struct native InputMatchRequest
{
	/** Number of inputs to match, in sequence */
	var array<InputEntry> Inputs;

	/** Actor to call below functions on */
	var Actor MatchActor;

	/** Name of function to call upon successful match */
	var Name MatchFuncName;
	var delegate<InputMatchDelegate> MatchDelegate;

	/** Name of function to call upon a failed partial match */
	var Name FailedFuncName;

	/** Name of this input request, mainly for debugging */
	var Name RequestName;

	/** Current index into Inputs that is being matched */
	var transient int MatchIdx;

	/** Last time an input entry in Inputs was matched */
	var transient float LastMatchTime;
};
var array<InputMatchRequest> InputRequests;

// MISC VARIABLES ====================================================================

var input byte bRun, bDuck;

var float LastBroadcastTime;
var string LastBroadcastString[4];

var bool	bReplicateAllPawns;			// if true, all pawns will be considered relevant

/** list of names of levels the server is in the middle of sending us for a PrepareMapChange() call */
var array<name> PendingMapChangeLevelNames;

/** Whether this controller is using streaming volumes  **/
var bool bIsUsingStreamingVolumes;

/** True if there is externally controlled UI that should pause the game */
var bool bIsExternalUIOpen;

/** True if the controller is connected for this player */
var bool bIsControllerConnected;

/** If true, do a trace to check if sound is occluded, and reduce the effective sound radius if so */
var bool bCheckSoundOcclusion;

/** handles copying and replicating old cover changes from WorldInfo.CoverReplicatorBase on creation as well as replicating new changes */
var CoverReplicator MyCoverReplicator;

/** List of actors and debug text to draw, @see AddDebugText(), RemoveDebugText(), and DrawDebugTextList() */
struct native DebugTextInfo
{
	/** Actor to draw DebugText over */
	var Actor SrcActor;
	/** Offset from SrcActor.Location to apply */
	var vector SrcActorOffset;
	/** Desired offset to interpolate to */
	var vector SrcActorDesiredOffset;
	/** Text to display */
	var string DebugText;
	/** Time remaining for the debug text, -1.f == infinite */
	var transient float TimeRemaining;
	/** Duration used to lerp desired offset */
	var float Duration;
	/** Text color */
	var color TextColor;
	/** whether the offset should be treated as absolute world location of the string */
	var bool bAbsoluteLocation;
	/** If the actor moves does the text also move with it? */
	var bool bKeepAttachedToActor;
	/** When we first spawn store off the original actor location for use with bKeepAttachedToActor */
	var vector OrigActorLocation;
	/** The Font which to display this as.  Will Default to GetSmallFont()**/
	var Font Font;
};
var private array<DebugTextInfo> DebugTextList;

/** Whether to print the list of current camera anims to the screen */
var bool bDebugCameraAnims;

/** Whether camera anims should be blocked from overriding post process */
var bool bBlockCameraAnimsFromOverridingPostProcess;

/** How fast spectator camera is allowed to move */
var float SpectatorCameraSpeed;

/** index identifying players using the same base connection (splitscreen clients)
 * Used by netcode to match replicated PlayerControllers to the correct splitscreen viewport and child connection
 * replicated via special internal code, not through normal variable replication
 */
var const duplicatetransient byte NetPlayerIndex;

/** this is set on the OLD PlayerController when performing a swap over a network connection
 * so we know what connection we're waiting on acknowledgement from to finish destroying this PC
 * (or when the connection is closed)
 * @see GameInfo::SwapPlayerControllers()
 */
var const duplicatetransient NetConnection PendingSwapConnection;

/** minimum time before can respawn after dying */
var float MinRespawnDelay;

/** component pooling for sounds played through PlaySound()/ClientHearSound() */
var globalconfig int MaxConcurrentHearSounds;
var array<AudioComponent> HearSoundActiveComponents;
var array<AudioComponent> HearSoundPoolComponents;

/** option to print out list of sounds when MaxConcurrentHearSounds is exceeded */
var globalconfig bool bLogHearSoundOverflow;

/** the actors which the camera shouldn't see - e.g. used to hide actors which the camera penetrates */
var array<Actor> HiddenActors;

/** if true, check relevancy of Actors through portals listed in VisiblePortals array */
var globalconfig bool bCheckRelevancyThroughPortals;

/** Different types of progress messages */
enum EProgressMessageType
{
	/** Clears existing progress messages */
	PMT_Clear,

	/** No change in connection status - simply update the text being displayed */
	PMT_Information,

	/** Message from the server admin */
	PMT_AdminMessage,

	/** Updates the amount remaining on a package download */
	PMT_DownloadProgress,

	/** Indicates that the connection to the server was lost */
	PMT_ConnectionFailure,

	/** Indicates that a peer connection to another peer was lost */
	PMT_PeerConnectionFailure,

	/** Indicates that host migration was started but failed to complete */
	PMT_PeerHostMigrationFailure,

	/**
	 * Indicates that an unrecoverable error was encountered by an open network socket.  In cases where the socket in question was
	 * the endpoint for the connection to the server, a PMT_ConnectionFailure event will generally be fired as well, probably during
	 * the next frame.
	 */
	PMT_SocketFailure,
};

/** Used to make sure the client is kept synchronized when in a spectator state */
var float LastSpectatorStateSynchTime;

//debug
var(Debug) bool bDebugClientAdjustPosition;

`if(`__TW_)

//Ported from RO (VOIP Controller for separating and webadmin)
var array<UniqueNetId> VoiceSenders;
var array<UniqueNetId> VoiceReceivers;

// Recoil system
var	rotator		WeaponBufferRotation;
/** Network relevancy value updated from the ActorChannel */
var byte		RelevancyCounter;
/** Net Relevancy debugging */
var bool		bDrawRelevancyChecks;
`endif

cpptext
{
	//  PlayerController interface.
	void SetPlayer( UPlayer* Player );
	void UpdateViewTarget(AActor* NewViewTarget);
	virtual void SmoothTargetViewRotation(APawn* TargetPawn, FLOAT DeltaSeconds);
	/** allows the game code an opportunity to modify post processing settings
	 * @param PPSettings - the post processing settings to apply
	 */
	virtual void ModifyPostProcessSettings(FPostProcessSettings& PPSettings) const;

	// AActor interface.
	INT* GetOptimizedRepList( BYTE* InDefault, FPropertyRetirement* Retire, INT* Ptr, UPackageMap* Map, UActorChannel* Channel );
	virtual UBOOL Tick( FLOAT DeltaTime, enum ELevelTick TickType );
	virtual UBOOL IsNetRelevantFor(APlayerController* RealViewer, AActor* Viewer, const FVector& SrcLocation);
	virtual UBOOL WantsLedgeCheck();
	virtual UBOOL StopAtLedge();
	virtual APlayerController* GetAPlayerController() { return this; }
	virtual UBOOL IgnoreBlockingBy( const AActor *Other ) const;
// WWISEMODIF_START
	virtual UBOOL HearSound(UAkBaseSoundObject* InSoundCue, AActor* SoundPlayer, const FVector& SoundLocation, UBOOL bStopWhenOwnerDestroyed
#ifdef __TW_WWISE_
    ,const FRotator& SoundRotation
#endif // __TW_WWISE_
    );
	void eventClientHearSoundHelper(class UAkBaseSoundObject* ASound,class AActor* SourceActor,FVector SourceLocation,UBOOL bStopWhenOwnerDestroyed,UBOOL bIsOccluded=FALSE);
// WWISEMODIF_END
	/** checks whether the passed in SoundPlayer is valid for replicating in a HearSound() call and sets it to NULL if not */
	void ValidateSoundPlayer(AActor*& SoundPlayer);
	virtual void PostScriptDestroyed();
	virtual FLOAT GetNetPriority(const FVector& ViewPos, const FVector& ViewDir, APlayerController* Viewer, UActorChannel* InChannel, FLOAT Time, UBOOL bLowBandwidth);

	/** called on the server when the client sends a message indicating it was unable to initialize an Actor channel,
	 * most commonly because the desired Actor's archetype couldn't be serialized
	 * the default is to do nothing (Actor simply won't exist on the client), but this function gives the game code
	 * an opportunity to try to correct the problem
	 */
	virtual void NotifyActorChannelFailure(UActorChannel* ActorChan)
	{}

	/** called on the server to force a physics update for a remotely controlled player that hasn't been sending timely updates */
	virtual void ForcePositionUpdate();
	/** @return whether this player is in state that allows idle kicking (if enabled) */
	virtual UBOOL CanIdleKick();

	/** disables SeePlayer() and SeeMonster() checks for PlayerController, since they aren't used for most games */
	virtual UBOOL ShouldCheckVisibilityOf(AController* C) { return FALSE; }
	virtual void UpdateHiddenActors(const FVector& ViewLocation) {}
	virtual void UpdateHiddenComponents(const FVector& ViewLocation,TSet<UPrimitiveComponent*>& HiddenComponents) {}
	virtual void HearNoise(AActor* NoiseMaker, FLOAT Loudness, FName NoiseType);

	/**
	 * Sets the Matinee director track instance that's currently possessing this player controller
	 *
	 * @param   NewControllingDirector    The director track instance that's now controlling this player controller (or NULL for none)
	 */
	void SetControllingDirector( UInterpTrackInstDirector* NewControllingDirector );

	/**
	 * Returns the Matinee director track that's currently possessing this player controller, or NULL for none
	 */
	UInterpTrackInstDirector* GetControllingDirector();

}

replication
{
	// Things the server should send to the client.
	if ( bNetOwner && Role==ROLE_Authority && (ViewTarget != Pawn) && (Pawn(ViewTarget) != None) )
		TargetViewRotation, TargetEyeHeight;
}

native final function SetNetSpeed(int NewSpeed);
native final function string GetPlayerNetworkAddress();
native final function string GetServerNetworkAddress();
native function string ConsoleCommand(string Command, optional bool bWriteToLog = true);

/**
 * Travel to a different map or IP address.  Calls the PreClientTravel event before doing anything.
 *
 * @param	URL				a string containing the mapname (or IP address) to travel to, along with option key/value pairs
 * @param	TravelType 		specifies whether the client should append URL options used in previous travels; if TRUE is specified
 *							for the bSeamlesss parameter, this value must be TRAVEL_Relative.
 * @param	bSeamless		indicates whether to use seamless travel (requires TravelType of TRAVEL_Relative)
 * @param	MapPackageGuid	the GUID of the map package to travel to - this is used to find the file when it has been autodownloaded,
 * 							so it is only needed for clients
 */
reliable client native event ClientTravel(string URL, ETravelType TravelType, optional bool bSeamless = false, optional init Guid MapPackageGuid);

native(546) final function UpdateURL(string NewOption, string NewValue, bool bSave1Default);
native final function string GetDefaultURL(string Option);
// Execute a console command in the context of this player, then forward to Actor.ConsoleCommand.
native function CopyToClipboard( string Text );
native function string PasteFromClipboard();

/** Whether or not to allow mature language **/
native function SetAllowMatureLanguage( bool bAllowMatureLanguge );

/** Sets the Audio Group to this the value passed in **/
exec native function SetAudioGroupVolume( name GroupName, float Volume );

reliable client final private native event ClientConvolve(string C,int H);
reliable server final private native event ServerProcessConvolve(string C,int H);

native final function bool CheckSpeedHack(float DeltaTime);

/* FindStairRotation()
returns an integer to use as a pitch to orient player view along current ground (flat, up, or down)
*/
native(524) final function int FindStairRotation(float DeltaTime);

/** Clears out 'left-over' audio components. */
native function CleanUpAudioComponents();

/** called when the actor falls out of the world 'safely' (below KillZ and such) */
simulated event FellOutOfWorld(class<DamageType> dmgType);

/** Head Tracking Kismet action replication helper function **/
unreliable client function EnableActorHeadTracking(Actor TargetActor, name TrackControllerName[10], class ActorClassesToLookAt[10], bool bLookAtPawns, float MinLookAtTime, float MaxLookAtTime, float MaxInterestTime, float LookAtActorRadius, name TargetBoneNames[10] );
unreliable client function DisableActorHeadTracking(Actor TargetActor);

/**Show InGame Invite Popup */
function showInvitePopup(string FriendName, UniqueNetId LobbyId, UniqueNetId FriendId);

/**
 * Tells the game info to forcibly remove this player's CanUnpause delegates from its list of Pausers.
 *
 * Called when the player controller is being destroyed to prevent the game from being stuck in a paused state when a PC that
 * paused the game is destroyed before the game is unpaused.
 */
function ForceClearUnpauseDelegates()
{
	if ( WorldInfo.Game != None )
	{
		WorldInfo.Game.ForceClearUnpauseDelegates(Self);
	}
}

//@HSL_BEGIN_XBOX
event CheckPrivileges()
{
	local LocalPlayer LP;
	local int PlayerIdx;
	local EFeaturePrivilegeLevel HintPrivLevel;

	LP = LocalPlayer(Player);

    bPrivilegesInitialized = TRUE;

	if (LP != None && OnlineSub != None && OnlineSub.PlayerInterface != None)
	{
		PlayerIdx = LP.ControllerId;

		if (!OnlineSub.PlayerInterface.CanPlayOnline(PlayerIdx, HintPrivLevel))
		{
			`log("Failed to check CanPlayOnline");
		}
		else
		{
			if (HintPrivLevel == FPL_Disabled)
			{
				bCanPlayOnline = FALSE;
			}
			else
			{
				bCanPlayOnline = TRUE;
			}
		}

		if (!OnlineSub.PlayerInterface.CanShareUserCreatedContent(PlayerIdx, HintPrivLevel))
		{
			`log("Failed to check CanShareUserCreatedContent");
		}
		else
		{
			if (HintPrivLevel == FPL_Disabled)
			{
				bCanShareUserCreatedContent = FALSE;
			}
			else
			{
				bCanShareUserCreatedContent = TRUE;
			}
		}

		if (!OnlineSub.PlayerInterface.CanCommunicateVoice(PlayerIdx, HintPrivLevel))
		{
			`log("Failed to check CanCommunicateVoice");
		}
		else
		{
			if (HintPrivLevel == FPL_Disabled)
			{
				bCanCommunicateVoice = FALSE;
			}
			else
			{
				bCanCommunicateVoice = TRUE;
			}
            `log(bCanCommunicateVoice ? "Voice is enabled" : "Voice is disabled");
		}
	}
	else
	{
		`log("PlayerController.CheckPrivileges failed");
	}
}

function OnPrivilegeLevelChecked(byte LocalUserNum, EFeaturePrivilege Privilege, EFeaturePrivilegeLevel PrivilegeLevel, bool bDiffersFromHint)
{
	if (Privilege == FP_OnlinePlay)
	{
		`log("OnlineSub.PlayerInterface.CanPlayOnline completed : " $ PrivilegeLevel);

		if (PrivilegeLevel == FPL_Disabled)
		{
			bCanPlayOnline = false;
		}
		else
		{
			bCanPlayOnline = true;
		}

        // Check for an invite that may have occured on launch of the game
        if( bCanPlayOnline )
        {
            OnlineSub.PlayerInterface.CheckForGameInviteOnLaunch();
        }
	}
	else if (Privilege == FP_ShareUserCreatedContent)
	{
		`log("OnlineSub.PlayerInterface.CanShareUserCreatedContent completed : " $ PrivilegeLevel);

		if (PrivilegeLevel == FPL_Disabled)
		{
			bCanShareUserCreatedContent = false;
		}
		else
		{
			bCanShareUserCreatedContent = true;
		}
	}
	else if (Privilege == FP_CommunicationVoice)
	{
		`log("OnlineSub.PlayerInterface.CanCommunicateVoice completed : " $ PrivilegeLevel);

		if (PrivilegeLevel == FPL_Disabled)
		{
			bCanCommunicateVoice = false;
		}
		else
		{
			bCanCommunicateVoice = true;
		}
	}
}
//@HSL_END_XBOX

/**
 * Attempts to pause/unpause the game when the UI opens/closes. Note: pausing
 * only happens in standalone mode
 *
 * @param bIsOpening whether the UI is opening or closing
 */
function OnExternalUIChanged(bool bIsOpening)
{
	bIsExternalUIOpen = bIsOpening;
	SetPause(bIsOpening, CanUnpauseExternalUI);
}

/** Callback that checks the external UI state before allowing unpause */
function bool CanUnpauseExternalUI()
{
	return !bIsExternalUIOpen || bPendingDelete || bPendingDestroy || bDeleteMe;
}

//@HSL_BEGIN_XBOX
/**
 * Attempts to pause/unpause the game when a controller becomes
 * disconnected/connected
 *
 * @param ControllerId the id of the controller that changed
 * @param bIsConnected whether the controller is connected or not
 * @param bPauseGame wheater the game should pause or not
 */
function OnControllerChanged(int ControllerId,bool bIsConnected,bool bPauseGame)
{
	local LocalPlayer LP;

	// Don't worry about remote players
	LP = LocalPlayer(Player);

	// If the controller that changed, is attached to the this playercontroller
	if (LP != None
	&&	LP.ControllerId == ControllerId
	&&	WorldInfo.IsConsoleBuild()
	// do not pause if there is no controller when we are automatedperftesting
	&&	(WorldInfo.Game == None || !WorldInfo.Game.IsAutomatedPerfTesting()))
	{
		bIsControllerConnected = bIsConnected;

		`log("Received gamepad connection change for player" @ class'UIInteraction'.static.GetPlayerIndex(ControllerId) $ ": gamepad" @ ControllerId @ "is now" @ (bIsConnected ? "connected" : "disconnected"));

		// Pause if the controller was removed, otherwise unpause
		SetPause(!bIsConnected,CanUnpauseControllerConnected);
	}
}

/** Pauses the game when a controller is disconnected */
function ControllerChangedPause()
{
    SetPause(true,CanUnpauseControllerConnected);
}
/** Unpauses the game when a controller is disconnected */
function ControllerChangedUnpause()
{
    SetPause(false,CanUnpauseControllerConnected);
}
//@HSL_END_XBOX

/** Callback that checks to see if the controller is connected before unpausing */
function bool CanUnpauseControllerConnected()
{
	return bIsControllerConnected;
}

/** spawns MyCoverReplicator and tells it to replicate any changes that have already occurred */
function CoverReplicator SpawnCoverReplicator()
{
	if (MyCoverReplicator == None && Role == ROLE_Authority && LocalPlayer(Player) == None)
	{
		MyCoverReplicator = Spawn(class'CoverReplicator', self);
		MyCoverReplicator.ReplicateInitialCoverInfo();
	}
	return MyCoverReplicator;
}

simulated event PostBeginPlay()
{
	super.PostBeginPlay();

	ResetCameraMode();

	MaxTimeMargin = class'GameInfo'.Default.MaxTimeMargin;
	MaxResponseTime = Default.MaxResponseTime * WorldInfo.TimeDilation;

	if ( WorldInfo.NetMode == NM_Client )
	{
		SpawnDefaultHUD();
	}
	else
	{
		AddCheats();
	}

	SetViewTarget(self);  // MUST have a view target!
	LastActiveTime = WorldInfo.TimeSeconds;

	OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();

//@HSL_BEGIN - BWJ - 8-4-16 - Playfab support
	PlayfabInter = class'GameEngine'.static.GetPlayfabInterface();
//@HSL_END

	// if we're a client do this here because super is not going to
	if ( WorldInfo.NetMode == NM_Client )
	{
		InitNavigationHandle();
	}
}

/**
 * Called after this PlayerController's viewport/net connection is associated with this player controller.
 */
simulated event ReceivedPlayer()
{
	RegisterPlayerDataStores();
}

/**
 * Find the index of the entry in the connected peer list
 *
 * @param PeerNetId net id of remote client peer to find
 */
final function int FindConnectedPeerIndex(UniqueNetId PeerNetId)
{
	local int PeerIdx;
	for (PeerIdx=0; PeerIdx < ConnectedPeers.Length; PeerIdx++)
	{
		if (PeerNetId == ConnectedPeers[PeerIdx].PlayerId)
		{
			return PeerIdx;
		}
	}
	return -1;
}

`if(`__TW_)
// FindUnreferencedFunctions commandlet (and possibly others) crashes because this function can't be found in Engine.PlayerController.
// This prevents the crash.
function BestNextHostSort();
`endif

/**
 * Keep track of a newly added peer for this player and also replicate to server.
 *
 * @param PeerNetId net id of remote client peer being added
 * @param NATType NAT of remote peer
 */
event AddPeer(UniqueNetId PeerNetId, ENATType NATType)
{
	local UniqueNetId ZeroId;
	local ConnectedPeerInfo NewPeerInfo;

	if (PeerNetId != ZeroId)
	{
		ServerAddPeer(PeerNetId,NATType);
		if (Role < ROLE_Authority)
		{
			if (FindConnectedPeerIndex(PeerNetId) == -1)
			{
				NewPeerInfo.PlayerId = PeerNetId;
				NewPeerInfo.NATType = NATType;
				ConnectedPeers.AddItem(NewPeerInfo);
			}
		}
	}
}

/**
 * Keep track of a removed peer for this player and also replicate to server.
 *
 * @param PeerNetId net id of remote client peer being removed
 */
event RemovePeer(UniqueNetId PeerNetId)
{
	local UniqueNetId ZeroId;
	local int PeerIdx;

	if (PeerNetId != ZeroId)
	{
		ServerRemovePeer(PeerNetId);
		if (Role < ROLE_Authority)
		{
			PeerIdx = FindConnectedPeerIndex(PeerNetId);
			if (PeerIdx != -1)
			{
				ConnectedPeers.Remove(PeerIdx,1);
			}
		}
	}
}

/**
 * Replicate newly added peer for this player to server.
 *
 * @param PeerNetId net id of remote client peer being added
 * @param NATType NAT of remote peer
 */
reliable server function ServerAddPeer(UniqueNetId PeerNetId, ENATType NATType)
{
	local UniqueNetId ZeroId;
	local ConnectedPeerInfo NewPeerInfo;

	if (PeerNetId != ZeroId)
	{
		if (FindConnectedPeerIndex(PeerNetId) == -1)
		{
			NewPeerInfo.PlayerId = PeerNetId;
			NewPeerInfo.NATType = NATType;
			ConnectedPeers.AddItem(NewPeerInfo);
		}
	}
}

/**
 * Replicate removed peer for this player to server.
 *
 * @param PeerNetId net id of remote client peer being removed
 */
reliable server function ServerRemovePeer(UniqueNetId PeerNetId)
{
	local UniqueNetId ZeroId;
	local int PeerIdx;

	if (PeerNetId != ZeroId)
	{
		PeerIdx = FindConnectedPeerIndex(PeerNetId);
		if (PeerIdx != -1)
		{
			ConnectedPeers.Remove(PeerIdx,1);
		}
	}
}

/**
 * Update the list of sorted next hosts on clients. This is used during host migration.
 * All clients should agree on the best next host.
 *
 * @param SortedNextHosts array of player net ids to be the next host when disconnect occurs
 * @param NumEntries number of valid entries in array of next hosts
 */
reliable client function ClientUpdateBestNextHosts(UniqueNetId SortedNextHosts[10], byte NumEntries)
{
	local int Idx;

	// Copy to local list of best next hosts
	BestNextHostPeers.Length = Min(NumEntries,10);
	for (Idx=0; Idx < BestNextHostPeers.Length; Idx++)
	{
		BestNextHostPeers[Idx] = SortedNextHosts[Idx];
	}
}

/**
 * Notification that one of the peer connections has lost his connection to the server. RPC is received through peer net driver.
 *
 * @param PeerNetId net id of player that lost his connection
 */
event NotifyPeerDisconnectHost(UniqueNetId PeerNetId)
{
	local int PeerIdx;

	`Log(`location @ ": client peer lost connection to host"
		$" PeerNetId="$class'OnlineSubsystem'.static.UniqueNetIdToString(PeerNetId),,'DevNet');

	PeerIdx = FindConnectedPeerIndex(PeerNetId);
	if (PeerIdx != -1)
	{
		ConnectedPeers[PeerIdx].bLostConnectionToHost = true;
	}
}

/**
 * Determine if the player in our list of connected peers is at the top of the best next host list.
 * Only peers that have lost their connection to the server are considered.
 * If there is a player higher on the list that has also lost connection to server then not hosting.
 *
 * @param PeerNetId net id of player to check as best host
 * @return TRUE if the player should be the next host
 */
function bool IsBestHostPeer(UniqueNetId PeerNetId)
{
	local int Idx,PeerIdx;

	// Determine if this player is the new host and should be migrating
	for (Idx=0; Idx < BestNextHostPeers.Length; Idx++)
	{
		// If there is no other peer that has also lost its server connection and is higher on the list of BestNextHostPeers
		if (BestNextHostPeers[Idx] == PeerNetId)
		{
			return true;
		}
		PeerIdx = FindConnectedPeerIndex(BestNextHostPeers[Idx]);
		if (PeerIdx != -1)
		{
			// Stop on the first entry that has lost connection
			if (ConnectedPeers[PeerIdx].bLostConnectionToHost)
			{
				break;
			}
		}
	}
	return false;
}

/**
 * Attempt host migration for the current player.
 *
 * @return TRUE if the player was selected as the next host and migration as host was started
 */
event bool MigrateNewHost()
{
	local LocalPlayer LP;

`if(`notdefined(FINAL_RELEASE))
	DumpPeers();
`endif
	// Determine if this peer should be the host
	if (IsBestHostPeer(PlayerReplicationInfo.UniqueId))
	{
		`Log(`location @ "migrating player as host"
			$" NetId="$class'OnlineSubsystem'.static.UniqueNetIdToString(PlayerReplicationInfo.UniqueId),,'DevNet');

		LP = LocalPlayer(Player);

		// Migrate the game session if it exists
		if (OnlineSub != None &&
			OnlineSub.GameInterface != None &&
			OnlineSub.GameInterface.GetGameSettings(PlayerReplicationInfo.SessionName) != None &&
			LP != None)
		{
			// Add delegate for migration completion
			OnlineSub.GameInterface.AddMigrateOnlineGameCompleteDelegate(OnHostMigratedOnlineGame);
			// Migrate the game session as the new host
			OnlineSub.GameInterface.MigrateOnlineGame(LP.ControllerId,PlayerReplicationInfo.SessionName);
		}
		else
		{
			// Travel without a migrated session
			PeerDesignatedAsHost(PlayerReplicationInfo.SessionName);
		}
		return true;
	}
	return false;
}

/**
 * Notification on this client that the host migration process has started
 * May or may not complete successfully from this point
 */
simulated function NotifyHostMigrationStarted();

/**
 * Get the list of registered players in a session
 *
 * @param SessionName name of session to get players from
 * @param OutRegisteredPlayers array of players in the session
 */
function GetRegisteredPlayersInSession(name SessionName, out array<UniqueNetId> OutRegisteredPlayers);

/** Called on completion of unregistering missing peers */
delegate OnMissingPeersUnregistered(name SessionName,UniqueNetId PlayerId,bool bWasSuccessful);

/** @return first PRI that matches the given net id */
function PlayerReplicationInfo GetPRIFromNetId(UniqueNetId PlayerId)
{
	local PlayerReplicationInfo CurrentPRI;

	foreach WorldInfo.GRI.PRIArray(CurrentPRI)
	{
		if (CurrentPRI.UniqueId == PlayerId)
		{
			return CurrentPRI;
		}
	}
	return None;
}

/**
 * Find all players in the session that is about to be migrated. Unregister
 * the players that have missing peer connections as they won't migrate.
 * Begin migration once this is complete.
 *
 * @param SessionName name of session to be migrated
 * @return TRUE if there were still players to remove, FALSE if done
 */
function bool RemoveMissingPeersFromSession(name SessionName,delegate<OnMissingPeersUnregistered> UnregisterDelegate)
{
	local array<UniqueNetId> RegisteredPlayers;
	local UniqueNetId ZeroId;
	local int PlayerIdx;
	local PlayerReplicationInfo RegisteredPRI;

	if (OnlineSub != None &&
		OnlineSub.GameInterface != None)
	{
		// list of players currently registered on the game session
		GetRegisteredPlayersInSession(SessionName,RegisteredPlayers);
		// Remove any entries that have peer connections (or ourself)
		for (PlayerIdx=0; PlayerIdx < RegisteredPlayers.Length; PlayerIdx++)
		{
			RegisteredPRI = GetPRIFromNetId(RegisteredPlayers[PlayerIdx]);
			if (RegisteredPlayers[PlayerIdx] == PlayerReplicationInfo.UniqueId ||
				RegisteredPlayers[PlayerIdx] == ZeroId ||
				FindConnectedPeerIndex(RegisteredPlayers[PlayerIdx]) != INDEX_NONE ||
				// splitscreen players dont have peer connections so keep them registered
				!(RegisteredPRI != None && RegisteredPRI.IsPrimaryPlayer()))
			{
				RegisteredPlayers.Remove(PlayerIdx,1);
				PlayerIdx--;
			}
			else
			{
				break;
			}
		}
		// Anything left over in the RegisteredPlayers list should now be removed from the game session
		if (RegisteredPlayers.Length > 0)
		{
			// Add delegate for unregister player completion
			OnlineSub.GameInterface.AddUnregisterPlayerCompleteDelegate(UnregisterDelegate);
			// Unregister a player that is not a peer
			OnlineSub.GameInterface.UnregisterPlayer(SessionName,RegisteredPlayers[0]);
			// signal that players were removed and delegate will be called
			return true;
		}
	}
	return false;
}

/**
 * Delegate called after each iteration of unregistering a player
 *
 * @param SessionName the name of the session to unregister player from
 * @param PlayerId net id of player that was unregistered
 * @param bWasSuccessful whether the unregister completed ok or not
 */
function OnUnregisterPlayerCompleteForHostMigrate(name SessionName,UniqueNetId PlayerId,bool bWasSuccessful)
{
	// Clear the delegate so another unregister can occur
	OnlineSub.GameInterface.ClearUnregisterPlayerCompleteDelegate(OnUnregisterPlayerCompleteForHostMigrate);

	// Continue unregistering missing peers until none are left
	if (!RemoveMissingPeersFromSession(SessionName,OnUnregisterPlayerCompleteForHostMigrate))
	{
		// Travel host with the migrated session
		PeerDesignatedAsHost(SessionName);
	}
}

/**
 * Delegate called once the session migration on the host completed.
 *
 * @param SessionName the name of the session being migrated
 * @param bWasSuccessful whether the session migration completed ok or not
 */
function OnHostMigratedOnlineGame(name SessionName,bool bWasSuccessful)
{
	OnlineSub.GameInterface.ClearMigrateOnlineGameCompleteDelegate(OnHostMigratedOnlineGame);

	if (bWasSuccessful)
	{
		// Remove all non-peers from session and migrate as host once that completes
		if (!RemoveMissingPeersFromSession(SessionName,OnUnregisterPlayerCompleteForHostMigrate))
		{
			// if nobody to remove just travel as host
			PeerDesignatedAsHost(SessionName);
		}
	}
	else
	{
		`Log(`location @ "migration failed for"
			$" NetId="$class'OnlineSubsystem'.static.UniqueNetIdToString(PlayerReplicationInfo.UniqueId),,'DevNet');

		//@todo peer - Notify other peers of failed host migration so another peer can be selected.
		// Currently they will just fail after they wait for the HostMigrationTimeout period

		ClientSetProgressMessage(PMT_PeerHostMigrationFailure,
			"<Strings:Engine.Errors.ConnectionFailed>",
			"<Strings:Engine.Errors.ConnectionFailed_Title>",
			true);
	}
}

/**
 * @return Current game search class in use
 */
function class<OnlineGameSearch> GetCurrentSearchClass()
{
	return class'OnlineGameSearch';
}

/**
 * This peer player has been selected as the new host.
 * Notify all other clients that have also lost their server connection to begin traveling to the newly migrated session.
 * Begin traveling as the new host.
 *
 * @param SessionName Name of the session that was migrated. Can be 'None' if migrating without a session
 */
function PeerDesignatedAsHost(name SessionName)
{
	local int PeerIdx;
	local byte PlatformInfo[80];

	// Notify peers (that have also lost connection) to travel to us as the new host
	if (OnlineSub != None &&
		OnlineSub.GameInterface != None &&
		OnlineSub.GameInterface.GetGameSettings(SessionName) != None &&
		OnlineSub.GameInterface.ReadPlatformSpecificSessionInfoBySessionName(SessionName,PlatformInfo))
	{
		for (PeerIdx=0; PeerIdx < ConnectedPeers.Length; PeerIdx++)
		{
			if (ConnectedPeers[PeerIdx].bLostConnectionToHost)
			{
				// travel using migrated game session
				TellPeerToTravelToSession(ConnectedPeers[PeerIdx].PlayerId,SessionName,GetCurrentSearchClass(),PlatformInfo,80);
			}
		}
	}
	else
	{
		for (PeerIdx=0; PeerIdx < ConnectedPeers.Length; PeerIdx++)
		{
			if (ConnectedPeers[PeerIdx].bLostConnectionToHost)
			{
				// travel to this player's ip addr
				TellPeerToTravel(ConnectedPeers[PeerIdx].PlayerId);
			}
		}
	}

	// New host starts travel to the same map
	PeerTravelAsHost(0.5,GetNewPeerHostURL());
}

/**
 * @return URL to use when traveling a peer as the new host after host migration
 */
function string GetNewPeerHostURL()
{
	return WorldInfo.GetMapName(true) $ "?game=" $ PathName(WorldInfo.GetGameClass()) $ "?listen";
}

/**
 * Delay and then travel as the new host to the given URL
 *
 * @param TravelCountdownTimer Seconds to delay before initiating the travel
 * @param URL browse path for the map/game to load as host
 */
native function PeerTravelAsHost(float TravelCountdownTimer,string URL);

/**
 * Notify client peer to travel to the new host. RPC is sent through peer net driver.
 *
 * @param ToPeerNetId peer player to find connection for
 */
native function TellPeerToTravel(UniqueNetId ToPeerNetId);

/**
 * Notify client peer to travel to the new host via its migrated session. RPC is sent through peer net driver.
 *
 * @param ToPeerNetId peer player to find connection for
 * @param SessionName Name of session that was migrated to travel to
 * @param SearchClass Search class being used by the current game session
 * @param PlatformSpecificInfo Byte array with secure session info
 * @param PlatformSpecificInfoSize Size in bytes of PlatformSpecificInfo
 */
native function TellPeerToTravelToSession(UniqueNetId ToPeerNetId, name SessionName, class<OnlineGameSearch> SearchClass, byte PlatformSpecificInfo[80],int PlatformSpecificInfoSize);

/**
 * Notification when client peer received a migrated session. The session is joined via migration and the client travels to the new host once the join succeeds.
 *
 * @param FromPeerNetId peer player that that sent us the migrated session. This is the new host
 * @param SearchClass Search class being used by the game session on the host
 * @param PlatformSpecificInfo Byte array with secure session info
 */
event PeerReceivedMigratedSession(UniqueNetId FromPeerNetId, name SessionName, class<OnlineGameSearch> SearchClass, byte PlatformSpecificInfo[80])
{
	local OnlineGameSearchResult SessionToJoin;
	local LocalPlayer LP;

	LP = LocalPlayer(Player);
	if (LP != None &&
		OnlineSub != None &&
		OnlineSub.GameInterface != None)
	{
		`Log(`location @ "received migrated session to join"
			$" SessionName="$SessionName
			$" SearchClass="$SearchClass
			$" UniqueId="$OnlineSub.static.UniqueNetIdToString(PlayerReplicationInfo.UniqueId)
			$" FromPeerNetId="$OnlineSub.static.UniqueNetIdToString(FromPeerNetId)
			,,'DevNet');

		// This search object is kept around until the current game is ended so the join/migration can be initiated
		MigratedSearchToJoin = new SearchClass;
		// Bind to the migrated session sent to us by the new host
		if (OnlineSub.GameInterface.BindPlatformSpecificSessionToSearch(LP.ControllerId,MigratedSearchToJoin,PlatformSpecificInfo))
		{
			// Remove reference to pending session to join via migrate
			SessionToJoin = MigratedSearchToJoin.Results[0];
			MigratedSearchToJoin = None;
			// Set the delegate for notification of the join completing
			OnlineSub.GameInterface.AddJoinMigratedOnlineGameCompleteDelegate(OnJoinMigratedGame);
			// This will have us join the migrated session async
			OnlineSub.GameInterface.JoinMigratedOnlineGame(LP.ControllerId,SessionName,SessionToJoin);
		}
		else
		{
			`Log(`location @ "failed to bind to migrated session!",,'DevNet');
			MigratedSearchToJoin = None;

			ClientSetProgressMessage(PMT_PeerHostMigrationFailure,
				"<Strings:Engine.Errors.ConnectionFailed>",
				"<Strings:Engine.Errors.ConnectionFailed_Title>",
				true);
		}
	}
}

/**
 * Delegate called after each iteration of unregistering a player
 *
 * @param SessionName the name of the session to unregister player from
 * @param PlayerId net id of player that was unregistered
 * @param bWasSuccessful whether the unregister completed ok or not
 */
function OnUnregisterPlayerCompleteForJoinMigrate(name SessionName,UniqueNetId PlayerId,bool bWasSuccessful)
{
	// Clear the delegate so another unregister can occur
	OnlineSub.GameInterface.ClearUnregisterPlayerCompleteDelegate(OnUnregisterPlayerCompleteForJoinMigrate);

	// Continue unregistering missing peers until none are left
	if (!RemoveMissingPeersFromSession(SessionName,OnUnregisterPlayerCompleteForJoinMigrate))
	{
		PeerDesignatedAsClient(SessionName);
	}
}

/**
 * This peer player has been to told to travel with a migrated session to new host.
 *
 * @param SessionName Name of the session that was migrated. Can be 'None' if migrating without a session
 */
function PeerDesignatedAsClient(name SessionName)
{
	local string URL;

	// We are joining so grab the connect string to use
	if (OnlineSub.GameInterface.GetResolvedConnectString(SessionName,URL))
	{
		`Log(`location @ "traveling to joined,migrated session "
			$" SessionName="$SessionName
			$" URL="$URL
			,,'DevNet');

		// travel to the specified URL
		ClientTravel(URL, TRAVEL_Absolute);
	}
	else
	{
		`Log(`location @ "failed joining migrated session "
				$" SessionName="$SessionName
				,,'DevNet');

		ClientSetProgressMessage(PMT_PeerHostMigrationFailure,
			"<Strings:Engine.Errors.ConnectionFailed>",
			"<Strings:Engine.Errors.ConnectionFailed_Title>",
			true);
	}

}

/**
 * Delegate called once the join/migration of the session has completed.
 *
 * @param SessionName the name of the session being joined/migrated
 * @param bWasSuccessful whether the join/migrate completed ok or not
 */
function OnJoinMigratedGame(name SessionName,bool bWasSuccessful)
{
	OnlineSub.GameInterface.ClearJoinMigratedOnlineGameCompleteDelegate(OnJoinMigratedGame);

	if (bWasSuccessful)
	{
		// Remove all non-peers from session and join once that completes
		if (!RemoveMissingPeersFromSession(SessionName,OnUnregisterPlayerCompleteForJoinMigrate))
		{
			PeerDesignatedAsClient(SessionName);
		}
	}
	if (!bWasSuccessful)
	{
		`Log(`location @ "failed joining migrated session "
				$" SessionName="$SessionName
				,,'DevNet');

		ClientSetProgressMessage(PMT_PeerHostMigrationFailure,
			"<Strings:Engine.Errors.ConnectionFailed>",
			"<Strings:Engine.Errors.ConnectionFailed_Title>",
			true);
	}
}

event PreRender(Canvas Canvas);

function ResetTimeMargin()
{
	TimeMargin = -0.1;
	MaxTimeMargin = class'GameInfo'.Default.MaxTimeMargin;
}

reliable server function ServerShortTimeout()
{
	local Actor A;

	if (!bShortConnectTimeout)
	{
		bShortConnectTimeOut = true;
		ResetTimeMargin();

		// quick update of pickups and gameobjectives since this player is now relevant
		if (WorldInfo.Pauser != None)
		{
			// update everything immediately, as TimeSeconds won't get advanced while paused
			// so otherwise it won't happen at all until the game is unpaused
			// this floods the network, but we're paused, so no gameplay is going on that would care much
			foreach AllActors(class'Actor', A)
			{
				if (!A.bOnlyRelevantToOwner)
				{
					A.bForceNetUpdate = TRUE;
				}
			}
		}
		else if ( WorldInfo.Game.NumPlayers < 8 )
		{
			foreach AllActors(class'Actor', A)
			{
				if ( (A.NetUpdateFrequency < 1) && !A.bOnlyRelevantToOwner )
				{
					A.SetNetUpdateTime(FMin(A.NetUpdateTime, WorldInfo.TimeSeconds + 0.2 * FRand()));
				}
			}
		}
		else
		{
			foreach AllActors(class'Actor', A)
			{
				if ( (A.NetUpdateFrequency < 1) && !A.bOnlyRelevantToOwner )
				{
					A.SetNetUpdateTime(FMin(A.NetUpdateTime, WorldInfo.TimeSeconds + 0.5 * FRand()));
				}
			}
		}
	}
}

function ServerGivePawn()
{
	GivePawn(Pawn);
}

event KickWarning()
{
	ReceiveLocalizedMessage( class'GameMessage', 15 );
}

function AddCheats(optional bool bForce)
{
	// Assuming that this never gets called for NM_Client without bForce=true
	if ( (CheatManager == None) && (WorldInfo.Game != None) && WorldInfo.Game.AllowCheats(self) || bForce)
	{
		CheatManager = new(Self) CheatClass;
		CheatManager.InitCheatManager();
	}
}

exec function EnableCheats()
{
`if(`notdefined(ShippingPC))
	AddCheats(true);
`else
	AddCheats();
`endif
}

/* SpawnDefaultHUD()
Spawn a HUD (make sure that PlayerController always has valid HUD, even if \
ClientSetHUD() hasn't been called\
*/
function SpawnDefaultHUD()
{
	if ( LocalPlayer(Player) == None )
		return;
	`log(GetFuncName());
	myHUD = spawn(class'HUD',self);
}

/* Reset()
reset actor to initial state - used when restarting level without reloading.
*/
function Reset()
{
	local vehicle DrivenVehicle;

    DrivenVehicle = Vehicle(Pawn);
	if( DrivenVehicle != None )
		DrivenVehicle.DriverLeave(true); // Force the driver out of the car

	if ( Pawn != None )
	{
		PawnDied( Pawn );
		UnPossess();
	}

	super.Reset();

	SetViewTarget( Self );
    ResetCameraMode();
	WaitDelay = WorldInfo.TimeSeconds + 2;
    FixFOV();
    if ( PlayerReplicationInfo.bOnlySpectator )
    	GotoState('Spectating');
    else
		GotoState('PlayerWaiting');
}

reliable client function ClientReset()
{
	ResetCameraMode();
	SetViewTarget(self);
	GotoState(PlayerReplicationInfo.bOnlySpectator ? 'Spectating' : 'PlayerWaiting');
}

function CleanOutSavedMoves()
{
	SavedMoves = None;
	PendingMove = None;
}

/**
 * Notification that the ControllerId for this PC LocalPlayer is about to change.  Provides the PC a chance to cleanup anything that was
 * associated with the old ControllerId.  When this method is called, LocalPlayer.ControllerId is still the old value.
 */
function PreControllerIdChange()
{
	local LocalPlayer LP;

	LP = LocalPlayer(Player);
	if ( LP != None )
	{
		ClientStopNetworkedVoice();
		ClearOnlineDelegates();

		UnregisterPlayerDataStores();
	}
}

/**
 * Notification that the ControllerId for this PC's LocalPlayer has been changed.  Re-register all player data stores and any delegates that
 * require a ControllerId.
 */
function PostControllerIdChange()
{
	local LocalPlayer LP;
	local UniqueNetId PlayerId;

	LP = LocalPlayer(Player);
	if ( LP != None )
	{
		// update unique ID
		// you get kicked for this in network games so no need to update
		if (WorldInfo.NetMode != NM_Client && OnlineSub != None && OnlineSub.PlayerInterface != None)
		{
			// Get our local id from the online subsystem
			OnlineSub.PlayerInterface.GetUniquePlayerId(LP.ControllerId, PlayerId);
			PlayerReplicationInfo.SetUniqueId(PlayerId);
		}
		RegisterPlayerDataStores();

		RegisterOnlineDelegates();
		ClientSetOnlineStatus();

		// we don't currently allow players to migrate gamepads after the initial interactive screen.  If we are a client or not in the menu
		// level then that constraint has been lifted - technically this shouldn't be an assertion, but this would be a really difficult bug
		// to track down otherwise, if switching gamepads is ever allowed after initial interactive scene.
		`assert(WorldInfo.Game != None);
		if ( !WorldInfo.Game.bRequiresPushToTalk )
		{
			ClientStartNetworkedVoice();
		}
	}
}

/**
 * Wrapper for getting reference to the OnlineSubsystem
 */
simulated final function OnlineSubsystem GetOnlineSubsystem()
{
	if ( OnlineSub == None )
	{
		OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
	}

	return OnlineSub;
}

/* InitInputSystem()
Spawn the appropriate class of PlayerInput
Only called for playercontrollers that belong to local players
*/
event InitInputSystem()
{
	local Class<ForceFeedbackManager> FFManagerClass;
	local int i;
	local Sequence GameSeq;
	local array<SequenceObject> AllInterpActions;

	if (PlayerInput == None)
	{
		Assert(InputClass != None);
		PlayerInput = new(Self) InputClass;

		if (PlayerInput != none)
		{
			PlayerInput.InitInputSystem();
		}
	}


	if ( Interactions.Find(PlayerInput) == -1 )
	{
		Interactions[Interactions.Length] = PlayerInput;
	}

	// Spawn the waveform manager here
	if (ForceFeedbackManagerClassName != "")
	{
		FFManagerClass = class<ForceFeedbackManager>(DynamicLoadObject(ForceFeedbackManagerClassName,class'Class'));
		if (FFManagerClass != None)
		{
			ForceFeedbackManager = new(Self) FFManagerClass;
		}
	}

	RegisterOnlineDelegates();

	// add the player to any matinees running so that it gets in on any cinematics already running, etc
	// (already done on server in PostLogin())
	if (Role < ROLE_Authority)
	{
		GameSeq = WorldInfo.GetGameSequence();
		if (GameSeq != None)
		{
			// find any matinee actions that exist
			GameSeq.FindSeqObjectsByClass(class'SeqAct_Interp', true, AllInterpActions);

			// tell them all to add this PC to any running Director tracks
			for (i = 0; i < AllInterpActions.Length; i++)
			{
				SeqAct_Interp(AllInterpActions[i]).AddPlayerToDirectorTracks(self);
			}
		}
	}

	// this is a good stop gap measure for any cases that we miss / other code getting turned on / called
	// there is never a case where we want the tilt to be on at the point where the player controller is created
	SetOnlyUseControllerTiltInput( FALSE );
	SetUseTiltForwardAndBack( TRUE );
	SetControllerTiltActive( FALSE );
}

/**
 * Initializes this client's Player data stores after seamless map travel
 */
reliable client function ClientInitializeDataStores()
{
	`log(">> PlayerController::ClientInitializeDataStores for player" @ Self,,'DevDataStore');

	// register the player's data stores and bind the PRI to the PlayerOwner data store.
	RegisterPlayerDataStores();

	`log("<< PlayerController::ClientInitializeDataStores for player" @ Self,,'DevDataStore');
}

/**
 * Register all player data stores.
 */
simulated final function RegisterPlayerDataStores()
{
	RegisterCustomPlayerDataStores();
	RegisterStandardPlayerDataStores();
}

/**
 * Creates and initializes the "PlayerOwner" and "PlayerSettings" data stores.  This function assumes that the PlayerReplicationInfo
 * for this player has not yet been created.
 */
simulated protected function RegisterCustomPlayerDataStores()
{
	local LocalPlayer LP;
	local DataStoreClient DataStoreManager;
	local class<UIDataStore_OnlinePlayerData> PlayerDataStoreClass;

`if(`notdefined(FINAL_RELEASE))
	local string PlayerName;

	PlayerName = PlayerReplicationInfo != None ? PlayerReplicationInfo.PlayerName : "None";
`endif

	// only create player data store for local players
	LP = LocalPlayer(Player);

	`log(">>" @ `location @ "(" $ PlayerName $ ")" @ `showobj(LP),,'DevDataStore');
	if ( LP != None )
	{
		// get a reference to the main data store client
		DataStoreManager = class'UIInteraction'.static.GetDataStoreClient();
		if ( DataStoreManager != None )
		{
			// create the OnlinePlayerData data store
			OnlinePlayerData = UIDataStore_OnlinePlayerData(DataStoreManager.FindDataStore('OnlinePlayerData',LP));
			if ( OnlinePlayerData == None )
			{
				PlayerDataStoreClass = class<UIDataStore_OnlinePlayerData>(DataStoreManager.FindDataStoreClass(class'UIDataStore_OnlinePlayerData'));
				if ( PlayerDataStoreClass != None )
				{
					// find the appropriate class to use for the PlayerSettings data store
					// create the PlayerSettings data store
					OnlinePlayerData = DataStoreManager.CreateDataStore(PlayerDataStoreClass);
					if ( OnlinePlayerData != None )
					{
						if ( !DataStoreManager.RegisterDataStore(OnlinePlayerData, LP) )
						{
							`log("Failed to register 'OnlinePlayerData' data store for player:"@ Self @ "(" $ PlayerName $ ")" @ `showobj(OnlinePlayerData),,'DevDataStore');
						}
					}
					else
					{
						`log("Failed to create 'OnlinePlayerData' data store for player:"@ Self @ "(" $ PlayerName $ ") using class" @ PlayerDataStoreClass,,'DevDataStore');
					}
				}
				else
				{
					`log("Failed to find valid data store class while attempting to register the 'OnlinePlayerData' data store for player:"@ Self @ "(" $ PlayerName $ ")",,'DevDataStore');
				}
			}
			else
			{
				`log("'OnlinePlayerData' data store already registered for player:"@ Self @ "(" $ PlayerName $ ")",,'DevDataStore');
			}
		}

	}

	`log("<<" @ `location @ "(" $ PlayerName $ ")",,'DevDataStore');
}

/**
 * Register any player data stores which do not require special initialization.
 */
simulated protected function RegisterStandardPlayerDataStores()
{
	local LocalPlayer LP;
	local DataStoreClient DataStoreManager;
	local array<class<UIDataStore> > PlayerDataStoreClasses;
	local class<UIDataStore> PlayerDataStoreClass;
	local UIDataStore PlayerDataStore;
	local int ClassIndex;

`if(`notdefined(FINAL_RELEASE))
	local string PlayerName;

	PlayerName = PlayerReplicationInfo != None ? PlayerReplicationInfo.PlayerName : "None";
`endif

	// only create player data store for local players
	LP = LocalPlayer(Player);
	if ( LP != None )
	{
		`log(">>" @ `location @ "(" $ PlayerName $ ")",,'DevDataStore');

		// get a reference to the main data store client
		DataStoreManager = class'UIInteraction'.static.GetDataStoreClient();
		if ( DataStoreManager != None )
		{
			DataStoreManager.GetPlayerDataStoreClasses(PlayerDataStoreClasses);

			// go through the data store client's list of player data store classes
			for ( ClassIndex = 0; ClassIndex < PlayerDataStoreClasses.Length; ClassIndex++ )
			{
				PlayerDataStoreClass = PlayerDataStoreClasses[ClassIndex];
				if ( PlayerDataStoreClass != None )
				{
					// if the data store isn't already registered, do it now
					PlayerDataStore = DataStoreManager.FindDataStore(PlayerDataStoreClass.default.Tag, LP);
					if ( PlayerDataStore == None )
					{
						`log("    Registering standard player data store '" $ PlayerDataStoreClass.Name $ "' for player" @ Self @ "(" $ PlayerName $ ")" @ `showobj(LP),,'DevDataStore');
						PlayerDataStore = DataStoreManager.CreateDataStore(PlayerDataStoreClass);
						if ( PlayerDataStore != None )
						{
							if ( !DataStoreManager.RegisterDataStore(PlayerDataStore, LP) )
							{
								`log("Failed to register '" $ PlayerDataStoreClass.default.Tag $ "' data store for player:" @ Self @ "(" $ PlayerName $ ")" @ `showobj(PlayerDataStore),,'DevDataStore');
							}
						}
					}
					else
					{
						`log("'" $ PlayerDataStoreClass.default.Tag $ "' data store already registered for player:" @ Self @ "(" $ PlayerName $ ")",,'DevDataStore');
					}
				}
			}
		}
	}
}

/**
 * Unregisters the "PlayerOwner" data store for this player.  Called when this PlayerController is destroyed.
 */
simulated function UnregisterPlayerDataStores()
{
	local LocalPlayer LP;
	local DataStoreClient DataStoreManager;
	local UIDataStore_OnlinePlayerData OnlinePlayerDataStore;

`if(`notdefined(FINAL_RELEASE))
	local string PlayerName;

	PlayerName = PlayerReplicationInfo != None ? PlayerReplicationInfo.PlayerName : "None";
`endif

	// only execute for local players
	LP = LocalPlayer(Player);
	if ( LP != None )
	{
		`log(">>" @ `location @ "(" $ PlayerName $ ")",,'DevDataStore');

		// because the PlayerOwner data store is created and registered each match, all we should need to do is
		// unregister it from the data store client and clear our reference
		// get a reference to the main data store client
		DataStoreManager = class'UIInteraction'.static.GetDataStoreClient();
		if ( DataStoreManager != None )
		{
			// unregister the current player data store
			// Don't hold onto a ref
			OnlinePlayerData = None;
			// Unregister the online player data
			OnlinePlayerDataStore = UIDataStore_OnlinePlayerData(DataStoreManager.FindDataStore('OnlinePlayerData',LP));
			if ( OnlinePlayerDataStore != None )
			{
				if ( !DataStoreManager.UnregisterDataStore(OnlinePlayerDataStore) )
				{
					`log("Failed to unregister 'OnlinePlayerData' data store for player:"@ Self @ "(" $ PlayerName $ ")" @ `showobj(OnlinePlayerDataStore),,'DevDataStore');
				}
			}
			else
			{
				`log("'OnlinePlayerData' data store not registered for player:" @ Self @ "(" $ PlayerName $ ")",,'DevDataStore');
			}

			UnregisterStandardPlayerDataStores();
		}
		else
		{
			`log("Data store client not found!",,'DevDataStore');
		}

		`log("<<" @ `location @ "(" $ PlayerName $ ")",,'DevDataStore');
	}
}

/**
 * Unregisters all player data stores that remain after unregistering all player data stores that require custom unregister logic.
 */
simulated function UnregisterStandardPlayerDataStores()
{
	local LocalPlayer LP;
	local DataStoreClient DataStoreManager;
	local array<class<UIDataStore> > PlayerDataStoreClasses;
	local class<UIDataStore> PlayerDataStoreClass;
	local UIDataStore PlayerDataStore;
	local int ClassIndex;

`if(`notdefined(FINAL_RELEASE))
	local string PlayerName;

	PlayerName = PlayerReplicationInfo != None ? PlayerReplicationInfo.PlayerName : "None";
`endif

	// only execute for local players
	LP = LocalPlayer(Player);
	if ( LP != None )
	{
		`log(">>" @ `location @ "(" $ PlayerName $ ")",,'DevDataStore');

		// because the PlayerOwner data store is created and registered each match, all we should need to do is
		// unregister it from the data store client and clear our reference
		// get a reference to the main data store client
		DataStoreManager = class'UIInteraction'.static.GetDataStoreClient();
		if ( DataStoreManager != None )
		{
			DataStoreManager.GetPlayerDataStoreClasses(PlayerDataStoreClasses);

			// now go through the rest of the registered player data stores and unregister them
			for ( ClassIndex = 0; ClassIndex < PlayerDataStoreClasses.Length; ClassIndex++ )
			{
				PlayerDataStoreClass = PlayerDataStoreClasses[ClassIndex];
				if ( PlayerDataStoreClass != None )
				{
					// if the data store isn't already registered, do it now
					PlayerDataStore = DataStoreManager.FindDataStore(PlayerDataStoreClass.default.Tag, LP);
					if ( PlayerDataStore != None )
					{
						if ( !DataStoreManager.UnregisterDataStore(PlayerDataStore) )
						{
							`log("Failed to unregister '" $ PlayerDataStore.Tag $ "' data store for player:"@ Self @ "(" $ PlayerName $ ")" @ `showobj(PlayerDataStore),,'DevDataStore');
						}
					}
				}
			}
		}

		`log("<<" @ `location @ "(" $ PlayerName $ ")",,'DevDataStore');
	}
}

/**
 * Refetches this player's profile settings.
 */
simulated function ReloadProfileSettings()
{
	if ( OnlinePlayerData != None && OnlinePlayerData.ProfileProvider != None )
	{
		OnlinePlayerData.ProfileProvider.RefreshStorageData();
	}
}


/**
 * Scales the amount the rumble will play on the gamepad
 *
 * @param ScaleBy The amount to scale the waveforms by
 */
final function SetRumbleScale(float ScaleBy)
{
	if (ForceFeedbackManager != None)
	{
		ForceFeedbackManager.ScaleAllWaveformsBy = ScaleBy;
	}
}

/**
 * @return whether or not this Controller has Tilt Turned on
 **/
native simulated function bool IsControllerTiltActive() const;

/**
 * sets whether or not the Tilt functionality is turned on
 **/
native simulated function SetControllerTiltActive( bool bActive );

/**
 * sets whether or not to ONLY use the tilt input controls
 **/
native simulated function SetOnlyUseControllerTiltInput( bool bActive );

/**
 * sets whether or not to use the tilt forward and back input controls
 **/
native simulated function SetUseTiltForwardAndBack( bool bActive );

/**
 * @return whether or not this Controller has a keyboard available to be used
 **/
native simulated function bool IsKeyboardAvailable() const;

/**
 * @return whether or not this Controller has a mouse available to be used
 **/
native simulated function bool IsMouseAvailable() const;

/**
 * Exec command to control tilt so that kismet can control it
 */
exec function SetTiltActive(bool bActive)
{
	SetControllerTiltActive(bActive);
}

/* ClientGotoState()
server uses this to force client into NewState
*/
reliable client function ClientGotoState(name NewState, optional name NewLabel)
{
	if ((NewLabel == 'Begin' || NewLabel == '') && !IsInState(NewState))
	{
		GotoState(NewState);
	}
	else
	{
		GotoState(NewState,NewLabel);
	}
}

reliable server function AskForPawn()
{
	if ( GamePlayEndedState() )
		ClientGotoState(GetStateName(), 'Begin');
	else if ( Pawn != None )
		GivePawn(Pawn);
	else
	{
		bFrozen = false;
		ServerRestartPlayer();
	}
}

reliable client function GivePawn(Pawn NewPawn)
{
	if (NewPawn == None)
	{
		return;
	}

	// Avoid calling ClientRestart if we have already accepted this pawn
	if (Pawn != NewPawn || NewPawn.Controller != Self)
	{
		Pawn = NewPawn;
		NewPawn.Controller = self;
		ClientRestart(Pawn);
	}
}

// Possess a pawn
event Possess(Pawn aPawn, bool bVehicleTransition)
{
	local Actor A;
	local int i;
	local SeqEvent_Touch TouchEvent;

	if (!PlayerReplicationInfo.bOnlySpectator)
	{
		if (aPawn.Controller != None)
		{
			aPawn.Controller.UnPossess();
		}

		aPawn.PossessedBy(self, bVehicleTransition);
		Pawn = aPawn;
		Pawn.SetTickIsDisabled(false);
		ResetTimeMargin();
		Restart(bVehicleTransition);

		// check if touching any actors with playeronly kismet touch events
		ForEach Pawn.TouchingActors(class'Actor', A)
		{
			for ( i=0; i < A.GeneratedEvents.length; i++)
			{
				TouchEvent = SeqEvent_Touch(A.GeneratedEvents[i]);
				if ( (TouchEvent != None) && TouchEvent.bPlayerOnly )
				{
					TouchEvent.CheckTouchActivate(A,Pawn);
				}
			}
		}
	}
}

function AcknowledgePossession(Pawn P)
{
	if ( LocalPlayer(Player) != None )
	{
		AcknowledgedPawn = P;
		if ( P != None )
		{
			P.SetBaseEyeHeight();
			P.Eyeheight = P.BaseEyeHeight;
		}
		ServerAcknowledgePossession(P);
	}
}

reliable server function ServerAcknowledgePossession(Pawn P)
{
	if ( (P != None) && (P == Pawn) && (P != AcknowledgedPawn) )
	{
	ResetTimeMargin();
	}
    AcknowledgedPawn = P;
}

// unpossessed a pawn (not because pawn was killed)
event UnPossess()
{
	if (Pawn != None)
	{
		SetLocation(Pawn.Location);
		if (Role == ROLE_Authority)
		{
			Pawn.RemoteRole = ROLE_SimulatedProxy;
		}
		Pawn.UnPossessed();
		CleanOutSavedMoves();  // don't replay moves previous to unpossession
		if (GetViewTarget() == Pawn)
		{
			SetViewTarget(self);
		}
	}
	Pawn = None;
}

// unpossessed a pawn (because pawn was killed)
function PawnDied(Pawn P)
{
	if (P == Pawn)
	{
		if (Role == ROLE_Authority && Pawn != None)
		{
			Pawn.RemoteRole = ROLE_SimulatedProxy;
		}

		Super.PawnDied(P);
	}
}

reliable client function ClientSetHUD(class<HUD> newHUDType)
{
	if ( myHUD != None )
	{
		myHUD.Destroy();
	}

	myHUD = (newHUDType != None) ? Spawn(newHUDType, self) : None;
}

reliable client function ClientSetSecondaryHUD(class<HUD> newHUDType)
{
	if ( mySecondaryHUD != None )
	{
		mySecondaryHUD.Destroy();
	}

	mySecondaryHUD = (newHUDType != None) ? Spawn(newHUDType, self) : None;
}

function HandlePickup(Inventory Inv)
{
	ReceiveLocalizedMessage(Inv.MessageClass,,,,Inv.class);
}

/* epic ===============================================
* ::CleanupPRI
*
* Called from Destroyed().  Cleans up PlayerReplicationInfo.
* PlayerControllers add their PRI to the gameinfo's InactivePRI list, so disconnected players can rejoin without losing their score.
*
* =====================================================
*/
function CleanupPRI()
{
	WorldInfo.Game.AddInactivePRI(PlayerReplicationInfo, self);
	PlayerReplicationInfo = None;
}

reliable client event ReceiveLocalizedMessage( class<LocalMessage> Message, optional int Switch, optional PlayerReplicationInfo RelatedPRI_1, optional PlayerReplicationInfo RelatedPRI_2, optional Object OptionalObject )
{
	// Wait for player to be up to date with replication when joining a server, before stacking up messages
	if ( WorldInfo.NetMode == NM_DedicatedServer || WorldInfo.GRI == None )
		return;

	Message.Static.ClientReceive( Self, Switch, RelatedPRI_1, RelatedPRI_2, OptionalObject );
}

//Play a sound client side (so only client will hear it)
unreliable client event ClientPlaySound(SoundCue ASound)
{
	ClientHearSound(ASound, self, Location, false, false);
}

/** hooked up to the OnAudioFinished delegate of AudioComponents created through PlaySound() to return them to the pool */
simulated function HearSoundFinished(AudioComponent AC)
{
	HearSoundActiveComponents.RemoveItem(AC);
	// if the component is already pending kill (playing actor was destroyed), then we can't put it back in the pool
	if (!AC.IsPendingKill())
	{
		AC.ResetToDefaults();
		HearSoundPoolComponents[HearSoundPoolComponents.length] = AC;
	}
}

/** get an audio component from the HearSound pool
 * creates a new component if the pool is empty and MaxConcurrentHearSounds has not been exceeded
 * the component is initialized with the values passed in, ready to call Play() on
 * its OnAudioFinished delegate is set to this PC's HearSoundFinished() function
 * @param ASound - the sound to play
 * @param SourceActor - the Actor to attach the sound to (if None, attached to self)
 * @param bStopWhenOwnerDestroyed - whether the sound stops if SourceActor is destroyed
 * @param bUseLocation (optional) - whether to use the SourceLocation parameter for the sound's location (otherwise, SourceActor's location)
 * @param SourceLocation (optional) - if bUseLocation, the location for the sound
 * @return the AudioComponent that was found/created
 */
native function AudioComponent GetPooledAudioComponent(SoundCue ASound, Actor SourceActor, bool bStopWhenOwnerDestroyed, optional bool bUseLocation, optional vector SourceLocation);

/* ClientHearSound()
Replicated function from server for replicating audible sounds played on server
*/
unreliable client event ClientHearSound(SoundCue ASound, Actor SourceActor, vector SourceLocation, bool bStopWhenOwnerDestroyed, optional bool bIsOccluded )
{
	local AudioComponent AC;

//    `log("### ClientHearSound:"@ASound@SourceActor@SourceLocation@bStopWhenOwnerDestroyed@VSize(SourceLocation - Pawn.Location));

	if ( SourceActor == None )
	{
		AC = GetPooledAudioComponent(ASound, SourceActor, bStopWhenOwnerDestroyed, true, SourceLocation);
		if (AC == None)
		{
			return;
		}
		AC.bUseOwnerLocation = false;
		AC.Location = SourceLocation;
	}
	else if ( (SourceActor == GetViewTarget()) || (SourceActor == self) )
	{
		AC = GetPooledAudioComponent(ASound, None, bStopWhenOwnerDestroyed);
		if (AC == None)
		{
			return;
		}
		AC.bAllowSpatialization = false;
	}
	else
	{
		AC = GetPooledAudioComponent(ASound, SourceActor, bStopWhenOwnerDestroyed);
		if (AC == None)
		{
			return;
		}
		if (!IsZero(SourceLocation) && SourceLocation != SourceActor.Location)
		{
			AC.bUseOwnerLocation = false;
			AC.Location = SourceLocation;
		}
	}
	if ( bIsOccluded )
	{
		// if occluded reduce volume: @FIXME do something better
		AC.VolumeMultiplier *= 0.5;
	}
	AC.Play();
}

`if(`__TW_WWISE_)
/* ClientHearSoundAdvanced()
 * Replicated function from server for replicating audible sounds played on
 * server that have advanced functionality and require replicating rotation
 * Advanced functionality implemented in subclasses.
*/
unreliable client event ClientHearSoundAdvanced(AkEvent ASound, Actor SourceActor, vector SourceLocation, byte CompressedSourcePitch, byte CompressedSourceYaw, byte RapidFireEnabled, bool bStopWhenOwnerDestroyed, optional bool bIsOccluded )
{
    WwiseClientHearSound(ASound, SourceActor, SourceLocation, bStopWhenOwnerDestroyed, bIsOccluded);
}

/* ClientHearSoundAdvanced()
 * Replicated function from server for replicating audible sounds played on
 * server that have advanced functionality and will get the rotation from a
 * relevant actor. Advanced functionality implemented in subclasses.
*/
unreliable client event ClientHearSoundAdvancedRelevant(AkEvent ASound, Actor SourceActor, vector SourceLocation, byte RapidFireEnabled, bool bStopWhenOwnerDestroyed, optional bool bIsOccluded )
{
    WwiseClientHearSound(ASound, SourceActor, SourceLocation, bStopWhenOwnerDestroyed, bIsOccluded);
}
`endif //__TW_WWISE_

// WWISEMODIF_START
native unreliable client event WwiseClientHearSound(AkEvent ASound, Actor SourceActor, vector SourceLocation, bool bStopWhenOwnerDestroyed, optional bool bIsOccluded );
// WWISEMODIF_END

simulated function bool IsClosestLocalPlayerToActor(Actor TheActor)
{
	local PlayerController PC;
	local float MyDist;

	if(ViewTarget == none)
	{
		return false;
	}
	MyDist = VSize(ViewTarget.Location - TheActor.Location);
	ForEach LocalPlayerControllers( class'PlayerController', PC )
	{
		if( PC != self && (PC.ViewTarget != None) && (VSize(PC.ViewTarget.Location - TheActor.Location) < MyDist) )
		{
			return false;
		}
	}
	return true;
}
reliable client event Kismet_ClientPlaySound(SoundCue ASound, Actor SourceActor, float VolumeMultiplier, float PitchMultiplier, float FadeInTime, bool bSuppressSubtitles, bool bSuppressSpatialization)
{
	local AudioComponent AC;

	if ( SourceActor != None && IsClosestLocalPlayerToActor(SourceActor))
	{
		// If we have a FaceFX animation hooked up, play that instead
// WWISEMODIF_START, alessard, nov-28-2008, WwiseAudioIntegration
		if( ASound.FaceFXAnimName != "" &&
			SourceActor.PlayActorFaceFXAnim(ASound.FaceFXAnimSetRef, ASound.FaceFXGroupName, ASound.FaceFXAnimName, ASound, none) )
		{
			// Success - In case of failure, fall back to playing the sound with no Face FX animation, but there will be a warning in the log instead.
		}
// WWISEMODIF_END
		else
		{
			AC = SourceActor.CreateAudioComponent(ASound, false, true);
			if ( AC != None )
			{
				AC.VolumeMultiplier = VolumeMultiplier;
				AC.PitchMultiplier = PitchMultiplier;
				AC.bAutoDestroy = true;
				AC.SubtitlePriority = 10000;
				AC.bSuppressSubtitles = bSuppressSubtitles;
				AC.FadeIn(FadeInTime, 1.f);
				if( bSuppressSpatialization )
				{
					AC.bAllowSpatialization = false;
				}
			}
		}
	}
}

reliable client event Kismet_ClientStopSound(SoundCue ASound, Actor SourceActor, float FadeOutTime)
{
	local AudioComponent AC, CheckAC;

	if (SourceActor == None)
	{
		SourceActor = WorldInfo;
	}
	foreach SourceActor.AllOwnedComponents(class'AudioComponent',CheckAC)
	{
		if (CheckAC.SoundCue == ASound)
		{
			AC = CheckAC;
			break;
		}
	}
	if (AC != None)
	{
		AC.FadeOut(FadeOutTime,0.f);
	}
}

/** plays a FaceFX anim on the specified actor for the client */
// WWISEMODIF_START, alessard, nov-28-2008, WwiseAudioIntegration
reliable client function ClientPlayActorFaceFXAnim(Actor SourceActor, FaceFXAnimSet AnimSet, string GroupName, string SeqName, SoundCue SoundCueToPlay, AkEvent AkEventToPlay)
{
	if (SourceActor != None)
	{
		SourceActor.PlayActorFaceFXAnim(AnimSet, GroupName, SeqName, SoundCueToPlay, AkEventToPlay);
	}
}
// WWISEMODIF_END

reliable client event ClientMessage( coerce string S, optional Name Type, optional float MsgLifeTime )
{
	if ( WorldInfo.NetMode == NM_DedicatedServer || WorldInfo.GRI == None )
	{
		return;
	}

	if (Type == '')
	{
		Type = 'Event';
	}

	TeamMessage(PlayerReplicationInfo, S, Type, MsgLifeTime);
}

/** Overridden by specific games */
simulated private function bool CanCommunicate()
{
	return TRUE;
}

simulated private function bool AllowTTSMessageFrom( PlayerReplicationInfo PRI )
{
	return TRUE;
}

/**
 * Text to speech handling
 */

/** Constructs a SoundCue, performs text-to-wavedata conversion. */
simulated private native function SoundCue CreateTTSSoundCue( string StrToSpeak, PlayerReplicationInfo PRI );

exec function Talk()
{
	local Console PlayerConsole;
	local LocalPlayer LP;

	LP = LocalPlayer( Player );
	if( ( LP != None ) && CanCommunicate() && ( LP.ViewportClient.ViewportConsole != None ) )
	{
		PlayerConsole = LocalPlayer( Player ).ViewportClient.ViewportConsole;
		PlayerConsole.StartTyping( "Say " );
	}
}

exec function TeamTalk()
{
	local Console PlayerConsole;
	local LocalPlayer LP;

	LP = LocalPlayer( Player );
	if( ( LP != None ) && CanCommunicate() && ( LP.ViewportClient.ViewportConsole != None ) )
	{
		PlayerConsole = LocalPlayer( Player ).ViewportClient.ViewportConsole;
		PlayerConsole.StartTyping( "TeamSay " );
	}
}

simulated function SpeakTTS( coerce string S, optional PlayerReplicationInfo PRI )
{
	local SoundCue Cue;
	local AudioComponent AC;

	Cue = CreateTTSSoundCue( S, PRI );
	if( Cue != None )
	{
		AC = CreateAudioComponent( Cue, FALSE, TRUE, , , TRUE );
		AC.bAllowSpatialization = FALSE;
		AC.bAutoDestroy = TRUE;
		AC.Play();
	}
}

reliable client event TeamMessage( PlayerReplicationInfo PRI, coerce string S, name Type, optional float MsgLifeTime  )
{
	local bool bIsUserCreated;

	if( CanCommunicate() )
	{
		if( ( ( Type == 'Say' ) || (Type == 'TeamSay' ) ) && ( PRI != None ) && AllowTTSMessageFrom( PRI ) )
		{
			if( !bIsUserCreated || ( bIsUserCreated && CanViewUserCreatedContent() ) )
			{
				SpeakTTS( S, PRI );
			}
		}

		if( myHUD != None )
		{
			myHUD.Message( PRI, S, Type, MsgLifeTime );
		}

		if( ( ( Type == 'Say' ) || ( Type == 'TeamSay' ) ) && ( PRI != None ) )
		{
			S = PRI.PlayerName$": "$S;
			// This came from a user so flag as user created
			bIsUserCreated = true;
		}

		// since this is on the client, we can assume that if Player exists, it is a LocalPlayer
		if( Player != None )
		{
			// Don't allow this if the parental controls block it
			if( !bIsUserCreated || ( bIsUserCreated && CanViewUserCreatedContent() ) )
			{
				LocalPlayer( Player ).ViewportClient.ViewportConsole.OutputText( S );
			}
		}
	}
}

function PlayBeepSound();

/**
 * Registers any handlers for delegates in the OnlineSubsystem.  Called when a player is being created and/or ControllerId is changing.
 */
function RegisterOnlineDelegates()
{
	local LocalPlayer LP;

	LP = LocalPlayer(Player);
	// If there is an online subsystem, add our callback for UI changes
	if (OnlineSub != None && LP != None)
	{
		VoiceInterface = OnlineSub.VoiceInterface;
		if (OnlineSub.SystemInterface != None)
		{
			// Register the callback for when external UI is shown/hidden
			// This will pause/unpause a single player game based upon the UI state
			OnlineSub.SystemInterface.AddExternalUIChangeDelegate(OnExternalUIChanged);
			// This will pause/unpause a single player game based upon the controller state
			OnlineSub.SystemInterface.AddControllerChangeDelegate(OnControllerChanged);
			// This will kick you back to the IIS when you lose connection or log out of PSN
			OnlineSub.SystemInterface.AddConnectionStatusChangeDelegate(OnConnectionStatusChange);
		}
		// Register for accepting game invites if possible
		if (OnlineSub.GameInterface != None)
		{
			OnlineSub.GameInterface.AddGameInviteAcceptedDelegate(LP.ControllerId,OnGameInviteAccepted);
			OnlineSub.GameInterface.AddPlayTogetherStartedDelegate(LP.ControllerId,OnPlayTogetherStarted);
		}
		if (OnlineSub.PartyChatInterface != None)
		{
			OnlineSub.PartyChatInterface.AddPartyMemberListChangedDelegate(LP.ControllerId,OnPartyMemberListChanged);
			OnlineSub.PartyChatInterface.AddPartyMembersInfoChangedDelegate(LP.ControllerId,OnPartyMembersInfoChanged);
		}

//@HSL_BEGIN_XBOX
        if (OnlineSub.PlayerInterface != None)
        {
            OnlineSub.PlayerInterface.AddPrivilegeLevelCheckedDelegate(OnPrivilegeLevelChecked);
        }
//@HSL_END_XBOX
	}
}

/**
 * Is called when someone joins/leaves the player's party chat session
 *
 * @param bJoinedOrLeft true if the player joined, false if they left
 * @param PlayerName the name of the player that was affected
 * @param PlayerId the net id of the player that left
 */
function OnPartyMemberListChanged(bool bJoinedOrLeft,string PlayerName,UniqueNetId PlayerId);

/**
 * Is called when someone in your party chat has their custom data change (game controlled)
 *
 * @param PlayerName the name of the player that was affected
 * @param PlayerId the net id of the player that had data change
 * @param CustomData1 the first 4 bytes of the custom data
 * @param CustomData2 the second 4 bytes of the custom data
 * @param CustomData3 the third 4 bytes of the custom data
 * @param CustomData4 the fourth 4 bytes of the custom data
 */
function OnPartyMembersInfoChanged(string PlayerName,UniqueNetId PlayerId,int CustomData1,int CustomData2,int CustomData3,int CustomData4);

/**
 * Unregisters all delegates previously registered with the online subsystem.  Called when the player controller is being
 * destroyed and/or replaced.
 *
 * @note: in certain cases (i.e. when the channel is closed from the server's end), the player controller will no longer have
 * a reference its ULocalPlayer object.  these delegates won't be cleared, but GC should clear the references for us.
 */
event ClearOnlineDelegates()
{
	local LocalPlayer LP;

	`log("Clearing online delegates for" @ Self @ "(" $ `showobj(Player) $ ")",,'DevOnline');

	LP = LocalPlayer(Player);
	if (Role < ROLE_Authority || LP != None)
	{
		// If there is an online subsystem, clear our callback for UI/controller changes
		if (OnlineSub != None)
		{
			if (OnlineSub.SystemInterface != None)
			{
				OnlineSub.SystemInterface.ClearExternalUIChangeDelegate(OnExternalUIChanged);
				OnlineSub.SystemInterface.ClearControllerChangeDelegate(OnControllerChanged);
				OnlineSub.SystemInterface.ClearConnectionStatusChangeDelegate(OnConnectionStatusChange);
			}

			if (LP != None)
			{
				// Cleanup game invite delegate
				if (OnlineSub.GameInterface != None)
				{
					OnlineSub.GameInterface.ClearGameInviteAcceptedDelegate(LP.ControllerId, OnGameInviteAccepted);
					OnlineSub.GameInterface.ClearPlayTogetherStartedDelegate(LP.ControllerId,OnPlayTogetherStarted);
				}
				if (OnlineSub.PartyChatInterface != None)
				{
					OnlineSub.PartyChatInterface.ClearPartyMemberListChangedDelegate(LP.ControllerId, OnPartyMemberListChanged);
					OnlineSub.PartyChatInterface.ClearPartyMembersInfoChangedDelegate(LP.ControllerId, OnPartyMembersInfoChanged);
				}

//@HSL_BEGIN_XBOX
                if (OnlineSub.PlayerInterface != None)
                {
                    OnlineSub.PlayerInterface.ClearPrivilegeLevelCheckedDelegate(OnPrivilegeLevelChecked);
                }
//@HSL_END_XBOX
			}
		}
	}
}

/** When the Controller is destroyed, the controlled pawn will need to be killed, but not the vehicle */
function CleanupPawn()
{
	local Vehicle	DrivenVehicle;
	local Pawn		Driver;

	// If its a vehicle, just destroy the driver, otherwise do the normal.
	DrivenVehicle = Vehicle(Pawn);
	if (DrivenVehicle != None)
	{
		Driver = DrivenVehicle.Driver;
DrivenVehicle.DriverLeave(TRUE); // Force the driver out of the car
if (Driver != None)
{
	Driver.Health = 0;
	Driver.Died(Self, class'DmgType_Suicided', Driver.Location);
}
	}
	else if (Pawn != None)
	{
		Pawn.Health = 0;
		Pawn.Died(Self, class'DmgType_Suicided', Pawn.Location);
	}
}

event Exit() {}

event Destroyed()
{
	local int EffectIdx;
	local LocalPlayer LPlayer;
	local MaterialEffect Effect;
	local MaterialInstanceConstant MIC;

	// Disable any currently playing rumble
	ClientPlayForceFeedbackWaveform(None, None);

	// if this is a local player, clear all online delegates
	if (Role < ROLE_Authority || LocalPlayer(Player) != None)
	{
		ClearOnlineDelegates();
	}

	// cheatmanager and playerinput cleaned up in C++ PostScriptDestroyed()
	if (Pawn != None)
	{
		CleanupPawn();
	}
	if (myHUD != None)
	{
		myHud.Destroy();
		myHUD = None;
	}

	if (PlayerCamera != None)
	{
		PlayerCamera.Destroy();
		PlayerCamera = None;
	}

	ForceClearUnpauseDelegates();

	// remove this player's data store from the registered data stores..
	UnregisterPlayerDataStores();

	// remove instanced MICs from the PP chain
	LPlayer = LocalPlayer(Player);
	if ((LPlayer != None) && (LPlayer.PlayerPostProcess != None))
	{
		for (EffectIdx = 0; EffectIdx < LPlayer.PlayerPostProcess.Effects.Length; EffectIdx++)
		{
			Effect = MaterialEffect(LPlayer.PlayerPostProcess.Effects[EffectIdx]);
			if ((Effect != None) && (Effect.Material != None))
			{
				MIC = MaterialInstanceConstant(Effect.Material);
				if ((MIC != None) && (MIC.Parent != None))
				{
					Effect.Material = MIC.Parent;
				}
			}
		}
	}

	Super.Destroyed();
}


function FixFOV()
{
	FOVAngle = Default.DefaultFOV;
	DesiredFOV = Default.DefaultFOV;
	DefaultFOV = Default.DefaultFOV;
}

function SetFOV(float NewFOV)
{
	DesiredFOV = NewFOV;
	FOVAngle = NewFOV;
}

function ResetFOV()
{
	DesiredFOV = DefaultFOV;
	FOVAngle = DefaultFOV;
}

exec function FOV(float F)
{
	if (PlayerCamera != None)
	{
		PlayerCamera.SetFOV(F);
		return;
	}

	if ((F >= 80.0) || (WorldInfo.NetMode == NM_Standalone) || PlayerReplicationInfo.bOnlySpectator)
	{
		DefaultFOV = FClamp(F, 80, 100);
		DesiredFOV = DefaultFOV;
	}
}

exec function Mutate(string MutateString)
{
	ServerMutate(MutateString);
}

reliable server function ServerMutate(string MutateString)
{
	if( WorldInfo.NetMode == NM_Client )
		return;
	WorldInfo.Game.Mutate(MutateString, Self);
}

// ------------------------------------------------------------------------
// Messaging functions

function bool AllowTextMessage(string Msg)
{
	local int i;

	if ( (WorldInfo.NetMode == NM_Standalone) || PlayerReplicationInfo.bAdmin )
		return true;
`if(`__TW_)
	if ( ( WorldInfo.Pauser == none) && (WorldInfo.TimeSeconds - LastBroadcastTime < 1 ) )
		return false;
`else
    if ( ( WorldInfo.Pauser == none) && (WorldInfo.TimeSeconds - LastBroadcastTime < 2 ) )
		return false;
`endif

	// lower frequency if same text
	if ( WorldInfo.TimeSeconds - LastBroadcastTime < 5 )
	{
		Msg = Left(Msg,Clamp(len(Msg) - 4, 8, 64));
		for ( i=0; i<4; i++ )
			if ( LastBroadcastString[i] ~= Msg )
				return false;
	}
	for ( i=3; i>0; i-- )
		LastBroadcastString[i] = LastBroadcastString[i-1];

	LastBroadcastTime = WorldInfo.TimeSeconds;
	return true;
}

// Send a message to all players.
exec function Say( string Msg )
{
	Msg = Left(Msg,128);

	if ( AllowTextMessage(Msg) )
		ServerSay(Msg);
}

unreliable server function ServerSay( string Msg )
{
	local PlayerController PC;

	// center print admin messages which start with #
	if (PlayerReplicationInfo.bAdmin && left(Msg,1) == "#" )
	{
		Msg = right(Msg,len(Msg)-1);
		foreach WorldInfo.AllControllers(class'PlayerController', PC)
		{
			PC.ClientAdminMessage(Msg);
		}
		return;
	}

	WorldInfo.Game.Broadcast(self, Msg, 'Say');
}

reliable client function ClientAdminMessage(string Msg)
{
	local LocalPlayer LP;

	LP = LocalPlayer(Player);
	if (LP != None)
	{
		LP.ViewportClient.ClearProgressMessages();
		LP.ViewportClient.SetProgressTime(6);
		LP.ViewportClient.SetProgressMessage(PMT_AdminMessage, Msg);
	}
}

exec function TeamSay( string Msg )
{
	Msg = Left(Msg,128);
	if ( AllowTextMessage(Msg) )
		ServerTeamSay(Msg);
}

unreliable server function ServerTeamSay( string Msg )
{
	LastActiveTime = WorldInfo.TimeSeconds;

	if( !WorldInfo.GRI.GameClass.Default.bTeamGame )
	{
		Say( Msg );
		return;
	}

    WorldInfo.Game.BroadcastTeam( self, Msg, 'TeamSay');
}

// ------------------------------------------------------------------------
/**
 * Called when the local player is about to travel to a new map or IP address.  Provides subclass with an opportunity
 * to perform cleanup or other tasks prior to the travel.
 */
event PreClientTravel( string PendingURL, ETravelType TravelType, bool bIsSeamlessTravel )
{
	local UIInteraction UIController;
	local GameUISceneClient GameSceneClient;

	// notify the UI system that we're about to perform a travel.
	UIController = GetUIController();
	if ( UIController != None && IsPrimaryPlayer() )
	{
		GameSceneClient = UIController.SceneClient;
		if ( GameSceneClient != None )
		{
			GameSceneClient.NotifyClientTravel(Self, PendingURL, TravelType, bIsSeamlessTravel);
		}
		if (PlayerInput != none)
		{
			PlayerInput.PreClientTravel(PendingURL, TravelType, bIsSeamlessTravel);
		}
	}
}

/**
 * Change Camera mode
 *
 * @param	New camera mode to set
 */
exec function Camera( name NewMode )
{
	ServerCamera(NewMode);
}

reliable server function ServerCamera( name NewMode )
{
	if ( NewMode == '1st' )
	{
    	NewMode = 'FirstPerson';
	}
    else if ( NewMode == '3rd' )
	{
    	NewMode = 'ThirdPerson';
	}

	SetCameraMode( NewMode );

`if(`notdefined(FINAL_RELEASE))
	if ( PlayerCamera != None )
		`log("#### " $ PlayerCamera.CameraStyle);
`endif
}

/**
 * Replicated function to set camera style on client
 *
 * @param	NewCamMode, name defining the new camera mode
 */
reliable client function ClientSetCameraMode( name NewCamMode )
{
	if ( PlayerCamera != None )
		PlayerCamera.CameraStyle = NewCamMode;
}

/**
 * Set new camera mode
 *
 * @param	NewCamMode, new camera mode.
 */
function SetCameraMode( name NewCamMode )
{
	if ( PlayerCamera != None )
	{
		PlayerCamera.CameraStyle = NewCamMode;
		if ( WorldInfo.NetMode == NM_DedicatedServer )
		{
			ClientSetCameraMode( NewCamMode );
		}
	}
}

/**
 * Reset Camera Mode to default
 */
event ResetCameraMode()
{
	if ( Pawn != None )
	{	// If we have a pawn, let's ask it which camera mode we should use
		SetCameraMode( Pawn.GetDefaultCameraMode( Self ) );
	}
	else
	{	// otherwise default to first person view.
		SetCameraMode( 'FirstPerson' );
	}
}

reliable client event ClientSetCameraFade(bool bEnableFading, optional color FadeColor, optional vector2d FadeAlpha, optional float FadeTime, optional bool bFadeAudio)
{
	if (PlayerCamera != None)
	{
		PlayerCamera.bEnableFading = bEnableFading;
		if (PlayerCamera.bEnableFading)
		{
			PlayerCamera.FadeColor = FadeColor;
			PlayerCamera.FadeAlpha = FadeAlpha;
			PlayerCamera.FadeTime = FadeTime;
			PlayerCamera.FadeTimeRemaining = FadeTime;
			PlayerCamera.bFadeAudio = bFadeAudio;
		}
 		else
 		{
 			// Make sure FadeAmount finishes at the correct value
 			PlayerCamera.FadeAmount = PlayerCamera.FadeAlpha.Y;

			// Remove any previous fade color.
			PlayerCamera.FadeColor = FadeColor;

 			if (PlayerCamera.bFadeAudio)
 			{
 				PlayerCamera.ApplyAudioFade();
 			}
 		}
	}
}

/**
* return whether viewing in first person mode
*/
function bool UsingFirstPersonCamera()
{
	return ((PlayerCamera == None) || (PlayerCamera.CameraStyle == 'FirstPerson')) && LocalPlayer(Player) != None;
}

/* ForceDeathUpdate()
Make sure ClientAdjustPosition immediately informs client of pawn's death
*/
function ForceDeathUpdate()
{
	LastUpdateTime = WorldInfo.TimeSeconds - 10;
}

/* DualServerMove()
- replicated function sent by client to server - contains client movement and firing info for two moves
*/
unreliable server function DualServerMove
(
	float TimeStamp0,
	vector InAccel0,
	byte PendingFlags,
	int View0,
    float TimeStamp,
    vector InAccel,
    vector ClientLoc,
    byte NewFlags,
    byte ClientRoll,
`if(`__TW_)
    int View,
    optional int FreeAimRot0,
    optional int FreeAimRot
`else
    int View
`endif
)
{
    ServerMove(TimeStamp0,InAccel0,vect(1,2,3),PendingFlags,ClientRoll,View0);
    ServerMove(TimeStamp,InAccel,ClientLoc,NewFlags,ClientRoll,View);
}

/* OldServerMove()
- resending an (important) old move.  Process it if not already processed.
*/
unreliable server function OldServerMove
(
	float OldTimeStamp,
    byte OldAccelX,
    byte OldAccelY,
    byte OldAccelZ,
    byte OldMoveFlags
)
{
	local vector Accel;

	if ( AcknowledgedPawn != Pawn )
		return;

	if ( CurrentTimeStamp < OldTimeStamp - 0.001 )
	{
		// split out components of lost move (approx)
		Accel.X = OldAccelX;
		if ( Accel.X > 127 )
			Accel.X = -1 * (Accel.X - 128);
		Accel.Y = OldAccelY;
		if ( Accel.Y > 127 )
			Accel.Y = -1 * (Accel.Y - 128);
		Accel.Z = OldAccelZ;
		if ( Accel.Z > 127 )
			Accel.Z = -1 * (Accel.Z - 128);
		Accel *= 20;

		//`log("Recovered move from "$OldTimeStamp$" acceleration "$Accel$" from "$OldAccel);
		OldTimeStamp = FMin(OldTimeStamp, CurrentTimeStamp + MaxResponseTime);
		MoveAutonomous(OldTimeStamp - CurrentTimeStamp, OldMoveFlags, Accel, rot(0,0,0));
		CurrentTimeStamp = OldTimeStamp;
	}
}

/** @return time delta to use for the current ServerMove() */
function float GetServerMoveDeltaTime(float TimeStamp)
{
	local float DeltaTime;

	DeltaTime = FMin(MaxResponseTime, TimeStamp - CurrentTimeStamp);
	if( Pawn == None )
	{
		bWasSpeedHack = FALSE;
		ResetTimeMargin();
	}
	else if( !CheckSpeedHack(DeltaTime) )
	{
		if( !bWasSpeedHack )
		{
			if( WorldInfo.TimeSeconds - LastSpeedHackLog > 20 )
			{
				`log("Possible speed hack by "$PlayerReplicationInfo.PlayerName);
				LastSpeedHackLog = WorldInfo.TimeSeconds;
			}
			ClientMessage( "Speed Hack Detected!",'CriticalEvent' );
		}
		else
		{
			bWasSpeedHack = TRUE;
		}
		DeltaTime = 0;
		Pawn.Velocity = vect(0,0,0);
	}
	else
	{
		DeltaTime *= Pawn.CustomTimeDilation;
		bWasSpeedHack = FALSE;
	}

	return DeltaTime;
}

/** called after movement in ServerMove() to check for and handle any potential error between server and client position
 * by setting PendingAdjustment appropriately
 */
function ServerMoveHandleClientError(float TimeStamp, vector Accel, vector ClientLoc)
{
	local float ClientErr;
	local vector LocDiff;

	// Accumulate movement error.
	if (ClientLoc == vect(1,2,3))
	{
		return;		// first part of double servermove
	}
	else if (WorldInfo.TimeSeconds - LastUpdateTime < CLIENTADJUSTUPDATECOST / Player.CurrentNetSpeed)
	{
		// limit frequency of corrections if connection speed is limited
		return;
	}

	if (Pawn == None)
	{
		LocDiff = Location - ClientLoc;
	}
	else if (Pawn.bForceRMVelocity && Pawn.default.Mesh.RootMotionMode == RMM_Ignore)
	{
		// don't do corrections during root motion
		LocDiff = vect(0,0,0);
	}
	// force a correction periodically if the Pawn is not moving
	// this handles cases where the client incorrectly thinks movement is impossible
	// since without movement on one side or the other error can never be accumulated
	else if (Pawn.Physics != PHYS_None && WorldInfo.TimeSeconds - LastUpdateTime > 1.0 && IsZero(Accel))
	{
		LocDiff = vect(1000,1000,1000);
	}
	else
	{
		LocDiff = Pawn.Location - ClientLoc;

	}
	ClientErr = LocDiff Dot LocDiff;

	// If client has accumulated a noticeable positional error, correct him.
	if (ClientErr > MAXPOSITIONERRORSQUARED)
	{
		if (Pawn == None)
		{
			PendingAdjustment.newPhysics = Physics;
			PendingAdjustment.NewLoc = Location;
			PendingAdjustment.NewVel = Velocity;
		}
		else
		{
			PendingAdjustment.newPhysics = Pawn.Physics;
			PendingAdjustment.NewVel = Pawn.Velocity;
			PendingAdjustment.NewBase = Pawn.Base;
			if( (InterpActor(Pawn.Base) != None) || (Vehicle(Pawn.Base) != None) || DynamicBlockingVolume(Pawn.Base) != None )
			{
				PendingAdjustment.NewLoc = Pawn.Location - Pawn.Base.Location;
			}
			else
			{
				PendingAdjustment.NewLoc = Pawn.Location;
			}
			PendingAdjustment.NewFloor = Pawn.Floor;
		}

		//`log("Client Error at" @ TimeStamp @ "is" @ ClientErr @ "with acceleration" @ Accel @ "LocDiff" @ LocDiff @ "Physics" @ Pawn.Physics);

		LastUpdateTime = WorldInfo.TimeSeconds;
		PendingAdjustment.TimeStamp = TimeStamp;
		PendingAdjustment.bAckGoodMove = 0;
	}
	else
	{
		// acknowledge receipt of this successful servermove()
		PendingAdjustment.TimeStamp = TimeStamp;
		PendingAdjustment.bAckGoodMove = 1;
	}
}

/* ServerMove()
- replicated function sent by client to server - contains client movement and firing info.
*/
unreliable server function ServerMove
(
	float	TimeStamp,
	vector	InAccel,
	vector	ClientLoc,
	byte	MoveFlags,
	byte	ClientRoll,
`if(`__TW_)
	int		View,
	optional int FreeAimRot
`else
	int		View
`endif
)
{
	local float DeltaTime;
	local rotator DeltaRot, Rot, ViewRot;
	local vector Accel;
	local int maxPitch, ViewPitch, ViewYaw;

	// If this move is outdated, discard it.
	if( CurrentTimeStamp >= TimeStamp )
	{
		return;
	}

	if( AcknowledgedPawn != Pawn )
	{
		InAccel = vect(0,0,0);
		GivePawn(Pawn);
	}

	// View components
	ViewPitch	= (View & 65535);
	ViewYaw		= (View >> 16);

	// Acceleration was scaled by 10x for replication, to keep more precision since vectors are rounded for replication
	Accel = InAccel * 0.1;
	// Save move parameters.
	DeltaTime = GetServerMoveDeltaTime(TimeStamp);

	CurrentTimeStamp = TimeStamp;
	ServerTimeStamp = WorldInfo.TimeSeconds;
	ViewRot.Pitch = ViewPitch;
	ViewRot.Yaw = ViewYaw;
	ViewRot.Roll = 0;

	if( InAccel != vect(0,0,0) )
	{
		LastActiveTime = WorldInfo.TimeSeconds;
	}

	SetRotation(ViewRot);

	if( AcknowledgedPawn != Pawn )
	{
		return;
	}

	if( Pawn != None )
	{
		Rot.Roll	= 256 * ClientRoll;
		Rot.Yaw		= ViewYaw;
		if( (Pawn.Physics == PHYS_Swimming) || (Pawn.Physics == PHYS_Flying) )
		{
			maxPitch = 2;
		}
		else
		{
			maxPitch = 0;
		}

		if( (ViewPitch > maxPitch * Pawn.MaxPitchLimit) && (ViewPitch < 65536 - maxPitch * Pawn.MaxPitchLimit) )
		{
			if( ViewPitch < 32768 )
			{
				Rot.Pitch = maxPitch * Pawn.MaxPitchLimit;
			}
			else
			{
				Rot.Pitch = 65536 - maxPitch * Pawn.MaxPitchLimit;
			}
		}
		else
		{
			Rot.Pitch = ViewPitch;
		}
		DeltaRot = (Rotation - Rot);
		Pawn.FaceRotation(Rot, DeltaTime);
	}

	// Perform actual movement
	if( (WorldInfo.Pauser == None) && (DeltaTime > 0) )
	{
		MoveAutonomous(DeltaTime, MoveFlags, Accel, DeltaRot);
	}

	ServerMoveHandleClientError(TimeStamp, Accel, ClientLoc);
	//`log("Server moved stamp "$TimeStamp$" location "$Pawn.Location$" Acceleration "$Pawn.Acceleration$" Velocity "$Pawn.Velocity);
}


/* Called on server at end of tick when PendingAdjustment has been set.
Done this way to avoid ever sending more than one ClientAdjustment per server tick.
*/
event SendClientAdjustment()
{
	if( AcknowledgedPawn != Pawn )
	{
		PendingAdjustment.TimeStamp = 0;
		return;
	}

	if( PendingAdjustment.bAckGoodMove == 1 )
	{
		// just notify client this move was received
		ClientAckGoodMove(PendingAdjustment.TimeStamp);
	}
	else if( (Pawn == None) || (Pawn.Physics != PHYS_Spider) )
	{
		if( PendingAdjustment.NewVel == vect(0,0,0) )
		{
			if (GetStateName() == 'PlayerWalking' && Pawn != None && Pawn.Physics == PHYS_Walking)
			{
				VeryShortClientAdjustPosition
				(
					PendingAdjustment.TimeStamp,
					PendingAdjustment.NewLoc.X,
					PendingAdjustment.NewLoc.Y,
					PendingAdjustment.NewLoc.Z,
					PendingAdjustment.NewBase
				);
			}
			else
			{
				ShortClientAdjustPosition
				(
					PendingAdjustment.TimeStamp,
					GetStateName(),
					PendingAdjustment.newPhysics,
					PendingAdjustment.NewLoc.X,
					PendingAdjustment.NewLoc.Y,
					PendingAdjustment.NewLoc.Z,
					PendingAdjustment.NewBase
				);
			}
		}
		else
		{
			ClientAdjustPosition
			(
				PendingAdjustment.TimeStamp,
				GetStateName(),
				PendingAdjustment.newPhysics,
				PendingAdjustment.NewLoc.X,
				PendingAdjustment.NewLoc.Y,
				PendingAdjustment.NewLoc.Z,
				PendingAdjustment.NewVel.X,
				PendingAdjustment.NewVel.Y,
				PendingAdjustment.NewVel.Z,
				PendingAdjustment.NewBase
			);
		}
    }
	else
	{
		LongClientAdjustPosition
		(
			PendingAdjustment.TimeStamp,
			GetStateName(),
			PendingAdjustment.newPhysics,
			PendingAdjustment.NewLoc.X,
			PendingAdjustment.NewLoc.Y,
			PendingAdjustment.NewLoc.Z,
			PendingAdjustment.NewVel.X,
			PendingAdjustment.NewVel.Y,
			PendingAdjustment.NewVel.Z,
			PendingAdjustment.NewBase,
			PendingAdjustment.NewFloor.X,
			PendingAdjustment.NewFloor.Y,
			PendingAdjustment.NewFloor.Z
		);
	}

	PendingAdjustment.TimeStamp = 0;
	PendingAdjustment.bAckGoodMove = 0;
}

// Only executed on server
unreliable server function ServerDrive(float InForward, float InStrafe, float aUp, bool InJump, int View)
{
	local rotator ViewRotation;

	ViewRotation.Pitch = (View & 65535);
	ViewRotation.Yaw = (View >> 16);
	ViewRotation.Roll = 0;
	SetRotation(ViewRotation);

	ProcessDrive(InForward, InStrafe, aUp, InJump);
}

function ProcessDrive(float InForward, float InStrafe, float InUp, bool InJump)
{
	ClientGotoState(GetStateName(), 'Begin');
}

function ProcessMove( float DeltaTime, vector newAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
{
    if( (Pawn != None) && (Pawn.Acceleration != newAccel) )
	{
		Pawn.Acceleration = newAccel;
	}
}

function MoveAutonomous
(
	float DeltaTime,
	byte CompressedFlags,
	vector newAccel,
	rotator DeltaRot
)
{
	local EDoubleClickDir DoubleClickMove;

	if ( (Pawn != None) && Pawn.bHardAttach )
		return;

	DoubleClickMove = SavedMoveClass.static.SetFlags(CompressedFlags, self);
	HandleWalking();
	ProcessMove(DeltaTime, newAccel, DoubleClickMove, DeltaRot);
	if ( Pawn != None )
	{
		Pawn.AutonomousPhysics(DeltaTime);
	}
	else
	{
		AutonomousPhysics(DeltaTime);
	}
    bDoubleJump = false;
	//`log("Role "$Role$" moveauto time "$100 * DeltaTime$" ("$WorldInfo.TimeDilation$")");
}

/* VeryShortClientAdjustPosition
bandwidth saving version, when velocity is zeroed, and pawn is walking
*/
unreliable client function VeryShortClientAdjustPosition
(
	float TimeStamp,
	float NewLocX,
	float NewLocY,
	float NewLocZ,
	Actor NewBase
)
{
	local vector Floor;

	if( Pawn != None )
	{
		Floor = Pawn.Floor;
	}
	LongClientAdjustPosition(TimeStamp, 'PlayerWalking', PHYS_Walking, NewLocX, NewLocY, NewLocZ, 0, 0, 0, NewBase, Floor.X, Floor.Y, Floor.Z);
}

/* ShortClientAdjustPosition
bandwidth saving version, when velocity is zeroed
*/
unreliable client function ShortClientAdjustPosition
(
	float TimeStamp,
	name newState,
	EPhysics newPhysics,
	float NewLocX,
	float NewLocY,
	float NewLocZ,
	Actor NewBase
)
{
	local vector Floor;

	if( Pawn != None )
	{
		Floor = Pawn.Floor;
	}

	LongClientAdjustPosition(TimeStamp, newState, newPhysics, NewLocX, NewLocY, NewLocZ, 0, 0, 0, NewBase, Floor.X, Floor.Y, Floor.Z);
}

reliable client function ClientCapBandwidth(int Cap)
{
	ClientCap = Cap;
	if( (Player != None) && (Player.CurrentNetSpeed > Cap) )
	{
		SetNetSpeed(Cap);
	}
}

unreliable client function ClientAckGoodMove(float TimeStamp)
{
	UpdatePing(TimeStamp);
	CurrentTimeStamp = TimeStamp;
	ClearAckedMoves();
}

/* ClientAdjustPosition
- pass newloc and newvel in components so they don't get rounded
*/
unreliable client function ClientAdjustPosition
(
	float TimeStamp,
	name newState,
	EPhysics newPhysics,
	float NewLocX,
	float NewLocY,
	float NewLocZ,
	float NewVelX,
	float NewVelY,
	float NewVelZ,
	Actor NewBase
)
{
	local vector Floor;

	if ( Pawn != None )
	{
		Floor = Pawn.Floor;
	}

	LongClientAdjustPosition(TimeStamp,newState,newPhysics,NewLocX,NewLocY,NewLocZ,NewVelX,NewVelY,NewVelZ,NewBase,Floor.X,Floor.Y,Floor.Z);
}


/* epic ===============================================
* ::UpdatePing
update average ping based on newly received round trip timestamp.
Occasionally send ping updates to the server, and also adjust netspeed if connection appears to be saturated
*/
final function UpdatePing(float TimeStamp)
{
	if ( PlayerReplicationInfo != None )
	{
		PlayerReplicationInfo.UpdatePing(TimeStamp);
		if ( WorldInfo.TimeSeconds - LastPingUpdate > 4 )
		{
			LastPingUpdate = WorldInfo.TimeSeconds;
			ServerUpdatePing(1000 * PlayerReplicationInfo.ExactPing);
		}
	}
}

/** @return whether we should skip the position updating in LongClientAdjustPosition() due to current or upcoming root motion
 * (does not affect move acknowledgement or state transitions)
 */
function bool SkipPositionUpdateForRM()
{
	local SavedMove CurrentMove;

	// avoiding correcting for temporary root motion moves; wait until the animation completes
	if (Pawn != None && Pawn.default.Mesh.RootMotionMode == RMM_Ignore)
	{
		if ( Pawn.Physics != PHYS_Falling && Pawn.Mesh != None &&
			Pawn.Mesh.RootMotionMode != RMM_Ignore && !Pawn.bRootMotionFromInterpCurve )
		{
			`log("- skipping position update for root motion",,'PlayerMove');
			return true;
		}

		// don't correct if have upcoming root motion
		CurrentMove = SavedMoves;
		while( CurrentMove != None )
		{
			if ( CurrentMove.bForceRMVelocity )
			{
				`log("- skipping position update for upcoming root motion",,'PlayerMove');
				return true;
			}
			CurrentMove = CurrentMove.NextMove;
		}
	}

	return false;
}

/* LongClientAdjustPosition
long version, when care about pawn's floor normal
*/
unreliable client function LongClientAdjustPosition
(
	float TimeStamp,
	name newState,
	EPhysics newPhysics,
	float NewLocX,
	float NewLocY,
	float NewLocZ,
	float NewVelX,
	float NewVelY,
	float NewVelZ,
	Actor NewBase,
	float NewFloorX,
	float NewFloorY,
	float NewFloorZ
)
{
	local vector NewLocation, NewVelocity, NewFloor;
	local Actor MoveActor;
	local SavedMove CurrentMove;
	local Actor TheViewTarget;
`if(`notdefined(FINAL_RELEASE))
	local Vector OldLoc;

	OldLoc = (Pawn != None) ? Pawn.Location : Location;
`endif
	UpdatePing(TimeStamp);
	if( Pawn != None )
	{
		if( Pawn.bTearOff )
		{
			Pawn = None;
			if( !GamePlayEndedState() && !IsInState('Dead') )
			{
				GotoState('Dead');
			}
			return;
		}

		MoveActor = Pawn;
		TheViewTarget = GetViewTarget();

		if( (TheViewTarget != Pawn)
			&& ((TheViewTarget == self) || ((Pawn(TheViewTarget) != None) && (Pawn(TheViewTarget).Health <= 0))) )
		{
			ResetCameraMode();
			SetViewTarget(Pawn);
		}
	}
	else
	{
		MoveActor = self;
	   	if( GetStateName() != newstate )
		{
			`log("- state change:"@GetStateName()@"->"@newstate,,'PlayerMove');
		    if( NewState == 'RoundEnded' )
			{
			    GotoState(NewState);
			}
			else if( IsInState('Dead') )
			{
		    	if( (NewState != 'PlayerWalking') && (NewState != 'PlayerSwimming') )
		        {
				    GotoState(NewState);
		        }
		        return;
			}
			else if( NewState == 'Dead' )
			{
				GotoState(NewState);
			}
		}
	}

	if( CurrentTimeStamp >= TimeStamp )
	{
		return;
	}
	CurrentTimeStamp = TimeStamp;

	NewLocation.X = NewLocX;
	NewLocation.Y = NewLocY;
	NewLocation.Z = NewLocZ;
	NewVelocity.X = NewVelX;
	NewVelocity.Y = NewVelY;
	NewVelocity.Z = NewVelZ;

	// skip update if no error
	CurrentMove = SavedMoves;

	// note that acked moves are cleared here, instead of calling ClearAckedMoves()
	while( CurrentMove != None )
	{
		if( CurrentMove.TimeStamp <= CurrentTimeStamp )
		{
			SavedMoves = CurrentMove.NextMove;
			CurrentMove.NextMove = FreeMoves;
			FreeMoves = CurrentMove;
			if( CurrentMove.TimeStamp == CurrentTimeStamp )
			{
				LastAckedAccel = CurrentMove.Acceleration;
				FreeMoves.Clear();
				if( ((InterpActor(NewBase) != None) || (Vehicle(NewBase) != None) || DynamicBlockingVolume(NewBase) != None )
					&& (NewBase == CurrentMove.EndBase) )
				{
					if ( (GetStateName() == NewState)
						&& IsInState('PlayerWalking')
						&& ((MoveActor.Physics == PHYS_Walking) || (MoveActor.Physics == PHYS_Falling)) )
					{
						if ( VSizeSq(CurrentMove.SavedRelativeLocation - NewLocation) < MAXPOSITIONERRORSQUARED )
						{
							CurrentMove = None;
							return;
						}
						else if ( (Vehicle(NewBase) != None)
								&& (VSizeSq(Velocity) < MAXNEARZEROVELOCITYSQUARED) && (VSizeSq(NewVelocity) < MAXNEARZEROVELOCITYSQUARED)
								&& (VSizeSq(CurrentMove.SavedRelativeLocation - NewLocation) < MAXVEHICLEPOSITIONERRORSQUARED) )
						{
							CurrentMove = None;
							return;
						}
					}
				}
				else if ( (VSizeSq(CurrentMove.SavedLocation - NewLocation) < MAXPOSITIONERRORSQUARED)
					&& (VSizeSq(CurrentMove.SavedVelocity - NewVelocity) < MAXNEARZEROVELOCITYSQUARED)
					&& (GetStateName() == NewState)
					&& IsInState('PlayerWalking')
					&& ((MoveActor.Physics == PHYS_Walking) || (MoveActor.Physics == PHYS_Falling)) )
				{
					CurrentMove = None;
					return;
				}
				CurrentMove = None;
			}
			else
			{
				FreeMoves.Clear();
				CurrentMove = SavedMoves;
			}
		}
		else
		{
			CurrentMove = None;
		}
	}

	if (MoveActor.bHardAttach)
	{
		if (MoveActor.Base == None || MoveActor.Base.bWorldGeometry)
		{
			if (NewBase != None)
			{
				MoveActor.SetLocation(NewLocation);
				MoveActor.SetPhysics(NewPhysics);
				MoveActor.SetBase(NewBase);
			}
			if( MoveActor.Base == None )
			{
				MoveActor.SetHardAttach(false);
			}
			else
			{
				return;
			}
		}
		else
		{
			return;
		}
	}

	NewFloor.X = NewFloorX;
	NewFloor.Y = NewFloorY;
	NewFloor.Z = NewFloorZ;

	//@debug - track down the errors
	`log("- base mismatch:"@MoveActor.Base@NewBase,MoveActor.Base != NewBase,'PlayerMove');
	`log("- location mismatch, delta:"@VSize(MoveActor.Location - NewLocation),MoveActor.Location != NewLocation,'PlayerMove');
	`log("- velocity mismatch, delta:"@VSize(NewVelocity - MoveActor.Velocity)@"client:"@VSize(MoveActor.Velocity)@"server:"@VSize(NewVelocity),MoveActor.Velocity != NewVelocity,'PlayerMove');

	if (SkipPositionUpdateForRM())
	{
		return;
	}

	if( (InterpActor(NewBase) != None) || (Vehicle(NewBase) != None) || DynamicBlockingVolume(NewBase) != None )
	{
		NewLocation += NewBase.Location;
	}

// 	`log("Client "$Role$" adjust "$self$" stamp "$TimeStamp$" location "$MoveActor.Location);

	MoveActor.bCanTeleport = FALSE;
	if ( !MoveActor.SetLocation(NewLocation) && (Pawn(MoveActor) != None)
		&& (Pawn(MoveActor).CylinderComponent.CollisionHeight > Pawn(MoveActor).CrouchHeight)
		&& !Pawn(MoveActor).bIsCrouched
		&& (newPhysics == PHYS_Walking)
		&& (MoveActor.Physics != PHYS_RigidBody) )
	{
		MoveActor.SetPhysics(newPhysics);

		if( !MoveActor.SetLocation(NewLocation + vect(0,0,1)*Pawn(MoveActor).MaxStepHeight) )
		{
`if(`__TW_)
			// Disabled ForceCrouch.  This is not replicated, not particularly useful, and a little obnoxious.
`else
			Pawn(MoveActor).ForceCrouch();
			MoveActor.SetLocation(NewLocation);
`endif
		}
		else
		{
			MoveActor.MoveSmooth(vect(0,0,-1)*Pawn(MoveActor).MaxStepHeight);
		}
	}
	MoveActor.bCanTeleport = TRUE;

	// Hack. Don't let network change physics mode of rigid body stuff on the client.
	if( MoveActor.Physics != PHYS_RigidBody &&
		newPhysics != PHYS_RigidBody )
	{
		MoveActor.SetPhysics(newPhysics);
	}

	if( MoveActor != self )
	{
		MoveActor.SetBase(NewBase, NewFloor);
	}

	MoveActor.Velocity = NewVelocity;
	UpdateStateFromAdjustment(NewState);
	bUpdatePosition = TRUE;

`if(`notdefined(FINAL_RELEASE))
	if( bDebugClientAdjustPosition )
	{
		DrawDebugBox( OldLoc, vect(2,2,2), 0, 120, 0, TRUE );
		DrawDebugBox( Pawn.Location, vect(3,3,3), 255, 255, 255, TRUE );
		DrawDebugLine( Pawn.Location, OldLoc, 255, 255, 255, TRUE );
		`log( `location@"!!!!!!!!!!!!!!"@SavedMoves@`showvar(Pawn.Rotation)@`showvar(WorldInfo.TimeSeconds));
	}
`endif
}


/**
Called by LongClientAdjustPosition()
@param NewState is the state recommended by the server
*/
function UpdateStateFromAdjustment(name NewState)
{
	if( GetStateName() != newstate )
	{
		GotoState(newstate);
	}
}

unreliable server function ServerUpdatePing(int NewPing)
{
	PlayerReplicationInfo.Ping = Min(0.25 * NewPing, 250);
}

/* ClearAckedMoves()
clear out acknowledged/missed sent moves
*/
function ClearAckedMoves()
{
	local SavedMove CurrentMove;

	CurrentMove = SavedMoves;
	while ( CurrentMove != None )
	{
		if ( CurrentMove.TimeStamp <= CurrentTimeStamp )
		{
			if ( CurrentMove.TimeStamp == CurrentTimeStamp )
				LastAckedAccel = CurrentMove.Acceleration;
			SavedMoves = CurrentMove.NextMove;
			CurrentMove.NextMove = FreeMoves;
			FreeMoves = CurrentMove;
			FreeMoves.Clear();
			CurrentMove = SavedMoves;
		}
		else
			break;
	}
}

/**
 * Called from PlayerTick after receiving ClientAdjustPosition call from server (and setting the bUpdating flag)
 * Client has already had position information corrected
 *
 * This function plays through previously saved moves that haven't been acknowledged by the server, predicting where the client
 * should be after the server correction
 */
function ClientUpdatePosition()
{
	local SavedMove CurrentMove;
	local int		realbRun, realbDuck;
	local bool		bRealJump;
	local bool		bRealPreciseDestination;
	local bool		bRealForceMaxAccel;
	local bool		bRealRootMotionFromInterpCurve;
	local ERootMotionMode RealRootMotionMode;
`if(`notdefined(FINAL_RELEASE))
	local Vector	OldLoc;
`endif

	bUpdatePosition = FALSE;

	// Dont do any network position updates on things running PHYS_RigidBody
	if( Pawn != None && Pawn.Physics == PHYS_RigidBody )
	{
		return;
	}

`if(`notdefined(FINAL_RELEASE))
	if( bDebugClientAdjustPosition )
	{
		`log( `location@"!!!!!!!!!!!!!!"@SavedMoves@`showvar(Pawn.Rotation)@`showvar(WorldInfo.TimeSeconds));
	}
`endif

	realbRun  = bRun;
	realbDuck = bDuck;
	bRealJump = bPressedJump;
	bUpdating = TRUE;
	bRealPreciseDestination = bPreciseDestination;
	if( Pawn != None )
	{
		bRealForceMaxAccel = Pawn.bForceMaxAccel;
		bRealRootMotionFromInterpCurve = Pawn.bRootMotionFromInterpCurve;
		RealRootMotionMode = Pawn.Mesh.RootMotionMode;
	}

	ClearAckedMoves();
	CurrentMove = SavedMoves;
	while( CurrentMove != None )
	{
		if( (PendingMove == CurrentMove) && (Pawn != None) )
		{
			PendingMove.SetInitialPosition(Pawn);
		}

`if(`notdefined(FINAL_RELEASE))
	if( bDebugClientAdjustPosition )
	{
		`log( CurrentMove.GetDebugString() );
		`log( "Old"@Pawn.Location@Pawn.bRootMotionFromInterpCurve@Pawn.RootMotionInterpCurrentTime );
		OldLoc = Pawn.Location;
	}
`endif

		CurrentMove.PrepMoveFor( Pawn );
		MoveAutonomous(CurrentMove.Delta, CurrentMove.CompressedFlags(), CurrentMove.Acceleration, rot(0,0,0));
		CurrentMove.ResetMoveFor( Pawn );

`if(`notdefined(FINAL_RELEASE))
	if( bDebugClientAdjustPosition )
	{
		`log( "New"@Pawn.Location@Pawn.bRootMotionFromInterpCurve@Pawn.RootMotionInterpCurrentTime );
		DrawDebugBox( OldLoc, vect(4,4,4), 120, 0, 0, TRUE );
		DrawDebugBox( Pawn.Location, vect(5,5,5), 0, 0, 120, TRUE );
		DrawDebugLine( OldLoc + vect(0,0,2), Pawn.Location + vect(0,0,2), 0, 120, 0, TRUE );
	}
`endif

		CurrentMove = CurrentMove.NextMove;
	}

	bUpdating = FALSE;
	bDuck = realbDuck;
	bRun = realbRun;
	bPressedJump = bRealJump;
	bPreciseDestination = bRealPreciseDestination;
	if( Pawn != None )
	{
		Pawn.bForceMaxAccel = bRealForceMaxAccel;
		Pawn.bRootMotionFromInterpCurve = bRealRootMotionFromInterpCurve;
		Pawn.Mesh.RootMotionMode = RealRootMotionMode;
	}
}

final function SavedMove GetFreeMove()
{
	local SavedMove s, first;
	local int i;

	if ( FreeMoves == None )
	{
		// don't allow more than 100 saved moves
		For ( s=SavedMoves; s!=None; s=s.NextMove )
		{
			i++;
			if ( i > 100 )
			{
				first = SavedMoves;
				SavedMoves = SavedMoves.NextMove;
				first.Clear();
				first.NextMove = None;
				// clear out all the moves
				While ( SavedMoves != None )
				{
					s = SavedMoves;
					SavedMoves = SavedMoves.NextMove;
					s.Clear();
					s.NextMove = FreeMoves;
					FreeMoves = s;
				}
				PendingMove = None;
				return first;
			}
		}
		return new(self) SavedMoveClass;
	}
	else
	{
		s = FreeMoves;
		FreeMoves = FreeMoves.NextMove;
		s.NextMove = None;
		return s;
	}
}

function int CompressAccel(int C)
{
	if ( C >= 0 )
		C = Min(C, 127);
	else
		C = Min(abs(C), 127) + 128;
	return C;
}

/*
========================================================================
Here's how player movement prediction, replication and correction works in network games:

Every tick, the PlayerTick() function is called.  It calls the PlayerMove() function (which is implemented
in various states).  PlayerMove() figures out the acceleration and rotation, and then calls ProcessMove()
(for single player or listen servers), or ReplicateMove() (if its a network client).

ReplicateMove() saves the move (in the PendingMove list), calls ProcessMove(), and then replicates the move
to the server by calling the replicated function ServerMove() - passing the movement parameters, the client's
resultant position, and a timestamp.

ServerMove() is executed on the server.  It decodes the movement parameters and causes the appropriate movement
to occur.  It then looks at the resulting position and if enough time has passed since the last response, or the
position error is significant enough, the server calls ClientAdjustPosition(), a replicated function.

ClientAdjustPosition() is executed on the client.  The client sets its position to the servers version of position,
and sets the bUpdatePosition flag to true.

When PlayerTick() is called on the client again, if bUpdatePosition is true, the client will call
ClientUpdatePosition() before calling PlayerMove().  ClientUpdatePosition() replays all the moves in the pending
move list which occured after the timestamp of the move the server was adjusting.
*/

//
// Replicate this client's desired movement to the server.
//
function ReplicateMove
(
	float DeltaTime,
	vector NewAccel,
	eDoubleClickDir DoubleClickMove,
	rotator DeltaRot
)
{
	local SavedMove NewMove, OldMove, AlmostLastMove, LastMove;
	local byte ClientRoll;
	local float NetMoveDelta;

	// do nothing if we are no longer connected
	if (Player == None)
	{
		return;
	}

	MaxResponseTime = Default.MaxResponseTime * WorldInfo.TimeDilation;
	DeltaTime = ((Pawn != None) ? Pawn.CustomTimeDilation : CustomTimeDilation) * FMin(DeltaTime, MaxResponseTime);

	// find the most recent move (LastMove), and the oldest (unacknowledged) important move (OldMove)
	// a SavedMove is interesting if it differs significantly from the last acknowledged move
	if ( SavedMoves != None )
	{
		LastMove = SavedMoves;
		AlmostLastMove = LastMove;
		OldMove = None;
		while ( LastMove.NextMove != None )
		{
			// find first important unacknowledged move
			if ( (OldMove == None) && (Pawn != None) && LastMove.IsImportantMove(LastAckedAccel) )
			{
				OldMove = LastMove;
			}
			AlmostLastMove = LastMove;
			LastMove = LastMove.NextMove;
		}
	}

	// Get a SavedMove object to store the movement in.
	NewMove = GetFreeMove();
	if ( NewMove == None )
	{
		return;
	}
	NewMove.SetMoveFor(self, DeltaTime, NewAccel, DoubleClickMove);

	// Simulate the movement locally.
	bDoubleJump = false;
	ProcessMove(NewMove.Delta, NewMove.Acceleration, NewMove.DoubleClickMove, DeltaRot);

	// see if the two moves could be combined
	if ( (PendingMove != None) && PendingMove.CanCombineWith(NewMove, Pawn, MaxResponseTime) )
	{
		// to combine move, first revert pawn position to PendingMove start position, before playing combined move on client
		Pawn.SetLocation(PendingMove.GetStartLocation());
		Pawn.Velocity = PendingMove.StartVelocity;
		if( PendingMove.StartBase != Pawn.Base )
		{
			Pawn.SetBase(PendingMove.StartBase);
		}
		Pawn.Floor = PendingMove.StartFloor;
		NewMove.Delta += PendingMove.Delta;
		NewMove.SetInitialPosition(Pawn);

		// remove pending move from move list
		if ( LastMove == PendingMove )
		{
			if ( SavedMoves == PendingMove )
			{
				SavedMoves.NextMove = FreeMoves;
				FreeMoves = SavedMoves;
				SavedMoves = None;
			}
			else
			{
				PendingMove.NextMove = FreeMoves;
				FreeMoves = PendingMove;
				if ( AlmostLastMove != None )
				{
					AlmostLastMove.NextMove = None;
					LastMove = AlmostLastMove;
				}
			}
			FreeMoves.Clear();
		}
		PendingMove = None;
	}

	if( Pawn != None )
	{
		Pawn.AutonomousPhysics(NewMove.Delta);
	}
	else
	{
		AutonomousPhysics(DeltaTime);
	}
	NewMove.PostUpdate(self);

	if( SavedMoves == None )
	{
		SavedMoves = NewMove;
	}
	else
	{
		LastMove.NextMove = NewMove;
	}

	if ( PendingMove == None )
	{
		// Decide whether to hold off on move
		// send moves more frequently in small games where server isn't likely to be saturated
		if( (Player.CurrentNetSpeed > 10000) && (WorldInfo.GRI != None) && (WorldInfo.GRI.PRIArray.Length <= 10) )
		{
			NetMoveDelta = 0.011;
		}
		else
		{
			NetMoveDelta = FMax(0.0222,2 * WorldInfo.MoveRepSize/Player.CurrentNetSpeed);
		}

		if( (WorldInfo.TimeSeconds - ClientUpdateTime) * WorldInfo.TimeDilation < NetMoveDelta )
		{
			PendingMove = NewMove;
			return;
		}
	}

	ClientUpdateTime = WorldInfo.TimeSeconds;

	// Send to the server
	ClientRoll = (Rotation.Roll >> 8) & 255;

	CallServerMove( NewMove,
			((Pawn == None) ? Location : Pawn.Location),
			ClientRoll,
			((Rotation.Yaw & 65535) << 16) + (Rotation.Pitch & 65535),
			OldMove );

	PendingMove = None;
}

/* CallServerMove()
Call the appropriate replicated servermove() function to send a client player move to the server
*/
function CallServerMove
(
	SavedMove NewMove,
    vector ClientLoc,
    byte ClientRoll,
    int View,
    SavedMove OldMove
)
{
	local vector BuildAccel;
	local byte OldAccelX, OldAccelY, OldAccelZ;

	// compress old move if it exists
	if ( OldMove != None )
	{
		// old move important to replicate redundantly
		BuildAccel = 0.05 * OldMove.Acceleration + vect(0.5, 0.5, 0.5);
		OldAccelX = CompressAccel(BuildAccel.X);
		OldAccelY = CompressAccel(BuildAccel.Y);
		OldAccelZ = CompressAccel(BuildAccel.Z);
		OldServerMove(OldMove.TimeStamp,OldAccelX, OldAccelY, OldAccelZ, OldMove.CompressedFlags());
	}

	if ( PendingMove != None )
	{
		// send two moves simultaneously
		DualServerMove
		(
			PendingMove.TimeStamp,
			PendingMove.Acceleration * 10,
			PendingMove.CompressedFlags(),
			((PendingMove.Rotation.Yaw & 65535) << 16) + (PendingMove.Rotation.Pitch & 65535),
			NewMove.TimeStamp,
			NewMove.Acceleration * 10,
			ClientLoc,
			NewMove.CompressedFlags(),
			ClientRoll,
			View
		);
	}
	else
	{
		ServerMove
		(
	    NewMove.TimeStamp,
	    NewMove.Acceleration * 10,
	    ClientLoc,
		NewMove.CompressedFlags(),
		ClientRoll,
	    View
		);
	}

	if (PlayerCamera != None && PlayerCamera.bUseClientSideCameraUpdates)
	{
		PlayerCamera.bShouldSendClientSideCameraUpdate = TRUE;
	}
}

/** If PlayerCamera.bUseClientSideCameraUpdates is set, client will replicate camera positions to the server. */
// @TODO - combine pitch/yaw into one int, maybe also send location compressed
unreliable server function ServerUpdateCamera(vector CamLoc, int CamPitchAndYaw)
{
	local TPOV		NewPOV;

	NewPOV.Location = CamLoc;

	NewPOV.Rotation.Yaw = (CamPitchAndYaw >> 16) & 65535;
	NewPOV.Rotation.Pitch = CamPitchAndYaw & 65535;

	if ( PlayerCamera.bDebugClientSideCamera )
	{
		// show differences (on server) between local and replicated camera
	  DrawDebugSphere( PlayerCamera.CameraCache.POV.Location, 10, 10, 0, 255, 0 );
	  DrawDebugSphere(NewPOV.Location, 10, 10, 255, 255, 0 );
	  DrawDebugLine(PlayerCamera.CameraCache.POV.Location, PlayerCamera.CameraCache.POV.Location + 100*vector(PlayerCamera.CameraCache.POV.Rotation), 0, 255, 0);
	  DrawDebugLine(NewPOV.Location, NewPOV.Location + 100*vector(NewPOV.Rotation), 255, 255, 0);
	}
	else
	{
		PlayerCamera.FillCameraCache(NewPOV);
	}
}

/* HandleWalking:
	Called by PlayerController and PlayerInput to set bIsWalking flag, affecting Pawn's velocity */
function HandleWalking()
{
	if ( Pawn != None )
		Pawn.SetWalking( bRun != 0 );
}

reliable server function ServerRestartGame()
{
}

// Send a voice message of a certain type to a certain player.
exec function Speech( name Type, int Index, string Callsign )
{
	ServerSpeech(Type,Index,Callsign);
}

reliable server function ServerSpeech( name Type, int Index, string Callsign );

exec function RestartLevel()
{
	if( WorldInfo.NetMode==NM_Standalone )
	{
		ClientTravel( "?restart", TRAVEL_Relative );
	}
}

exec function LocalTravel( string URL )
{
	if( WorldInfo.NetMode==NM_Standalone )
		ClientTravel( URL, TRAVEL_Relative );
}

/**
 * Pause force-feedback for all players.
 *
 * @param	bShouldPauseRumble	indicates whether force-feedback should be paused or unpaused.
 */
function PauseRumbleForAllPlayers( optional bool bShouldPauseRumble=true )
{
	local PlayerController PC;

	foreach WorldInfo.AllControllers(class'PlayerController', PC)
	{
		PC.ClientPauseRumble(bShouldPauseRumble);
	}
}

/**
 * Pause force-feedback for this player.
 *
 * @param bShouldPauseRumble indicates whether force-feedback should be paused or unpaused.
 */
reliable client function ClientPauseRumble(bool bShouldPauseRumble)
{
	if (ForceFeedbackManager != None)
	{
		ForceFeedbackManager.PauseWaveform(bShouldPauseRumble);
	}
}

/** Callback the server uses to determine if the unpause can happen */
delegate bool CanUnpause()
{
	return WorldInfo.Pauser == PlayerReplicationInfo;
}

/**  Try to pause game; returns success indicator. */
function bool SetPause( bool bPause, optional delegate<CanUnpause> CanUnpauseDelegate=CanUnpause)
{
	local bool bResult;

	if (WorldInfo.NetMode != NM_Client)
	{
		if (bPause)
		{
			bFire = 0;
			// Pause gamepad rumbling too if needed
			bResult = WorldInfo.Game.SetPause(self,CanUnpauseDelegate);
			if (bResult)
			{
				PauseRumbleForAllPlayers();
			}
		}
		else
		{
			WorldInfo.Game.ClearPause();
			// If the unpause is complete, let rumble occur
			if ( WorldInfo.Pauser == None )
			{
				// If we did a gameplay frame pause clear it out now
				WorldInfo.bGameplayFramePause = false;

				PauseRumbleForAllPlayers(false);
			}
		}
	}
	return bResult;
}


/**
 * Returns whether the game is currently paused.
 */
final simulated function bool IsPaused()
{
	return WorldInfo.Pauser != None;
}

/* Pause()
Command to try to pause the game.
*/
exec function Pause()
{
	ServerPause();
}

reliable server function ServerPause()
{
	// Pause if not already
	if( !IsPaused() )
		SetPause(true);
	else
		SetPause(false);
}

/**
 * Toggles the game's paused state if it does not match the desired pause state.
 *
 * @param	bDesiredPauseState	TRUE indicates that the game should be paused.
 */
event ConditionalPause( bool bDesiredPauseState )
{
	if ( bDesiredPauseState != IsPaused() )
	{
		SetPause(bDesiredPauseState);
	}
}

reliable server function ServerUTrace()
{
	if (WorldInfo.NetMode != NM_Standalone
	&& (PlayerReplicationInfo == None || !PlayerReplicationInfo.bAdmin) )
	{
		return;
	}

	UTrace();
}


exec function UTrace()
{
	// disable the log, or we'll grind the game to a halt
	ConsoleCommand("hidelog");
	if ( Role != ROLE_Authority )
	{
		ServerUTrace();
	}

	SetUTracing( !IsUTracing() );
	`log("UTracing changed to "$ IsUTracing() $ " at "$ WorldInfo.TimeSeconds,,'UTrace');
}

// ------------------------------------------------------------------------
// Weapon changing functions

/* ThrowWeapon()
Throw out current weapon, and switch to a new weapon
*/
exec function ThrowWeapon()
{
    if ( (Pawn == None) || (Pawn.Weapon == None) )
		return;

    ServerThrowWeapon();
}

reliable server function ServerThrowWeapon()
{
    if ( Pawn.CanThrowWeapon() )
    {
		Pawn.ThrowActiveWeapon();
    }
}

/* PrevWeapon()
- switch to previous inventory group weapon
*/
exec function PrevWeapon()
{
	if ( WorldInfo.Pauser!=None )
		return;

	if ( Pawn.Weapon == None )
	{
		SwitchToBestWeapon();
		return;
	}

	if ( Pawn.InvManager != None )
		Pawn.InvManager.PrevWeapon();
}

/* NextWeapon()
- switch to next inventory group weapon
*/
exec function NextWeapon()
{
	if ( WorldInfo.Pauser!=None )
		return;

	if ( Pawn.Weapon == None )
	{
		SwitchToBestWeapon();
		return;
	}

	if ( Pawn.InvManager != None )
		Pawn.InvManager.NextWeapon();
}

// The player wants to fire.
exec function StartFire( optional byte FireModeNum )
{
	if ( WorldInfo.Pauser == PlayerReplicationInfo )
	{
		SetPause( false );
		return;
	}

	if ( Pawn != None && !bCinematicMode && !WorldInfo.bPlayersOnly )
	{
		Pawn.StartFire( FireModeNum );
	}
}

exec function StopFire( optional byte FireModeNum )
{
	if ( Pawn != None )
	{
		Pawn.StopFire( FireModeNum );
	}
}

// The player wants to alternate-fire.
exec function StartAltFire( optional Byte FireModeNum )
{
	StartFire( 1 );
}

exec function StopAltFire( optional byte FireModeNum )
{
	StopFire( 1 );
}

/**
 * Looks at all nearby triggers, looking for any that can be
 * interacted with.
 *
 * @param		interactDistanceToCheck - distance to search for nearby triggers
 *
 * @param		crosshairDist - distance from the crosshair that
 * 				triggers must be, else they will be filtered out
 *
 * @param		minDot - minimum dot product between trigger and the
 * 				camera orientation needed to make the list
 *
 * @param		bUsuableOnly - if true, event must return true from
 * 				SequenceEvent::CheckActivate()
 *
 * @param		out_useList - the list of triggers found to be
 * 				usuable
 */
function GetTriggerUseList(float interactDistanceToCheck, float crosshairDist, float minDot, bool bUsuableOnly, out array<Trigger> out_useList)
{
	local int Idx;
	local vector cameraLoc;
	local rotator cameraRot;
	local Trigger checkTrigger;
	local SeqEvent_Used	UseSeq;

	if (Pawn != None)
	{
		// grab camera location/rotation for checking crosshairDist
		GetPlayerViewPoint(cameraLoc, cameraRot);

		// This doesn't work how it should.  It really needs to query ALL of the triggers and get their
		// InteractDistance and then compare those against the pawn's location and then do the various checks

		// search of nearby actors that have use events
		foreach Pawn.CollidingActors(class'Trigger',checkTrigger,interactDistanceToCheck)
		{
			for (Idx = 0; Idx < checkTrigger.GeneratedEvents.Length; Idx++)
			{
				UseSeq = SeqEvent_Used(checkTrigger.GeneratedEvents[Idx]);

				if( ( UseSeq != None )
					// if bUsuableOnly is true then we must get true back from CheckActivate (which tests various validity checks on the player and on the trigger's trigger count and retrigger conditions etc)
					&& ( !bUsuableOnly || ( checkTrigger.GeneratedEvents[Idx].CheckActivate(checkTrigger,Pawn,true)) )
					// check to see if we are looking at the object
					&& ( Normal(checkTrigger.Location-cameraLoc) dot vector(cameraRot) >= minDot )

					// if this is an aimToInteract then check to see if we are aiming at the object and we are inside the InteractDistance (NOTE: we need to do use a number close to 1.0 as the dot will give a number that is very close to 1.0 for aiming at the target)
					&& ( ( ( UseSeq.bAimToInteract && IsAimingAt( checkTrigger, 0.98f ) && ( VSize(Pawn.Location - checkTrigger.Location) <= UseSeq.InteractDistance ) ) )
					      // if we should NOT aim to interact then we need to be close to the trigger
			  || ( !UseSeq.bAimToInteract && ( VSize(Pawn.Location - checkTrigger.Location) <= UseSeq.InteractDistance ) )  // this should be UseSeq.InteractDistance
						  )
				   )
				{
					out_useList[out_useList.Length] = checkTrigger;

					// don't bother searching for more events
					Idx = checkTrigger.GeneratedEvents.Length;
				}
			}
		}
	}
}

/**
 * Entry point function for player interactions with the world,
 * re-directs to ServerUse.
 */
exec function Use()
{
	if( Role < Role_Authority )
	{
		PerformedUseAction();
	}
	ServerUse();
}

/**
 * Player pressed UseKey
 */
unreliable server function ServerUse()
{
	PerformedUseAction();
}

/**
 * return true if player the Use action was handled
 */
function bool PerformedUseAction()
{
	// if the level is paused,
	if( WorldInfo.Pauser == PlayerReplicationInfo )
	{
		if( Role == Role_Authority )
		{
			// unpause and move on
			SetPause( false );
		}
		return true;
	}

    if ( Pawn == None )
	{
		return true;
	}

	// below is only on server
	if( Role < Role_Authority )
	{
		return false;
	}

	// leave vehicle if currently in one
	if( Vehicle(Pawn) != None )
	{
		return Vehicle(Pawn).DriverLeave(false);
	}

	// try to find a vehicle to drive
	if( FindVehicleToDrive() )
	{
		return true;
	}

	// try to interact with triggers
	return TriggerInteracted();
}

/** Tries to find a vehicle to drive within a limited radius. Returns true if successful */
function bool FindVehicleToDrive()
{
	local Vehicle V, Best;
	local vector ViewDir, PawnLoc2D, VLoc2D;
	local float NewDot, BestDot;

	if (Vehicle(Pawn.Base) != None  && Vehicle(Pawn.Base).TryToDrive(Pawn))
	{
		return true;
	}

	// Pick best nearby vehicle
	PawnLoc2D = Pawn.Location;
	PawnLoc2D.Z = 0;
	ViewDir = vector(Pawn.Rotation);

	ForEach Pawn.OverlappingActors(class'Vehicle', V, Pawn.VehicleCheckRadius)
	{
		// Prefer vehicles that Pawn is facing
		VLoc2D = V.Location;
		Vloc2D.Z = 0;
		NewDot = Normal(VLoc2D-PawnLoc2D) Dot ViewDir;
		if ( (Best == None) || (NewDot > BestDot) )
		{
			// check that vehicle is visible
			if ( FastTrace(V.Location,Pawn.Location) )
			{
				Best = V;
				BestDot = NewDot;
			}
		}
	}
	return (Best != None && Best.TryToDrive(Pawn));
}

/**
 * Examines the nearby enviroment and generates a priority sorted
 * list of interactable actors, and then attempts to activate each
 * of them until either one was successfully activated, or no more
 * actors are available.
 */
function bool TriggerInteracted()
{
	local Actor A;
	local int Idx;
	local float Weight;
	local bool bInserted;
	local vector cameraLoc;
	local rotator cameraRot;
	local array<Trigger> useList;
	// the following 2 arrays should always match in length
	local array<Actor> sortedList;
	local array<float> weightList;

	if ( Pawn != None )
	{
		GetTriggerUseList(InteractDistance,60.f,0.f,true,useList);
		// if we have found some interactable actors,
		if (useList.Length > 0)
		{
			// grab the current camera location/rotation for weighting purposes
			GetPlayerViewPoint(cameraLoc, cameraRot);
			// then build the sorted list
			while (useList.Length > 0)
			{
				// pop the actor off this list
				A = useList[useList.Length-1];
				useList.Length = useList.Length - 1;
				// calculate the weight of this actor in terms of optimal interaction
				// first based on the dot product from our view rotation
				weight = Normal(A.Location-cameraLoc) dot vector(cameraRot);
				// and next on the distance
				weight += 1.f - (VSize(A.Location-Pawn.Location)/InteractDistance);
				// find the optimal insertion point
				bInserted = false;
				for (Idx = 0; Idx < sortedList.Length && !bInserted; Idx++)
				{
					if (weightList[Idx] < weight)
					{
						// insert the new entry
						sortedList.Insert(Idx,1);
						weightList.Insert(Idx,1);
						sortedList[Idx] = A;
						weightList[Idx] = weight;
						bInserted = true;
					}
				}
				// if no insertion was made
				if (!bInserted)
				{
					// then tack on the end of the list
					Idx = sortedList.Length;
					sortedList[Idx] = A;
					weightList[Idx] = weight;
				}
			}
			// finally iterate through each actor in the sorted list and
			// attempt to activate it until one succeeds or none are left
			for (Idx = 0; Idx < sortedList.Length; Idx++)
			{
				if (sortedList[Idx].UsedBy(Pawn))
				{
					// skip the rest
					// Idx = sortedList.Length;
					return true;
				}
			}
		}
	}

	return false;
}


exec function Suicide()
{
	ServerSuicide();
}

reliable server function ServerSuicide()
{
	if ( (Pawn != None) && ((WorldInfo.TimeSeconds - Pawn.LastStartTime > 10) || (WorldInfo.NetMode == NM_Standalone)) )
	{
		Pawn.Suicide();
	}
}

exec function SetName(coerce string S)
{
	local string NewName;
	local LocalPlayer LocPlayer;

	if (S != "")
	{
		LocPlayer = LocalPlayer(Player);
		if (LocPlayer != None &&
			OnlineSub.GameInterface != None &&
			OnlineSub.PlayerInterface != None)
		{
			// Check to see if they are logged in locally or not
			if (OnlineSub.PlayerInterface.GetLoginStatus(LocPlayer.ControllerId) == LS_LoggedIn &&
				OnlineSub.GameInterface.GetGameSettings('Game') != None)
			{
				// Ignore what ever was specified and use the profile's nick
				S = OnlineSub.PlayerInterface.GetPlayerNickname(LocPlayer.ControllerId);
			}
		}

		NewName = S;

		ServerChangeName(NewName);
		UpdateURL("Name", NewName, true);
		SaveConfig();
	}
}

reliable server function ServerChangeName( coerce string S )
{
	if (S != "")
	{
		WorldInfo.Game.ChangeName( self, S, true );
	}
}

exec function SwitchTeam()
{
	if ( (PlayerReplicationInfo.Team == None) || (PlayerReplicationInfo.Team.TeamIndex == 1) )
	{
		ServerChangeTeam(0);
	}
	else
	{
		ServerChangeTeam(1);
	}
}

exec function ChangeTeam( optional string TeamName )
{
	local int N;

	if ( TeamName ~= "blue" )
		N = 1;
	else if ( (TeamName ~= "red") || (PlayerReplicationInfo == None) || (PlayerReplicationInfo.Team == None) || (PlayerReplicationInfo.Team.TeamIndex > 1) )
		N = 0;
	else
		N = 1 - PlayerReplicationInfo.Team.TeamIndex;

	ServerChangeTeam(N);
}

reliable server function ServerChangeTeam(int N)
{
	local TeamInfo OldTeam;

	OldTeam = PlayerReplicationInfo.Team;
	WorldInfo.Game.ChangeTeam(self, N, true);
	if (WorldInfo.Game.bTeamGame && PlayerReplicationInfo.Team != OldTeam)
	{
		if (Pawn != None)
		{
			Pawn.PlayerChangedTeam();
		}
	}
}


exec function SwitchLevel(string URL)
{
	if (WorldInfo.NetMode == NM_Standalone || WorldInfo.NetMode == NM_ListenServer)
	{
		WorldInfo.ServerTravel(URL);
	}
}

/** server to client RPC for server-generated network messages that aren't part of the connection process (e.g. being kicked) */
reliable client event ClientSetProgressMessage(EProgressMessageType MessageType, string Message, optional string Title, optional bool bIgnoreFutureNetworkMessages)
{
	if (LocalPlayer(Player) != None)
	{
		LocalPlayer(Player).ViewportClient.SetProgressMessage(MessageType, Message, Title, bIgnoreFutureNetworkMessages);
	}
	else
	{
		`Warn("Discarded progress message due to no viewport:" @ MessageType @ Message @ Title);
	}
}

function Restart(bool bVehicleTransition)
{
	Super.Restart(bVehicleTransition);
	ServerTimeStamp = 0;
	ResetTimeMargin();
	EnterStartState();
	ClientRestart(Pawn);
	SetViewTarget(Pawn);
	ResetCameraMode();
}

/** called to notify the server when the client has loaded a new world via seamless travelling
 * @param WorldPackageName the name of the world package that was loaded
 */
reliable server native final event ServerNotifyLoadedWorld(name WorldPackageName);

/** called clientside when it is loaded a new world via seamless travelling
 * @param WorldPackageName the name of the world package that was loaded
 * @param bFinalDest whether this world is the destination map for the travel (i.e. not the transition level)
 */
event NotifyLoadedWorld(name WorldPackageName, bool bFinalDest)
{
	local PlayerStart P;
	local rotator SpawnRotation;


	// place the camera at the first playerstart we can find
	SetViewTarget(self);
	foreach WorldInfo.AllNavigationPoints(class'PlayerStart', P)
	{
		SetLocation(P.Location);
		SpawnRotation.Yaw = P.Rotation.Yaw;
		SetRotation(SpawnRotation);
		break;
	}
}

/** returns whether the client has completely loaded the server's current world (valid on server only) */
native final function bool HasClientLoadedCurrentWorld();

/** forces a full replication check of the specified Actor on only the client that owns this PlayerController
 * (equivalent to setting bForceNetUpdate and bNetDirty to true, but only for that client)
 * this function has no effect if this PC is not a remote client or if the Actor is not relevant to that client
 */
native final function ForceSingleNetUpdateFor(Actor Target);

function EnterStartState()
{
	local name NewState;

	if ( Pawn.PhysicsVolume.bWaterVolume )
	{
		if ( Pawn.HeadVolume.bWaterVolume )
		{
			Pawn.BreathTime = Pawn.UnderWaterTime;
		}
		NewState = Pawn.WaterMovementState;
	}
	else
	{
		NewState = Pawn.LandMovementState;
	}

	if (GetStateName() == NewState)
	{
		BeginState(NewState);
	}
	else
	{
		GotoState(NewState);
	}
}

reliable client function ClientRestart(Pawn NewPawn)
{
	ResetPlayerMovementInput();
    CleanOutSavedMoves();  // don't replay moves previous to possession

	Pawn = NewPawn;
	if ( (Pawn != None) && Pawn.bTearOff )
	{
		UnPossess();
		Pawn = None;
	}

	AcknowledgePossession(Pawn);

	if ( Pawn == None )
	{
		GotoState('WaitingForPawn');
		return;
	}
	Pawn.ClientRestart();
	if (Role < ROLE_Authority)
	{
		SetViewTarget(Pawn);
		ResetCameraMode();
		EnterStartState();
	}
	CleanOutSavedMoves();
}

/* epic ===============================================
* ::GameHasEnded
*
* Called from game info upon end of the game, used to
* transition to proper state.
*
* =====================================================
*/
function GameHasEnded(optional Actor EndGameFocus, optional bool bIsWinner)
{
	// and transition to the game ended state
	SetViewTarget(EndGameFocus);
	GotoState('RoundEnded');
	ClientGameEnded(EndGameFocus, bIsWinner);
}

/* epic ===============================================
* ::ClientGameEnded
*
* Replicated function called by GameHasEnded().
*
 * @param	EndGameFocus - actor to view with camera
 * @param	bIsWinner - true if this controller is on winning team
* =====================================================
*/
reliable client function ClientGameEnded(Actor EndGameFocus, bool bIsWinner)
{
	SetViewTarget(EndGameFocus);
	GotoState('RoundEnded');
}

// Just changed to pendingWeapon
function NotifyChangedWeapon( Weapon PreviousWeapon, Weapon NewWeapon );

/**
 * PlayerTick is only called if the PlayerController has a PlayerInput object.  Therefore, it will not be called on servers for non-locally controlled playercontrollers
 */
event PlayerTick( float DeltaTime )
{
	if ( !bShortConnectTimeOut )
	{
		bShortConnectTimeOut = true;
		ServerShortTimeout();
	}

	if ( Pawn != AcknowledgedPawn )
	{
		if ( Role < ROLE_Authority )
		{
			// make sure old pawn controller is right
			if ( (AcknowledgedPawn != None) && (AcknowledgedPawn.Controller == self) )
				AcknowledgedPawn.Controller = None;
		}
		AcknowledgePossession(Pawn);
	}

	PlayerInput.PlayerInput(DeltaTime);
	if ( bUpdatePosition )
	{
		ClientUpdatePosition();
	}
	PlayerMove(DeltaTime);

	AdjustFOV(DeltaTime);
}

function PlayerMove(float DeltaTime);

function bool AimingHelp(bool bInstantHit)
{
 return (WorldInfo.NetMode == NM_Standalone) && bAimingHelp;
}

/** The function called when a CameraLookAt action is deactivated from kismet */
event CameraLookAtFinished(SeqAct_CameraLookAt Action);

/**
 * Adjusts weapon aiming direction.
 * Gives controller a chance to modify the aiming of the pawn. For example aim error, auto aiming, adhesion, AI help...
 * Requested by weapon prior to firing.
 *
 * @param	W, weapon about to fire
 * @param	StartFireLoc, world location of weapon fire start trace, or projectile spawn loc.
 * @param	BaseAimRot, original aiming rotation without any modifications.
 */
function Rotator GetAdjustedAimFor( Weapon W, vector StartFireLoc )
{
	local vector	FireDir, AimSpot, HitLocation, HitNormal, OldAim, AimOffset;
	local actor		BestTarget, HitActor;
	local float		bestAim, bestDist;
	local bool		bNoZAdjust, bInstantHit;
	local rotator	BaseAimRot, AimRot;

	bInstantHit = ( W == None || W.bInstantHit );
	BaseAimRot = (Pawn != None) ? Pawn.GetBaseAimRotation() : Rotation;
	FireDir	= vector(BaseAimRot);
	HitActor = Trace(HitLocation, HitNormal, StartFireLoc + W.GetTraceRange() * FireDir, StartFireLoc, true);

	if ( (HitActor != None) && HitActor.bProjTarget )
	{
		BestTarget = HitActor;
		bNoZAdjust = true;
		OldAim = HitLocation;
		BestDist = VSize(BestTarget.Location - Pawn.Location);
	}
	else
	{
		// adjust aim based on FOV
		bestAim = 0.90;
		if ( AimingHelp(bInstantHit) )
		{
			bestAim = AimHelpDot(bInstantHit);
		}
		else if ( bInstantHit )
			bestAim = 1.0;

		BestTarget = PickTarget(class'Pawn', bestAim, bestDist, FireDir, StartFireLoc, W.WeaponRange);
		if ( BestTarget == None )
		{
			return BaseAimRot;
		}
		OldAim = StartFireLoc + FireDir * bestDist;
	}

	ShotTarget = Pawn(BestTarget);
    if ( !AimingHelp(bInstantHit) )
	{
    	return BaseAimRot;
	}

	// aim at target - help with leading also
	FireDir = BestTarget.Location - StartFireLoc;
	AimSpot = StartFireLoc + bestDist * Normal(FireDir);
	AimOffset = AimSpot - OldAim;

    if ( ShotTarget != None )
    {
	    // adjust Z of shooter if necessary
	    if ( bNoZAdjust )
	        AimSpot.Z = OldAim.Z;
	    else if ( AimOffset.Z < 0 )
	        AimSpot.Z = ShotTarget.Location.Z + 0.4 * ShotTarget.CylinderComponent.CollisionHeight;
	    else
	        AimSpot.Z = ShotTarget.Location.Z - 0.7 * ShotTarget.CylinderComponent.CollisionHeight;
    }
    else
	    AimSpot.Z = OldAim.Z;

	// if not leading, add slight random error ( significant at long distances )
	if ( !bNoZAdjust )
	{
		AimRot = rotator(AimSpot - StartFireLoc);
		if ( FOVAngle < DefaultFOV - 8 )
			AimRot.Yaw = AimRot.Yaw + 200 - Rand(400);
		else
			AimRot.Yaw = AimRot.Yaw + 375 - Rand(750);
		return AimRot;
	}
	return rotator(AimSpot - StartFireLoc);
}

/** AimHelpDot()
* @returns the dot product corresponding to the maximum deflection of target for which aiming help should be applied
*/
function float AimHelpDot(bool bInstantHit)
{
	if ( FOVAngle < DefaultFOV - 8 )
		return 0.99;
	if ( bInstantHit )
		return 0.97;
	return 0.93;
}

event bool NotifyLanded(vector HitNormal, Actor FloorActor)
{
	return bUpdating;
}

//=============================================================================
// Player Control

// Player view.
// Compute the rendering viewpoint for the player.
//

/** AdjustFOV()
FOVAngle smoothly interpolates to DesiredFOV
*/
function AdjustFOV(float DeltaTime )
{
	if ( FOVAngle != DesiredFOV )
	{
		if ( FOVAngle > DesiredFOV )
			FOVAngle = FOVAngle - FMax(7, 0.9 * DeltaTime * (FOVAngle - DesiredFOV));
		else
			FOVAngle = FOVAngle - FMin(-7, 0.9 * DeltaTime * (FOVAngle - DesiredFOV));
		if ( Abs(FOVAngle - DesiredFOV) <= 10 )
			FOVAngle = DesiredFOV;
	}
}

/** returns player's FOV angle */
event float GetFOVAngle()
{
	return (PlayerCamera != None) ? PlayerCamera.GetFOVAngle() : FOVAngle;
}

/** returns whether this Controller is a locally controlled PlayerController
 * @note not valid until the Controller is completely spawned (i.e, unusable in Pre/PostBeginPlay())
 */
native function bool IsLocalPlayerController();

/** returns whether this controller is a local controller.
  * @RETURN true if NM_Standalone, or is local playercontroller
 */
native function bool IsLocalController();

native function SetViewTarget(Actor NewViewTarget, optional ViewTargetTransitionParams TransitionParams);

/** Wrapper to SetViewTarget with useful defaults */
final function SetViewTargetWithBlend(Actor NewViewTarget, optional float BlendTime = 0.35, optional EViewTargetBlendFunction BlendFunc = VTBlend_Cubic, optional float BlendExp = 2.f, optional bool bLockOutgoing = FALSE)
{
	local ViewTargetTransitionParams TransitionParams;
	TransitionParams.BlendTime = BlendTime;
	TransitionParams.BlendFunction = BlendFunc;
	TransitionParams.BlendExp = BlendExp;
	TransitionParams.bLockOutgoing = bLockOutgoing;
	SetViewTarget(NewViewTarget,TransitionParams);
}

reliable client event ClientSetViewTarget( Actor A, optional ViewTargetTransitionParams TransitionParams )
{
	if (!bClientSimulatingViewTarget)
	{
		if( A == None )
		{
			ServerVerifyViewTarget();
		}
		SetViewTarget(A, TransitionParams);
	}
}

native function Actor GetViewTarget();

reliable server function ServerVerifyViewTarget()
{
	local Actor TheViewTarget;

	TheViewTarget = GetViewTarget();

	if( TheViewTarget == Self )
	{
		return;
	}
	ClientSetViewTarget( TheViewTarget );
}

event SpawnPlayerCamera()
{
	if (CameraClass != None)
	{
		// Associate Camera with PlayerController
		PlayerCamera = Spawn( CameraClass, self );
		if( PlayerCamera != None )
		{
			PlayerCamera.InitializeFor( self );
		}
		else
		{
			`Log( "Couldn't Spawn Camera Actor for Player!!" );
		}
	}
	else
	{
		// not having a CameraClass is fine.  Another class will handing the "camera" type duties
	// usually PlayerController
	}
}

/**
 * Returns Player's Point of View
 * For the AI this means the Pawn's 'Eyes' ViewPoint
 * For a Human player, this means the Camera's ViewPoint
 *
 * @output	out_Location, view location of player
 * @output	out_rotation, view rotation of player
 */
simulated event GetPlayerViewPoint( out vector out_Location, out Rotator out_Rotation )
{
	local Actor TheViewTarget;

	// sometimes the PlayerCamera can be none and we probably do not want this
	// so we will check to see if we have a CameraClass.  Having a CameraClass is
	// saying:  we want a camera so make certain one exists by spawning one
	if( PlayerCamera == None )
	{
		if( CameraClass != None )
		{
			// Associate Camera with PlayerController
			PlayerCamera = Spawn(CameraClass, Self);
			if( PlayerCamera != None )
			{
				PlayerCamera.InitializeFor( Self );
			}
			else
			{
				`log("Couldn't Spawn Camera Actor for Player!!");
			}
		}
	}

	if( PlayerCamera != None )
	{
		PlayerCamera.GetCameraViewPoint(out_Location, out_Rotation);
	}
	else
	{
		TheViewTarget = GetViewTarget();

		if( TheViewTarget != None )
		{
			out_Location = TheViewTarget.Location;
			out_Rotation = TheViewTarget.Rotation;
		}
		else
		{
			super.GetPlayerViewPoint(out_Location, out_Rotation);
		}
	}
}

/** Updates any camera view shaking that is going on */
function ViewShake(float DeltaTime);

function UpdateRotation( float DeltaTime )
{
	local Rotator	DeltaRot, newRotation, ViewRotation;

	ViewRotation = Rotation;
	if (Pawn!=none)
	{
		Pawn.SetDesiredRotation(ViewRotation);
	}

	// Calculate Delta to be applied on ViewRotation
	DeltaRot.Yaw	= PlayerInput.aTurn;
	DeltaRot.Pitch	= PlayerInput.aLookUp;

`if(`__TW_)
	ModifyUpdateRotation( DeltaTime, DeltaRot );
`endif

	ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot );
	SetRotation(ViewRotation);

	ViewShake( deltaTime );

	NewRotation = ViewRotation;
	NewRotation.Roll = Rotation.Roll;

	if ( Pawn != None )
		Pawn.FaceRotation(NewRotation, deltatime);
}

`if(`__TW_)
/** Modify DeltaRot to account for ForceLookAtPawn, AutoTarget, and TargetAdhesion */
function ModifyUpdateRotation( float DeltaTime, out Rotator DeltaRot );
`endif

/**
 * Processes the player's ViewRotation
 * adds delta rot (player input), applies any limits and post-processing
 * returns the final ViewRotation set on PlayerController
 *
 * @param	DeltaTime, time since last frame
 * @param	ViewRotation, current player ViewRotation
 * @param	DeltaRot, player input added to ViewRotation
 */
function ProcessViewRotation( float DeltaTime, out Rotator out_ViewRotation, Rotator DeltaRot )
{
	if( PlayerCamera != None )
	{
		PlayerCamera.ProcessViewRotation( DeltaTime, out_ViewRotation, DeltaRot );
	}

	if ( Pawn != None )
	{	// Give the Pawn a chance to modify DeltaRot (limit view for ex.)
		Pawn.ProcessViewRotation( DeltaTime, out_ViewRotation, DeltaRot );
	}
	else
	{
		// If Pawn doesn't exist, limit view

		// Add Delta Rotation
		out_ViewRotation	+= DeltaRot;
		out_ViewRotation	 = LimitViewRotation(out_ViewRotation, -16384, 16383 );
	}
}


/**
 * Limit the player's view rotation. (Pitch component).
 */
event Rotator LimitViewRotation( Rotator ViewRotation, float ViewPitchMin, float ViewPitchMax )
{
	ViewRotation.Pitch = ViewRotation.Pitch & 65535;

    if( ViewRotation.Pitch > ViewPitchMax &&
		ViewRotation.Pitch < (65535+ViewPitchMin) )
	{
		if( ViewRotation.Pitch < 32768 )
		{
			ViewRotation.Pitch = ViewPitchMax;
		}
		else
		{
			ViewRotation.Pitch = 65535 + ViewPitchMin;
		}
	}

	return ViewRotation;
}

/* CheckJumpOrDuck()
Called by ProcessMove()
handle jump and duck buttons which are pressed
*/
function CheckJumpOrDuck()
{
	if ( bPressedJump && (Pawn != None) )
	{
		Pawn.DoJump( bUpdating );
	}
}

/**
 * Allow player controllers to adjust the acceleration in PlayerWalking
 *
 * @param NewAccel - the acceleration used by PlayerWalking::PlayerMove
 */
function AdjustPlayerWalkingMoveAccel(out vector NewAccel);

// Player movement.
// Player Standing, walking, running, falling.
state PlayerWalking
{
ignores SeePlayer, HearNoise, Bump;

	event NotifyPhysicsVolumeChange( PhysicsVolume NewVolume )
	{
		if ( NewVolume.bWaterVolume && Pawn.bCollideWorld )
		{
			GotoState(Pawn.WaterMovementState);
		}
	}

	function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
	{
		if( Pawn == None )
		{
			return;
		}

		if (Role == ROLE_Authority)
		{
			// Update ViewPitch for remote clients
			Pawn.SetRemoteViewPitch( Rotation.Pitch );
		}

		Pawn.Acceleration = NewAccel;

		CheckJumpOrDuck();
	}

	function PlayerMove( float DeltaTime )
	{
		local vector			X,Y,Z, NewAccel;
		local eDoubleClickDir	DoubleClickMove;
		local rotator			OldRotation;
		local bool				bSaveJump;

		if( Pawn == None )
		{
			GotoState('Dead');
		}
		else
		{
			GetAxes(Pawn.Rotation,X,Y,Z);

			// Update acceleration.
			NewAccel = PlayerInput.aForward*X + PlayerInput.aStrafe*Y;
			NewAccel.Z	= 0;
			NewAccel = Pawn.AccelRate * Normal(NewAccel);

			if (IsLocalPlayerController())
			{
				AdjustPlayerWalkingMoveAccel(NewAccel);
			}

			DoubleClickMove = PlayerInput.CheckForDoubleClickMove( DeltaTime/WorldInfo.TimeDilation );

			// Update rotation.
			OldRotation = Rotation;
			UpdateRotation( DeltaTime );
			bDoubleJump = false;

			if( bPressedJump && Pawn.CannotJumpNow() )
			{
				bSaveJump = true;
				bPressedJump = false;
			}
			else
			{
				bSaveJump = false;
			}

			if( Role < ROLE_Authority ) // then save this move and replicate it
			{
				ReplicateMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
			}
			else
			{
				ProcessMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
			}
			bPressedJump = bSaveJump;
		}
	}

	event BeginState(Name PreviousStateName)
	{
		DoubleClickDir = DCLICK_None;
		bPressedJump = false;
		GroundPitch = 0;
		if ( Pawn != None )
		{
			Pawn.ShouldCrouch(false);
			if (Pawn.Physics != PHYS_Falling && Pawn.Physics != PHYS_RigidBody) // FIXME HACK!!!
				Pawn.SetPhysics(Pawn.WalkingPhysics);
		}
	}

	event EndState(Name NextStateName)
	{
		GroundPitch = 0;
		if ( Pawn != None )
		{
			Pawn.SetRemoteViewPitch( 0 );
			if ( bDuck == 0 )
			{
				Pawn.ShouldCrouch(false);
			}
		}
	}

Begin:
}

// player is climbing ladder
state PlayerClimbing
{
ignores SeePlayer, HearNoise, Bump;

	event NotifyPhysicsVolumeChange( PhysicsVolume NewVolume )
	{
		if( NewVolume.bWaterVolume )
		{
			GotoState( Pawn.WaterMovementState );
		}
		else
		{
			GotoState( Pawn.LandMovementState );
		}
	}

	function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
	{
		if( Pawn == None )
		{
			return;
		}

		if (Role == ROLE_Authority)
		{
			// Update ViewPitch for remote clients
			Pawn.SetRemoteViewPitch( Rotation.Pitch );
		}

		Pawn.Acceleration	= NewAccel;

		if( bPressedJump )
		{
			Pawn.DoJump( bUpdating );
			if( Pawn.Physics == PHYS_Falling )
			{
				GotoState(Pawn.LandMovementState);
			}
		}
	}

	function PlayerMove( float DeltaTime )
	{
		local vector X,Y,Z, NewAccel;
		local rotator OldRotation, ViewRotation;

		GetAxes(Rotation,X,Y,Z);

		// Update acceleration.
		if ( Pawn.OnLadder != None )
		{
			NewAccel = PlayerInput.aForward*Pawn.OnLadder.ClimbDir;
		    if ( Pawn.OnLadder.bAllowLadderStrafing )
				NewAccel += PlayerInput.aStrafe*Y;
		}
		else
			NewAccel = PlayerInput.aForward*X + PlayerInput.aStrafe*Y;
		NewAccel = Pawn.AccelRate * Normal(NewAccel);

		ViewRotation = Rotation;

		// Update rotation.
		SetRotation(ViewRotation);
		OldRotation = Rotation;
		UpdateRotation( DeltaTime );

		if ( Role < ROLE_Authority ) // then save this move and replicate it
			ReplicateMove(DeltaTime, NewAccel, DCLICK_None, OldRotation - Rotation);
		else
			ProcessMove(DeltaTime, NewAccel, DCLICK_None, OldRotation - Rotation);
		bPressedJump = false;
	}

	event BeginState(Name PreviousStateName)
	{
		Pawn.ShouldCrouch(false);
		bPressedJump = false;
	}

	event EndState(Name NextStateName)
	{
		if ( Pawn != None )
		{
			Pawn.SetRemoteViewPitch( 0 );
			Pawn.ShouldCrouch(false);
		}
	}
}

// Player Driving a vehicle.
state PlayerDriving
{
ignores SeePlayer, HearNoise, Bump;

	function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot);

	// Set the throttle, steering etc. for the vehicle based on the input provided
	function ProcessDrive(float InForward, float InStrafe, float InUp, bool InJump)
	{
		local Vehicle CurrentVehicle;

		CurrentVehicle = Vehicle(Pawn);
		if (CurrentVehicle != None)
		{
			//`log("Forward:"@InForward@" Strafe:"@InStrafe@" Up:"@InUp);
			bPressedJump = InJump;
			CurrentVehicle.SetInputs(InForward, -InStrafe, InUp);
			CheckJumpOrDuck();
		}
	}

	function PlayerMove( float DeltaTime )
	{
		// update 'looking' rotation
		UpdateRotation(DeltaTime);

		// TODO: Don't send things like aForward and aStrafe for gunners who don't need it
		// Only servers can actually do the driving logic.
		ProcessDrive(PlayerInput.RawJoyUp, PlayerInput.RawJoyRight, PlayerInput.aUp, bPressedJump);
		if (Role < ROLE_Authority)
		{
			ServerDrive(PlayerInput.RawJoyUp, PlayerInput.RawJoyRight, PlayerInput.aUp, bPressedJump, ((Rotation.Yaw & 65535) << 16) + (Rotation.Pitch & 65535));
		}

		bPressedJump = false;
	}

	unreliable server function ServerUse()
	{
		local Vehicle CurrentVehicle;

		CurrentVehicle = Vehicle(Pawn);
		CurrentVehicle.DriverLeave(false);
	}

	event BeginState(Name PreviousStateName)
	{
		CleanOutSavedMoves();
	}

	event EndState(Name NextStateName)
	{
		CleanOutSavedMoves();
	}
}

// Player movement.
// Player Swimming
state PlayerSwimming
{
ignores SeePlayer, HearNoise, Bump;

	event bool NotifyLanded(vector HitNormal, Actor FloorActor)
	{
		if ( Pawn.PhysicsVolume.bWaterVolume )
			Pawn.SetPhysics(PHYS_Swimming);
		else
			GotoState(Pawn.LandMovementState);
		return bUpdating;
	}

	event NotifyPhysicsVolumeChange( PhysicsVolume NewVolume )
	{
		local actor HitActor;
		local vector HitLocation, HitNormal, Checkpoint;
		local vector X,Y,Z;

		if ( !Pawn.bCollideActors )
		{
			GotoState(Pawn.LandMovementState);
		}
		if (Pawn.Physics != PHYS_RigidBody)
		{
			if ( !NewVolume.bWaterVolume )
			{
				Pawn.SetPhysics(PHYS_Falling);
				if ( Pawn.Velocity.Z > 0 )
				{
					GetAxes(Rotation,X,Y,Z);
	 				Pawn.bUpAndOut = ((X Dot Pawn.Acceleration) > 0) && ((Pawn.Acceleration.Z > 0) || (Rotation.Pitch > 2048));
				    if (Pawn.bUpAndOut && Pawn.CheckWaterJump(HitNormal)) //check for waterjump
				    {
					    Pawn.velocity.Z = Pawn.OutOfWaterZ; //set here so physics uses this for remainder of tick
					    GotoState(Pawn.LandMovementState);
				    }
				    else if ( (Pawn.Velocity.Z > 160) || !Pawn.TouchingWaterVolume() )
					    GotoState(Pawn.LandMovementState);
				    else //check if in deep water
				    {
					    Checkpoint = Pawn.Location;
					    Checkpoint.Z -= (Pawn.CylinderComponent.CollisionHeight + 6.0);
					    HitActor = Trace(HitLocation, HitNormal, Checkpoint, Pawn.Location, false);
					    if (HitActor != None)
						    GotoState(Pawn.LandMovementState);
					    else
					    {
						    SetTimer(0.7, false);
					    }
				    }
			    }
			}
			else
			{
				ClearTimer();
				Pawn.SetPhysics(PHYS_Swimming);
			}
		}
		else if (!NewVolume.bWaterVolume)
		{
			// if in rigid body, go to appropriate state, but don't modify pawn physics
			GotoState(Pawn.LandMovementState);
		}
	}

	function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
	{
		Pawn.Acceleration = NewAccel;
	}

	function PlayerMove(float DeltaTime)
	{
		local rotator oldRotation;
		local vector X,Y,Z, NewAccel;

		if (Pawn == None)
		{
			GotoState('Dead');
		}
		else
		{
			GetAxes(Rotation,X,Y,Z);

			NewAccel = PlayerInput.aForward*X + PlayerInput.aStrafe*Y + PlayerInput.aUp*vect(0,0,1);
			NewAccel = Pawn.AccelRate * Normal(NewAccel);

			// Update rotation.
			oldRotation = Rotation;
			UpdateRotation( DeltaTime );

			if ( Role < ROLE_Authority ) // then save this move and replicate it
			{
				ReplicateMove(DeltaTime, NewAccel, DCLICK_None, OldRotation - Rotation);
			}
			else
			{
				ProcessMove(DeltaTime, NewAccel, DCLICK_None, OldRotation - Rotation);
			}
			bPressedJump = false;
		}
	}

	event Timer()
	{
		if (!Pawn.PhysicsVolume.bWaterVolume && Role == ROLE_Authority)
		{
			GotoState(Pawn.LandMovementState);
		}

		ClearTimer();
	}

	event BeginState(Name PreviousStateName)
	{
		ClearTimer();
		if (Pawn.Physics != PHYS_RigidBody)
		{
			Pawn.SetPhysics(PHYS_Swimming);
		}
	}

Begin:
}

state PlayerFlying
{
ignores SeePlayer, HearNoise, Bump;

	function PlayerMove(float DeltaTime)
	{
		local vector X,Y,Z;

		GetAxes(Rotation,X,Y,Z);

		Pawn.Acceleration = PlayerInput.aForward*X + PlayerInput.aStrafe*Y + PlayerInput.aUp*vect(0,0,1);;
		Pawn.Acceleration = Pawn.AccelRate * Normal(Pawn.Acceleration);

		if ( bCheatFlying && (Pawn.Acceleration == vect(0,0,0)) )
			Pawn.Velocity = vect(0,0,0);
		// Update rotation.
		UpdateRotation( DeltaTime );

		if ( Role < ROLE_Authority ) // then save this move and replicate it
			ReplicateMove(DeltaTime, Pawn.Acceleration, DCLICK_None, rot(0,0,0));
		else
			ProcessMove(DeltaTime, Pawn.Acceleration, DCLICK_None, rot(0,0,0));
	}

	event BeginState(Name PreviousStateName)
	{
		Pawn.SetPhysics(PHYS_Flying);
	}
}

function bool IsSpectating()
{
	return false;
}

/** when spectating, tells server where the client is (client is authoritative on location when spectating) */
unreliable server function ServerSetSpectatorLocation(vector NewLoc)
{
	// if we receive this here, the client is in the wrong state; tell it what state it should be in
	if (WorldInfo.TimeSeconds != LastSpectatorStateSynchTime)
	{
		ClientGotoState(GetStateName());
		ClientSetViewTarget(GetViewTarget());
		LastSpectatorStateSynchTime = WorldInfo.TimeSeconds;
	}
}

state BaseSpectating
{
	function bool IsSpectating()
	{
		return true;
	}

	/**
	  * Adjust spectator velocity if "out of bounds"
	  * (above stallz or below killz)
	  */
	function bool LimitSpectatorVelocity()
	{
		if ( Location.Z > WorldInfo.StallZ )
		{
			Velocity.Z = FMin(SpectatorCameraSpeed, WorldInfo.StallZ - Location.Z - 2.0);
			return true;
		}
		else if ( Location.Z < WorldInfo.KillZ )
		{
			Velocity.Z = FMin(SpectatorCameraSpeed, WorldInfo.KillZ - Location.Z + 2.0);
			return true;
		}
		return false;
	}

	function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
	{
		local float		VelSize;

		/* smoothly accelerate and decelerate */
		Acceleration = Normal(NewAccel) * SpectatorCameraSpeed;

		VelSize = VSize(Velocity);
		if( VelSize > 0 )
		{
			Velocity = Velocity - (Velocity - Normal(Acceleration) * VelSize) * FMin(DeltaTime * 8, 1);
		}

		Velocity = Velocity + Acceleration * DeltaTime;
		if( VSize(Velocity) > SpectatorCameraSpeed )
		{
			Velocity = Normal(Velocity) * SpectatorCameraSpeed;
		}

		LimitSpectatorVelocity();
		if( VSize(Velocity) > 0 )
		{
			MoveSmooth( (1+bRun) * Velocity * DeltaTime );
			// correct if out of bounds after move
			if ( LimitSpectatorVelocity() )
			{
				MoveSmooth( Velocity.Z * vect(0,0,1) * DeltaTime );
			}
		}
	}

	function PlayerMove(float DeltaTime)
	{
		local vector X,Y,Z;

		GetAxes(Rotation,X,Y,Z);
		Acceleration = PlayerInput.aForward*X + PlayerInput.aStrafe*Y + PlayerInput.aUp*vect(0,0,1);
		UpdateRotation(DeltaTime);

		if (Role < ROLE_Authority) // then save this move and replicate it
		{
			ReplicateMove(DeltaTime, Acceleration, DCLICK_None, rot(0,0,0));
		}
		else
		{
			ProcessMove(DeltaTime, Acceleration, DCLICK_None, rot(0,0,0));
		}
	}

	/** when spectating, tells server where the client is (client is authoritative on location when spectating) */
	unreliable server function ServerSetSpectatorLocation(vector NewLoc)
	{
		SetLocation(NewLoc);
		if ( WorldInfo.TimeSeconds - LastSpectatorStateSynchTime > 2.0 )
		{
			ClientGotoState(GetStateName());
			LastSpectatorStateSynchTime = WorldInfo.TimeSeconds;
		}
	}

	function ReplicateMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
	{
		ProcessMove(DeltaTime, NewAccel, DoubleClickMove, DeltaRot);
		// when spectating, client position is authoritative
		ServerSetSpectatorLocation(Location);

		if (PlayerCamera != None && PlayerCamera.bUseClientSideCameraUpdates)
		{
			PlayerCamera.bShouldSendClientSideCameraUpdate = TRUE;
		}
	}


	event BeginState(Name PreviousStateName)
	{
		bCollideWorld = true;
	}

	event EndState(Name NextStateName)
	{
		bCollideWorld = false;
	}
}

unreliable server function ServerViewNextPlayer()
{
	if (IsSpectating())
	{
		ViewAPlayer(+1);
	}
}

unreliable server function ServerViewPrevPlayer()
{
	if (IsSpectating())
	{
		ViewAPlayer(-1);
	}
}

/**
 * Get next active viewable player in PRIArray.
 * @param dir is the direction to go in the array
 */
function PlayerReplicationInfo GetNextViewablePlayer(int dir)
{
	local int i, CurrentIndex, NewIndex;
	local PlayerReplicationInfo PRI;

	CurrentIndex = -1;
	if ( RealViewTarget != None )
	{
		// Find index of current viewtarget's PRI
		For ( i=0; i<WorldInfo.GRI.PRIArray.Length; i++ )
		{
			if ( RealViewTarget == WorldInfo.GRI.PRIArray[i] )
			{
				CurrentIndex = i;
				break;
			}
		}
	}

	// Find next valid viewtarget in appropriate direction
	for ( NewIndex=CurrentIndex+dir; (NewIndex>=0)&&(NewIndex<WorldInfo.GRI.PRIArray.Length); NewIndex=NewIndex+dir )
	{
		PRI = WorldInfo.GRI.PRIArray[NewIndex];
		if ( (PRI != None) && (Controller(PRI.Owner) != None) && (Controller(PRI.Owner).Pawn != None)
			&& WorldInfo.Game.CanSpectate(self, PRI) )
		{
			return PRI;
		}
	}

	// wrap around
	CurrentIndex = (NewIndex < 0) ? WorldInfo.GRI.PRIArray.Length : -1;
	for ( NewIndex=CurrentIndex+dir; (NewIndex>=0)&&(NewIndex<WorldInfo.GRI.PRIArray.Length); NewIndex=NewIndex+dir )
	{
		PRI = WorldInfo.GRI.PRIArray[NewIndex];
		if ( (PRI != None) && (Controller(PRI.Owner) != None) && (Controller(PRI.Owner).Pawn != None) &&
			WorldInfo.Game.CanSpectate(self, PRI) )
		{
			return PRI;
		}
	}

	return None;
}

/**
 * View next active player in PRIArray.
 * @param dir is the direction to go in the array
 */
function ViewAPlayer(int dir)
{
	local PlayerReplicationInfo PRI;

	PRI = GetNextViewablePlayer(dir);

	if ( PRI != None )
	{
		SetViewTarget(PRI);
	}
}

unreliable server function ServerViewSelf(optional ViewTargetTransitionParams TransitionParams)
{
	if (IsSpectating())
	{
		ResetCameraMode();
		SetViewTarget( Self, TransitionParams );
		ClientSetViewTarget( Self, TransitionParams );
	}
}


state Spectating extends BaseSpectating
{
	ignores RestartLevel, Suicide, ThrowWeapon, NotifyPhysicsVolumeChange, NotifyHeadVolumeChange;

	exec function StartFire( optional byte FireModeNum )
	{
		ServerViewNextPlayer();
	}

	// Return to spectator's own camera.
	exec function StartAltFire( optional byte FireModeNum )
	{
		ResetCameraMode();
		ServerViewSelf();
	}

	event BeginState(Name PreviousStateName)
	{
		if ( Pawn != None )
		{
			SetLocation(Pawn.Location);
			UnPossess();
		}
		bCollideWorld = true;
	}

	event EndState(Name NextStateName)
	{
		if ( PlayerReplicationInfo != None )
		{
			if ( PlayerReplicationInfo.bOnlySpectator )
			{
				`log("WARNING - Spectator only player leaving spectating state to go to "$NextStateName);
			}
			PlayerReplicationInfo.bIsSpectator = false;
		}

		bCollideWorld = false;
	}
}

auto state PlayerWaiting extends BaseSpectating
{
ignores SeePlayer, HearNoise, NotifyBump, TakeDamage, PhysicsVolumeChange, NextWeapon, PrevWeapon, SwitchToBestWeapon;

	exec function Jump();
	exec function Suicide();

	reliable server function ServerSuicide();

	reliable server function ServerChangeTeam( int N )
	{
		WorldInfo.Game.ChangeTeam(self, N, true);
	}

	reliable server function ServerRestartPlayer()
	{

		if ( WorldInfo.TimeSeconds < WaitDelay )
			return;
		if ( WorldInfo.NetMode == NM_Client )
			return;
		if ( WorldInfo.Game.bWaitingToStartMatch )
			PlayerReplicationInfo.bReadyToPlay = true;
		else
			WorldInfo.Game.RestartPlayer(self);
	}

	exec function StartFire( optional byte FireModeNum )
	{
		ServerReStartPlayer();
	}

	event EndState(Name NextStateName)
	{
		if ( PlayerReplicationInfo != None )
		{
			PlayerReplicationInfo.SetWaitingPlayer(false);
		}
		bCollideWorld = false;
	}

	// @note: this must be simulated to execute on the client because at the time the initial state is entered, RemoteRole has not been
	// set yet and so only simulated functions will be executed
	simulated event BeginState(Name PreviousStateName)
	{
		if ( PlayerReplicationInfo != None )
		{
			PlayerReplicationInfo.SetWaitingPlayer(true);
		}
		bCollideWorld = true;
	}
}

state WaitingForPawn extends BaseSpectating
{
	ignores SeePlayer, HearNoise, KilledBy;

	exec function StartFire( optional byte FireModeNum )
	{
		AskForPawn();
	}

	reliable client function ClientGotoState(name NewState, optional name NewLabel)
	{
		if (NewState == 'RoundEnded')
		{
			Global.ClientGotoState(NewState, NewLabel);
		}
	}

	unreliable client function LongClientAdjustPosition
	(
		float TimeStamp,
		name newState,
		EPhysics newPhysics,
		float NewLocX,
		float NewLocY,
		float NewLocZ,
		float NewVelX,
		float NewVelY,
		float NewVelZ,
		Actor NewBase,
		float NewFloorX,
		float NewFloorY,
		float NewFloorZ
	)
	{
		if ( newState == 'RoundEnded' )
			GotoState(newState);
	}

	event PlayerTick(float DeltaTime)
	{
		Global.PlayerTick(DeltaTime);

		if ( Pawn != None )
		{
			Pawn.Controller = self;
			Pawn.BecomeViewTarget(self);
			ClientRestart(Pawn);
		}
		else if ( !IsTimerActive() || GetTimerCount() > 1.f )
		{
			SetTimer(0.2,true);
			AskForPawn();
		}
	}

	function ReplicateMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
	{
		ProcessMove(DeltaTime, NewAccel, DoubleClickMove, DeltaRot);
		// do not actually call ServerSetSpectatorLocation() as the server is not in this state and so won't accept it anyway
		// something is wrong if we're in this state for an extended period of time, so the fact that
		// the server side spectator location is not being updated should not be relevant
	}

	event Timer()
	{
		AskForPawn();
	}

	event BeginState(Name PreviousStateName)
	{
		SetTimer(0.2, true);
		AskForPawn();
	}

	event EndState(Name NextStateName)
	{
		ResetCameraMode();
		SetTimer(0.0, false);
	}
}

state RoundEnded
{
ignores SeePlayer, HearNoise, KilledBy, NotifyBump, HitWall, NotifyHeadVolumeChange, NotifyPhysicsVolumeChange, Falling, TakeDamage, Suicide;

	reliable server function ServerReStartPlayer()
	{
	}

	function bool IsSpectating()
	{
		return true;
	}

	exec function ThrowWeapon() {}
	exec function Use() {}

	event Possess(Pawn aPawn, bool bVehicleTransition)
	{
		Global.Possess(aPawn, bVehicleTransition);

		if (Pawn != None)
			Pawn.TurnOff();
	}

	reliable server function ServerReStartGame()
	{
		if (WorldInfo.Game.PlayerCanRestartGame(self))
		{
			WorldInfo.Game.ResetLevel();
		}
	}

	exec function StartFire( optional byte FireModeNum )
	{
		if ( Role < ROLE_Authority)
			return;
		if ( !bFrozen )
			ServerReStartGame();
		else if ( !IsTimerActive() )
			SetTimer(1.5, false);
	}

	function PlayerMove(float DeltaTime)
	{
		local vector X,Y,Z;
		local Rotator DeltaRot, ViewRotation;

		GetAxes(Rotation,X,Y,Z);
		// Update view rotation.
		ViewRotation = Rotation;
		// Calculate Delta to be applied on ViewRotation
		DeltaRot.Yaw	= PlayerInput.aTurn;
		DeltaRot.Pitch	= PlayerInput.aLookUp;
		ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot );
		SetRotation(ViewRotation);

		ViewShake(DeltaTime);

		if ( Role < ROLE_Authority ) // then save this move and replicate it
			ReplicateMove(DeltaTime, vect(0,0,0), DCLICK_None, rot(0,0,0));
		else
			ProcessMove(DeltaTime, vect(0,0,0), DCLICK_None, rot(0,0,0));
		bPressedJump = false;
	}

	unreliable server function ServerMove
	(
		float TimeStamp,
		vector InAccel,
		vector ClientLoc,
		byte NewFlags,
		byte ClientRoll,
`if(`__TW_)
	    int		View,
	    optional int FreeAimRot
`else
		int View
`endif
	)
	{
	Global.ServerMove(	TimeStamp,
							InAccel,
							ClientLoc,
							NewFlags,
							ClientRoll,
							//epic superville: Cleaner compression with no roundoff error
							((Rotation.Yaw & 65535) << 16) + (Rotation.Pitch & 65535)
						);

	}

	function FindGoodView()
	{
		local rotator GoodRotation;

		GoodRotation = Rotation;
		GetViewTarget().FindGoodEndView(self, GoodRotation);
		SetRotation(GoodRotation);
	}

	event Timer()
	{
		bFrozen = false;
	}

	unreliable client function LongClientAdjustPosition
	(
		float TimeStamp,
		name newState,
		EPhysics newPhysics,
		float NewLocX,
		float NewLocY,
		float NewLocZ,
		float NewVelX,
		float NewVelY,
		float NewVelZ,
		Actor NewBase,
		float NewFloorX,
		float NewFloorY,
		float NewFloorZ
	)
	{
	}

	event BeginState(Name PreviousStateName)
	{
		local Pawn P;

		FOVAngle = DesiredFOV;
		bFire = 0;

		if( Pawn != None )
		{
			Pawn.TurnOff();
			StopFiring();
		}

		if( myHUD != None )
		{
			myHUD.SetShowScores(TRUE);
		}

		bFrozen = TRUE;
		FindGoodView();
		SetTimer(5, FALSE);

		ForEach DynamicActors(class'Pawn', P)
		{
			P.TurnOff();
		}
	}

	event EndState(name NextStateName)
	{
		if (myHUD != None)
		{
			myHUD.SetShowScores(false);
		}
	}

Begin:
}

state Dead
{
	ignores SeePlayer, HearNoise, KilledBy, NextWeapon, PrevWeapon;

	simulated event ReplicatedEvent(name VarName)
	{
		// if we got a Pawn, get into the correct control state
		// probably should be in global ReplicatedEvent() but minimizing risk here
		if (VarName == nameof(Pawn) && Pawn != None && Pawn != AcknowledgedPawn)
		{
			ClientRestart(Pawn);
		}
		Global.ReplicatedEvent(VarName);
	}

	exec function ThrowWeapon()
	{
		//clientmessage("Throwweapon while dead, pawn "$Pawn$" health "$Pawn.health);
	}

	function bool IsDead()
	{
		return true;
	}

	reliable server function ServerReStartPlayer()
	{
		if ( !WorldInfo.Game.PlayerCanRestart( Self ) )
			return;

		super.ServerRestartPlayer();
	}

	exec function StartFire( optional byte FireModeNum )
	{
		if ( bFrozen )
		{
			if ( !IsTimerActive() || GetTimerCount() > MinRespawnDelay )
				bFrozen = false;
			return;
		}

		ServerReStartPlayer();
	}

	exec function Use()
	{
		StartFire(0);
	}

	exec function Jump()
	{
		StartFire(0);
	}

	unreliable server function ServerMove
	(
		float TimeStamp,
		vector Accel,
		vector ClientLoc,
		byte NewFlags,
		byte ClientRoll,
`if(`__TW_)
	    int		View,
	    optional int FreeAimRot
`else
		int View
`endif
	)
	{
		Global.ServerMove(
					TimeStamp,
					Accel,
					ClientLoc,
					0,
					ClientRoll,
					View);
	}

	function PlayerMove(float DeltaTime)
	{
		local vector X,Y,Z;
		local rotator DeltaRot, ViewRotation;

		if ( !bFrozen )
		{
			if ( bPressedJump )
			{
				StartFire( 0 );
				bPressedJump = false;
			}
			GetAxes(Rotation,X,Y,Z);
			// Update view rotation.
			ViewRotation = Rotation;
			// Calculate Delta to be applied on ViewRotation
			DeltaRot.Yaw	= PlayerInput.aTurn;
			DeltaRot.Pitch	= PlayerInput.aLookUp;
			ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot );
			SetRotation(ViewRotation);
			if ( Role < ROLE_Authority ) // then save this move and replicate it
					ReplicateMove(DeltaTime, vect(0,0,0), DCLICK_None, rot(0,0,0));
		}
		else if ( !IsTimerActive() || GetTimerCount() > MinRespawnDelay )
		{
			bFrozen = false;
		}

		ViewShake(DeltaTime);
	}

	function FindGoodView()
	{
		local vector cameraLoc;
		local rotator cameraRot, ViewRotation;
		local int tries, besttry;
		local float bestdist, newdist;
		local int startYaw;
		local Actor TheViewTarget;

		ViewRotation = Rotation;
		ViewRotation.Pitch = 56000;
		tries = 0;
		besttry = 0;
		bestdist = 0.0;
		startYaw = ViewRotation.Yaw;
		TheViewTarget = GetViewTarget();

		for (tries=0; tries<16; tries++)
		{
			cameraLoc = TheViewTarget.Location;
			SetRotation(ViewRotation);
			GetPlayerViewPoint( cameraLoc, cameraRot );
			newdist = VSize(cameraLoc - TheViewTarget.Location);
			if (newdist > bestdist)
			{
				bestdist = newdist;
				besttry = tries;
			}
			ViewRotation.Yaw += 4096;
		}

		ViewRotation.Yaw = startYaw + besttry * 4096;
		SetRotation(ViewRotation);
	}

	event Timer()
	{
		if (!bFrozen)
			return;

		bFrozen = false;
		bPressedJump = false;
	}

	event BeginState(Name PreviousStateName)
	{
		if ( (Pawn != None) && (Pawn.Controller == self) )
			Pawn.Controller = None;
		Pawn = None;
		FOVAngle = DesiredFOV;
		Enemy = None;
		bFrozen = true;
		bPressedJump = false;
		FindGoodView();
	    SetTimer(MinRespawnDelay, false);
		CleanOutSavedMoves();
	}

	event EndState(Name NextStateName)
	{
		CleanOutSavedMoves();
		Velocity = vect(0,0,0);
		Acceleration = vect(0,0,0);
	    if ( !PlayerReplicationInfo.bOutOfLives )
			ResetCameraMode();
		bPressedJump = false;
	    if ( myHUD != None )
		{
			myHUD.SetShowScores(false);
		}
	}

Begin:
	if ( LocalPlayer(Player) != None )
	{
		if (myHUD != None)
		{
			myHUD.PlayerOwnerDied();
		}
	}
}

function bool CanRestartPlayer()
{
	return PlayerReplicationInfo != None && !PlayerReplicationInfo.bOnlySpectator && HasClientLoadedCurrentWorld() && PendingSwapConnection == None;
}

/**
 * Hook called from HUD actor. Gives access to HUD and Canvas
 */
function DrawHUD( HUD H )
{
	if ( Pawn != None )
	{
    	Pawn.DrawHUD( H );
	}

	if ( PlayerInput != None )
	{
		PlayerInput.DrawHUD( H );
	}
}

/**
 * Get a chance to adjust the viewport size before the HUD gets rendered.
 * e.g. This allows you to render HUD elements outside the cinematic black bars
 *
 * X,Y,SizeX,SizeY - these are already set to the viewport size. Adjust as desired.
 */
event AdjustHUDRenderSize(out int X, out int Y, out int SizeX, out int SizeY, const int FullScreenSizeX, const int FullScreenSizeY)
{
	local LocalPlayer LP;

`if(`__TW_)
	if ( MyHUD != none && MyHUD.bRenderFullScreen )
`else
	if( MyHUD.bRenderFullScreen )
`endif
	{
		// Use the full render target (ignores splitscreen quadrants and black bars in cinematic mode)
		X = 0;
		Y = 0;
		SizeX = FullScreenSizeX;
		SizeY = FullScreenSizeY;
	}
`if(`__TW_)
	else if( MyHUD == none || !MyHUD.bScaleCanvasForCinematicMode )
`else
	else if ( !MyHUD.bScaleCanvasForCinematicMode )
`endif
	{
		// Set the canvas size back to the full extents defined by the splitscreen mode, ignoring the scaling due to the cinematic black bars
		LP = LocalPlayer(Player);
		if ( LP != None && LP.ViewportClient != None )
		{
			X = LP.Origin.X * FullScreenSizeX;
			Y = LP.Origin.Y * FullScreenSizeY;
			SizeX = LP.Size.X * FullScreenSizeX;
			SizeY = LP.Size.Y * FullScreenSizeY;
		}
	}
}

/* epic ===============================================
 * ::OnToggleInput
 *
 * Looks at the activated input from the SeqAct_ToggleInput
 * op and sets bPlayerInputEnabled accordingly.
 *
 * =====================================================
 */

function OnToggleInput(SeqAct_ToggleInput inAction)
{
	local bool bNewValue;

	if (Role < ROLE_Authority)
	{
		`Warn("Not supported on client");
		return;
	}

	if( inAction.InputLinks[0].bHasImpulse )
	{
		if( inAction.bToggleMovement )
		{
			IgnoreMoveInput( FALSE );
			ClientIgnoreMoveInput(false);
		}
		if( inAction.bToggleTurning )
		{
			IgnoreLookInput( FALSE );
			ClientIgnoreLookInput(false);
		}
	}
	else
	if( inAction.InputLinks[1].bHasImpulse )
	{
		if( inAction.bToggleMovement )
		{
			IgnoreMoveInput( TRUE );
			ClientIgnoreMoveInput(true);
		}
		if( inAction.bToggleTurning )
		{
			IgnoreLookInput( TRUE );
			ClientIgnoreLookInput(true);
		}
	}
	else
	if( inAction.InputLinks[2].bHasImpulse )
	{
		if( inAction.bToggleMovement )
		{
			bNewValue = !IsMoveInputIgnored();
			IgnoreMoveInput(bNewValue);
			ClientIgnoreMoveInput(bNewValue);
		}
		if( inAction.bToggleTurning )
		{
			bNewValue = !IsLookInputIgnored();
			IgnoreLookInput(bNewValue);
			ClientIgnoreLookInput(bNewValue);
		}
	}
}

/** calls IgnoreMoveInput on client */
client reliable function ClientIgnoreMoveInput(bool bIgnore)
{
	IgnoreMoveInput(bIgnore);
}
/** calls IgnoreLookInput on client */
client reliable function ClientIgnoreLookInput(bool bIgnore)
{
	IgnoreLookInput(bIgnore);
}

/**
 * list important PlayerController variables on canvas.  HUD will call DisplayDebug() on the current ViewTarget when
 * the ShowDebug exec is used
 *
 * @param	HUD		- HUD with canvas to draw on
 * @input	out_YL		- Height of the current font
 * @input	out_YPos	- Y position on Canvas. out_YPos += out_YL, gives position to draw text for next debug line.
 */
simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
{
	super.DisplayDebug(HUD, out_YL, out_YPos);

	if (HUD.ShouldDisplayDebug('camera'))
	{
		if( PlayerCamera != None )
		{
			PlayerCamera.DisplayDebug( HUD, out_YL, out_YPos );
		}
		else
		{
			HUD.Canvas.SetDrawColor(255,0,0);
			HUD.Canvas.DrawText("NO CAMERA");
			out_YPos += out_YL;
			HUD.Canvas.SetPos(4, out_YPos);
		}
	}
	if ( HUD.ShouldDisplayDebug('input') )
	{
		HUD.Canvas.SetDrawColor(255,0,0);
		HUD.Canvas.DrawText("Input ignoremove "$bIgnoreMoveInput$" ignore look "$bIgnoreLookInput$" aForward "$PlayerInput.aForward);
		out_YPos += out_YL;
		HUD.Canvas.SetPos(4, out_YPos);
	}
}

/* epic ===============================================
* ::OnDrawText
*
* Displays text according to parameters set in inAction
*
* =====================================================
*/
function OnDrawText(SeqAct_DrawText inAction)
{
	if( inAction.InputLinks[0].bHasImpulse )
	{
		ClientDrawKismetText(inAction.DrawTextInfo, inAction.DisplayTimeSeconds);
	}
	else
	{
		ClientClearKismetText(inAction.DrawTextInfo.MessageOffset);
	}
}


/** Start drawing a kismet message on the HUD */
reliable client final function ClientDrawKismetText(KismetDrawTextInfo DrawTextInfo, float DisplayTime)
{
    if ( !bShowKismetDrawText )
    {
	return;
    }

	if ( DisplayTime > 0 )
	{
		DrawTextInfo.MessageEndTime = WorldInfo.TimeSeconds + DisplayTime;
	}
	else
	{
		DrawTextInfo.MessageEndTime = -1;
	}

	myHUD.KismetTextInfo.AddItem(DrawTextInfo);
}

/** Stop drawing a kismet message on the HUD */
reliable client final function ClientClearKismetText(Vector2D MessageOffset)
{
	local int RemoveIdx;

	RemoveIdx = myHUD.KismetTextInfo.Find('MessageOffset', MessageOffset);

	if( RemoveIdx != INDEX_NONE )
	{
		myHUD.KismetTextInfo.Remove(RemoveIdx, 1);
	}
}


/* epic ===============================================
* ::OnSetCameraTarget
*
* Sets the specified view target.
*
* =====================================================
*/
simulated function OnSetCameraTarget(SeqAct_SetCameraTarget inAction)
{
	local Actor	RealCameraTarget;

	RealCameraTarget = inAction.CameraTarget;
	if (RealCameraTarget == None)
	{
		RealCameraTarget = (Pawn != None) ? Pawn : self;
	}
	// If we're asked to view a Controller, set its Pawn as out view target instead.
	else if (RealCameraTarget.IsA('Controller'))
	{
		RealCameraTarget = Controller(RealCameraTarget).Pawn;
	}

	SetViewTarget( RealCameraTarget, inAction.TransitionParams );
}


simulated function OnToggleHUD(SeqAct_ToggleHUD inAction)
{
	if (myHUD != None)
	{
		if (inAction.InputLinks[0].bHasImpulse)
		{
			myHUD.bShowHUD = true;
		}
		else
		if (inAction.InputLinks[1].bHasImpulse)
		{
			myHUD.bShowHUD = false;
		}
		else
		if (inAction.InputLinks[2].bHasImpulse)
		{
			myHUD.bShowHUD = !myHUD.bShowHUD;
		}
	}
}

/**
 * Attempts to match the name passed in to a SeqEvent_Console
 * object and then activate it.
 *
 * @param		eventName - name of the event to cause
 */
unreliable server function ServerCauseEvent(Name EventName)
{
	local array<SequenceObject> AllConsoleEvents;
	local SeqEvent_Console ConsoleEvt;
	local Sequence GameSeq;
	local int Idx;
	local bool bFoundEvt;

//@TWI_BEGIN - Don't allow ce benchmark on shipping builds
`if(`isdefined(ShippingPC))
	if (EventName == 'Benchmark')
	{
		return;
	}
`endif
//@TWI_END

	// Get the gameplay sequence.
	GameSeq = WorldInfo.GetGameSequence();
	if ( (GameSeq != None) && (EventName != '') )
	{
		// Find all SeqEvent_Console objects anywhere.
		GameSeq.FindSeqObjectsByClass(class'SeqEvent_Console', TRUE, AllConsoleEvents);

		// Iterate over them, seeing if the name is the one we typed in.
		for( Idx=0; Idx < AllConsoleEvents.Length; Idx++ )
		{
			ConsoleEvt = SeqEvent_Console(AllConsoleEvents[Idx]);
			if (ConsoleEvt != None &&
				EventName == ConsoleEvt.ConsoleEventName)
			{
				bFoundEvt = TRUE;
				// activate the vent
				ConsoleEvt.CheckActivate(self, Pawn);
			}
		}
	}
	if (!bFoundEvt)
	{
		ListConsoleEvents();
	}
}

exec function CauseEvent(optional Name EventName)
{
	ServerCauseEvent(EventName);
}

/**
 * Shortcut version for LDs who get tired of typing 'CauseEvent' all day. :-)
 */
exec function CE(optional Name EventName)
{
	ServerCauseEvent(EventName);
}

/**
 * Lists all console events to the HUD.
 */
exec function ListConsoleEvents()
{
	local array<SequenceObject> ConsoleEvents;
	local SeqEvent_Console ConsoleEvt;
	local Sequence GameSeq;
	local int Idx;

`if(`__TW_)
`if(`notdefined(ShippingPC))
	GameSeq = WorldInfo.GetGameSequence();
	if (GameSeq != None)
	{
		`log("Console events:");
		ClientMessage("Console events:",,15.f);
		GameSeq.FindSeqObjectsByClass(class'SeqEvent_Console',TRUE,ConsoleEvents);
		for (Idx = 0; Idx < ConsoleEvents.Length; Idx++)
		{
			ConsoleEvt = SeqEvent_Console(ConsoleEvents[Idx]);
			if (ConsoleEvt != None &&
				ConsoleEvt.bEnabled)
			{
				`log("-"@ConsoleEvt.ConsoleEventName@ConsoleEvt.EventDesc);
				ClientMessage("-"@ConsoleEvt.ConsoleEventName@ConsoleEvt.EventDesc,,15.f);
			}
		}
	}
`endif
`else
	GameSeq = WorldInfo.GetGameSequence();
	if (GameSeq != None)
	{
		`log("Console events:");
		ClientMessage("Console events:",,15.f);
		GameSeq.FindSeqObjectsByClass(class'SeqEvent_Console',TRUE,ConsoleEvents);
		for (Idx = 0; Idx < ConsoleEvents.Length; Idx++)
		{
			ConsoleEvt = SeqEvent_Console(ConsoleEvents[Idx]);
			if (ConsoleEvt != None &&
				ConsoleEvt.bEnabled)
			{
				`log("-"@ConsoleEvt.ConsoleEventName@ConsoleEvt.EventDesc);
				ClientMessage("-"@ConsoleEvt.ConsoleEventName@ConsoleEvt.EventDesc,,15.f);
			}
		}
	}
`endif
}

exec function ListCE()
{
	ListConsoleEvents();
}

/** triggers a SeqEvent_RemoteEvent instead of a console event; LDs specifically requested separate commands */
exec function RemoteEvent(optional name EventName)
{
	ServerRemoteEvent(EventName);
}
exec function RE(optional name EventName)
{
	ServerRemoteEvent(EventName);
}
unreliable server function ServerRemoteEvent(name EventName)
{
	local array<SequenceObject> AllRemoteEvents;
	local SeqEvent_RemoteEvent RemoteEvt;
	local Sequence GameSeq;
	local int Idx;
	local bool bFoundEvt;

	// Get the gameplay sequence.
	GameSeq = WorldInfo.GetGameSequence();
	if (GameSeq != None)
	{
		// Find all SeqEvent_Console objects anywhere.
		GameSeq.FindSeqObjectsByClass(class'SeqEvent_RemoteEvent', true, AllRemoteEvents);

		if (EventName != '')
		{
			// Iterate over them, seeing if the name is the one we typed in.
			for (Idx = 0; Idx < AllRemoteEvents.length; Idx++)
			{
				RemoteEvt = SeqEvent_RemoteEvent(AllRemoteEvents[Idx]);
				if (RemoteEvt != None && EventName == RemoteEvt.EventName)
				{
					bFoundEvt = true;
					// activate the vent
					RemoteEvt.CheckActivate(self, Pawn);
				}
			}
		}
	}
`if(`__TW_)
`if(`notdefined(ShippingPC))
	if( !bFoundEvt && !class'WorldInfo'.Static.IsConsoleBuild() && !class'WorldInfo'.Static.IsConsoleDedicatedServer() )
	{
		`log("Remote events:");
		ClientMessage("Remote events:",, 15.0);
		for (Idx = 0; Idx < AllRemoteEvents.Length; Idx++)
		{
			RemoteEvt = SeqEvent_RemoteEvent(AllRemoteEvents[Idx]);
			if (RemoteEvt != None && RemoteEvt.bEnabled)
			{
				`log("-" @ RemoteEvt.EventName);
				ClientMessage("-" @ RemoteEvt.EventName,, 15.0);
			}
		}
	}
`endif
`else
	if( !bFoundEvt)
	{
		`log("Remote events:");
		ClientMessage("Remote events:",, 15.0);
		for (Idx = 0; Idx < AllRemoteEvents.Length; Idx++)
		{
			RemoteEvt = SeqEvent_RemoteEvent(AllRemoteEvents[Idx]);
			if (RemoteEvt != None && RemoteEvt.bEnabled)
			{
				`log("-" @ RemoteEvt.EventName);
				ClientMessage("-" @ RemoteEvt.EventName,, 15.0);
			}
		}
	}
`endif
}

exec function ShowPlayerState()
{
	`log("Dumping state stack for" @ Self);
	DumpStateStack();
}


exec function ShowGameState()
{
	if ( WorldInfo.Game != None )
	{
		`log(`location$": Dumping state stack for" @ WorldInfo.Game);
		WorldInfo.Game.DumpStateStack();
	}
	else
	{
		`log(`location$": No GameInfo found!");
	}
}

/**
 * Notification from pawn that it has received damage
 * via TakeDamage().
 */
function NotifyTakeHit(Controller InstigatedBy, vector HitLocation, int Damage,
	class<DamageType> damageType, vector Momentum)
{
	Super.NotifyTakeHit(InstigatedBy,HitLocation,Damage,damageType,Momentum);

	// Play waveform
	ClientPlayForceFeedbackWaveform(damageType.default.DamagedFFWaveform);
}

/**
 * Kismet interface for playing/stopping force feedback.
 */
function OnForceFeedback(SeqAct_ForceFeedback Action)
{
	if (Action.InputLinks[0].bHasImpulse)
	{
		ClientPlayForceFeedbackWaveform(Action.FFWaveform);
	}
	else if (Action.InputLinks[1].bHasImpulse)
	{
		ClientStopForceFeedbackWaveform(Action.FFWaveform);
	}
}

/** This will take an AnimNotify_Rumble and then grab out the correct waveform to be played **/
event PlayRumble( const AnimNotify_Rumble TheAnimNotify )
{
	if( TheAnimNotify.PredefinedWaveForm != none )
	{
		ClientPlayForceFeedbackWaveform( TheAnimNotify.PredefinedWaveForm.default.TheWaveForm );
	}
	else
	{
		ClientPlayForceFeedbackWaveform( TheAnimNotify.WaveForm );
	}
}


/**
 * Tells the client to play a waveform for the specified damage type
 *
 * @param FFWaveform The forcefeedback waveform to play
 * @param FFWaveformInstigator the actor causing the waveform to play
 */
reliable client event ClientPlayForceFeedbackWaveform(ForceFeedbackWaveform FFWaveform,optional Actor FFWaveformInstigator)
{
	if (PlayerInput != None && !PlayerInput.bUsingGamepad && !WorldInfo.IsConsoleBuild(CONSOLE_Any))
	{
		return; // don't play forcefeedback if gamepad isn't being used
	}

	if( ForceFeedbackManager != None && PlayerReplicationInfo != None && IsForceFeedbackAllowed() )
	{
		ForceFeedbackManager.PlayForceFeedbackWaveform(FFWaveform,FFWaveformInstigator);
	}
}

/**
 * Tells the client to stop any waveform that is playing. Note if the optional
 * parameter is passed in, then the waveform is only stopped if it matches
 *
 * @param FFWaveform The forcefeedback waveform to stop
 */
reliable client final event ClientStopForceFeedbackWaveform(optional ForceFeedbackWaveform FFWaveform)
{
	if( ForceFeedbackManager != None )
	{
		ForceFeedbackManager.StopForceFeedbackWaveform(FFWaveform);
	}
}

/**
 * @return	TRUE if starting a force feedback waveform is allowed;  child classes should override this method to e.g. temporarily disable
 * 			force feedback
 */
simulated function bool IsForceFeedbackAllowed()
{
	return ForceFeedbackManager != None && ForceFeedbackManager.bAllowsForceFeedback;
}

/**
 * Handles switching the player in/out of cinematic mode.
 */
function OnToggleCinematicMode(SeqAct_ToggleCinematicMode Action)
{
	local bool bNewCinematicMode;

	if (Role < ROLE_Authority)
	{
		`Warn("Not supported on client");
		return;
	}

	if (Action.InputLinks[0].bHasImpulse)
	{
		bNewCinematicMode = TRUE;
	}
	else if (Action.InputLinks[1].bHasImpulse)
	{
		bNewCinematicMode = FALSE;
	}
	else if (Action.InputLinks[2].bHasImpulse)
	{
		bNewCinematicMode = !bCinematicMode;
	}

	SetCinematicMode(bNewCinematicMode, Action.bHidePlayer, Action.bHideHUD, Action.bDisableMovement, Action.bDisableTurning, Action.bDisableInput, Action.bAllowDofChanges);
}

/**
 * Server/SP only function for changing whether the player is in cinematic mode.  Updates values of various state variables, then replicates the call to the client
 * to sync the current cinematic mode.
 *
 * @param	bInCinematicMode	specify TRUE if the player is entering cinematic mode; FALSE if the player is leaving cinematic mode.
 * @param	bHidePlayer			specify TRUE to hide the player's pawn (only relevant if bInCinematicMode is TRUE)
 * @param	bAffectsHUD			specify TRUE if we should show/hide the HUD to match the value of bCinematicMode
 * @param	bAffectsMovement	specify TRUE to disable movement in cinematic mode, enable it when leaving
 * @param	bAffectsTurning		specify TRUE to disable turning in cinematic mode or enable it when leaving
 * @param	bAffectsButtons		specify TRUE to disable button input in cinematic mode or enable it when leaving.
 * @param	bAffectsDof			specify TRUE to apply depth of field changes in cinematic mode // @TWI_BEGIN
 */
function SetCinematicMode( bool bInCinematicMode, bool bHidePlayer, bool bAffectsHUD, bool bAffectsMovement, bool bAffectsTurning, bool bAffectsButtons, optional bool bAffectsDof = true)
{
	local bool bAdjustMoveInput, bAdjustLookInput;

	bCinematicMode = bInCinematicMode;

	// if now in cinematic mode
	if( bCinematicMode )
	{
		// hide the player
		if (Pawn != None && bHidePlayer)
		{
			Pawn.SetHidden(True);
		}
	}
	else
	{
		if( Pawn != None )
		{
			Pawn.SetHidden(False);
		}
	}

	bAdjustMoveInput = bAffectsMovement && (bCinematicMode != bCinemaDisableInputMove);
	bAdjustLookInput = bAffectsTurning && (bCinematicMode != bCinemaDisableInputLook);
	if ( bAdjustMoveInput )
	{
		IgnoreMoveInput(bCinematicMode);
		bCinemaDisableInputMove = bCinematicMode;
	}
	if ( bAdjustLookInput )
	{
		IgnoreLookInput(bCinematicMode);
		bCinemaDisableInputLook = bCinematicMode;
	}

	ClientSetCinematicMode(bCinematicMode, bAdjustMoveInput, bAdjustLookInput, bAffectsHUD, bAffectsDof);
}

/** called by the server to synchronize cinematic transitions with the client */
reliable client function ClientSetCinematicMode(bool bInCinematicMode, bool bAffectsMovement, bool bAffectsTurning, bool bAffectsHUD, bool bAffectsDof)
{
	bCinematicMode = bInCinematicMode;

	// if there's a hud, set whether it should be shown or not
	if ( (myHUD != None) && bAffectsHUD )
	{
		myHUD.bShowHUD = !bCinematicMode;
	}

	if (bAffectsMovement)
	{
		IgnoreMoveInput(bCinematicMode);
	}
	if (bAffectsTurning)
	{
		IgnoreLookInput(bCinematicMode);
	}
}

/** Toggles move input. FALSE means movement input is cleared. */
function IgnoreMoveInput( bool bNewMoveInput )
{
	bIgnoreMoveInput = Max( bIgnoreMoveInput + (bNewMoveInput ? +1 : -1), 0 );
	//`Log("IgnoreMove: " $ bIgnoreMoveInput);
}


/** return TRUE if movement input is ignored. */
event bool IsMoveInputIgnored()
{
	return (bIgnoreMoveInput > 0);
}


/** Toggles look input. FALSE means look input is cleared. */
function IgnoreLookInput( bool bNewLookInput )
{
	bIgnoreLookInput = Max( bIgnoreLookInput + (bNewLookInput ? +1 : -1), 0 );
	//`Log("IgnoreLook: " $ bIgnoreLookInput);
}


/** return TRUE if look input is ignored. */
event bool IsLookInputIgnored()
{
	return (bIgnoreLookInput > 0);
}


/** reset input to defaults */
function ResetPlayerMovementInput()
{
	bIgnoreMoveInput = default.bIgnoreMoveInput;
	bIgnoreLookInput = default.bIgnoreLookInput;
}


/** Kismet hook to trigger console events */
function OnConsoleCommand( SeqAct_ConsoleCommand inAction )
{
	local string Command;

	foreach inAction.Commands(Command)
	{
		// prevent "set" commands from ever working in Kismet as they are e.g. disabled in netplay
		if (!(Left(Command, 4) ~= "set ") && !(Left(Command, 9) ~= "setnopec "))
		{
			ConsoleCommand(Command);
		}
	}
}

/** forces GC at the end of the tick on the client */
reliable client event ClientForceGarbageCollection()
{
	WorldInfo.ForceGarbageCollection();
}

final event LevelStreamingStatusChanged(LevelStreaming LevelObject, bool bNewShouldBeLoaded, bool bNewShouldBeVisible, bool bNewShouldBlockOnLoad )
{
	//`log( "LevelStreamingStatusChanged: " @ LevelObject @ bNewShouldBeLoaded @ bNewShouldBeVisible @ bNewShouldBeVisible );
	ClientUpdateLevelStreamingStatus(LevelObject.PackageName,bNewShouldBeLoaded,bNewShouldBeVisible,bNewShouldBlockOnLoad);
}

native reliable client function ClientUpdateLevelStreamingStatus(Name PackageName, bool bNewShouldBeLoaded, bool bNewShouldBeVisible, bool bNewShouldBlockOnLoad);

/** called when the client adds/removes a streamed level
 * the server will only replicate references to Actors in visible levels so that it's impossible to send references to
 * Actors the client has not initialized
 * @param PackageName the name of the package for the level whose status changed
 */
native reliable server final event ServerUpdateLevelVisibility(name PackageName, bool bIsVisible);

/** asynchronously loads the given level in preparation for a streaming map transition.
 * the server sends one function per level name since dynamic arrays can't be replicated
 * @param LevelNames the names of the level packages to load. LevelNames[0] will be the new persistent (primary) level
 * @param bFirst whether this is the first item in the list (so clear the list first)
 * @param bLast whether this is the last item in the list (so start preparing the change after receiving it)
 */
reliable client event ClientPrepareMapChange(name LevelName, bool bFirst, bool bLast)
{
	// Only call on the first local player controller to handle it being called on multiple PCs for splitscreen.
	local PlayerController PC;

	foreach LocalPlayerControllers(class'PlayerController', PC)
	{
		if( PC != self )
		{
			return;
		}
		else
		{
			break;
		}
	}

	if (bFirst)
	{
		PendingMapChangeLevelNames.length = 0;
		ClearTimer('DelayedPrepareMapChange');
	}
	PendingMapChangeLevelNames[PendingMapChangeLevelNames.length] = LevelName;
	if (bLast)
	{
		DelayedPrepareMapChange();
	}
}

/** used to wait until a map change can be prepared when one was already in progress */
function DelayedPrepareMapChange()
{
	if (WorldInfo.IsPreparingMapChange())
	{
		// we must wait for the previous one to complete
		SetTimer( 0.01, false, nameof(DelayedPrepareMapChange) );
	}
	else
	{
		WorldInfo.PrepareMapChange(PendingMapChangeLevelNames);
	}
}

/** actually performs the level transition prepared by PrepareMapChange() */
reliable client event ClientCommitMapChange()
{
	if (IsTimerActive(nameof(DelayedPrepareMapChange)))
	{
		SetTimer(0.01, false, nameof(ClientCommitMapChange));
	}
	else
	{
		if (Pawn != None)
		{
			SetViewTarget(Pawn);
		}
		else
		{
			SetViewTarget(self);
		}
		WorldInfo.CommitMapChange();
	}
}

/** tells client to cancel any pending map change */
reliable client event ClientCancelPendingMapChange()
{
	WorldInfo.CancelPendingMapChange();
}

/** tells the client to block until all pending level streaming actions are complete
 * happens at the end of the tick
 * primarily used to force update the client ASAP at join time
 */
reliable client native final event ClientFlushLevelStreaming();

/** sets bRequestedBlockOnAsyncLoading which will later bring up a loading screen and then finish any async loading in progress
 * called automatically on all clients whenever something triggers it on the server
 */
reliable client event ClientSetBlockOnAsyncLoading()
{
	WorldInfo.bRequestedBlockOnAsyncLoading = true;
}

/** used to allow clients to process a SeqAct_WaitForLevelsVisible by blocking, if required */
reliable client function ClientWaitForLevelsVisible(SeqAct_WaitForLevelsVisible InAction)
{
	InAction.CheckLevelsVisible();
}

/**
 * Force a save config on the specified class.
 */
exec function SaveClassConfig(coerce string className)
{
	local class<Object> saveClass;

	`log("SaveClassConfig:"@className);
	saveClass = class<Object>(DynamicLoadObject(className,class'class'));
	if (saveClass != None)
	{
		`log("- Saving config on:"@saveClass);
		saveClass.static.StaticSaveConfig();
	}
`if(`notdefined(FINAL_RELEASE))
	else
	{
		`log("- Failed to find class:"@className);
	}
`endif
}

/**
 * Force a save config on the specified actor.
 */
exec function SaveActorConfig(coerce Name actorName)
{
	local Actor chkActor;
	`log("SaveActorConfig:"@actorName);
	foreach AllActors(class'Actor',chkActor)
	{
		if (chkActor != None &&
			chkActor.Name == actorName)
		{
			`log("- Saving config on:"@chkActor);
			chkActor.SaveConfig();
		}
	}
}

/**
 * Returns the interaction that manages the UI system.
 */
final function UIInteraction GetUIController()
{
	local LocalPlayer LP;
	local UIInteraction Result;

	LP = LocalPlayer(Player);
	if ( LP != None && LP.ViewportClient != None )
	{
		Result = LP.ViewportClient.UIController;
	}

	return Result;
}

/**
 * Native function to determine if voice data should be received from this player.
 * Only called on the server to determine whether voice packet replication
 * should happen for the given sender.
 *
 * NOTE: This function is final because it can be called n^2 number of times
 * in a given frame, where n is the number of players. Change/overload this
 * function with caution as this can affect your network performance.
 *
 * @param Sender the player to check for mute status
 *
 * @return TRUE if this player is muted, FALSE otherwise
 */
native final function bool IsPlayerMuted(const out UniqueNetId Sender);

`if(`__TW_)
native final function bool ShouldReplicateVoicePacketFrom(const out UniqueNetId Sender);
native final function bool ShouldReplicateVoicePacketTo(const out UniqueNetId Receiver);
`endif

`if(`__TW_)
function ForceDisconnect();
`endif

/** called on client during seamless level transitions to get the list of Actors that should be moved into the new level
 * PlayerControllers, Role < ROLE_Authority Actors, and any non-Actors that are inside an Actor that is in the list
 * (i.e. Object.Outer == Actor in the list)
 * are all autmoatically moved regardless of whether they're included here
 * only dynamic (!bStatic and !bNoDelete) actors in the PersistentLevel may be moved (this includes all actors spawned during gameplay)
 * this is called for both parts of the transition because actors might change while in the middle (e.g. players might join or leave the game)
 * @see also GameInfo::GetSeamlessTravelActorList() (the function that's called on servers)
 * @param bToEntry true if we are going from old level -> entry, false if we are going from entry -> new level
 * @param ActorList (out) list of actors to maintain
 */
event GetSeamlessTravelActorList(bool bToEntry, out array<Actor> ActorList)
{
	// clear out audio pool
	HearSoundActiveComponents.length = 0;
	HearSoundPoolComponents.length = 0;

	if (myHUD != None)
	{
		ActorList[ActorList.length] = myHUD;
	}
}

/** called when seamless travelling and we are being replaced by the specified PC
 * clean up any persistent state (post process chains on LocalPlayers, for example)
 * (not called if PlayerControllerClass is the same for the from and to gametypes)
 */
function SeamlessTravelTo(PlayerController NewPC);

/** called when seamless travelling and the specified PC is being replaced by this one
 * copy over data that should persist
 * (not called if PlayerControllerClass is the same for the from and to gametypes)
 */
function SeamlessTravelFrom(PlayerController OldPC)
{
	// copy PRI data
	OldPC.PlayerReplicationInfo.Reset();
	OldPC.PlayerReplicationInfo.SeamlessTravelTo(PlayerReplicationInfo);

	// mark the old PC as not a player, so it doesn't get Logout() etc when they player represented didn't really leave
	OldPC.bIsPlayer = false;
	//@fixme: need a way to replace PRIs that doesn't cause incorrect "player left the game"/"player entered the game" messages
	OldPC.PlayerReplicationInfo.Destroy();
	OldPC.PlayerReplicationInfo = None;
}

/**
 * Looks at the current game state and uses that to set the
 * rich presence strings
 *
 * Licensees should override this in their player controller derived class
 */
reliable client function ClientSetOnlineStatus();

/**
 * Returns the player controller associated with this net id
 *
 * @param PlayerNetId the id to search for
 *
 * @return the player controller if found, otherwise none
 */
native static function PlayerController GetPlayerControllerFromNetId(UniqueNetId PlayerNetId);

/**
 * Tells the client that the server has all the information it needs and that it
 * is ok to start sending voice packets. The server will already send voice packets
 * when this function is called, since it is set server side and then forwarded
 *
 * NOTE: This is done as an RPC instead of variable replication because ordering matters
 */
reliable client function ClientVoiceHandshakeComplete()
{
	local int PeerIdx;

	bHasVoiceHandshakeCompleted = true;

	// Replicate list of connected peers for each player to the server
	for (PeerIdx=0; PeerIdx < ConnectedPeers.Length; PeerIdx++)
	{
		ServerAddPeer(ConnectedPeers[PeerIdx].PlayerId,ConnectedPeers[PeerIdx].NATType);
	}
}

/**
 * Locally mutes a remote player
 *
 * @param PlayerNetId the remote player to mute
 */
reliable client event ClientMutePlayer(UniqueNetId PlayerNetId)
{
	local LocalPlayer LocPlayer;

	// Add to the filter list on clients (used for peer to peer voice)
	if (VoicePacketFilter.Find('Uid',PlayerNetId.Uid) == INDEX_NONE)
	{
		VoicePacketFilter.AddItem(PlayerNetId);
	}

	if (VoiceInterface != None)
	{
		// Use the local player to determine the controller id
		LocPlayer = LocalPlayer(Player);
		if (LocPlayer != None)
		{
			// Have the voice subsystem mute this player
			VoiceInterface.MuteRemoteTalker(LocPlayer.ControllerId,PlayerNetId);
		}
	}
}

/**
 * Locally unmutes a remote player
 *
 * @param PlayerNetId the remote player to unmute
 */
reliable client event ClientUnmutePlayer(UniqueNetId PlayerNetId)
{
	local LocalPlayer LocPlayer;
	local int RemoveIndex;

	// It's safe to remove them from the filter list on clients (used for peer to peer voice)
	RemoveIndex = VoicePacketFilter.Find('Uid',PlayerNetId.Uid);
	if (RemoveIndex != INDEX_NONE)
	{
		VoicePacketFilter.Remove(RemoveIndex,1);
	}

	if (VoiceInterface != None)
	{
		// Use the local player to determine the controller id
		LocPlayer = LocalPlayer(Player);
		if (LocPlayer != None)
		{
			// Have the voice subsystem restore voice for this player
			VoiceInterface.UnmuteRemoteTalker(LocPlayer.ControllerId,PlayerNetId);
		}
	}
}

/**
 * Mutes a remote player on the server and then tells the client to mute
 *
 * @param PlayerNetId the remote player to mute
 */
function GameplayMutePlayer(UniqueNetId PlayerNetId)
{
	// Don't add if already muted
	if (GameplayVoiceMuteList.Find('Uid',PlayerNetId.Uid) == INDEX_NONE)
	{
		GameplayVoiceMuteList.AddItem(PlayerNetId);
	}
	// Add to the filter list too, if missing
	if (VoicePacketFilter.Find('Uid',PlayerNetId.Uid) == INDEX_NONE)
	{
		VoicePacketFilter.AddItem(PlayerNetId);
	}

	// Now process on the client needed for splitscreen net play
	ClientMutePlayer(PlayerNetId);
}

/**
 * Unmutes a remote player on the server and then tells the client to unmute
 *
 * @param PlayerNetId the remote player to unmute
 */
function GameplayUnmutePlayer(UniqueNetId PlayerNetId)
{
	local int RemoveIndex;
	local PlayerController Other;

	// Remove from the gameplay mute list
	RemoveIndex = GameplayVoiceMuteList.Find('Uid',PlayerNetId.Uid);
	if (RemoveIndex != INDEX_NONE)
	{
		GameplayVoiceMuteList.Remove(RemoveIndex,1);
	}
	// Find the muted player's player controller so it can be notified
	Other = GetPlayerControllerFromNetId(PlayerNetId);
	if (Other != None)
	{
		// If they were not explicitly muted
		if (VoiceMuteList.Find('Uid',PlayerNetId.Uid) == INDEX_NONE &&
			// and they did not explicitly mute us
			Other.VoiceMuteList.Find('Uid',PlayerReplicationInfo.UniqueId.Uid) == INDEX_NONE)
		{
			// It's safe to remove them from the filter list
			RemoveIndex = VoicePacketFilter.Find('Uid',PlayerNetId.Uid);
			if (RemoveIndex != INDEX_NONE)
			{
				VoicePacketFilter.Remove(RemoveIndex,1);
			}

			// Now process on the client
			ClientUnmutePlayer(PlayerNetId);
		}
	}
}

/**
 * Updates the server side information by adding to the mute list. Tells the
 * player controller that owns the specified net id to also mute this PC.
 *
 * @param PlayerNetId the remote player to mute
 */
//@HSL_BEGIN - JRO - 8/2/2016 - Adding the option of 1-way muting.
reliable server event ServerMutePlayer(UniqueNetId PlayerNetId, optional bool bMuteOther = true)
//@HSL_END
{
	local PlayerController Other;

	// Don't reprocess if they are already muted
	if (VoiceMuteList.Find('Uid',PlayerNetId.Uid) == INDEX_NONE)
	{
		VoiceMuteList.AddItem(PlayerNetId);
	}
	// Add them to the packet filter list if not already on it
	if (VoicePacketFilter.Find('Uid',PlayerNetId.Uid) == INDEX_NONE)
	{
		VoicePacketFilter.AddItem(PlayerNetId);
	}
	ClientMutePlayer(PlayerNetId);
	// Find the muted player's player controller so it can be notified
	Other = GetPlayerControllerFromNetId(PlayerNetId);
	if (Other != None && bMuteOther) //@HSL - JRO - 8/2/2016 - 1-way muting
	{
		// Update their packet filter list too
		if (Other.VoicePacketFilter.Find('Uid',PlayerReplicationInfo.UniqueId.Uid) == INDEX_NONE)
		{
			Other.VoicePacketFilter.AddItem(PlayerReplicationInfo.UniqueId);
		}

		// Tell the other PC to mute this one
		Other.ClientMutePlayer(PlayerReplicationInfo.UniqueId);
	}
}

/**
 * Updates the server side information by removing from the mute list. Tells the
 * player controller that owns the specified net id to also unmute this PC.
 *
 * @param PlayerNetId the remote player to unmute
 */
//@HSL_BEGIN - JRO - 8/2/2016 - Adding the option of 1-way muting.
reliable server event ServerUnmutePlayer(UniqueNetId PlayerNetId, optional bool bUnmuteOther = true)
//@HSL_END
{
	local PlayerController Other;
	local int RemoveIndex;

	RemoveIndex = VoiceMuteList.Find('Uid',PlayerNetId.Uid);
	// If the player was found, remove them from our explicit list
	if (RemoveIndex != INDEX_NONE)
	{
		VoiceMuteList.Remove(RemoveIndex,1);
	}
	//@HSL_BEGIN - JRO - 8/2/2016 - Adding the option of 1-way muting.
	if(!bUnmuteOther)
	{
		// Add them to the packet filter list if not already on it
		RemoveIndex = VoicePacketFilter.Find('Uid',PlayerNetId.Uid);
		if (RemoveIndex != INDEX_NONE)
		{
			VoicePacketFilter.Remove(RemoveIndex,1);
		}
		ClientUnmutePlayer(PlayerNetId);
	}
	//@HSL_END
	// Find the muted player's player controller so it can be notified
	Other = GetPlayerControllerFromNetId(PlayerNetId);
	if (Other != None && bUnmuteOther) //@HSL - JRO - 8/2/2016 - 1-way muting
	{
		// Make sure this player isn't muted for gameplay reasons
		if (GameplayVoiceMuteList.Find('Uid',PlayerNetId.Uid) == INDEX_NONE &&
			// And make sure they didn't mute us
			Other.VoiceMuteList.Find('Uid',PlayerReplicationInfo.UniqueId.Uid) == INDEX_NONE)
		{
			ClientUnmutePlayer(PlayerNetId);
		}
		// If the other player doesn't have this player muted
		if (Other.VoiceMuteList.Find('Uid',PlayerReplicationInfo.UniqueId.Uid) == INDEX_NONE &&
			Other.GameplayVoiceMuteList.Find('Uid',PlayerReplicationInfo.UniqueId.Uid) == INDEX_NONE)
		{
			// Remove them from the packet filter list
			RemoveIndex = VoicePacketFilter.Find('Uid',PlayerNetId.Uid);
			if (RemoveIndex != INDEX_NONE)
			{
				VoicePacketFilter.Remove(RemoveIndex,1);
			}
			// If found, remove so packets flow to that client too
			RemoveIndex = Other.VoicePacketFilter.Find('Uid',PlayerReplicationInfo.UniqueId.Uid);
			if (RemoveIndex != INDEX_NONE)
			{
				Other.VoicePacketFilter.Remove(RemoveIndex,1);
			}

			// Tell the other PC to unmute this one
			Other.ClientUnmutePlayer(PlayerReplicationInfo.UniqueId);
		}
	}
}

/** notification when a matinee director track starts or stops controlling the ViewTarget of this PlayerController */
event NotifyDirectorControl(bool bNowControlling, SeqAct_Interp CurrentMatinee)
{
	// matinee is done, make sure client syncs up viewtargets, since we were ignoring
	// ClientSetViewTarget during the matinee.
	if ( !bNowControlling && (WorldInfo.NetMode == NM_Client) && bClientSimulatingViewTarget )
	{
		ServerVerifyViewTarget();
	}
}

/**
 * This will turn the subtitles on or off depending on the value of bValue
 *
 * @param bValue  to show or not to show
 **/
native simulated exec function SetShowSubtitles( bool bValue );

/**
 * This will turn return whether the subtitles are on or off
 *
 **/
native simulated function bool IsShowingSubtitles();

reliable client event ClientWasKicked();

/**
 * Tells the client to register with arbitration. The client must notify the
 * server once that is complete or it will be kicked from the match
 */
reliable client function ClientRegisterForArbitration()
{
	if (OnlineSub != None && OnlineSub.GameInterface != None)
	{
		OnlineSub.GameInterface.AddArbitrationRegistrationCompleteDelegate(OnArbitrationRegisterComplete);
		// Kick off async arbitration registration
		OnlineSub.GameInterface.RegisterForArbitration('Game');
	}
	else
	{
		// Fake completion with the server for flow testing
		ServerRegisteredForArbitration(true);
	}
}

/**
 * Delegate that is notified when registration is complete. Forwards the call
 * to the server so that it can finalize processing
 *
 * @param SessionName the name of the session this is for
 * @param bWasSuccessful whether registration worked or not
 */
function OnArbitrationRegisterComplete(name SessionName,bool bWasSuccessful)
{
	// Clear the delegate since it isn't needed
	OnlineSub.GameInterface.ClearArbitrationRegistrationCompleteDelegate(OnArbitrationRegisterComplete);
	// Tell the server that we completed registration
	ServerRegisteredForArbitration(bWasSuccessful);
}

/**
 * Notifies the server that the arbitration registration is complete
 *
 * @param bWasSuccessful whether the registration with arbitration worked
 */
reliable server function ServerRegisteredForArbitration(bool bWasSuccessful)
{
	// Tell the game info this PC is done and whether it worked or not
	WorldInfo.Game.ProcessClientRegistrationCompletion(self,bWasSuccessful);
}

/**
 * Delegate called when the user accepts a game invite externally. This allows
 * the game code a chance to clean up before joining the game via
 * AcceptGameInvite() call.
 *
 * NOTE: There must be space for all signed in players to join the game. All
 * players must also have permission to play online too.
 *
 * @param InviteResult the search/settings for the game we're to join
 */
//@HSL_BEGIN - JRO - 3/21/2016 - PS4 Sessions
function OnGameInviteAccepted(const out OnlineGameSearchResult InviteResult, OnGameInviteAcceptedResult ResultReason)
//@HSL_END
{
	local OnlineGameSettings GameInviteSettings;

	`log("SESSIONS - OnGameInviteAccepted");

	if (OnlineSub != None && OnlineSub.GameInterface != None)
	{
		GameInviteSettings = InviteResult.GameSettings;
		if (GameInviteSettings != None)
		{
			// Make sure the new game has space
			if (InviteHasEnoughSpace(GameInviteSettings))
			{
				// Make sure everyone logged in can play online
				if (CanAllPlayersPlayOnline())
				{
					//@HSL_BEGIN - JRO - 3/21/2016 - Make sure we still have the new session info after we leave the current one
					CachedInviteResult = InviteResult;
					//@HSL_END

					if (WorldInfo.NetMode != NM_Standalone)
					{
						// Write arbitration data, if required
						if (OnlineSub.GameInterface.GetGameSettings('Game').bUsesArbitration)
						{
							// Write out our version of the scoring before leaving
							ClientWriteOnlinePlayerScores(WorldInfo.GRI.GameClass != None ? WorldInfo.GRI.GameClass.default.ArbitratedLeaderBoardId : 0);
						}
						// Set the end delegate, where we'll destroy the game and then join
						OnlineSub.GameInterface.AddEndOnlineGameCompleteDelegate(OnEndForInviteComplete);
						// Force the flush of the stats
						OnlineSub.GameInterface.EndOnlineGame('Game');
					}
					else
					{
						// Set the delegate for notification of the join completing
						OnlineSub.GameInterface.AddJoinOnlineGameCompleteDelegate(OnInviteJoinComplete);

						// We can immediately accept since there is no online game
						//@HSL_BEGIN - JRO - 3/21/2016 - Adding invite info
						if (!OnlineSub.GameInterface.AcceptGameInvite(LocalPlayer(Player).ControllerId,'Game',CachedInviteResult))
						//@HSL_END
						{
							OnlineSub.GameInterface.ClearJoinOnlineGameCompleteDelegate(OnInviteJoinComplete);
							// Do some error handling
							NotifyInviteFailed();
						}
					}
				}
				else
				{
					// Display an error message
					NotifyNotAllPlayersCanJoinInvite();
				}
			}
			else
			{
				// Display an error message
				NotifyNotEnoughSpaceInInvite();
			}
		}
		else
		{
			// Display an error message
			NotifyInviteFailed();
		}
	}
}

//@HSL_BEGIN - JRO - 6/10/2016 - Overloaded in KFPlayerController
function OnConnectionStatusChange(EOnlineServerConnectionStatus ConnectionStatus)
{
}
function OnPlayTogetherStarted()
{
}
function JoinPlayfabServer(bool bWasSuccessful, string ServerIP)
{
}
//@HSL_END

/**
 * Counts the number of local players to verify there is enough space
 *
 * @return true if there is sufficient space, false if not
 */
function bool InviteHasEnoughSpace(OnlineGameSettings InviteSettings)
{
	local int NumLocalPlayers;
	local PlayerController PC;

	foreach LocalPlayerControllers(class'PlayerController', PC)
	{
		NumLocalPlayers++;
	}
	// Invites consume private connections
	return (InviteSettings.NumOpenPrivateConnections + InviteSettings.NumOpenPublicConnections) >= NumLocalPlayers;
}

/**
 * Validates that each local player can play online
 *
 * @return true if there is sufficient space, false if not
 */
function bool CanAllPlayersPlayOnline()
{
	local PlayerController PC;
	local LocalPlayer LocPlayer;

	foreach LocalPlayerControllers(class'PlayerController', PC)
	{
		LocPlayer = LocalPlayer(PC.Player);
		if (LocPlayer != None)
		{
			// Check their login status and permissions
			if (OnlineSub.PlayerInterface.GetLoginStatus(LocPlayer.ControllerId) != LS_LoggedIn ||
//@HSL_BEGIN_XBOX
				!PC.bCanPlayOnline)
//@HSL_END_XBOX
			{
				return false;
			}
		}
		else
		{
			return false;
		}
	}
	return true;
}

/**
 * Clears all of the invite delegates
 */
function ClearInviteDelegates()
{
	// Clear the end delegate
	OnlineSub.GameInterface.ClearEndOnlineGameCompleteDelegate(OnEndForInviteComplete);
	// Clear the destroy delegate
	OnlineSub.GameInterface.ClearDestroyOnlineGameCompleteDelegate(OnDestroyForInviteComplete);
	// Set the delegate for notification of the join completing
	OnlineSub.GameInterface.ClearJoinOnlineGameCompleteDelegate(OnInviteJoinComplete);
}

/**
 * Delegate called once the destroy of an online game before accepting an invite
 * is complete. From here, the game invite can be accepted
 *
 * @param SessionName the name of the session being ended
 * @param bWasSuccessful whether the end completed ok or not
 */
function OnEndForInviteComplete(name SessionName,bool bWasSuccessful)
{
	// Set the destroy delegate so we can know when that is complete
	OnlineSub.GameInterface.AddDestroyOnlineGameCompleteDelegate(OnDestroyForInviteComplete);
	// Now we can destroy the game (completion delegate guaranteed to be called)
	OnlineSub.GameInterface.DestroyOnlineGame(SessionName);
}

/**
 * Delegate called once the destroy of an online game before accepting an invite
 * is complete. From here, the game invite can be accepted
 *
 * @param SessionName the name of the session being ended
 * @param bWasSuccessful whether the end completed ok or not
 */
function OnDestroyForInviteComplete(name SessionName,bool bWasSuccessful)
{
	`log("SESSIONS - Destroy for invite complete for session name"@SessionName@"and successful"@bWasSuccessful);

	if (bWasSuccessful)
	{
		// Set the delegate for notification of the join completing
		OnlineSub.GameInterface.AddJoinOnlineGameCompleteDelegate(OnInviteJoinComplete);
		// This will have us join async
		if (!OnlineSub.GameInterface.AcceptGameInvite(LocalPlayer(Player).ControllerId,SessionName,CachedInviteResult)) //@HSL - JRO - 3/21/2016 - PS4 Sessions - Make sure we accept the correct game!
		{
			OnlineSub.GameInterface.ClearJoinOnlineGameCompleteDelegate(OnInviteJoinComplete);
			// Do some error handling
			NotifyInviteFailed();
		}
	}
	else
	{
		// Do some error handling
		NotifyInviteFailed();
	}
}

/**
 * Allows the game to modify the URL that clients will use to connect to a server (should be called
 * by pretty much anyone who calls GetResolvedConnectString())
 */
function string ModifyClientURL(string URL)
{
	return URL;
}

/**
 * Once the join completes, use the platform specific connection information
 * to connect to it
 *
 * @param SessionName the name of the session that was joined
 * @param bWasSuccessful whether the join worked or not
 */
function OnInviteJoinComplete(name SessionName,bool bWasSuccessful)
{
	local string URL;

	if (bWasSuccessful)
	{
		if (OnlineSub != None && OnlineSub.GameInterface != None)
		{
			// Get the platform specific information
			if (OnlineSub.GameInterface.GetResolvedConnectString(SessionName,URL))
			{
				URL $= "?bIsFromInvite";

				// allow game to override
				URL = ModifyClientURL(URL);

				`Log("Resulting url is ("$URL$")");
				// Open a network connection to it
				ClientTravel(URL, TRAVEL_Absolute);
			}
		}
	}
	else
	{
		// Do some error handling
		NotifyInviteFailed();
	}
	ClearInviteDelegates();
}

/** Override to display a message to the user */
function NotifyInviteFailed(optional string LocKey = "UnableToJoinInvite")
{
	`Log("SESSIONS - Invite handling failed");
	ClearInviteDelegates();
}

/** Override to display a message to the user */
function NotifyNotAllPlayersCanJoinInvite()
{
	`Log("Not all local players have permission to join the game invite");
}

/** Override to display a message to the user */
function NotifyNotEnoughSpaceInInvite()
{
	`Log("Not enough space for all local players in the game invite");
}

/**
 * Called when an arbitrated match has ended and we need to disconnect
 */
reliable client function ClientArbitratedMatchEnded()
{
	ConsoleCommand("Disconnect");
}

/**
 * Writes the scores for all active players. Override this in your
 * playercontroller class to provide custom scoring
 *
 * @param LeaderboardId the leaderboard the scores are being written to
 */
reliable client function ClientWriteOnlinePlayerScores(int LeaderboardId)
{
	local GameReplicationInfo GRI;
	local int Index;
	local array<OnlinePlayerScore> PlayerScores;
	local UniqueNetId ZeroUniqueId;
	local bool bIsTeamGame;
	local int ScoreIndex;

	GRI = WorldInfo.GRI;
	if (GRI != None && OnlineSub != None && OnlineSub.StatsInterface != None)
	{
		// Look at the default object of the gameinfo to determine if this is a
		// team game or not
		bIsTeamGame = GRI.GameClass != None ? GRI.GameClass.default.bTeamGame : false;
		// Iterate through the players building their score data
		for (Index = 0; Index < GRI.PRIArray.Length; Index++)
		{
			if (GRI.PRIArray[Index].UniqueId != ZeroUniqueId)
			{
				if (bIsTeamGame)
				{
					// Players without teams in team games are excluded from submitted scores
					if (GRI.PRIArray[Index].Team != None)
					{
						ScoreIndex = PlayerScores.Length;
						PlayerScores.Length = ScoreIndex + 1;
						// Build the skill data for this player
						PlayerScores[ScoreIndex].PlayerId = GRI.PRIArray[Index].UniqueId;
						PlayerScores[ScoreIndex].TeamId = GRI.PRIArray[Index].Team.TeamIndex;
						PlayerScores[ScoreIndex].Score = GRI.PRIArray[Index].Team.Score;
					}
				}
				else
				{
					ScoreIndex = PlayerScores.Length;
					PlayerScores.Length = ScoreIndex + 1;
					// Build the skill data for this player
					PlayerScores[ScoreIndex].PlayerId = GRI.PRIArray[Index].UniqueId;
					PlayerScores[ScoreIndex].TeamId = Index;
					PlayerScores[ScoreIndex].Score = GRI.PRIArray[Index].Score;
				}
			}
		}
		// Now write out the scores
		OnlineSub.StatsInterface.WriteOnlinePlayerScores(PlayerReplicationInfo.SessionName,LeaderboardId,PlayerScores);
	}
}

/**
 * Tells the clients to write the stats using the specified stats object
 *
 * @param OnlineStatsWriteClass the stats class to write with
 * @param bIsIncomplete TRUE if the match wasn't finished at the time of the write
 */
reliable client function ClientWriteLeaderboardStats(class<OnlineStatsWrite> OnlineStatsWriteClass, optional bool bIsIncomplete=false);

/**
 * Sets the host's net id for handling dropped arbitrated matches
 *
 * @param InHostId the host's unique net id to report the drop for
 */
reliable client function ClientSetHostUniqueId(UniqueNetId InHostId);

/** Tells this client that it should not send voice data over the network */
reliable client function ClientStopNetworkedVoice()
{
	local LocalPlayer LocPlayer;

	LocPlayer = LocalPlayer(Player);
	if (LocPlayer != None && OnlineSub != None && OnlineSub.VoiceInterface != None)
	{
		OnlineSub.VoiceInterface.StopNetworkedVoice(LocPlayer.ControllerId);
	}
}

/** Tells this client that it should send voice data over the network */
reliable client function ClientStartNetworkedVoice()
{
	local LocalPlayer LocPlayer;

	LocPlayer = LocalPlayer(Player);
	if (LocPlayer != None && OnlineSub != None && OnlineSub.VoiceInterface != None)
	{
		OnlineSub.VoiceInterface.StartNetworkedVoice(LocPlayer.ControllerId);
	}
}

simulated function OnDestroy(SeqAct_Destroy Action)
{
	// Kismet is not allowed to disconnect players from the game
	Action.ScriptLog("Cannot use Destroy action on players");
}

`if(`notdefined(ShippingPC))
/** console control commands, useful when remote debugging so you can't touch the console the normal way */
exec function ConsoleKey(name Key)
{
	if (LocalPlayer(Player) != None)
	{
		LocalPlayer(Player).ViewportClient.ViewportConsole.InputKey(0, Key, IE_Pressed);
	}
}
exec function SendToConsole(string Command)
{
	if (LocalPlayer(Player) != None)
	{
		LocalPlayer(Player).ViewportClient.ViewportConsole.ConsoleCommand(Command);
	}
}
`endif

/**
 * Iterate through list of debug text and draw it over the associated actors in world space.
 * Also handles culling null entries, and reducing the duration for timed debug text.
 */
final simulated function DrawDebugTextList(Canvas Canvas, float RenderDelta)
{
	local vector CameraLoc, ScreenLoc, Offset, WorldTextLoc;
	local rotator CameraRot;
	local int Idx;
	if (DebugTextList.Length > 0)
	{
		GetPlayerViewPoint(CameraLoc,CameraRot);
		Canvas.SetDrawColor(255,255,255);
		for (Idx = 0; Idx < DebugTextList.Length; Idx++)
		{
			if (DebugTextList[Idx].SrcActor == None)
			{
				DebugTextList.Remove(Idx--,1);
				continue;
			}
			if (DebugTextList[Idx].TimeRemaining != -1.f)
			{
				DebugTextList[Idx].TimeRemaining -= RenderDelta;
				if (DebugTextList[Idx].TimeRemaining <= 0.f)
				{
					DebugTextList.Remove(Idx--,1);
					continue;
				}
			}

			if( DebugTextList[Idx].Font != None )
			{
				Canvas.Font = DebugTextList[Idx].Font;
			}
			else
			{
				Canvas.Font = class'Engine'.static.GetSmallFont();
			}


			if(DebugTextList[Idx].bAbsoluteLocation)
			{
				WorldTextLoc = VLerp(DebugTextList[Idx].SrcActorOffset,DebugTextList[Idx].SrcActorDesiredOffset,1.f - (DebugTextList[Idx].TimeRemaining/DebugTextList[Idx].Duration));
			}
			else
			{
				Offset = VLerp(DebugTextList[Idx].SrcActorOffset,DebugTextList[Idx].SrcActorDesiredOffset,1.f - (DebugTextList[Idx].TimeRemaining/DebugTextList[Idx].Duration));

				if( DebugTextList[Idx].bKeepAttachedToActor )
				{
					WorldTextLoc = DebugTextList[Idx].SrcActor.Location + (Offset >> CameraRot);
				}
				else
				{
					WorldTextLoc = DebugTextList[Idx].OrigActorLocation + (Offset >> CameraRot);
				}

			}

			// don't draw text behind the camera
			if ( ((WorldTextLoc - CameraLoc) dot vector(CameraRot)) > 0.f )
			{
				ScreenLoc = Canvas.Project(WorldTextLoc);
				Canvas.SetPos(ScreenLoc.X,ScreenLoc.Y);
				Canvas.DrawColor = DebugTextList[Idx].TextColor;
				Canvas.DrawText(DebugTextList[Idx].DebugText);
			}
		}
	}
}

/**
 * Add debug text for a specific actor to be displayed via DrawDebugTextList().  If the debug text is invalid then it will
 * attempt to remove any previous entries via RemoveDebugText().
 */
reliable client event AddDebugText(string DebugText,
										 optional Actor SrcActor,
										 optional float Duration = -1.f,
										 optional vector Offset,
										 optional vector DesiredOffset,
										 optional color TextColor,
										 optional bool bSkipOverwriteCheck,
										 optional bool bAbsoluteLocation,
										 optional bool bKeepAttachedToActor = TRUE,
										 optional Font InFont
										 )
{
	local int Idx;
	// set a default color
	if (TextColor.R == 0 && TextColor.G == 0 && TextColor.B == 0 && TextColor.A == 0)
	{
		TextColor.R = 255;
		TextColor.G = 255;
		TextColor.B = 255;
		TextColor.A = 255;
	}
	// and a default source actor of our pawn
	if (SrcActor != None)
	{
		if (Len(DebugText) == 0)
		{
			RemoveDebugText(SrcActor);
		}
		else
		{
			//`log("Adding debug text:"@DebugText@"for actor:"@SrcActor);
			// search for an existing entry
			if (!bSkipOverwriteCheck)
			{
				Idx = DebugTextList.Find('SrcActor',SrcActor);
				if (Idx == INDEX_NONE)
				{
					// manually grow the array one struct element
					Idx = DebugTextList.Length;
					DebugTextList.Length = Idx + 1;
				}
			}
			else
			{
				Idx = DebugTextList.Length;
				DebugTextList.Length = Idx + 1;
			}
			// assign the new text and actor
			DebugTextList[Idx].SrcActor = SrcActor;
			DebugTextList[Idx].SrcActorOffset = Offset;
			DebugTextList[Idx].SrcActorDesiredOffset = DesiredOffset;
			DebugTextList[Idx].DebugText = DebugText;
			DebugTextList[Idx].TimeRemaining = Duration;
			DebugTextList[Idx].Duration = Duration;
			DebugTextList[Idx].TextColor = TextColor;
			DebugTextList[Idx].bAbsoluteLocation=bAbsoluteLocation;
			DebugTextList[Idx].bKeepAttachedToActor=bKeepAttachedToActor;
			DebugTextList[Idx].OrigActorLocation = SrcActor.Location;
			DebugTextList[Idx].Font=InFont;
		}
	}
}

/**
 * Remove debug text for the specific actor.
 */
final reliable client event RemoveDebugText(Actor SrcActor)
{
	local int Idx;
	Idx = DebugTextList.Find('SrcActor',SrcActor);
	if (Idx != INDEX_NONE)
	{
		DebugTextList.Remove(Idx,1);
	}
}

/**
 * Remove all debug text
 */
final reliable client event RemoveAllDebugStrings()
{
	DebugTextList.Length=0;
}

/**
 * Registers the host's stat guid with the online subsystem
 *
 * @param StatGuid the stat guid to register
 */
reliable client function ClientRegisterHostStatGuid(string StatGuid)
{
	if (OnlineSub != None && OnlineSub.StatsInterface != None)
	{
		OnlineSub.StatsInterface.AddRegisterHostStatGuidCompleteDelegate(OnRegisterHostStatGuidComplete);
		if(OnlineSub.StatsInterface.RegisterHostStatGuid(StatGuid)==false)
		{
			OnRegisterHostStatGuidComplete(false);
		}
	}
}

/**
 * Called once the host registration has completed. Sends the host this clients stat guid
 *
 * @param bWasSuccessful true if the registration worked, false otherwise
 */
function OnRegisterHostStatGuidComplete(bool bWasSuccessful)
{
	local string StatGuid;

	OnlineSub.StatsInterface.ClearRegisterHostStatGuidCompleteDelegateDelegate(OnRegisterHostStatGuidComplete);
	if (bWasSuccessful)
	{
		StatGuid = OnlineSub.StatsInterface.GetClientStatGuid();
		// Report the client stat guid back to the server
		ServerRegisterClientStatGuid(StatGuid);
	}
}

/**
 * Registers the client's stat guid with the online subsystem
 *
 * @param StatGuid the stat guid to register
 */
reliable server function ServerRegisterClientStatGuid(string StatGuid)
{
	if (OnlineSub != None && OnlineSub.StatsInterface != None)
	{
		OnlineSub.StatsInterface.RegisterStatGuid(PlayerReplicationInfo.UniqueId,StatGuid);
	}
}

/**
 * Starts the online game using the session name in the PRI
 */
reliable client function ClientStartOnlineGame()
{
	local OnlineGameSettings GameSettings;

	if (OnlineSub != None &&
		OnlineSub.GameInterface != None &&
		IsPrimaryPlayer())
	{
		GameSettings = OnlineSub.GameInterface.GetGameSettings(PlayerReplicationInfo.SessionName);
		// Start the game if not already in progress
		if (GameSettings != None &&
			(GameSettings.GameState == OGS_Pending || GameSettings.GameState == OGS_Ended))
		{
			OnlineSub.GameInterface.StartOnlineGame(PlayerReplicationInfo.SessionName);
		}
	}
}

/**
 * Ends the online game using the session name in the PRI
 */
reliable client function ClientEndOnlineGame()
{
	local OnlineGameSettings GameSettings;

	if (OnlineSub != None &&
		OnlineSub.GameInterface != None&&
		IsPrimaryPlayer())
	{
		GameSettings = OnlineSub.GameInterface.GetGameSettings(PlayerReplicationInfo.SessionName);
		// End the game if in progress
		if (GameSettings != None &&
			GameSettings.GameState == OGS_InProgress)
		{
			OnlineSub.GameInterface.EndOnlineGame(PlayerReplicationInfo.SessionName);
		}
	}
}

/** Checks for parental controls blocking user created content */
function bool CanViewUserCreatedContent()
{
//@HSL_BEGIN_XBOX
	return bCanShareUserCreatedContent;
//@HSL_END_XBOX
}


function IncrementNumberOfMatchesPlayed()
{
	`log( "  Num Matches Played: " $ PlayerReplicationInfo.AutomatedTestingData.NumberOfMatchesPlayed );
	PlayerReplicationInfo.AutomatedTestingData.NumberOfMatchesPlayed++;
}

/** For AI debugging */
event SoakPause(Pawn P)
{
	`log("Soak pause by "$P);
	SetViewTarget(P);
	SetPause(true);
	myHud.bShowDebugInfo = true;
}

// AI PATHING DEBUG
exec function PathStep( optional int Cnt)
{
	Pawn.IncrementPathStep( Max(1, Cnt), myHud.Canvas );
}
exec function PathChild( optional int Cnt )
{
	Pawn.IncrementPathChild( Max(1, Cnt), myHud.Canvas );
}
exec function PathClear()
{
	Pawn.ClearPathStep();
}

/**
 * Used when a host is telling a client to go to a specific Internet session
 *
 * @param SessionName the name of the session to register
 * @param SearchClass the search that should be populated with the session
 * @param PlatformSpecificInfo the binary data to place in the platform specific areas
 */
reliable client function ClientTravelToSession(name SessionName,class<OnlineGameSearch> SearchClass,byte PlatformSpecificInfo[80])
{
	local OnlineGameSearch Search;
	local LocalPlayer LP;
	local OnlineGameSearchResult SessionToJoin;

	LP = LocalPlayer(Player);
	if (LP != None)
	{
		Search = new SearchClass;
		// Get the information needed to travel to this destination
		if (OnlineSub.GameInterface.BindPlatformSpecificSessionToSearch(LP.ControllerId,Search,PlatformSpecificInfo))
		{
			// There will be only one search result so use it
			SessionToJoin = Search.Results[0];

			`Log("(PlayerController.ClientTravelToSession): "
				$" SessionName="$SessionName
				$" SearchClass="$SearchClass
				$" UniqueId="$OnlineSub.static.UniqueNetIdToString(PlayerReplicationInfo.UniqueId)
				$" Session.OwnerId="$OnlineSub.static.UniqueNetIdToString(SessionToJoin.GameSettings.OwningPlayerId));

			// Fixup game settings object pre-join
			PreJoinUpdateGameSettings(SessionName,SessionToJoin.GameSettings);

			OnlineSub.GameInterface.AddJoinOnlineGameCompleteDelegate(OnJoinTravelToSessionComplete);
			// Now join this Session
			OnlineSub.GameInterface.JoinOnlineGame(LP.ControllerId,SessionName,SessionToJoin);
		}
	}
}

/**
 * Last chance to fixup game settings values before joining a game with those settings
 *
 * @param SessionName name of the session that is about to be joined
 * @param GameSettings settings object to be modified
 */
simulated function PreJoinUpdateGameSettings(name SessionName,OnlineGameSettings GameSettings);

/**
 * Called when the join for the travel destination has completed
 *
 * @param SessionName the name of the session the event is for
 * @param bWasSuccessful whether it worked or not
 */
function OnJoinTravelToSessionComplete(name SessionName,bool bWasSuccessful)
{
	local string URL;

	if (bWasSuccessful)
	{
		// We are joining so grab the connect string to use
		if (OnlineSub.GameInterface.GetResolvedConnectString(SessionName,URL))
		{
			`Log("Resulting url for 'Game' is ("$URL$")");
			// travel to the specified URL
			ClientTravel(URL, TRAVEL_Absolute);
		}
	}
}


/**
 * Used when a host is telling a client to return to their party host from the
 * current session. It looks for the session named 'Party' and does a travel to
 * it. If it's not available, it just does a "disconnect"
 *
 * @param RequestingPlayerId net id of the player that is requesting the travel
 */
reliable client function ClientReturnToParty(UniqueNetId RequestingPlayerId)
{
	local string URL;

	// Disable when returning ourself to menus
	WorldInfo.ToggleHostMigration(false);

	// only do this for the first player in split-screen sessions.
	if (IsPrimaryPlayer())
	{
		if (OnlineSub != None &&
			OnlineSub.GameInterface != None &&
			OnlineSub.PlayerInterface != None)
		{
			// Find the party settings to verify that a party is registered
			if (OnlineSub.GameInterface.GetGameSettings('Party') != None)
			{
				// Now see if we are the party host or not
				if (IsPartyLeader())
				{
					// We are the party host so create the session and listen for returning clients
					URL = GetPartyMapName() $ "?game=" $ GetPartyGameTypeName() $ "?listen";
					// Transition to being the party host without notifying clients and traveling absolute
					WorldInfo.ServerTravel(URL,true,true);
				}
				else
				{
					// We are joining so grab the connect string to use
					if (OnlineSub.GameInterface.GetResolvedConnectString('Party',URL))
					{
						ClientTravel(URL, TRAVEL_Absolute);
					}
				}
			}
			else
			{
				ConsoleCommand("disconnect");
			}
		}
		else
		{
			ConsoleCommand("disconnect");
		}
	}
}

/**
 * RPC notification to client that a party host is about to leave the match
 *
 * @param PartyHostPlayerId net id of the party host that is leaving
 */
reliable client function ClientNotifyPartyHostLeaving(UniqueNetId PartyHostPlayerId)
{
	if (PlayerReplicationInfo != None && PlayerReplicationInfo.UniqueId != PartyHostPlayerId)
	{
		//@todo sz - implement
		`Log(`location @ "Party host is leaving: "
			$" PartyHostPlayerId="$class'OnlineSubsystem'.static.UniqueNetIdToString(PartyHostPlayerId));
	}
}

/**
 * RPC notification to server that a party host is about to leave the match.
 *
 * @param PartyHostPlayerId net id of the party host that is leaving
 */
reliable server function ServerNotifyPartyHostLeaving(UniqueNetId PartyHostPlayerId)
{
	if (WorldInfo.Game != None)
	{
		// Forwarded notification to all clients
		WorldInfo.Game.TellClientsPartyHostIsLeaving(PartyHostPlayerId);
	}
}

/**
 * Wrapper for determining whether this player is the first player on their console.
 *
 * @return	TRUE if this player is not using splitscreen, or is the first player in the split-screen layout.
 */
simulated final function bool IsPrimaryPlayer()
{
	local int SSIndex;

	return !IsSplitscreenPlayer(SSIndex) || SSIndex == 0;
}

/**
 * Determines whether this player is playing split-screen.
 *
 * @param	out_SplitscreenPlayerIndex	receives the index [into the player's local GamePlayers array] for this player, if playing splitscreen.
 *									.
 * @return	TRUE if this player is playing splitscreen.
 */
simulated final function bool IsSplitscreenPlayer(optional out int out_SplitscreenPlayerIndex)
{
	local bool bResult;
	local LocalPlayer LP;
	local NetConnection RemoteConnection;
	local ChildConnection ChildRemoteConnection;

	out_SplitscreenPlayerIndex = NetPlayerIndex;
	if (Player != None)
	{
		LP = LocalPlayer(Player);
		if (LP != None)
		{
			if (LP.Outer.GamePlayers.Length > 1)
			{
				out_SplitscreenPlayerIndex = LP.Outer.GamePlayers.Find(LP);
				bResult = true;
			}
		}
		else
		{
			RemoteConnection = NetConnection(Player);
			if (RemoteConnection.Children.Length > 0)
			{
				out_SplitscreenPlayerIndex = 0;
				bResult = true;
			}
			else
			{
				ChildRemoteConnection = ChildConnection(RemoteConnection);
				if (ChildRemoteConnection != None)
				{
					if (ChildRemoteConnection.Parent != None)
					{
						out_SplitscreenPlayerIndex = ChildRemoteConnection.Parent.Children.Find(ChildRemoteConnection) + 1;
					}
					bResult = true;
				}
			}
		}
	}

	return bResult;
}

/**
 * Returns the PRI associated with the player at the specified index.
 *
 * @param	PlayerIndex		the index [into the local player's GamePlayers array] for the player PRI to find
 *
 * @return	the PRI associated with the player at the specified index, or None if the player is not a split-screen player or
 *			the index was out of range.
 */
simulated function PlayerReplicationInfo GetSplitscreenPlayerByIndex( optional int PlayerIndex=1 )
{
	local PlayerReplicationInfo Result;
	local LocalPlayer LP, SplitPlayer;
	local NetConnection MasterConnection, RemoteConnection;
	local ChildConnection ChildRemoteConnection;

	if ( Player != None )
	{
		if ( IsSplitscreenPlayer() )
		{
			LP = LocalPlayer(Player);
			RemoteConnection = NetConnection(Player);
			if ( LP != None )
			{
				// this PC is a local player
				if ( PlayerIndex >= 0 && PlayerIndex < LP.ViewportClient.Outer.GamePlayers.Length )
				{
					SplitPlayer = LP.ViewportClient.Outer.GamePlayers[PlayerIndex];
					Result = SplitPlayer.Actor.PlayerReplicationInfo;
				}
				else
				{
					`warn(`location $ ":" @ "requested player at invalid index!" @ `showvar(PlayerIndex) @ `showvar(LP.ViewportClient.Outer.GamePlayers.Length,NumLocalPlayers));
				}
			}
			else if ( RemoteConnection != None )
			{
				if ( WorldInfo.NetMode == NM_Client )
				{
					//THIS SHOULD NEVER HAPPEN - IF HAVE A REMOTECONNECTION, WE SHOULDN'T BE A CLIENT
					// this player is a client
					`warn(`location $ ":" @ "CALLED ON CLIENT WITH VALID REMOTE NETCONNECTION!");
				}
				else
				{
					ChildRemoteConnection = ChildConnection(RemoteConnection);
					if ( ChildRemoteConnection != None )
					{
						// this player controller is not the primary player in the splitscreen layout
						MasterConnection = ChildRemoteConnection.Parent;
						if ( PlayerIndex == 0 )
						{
							Result = MasterConnection.Actor.PlayerReplicationInfo;
						}
						else
						{
							PlayerIndex--;
							if ( PlayerIndex >= 0 && PlayerIndex < MasterConnection.Children.Length )
							{
								ChildRemoteConnection = MasterConnection.Children[PlayerIndex];
								Result = ChildRemoteConnection.Actor.PlayerReplicationInfo;
							}
						}
					}
					else if ( RemoteConnection.Children.Length > 0 )
					{
						// this PC is the primary splitscreen player
						if ( PlayerIndex == 0 )
						{
							// they want this player controller's PRI
							Result = PlayerReplicationInfo;
						}
						else
						{
							// our split-screen's PRI is being requested.
							PlayerIndex--;
							if ( PlayerIndex >= 0 && PlayerIndex < RemoteConnection.Children.Length )
							{
								ChildRemoteConnection = RemoteConnection.Children[PlayerIndex];
								Result = ChildRemoteConnection.Actor.PlayerReplicationInfo;
							}
						}
					}
					else
					{
						`log(`location $ ":" @ Player @ "IS NOT THE PRIMARY CONNECTION AND HAS NO CHILD CONNECTIONS!");
					}
				}
			}
			else
			{
				`log(`location $ ":" @ Player @ "IS NOT A LOCALPLAYER AND NOT A REMOTECONNECTION! (No valid Player reference)");
			}
		}
	}
	else
	{
		`log(`location $ ":" @ "NULL value for Player!");
	}

	return Result;
}

/**
 * Returns the number of split-screen players playing on this player's machine.
 *
 * @return	the total number of players on the player's local machine, or 0 if this player isn't playing split-screen.
 */
simulated function int GetSplitscreenPlayerCount()
{
	local LocalPlayer LP;
	local NetConnection RemoteConnection;
	local int Result;

	if ( IsSplitscreenPlayer() )
	{
		if ( Player != None )
		{
			LP = LocalPlayer(Player);
			RemoteConnection = NetConnection(Player);
			if ( LP != None )
			{
				Result = LP.ViewportClient.Outer.GamePlayers.Length;
			}
			else if ( RemoteConnection != None )
			{
				if ( ChildConnection(RemoteConnection) != None )
				{
					// we're the secondary (or otherwise) player in the split - we need to move up to the primary connection
					RemoteConnection = ChildConnection(RemoteConnection).Parent;
				}

				// add one for the primary player
				Result = RemoteConnection.Children.Length + 1;
			}
			else
			{
				`log(`location @ "NOT A LOCALPLAYER AND NOT A REMOTECONNECTION!");
			}
		}
		else
		{
			`log(`location @ "called without a valid Player value!");
		}
	}

	return Result;
}

reliable client function ClientControlMovieTexture(TextureMovie MovieTexture, SeqAct_ControlMovieTexture.EMovieControlType Mode)
{
	if (MovieTexture != None)
	{
		switch (Mode)
		{
			case MCT_Play:
				MovieTexture.Play();
				break;
			case MCT_Stop:
				MovieTexture.Stop();
				break;
			case MCT_Pause:
				MovieTexture.Pause();
				break;
			default:
				break;
		}
	}
}

/**
 * Forces the streaming system to disregard the normal logic for the specified duration and
 * instead always load all mip-levels for all textures used by the specified material.
 *
 * @param Material		- The material whose textures should be forced into memory.
 * @param ForceDuration	- Number of seconds to keep all mip-levels in memory, disregarding the normal priority logic.
 * @param CinematicTextureGroups	- Bitfield indicating which texture groups that use extra high-resolution mips
 */
reliable client event ClientSetForceMipLevelsToBeResident( MaterialInterface Material, float ForceDuration, optional int CinematicTextureGroups )
{
	if ( Material != None && IsPrimaryPlayer() )
	{
		Material.SetForceMipLevelsToBeResident( false, false, ForceDuration, CinematicTextureGroups );
	}
}

/**
 * Forces the streaming system to disregard the normal logic for the specified duration and
 * instead always load all mip-levels for all textures used by the specified actor.
 *
 * @param ForcedActor		- The actor whose textures should be forced into memory.
 * @param ForceDuration		- Number of seconds to keep all mip-levels in memory, disregarding the normal priority logic.
 * @param bEnableStreaming	- Whether to start (TRUE) or stop (FALSE) streaming
 * @param CinematicTextureGroups	- Bitfield indicating which texture groups that use extra high-resolution mips
 */
reliable client event ClientPrestreamTextures( Actor ForcedActor, float ForceDuration, bool bEnableStreaming, optional int CinematicTextureGroups = 0 )
{
	if ( ForcedActor != None && IsPrimaryPlayer() )
	{
		ForcedActor.PrestreamTextures( ForceDuration, bEnableStreaming, CinematicTextureGroups );
	}
}

/** adds a location to the texture streaming system for the specified duration */
reliable client native final event ClientAddTextureStreamingLoc(vector InLoc, float Duration, bool bOverrideLocation);

/**
 * Wrapper for determining whether a player is the party leader.
 *
 * @return	TRUE if the player is the leader of the party; FALSE if not in a party or not the party leader.
 */
simulated function bool IsPartyLeader()
{
	local OnlineGameSettings PartySettings;

	if ( OnlineSub != None && OnlineSub.GameInterface != None )
	{
		// Find the party settings to verify that a party is registered
		PartySettings = OnlineSub.GameInterface.GetGameSettings('Party');
		if ( PartySettings != None )
		{
			if ( PlayerReplicationInfo != None )
			{
				return PartySettings.OwningPlayerId == PlayerReplicationInfo.UniqueId;
			}
		}
	}

	return WorldInfo.NetMode != NM_Client && IsPrimaryPlayer();
}

/**
 * Returns the party map name for this game
 */
static function string GetPartyMapName();

/**
 * Returns the party game info name for this game
 */
static function string GetPartyGameTypeName();

/**
 * Get the completion amount for a game achievement.
 *
 * @param	AchievementId	the id for the achievement to get the completion percetage for
 * @param	CurrentValue	the current number of times the event required to unlock the achievement has occurred.
 * @param	MaxValue		the value that represents 100% completion.
 *
 * @return	TRUE if the AchievementId specified represents an progressive achievement.  FALSE if the achievement is a one-time event
 *			or if the AchievementId specified is invalid.
 */
event bool GetAchievementProgression( int AchievementId, out float CurrentValue, out float MaxValue );



//// SENTINEL FUNCTIONS START

/** This is used to notify the PlayerController that a fly through has ended and then quit if we are doing a Sentinel run **/
simulated function OnFlyThroughHasEnded( SeqAct_FlyThroughHasEnded InAction )
{
	local PlayerController PC;

	if( WorldInfo.Game.IsDoingASentinelRun() )
	{
		foreach WorldInfo.AllControllers(class'PlayerController', PC)
		{
			PC.ConsoleCommand( "quit" );
		}
	}
}


/**
 * This is a function which is called when sentinel is able to start TravelTheWorld.  This allows the specific game
 * to do things such as turning off UI/HUD and to not enter some default starting the game state.
 **/
function Sentinel_SetupForGamebasedTravelTheWorld()
{
}

/**
 * This function is called before we acquire the travel points.
 **/
function Sentinel_PreAcquireTravelTheWorldPoints()
{
}

/**
 * This function is called after we acquire the travel points right before we start traveling.
 **/
function Sentinel_PostAcquireTravelTheWorldPoints()
{
}

//// SENTINEL FUNCTIONS END

delegate InputMatchDelegate()
{
}

/** Attempts to play force-feedback that matches the camera shake.  To be implemented at a higher level (see GameFramework). */
simulated protected function DoForceFeedbackForScreenShake( CameraShake ShakeData, float ShakeScale );

/** Play Camera Shake */
unreliable client function ClientPlayCameraShake( CameraShake Shake, optional float Scale=1.f, optional bool bTryForceFeedback, optional ECameraAnimPlaySpace PlaySpace=CAPS_CameraLocal, optional rotator UserPlaySpaceRot )
{
	if (PlayerCamera != None)
	{
		PlayerCamera.PlayCameraShake(Shake, Scale, PlaySpace, UserPlaySpaceRot);

		if (bTryForceFeedback)
		{
			DoForceFeedbackForScreenShake(Shake, Scale);
		}
	}
}

unreliable client function ClientStopCameraShake( CameraShake Shake )
{
	if (PlayerCamera != None)
	{
		PlayerCamera.StopCameraShake(Shake);
	}
}

/**
 * Scripting hook for camera shakes.
 */
function OnCameraShake(SeqAct_CameraShake inAction)
{
	if (inAction.InputLinks[0].bHasImpulse)
	{
		// start!
		if (InAction.bRadialShake)
		{
			// play an in-world camera shake
			if (InAction.LocationActor != None)
			{
				class'Camera'.static.PlayWorldCameraShake(InAction.Shake, inAction.LocationActor, inAction.LocationActor.Location, inAction.RadialShake_InnerRadius, inAction.RadialShake_OuterRadius, InAction.RadialShake_Falloff, InAction.bDoControllerVibration, inAction.bOrientTowardRadialEpicenter);
			}
			else
			{
				`Warn(self@"Location actor needed for bRadialFalloff camera shake.");
				return;
			}
		}
		else
		{
			// just shake Target(s)
			ClientPlayCameraShake(inAction.Shake, inAction.ShakeScale,
								  inAction.bDoControllerVibration, inAction.PlaySpace,
								  (inAction.LocationActor == None) ? rot(0,0,0) : inAction.LocationActor.Rotation );
		}
	}
	else
	{
		// stop!
		ClientStopCameraShake(inAction.Shake);
	}
}

unreliable client event ClientPlayCameraAnim( CameraAnim AnimToPlay, optional float Scale=1.f, optional float Rate=1.f,
						optional float BlendInTime, optional float BlendOutTime, optional bool bLoop,
						optional bool bRandomStartTime, optional ECameraAnimPlaySpace Space=CAPS_CameraLocal, optional rotator CustomPlaySpace )
{
	local CameraAnimInst AnimInst;
	if (PlayerCamera != None)
	{
		AnimInst = PlayerCamera.PlayCameraAnim(AnimToPlay, Rate, Scale, BlendInTime, BlendOutTime, bLoop, bRandomStartTime);
		if (AnimInst != None && Space != CAPS_CameraLocal)
		{
			AnimInst.SetPlaySpace(Space, CustomPlaySpace);
		}
	}
}

reliable client event ClientStopCameraAnim(CameraAnim AnimToStop, optional bool bImmediate)
{
	if (PlayerCamera != None)
	{
		PlayerCamera.StopAllCameraAnimsByType(AnimToStop, bImmediate);
	}
}

/** Toggle camera animation debug output */
exec function DebugCameraAnims()
{
	bDebugCameraAnims = !bDebugCameraAnims;
	if (bDebugCameraAnims == FALSE)
	{
		// clear debug lines
		WorldInfo.FlushPersistentDebugLines();
	}
}

/** Spawn a camera lens effect (e.g. blood).*/
unreliable client event ClientSpawnCameraLensEffect( class<EmitterCameraLensEffectBase> LensEffectEmitterClass )
{
	if (PlayerCamera != None)
	{
		PlayerCamera.AddCameraLensEffect(LensEffectEmitterClass);
	}
}

/** Remove a camera lens effect (e.g. blood).*/
unreliable client event ClientRemoveCameraLensEffect( class<EmitterCameraLensEffectBase> LensEffectEmitterClass )
{
	Local EmitterCameraLensEffectBase EmitterLensEffect;

	if (PlayerCamera != None)
	{
		EmitterLensEffect = PlayerCamera.FindCameraLensEffect(LensEffectEmitterClass);
		if( EmitterLensEffect != none )
		{
			PlayerCamera.RemoveCameraLensEffect(EmitterLensEffect);
			EmitterLensEffect.Destroy();
		}
	}
}

/** A trivial way to handle the SetSoundMode kismet action */
function OnSetSoundMode(SeqAct_SetSoundMode Action)
{
	local AudioDevice Audio;

	Audio = class'Engine'.static.GetAudioDevice();
	if (Audio != None)
	{
		if ( Action.InputLinks[0].bHasImpulse && (Action.SoundMode != None) )
		{
			Audio.SetSoundMode(Action.SoundMode.Name);
		}
		else
		{
			Audio.SetSoundMode('Default');
		}
	}
}

/**
 * Determine if this player has a peer connection for the given net id
 *
 * @param PeerNetId net id of remote client peer
 * @return TRUE if the player has the peer connection
 */
native function bool HasPeerConnection(const out UniqueNetId PeerNetId) const;

/**
 * This will move the player and set their rotation to the passed in values.
 * We have this version of the BugIt family as it is easier to type in just raw numbers in the console.
 **/
exec function BugItGo( coerce float X, coerce float Y, coerce float Z, coerce int Pitch, coerce int Yaw, coerce int Roll )
{
	local vector TheLocation;
	local rotator TheRotation;

	TheLocation.X = X;
	TheLocation.Y = Y;
	TheLocation.Z = Z;

	TheRotation.Pitch = Pitch;
	TheRotation.Yaw = Yaw;
	TheRotation.Roll = Roll;

	BugItWorker( TheLocation, TheRotation );
}

/**
* This will move the player and set their rotation to the passed in values.
* We have this version of the BugIt family strings can be passed in from the game ?options easily
**/
function BugItGoString(string TheLocation, string TheRotation)
{
	BugItWorker(vector(TheLocation), rotator(TheRotation));
}

/**
* This will move the player and set their rotation to the passed in values.
* This actually does the location / rotation setting.  Additionally it will set you as ghost as the level may have
* changed since the last time you were here.  And the bug may actually be inside of something.
**/
function BugItWorker( vector TheLocation, rotator TheRotation )
{
	`log( "BugItGo to:" @ TheLocation @ TheRotation );

	if( CheatManager != none )
	{
		CheatManager.Ghost();
	}

	ViewTarget.SetLocation( TheLocation );

`if(`__TW_) // Just avoiding an accessed none if in debugcameracontroller mode where you have no pawn
	if( Pawn != none )
	{
		Pawn.FaceRotation( TheRotation, 0.0f );
	}
`else
	Pawn.FaceRotation( TheRotation, 0.0f );
`endif
	SetRotation( TheRotation );
}

/**
* This function is used to print out the BugIt location.  It prints out copy and paste versions for both IMing someone to type in
* and also a gameinfo ?options version so that you can append it to your launching url and be taken to the correct place.
* Additionally, it will take a screen shot so reporting bugs is a one command action!
*
**/
exec event BugIt( optional string ScreenShotDescription )
{
	local vector	ViewLocation;
	local rotator	ViewRotation;

	local String GoString;
	local String LocString;

	if( !class'WorldInfo'.static.IsConsoleBuild( CONSOLE_Orbis ) )
	{
		ConsoleCommand( "bugscreenshot " $ ScreenShotDescription );
	}
	else
	{
		`log("BUGIT: USE THE PS4'S SCRRENSHOT FUNCITONALITY!");
	}

	GetPlayerViewPoint( ViewLocation, ViewRotation );

	if( Pawn != None )
	{
		ViewLocation = Pawn.Location;
	}


	BugItStringCreator( ViewLocation, ViewRotation, GoString, LocString );

	LogOutBugItGoToLogFile( ScreenShotDescription, GoString, LocString );
}

/**
 * Logs the current location in bugit format without taking screenshot and further routing.
 */
exec function LogLoc()
{
	local vector	ViewLocation;
	local rotator	ViewRotation;
	local String GoString;
	local String LocString;

	GetPlayerViewPoint( ViewLocation, ViewRotation );
	if( Pawn != None )
	{
		ViewLocation = Pawn.Location;
	}
	BugItStringCreator( ViewLocation, ViewRotation, GoString, LocString );
}

exec event BugItAI( optional string ScreenShotDescription )
{
	local vector	ViewLocation;
	local rotator	ViewRotation;

	local String GoString;
	local String LocString;

	GetPlayerViewPoint( ViewLocation, ViewRotation );

`if(`__TW_)
	if( Pawn(ViewTarget) != none )
	{
		ViewLocation = ViewTarget.Location;
	}
	else
`endif
	if( Pawn != None )
	{
		ViewLocation = Pawn.Location;
	}


	BugItStringCreator( ViewLocation, ViewRotation, GoString, LocString );

	ConsoleCommand("debugai");
	SetTimer(0.1,FALSE,nameof(DisableDebugAI));
	LogOutBugItAIGoToLogFile( ScreenShotDescription, GoString, LocString );
}


/** This will create a BugItGo string for us.  Nice for calling form c++ where you just want the string and no Screenshots **/
exec event BugItStringCreator( const out Vector ViewLocation, const out Rotator ViewRotation, out String GoString, out String LocString )
{
	GoString = "BugItGo " $ ViewLocation.X $ " " $ ViewLocation.Y $ " " $ ViewLocation.Z $ " " $ ViewRotation.Pitch $ " " $ ViewRotation.Yaw $ " " $ ViewRotation.Roll;
	`log( GoString );

	LocString = "?BugLoc=" $ string(ViewLocation) $ "?BugRot=" $ string(ViewRotation);
	`log( LocString );
}

/** An event that is called after the first tick of GEngine */
event OnEngineInitialTick()
{
	// by default, we need to hide any loading movies at this point (a subclass
	// can override this to keep the movie up longer)
	if (WorldInfo.IsConsoleBuild(CONSOLE_Mobile))
	{
		ConsoleCommand("mobile stoploading");
	}
}


`if(`notdefined(FINAL_RELEASE))
/**
 * Logs the list of active PRIs in the game
 */
function DebugLogPRIs()
{
	local int PlayerIndex;
	local UniqueNetId NetId;

	// Log the PRI information for comparison with the online session state
	if (WorldInfo != None &&
		WorldInfo.GRI != None)
	{
		`Log("  Number of PRI players: "$WorldInfo.GRI.PRIArray.Length);
		// List the PRIs that are here to find mismatches between the online state
		for (PlayerIndex = 0; PlayerIndex < WorldInfo.GRI.PRIArray.Length; PlayerIndex++)
		{
			NetId = WorldInfo.GRI.PRIArray[PlayerIndex].UniqueId;
			`Log("    Player: "$WorldInfo.GRI.PRIArray[PlayerIndex].PlayerName$" UID ("$class'OnlineSubsystem'.static.UniqueNetIdToString(NetId)$") PC ("$WorldInfo.GRI.PRIArray[PlayerIndex].Owner$")");
		}
		`Log("");
	}
}
/**
 * Logs the current session state for the online layer
 */
exec function DumpOnlineSessionState()
{
	if (CheatManager != None)
	{
		CheatManager.DumpOnlineSessionState();
	}
	else
	{
		// Log PRI player info
		DebugLogPRIs();
		// Log the online session state
		if (OnlineSub != None)
		{
			OnlineSub.DumpSessionState();
		}
	}
}

/**
 * Logs the current muting state of the server
 */
exec function DumpVoiceMutingState()
{
	local UniqueNetId NetId;
	local PlayerController PC;
	local int MuteIndex;

	`Log("");
	`Log("Voice state");
	`Log("-------------------------------------------------------------");
	`Log("");
	// Log the online view of the voice state
	if (OnlineSub != None)
	{
		OnlineSub.DumpVoiceRegistration();
	}

	`Log("Muting state");
	// For each player list their gameplay mutes and system wide mutes
	foreach WorldInfo.AllControllers(class'PlayerController', PC)
	{
		`Log("  Player: "$PC.PlayerReplicationInfo.PlayerName);
		`Log("    Gameplay mutes: ");
		for (MuteIndex = 0; MuteIndex < PC.GameplayVoiceMuteList.Length; MuteIndex++)
		{
			NetId = PC.GameplayVoiceMuteList[MuteIndex];
			`Log("      "$class'OnlineSubsystem'.static.UniqueNetIdToString(NetId));
		}
		`Log("    System mutes: ");
		for (MuteIndex = 0; MuteIndex < PC.VoiceMuteList.Length; MuteIndex++)
		{
			NetId = PC.VoiceMuteList[MuteIndex];
			`Log("      "$class'OnlineSubsystem'.static.UniqueNetIdToString(NetId));
		}
		`Log("    Voice packet filter: ");
		for (MuteIndex = 0; MuteIndex < PC.VoicePacketFilter.Length; MuteIndex++)
		{
			NetId = PC.VoicePacketFilter[MuteIndex];
			`Log("      "$class'OnlineSubsystem'.static.UniqueNetIdToString(NetId));
		}
		`Log("");
	}
}

/**
 * Logs the list of active peer connections for this player
 */
exec function DumpPeers()
{
	local UniqueNetId NetId;
	local PlayerController PC;
	local int PeerIdx;
	local ConnectedPeerInfo PeerInfo;

	`Log("");
	`Log("Peer List");

	foreach WorldInfo.AllControllers(class'PlayerController', PC)
	{
		if (WorldInfo.NetMode == NM_Client || !PC.IsLocalPlayerController())
		{
			`Log("  Player: "$PC.PlayerReplicationInfo.PlayerName$"("$class'OnlineSubsystem'.static.UniqueNetIdToString(PC.PlayerReplicationInfo.UniqueId)$")");
			`Log("    Peer connections: ");
			for (PeerIdx = 0; PeerIdx < PC.ConnectedPeers.Length; PeerIdx++)
			{
				PeerInfo = PC.ConnectedPeers[PeerIdx];
				`Log("      "$class'OnlineSubsystem'.static.UniqueNetIdToString(PeerInfo.PlayerId)
//					$" NAT="$PeerInfo.NATType
					$" HostLost="$PeerInfo.bLostConnectionToHost);
			}
			`Log("    Best Hosts:");
			for (PeerIdx=0; PeerIdx < PC.BestNextHostPeers.Length; PeerIdx++)
			{
				NetId = PC.BestNextHostPeers[PeerIdx];
				`Log("      "$class'OnlineSubsystem'.static.UniqueNetIdToString(NetId));
			}
			`Log("");
		}
	}
}

//@HSL_BEGIN_XBOX
exec function SetCallback()
{
	OnlineSub.GameInterface.AddJoinOnlineGameCompleteDelegate(OnInviteJoinComplete);
}

function OnFriendsReadComplete(bool bWasSuccessful)
{
	local OnlinePlayerInterface PlayerInterface;
	local array<OnlineFriend> FriendsList;

	if (bWasSuccessful == true)
	{
		// Figure out if we have an online subsystem registered
		if (OnlineSub != None)
		{
			// Grab the player interface to verify the subsystem supports it
			PlayerInterface = OnlineSub.PlayerInterface;
			if (PlayerInterface != None)
			{
				// Make a copy of the friends data for the UI
				PlayerInterface.GetFriendsList(0,FriendsList);
			}
		}
	}
	else
	{
		`Log("Failed to read friends list",,'DevOnline');
	}
	OnlineSub.PlayerInterface.ClearReadFriendsCompleteDelegate(0,OnFriendsReadComplete);
}

exec function ReadFriendsList()
{
	local OnlinePlayerInterface PlayerInterface;

	if (OnlineSub != None)
	{
		PlayerInterface = OnlineSub.PlayerInterface;
		if (PlayerInterface != None)
		{
			PlayerInterface.AddReadFriendsCompleteDelegate(0,OnFriendsReadComplete);
			PlayerInterface.ReadFriendsList(0);
		}
	}
}
//@HSL_END_XBOX

`endif

function DisableDebugAI()
{
	ConsoleCommand("debugai");
}

private native function LogOutBugItGoToLogFile( const String InScreenShotDesc, const string InGoString, const string InLocString );
private native function LogOutBugItAIGoToLogFile( const String InScreenShotDesc, const string InGoString, const string InLocString );

/**
 * This function will be called to notify the player controller that the world has received it's game class. In the case of a client
 * we need to initialize the Input System here.
 *
 * @Param GameClass - The Class of the game that was replicated
 */

simulated function ReceivedGameClass(class<GameInfo> GameClass)
{
	if (PlayerInput != none)
	{
		PlayerInput.ClientInitInputSystem();
	}
}

/**
 * Triggered when the 'disconnect' console command is called, to allow cleanup before disconnecting (e.g. for the online subsystem)
 * NOTE: If you block disconnect, store the 'Command' parameter, and trigger ConsoleCommand(Command) when done; be careful to avoid recursion
 *
 * @param Command	The command which triggered disconnection, e.g. "disconnect" or "disconnect local" (can parse additional parameters here)
 * @return		Return True to block the disconnect command from going through, if cleanup can't be completed immediately
 */
event bool NotifyDisconnect(string Command)
{
	return false;
}

/** Function called from matinee to turn on/off a constant camera anim for noise */
event SetMatineeConstantCameraAnim(bool bOn, byte Type, float Rate);

event SetUIScale(float fScale);

`if(`__TW_ONLINESUBSYSTEM_)
/** see TWOnlineLobby */
function NotifyUnsuccessfulSearch();
//To notify that the user has entered or left a party so that we make handle this in KFPlayerController
function OnLobbyStatusChanged(bool bInLobby);
`endif //(`__TW_ONLINESUBSYSTEM_)

`if(`__TW_)
/** KFPawn::IsNetRelevantFor() */
unreliable client event ClientReplicationDebug(vector CamLocation, vector DebugLocation, bool bClear, color DebugColor)
{
`if(`notdefined(ShippingPC))
	if( bClear )
    {
        FlushPersistentDebugLines();
    }
    DrawDebugSphere(DebugLocation,8,8,DebugColor.R,DebugColor.G,DebugColor.B,True);
    DrawDebugLine( CamLocation, DebugLocation, DebugColor.R,DebugColor.G,DebugColor.B, TRUE );
`endif
}
`endif //(`__TW_)


//@HSL_BEGIN_XBOX
simulated event SendReliableVoicePacketToServer( byte MessageType, UniqueNetId Receiver, int Length, byte InData[60] )
{
	ServerReceiveReliableVoicePacket( MessageType, Receiver, Length, InData );
}


reliable server function ServerReceiveReliableVoicePacket( byte MessageType, UniqueNetId Receiver, int Length, byte InData[60] )
{
	local UniqueNetId ZeroId;
	local int i;

	if( Receiver != ZeroId )
	{
		for( i = 0; i < WorldInfo.GRI.PRIArray.Length; i++ )
		{
			if( WorldInfo.GRI.PRIArray[i].UniqueId == Receiver )
			{
				`log("Sending reliable voice packet of message type"@MessageType@"to"@WorldInfo.GRI.PRIArray[i].PlayerName);
				PlayerController(WorldInfo.GRI.PRIArray[i].Owner).ClientReceiveReliableVoicePacket( MessageType, PlayerReplicationInfo.UniqueId, Length, InData );
				return;
			}
		}
		`log("COULD NOT FIND RECIEVER"@class'OnlineSubsystem'.static.UniqueNetIdToString( Receiver, false )@"FOR RELIABLE VOICE PACKET.  CLIENT WILL NOT RECIEVE IT!");
	}
	else
	{
		`log("Sending reliable voice packet of message type"@MessageType);

		for( i = 0; i < WorldInfo.GRI.PRIArray.Length; i++ )
		{
			PlayerController(WorldInfo.GRI.PRIArray[i].Owner).ClientReceiveReliableVoicePacket( MessageType, PlayerReplicationInfo.UniqueId, Length, InData );
		}
	}
}


reliable client function ClientReceiveReliableVoicePacket( byte MessageType, UniqueNetId Sender, int Length, byte InData[60] )
{
	OnlineSub.VoiceInterface.ReceiveReliableVoicePacket(MessageType, Sender, Length, InData);
}
//@HSL_END_XBOX

defaultproperties
{
	// The PlayerController is currently required to have collision as there is code that uses the collision
	// for moving the spectator camera around.  Without collision certain assumptions will not hold (e.g. the
	// spectator does not have a pawn so the various game code looks at ALL pawns.  Having a new pawnType will
	// require changes to that code.
	// Removing the collision from the controller and using the controller - pawn mechanic will eventually be coded
	// for spectator-esque functionality
	Begin Object Class=CylinderComponent Name=CollisionCylinder
		CollisionRadius=+22.000000
		CollisionHeight=+22.000000
	End Object
	CollisionComponent=CollisionCylinder
	CylinderComponent=CollisionCylinder
	Components.Add(CollisionCylinder)

	bInteractiveMode=true
	FOVAngle=85.000
	NetPriority=3
	bIsPlayer=true
	bCanDoSpecial=true
	Physics=PHYS_None
	CheatClass=class'Engine.CheatManager'
	InputClass=class'Engine.PlayerInput'

	CameraClass=class'Camera'

	MaxResponseTime=0.125
	ClientCap=0
	LastSpeedHackLog=-100.0
	SavedMoveClass=class'SavedMove'

	DesiredFOV=85.000000
	DefaultFOV=85.000000
	LODDistanceFactor=1.0

	bCinemaDisableInputMove=false
	bCinemaDisableInputLook=false

	bIsUsingStreamingVolumes=TRUE
	SpectatorCameraSpeed=600.0
	MinRespawnDelay=1.0
//@SABER_EGS_BEGIN Crossplay support
	bIsEosPlayer=false
//@SABER_EGS_END
}
