1670 lines
52 KiB
Ucode
1670 lines
52 KiB
Ucode
/**
|
|
* A game viewport (FViewport) is a high-level abstract interface for the
|
|
* platform specific rendering, audio, and input subsystems.
|
|
* GameViewportClient is the engine's interface to a game viewport.
|
|
* Exactly one GameViewportClient is created for each instance of the game. The
|
|
* only case (so far) where you might have a single instance of Engine, but
|
|
* multiple instances of the game (and thus multiple GameViewportClients) is when
|
|
* you have more than one PIE window running.
|
|
*
|
|
* Responsibilities:
|
|
* propagating input events to the global interactions list
|
|
*
|
|
*
|
|
*
|
|
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
|
|
*/
|
|
class GameViewportClient extends ScriptViewportClient
|
|
within Engine
|
|
transient
|
|
native
|
|
Inherits(FExec)
|
|
config(Engine)
|
|
;
|
|
|
|
/** The platform-specific viewport which this viewport client is attached to. */
|
|
var const pointer Viewport{FViewport};
|
|
|
|
/** The platform-specific viewport frame which this viewport is contained by. */
|
|
var const pointer ViewportFrame{FViewportFrame};
|
|
|
|
/** A list of interactions which have a chance at all input before the player's interactions. */
|
|
var init protected array<Interaction> GlobalInteractions;
|
|
|
|
/** The class for the UI controller */
|
|
var class<UIInteraction> UIControllerClass;
|
|
|
|
/** The viewport's UI controller */
|
|
var UIInteraction UIController;
|
|
|
|
/** The viewport's console. Might be null on consoles */
|
|
var Console ViewportConsole;
|
|
|
|
/** This struct needs to be the same size as EShowFlags */
|
|
struct native ShowFlags_Mirror
|
|
{
|
|
var native const qword flags0;
|
|
var native const qword flags1;
|
|
};
|
|
|
|
/** This empty struct is here so we have a non-native struct that can be exported to the header properly */
|
|
struct {EShowFlags} ExportShowFlags_Mirror extends ShowFlags_Mirror {};
|
|
|
|
/** The show flags used by the viewport's players. */
|
|
var const ExportShowFlags_Mirror ShowFlags;
|
|
|
|
/** @name Localized transition messages. */
|
|
//@{
|
|
var localized string LoadingMessage;
|
|
var localized string SavingMessage;
|
|
var localized string ConnectingMessage;
|
|
var localized string PausedMessage;
|
|
var localized string PrecachingMessage;
|
|
//@}
|
|
|
|
/** if TRUE then the title safe border is drawn */
|
|
var bool bShowTitleSafeZone;
|
|
/** Max/Recommended screen viewable extents as a percentage */
|
|
struct native TitleSafeZoneArea
|
|
{
|
|
var float MaxPercentX;
|
|
var float MaxPercentY;
|
|
var float RecommendedPercentX;
|
|
var float RecommendedPercentY;
|
|
};
|
|
/** border of safe area */
|
|
var TitleSafeZoneArea TitleSafeZone;
|
|
|
|
//If true, this will cause the hardware mouse cursor to render
|
|
var transient bool bDisplayHardwareMouseCursor;
|
|
|
|
var transient bool bOverrideDiffuseAndSpecular;
|
|
|
|
/** If TRUE, this viewport is a play in editor viewport */
|
|
var transient bool bIsPlayInEditorViewport;
|
|
|
|
/** If TRUE, we will show the OS mouse cursor at all times (only applies to PIE viewports)*/
|
|
var transient bool bShowSystemMouseCursor;
|
|
|
|
/**
|
|
* Enum of the different splitscreen types
|
|
*/
|
|
enum ESplitScreenType
|
|
{
|
|
eSST_NONE, // No split
|
|
eSST_2P_HORIZONTAL, // 2 player horizontal split
|
|
eSST_2P_VERTICAL, // 2 player vertical split
|
|
eSST_3P_FAVOR_TOP, // 3 Player split with 1 player on top and 2 on bottom
|
|
eSST_3P_FAVOR_BOTTOM, // 3 Player split with 1 player on bottom and 2 on top
|
|
eSST_4P, // 4 Player split
|
|
};
|
|
|
|
/**
|
|
* The 4 different kinds of safezones
|
|
*/
|
|
enum ESafeZoneType
|
|
{
|
|
eSZ_TOP,
|
|
eSZ_BOTTOM,
|
|
eSZ_LEFT,
|
|
eSZ_RIGHT,
|
|
};
|
|
|
|
/**
|
|
* Specifies whether a setting should be toggled, enabled or disabled.
|
|
*/
|
|
enum ESetMode
|
|
{
|
|
SetMode_Toggle,
|
|
SetMode_Enable,
|
|
SetMode_Disable,
|
|
};
|
|
|
|
/**
|
|
* Structure to store splitscreen data.
|
|
*/
|
|
struct native PerPlayerSplitscreenData
|
|
{
|
|
var float SizeX;
|
|
var float SizeY;
|
|
var float OriginX;
|
|
var float OriginY;
|
|
};
|
|
|
|
/**
|
|
* Structure containing all the player splitscreen datas per splitscreen configuration.
|
|
*/
|
|
struct native SplitscreenData
|
|
{
|
|
var array<PerPlayerSplitscreenData> PlayerData;
|
|
};
|
|
|
|
/** Array of the screen data needed for all the different splitscreen configurations */
|
|
var array<SplitscreenData> SplitscreenInfo;
|
|
|
|
/**
|
|
* The splitscreen layout type that the player wishes to use; this value usually comes from places like the player's profile
|
|
*/
|
|
var protected{protected} ESplitScreenType DesiredSplitscreenType;
|
|
|
|
/**
|
|
* The splitscreen type that is actually being used; takes into account the number of players and other factors (such as cinematic mode)
|
|
* that could affect the splitscreen mode that is actually used.
|
|
*/
|
|
var protected{protected} ESplitscreenType ActiveSplitscreenType;
|
|
|
|
/** Defaults for intances where there are multiple configs for a certain number of players */
|
|
var const ESplitScreenType Default2PSplitType;
|
|
var const ESplitScreenType Default3PSplitType;
|
|
|
|
/** Set to disable world rendering */
|
|
var bool bDisableWorldRendering;
|
|
|
|
/** Set to capture the world rendering into an offscreen target */
|
|
var bool bCapturedWorldRendering;
|
|
|
|
// Progress Indicator - used by the engine to provide status messages (see SetProgressMessage())
|
|
var string ProgressMessage[2];
|
|
var float ProgressTimeOut;
|
|
var float ProgressFadeTime;
|
|
|
|
/** debug property display functionality
|
|
* to interact with this, use "display", "displayall", "displayclear"
|
|
*/
|
|
struct native DebugDisplayProperty
|
|
{
|
|
/** the object whose property to display. If this is a class, all objects of that class are drawn. */
|
|
var Object Obj;
|
|
/** name of the property to display */
|
|
var name PropertyName;
|
|
/** whether PropertyName is a "special" value not directly mapping to a real property (e.g. state name) */
|
|
var bool bSpecialProperty;
|
|
};
|
|
var array<DebugDisplayProperty> DebugProperties;
|
|
|
|
/** Stores the pointer to any data needed for scaleform (if defined)*/
|
|
var native const pointer ScaleformInteraction { UGFxInteraction };
|
|
|
|
/** DEBUG: If TRUE, the GFx UI will NOT be rendered at runtime. Note that to REMOVE GFx functionality permanently, you should compile with WITH_GFx set to 0. This bool is for debugging only. */
|
|
var config bool bDebugNoGFxUI;
|
|
|
|
//@HSL_BEGIN - BWJ - Dingo controller support
|
|
/** TRUE if input is allowed from multiple controllers */
|
|
var bool bAllowInputFromMultipleControllers;
|
|
|
|
/** TRUE if we need to pair a new gamepad due a controller disconnect */
|
|
var bool bNeedsNewGamepadPairingForControllerDisconnect;
|
|
|
|
/** TRUE if we need to pair a new gamepad because of a new profile taking over. We aren't sure which gamepad to use */
|
|
var bool bNeedsNewGamepadPairingForNewProfile;
|
|
//@HSL_END
|
|
|
|
cpptext
|
|
{
|
|
/** Make sure that the UC mirror struct matches the size of EShowFlags */
|
|
checkAtCompileTime( sizeof(FShowFlags_Mirror) == sizeof(EShowFlags), ShowFlags_Mirror__MustMatchSizeOfEShowFlags );
|
|
|
|
// Constructor.
|
|
UGameViewportClient();
|
|
|
|
/**
|
|
* Cleans up all rooted or referenced objects created or managed by the GameViewportClient. This method is called
|
|
* when this GameViewportClient has been disassociated with the game engine (i.e. is no longer the engine's GameViewport).
|
|
*/
|
|
virtual void DetachViewportClient();
|
|
|
|
/**
|
|
* Called every frame to allow the game viewport to update time based state.
|
|
* @param DeltaTime The time since the last call to Tick.
|
|
*/
|
|
void Tick( FLOAT DeltaTime );
|
|
|
|
// FViewportClient interface.
|
|
virtual void RedrawRequested(FViewport* InViewport) {}
|
|
|
|
/**
|
|
* Routes an input key event received from the viewport to the Interactions array for processing.
|
|
*
|
|
* @param Viewport the viewport the input event was received from
|
|
* @param ControllerId gamepad/controller that generated this input event
|
|
* @param Key the name of the key which an event occured for (KEY_Up, KEY_Down, etc.)
|
|
* @param EventType the type of event which occured (pressed, released, etc.)
|
|
* @param AmountDepressed (analog keys only) the depression percent.
|
|
* @param bGamepad - input came from gamepad (ie xbox controller)
|
|
*
|
|
* @return TRUE to consume the key event, FALSE to pass it on.
|
|
*/
|
|
virtual UBOOL InputKey(FViewport* Viewport,INT ControllerId,FName Key,EInputEvent EventType,FLOAT AmountDepressed=1.f,UBOOL bGamepad=FALSE);
|
|
|
|
/**
|
|
* Routes an input axis (joystick, thumbstick, or mouse) event received from the viewport to the Interactions array for processing.
|
|
*
|
|
* @param Viewport the viewport the input event was received from
|
|
* @param ControllerId the controller that generated this input axis event
|
|
* @param Key the name of the axis that moved (KEY_MouseX, KEY_XboxTypeS_LeftX, etc.)
|
|
* @param Delta the movement delta for the axis
|
|
* @param DeltaTime the time (in seconds) since the last axis update.
|
|
*
|
|
* @return TRUE to consume the axis event, FALSE to pass it on.
|
|
*/
|
|
virtual UBOOL InputAxis(FViewport* Viewport,INT ControllerId,FName Key,FLOAT Delta,FLOAT DeltaTime, UBOOL bGamepad=FALSE);
|
|
|
|
/**
|
|
* Routes a character input event (typing) received from the viewport to the Interactions array for processing.
|
|
*
|
|
* @param Viewport the viewport the input event was received from
|
|
* @param ControllerId the controller that generated this character input event
|
|
* @param Character the character that was typed
|
|
*
|
|
* @return TRUE to consume the key event, FALSE to pass it on.
|
|
*/
|
|
virtual UBOOL InputChar(FViewport* Viewport,INT ControllerId,TCHAR Character);
|
|
|
|
/**
|
|
* Check a key event received by the viewport.
|
|
* If the viewport client uses the event, it should return true to consume it.
|
|
* @param Viewport - The viewport which the event is from.
|
|
* @param ControllerId - The controller which the key event is from.
|
|
* @param Handle - Identifier unique to this touch event
|
|
* @param Type - What kind of touch event this is (see ETouchType)
|
|
* @param TouchLocation - Screen position of the touch
|
|
* @param DeviceTimestamp - Timestamp of the event
|
|
* @param TouchpadIndex - For devices with multiple touchpads, this is the index of which one
|
|
* @return True to consume the key event, false to pass it on.
|
|
*/
|
|
virtual UBOOL InputTouch(FViewport* Viewport, INT ControllerId, UINT Handle, BYTE Type, FVector2D TouchLocation, DOUBLE DeviceTimestamp, UINT TouchpadIndex=0);
|
|
|
|
/**
|
|
* Each frame, the input system will update the motion data.
|
|
*
|
|
* @param Viewport - The viewport which the key event is from.
|
|
* @param ControllerId - The controller which the key event is from.
|
|
* @param Tilt The current orientation of the device
|
|
* @param RotationRate How fast the tilt is changing
|
|
* @param Gravity Describes the current gravity of the device
|
|
* @param Acceleration Describes the acceleration of the device
|
|
* @return True to consume the motion event, false to pass it on.
|
|
*/
|
|
virtual UBOOL InputMotion(FViewport* Viewport, INT ControllerId, const FVector& Tilt, const FVector& RotationRate, const FVector& Gravity, const FVector& Acceleration);
|
|
|
|
/** Returns the platform specific forcefeedback manager associated with this viewport */
|
|
virtual class UForceFeedbackManager* GetForceFeedbackManager(INT ControllerId);
|
|
|
|
/**
|
|
* @return the splitscreen type that is currently being used
|
|
*/
|
|
FORCEINLINE ESplitScreenType GetCurrentSplitscreenType() const
|
|
{
|
|
return static_cast<ESplitScreenType>(ActiveSplitscreenType);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the cursor that should be displayed by the OS
|
|
*
|
|
* @param Viewport the viewport that contains the cursor
|
|
* @param X the x position of the cursor
|
|
* @param Y the Y position of the cursor
|
|
*
|
|
* @return the cursor that the OS should display
|
|
*/
|
|
virtual EMouseCursor GetCursor( FViewport* Viewport, INT X, INT Y );
|
|
|
|
|
|
virtual void Precache();
|
|
virtual void Draw(FViewport* Viewport,FCanvas* Canvas);
|
|
virtual void LostFocus(FViewport* Viewport);
|
|
virtual void ReceivedFocus(FViewport* Viewport);
|
|
virtual UBOOL IsFocused(FViewport* Viewport);
|
|
virtual void CloseRequested(FViewport* Viewport);
|
|
virtual UBOOL RequiresHitProxyStorage() { return 0; }
|
|
|
|
/**
|
|
* Determines whether this viewport client should receive calls to InputAxis() if the game's window is not currently capturing the mouse.
|
|
* Used by the UI system to easily receive calls to InputAxis while the viewport's mouse capture is disabled.
|
|
*/
|
|
virtual UBOOL RequiresUncapturedAxisInput() const;
|
|
|
|
// FExec interface.
|
|
virtual UBOOL Exec(const TCHAR* Cmd,FOutputDevice& Ar);
|
|
|
|
/**
|
|
* Helper function to toggles, enable or disable the specified show flag. Called by Exec().
|
|
*
|
|
* @param Cmd Exec command line, as passed on from Exec().
|
|
* @param Ar Output device used for reporting the result.
|
|
* @param SetMode Specifies whether the flag should be toggled, enabled or disabled.
|
|
* @return TRUE if the flag was modified.
|
|
*/
|
|
UBOOL SetShowFlags(const TCHAR* Cmd,FOutputDevice& Ar, ESetMode SetMode );
|
|
|
|
/**
|
|
* Set this GameViewportClient's viewport and viewport frame to the viewport specified
|
|
*/
|
|
virtual void SetViewportFrame( FViewportFrame* InViewportFrame );
|
|
|
|
/**
|
|
* Set this GameViewportClient's viewport to the viewport specified
|
|
*/
|
|
virtual void SetViewport( FViewport* InViewportFrame );
|
|
|
|
/** sets bDropDetail and other per-frame detail level flags on the current WorldInfo
|
|
* @param DeltaSeconds - amount of time passed since last tick
|
|
*/
|
|
virtual void SetDropDetail(FLOAT DeltaSeconds);
|
|
|
|
#if __TW_
|
|
/** Stub for KF2 level ShowSpawnVolumes (see KFGameViewportClient) */
|
|
virtual void ShowSpawnVolumes( ESetMode SetMode ) { }
|
|
#endif
|
|
|
|
#if WITH_GFx
|
|
virtual UObject* GetUObject() { return this; }
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Provides script-only child classes the opportunity to handle input key events received from the viewport.
|
|
* This delegate is called before the input key event is passed to the interactions array for processing.
|
|
*
|
|
* @param ControllerId the controller that generated this input key event
|
|
* @param Key the name of the key which an event occured for (KEY_Up, KEY_Down, etc.)
|
|
* @param EventType the type of event which occured (pressed, released, etc.)
|
|
* @param AmountDepressed for analog keys, the depression percent.
|
|
* @param bGamepad input came from gamepad (ie xbox controller)
|
|
*
|
|
* @return return TRUE to indicate that the input event was handled. if the return value is TRUE, this input event will not
|
|
* be passed to the interactions array.
|
|
*/
|
|
delegate bool HandleInputKey( int ControllerId, name Key, EInputEvent EventType, float AmountDepressed, optional bool bGamepad );
|
|
|
|
/**
|
|
* Provides script-only child classes the opportunity to handle input axis events received from the viewport.
|
|
* This delegate is called before the input axis event is passed to the interactions array for processing.
|
|
*
|
|
* @param ControllerId the controller that generated this input axis event
|
|
* @param Key the name of the axis that moved (KEY_MouseX, KEY_XboxTypeS_LeftX, etc.)
|
|
* @param Delta the movement delta for the axis
|
|
* @param DeltaTime the time (in seconds) since the last axis update.
|
|
* @param bGamepad input came from gamepad (ie xbox controller)
|
|
*
|
|
* @return return TRUE to indicate that the input event was handled. if the return value is TRUE, this input event will not
|
|
* be passed to the interactions array.
|
|
*/
|
|
delegate bool HandleInputAxis( int ControllerId, name Key, float Delta, float DeltaTime, bool bGamepad);
|
|
|
|
/**
|
|
* Provides script-only child classes the opportunity to handle character input (typing) events received from the viewport.
|
|
* This delegate is called before the character event is passed to the interactions array for processing.
|
|
*
|
|
* @param ControllerId the controller that generated this character input event
|
|
* @param Unicode the character that was typed
|
|
*
|
|
* @return return TRUE to indicate that the input event was handled. if the return value is TRUE, this input event will not
|
|
* be passed to the interactions array.
|
|
*/
|
|
delegate bool HandleInputChar( int ControllerId, string Unicode );
|
|
|
|
/**
|
|
* Executes a console command in the context of this viewport.
|
|
* @param Command - The command to execute.
|
|
* @return The output of the command will be returned.
|
|
*/
|
|
native function string ConsoleCommand(string Command);
|
|
|
|
/**
|
|
* Retrieve the size of the main viewport.
|
|
*
|
|
* @param out_ViewportSize [out] will be filled in with the size of the main viewport
|
|
*/
|
|
native final function GetViewportSize( out Vector2D out_ViewportSize );
|
|
|
|
/** @return Whether or not the main viewport is fullscreen or windowed. */
|
|
native final function bool IsFullScreenViewport();
|
|
|
|
/** @return mouse position in game viewport coordinates (does not account for splitscreen) */
|
|
native final function vector2D GetMousePosition();
|
|
|
|
/**
|
|
* Determine whether a fullscreen viewport should be used in cases where there are multiple players.
|
|
*
|
|
* @return TRUE to use a fullscreen viewport; FALSE to allow each player to have their own area of the viewport.
|
|
*/
|
|
native final function bool ShouldForceFullscreenViewport() const;
|
|
|
|
/**Function that allow for custom numbers of interactions dictated in code*/
|
|
native function int GetNumCustomInteractions();
|
|
/**Defining the above mentioned custom interactions*/
|
|
native function class<UIInteraction> GetCustomInteractionClass(int InIndex);
|
|
/**Passing the custom interaction object back to native code to do with it as it likes*/
|
|
native function SetCustomInteractionObject(Interaction InInteraction);
|
|
|
|
/** Function to notify GFx of a change in the splitscreen layout*/
|
|
native function NotifySplitscreenLayoutChanged();
|
|
|
|
/** Force the mouse cursor to update */
|
|
native function ForceUpdateMouseCursor(bool bSetCursor);
|
|
|
|
/** Move the hardware mouse cursor to the designated location*/
|
|
native function SetMouse(int X, int Y);
|
|
|
|
/**
|
|
* Adds a new player.
|
|
* @param ControllerId - The controller ID the player should accept input from.
|
|
* @param OutError - If no player is returned, OutError will contain a string describing the reason.
|
|
* @param SpawnActor - True if an actor should be spawned for the new player.
|
|
* @return The player which was created.
|
|
*/
|
|
event LocalPlayer CreatePlayer(int ControllerId, out string OutError, bool bSpawnActor)
|
|
{
|
|
local LocalPlayer NewPlayer;
|
|
local int InsertIndex;
|
|
|
|
`log("Creating new player with ControllerId" @ ControllerId @ "(" $ GamePlayers.Length @ "existing players)",,'PlayerManagement');
|
|
Assert(LocalPlayerClass != None);
|
|
|
|
NewPlayer = new(Outer) LocalPlayerClass;
|
|
NewPlayer.ViewportClient = Self;
|
|
NewPlayer.ControllerId = ControllerId;
|
|
|
|
InsertIndex = AddLocalPlayer(NewPlayer);
|
|
if ( bSpawnActor && InsertIndex != INDEX_NONE )
|
|
{
|
|
if (GetCurrentWorldInfo().NetMode != NM_Client)
|
|
{
|
|
// server; spawn a new PlayerController immediately
|
|
if (!NewPlayer.SpawnPlayActor("", OutError))
|
|
{
|
|
RemoveLocalPlayer(NewPlayer);
|
|
NewPlayer = None;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// client; ask the server to let the new player join
|
|
NewPlayer.SendSplitJoin();
|
|
}
|
|
}
|
|
|
|
if (OutError != "")
|
|
{
|
|
`Log("Player creation failed with error:" @ OutError);
|
|
}
|
|
else
|
|
{
|
|
`log("Successfully created new player with ControllerId" @ ControllerId $ ":" @ NewPlayer @ "- inserted into GamePlayers array at index" @ InsertIndex
|
|
@ "(" $ GamePlayers.Length @ "existing players)",,'PlayerManagement');
|
|
|
|
if ( NewPlayer != None && InsertIndex != INDEX_NONE )
|
|
{
|
|
// let all interactions know about this
|
|
NotifyPlayerAdded(InsertIndex, NewPlayer);
|
|
}
|
|
}
|
|
return NewPlayer;
|
|
}
|
|
|
|
/**
|
|
* Removes a player.
|
|
* @param Player - The player to remove.
|
|
* @return whether the player was successfully removed. Removal is not allowed while connected to a server.
|
|
*/
|
|
event bool RemovePlayer(LocalPlayer ExPlayer)
|
|
{
|
|
local int OldIndex, i;
|
|
//Mapping of: index into array is new index, value is old index
|
|
local array<int> IDMappings;
|
|
|
|
// can't destroy viewports while connected to a server
|
|
if (ExPlayer.Actor.Role == ROLE_Authority)
|
|
{
|
|
`log("Removing player" @ ExPlayer @ " with ControllerId" @ ExPlayer.ControllerId @ "at index" @ GamePlayers.Find(ExPlayer)@ "(" $ GamePlayers.Length @ "existing players)",,'PlayerManagement');
|
|
|
|
`if(`isdefined(FIXING_SIGNIN_ISSUES))
|
|
ScriptTrace();
|
|
`endif
|
|
|
|
// Disassociate this viewport client from the player.
|
|
ExPlayer.ViewportClient = None;
|
|
if ( ExPlayer.Actor != None )
|
|
{
|
|
// Destroy the player's actors.
|
|
ExPlayer.Actor.Destroy();
|
|
}
|
|
|
|
// Remove the player from the global and viewport lists of players.
|
|
OldIndex = RemoveLocalPlayer(ExPlayer);
|
|
if ( OldIndex != INDEX_NONE )
|
|
{
|
|
// let all interactions know about this
|
|
// NOTE: This is where GFx will clean up any movies that used to reference that index
|
|
NotifyPlayerRemoved(OldIndex, ExPlayer);
|
|
}
|
|
|
|
//Now we need to handle the fact that we may have screwed up the player indices for other gfx movies
|
|
|
|
//If they were equal we would have removed the last element, so we wouldn't need to do anything at all
|
|
if (OldIndex != GamePlayers.Length)
|
|
{
|
|
//Otherwise we have to cleanup
|
|
for (i = 0; i < GamePlayers.Length; i++)
|
|
{
|
|
//If it is below the removal, everything is the same
|
|
if (i < OldIndex)
|
|
{
|
|
IDMappings.AddItem(i);
|
|
}
|
|
else //If it is at the removal point or greater everything is one less than what it used to be
|
|
{
|
|
IDMappings.AddItem(i+1);
|
|
}
|
|
}
|
|
}
|
|
|
|
//If we constructed any IDMappings, then something must be fixed up, so do so
|
|
if (IDMappings.Length > 0)
|
|
{
|
|
FixupOwnerReferences(IDMappings);
|
|
}
|
|
|
|
`log("Finished removing player " @ ExPlayer @ " with ControllerId" @ ExPlayer.ControllerId @ "at index" @ OldIndex@ "(" $ GamePlayers.Length @ "remaining players)",,'PlayerManagement');
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
`log("Not removing player" @ ExPlayer @ " with ControllerId" @ ExPlayer.ControllerId @ "because player does not have appropriate role (" $ GetEnum(enum'ENetRole',ExPlayer.Actor.Role) $ ")",,'PlayerManagement');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds a player by controller ID.
|
|
* @param ControllerId - The controller ID to search for.
|
|
* @return None or the player with matching controller ID.
|
|
*/
|
|
final event LocalPlayer FindPlayerByControllerId(int ControllerId)
|
|
{
|
|
local int PlayerIndex;
|
|
for(PlayerIndex = 0;PlayerIndex < GamePlayers.Length;PlayerIndex++)
|
|
{
|
|
if(GamePlayers[PlayerIndex].ControllerId == ControllerId)
|
|
{
|
|
return GamePlayers[PlayerIndex];
|
|
}
|
|
}
|
|
return None;
|
|
}
|
|
|
|
`if(`notdefined(ShippingPC))
|
|
/**
|
|
* Debug console command to create a player.
|
|
* @param ControllerId - The controller ID the player should accept input from.
|
|
*/
|
|
exec function DebugCreatePlayer(int ControllerId)
|
|
{
|
|
local string Error;
|
|
|
|
CreatePlayer(ControllerId, Error, TRUE);
|
|
}
|
|
|
|
/** Rotates controller ids among gameplayers, useful for testing splitscreen with only one controller. */
|
|
exec function SSSwapControllers()
|
|
{
|
|
local int Idx, TmpControllerID;
|
|
TmpControllerID = GamePlayers[0].ControllerID;
|
|
|
|
for (Idx=0; Idx<GamePlayers.Length-1; ++Idx)
|
|
{
|
|
GamePlayers[Idx].ControllerID = GamePlayers[Idx+1].ControllerID;
|
|
}
|
|
GamePlayers[GamePlayers.Length-1].ControllerID = TmpControllerID;
|
|
}
|
|
|
|
/**
|
|
* Debug console command to remove the player with a given controller ID.
|
|
* @param ControllerId - The controller ID to search for.
|
|
*/
|
|
exec function DebugRemovePlayer(int ControllerId)
|
|
{
|
|
local LocalPlayer ExPlayer;
|
|
|
|
ExPlayer = FindPlayerByControllerId(ControllerId);
|
|
if(ExPlayer != None)
|
|
{
|
|
RemovePlayer(ExPlayer);
|
|
}
|
|
}
|
|
|
|
/** debug test for testing splitscreens */
|
|
exec function SetSplit( int mode )
|
|
{
|
|
SetSplitscreenConfiguration( ESplitScreenType(mode) );
|
|
}
|
|
|
|
/**
|
|
* Exec for toggling the display of the title safe area
|
|
*/
|
|
exec function ShowTitleSafeArea()
|
|
{
|
|
bShowTitleSafeZone = !bShowTitleSafeZone;
|
|
}
|
|
|
|
/**
|
|
* Sets the player which console commands will be executed in the context of.
|
|
*/
|
|
exec function SetConsoleTarget(int PlayerIndex)
|
|
{
|
|
if (ViewportConsole != none)
|
|
{
|
|
if(PlayerIndex >= 0 && PlayerIndex < GamePlayers.Length)
|
|
{
|
|
ViewportConsole.ConsoleTargetPlayer = GamePlayers[PlayerIndex];
|
|
}
|
|
else
|
|
{
|
|
ViewportConsole.ConsoleTargetPlayer = None;
|
|
}
|
|
}
|
|
}
|
|
`endif
|
|
|
|
/**
|
|
* Initialize the game viewport.
|
|
* @param OutError - If an error occurs, returns the error description.
|
|
* @return False if an error occurred, true if the viewport was initialized successfully.
|
|
*/
|
|
event bool Init(out string OutError)
|
|
{
|
|
local PlayerManagerInteraction PlayerInteraction;
|
|
local int NumCustomInteractions;
|
|
local class<UIInteraction> CustomInteractionClass;
|
|
local UIInteraction CustomInteraction;
|
|
local int Idx;
|
|
|
|
assert(Outer.ConsoleClass != None);
|
|
|
|
ActiveSplitscreenType = DesiredSplitscreenType;
|
|
|
|
`if(`notdefined(FINAL_RELEASE))
|
|
// Create the viewport's console.
|
|
ViewportConsole = new(Self) Outer.ConsoleClass;
|
|
if ( InsertInteraction(ViewportConsole) == -1 )
|
|
{
|
|
OutError = "Failed to add interaction to GlobalInteractions array:" @ ViewportConsole;
|
|
return false;
|
|
}
|
|
`endif
|
|
|
|
// Initialize custom interactions
|
|
NumCustomInteractions = GetNumCustomInteractions();
|
|
for ( Idx = 0; Idx < NumCustomInteractions; Idx++ )
|
|
{
|
|
CustomInteractionClass = GetCustomInteractionClass(Idx);
|
|
CustomInteraction = new(Self) CustomInteractionClass;
|
|
if ( InsertInteraction(CustomInteraction) == -1 )
|
|
{
|
|
OutError = "Failed to add interaction to GlobalInteractions array:" @ CustomInteraction;
|
|
return false;
|
|
}
|
|
SetCustomInteractionObject(CustomInteraction);
|
|
}
|
|
|
|
assert(UIControllerClass != None);
|
|
|
|
// Create a interaction to handle UI input.
|
|
UIController = new(Self) UIControllerClass;
|
|
if ( InsertInteraction(UIController) == -1 )
|
|
{
|
|
OutError = "Failed to add interaction to GlobalInteractions array:" @ UIController;
|
|
return false;
|
|
}
|
|
|
|
// Create the viewport's player management interaction.
|
|
PlayerInteraction = new(Self) class'PlayerManagerInteraction';
|
|
if ( InsertInteraction(PlayerInteraction) == -1 )
|
|
{
|
|
OutError = "Failed to add interaction to GlobalInteractions array:" @ PlayerInteraction;
|
|
return false;
|
|
}
|
|
|
|
// Disable the old UI system, if desired for debugging
|
|
if( bDebugNoGFxUI )
|
|
{
|
|
DebugSetUISystemEnabled(TRUE, FALSE);
|
|
}
|
|
|
|
// create the initial player - this is necessary or we can't render anything in-game.
|
|
return CreateInitialPlayer(OutError);
|
|
}
|
|
|
|
/**
|
|
* Create the game's initial player at startup. First search for a player that is signed into the OnlineSubsystem; if none are found,
|
|
* create a player with a ControllerId of 0.
|
|
*
|
|
* @param OutError receives the error string if an error occurs while creating the player.
|
|
*
|
|
* @return TRUE if a player was successfully created.
|
|
*/
|
|
function bool CreateInitialPlayer( out string OutError )
|
|
{
|
|
local int ControllerId;
|
|
local bool bFoundInitialGamepad, bResult;
|
|
|
|
for ( ControllerId = 0; ControllerId < class'UIRoot'.const.MAX_SUPPORTED_GAMEPADS; ControllerId++ )
|
|
{
|
|
if ( UIController.IsLoggedIn(ControllerId) )
|
|
{
|
|
bFoundInitialGamepad = true;
|
|
bResult = CreatePlayer(ControllerId, OutError, false) != None;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !bFoundInitialGamepad || !bResult )
|
|
{
|
|
// find the first connected gamepad
|
|
for ( ControllerId = 0; ControllerId < class'UIRoot'.const.MAX_SUPPORTED_GAMEPADS; ControllerId++ )
|
|
{
|
|
if ( UIController.IsGamepadConnected(ControllerId) )
|
|
{
|
|
bFoundInitialGamepad = true;
|
|
bResult = CreatePlayer(ControllerId, OutError, false) != None;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !bFoundInitialGamepad || !bResult )
|
|
{
|
|
bResult = CreatePlayer(0, OutError, false) != None;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
/**
|
|
* Inserts an interaction into the GlobalInteractions array at the specified index
|
|
*
|
|
* @param NewInteraction the interaction that should be inserted into the array
|
|
* @param Index the position in the GlobalInteractions array to insert the element.
|
|
* if no value (or -1) is specified, inserts the interaction at the end of the array
|
|
*
|
|
* @return the position in the GlobalInteractions array where the element was placed, or -1 if the element wasn't
|
|
* added to the array for some reason
|
|
*/
|
|
event int InsertInteraction( Interaction NewInteraction, optional int InIndex = -1 )
|
|
{
|
|
local int Result;
|
|
|
|
Result = -1;
|
|
if ( NewInteraction != None )
|
|
{
|
|
// if the specified index is -1, assume that the item should be added to the end of the array
|
|
if ( InIndex == -1 )
|
|
{
|
|
InIndex = GlobalInteractions.Length;
|
|
}
|
|
|
|
// if the index is a negative value other than -1, don't add the element as someone made a mistake
|
|
if ( InIndex >= 0 )
|
|
{
|
|
// clamp the Index to avoid expanding the array needlessly
|
|
Result = Clamp(InIndex, 0, GlobalInteractions.Length);
|
|
|
|
// now insert the item
|
|
GlobalInteractions.Insert(Result, 1);
|
|
GlobalInteractions[Result] = NewInteraction;
|
|
NewInteraction.Init();
|
|
NewInteraction.OnInitialize();
|
|
}
|
|
else
|
|
{
|
|
`warn("Invalid insertion index specified:" @ InIndex);
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Called when the current map is being unloaded. Cleans up any references which would prevent garbage collection.
|
|
*/
|
|
event GameSessionEnded()
|
|
{
|
|
local int i;
|
|
|
|
for ( i = 0; i < GlobalInteractions.Length; i++ )
|
|
{
|
|
GlobalInteractions[i].NotifyGameSessionEnded();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the screen layout configuration that the player wishes to use when in split-screen mode.
|
|
*/
|
|
function SetSplitscreenConfiguration( ESplitScreenType SplitType )
|
|
{
|
|
DesiredSplitscreenType = SplitType;
|
|
}
|
|
|
|
/**
|
|
* @return the actual splitscreen type being used, taking into account the number of players.
|
|
*/
|
|
function ESplitScreenType GetSplitscreenConfiguration()
|
|
{
|
|
return ActiveSplitscreenType;
|
|
}
|
|
|
|
/**
|
|
* Sets the value of ActiveSplitscreenConfiguration based on the desired split-screen layout type, current number of players, and any other
|
|
* factors that might affect the way the screen should be layed out.
|
|
*/
|
|
function UpdateActiveSplitscreenType()
|
|
{
|
|
local ESplitScreenType SplitType;
|
|
|
|
SplitType = DesiredSplitscreenType;
|
|
switch ( GamePlayers.Length )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
SplitType = eSST_NONE;
|
|
break;
|
|
|
|
case 2:
|
|
if ( (SplitType != eSST_2P_HORIZONTAL) && (SplitType != eSST_2P_VERTICAL) )
|
|
{
|
|
SplitType = Default2PSplitType;
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
if ( (SplitType != eSST_3P_FAVOR_TOP) && (SplitType != eSST_3P_FAVOR_BOTTOM) )
|
|
{
|
|
SplitType = Default3PSplitType;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
SplitType = eSST_4P;
|
|
break;
|
|
}
|
|
|
|
ActiveSplitscreenType = SplitType;
|
|
}
|
|
|
|
/**
|
|
* Called before rendering to allow the game viewport to allocate subregions to players.
|
|
*/
|
|
event LayoutPlayers()
|
|
{
|
|
local int Idx;
|
|
local ESplitScreenType SplitType, PreviousSplitType;
|
|
|
|
PreviousSplitType = GetSplitscreenConfiguration();
|
|
UpdateActiveSplitscreenType();
|
|
SplitType = GetSplitscreenConfiguration();
|
|
// Initialize the players
|
|
for ( Idx = 0; Idx < GamePlayers.Length; Idx++ )
|
|
{
|
|
if ( SplitType < SplitscreenInfo.Length && Idx < SplitscreenInfo[SplitType].PlayerData.Length )
|
|
{
|
|
GamePlayers[Idx].Size.X = SplitscreenInfo[SplitType].PlayerData[Idx].SizeX;
|
|
GamePlayers[Idx].Size.Y = SplitscreenInfo[SplitType].PlayerData[Idx].SizeY;
|
|
GamePlayers[Idx].Origin.X = SplitscreenInfo[SplitType].PlayerData[Idx].OriginX;
|
|
GamePlayers[Idx].Origin.Y = SplitscreenInfo[SplitType].PlayerData[Idx].OriginY;
|
|
}
|
|
else
|
|
{
|
|
GamePlayers[Idx].Size.X = 0.f;
|
|
GamePlayers[Idx].Size.Y = 0.f;
|
|
GamePlayers[Idx].Origin.X = 0.f;
|
|
GamePlayers[Idx].Origin.Y = 0.f;
|
|
}
|
|
}
|
|
|
|
//If splitscreen type has changed, update GFx
|
|
if (PreviousSplitType != SplitType)
|
|
{
|
|
NotifySplitscreenLayoutChanged();
|
|
}
|
|
}
|
|
|
|
/** called before rending subtitles to allow the game viewport to determine the size of the subtitle area
|
|
* @param Min top left bounds of subtitle region (0 to 1)
|
|
* @param Max bottom right bounds of subtitle region (0 to 1)
|
|
*/
|
|
event GetSubtitleRegion(out vector2D MinPos, out vector2D MaxPos)
|
|
{
|
|
MaxPos.X = 1.0f;
|
|
MaxPos.Y = (GamePlayers.length == 1) ? 0.9f : 0.5f;
|
|
}
|
|
|
|
/**
|
|
* Convert a LocalPlayer to it's index in the GamePlayer array
|
|
* Returns -1 if the index could not be found.
|
|
*/
|
|
final function int ConvertLocalPlayerToGamePlayerIndex( LocalPlayer LPlayer )
|
|
{
|
|
return GamePlayers.Find( LPlayer );
|
|
}
|
|
|
|
/**
|
|
* Whether the player at LocalPlayerIndex's viewport has a "top of viewport" safezone or not.
|
|
*/
|
|
final function bool HasTopSafeZone( int LocalPlayerIndex )
|
|
{
|
|
switch ( GetSplitscreenConfiguration() )
|
|
{
|
|
case eSST_NONE:
|
|
case eSST_2P_VERTICAL:
|
|
return true;
|
|
|
|
case eSST_2P_HORIZONTAL:
|
|
case eSST_3P_FAVOR_TOP:
|
|
return (LocalPlayerIndex == 0) ? true : false;
|
|
|
|
case eSST_3P_FAVOR_BOTTOM:
|
|
case eSST_4P:
|
|
return (LocalPlayerIndex < 2) ? true : false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Whether the player at LocalPlayerIndex's viewport has a "bottom of viewport" safezone or not.
|
|
*/
|
|
final function bool HasBottomSafeZone( int LocalPlayerIndex )
|
|
{
|
|
switch ( GetSplitscreenConfiguration() )
|
|
{
|
|
case eSST_NONE:
|
|
case eSST_2P_VERTICAL:
|
|
return true;
|
|
|
|
case eSST_2P_HORIZONTAL:
|
|
case eSST_3P_FAVOR_TOP:
|
|
return (LocalPlayerIndex == 0) ? false : true;
|
|
|
|
case eSST_3P_FAVOR_BOTTOM:
|
|
case eSST_4P:
|
|
return (LocalPlayerIndex > 1) ? true : false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Whether the player at LocalPlayerIndex's viewport has a "left of viewport" safezone or not.
|
|
*/
|
|
final function bool HasLeftSafeZone( int LocalPlayerIndex )
|
|
{
|
|
switch ( GetSplitscreenConfiguration() )
|
|
{
|
|
case eSST_NONE:
|
|
case eSST_2P_HORIZONTAL:
|
|
return true;
|
|
|
|
case eSST_2P_VERTICAL:
|
|
return (LocalPlayerIndex == 0) ? true : false;
|
|
|
|
case eSST_3P_FAVOR_TOP:
|
|
return (LocalPlayerIndex < 2) ? true : false;
|
|
|
|
case eSST_3P_FAVOR_BOTTOM:
|
|
case eSST_4P:
|
|
return (LocalPlayerIndex == 0 || LocalPlayerIndex == 2) ? true : false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Whether the player at LocalPlayerIndex's viewport has a "right of viewport" safezone or not.
|
|
*/
|
|
final function bool HasRightSafeZone( int LocalPlayerIndex )
|
|
{
|
|
switch ( GetSplitscreenConfiguration() )
|
|
{
|
|
case eSST_NONE:
|
|
case eSST_2P_HORIZONTAL:
|
|
return true;
|
|
|
|
case eSST_2P_VERTICAL:
|
|
case eSST_3P_FAVOR_BOTTOM:
|
|
return (LocalPlayerIndex > 0) ? true : false;
|
|
|
|
case eSST_3P_FAVOR_TOP:
|
|
return (LocalPlayerIndex == 1) ? false : true;
|
|
|
|
case eSST_4P:
|
|
return (LocalPlayerIndex == 0 || LocalPlayerIndex == 2) ? false : true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the total pixel size of the screen.
|
|
* This is different from the pixel size of the viewport since we could be in splitscreen
|
|
*/
|
|
final function GetPixelSizeOfScreen( out float out_Width, out float out_Height, canvas Canvas, int LocalPlayerIndex )
|
|
{
|
|
switch ( GetSplitscreenConfiguration() )
|
|
{
|
|
case eSST_NONE:
|
|
out_Width = Canvas.ClipX;
|
|
out_Height = Canvas.ClipY;
|
|
return;
|
|
case eSST_2P_HORIZONTAL:
|
|
out_Width = Canvas.ClipX;
|
|
out_Height = Canvas.ClipY * 2;
|
|
return;
|
|
case eSST_2P_VERTICAL:
|
|
out_Width = Canvas.ClipX * 2;
|
|
out_Height = Canvas.ClipY;
|
|
return;
|
|
case eSST_3P_FAVOR_TOP:
|
|
if ( LocalPlayerIndex == 0 )
|
|
{
|
|
out_Width = Canvas.ClipX;
|
|
}
|
|
else
|
|
{
|
|
out_Width = Canvas.ClipX * 2;
|
|
}
|
|
out_Height = Canvas.ClipY * 2;
|
|
return;
|
|
case eSST_3P_FAVOR_BOTTOM:
|
|
if ( LocalPlayerIndex == 2 )
|
|
{
|
|
out_Width = Canvas.ClipX;
|
|
}
|
|
else
|
|
{
|
|
out_Width = Canvas.ClipX * 2;
|
|
}
|
|
out_Height = Canvas.ClipY * 2;
|
|
return;
|
|
case eSST_4P:
|
|
out_Width = Canvas.ClipX * 2;
|
|
out_Height = Canvas.ClipY * 2;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the amount of safezone needed for a single side for both vertical and horizontal dimensions
|
|
*/
|
|
final function CalculateSafeZoneValues( out float out_Horizontal, out float out_Vertical, canvas Canvas, int LocalPlayerIndex, bool bUseMaxPercent )
|
|
{
|
|
local float ScreenWidth, ScreenHeight, XSafeZoneToUse, YSafeZoneToUse;
|
|
|
|
XSafeZoneToUse = bUseMaxPercent ? TitleSafeZone.MaxPercentX : TitleSafeZone.RecommendedPercentX;
|
|
YSafeZoneToUse = bUseMaxPercent ? TitleSafeZone.MaxPercentY : TitleSafeZone.RecommendedPercentY;
|
|
|
|
GetPixelSizeOfScreen( ScreenWidth, ScreenHeight, Canvas, LocalPlayerIndex );
|
|
out_Horizontal = (ScreenWidth * (1 - XSafeZoneToUse) / 2.0f);
|
|
out_Vertical = (ScreenHeight * (1 - YSafeZoneToUse) / 2.0);
|
|
}
|
|
|
|
/*
|
|
* Return true if the safe zone exists
|
|
* pixel size of the deadzone for all sides (right/left/top/bottom) based on which local player it is
|
|
*/
|
|
final function bool CalculateDeadZoneForAllSides( LocalPlayer LPlayer, Canvas Canvas, out float fTopSafeZone, out float fBottomSafeZone, out float fLeftSafeZone, out float fRightSafeZone, optional bool bUseMaxPercent )
|
|
{
|
|
// save separate - if the split screen is in bottom right, then
|
|
local bool bHasTopSafeZone, bHasBottomSafeZone, bHasRightSafeZone, bHasLeftSafeZone;
|
|
local int LocalPlayerIndex;
|
|
local float HorizSafeZoneValue, VertSafeZoneValue;
|
|
|
|
if ( LPlayer != None )
|
|
{
|
|
LocalPlayerIndex = ConvertLocalPlayerToGamePlayerIndex( LPlayer );
|
|
|
|
if ( LocalPlayerIndex != -1 )
|
|
{
|
|
// see if this player should have a safe zone for any particular zonetype
|
|
bHasTopSafeZone = HasTopSafeZone( LocalPlayerIndex );
|
|
bHasBottomSafeZone = HasBottomSafeZone( LocalPlayerIndex );
|
|
bHasLeftSafeZone = HasLeftSafeZone( LocalPlayerIndex );
|
|
bHasRightSafeZone = HasRightSafeZone( LocalPlayerIndex );
|
|
|
|
// if they need a safezone, then calculate it and save it
|
|
if ( bHasTopSafeZone || bHasBottomSafeZone || bHasLeftSafeZone || bHasRightSafeZone)
|
|
{
|
|
// calculate the safezones
|
|
CalculateSafeZoneValues( HorizSafeZoneValue, VertSafeZoneValue, Canvas, LocalPlayerIndex, bUseMaxPercent );
|
|
|
|
if (bHasTopSafeZone)
|
|
{
|
|
fTopSafeZone = VertSafeZoneValue;
|
|
}
|
|
else
|
|
{
|
|
fTopSafeZone = 0.f;
|
|
}
|
|
|
|
if (bHasBottomSafeZone)
|
|
{
|
|
fBottomSafeZone = VertSafeZoneValue;
|
|
}
|
|
else
|
|
{
|
|
fBottomSafeZone = 0.f;
|
|
}
|
|
|
|
if (bHasLeftSafeZone)
|
|
{
|
|
fLeftSafeZone = HorizSafeZoneValue;
|
|
}
|
|
else
|
|
{
|
|
fLeftSafeZone = 0.f;
|
|
}
|
|
|
|
if (bHasRightSafeZone)
|
|
{
|
|
fRightSafeZone = HorizSafeZoneValue;
|
|
}
|
|
else
|
|
{
|
|
fRightSafeZone = 0.f;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Called every frame to allow the game viewport to update time based state.
|
|
* @param DeltaTime - The time since the last call to Tick.
|
|
*/
|
|
event Tick(float DeltaTime);
|
|
|
|
/**
|
|
* Draw the safe area using the current TitleSafeZone settings
|
|
*/
|
|
function DrawTitleSafeArea( canvas Canvas )
|
|
{
|
|
// red colored max safe area box
|
|
Canvas.SetDrawColor(255,0,0,255);
|
|
Canvas.SetPos(Canvas.ClipX * (1 - TitleSafeZone.MaxPercentX) / 2.0, Canvas.ClipY * (1 - TitleSafeZone.MaxPercentY) / 2.0);
|
|
Canvas.DrawBox(Canvas.ClipX * TitleSafeZone.MaxPercentX, Canvas.ClipY * TitleSafeZone.MaxPercentY);
|
|
|
|
// yellow colored recommended safe area box
|
|
Canvas.SetDrawColor(255,255,0,255);
|
|
Canvas.SetPos(Canvas.ClipX * (1 - TitleSafeZone.RecommendedPercentX) / 2.0, Canvas.ClipY * (1 - TitleSafeZone.RecommendedPercentY) / 2.0);
|
|
Canvas.DrawBox(Canvas.ClipX * TitleSafeZone.RecommendedPercentX, Canvas.ClipY * TitleSafeZone.RecommendedPercentY);
|
|
}
|
|
|
|
/**
|
|
* Called after rendering the player views and HUDs to render menus, the console, etc.
|
|
* This is the last rendering call in the render loop
|
|
* @param Canvas - The canvas to use for rendering.
|
|
*/
|
|
event PostRender(Canvas Canvas)
|
|
{
|
|
if( bShowTitleSafeZone )
|
|
{
|
|
DrawTitleSafeArea(Canvas);
|
|
}
|
|
|
|
if (ViewportConsole != none)
|
|
{
|
|
// Render the console.
|
|
ViewportConsole.PostRender_Console(Canvas);
|
|
}
|
|
|
|
// Draw the transition screen.
|
|
DrawTransition(Canvas);
|
|
}
|
|
|
|
/**
|
|
* display progress messages in center of screen
|
|
*/
|
|
function DisplayProgressMessage(Canvas Canvas)
|
|
{
|
|
local int i, LineCount;
|
|
local float FontDX, FontDY;
|
|
local float X, Y;
|
|
local byte Alpha;
|
|
local float TimeLeft;
|
|
|
|
TimeLeft = ProgressTimeOut - class'Engine'.static.GetCurrentWorldInfo().TimeSeconds;
|
|
Alpha = (TimeLeft >= ProgressFadeTime) ? 255 : byte((255 * TimeLeft) / ProgressFadeTime);
|
|
|
|
LineCount = 0;
|
|
|
|
for (i = 0; i < ArrayCount(ProgressMessage); i++)
|
|
{
|
|
if (ProgressMessage[i] != "")
|
|
{
|
|
LineCount++;
|
|
}
|
|
}
|
|
|
|
Canvas.Font = class'Engine'.Static.GetMediumFont();
|
|
Canvas.TextSize ("A", FontDX, FontDY);
|
|
|
|
X = (0.5 * Canvas.SizeX);
|
|
Y = (0.5 * Canvas.SizeY);
|
|
|
|
Y -= FontDY * (float(LineCount) / 2.0);
|
|
|
|
Canvas.DrawColor.R = 255;
|
|
Canvas.DrawColor.G = 255;
|
|
Canvas.DrawColor.B = 255;
|
|
for (i = 0; i < ArrayCount(ProgressMessage); i++)
|
|
{
|
|
if (ProgressMessage[i] != "")
|
|
{
|
|
Canvas.DrawColor.A = Alpha;
|
|
|
|
Canvas.TextSize(ProgressMessage[i], FontDX, FontDY);
|
|
Canvas.SetPos(X - (FontDX / 2.0), Y);
|
|
Canvas.DrawText(ProgressMessage[i]);
|
|
|
|
Y += FontDY;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Displays the transition screen.
|
|
* @param Canvas - The canvas to use for rendering.
|
|
*/
|
|
function DrawTransition(Canvas Canvas)
|
|
{
|
|
switch(Outer.TransitionType)
|
|
{
|
|
case TT_Loading:
|
|
DrawTransitionMessage(Canvas,LoadingMessage);
|
|
break;
|
|
case TT_Saving:
|
|
DrawTransitionMessage(Canvas,SavingMessage);
|
|
break;
|
|
case TT_Connecting:
|
|
DrawTransitionMessage(Canvas,ConnectingMessage);
|
|
break;
|
|
case TT_Precaching:
|
|
DrawTransitionMessage(Canvas,PrecachingMessage);
|
|
break;
|
|
case TT_Paused:
|
|
DrawTransitionMessage(Canvas,PausedMessage);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print a centered transition message with a drop shadow.
|
|
*/
|
|
function DrawTransitionMessage(Canvas Canvas,string Message)
|
|
{
|
|
local float XL, YL;
|
|
|
|
Canvas.Font = class'Engine'.Static.GetLargeFont();
|
|
Canvas.bCenter = false;
|
|
Canvas.StrLen( Message, XL, YL );
|
|
Canvas.SetPos(0.5 * (Canvas.ClipX - XL) + 1, 0.66 * Canvas.ClipY - YL * 0.5 + 1);
|
|
Canvas.SetDrawColor(0,0,0);
|
|
Canvas.DrawText( Message, false );
|
|
Canvas.SetPos(0.5 * (Canvas.ClipX - XL), 0.66 * Canvas.ClipY - YL * 0.5);
|
|
Canvas.SetDrawColor(0,0,255);;
|
|
Canvas.DrawText( Message, false );
|
|
}
|
|
|
|
/**
|
|
* Notifies all interactions that a new player has been added to the list of active players.
|
|
*
|
|
* @param PlayerIndex the index [into the GamePlayers array] where the player was inserted
|
|
* @param AddedPlayer the player that was added
|
|
*/
|
|
final function NotifyPlayerAdded( int PlayerIndex, LocalPlayer AddedPlayer )
|
|
{
|
|
local int InteractionIndex;
|
|
|
|
LayoutPlayers();
|
|
for ( InteractionIndex = 0; InteractionIndex < GlobalInteractions.Length; InteractionIndex++ )
|
|
{
|
|
if ( GlobalInteractions[InteractionIndex] != None )
|
|
{
|
|
GlobalInteractions[InteractionIndex].NotifyPlayerAdded(PlayerIndex, AddedPlayer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies all interactions that a new player has been added to the list of active players.
|
|
*
|
|
* @param PlayerIndex the index [into the GamePlayers array] where the player was located
|
|
* @param RemovedPlayer the player that was removed
|
|
*/
|
|
final function NotifyPlayerRemoved( int PlayerIndex, LocalPlayer RemovedPlayer )
|
|
{
|
|
local int InteractionIndex;
|
|
|
|
LayoutPlayers();
|
|
for ( InteractionIndex = GlobalInteractions.Length - 1; InteractionIndex >= 0; InteractionIndex-- )
|
|
{
|
|
if ( GlobalInteractions[InteractionIndex] != None )
|
|
{
|
|
GlobalInteractions[InteractionIndex].NotifyPlayerRemoved(PlayerIndex, RemovedPlayer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a LocalPlayer to the local and global list of Players.
|
|
*
|
|
* @param NewPlayer the player to add
|
|
*/
|
|
private final function int AddLocalPlayer( LocalPlayer NewPlayer )
|
|
{
|
|
local int InsertIndex;
|
|
|
|
InsertIndex = INDEX_NONE;
|
|
if ( NewPlayer != None )
|
|
{
|
|
// add to list
|
|
InsertIndex = GamePlayers.Length;
|
|
GamePlayers[InsertIndex] = NewPlayer;
|
|
}
|
|
return InsertIndex;
|
|
}
|
|
|
|
/**
|
|
* Removes a LocalPlayer from the local and global list of Players.
|
|
*
|
|
* @param ExistingPlayer the player to remove
|
|
*/
|
|
private final function int RemoveLocalPlayer( LocalPlayer ExistingPlayer )
|
|
{
|
|
local int Index;
|
|
|
|
Index = GamePlayers.Find(ExistingPlayer);
|
|
if ( Index != INDEX_NONE )
|
|
{
|
|
GamePlayers.Remove(Index,1);
|
|
}
|
|
|
|
return Index;
|
|
}
|
|
|
|
/** handler for global state messages, generally network connection related (failures, download progress, etc) */
|
|
event SetProgressMessage(EProgressMessageType MessageType, string Message, optional string Title, optional bool bIgnoreFutureNetworkMessages)
|
|
{
|
|
local WorldInfo WI;
|
|
|
|
WI = class'Engine'.static.GetCurrentWorldInfo();
|
|
|
|
if (MessageType == PMT_Clear)
|
|
{
|
|
ClearProgressMessages();
|
|
}
|
|
else
|
|
{
|
|
if (MessageType == PMT_ConnectionFailure || MessageType == PMT_SocketFailure)
|
|
{
|
|
// Attempt to start host migration on connection failure
|
|
if (WI != None &&
|
|
WI.NetMode == NM_Client &&
|
|
WI.BeginHostMigration())
|
|
{
|
|
`Log(`location @ "MessageType="$MessageType @ "Message="$Message @ ": host migration started.. "$WI.PeerHostMigration.HostMigrationProgress,,'DevNet');
|
|
}
|
|
//@FIXME: bIgnoreNetworkMessages needs to die
|
|
else if (!Outer.GamePlayers[0].Actor.bIgnoreNetworkMessages)
|
|
{
|
|
`Log(`location @ "MessageType="$MessageType @ "Message="$Message @ ": host migration not enabled.. handling connection error.",,'DevNet');
|
|
|
|
NotifyConnectionError(MessageType, Message, Title);
|
|
}
|
|
}
|
|
else if (MessageType == PMT_PeerHostMigrationFailure)
|
|
{
|
|
// Host migration was started but failed so just fallback to network error handling
|
|
Outer.GamePlayers[0].Actor.bIgnoreNetworkMessages = false;
|
|
NotifyConnectionError(MessageType, Message, Title);
|
|
}
|
|
else
|
|
{
|
|
if (Title != "")
|
|
{
|
|
ProgressMessage[0] = Title;
|
|
ProgressMessage[1] = Message;
|
|
}
|
|
else
|
|
{
|
|
ProgressMessage[1] = "";
|
|
ProgressMessage[0] = Message;
|
|
}
|
|
}
|
|
}
|
|
//@FIXME: bIgnoreNetworkMessages needs to die
|
|
if (!Outer.GamePlayers[0].Actor.bIgnoreNetworkMessages)
|
|
{
|
|
Outer.GamePlayers[0].Actor.bIgnoreNetworkMessages = bIgnoreFutureNetworkMessages;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies the player that an attempt to connect to a remote server failed, or an existing connection was dropped.
|
|
*
|
|
* @param MessageType EProgressMessageType of current connection error
|
|
* @param Message a description of why the connection was lost
|
|
* @param Title the title to use in the connection failure message.
|
|
*/
|
|
function NotifyConnectionError(EProgressMessageType MessageType, optional string Message=Localize("Errors", "ConnectionFailed", "Engine"), optional string Title=Localize("Errors", "ConnectionFailed_Title", "Engine") )
|
|
{
|
|
local WorldInfo WI;
|
|
|
|
WI = class'Engine'.static.GetCurrentWorldInfo();
|
|
`log(`location @ `showvar(Title) @ `showvar(Message) @ `showenum(ENetMode,WI.NetMode,NetMode) @ `showvar(WI.GetURLMap(),Map) ,,'DevNet');
|
|
if (WI.NetMode != NM_Standalone)
|
|
{
|
|
if ( WI.Game != None )
|
|
{
|
|
// Mark the server as having a problem
|
|
WI.Game.bHasNetworkError = true;
|
|
}
|
|
|
|
//@todo: should we have a Travel() function in this class?
|
|
ConsoleCommand("start ?failed");
|
|
}
|
|
}
|
|
|
|
exec event SetProgressTime(float T)
|
|
{
|
|
ProgressTimeOut = T + class'Engine'.static.GetCurrentWorldInfo().TimeSeconds;
|
|
}
|
|
|
|
exec function ClearProgressMessages()
|
|
{
|
|
local int i;
|
|
|
|
for (i=0; i<ArrayCount(ProgressMessage); i++)
|
|
{
|
|
ProgressMessage[i] = "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves a reference to a LocalPlayer.
|
|
*
|
|
* @param PlayerIndex if specified, returns the player at this index in the GamePlayers array. Otherwise, returns
|
|
* the player associated with the owner scene.
|
|
*
|
|
* @return the player that owns this scene or is located in the specified index of the GamePlayers array.
|
|
*/
|
|
native final function LocalPlayer GetPlayerOwner(int PlayerIndex);
|
|
|
|
/** Called after the primary player has been changed so that the UI references to the owner are switched */
|
|
native final function FixupOwnerReferences(array<int> IDMappings);
|
|
|
|
/** Called after a new primary player has been made */
|
|
function OnPrimaryPlayerSwitch(LocalPlayer OldPrimaryPlayer, LocalPlayer NewPrimaryPlayer);
|
|
|
|
/**
|
|
* Makes a player the primary player
|
|
* @param PlayerIndex - The index of the player to be made into the primary player
|
|
*/
|
|
function BecomePrimaryPlayer(int PlayerIndex)
|
|
{
|
|
local array<LocalPlayer> OtherPlayers;
|
|
local LocalPlayer PlayerOwner, NextPlayer, OriginalPrimaryPlayer;
|
|
local int NumPlayersRemoved;
|
|
local int i, count;
|
|
//Mapping of: index into array is new index, value is old index
|
|
local array<int> IDMappings;
|
|
|
|
if (UIController != None && PlayerIndex > 0 && PlayerIndex < UIController.GetPlayerCount())
|
|
{
|
|
OriginalPrimaryPlayer = GetPlayerOwner(0);
|
|
|
|
// get the player that owns this scene
|
|
PlayerOwner = GetPlayerOwner(PlayerIndex);
|
|
if (PlayerOwner == None)
|
|
{
|
|
`log("GameViewportClient:BecomePrimaryPlayer has failed to find the player owner for index" @ PlayerIndex @ "ABORTING!!!");
|
|
return;
|
|
}
|
|
|
|
if (PlayerOwner != None)
|
|
{
|
|
NextPlayer = OriginalPrimaryPlayer;
|
|
NumPlayersRemoved = 0;
|
|
while (NextPlayer != None && NextPlayer != PlayerOwner)
|
|
{
|
|
// the easiest way to ensure that everything is updated properly is to simulate the player being removed;
|
|
// do it manually so that their PlayerController and stuff aren't destroyed.
|
|
UIController.NotifyPlayerRemoved(0, NextPlayer);
|
|
UIController.Outer.Outer.GamePlayers.Remove(0, 1);
|
|
|
|
// we need to re-add the player so keep them in a temporary list
|
|
OtherPlayers.AddItem(NextPlayer);
|
|
|
|
NextPlayer = GetPlayerOwner(0);
|
|
|
|
NumPlayersRemoved++;
|
|
}
|
|
|
|
//Update mapping for all movies that just got shifted downwards
|
|
for (i = 0; i < UIController.Outer.Outer.GamePlayers.length; i++)
|
|
{
|
|
IDMappings.AddItem(i+NumPlayersRemoved);
|
|
}
|
|
|
|
// now re-add the previous players to the GamePlayers array.
|
|
count = 0;
|
|
while (OtherPlayers.Length > 0)
|
|
{
|
|
NextPlayer = OtherPlayers[0];
|
|
|
|
UIController.Outer.Outer.GamePlayers.AddItem(NextPlayer);
|
|
UIController.NotifyPlayerAdded(UIController.Outer.Outer.GamePlayers.length-1, NextPlayer);
|
|
|
|
OtherPlayers.Remove(0, 1);
|
|
|
|
IDMappings.AddItem(count);
|
|
count++;
|
|
}
|
|
}
|
|
|
|
// if we have a new primary player, reload their profile so that their settings will be applied and fixup references
|
|
NextPlayer = GetPlayerOwner(0);
|
|
if (OriginalPrimaryPlayer != NextPlayer)
|
|
{
|
|
// Players switched so reevaluate the viewports
|
|
LayoutPlayers();
|
|
FixupOwnerReferences(IDMappings);
|
|
NextPlayer.Actor.ReloadProfileSettings();
|
|
OnPrimaryPlayerSwitch(OriginalPrimaryPlayer, NextPlayer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Function to enable Scaleform processing/rendering */
|
|
native final function EnableScaleform();
|
|
|
|
/** Function to disable Scaleform processing/rendering */
|
|
native final function DisableScaleform();
|
|
|
|
/** Function to find out if Scaleform is enabled/disabled */
|
|
native final function bool IsScaleformEnabled();
|
|
|
|
/** DEBUG: function to easily allow script to turn on / off the two UI systems for developing during the transition from the old UI to the new GFx UI */
|
|
native function DebugSetUISystemEnabled(bool bOldUISystemActive, bool bGFxUISystemActive);
|
|
|
|
/**
|
|
* Function to set the hardware mouse cursor visibility
|
|
*
|
|
* @param bIsVisible - whether or not the cursor is visible
|
|
*/
|
|
simulated event SetHardwareMouseCursorVisibility(bool bIsVisible)
|
|
{
|
|
local Vector2D ViewportSize;
|
|
|
|
//If we are going to be turning on the hardware cursor when it was not already on, we will move the cursor to the middle of the screen
|
|
if (bIsVisible && !bDisplayHardwareMouseCursor)
|
|
{
|
|
GetViewportSize(ViewportSize);
|
|
SetMouse(ViewportSize.X/2,ViewportSize.Y/2);
|
|
}
|
|
bDisplayHardwareMouseCursor = bIsVisible;
|
|
|
|
ForceUpdateMouseCursor(TRUE);
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
UIControllerClass=class'Engine.UIInteraction'
|
|
TitleSafeZone=(MaxPercentX=0.9,MaxPercentY=0.9,RecommendedPercentX=0.8,RecommendedPercentY=0.8)
|
|
|
|
Default2PSplitType=eSST_2P_HORIZONTAL
|
|
Default3PSplitType=eSST_3P_FAVOR_TOP
|
|
DesiredSplitscreenType=eSST_NONE
|
|
|
|
bIsPlayInEditorViewport=False
|
|
bShowSystemMouseCursor=False
|
|
|
|
ProgressFadeTime=1.0
|
|
ProgressTimeOut=8.0
|
|
|
|
SplitscreenInfo(eSST_None)= (PlayerData=((SizeX=1.0f,SizeY=1.0f,OriginX=0.0f,OriginY=0.0f)))
|
|
|
|
SplitscreenInfo(eSST_2P_HORIZONTAL)={(PlayerData=(
|
|
(SizeX=1.0f,SizeY=0.5f,OriginX=0.0f,OriginY=0.0f),
|
|
(SizeX=1.0f,SizeY=0.5f,OriginX=0.0f,OriginY=0.5f))
|
|
)}
|
|
|
|
SplitscreenInfo(eSST_2P_VERTICAL)={(PlayerData=(
|
|
(SizeX=0.5f,SizeY=1.0f,OriginX=0.0f,OriginY=0.0f),
|
|
(SizeX=0.5f,SizeY=1.0f,OriginX=0.5f,OriginY=0.0))
|
|
)}
|
|
|
|
SplitscreenInfo(eSST_3P_FAVOR_TOP)={(PlayerData=(
|
|
(SizeX=1.0f,SizeY=0.5f,OriginX=0.0f,OriginY=0.0f),
|
|
(SizeX=0.5f,SizeY=0.5f,OriginX=0.0f,OriginY=0.5f),
|
|
(SizeX=0.5f,SizeY=0.5f,OriginX=0.5f,OriginY=0.5f))
|
|
)}
|
|
|
|
SplitscreenInfo(eSST_3P_FAVOR_BOTTOM)={(PlayerData=(
|
|
(SizeX=0.5f,SizeY=0.5f,OriginX=0.0f,OriginY=0.0f),
|
|
(SizeX=0.5f,SizeY=0.5f,OriginX=0.5f,OriginY=0.0f),
|
|
(SizeX=1.0f,SizeY=0.5f,OriginX=0.0f,OriginY=0.5f))
|
|
)}
|
|
|
|
SplitscreenInfo(eSST_4P)={(PlayerData=(
|
|
(SizeX=0.5f,SizeY=0.5f,OriginX=0.0f,OriginY=0.0f),
|
|
(SizeX=0.5f,SizeY=0.5f,OriginX=0.5f,OriginY=0.0f),
|
|
(SizeX=0.5f,SizeY=0.5f,OriginX=0.0f,OriginY=0.5f),
|
|
(SizeX=0.5f,SizeY=0.5f,OriginX=0.5f,OriginY=0.5f))
|
|
)}
|
|
}
|