1
0
This commit is contained in:
2020-12-13 18:01:13 +03:00
commit dd42f84140
3764 changed files with 596895 additions and 0 deletions

View File

@ -0,0 +1,278 @@
//-----------------------------------------------------------
// Debug Camera Controller
//
// To turn it on, please press Alt+C or both (left and right) analogs on xbox pad
// After turning:
// WASD | Left Analog - moving
// Mouse | Right Analog - rotating
// Shift | XBOX_KeyB - move faster
// Q/E | LT/RT - move Up/Down
// Enter | XBOX_KeyA - to call "FreezeRendering" console command
// Alt+C | LeftThumbstick - to toggle debug camera
//
// * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//-----------------------------------------------------------
class DebugCameraController extends GamePlayerController
config(Input)
native;
/** The key that triggers PrimarySelect(). */
var globalconfig name PrimaryKey;
/** The key that triggers SecondarySelect(). */
var globalconfig name SecondaryKey;
/** The key that triggers Unselect(). */
var globalconfig name UnselectKey;
/** Whether to show information about the selected actor on the debug camera HUD. */
var globalconfig bool bShowSelectedInfo;
/** When true the debug text is drawn. This is passed through from CheatManager's ToggleDebugCamera exec function. */
var bool bDrawDebugText;
/** HUD class to spawn. */
var class<HUD> HUDClass;
var PlayerController OriginalControllerRef;
var Player OriginalPlayer;
var bool bIsFrozenRendering;
var DrawFrustumComponent DrawFrustum;
var Actor SelectedActor;
var PrimitiveComponent SelectedComponent;
/**
* Called when an actor has been selected with the primary key (e.g. left mouse button).
*
* @param HitLoc World-space position of the selection point.
* @param HitNormal World-space normal of the selection point.
* @param HitInfo Info struct for the selection point.
*/
native function PrimarySelect( vector HitLoc, vector HitNormal, TraceHitInfo HitInfo );
/**
* Called when an actor has been selected with the secondary key (e.g. right mouse button).
*
* @param HitLoc World-space position of the selection point.
* @param HitNormal World-space normal of the selection point.
* @param HitInfo Info struct for the selection point.
*/
native function SecondarySelect( vector HitLoc, vector HitNormal, TraceHitInfo HitInfo );
/**
* Called when the user pressed the unselect key, just before the selected actor is cleared.
*/
native function Unselect();
simulated event PostBeginPlay()
{
super.PostBeginPlay();
// if hud is existing, delete it and create new hud for debug camera
if ( myHUD != None )
myHUD.Destroy();
myHUD = Spawn( HUDClass, self);
}
/*
* Function called on activation debug camera controller
*/
function OnActivate( PlayerController PC )
{
if ( DebugCameraHUD(myHUD) != None )
{
DebugCameraHUD(myHUD).bDrawDebugText = bDrawDebugText;
}
if(DrawFrustum==None)
{
DrawFrustum = new(PC.PlayerCamera) class'DrawFrustumComponent';
}
if ( bDrawDebugText )
{
DrawFrustum.SetHidden( false );
ConsoleCommand("show camfrustums true");
}
else
{
DrawFrustum.SetHidden( true );
ConsoleCommand("show camfrustums false");
}
PC.SetHidden(false);
PC.PlayerCamera.SetHidden(false);
DrawFrustum.FrustumAngle = PC.PlayerCamera.CameraCache.POV.FOV;
DrawFrustum.SetAbsolute(true, true, false);
DrawFrustum.SetTranslation(PC.PlayerCamera.CameraCache.POV.Location);
DrawFrustum.SetRotation(PC.PlayerCamera.CameraCache.POV.Rotation);
PC.PlayerCamera.AttachComponent(DrawFrustum);
}
/**
* Function called on deactivation debug camera controller
*/
function OnDeactivate( PlayerController PC )
{
DrawFrustum.SetHidden( true );
PC.PlayerCamera.DetachComponent(DrawFrustum);
PC.SetHidden(true);
PC.PlayerCamera.SetHidden(true);
}
//function called from key bindings command to save information about
// turning on/off FreezeRendering command.
exec function SetFreezeRendering()
{
ConsoleCommand("FreezeRendering");
bIsFrozenRendering = !bIsFrozenRendering;
}
//function called from key bindings command
exec function MoreSpeed()
{
bRun = 2;
}
//function called from key bindings command
exec function NormalSpeed()
{
bRun = 0;
}
/*
* Switch from debug camera controller to local player controller
*/
function DisableDebugCamera()
{
if( OriginalControllerRef != none )
{
// restore FreezeRendering command state before quite
if( bIsFrozenRendering==true ) {
ConsoleCommand("FreezeRendering");
bIsFrozenRendering = false;
}
if( OriginalPlayer != none )
{
OriginalPlayer.SwitchController( OriginalControllerRef );
OriginalControllerRef.InitInputSystem();
OnDeactivate( self );
}
}
}
/*
* Does any controller/input necessary initialization.
*/
function InitDebugInputSystem()
{
OriginalControllerRef.PlayerInput.InitInputSystem();
OriginalControllerRef.InitInputSystem();
}
auto state PlayerWaiting
{
function PlayerMove(float DeltaTime)
{
local float UndilatedDeltaTime;
UndilatedDeltaTime = DeltaTime / WorldInfo.TimeDilation;
super.PlayerMove(UndilatedDeltaTime);
if (WorldInfo.Pauser != None)
{
PlayerCamera.UpdateCamera(DeltaTime);
}
}
};
/**
* Called from DebugCameraInput
* Process an input key event routed through unrealscript from another object. This method is assigned as the value for the
* OnRecievedNativeInputKey delegate so that native input events are routed to this unrealscript function.
*
* @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.
*
* @return true to consume the key event, false to pass it on.
*/
function bool NativeInputKey( int ControllerId, name Key, EInputEvent Event, float AmountDepressed = 1.f, bool bGamepad = FALSE )
{
local vector CamLoc, ZeroVec;
local rotator CamRot;
local TraceHitInfo HitInfo;
local Actor HitActor;
local vector HitLoc, HitNormal;
CamLoc = PlayerCamera.CameraCache.POV.Location;
CamRot = PlayerCamera.CameraCache.POV.Rotation;
if ( Event == IE_Pressed )
{
if ( Key == UnselectKey )
{
Unselect();
SelectedActor = None;
SelectedComponent = None;
return true;
}
if ( Key == PrimaryKey )
{
HitActor = Trace(HitLoc, HitNormal, vector(camRot) * 5000 * 20 + CamLoc, CamLoc, true, ZeroVec, HitInfo);
if( HitActor != None)
{
SelectedActor = HitActor;
SelectedComponent = HitInfo.HitComponent;
PrimarySelect( HitLoc, HitNormal, HitInfo );
}
return true;
}
if ( Key == SecondaryKey )
{
HitActor = Trace(HitLoc, HitNormal, vector(camRot) * 5000 * 20 + CamLoc, CamLoc, true, ZeroVec, HitInfo);
if( HitActor != None)
{
SelectedActor = HitActor;
SelectedComponent = HitInfo.HitComponent;
SecondarySelect( HitLoc, HitNormal, HitInfo );
}
return true;
}
}
return false;
}
exec function ShowDebugSelectedInfo()
{
bShowSelectedInfo = !bShowSelectedInfo;
}
/** Overridden to potentially pipe commands to regular PlayerController */
native function string ConsoleCommand(string Command, optional bool bWriteToLog = true);
cpptext
{
/**
* Builds a list of components that are hidden based upon gameplay
*
* @param ViewLocation the view point to hide/unhide from
* @param HiddenComponents the list to add to/remove from
*/
virtual void UpdateHiddenComponents(const FVector& ViewLocation,TSet<UPrimitiveComponent*>& HiddenComponents);
}
defaultproperties
{
InputClass=class'GameFramework.DebugCameraInput'
OriginalControllerRef=None
OriginalPlayer=None
bIsFrozenRendering=false
DrawFrustum=none
bHidden=FALSE
bHiddenEd=FALSE
bDrawDebugText=true
HUDClass=class'DebugCameraHUD'
bAlwaysTick=TRUE
}

View File

@ -0,0 +1,140 @@
//-----------------------------------------------------------
//
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//-----------------------------------------------------------
class DebugCameraHUD extends HUD
config(Game);
/** When true the debug text is drawn. This is passed through from CheatManager's ToggleDebugCamera exec function. */
var bool bDrawDebugText;
simulated event PostBeginPlay()
{
super.PostBeginPlay();
}
function bool DisplayMaterials( float X, out float Y, float DY, MeshComponent MeshComp )
{
local int MaterialIndex;
local bool bDisplayedMaterial;
local MaterialInterface Material;
bDisplayedMaterial = false;
if ( MeshComp != None )
{
for ( MaterialIndex = 0; MaterialIndex < MeshComp.GetNumElements(); ++MaterialIndex )
{
Material = MeshComp.GetMaterial(MaterialIndex);
if ( Material != None )
{
Y += DY;
Canvas.SetPos( X + DY, Y );
Canvas.DrawText("Material: '" $ Material.Name $ "'" );
bDisplayedMaterial = true;
}
}
}
return bDisplayedMaterial;
}
event PostRender()
{
local DebugCameraController DCC;
local float xl,yl,X,Y;
local String MyText;
local vector CamLoc, ZeroVec;
local rotator CamRot;
local TraceHitInfo HitInfo;
local Actor HitActor;
local MeshComponent MeshComp;
local vector HitLoc, HitNormal;
local bool bFoundMaterial;
super.PostRender();
DCC = DebugCameraController( PlayerOwner );
if( DCC != none && bDrawDebugText )
{
Canvas.SetDrawColor(0, 0, 255, 255);
MyText = "DebugCameraHUD";
Canvas.Font = class'Engine'.Static.GetSmallFont();
Canvas.StrLen(MyText, XL, YL);
X = Canvas.SizeX * 0.05f;
Y = YL;//*1.67;
YL += 2*Y;
Canvas.SetPos( X, YL);
Canvas.DrawText(MyText, true);
Canvas.SetDrawColor(128, 128, 128, 255);
//DCC.GetPlayerViewPoint( CamLoc, CamRot );
CamLoc = DCC.PlayerCamera.CameraCache.POV.Location;
CamRot = DCC.PlayerCamera.CameraCache.POV.Rotation;
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("CamLoc:" $ CamLoc @ "CamRot:" $ CamRot );
HitActor = Trace(HitLoc, HitNormal, vector(camRot) * 5000 * 20 + CamLoc, CamLoc, true, ZeroVec, HitInfo);
if( HitActor != None)
{
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("HitLoc:" $ HitLoc @ "HitNorm:" $ HitNormal );
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("HitDist:" @ VSize(CamLoc - HitLoc) );
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("HitActor: '" $ HitActor.Name $ "'" );
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("PhysMat: '" $ HitInfo.PhysMaterial $ "'" );
bFoundMaterial = false;
if ( HitInfo.Material != None )
{
YL += Y;
Canvas.SetPos(X + Y,YL);
Canvas.DrawText("Material:" $ HitInfo.Material.Name );
bFoundMaterial = true;
}
else if ( HitInfo.HitComponent != None )
{
bFoundMaterial = DisplayMaterials( X, YL, Y, MeshComponent(HitInfo.HitComponent) );
}
else
{
foreach HitActor.AllOwnedComponents( class'MeshComponent', MeshComp )
{
bFoundMaterial = bFoundMaterial || DisplayMaterials( X, YL, Y, MeshComp );
}
}
if ( bFoundMaterial == false )
{
YL += Y;
Canvas.SetPos( X + Y, YL );
Canvas.DrawText("Material: NONE" );
}
DrawDebugLine( HitLoc, HitLoc+HitNormal*30, 255,255,1255 );
}
else
{
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText( "Not trace hit" );
}
if ( DCC.bShowSelectedInfo == true && DCC.SelectedActor != None )
{
YL += Y;
Canvas.SetPos( X, YL );
Canvas.DrawText( "Selected actor: '" $ DCC.SelectedActor.Name $ "'" );
DisplayMaterials( X, YL, Y, MeshComponent(DCC.SelectedComponent) );
}
}
}
DefaultProperties
{
bHidden=false
}

View File

@ -0,0 +1,44 @@
//-----------------------------------------------------------
// * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//-----------------------------------------------------------
class DebugCameraInput extends PlayerInput;
/**
* Process an input key event routed through unrealscript from another object. This method is assigned as the value for the
* OnRecievedNativeInputKey delegate so that native input events are routed to this unrealscript function.
*
* @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.
*
* @return true to consume the key event, false to pass it on.
*/
function bool InputKey( int ControllerId, name Key, EInputEvent Event, float AmountDepressed = 1.f, bool bGamepad = FALSE )
{
local PlayerController PC;
local DebugCameraController DCC;
foreach WorldInfo.AllControllers(class'PlayerController', PC)
{
if ( PC.bIsPlayer && PC.IsLocalPlayerController() )
{
DCC = DebugCameraController(PC);
if( DCC!=none && DCC.OriginalControllerRef==none )
{
//dcc are disabled, so we are looking for normal player controller
continue;
}
return DCC.NativeInputKey( ControllerId, Key, Event, AmountDepressed, bGamepad );
}
}
return false;
}
defaultproperties
{
OnReceivedNativeInputKey=InputKey
}

View File

@ -0,0 +1,13 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Moveable version of GameCrowdDestination
*
*/
class DynamicGameCrowdDestination extends GameCrowdDestination;
defaultproperties
{
bStatic=false
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class DynamicSpriteComponent extends SpriteComponent
DontCollapseCategories
native;
/** Animated Scale. Relative to DrawScale. */
var() InterpCurveFloat AnimatedScale;
/** Animated color + alpha */
var() InterpCurveLinearColor AnimatedColor;
/** Animated 2D position (screen space). Relative to StartPosition. */
var() InterpCurveVector2D AnimatedPosition;
/** 3D world space offset from Location */
var() Vector LocationOffset;
/** How many times to loop (-1 = infinite) */
var() int LoopCount;
cpptext
{
virtual FPrimitiveSceneProxy* CreateSceneProxy();
virtual void UpdateBounds();
}
defaultproperties
{
LoopCount=-1
}

View File

@ -0,0 +1,18 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class FrameworkGame extends GameInfo
config(game)
native;
struct native RequiredMobileInputConfig
{
var config string GroupName;
var config init array<string> RequireZoneNames;
var config bool bIsAttractModeGroup;
};
/** Holds a list of MobileInputZones to load */
var config array<RequiredMobileInputConfig> RequiredMobileInputConfigs;

View File

@ -0,0 +1,252 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameAICmd_Hover_MoveToGoal extends GameAICommand;
var transient Actor Path, Find;
var transient Actor Goal;
var float Radius;
var transient bool bWasFiring;
var float DesiredHoverHeight;
var transient float CurrentHoverHeight;
var float SubGoalReachDist;
/** how close to get to the enemy (only valid of bCompleteMove is TRUE) */
var float GoalDistance;
/** current vector destination */
var transient vector MoveVectDest;
var transient ReachSpec CurrentSpec;
/** GoW global macros */
/** Simple constructor that pushes a new instance of the command for the AI */
static function bool MoveToGoal( GameAIController AI, Actor InGoal, float InGoalDistance, float InHoverHeight )
{
local GameAICmd_Hover_MoveToGoal Cmd;
if( AI != None && AI.Pawn != None && AI.Pawn.bCanFly)
{
Cmd = new(AI) class'GameAICmd_Hover_MoveToGoal';
if( Cmd != None )
{
Cmd.GoalDistance = InGoalDistance;
Cmd.Goal = InGoal;
Cmd.DesiredHoverHeight = InHoverHeight;
Cmd.CurrentHoverHeight = InHoverHeight;
AI.PushCommand( Cmd );
return TRUE;
}
}
return FALSE;
}
function Pushed()
{
Super.Pushed();
GotoState('Moving');
}
function bool HandlePathObstruction(Actor BlockedBy)
{
// ! intentionally does not pass on to children
MoveTimer = -1.f; // kills latent moveto's
GotoState('MoveDown');
return false;
}
state MoveDown `DEBUGSTATE
{
function vector GetMoveDest()
{
local float Height,RadRad;
local navigationPoint PtForHeight;
local vector Dest,HitLocation,HitNormal;
local actor HitActor;
if(Pawn.Anchor != none)
{
PtForHeight = Pawn.Anchor;
}
else if(RouteCache.Length > 0 && RouteCache[0] != none)
{
PtForHeight = RouteCache[0];
}
if(PtForHeight != none)
{
PtForHeight.GetBoundingCylinder(RadRad,Height);
CurrentHoverHeight = Max(0.f, Height - (Pawn.GetCollisionHeight()*0.5f));
Dest = PtForHeight.Location;
Dest.z = PtForHeight.Location.Z + CurrentHoverHeight;
}
else
{
// do a linecheck down to find the ground
HitActor = Trace(HitLocation,HitNormal,Pawn.Location + vect(0,0,-4096.f),Pawn.Location);
if(HitActor != none)
{
Dest = HitLocation;
Dest.Z += Pawn.GetCollisionHeight() * 1.5f;
}
else
{
`AILog(GetFuncName()@"Could not find good hover height!");
Dest = Pawn.Location;
}
}
return Dest;
}
Begin:
MoveTo(GetMoveDest());
Sleep(1.0f);
GotoState('Moving');
};
state Moving `DEBUGSTATE
{
final function bool ReachedDest(Actor Dest)
{
local float latDistSq;
local float VertDist;
latDistSq = VSizeSq2D(Pawn.Location - Dest.Location);
//@TONKS_TEMP
`AILog("LatDist:"@sqrt(latDistSq));
if(latDistSq < SubGoalReachDist * SubGoalReachDist)
{
VertDist = abs(Pawn.Location.Z - Dest.location.Z);
//@TONKS_TEMP
`AILog("VertDist:"@VertDist);
if(VertDist < max(SubGoalReachDist,CurrentHoverHeight+(Pawn.GetCollisionHeight()*2)) )
{
return true;
}
}
return false;
}
final protected function bool PopNextNode( out vector Dest )
{
while( RouteCache.Length > 0 &&
RouteCache[0] != None )
{
if( ReachedDest( RouteCache[0] ) )
{
//debug
`AILog( "Reached route cache 0:"@RouteCache[0] );
// MAKE SURE ANCHOR IS UPDATED -- this is cause of NO CURRENT PATH bug
Pawn.SetAnchor( RouteCache[0] );
//debug
`AILog( "Remove from route:"@RouteCache[0], 'Move' );
RouteCache_RemoveIndex( 0 );
// reset hoverheight since we just arrived at a subgoal
CurrentHoverHeight = DesiredHoverHeight;
}
else
{
//debug
`AILog( "Did NOT reach route cache 0:"@RouteCache[0] );
break;
}
}
if( RouteCache.Length < 1 )
{
return false;
}
CurrentSpec = Pawn.Anchor.GetReachSpecTo(RouteCache[0]);
Dest = RouteCache[0].Location;
return true;
}
Begin:
`AILog("BEGIN TAG"@GetSTatename());
Find = Goal;
Radius = Pawn.GetCollisionRadius() + Enemy.GetCollisionRadius();
if( IsEnemyBasedOnInterpActor( Enemy ) == TRUE )
{
Find = Enemy.Base;
Radius = 0.f;
}
Radius = FMax(Radius, GoalDistance);
if( ActorReachable(Find) )
{
MoveVectDest = Find.Location;
MoveVectDest.Z += CurrentHoverHeight;
`AILog("Moving directly to "$Find);
MoveTo(MoveVectDest,Enemy);
}
else
{
//FIXME: Navmesh
/* // Try to find path to enemy
Path = GeneratePathTo( Find,GoalDistance, TRUE );
// If no path available
if( Path == None )
{
`AILog("Could not find path to enemy!!!");
GotoState( 'DelayFailure' );
}
else
{
//debug
`AILog( "Found path toward enemy..."@Find@Path, 'Move' );
while(PopNextNode(MoveVectDest))
{
MoveVectDest.Z += CurrentHoverHeight;
if(CurrentHoverHeight > CurrentSpec.CollisionHeight && !FastTrace(MoveVectDest,Pawn.Location,pawn.GetCollisionExtent()))
{
`AILog("Could not trace to next position, trying to move down...");
GotoState('MoveDown');
}
else
{
`AILog("Moving to "$MoveVectDest);
MoveTo(MoveVectDest,Enemy);
}
}
}
*/
}
GotoState('DelaySuccess');
}
/** Allows subclasses to determine if our enemy is based on an interp actor or not **/
function bool IsEnemyBasedOnInterpActor( Pawn InEnemy )
{
return FALSE;
}
defaultproperties
{
SubGoalReachDist=768.f
}

View File

@ -0,0 +1,636 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameAICmd_Hover_MoveToGoal_Mesh extends GameAICommand;
var transient Actor Find;
var float Radius;
var transient bool bWasFiring;
var float DesiredHoverHeight;
var transient float CurrentHoverHeight;
var float SubGoalReachDist;
/** how close to get to the enemy (only valid of bCompleteMove is TRUE) */
var transient float GoalDistance;
/** current vector destination */
var transient vector IntermediatePoint;
var transient vector LastMovePoint;
var transient int NumMovePointFails;
var int MaxMovePointFails;
var transient Vector FallbackDest;
var transient Actor MoveToActor;
/** location of MoveToActor last time we did pathfinding */
var BasedPosition LastMoveTargetPathLocation;
/** storage of initial desired move location */
var transient vector InitialFinalDestination;
/** is this AI on 'final approach' ( i.e. moving directly to it's end-goal destination )*/
var bool bFinalApproach;
/** TRUE when we are trying to get back on the mesh */
var bool bFallbackMoveToMesh;
/** Simple constructor that pushes a new instance of the command for the AI */
static function bool HoverToGoal( GameAIController AI, Actor InGoal, float InGoalDistance, float InHoverHeight )
{
local GameAICmd_Hover_MoveToGoal_Mesh Cmd;
if( AI != None && AI.Pawn != None && AI.Pawn.bCanFly)
{
Cmd = new(AI) class'GameAICmd_Hover_MoveToGoal_Mesh';
if( Cmd != None )
{
Cmd.GoalDistance = InGoalDistance;
Cmd.MoveToActor = InGoal;
Cmd.InitialFinalDestination = InGoal.GetDestination(AI);
Cmd.DesiredHoverHeight = InHoverHeight;
Cmd.CurrentHoverHeight = InHoverHeight;
AI.PushCommand( Cmd );
return TRUE;
}
}
return FALSE;
}
static function bool HoverToPoint( GameAIController AI, vector InPoint, float InGoalDistance, float InHoverHeight )
{
local GameAICmd_Hover_MoveToGoal_Mesh Cmd;
if( AI != None && AI.Pawn != None && AI.Pawn.bCanFly)
{
Cmd = new(AI) class'GameAICmd_Hover_MoveToGoal_Mesh';
if( Cmd != None )
{
Cmd.GoalDistance = InGoalDistance;
Cmd.MoveToActor = none;
Cmd.InitialFinalDestination = InPoint;
Cmd.DesiredHoverHeight = InHoverHeight;
Cmd.CurrentHoverHeight = InHoverHeight;
AI.PushCommand( Cmd );
return TRUE;
}
}
return FALSE;
}
static function bool HoverBackToMesh( GameAIController AI )
{
local GameAICmd_Hover_MoveToGoal_Mesh Cmd;
if( AI != None && AI.Pawn != None && AI.Pawn.bCanFly)
{
Cmd = new(AI) class'GameAICmd_Hover_MoveToGoal_Mesh';
if( Cmd != None )
{
Cmd.bFallbackMoveToMesh=true;
AI.PushCommand( Cmd );
return TRUE;
}
}
return FALSE;
}
function Pushed()
{
Super.Pushed();
if( bFallbackMoveToMesh )
{
`AILog("Going into breadcrumb fallback state to get back onto navmesh CurLoc:"@Pawn.Location);
GotoState('Fallback_Breadcrumbs');
return;
}
if( !NavigationHandle.ComputeValidFinalDestination(InitialFinalDestination) )
{
`AILog("ABORTING! Final destination"@InitialFinalDestination@"is not reachable! (ComputeValidFinalDestination returned FALSE)");
GotoState('DelayFailure');
}
else if( !NavigationHandle.SetFinalDestination(InitialFinalDestination) )
{
`AILog("ABORTING! Final destination"@InitialFinalDestination@"is not reachable! (SetFinalDestination returned FALSE)");
GotoState('DelayFailure');
}
else
{
GotoState('Moving');
}
}
function Popped()
{
Super.Popped();
// Check for any latent move actions
ClearLatentAction( class'SeqAct_AIMoveToActor', Status != 'Success' );
// clear the route cache to make sure any intermediate claims are destroyed
NavigationHandle.PathCache_Empty();
// explicitly clear velocity/accel as they aren't necessarily
// cleared by the physics code
if( Pawn != None )
{
Pawn.ZeroMovementVariables();
Pawn.DestinationOffset = 0.f;
}
ReachedMoveGoal();
}
function Tick( float DeltaTime )
{
Super.Tick(DeltaTime);
if( ShouldUpdateBreadCrumbs() )
{
NavigationHandle.UpdateBreadCrumbs(Pawn.Location);
}
NavigationHandle.DrawBreadCrumbs();
}
function bool HandlePathObstruction(Actor BlockedBy)
{
// ! intentionally does not pass on to children
MoveTimer = -1.f; // kills latent moveto's
GotoState('Fallback_Breadcrumbs');
return false;
}
state DelayFailure
{
function bool HandlePathObstruction(Actor BlockedBy);
Begin:
Sleep( 0.5f );
Status = 'Failure';
PopCommand( self );
}
state MoveDown `DEBUGSTATE
{
// find a safe altitude to fly to
function vector GetMoveDest()
{
local vector HitLocation;
local vector HitNormal;
local vector Dest;
local actor HitActor;
// find the poly we're above and try to fly to that
if(NavigationHandle.LineCheck(Pawn.Location, Pawn.Location + vect(0.f,0.f,-4096.f),vect(5,5,5),HitLocation,HitNormal))
{
// if we didn't hit the mesh for some reason, trace against geo
HitActor = Trace(HitLocation,HitNormal,Pawn.Location + vect(0,0,-4096.f),Pawn.Location);
if(HitActor == none)
{
`AILog(GetFuncName()@"Could not find surface to adjust height to!");
return Pawn.Location;
}
}
Dest = HitLocation;
Dest.Z += Pawn.GetCollisionHeight() * 1.5f;
return Dest;
}
Begin:
`AILog("Moving down!");
MoveTo(GetMoveDest());
Sleep(1.0f);
GotoState('Moving');
};
function ReEvaluatePath()
{
/* `AILog(GetFuncName()@bReevaluatePath);
if( HasReachedGoal() )
{
Status = 'Success';
PopCommand(self);
}
else if( bReevaluatePath &&
Pawn != None &&
MovePointIsValid() )
{
//debug
`AILog( "Move continued... Goal"@MoveGoal@"Anchor"@Pawn.Anchor);
NavigationHandle.PathCache_Empty();
`AILog(GetFuncName()@"disabling bReevaluatePath");
bReevaluatePath = FALSE;
// Restart the movement state
GotoState( 'Moving', 'Begin' );
}*/
}
/** Check if AI has successfully reached the given goal */
function bool HasReachedGoal()
{
if( Pawn == None )
{
return TRUE;
}
`AILog(GetFuncName()@bFinalApproach@MoveToActor);
if(bFinalApproach && MoveToActor != None)
{
return Pawn.ReachedDestination(MoveToActor);
}
if( BP2Vect( NavigationHandle.FinalDestination ) != vect(0,0,0) )
{
if( VSize(BP2Vect(NavigationHandle.FinalDestination)-Pawn.Location) < GoalDistance )
{
return TRUE;
}
return Pawn.ReachedPoint( BP2Vect(NavigationHandle.FinalDestination), None );
}
return FALSE;
}
function bool ShouldUpdateBreadCrumbs()
{
return true;
}
state Moving `DEBUGSTATE
{
final function float GetMoveDestinationOffset()
{
// return a negative destination offset so we get closer to our points (yay)
if( bFinalApproach )
{
return GoalDistance;
}
else
{
return (SubGoalReachDist - Pawn.GetCollisionRadius());
}
}
CheckMove:
//debug
`AILog("CHECKMOVE TAG");
if( HasReachedGoal() )
{
Goto( 'ReachedGoal' );
}
Begin:
`AILog("BEGIN TAG"@GetStateName());
if( Enemy != none )
{
Radius = Pawn.GetCollisionRadius() + Enemy.GetCollisionRadius();
}
Radius = FMax(Radius, GoalDistance);
NavigationHandle.SetFinalDestination(InitialFinalDestination);
if( NavigationHandle.PointReachable(BP2Vect(NavigationHandle.FinalDestination)) )
{
IntermediatePoint = BP2Vect(NavigationHandle.FinalDestination);
}
else
{
if( MoveToActor != None )
{
// update final dest in case target moved
if( !NavigationHandle.SetFinalDestination(MoveToActor.GetDestination(Outer)) )
{
`AILog("ABORTING! Final destination"@InitialFinalDestination@"is not reachable! (SetFinalDestination returned FALSE)");
Goto('FailedMove');
}
}
if( !GeneratePathToLocation( BP2Vect(NavigationHandle.FinalDestination),GoalDistance, TRUE ) )
{
//debug
`AILog("Couldn't generate path to location"@BP2Vect(NavigationHandle.FinalDestination)@"from"@Pawn.Location);
//`AILog("Retrying with mega debug on");
//NavigationHandle.bDebugConstraintsAndGoalEvals = TRUE;
//NavigationHandle.bUltraVerbosePathDebugging = TRUE;
//GeneratePathToLocation( BP2Vect(NavigationHandle.FinalDestination),GoalDistance, TRUE );
GotoState( 'Fallback_Breadcrumbs' );
}
//debug
`AILog( "Generated path..." );
`AILog( "Found path!" @ `showvar(BP2Vect(NavigationHandle.FinalDestination)), 'Move' );
if( !NavigationHandle.GetNextMoveLocation( IntermediatePoint, SubGoalReachDist ) )
{
//debug
`AILog("Generated path, but couldn't retrieve next move location?");
Goto( 'FailedMove' );
}
}
if( MoveToActor != None )
{
Vect2BP(LastMoveTargetPathLocation, MoveToActor.GetDestination(Outer));
}
while( TRUE )
{
//debug
`AILog( "Still moving to"@IntermediatePoint, 'Loop' );
bFinalApproach = VSizeSq(IntermediatePoint - BP2Vect(NavigationHandle.FinalDestination)) < 1.0;
`AILog("Calling MoveTo -- "@IntermediatePoint);
// if on our final move to an Actor, send it in directly so we account for any movement it does
if( bFinalApproach && MoveToActor != None )
{
`AILog(" - Final approach to" @ MoveToActor $ ", using MoveToward()");
Vect2BP(LastMoveTargetPathLocation, MoveToActor.GetDestination(outer));
NavigationHandle.SetFinalDestination(MoveToActor.GetDestination(outer));
MoveToward(MoveToActor, Enemy, GetMoveDestinationOffset(), FALSE);
}
else
{
// if we weren't given a focus, default to looking where we're going
if( Enemy == None )
{
SetFocalPoint(IntermediatePoint);
}
// use a negative offset so we get closer to our points!
MoveTo(IntermediatePoint, Enemy, GetMoveDestinationOffset() );
}
`AILog("MoveTo Finished -- "@IntermediatePoint);
// if( bReevaluatePath )
// {
// ReEvaluatePath();
// }
if( HasReachedGoal() )
{
Goto( 'CheckMove' );
}
// if we are moving towards a moving target, repath every time we successfully reach the next node
// as that Pawn's movement may cause the best path to change
else if( MoveToActor != None &&
VSize(MoveToActor.GetDestination(Outer) - BP2Vect(LastMoveTargetPathLocation)) > 512.0 )
{
Vect2BP(LastMoveTargetPathLocation, MoveToActor.GetDestination(outer));
`AILog("Repathing because target moved:" @ MoveToActor);
Goto('CheckMove');
}
else if( !NavigationHandle.GetNextMoveLocation( IntermediatePoint, SubGoalReachDist ) )
{
`AILog( "Couldn't get next move location" );
if (!bFinalApproach && ((MoveToActor != None) ? /*NavigationHandle.*/ActorReachable(MoveToActor) : /*NavigationHandle.*/PointReachable(BP2Vect(NavigationHandle.FinalDestination))))
{
`AILog("Target is directly reachable; try direct move");
IntermediatePoint =((MoveToActor != None) ? MoveToActor.GetDestination(outer) : BP2Vect(NavigationHandle.FinalDestination));
Sleep(RandRange(0.1,0.175));
}
else
{
Sleep(0.1f);
`AILog("GetNextMoveLocation returned false, and finaldest is not directly reachable");
Goto('FailedMove');
}
}
else
{
if(VSize(IntermediatePoint-LastMovePoint) < (Pawn.GetCollisionRadius() * 0.1f) )
{
NumMovePointFails++;
// DrawDebugBox(Pawn.Location, vect(2,2,2) * NumMovePointFails, 255, 255, 255, true );
`AILog("WARNING: Got same move location... something's wrong?!"@`showvar(LastMovePoint)@`showvar(IntermediatePoint)@"Delta"@VSize(LastMovePoint-IntermediatePoint)@"ChkDist"@(Pawn.GetCollisionRadius() * 0.1f));
}
else
{
NumMovePointFails=0;
}
LastMovePoint = IntermediatePoint;
if(NumMovePointFails >= MaxMovePointFails && MaxMovePointFails >= 0)
{
`AILog("ERROR: Got same move location 5x in a row.. something's wrong! bailing from this move");
Goto('FailedMove');
}
else
{
//debug
`AILog( "NextMove"@IntermediatePoint@`showvar(NumMovePointFails) );
}
}
}
//debug
`AILog( "Reached end of move loop??" );
Goto( 'CheckMove' );
FailedMove:
`AILog( "Failed move. Now ZeroMovementVariables" );
MoveTo(Pawn.Location);
Pawn.ZeroMovementVariables();
GotoState('DelayFailure');
ReachedGoal:
//debug
`AILog("Reached move point:"@BP2Vect(NavigationHandle.FinalDestination)@VSize(Pawn.Location-BP2Vect(NavigationHandle.FinalDestination)));
Status = 'Success';
PopCommand( self );
}
/**
* this state will follow our breadcrumbs backward until we are back in the mesh, and then transition back to moving, or go to other fallback states
* if we run out of breadcrumbs and are not yet back in the mesh
*/
state Fallback_Breadcrumbs `DEBUGSTATE
{
function bool ShouldUpdateBreadCrumbs()
{
return false;
}
function bool HandlePathObstruction(Actor BlockedBy)
{
Pawn.SetLocation(IntermediatePoint);
MoveTimer=-1;
GotoState('Fallback_Breadcrumbs','Begin');
return true;
}
Begin:
`AILog("trying to move back along breadcrumb path");
if( NavigationHandle.GetNextBreadCrumb(IntermediatePoint) )
{
`AILog("Moving to breadcrumb pos:"$IntermediatePoint);
// GameAIOwner.DrawDebugLine(Pawn.Location,IntermediatePoint,255,0,0,TRUE);
// GameAIOwner.DrawDebugLine(IntermediatePoint+vect(0,0,100),IntermediatePoint,255,0,0,TRUE);
MoveToDirectNonPathPos(IntermediatePoint);
if( !NavigationHandle.IsAnchorInescapable() )
{
GotoState('Moving');
}
Sleep(0.1);
Goto('Begin');
}
else if( !NavigationHandle.IsAnchorInescapable())
{
GotoState('Moving','Begin');
}
else
{
GotoState('Fallback_FindNearbyMeshPoint');
}
}
state Fallback_FindNearbyMeshPoint `DEBUGSTATE
{
function bool FindAPointWhereICanHoverTo( out Vector out_FallbackDest, float Inradius, optional float minRadius=0, optional float entityRadius = 32, optional bool bDirectOnly=true, optional int MaxPoints=-1,optional float ValidHitBoxSize)
{
local Vector Retval;
local array<vector> poses;
// local int i;
local vector extent;
local vector validhitbox;
Extent.X = entityRadius;
Extent.Y = entityRadius;
Extent.Z = entityRadius;
validhitbox = vect(1,1,1) * ValidHitBoxSize;
NavigationHandle.GetValidPositionsForBox(Pawn.Location,Inradius,Extent,bDirectOnly,poses,MaxPoints,minRadius,validhitbox);
// for(i=0;i<Poses.length;++i)
// {
// DrawDebugStar(poses[i],55.f,255,255,0,TRUE);
// if(i < poses.length-1 )
// {
// DrawDebugLine(poses[i],poses[i+1],255,255,0,TRUE);
// }
// }
if( poses.length > 0)
{
Retval = Poses[Rand(Poses.Length)];
// if for some reason we have a 0,0,0 vect that is never going to be correct
if( VSize(Retval) == 0.0f )
{
out_FallbackDest = vect(0,0,0);
return FALSE;
}
`AILog( `showvar(Retval) );
out_FallbackDest = Retval;
return TRUE;
}
out_FallbackDest = vect(0,0,0);
return FALSE;
}
function bool ShouldUpdateBreadCrumbs()
{
return false;
}
Begin:
`AILog( "Fallback! We now try MoveTo directly to a point that is avail to us" );
if( !FindAPointWhereICanHoverTo( FallbackDest, 2048 ) )
{
GotoState('MoveDown');
}
else
{
MoveToDirectNonPathPos( FallbackDest,, SubGoalReachDist );
Sleep( 0.5f );
if( bFallbackMoveToMesh )
{
GotoState('DelaySuccess');
}
else
{
GotoState('Moving','Begin');
}
}
}
/** Allows subclasses to determine if our enemy is based on an interp actor or not **/
function bool IsEnemyBasedOnInterpActor( Pawn InEnemy )
{
return FALSE;
}
event DrawDebug( HUD H, Name Category )
{
Super.DrawDebug( H, Category );
if(Category != 'Pathing')
{
return;
}
// BLUE
DrawDebugLine( Pawn.Location, GetDestinationPosition(), 0, 0, 255 );
// GREEN
DrawDebugLine( Pawn.Location, BP2Vect(NavigationHandle.FinalDestination), 0, 255, 0 );
NavigationHandle.DrawPathCache( vect(0,0,15) );
}
defaultproperties
{
SubGoalReachDist=128.0
MaxMovePointFails=5
}

View File

@ -0,0 +1,261 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameAICommand extends AICommandBase within GameAIController
abstract
native;
/** Current child node executing on top of this command */
var() const transient editinline GameAICommand ChildCommand;
/** Exiting status of the last child command to execute */
var() const transient Name ChildStatus;
/** Extra reference to the AI this command is being used by */
var() transient GameAIController GameAIOwner;
/** Exiting status of this command */
var() transient Name Status;
/** if this is FALSE and we're trying to push a new instance of a given class, but the top of the stack is already an instance of that class ignore the attempted push */
var bool bAllowNewSameClassInstance;
/** if this is TRUE, when we try to push a new instance of a command who has the same class as the command on the top of the stack, pop the one on the stack, and push the new one
NOTE: This trumps bAllowNewClassInstance (e.g. if this is true and bAllowNewClassInstance is false the active instance will still be replaced) */
var bool bReplaceActiveSameClassInstance;
/** Command has been aborted and should be popped next frame */
var transient private bool bAborted;
var bool bIgnoreNotifies;
var bool bIgnoreStepAside;
/** this command is about to be popped, and shouldn't have resumed called on it when children are popped */
var transient private bool bPendingPop;
`if(`__TW_)
var bool bDisableSteering; // JC [Temp!]
var string HistoryString;
`endif
cpptext
{
virtual void TickCommand(FLOAT DeltaTime);
void ProcessState(FLOAT DeltaSeconds);
virtual EGotoState GotoState( FName State, UBOOL bForceEvents = 0, UBOOL bKeepStack = 0 );
void PopChildCommand();
}
/** Simple constructor that takes one extra userdata param **/
static function bool InitCommandUserActor( GameAIController AI, Actor UserActor )
{
//default is to call the constructor
return InitCommand(AI);
}
/** Simple constructor that pushes a new instance of the command for the AI */
static function bool InitCommand( GameAIController AI )
{
local GameAICommand Cmd;
if( AI != None )
{
Cmd = new(AI) Default.Class;
if( Cmd != None )
{
AI.PushCommand(Cmd);
return TRUE;
}
}
return FALSE;
}
/** == INTERNAL INTERFACE == */
/** called to set up internal pointers to the owning AI, before Pushed is called */
final event InternalPrePushed(GameAIController AI)
{
GameAIOwner = AI;
PrePushed(AI);
}
/** Called when command pushed to perform any necessary work, independent of the individual command implementations - @see Pushed() instead */
final event InternalPushed()
{
GotoState('Auto');
// call the overrideable notification
Pushed();
}
/** Called when command popped to perform any necessary cleanup, independent of the individual command implementations - @see Popped() instead */
event InternalPopped()
{
// call the overrideable notifications
Popped();
GameAIOwner=none;
PostPopped();
}
/** Called when another command is pushed on top of this one */
final event InternalPaused( GameAICommand NewCommand )
{
Paused( NewCommand );
}
/** Called when the command that was on top of this one in the stack is popped */
final event InternalResumed( Name OldCommandName )
{
Resumed( OldCommandName );
}
final event InternalTick( float DeltaTime )
{
Tick( DeltaTime );
}
final native function bool ShouldIgnoreNotifies() const;
/** == OVERRIDABLE INTERFACE == */
function Tick( float DeltaTime )
{
}
function bool AllowTransitionTo( class<GameAICommand> AttemptCommand )
{
return (ChildCommand == None || ChildCommand.AllowTransitionTo( AttemptCommand ));
}
function bool AllowStateTransitionTo(Name StateName)
{
return (ChildCommand == None || ChildCommand.AllowStateTransitionTo(StateName));
}
/** called to set up internal pointers to the owning AI, before Pushed is called */
function PrePushed(GameAIController AI);
/** called just before popped.. useful for cleaning up things before the popped chain gets called */
function PostPopped();
/** Notification called when this command has pushed */
function Pushed()
{
//debug
`AILog( "COMMAND PUSHED:"@self, 'GameAICommand' );
}
/** Notification when this command has popped */
function Popped()
{
//debug
`AILog( "COMMAND POPPED:"@self@"with"@Status, 'GameAICommand' );
}
function Paused(GameAICommand NewCommand)
{
//debug
`AILog( "COMMAND PAUSED:"@self@"by"@NewCommand, 'GameAICommand' );
}
function Resumed( Name OldCommandName )
{
//debug
`AILog( "COMMAND RESUMED:"@self@"from"@OldCommandName@"with"@ChildStatus, 'GameAICommand' );
}
event String GetDumpString()
{
return String(self);
}
/**
* ===========
* DEBUG STATES
* ===========
*/
state DEBUGSTATE
{
function BeginState( Name PreviousStateName )
{
//debug
`AILog( "BEGINSTATE"@PreviousStateName, 'State' );
}
function EndState( Name NextStateName )
{
//debug
`AILog( "ENDSTATE"@NextStateName, 'State' );
}
function PushedState()
{
//debug
`AILog( "PUSHED", 'State' );
}
function PoppedState()
{
//debug
`AILog( "POPPED", 'State' );
}
function ContinuedState()
{
//debug
`AILog( "CONTINUED", 'State' );
}
function PausedState()
{
//debug
`AILog( "PAUSED", 'State' );
}
}
/**
* Command has failed but delay pop to avoid infinite recursion
*/
state DelayFailure `DEBUGSTATE
{
Begin:
Sleep( 0.5f );
Status = 'Failure';
PopCommand( self );
}
state DelaySuccess `DEBUGSTATE
{
Begin:
Sleep( 0.1f );
Status = 'Success';
PopCommand( self );
}
event DrawDebug( HUD H, Name Category );
/** Used to get text from the AICmds **/
function GetDebugOverheadText( PlayerController PC, out array<string> OutText );
event String GetDebugVerboseText();
function NotifyNeedRepath();
function bool MoveUnreachable( Vector AttemptedDest, Actor AttemptedTarget );
function bool HandlePathObstruction( Actor BlockedBy )
{
if ( ChildCommand!= none )
{
return ChildCommand.HandlePathObstruction(BlockedBy);
}
return false;
}
defaultproperties
{
bAllowNewSameClassInstance=FALSE
bReplaceActiveSameClassInstance=FALSE
HistoryString="[I]"
}

View File

@ -0,0 +1,341 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameAIController extends AIController
dependson(GameTypes)
abstract
native
config(Game);
/** Current command stack, with the last element being the currently active (ticked) one */
var() editinline transient const GameAICommand CommandList;
/** Whether this AI has a runaway loop or not. If it does we are going to do AbortCommand( CommandList ) at the end of Tick(). **/
var transient bool bHasRunawayCommandList;
/** Debug log file, @see: AILog */
var transient FileLog AILogFile;
var(Debug) config bool bAILogging;
var(Debug) config bool bAILogToWindow;
var(Debug) config bool bFlushAILogEachLine;
var(Debug) config bool bMapBasedLogName;
var(Debug) config bool bAIDrawDebug;
var transient bool bAIBroken;
var(Debug) float DebugTextMaxLen;
var(Debug) transient const array<AICmdHistoryItem> CommandHistory;
var(Debug) config int CommandHistoryNum;
/** List of categories to filter */
var(Debug) config array<Name> AILogFilter;
// DEMO RECORDING PROPERTIES - for saving AI info into demos
var string DemoActionString;
cpptext
{
#if !DO_AI_LOGGING
#if COMPILER_SUPPORTS_NOOP
#define AIObjLog __noop
#define AILog __noop
#define AI_LOG __noop
#elif SUPPORTS_VARIADIC_MACROS
#define AIObjLog(...)
#define AILog(...)
#define AI_LOG(...)
#else
#define AIObjLog GNull->Logf
#define AILog GNull->Logf
#define AI_LOG GNull->Logf
#endif
#else
#define AI_LOG(Object, FuncParams) \
if(RUNTIME_DO_AI_LOGGING)\
{\
Object->AILog FuncParams;\
}
#define AIObjLog(FuncParams) AI_LOG(AI,FuncParams)
VARARG_DECL(void,void,{},AILog,VARARG_NONE,const TCHAR*,VARARG_NONE,VARARG_NONE);
VARARG_DECL(void,void,{},AILog,VARARG_NONE,const TCHAR*,VARARG_EXTRA(enum EName E),VARARG_EXTRA(E));
#endif
UBOOL Tick( FLOAT DeltaTime, enum ELevelTick TickType );
virtual EGotoState GotoState( FName State, UBOOL bForceEvents = 0, UBOOL bKeepStack = 0 );
virtual void ProcessState( FLOAT DeltaSeconds );
virtual void StoreCommandHistory( UGameAICommand* Cmd );
/**
* DebugLog function which is called to log information specific to this AI (call NAVHANDLE_DEBUG_LOG macro, don't call this directly)
* @param LogText - text to log for this AI
*/
virtual void DebugLogInternal(const TCHAR* LogText)
{
AI_LOG(this,(LogText));
}
};
replication
{
if( bDemoRecording )
DemoActionString;
}
/** returns all AI Commands in the CommandList that are of the specified class or a subclass
* @note this function is only useful on the server
* @param BaseClass the base class of GameAICommand to return
* @param (out) Cmd the returned GameAICommand for each iteration
*/
native final iterator function AllCommands(class<GameAICommand> BaseClass, out GameAICommand Cmd);
/**
* PushCommand
* pushes a new AI command on top of the command stack
* @param NewCommand - command to place on top of stack
*/
native function PushCommand(GameAICommand NewCommand);
/**
* PopCommand
* will pop the passed command (and everything above it in the stack)
* @param ToBePoppedCommand - the command to pop
*/
native function PopCommand(GameAICommand ToBePoppedCommand);
/** AbortCommand
* Aborts a command (and all of its children)
* @param AbortCmd - the command to abort (can be NULL, in which case AbortClass will be used to determine which command to abort
* @param AbortClass - not used unless AbortCmd is NULL, in which case the first command int he stack of class 'AbortClass' will be aborted (and all its children)
*/
native function bool AbortCommand( GameAICommand AbortCmd, optional class<GameAICommand> AbortClass );
native final function GameAICommand GetActiveCommand();
/** checks the command stack for too many commands and/or infinite recursion */
native final function CheckCommandCount();
native final function DumpCommandStack();
/** finds and returns the lowest command of the specified class on the stack (will return subclasses of the specified class) */
native final function coerce GameAICommand FindCommandOfClass(class<GameAICommand> SearchClass) const;
/** This will search the CommandList for the passed in command class. **/
native function GameAICommand GetAICommandInStack( const class<GameAICommand> InClass );
/** return desired offset to move position... used for MoveTo/MoveToward */
function float GetDestinationOffset();
// called from movetogoal when we arrive at our destination
function ReachedMoveGoal();
function ReachedIntermediateMoveGoal();
/**
* =====
* DEBUG
* =====
*/
event Destroyed()
{
Super.Destroyed();
if (AILogFile != None)
{
AILogFile.Destroy();
}
// so this cases handles when AI has been destroyed. We need to give a chance to the AICmds to handle that case
if( Commandlist != None )
{
AbortCommand( CommandList );
}
}
protected function RecordDemoAILog( coerce string LogText );
`if(`__TW_PATHFINDING_)
event AILog_Internal( coerce string LogText, optional Name LogCategory, optional bool bForce, optional bool BugIt, optional bool bSkipExtraInfo )
`else
event AILog_Internal( coerce string LogText, optional Name LogCategory, optional bool bForce )
`endif
{
`if(`notdefined(FINAL_RELEASE))
local int Idx;
local String ActionStr, FinalStr;
local String FileName;
local GameAICommand ActiveCommand;
local int FileNameLength;
local Engine Eng;
Eng = class'Engine'.static.GetEngine();
if( Eng.bDisableAILogging )
{
return;
}
if( !bForce &&
!bAILogging )
{
return;
}
if (WorldInfo.IsConsoleBuild(CONSOLE_PS3))
{
return;
}
if( !bForce )
{
for( Idx = 0; Idx < AILogFilter.Length; Idx++ )
{
if( AILogFilter[Idx] == LogCategory )
{
return;
}
}
}
if (AILogFile == None)
{
AILogFile = Spawn(class'FileLog');
if (bMapBasedLogName)
{
FileName = WorldInfo.GetMapName()$"_"$string(self);
FileName = Repl(FileName,"ai_","",false);
}
else
{
FileName = string(self);
}
// for consoles we need to make certain the filename is short enough
if( class'WorldInfo'.static.GetWorldInfo().IsConsoleBuild() == TRUE )
{
// Include the file extension in determining the length
FileNameLength = Len(Filename) + 6;
// make sure file name isn't too long for consoles
if(FileNameLength > 40)
{
// Make sure there is room for the .ailog part too
FileName = Right(Filename,34);
}
}
AILogFile.bKillDuringLevelTransition = TRUE;
AILogFile.bFlushEachWrite = bFlushAILogEachLine;
// Use async unless flushing was requested
AILogFile.bWantsAsyncWrites = !bFlushAILogEachLine;
AILogFile.OpenLog(FileName,".ailog");
}
ActionStr = String(GetStateName());
ActiveCommand = GetActiveCommand();
if (ActiveCommand != None)
{
ActionStr = String(ActiveCommand.Class)$":"$String(ActiveCommand.GetStateName());
}
FinalStr = "["$WorldInfo.TimeSeconds$"]"@ActionStr$":"@LogText;
AILogFile.Logf(FinalStr);
if (WorldInfo.IsRecordingDemo())
{
RecordDemoAILog(FinalStr);
}
`Log( Pawn@"["$WorldInfo.TimeSeconds$"]"@ActionStr$":"@LogText, bAILogToWindow );
`endif
}
/** SetDesiredRotation
* Calls Pawn's SetDesiredRotation: Simple interface to Pawn
*/
function SetDesiredRotation(Rotator TargetDesiredRotation, bool InLockDesiredRotation=FALSE, bool InUnlockWhenReached=FALSE, FLOAT InterpolationTime=-1.f/*Use Default RotationRate*/)
{
if ( Pawn!=none )
{
Pawn.SetDesiredRotation(TargetDesiredRotation, InLockDesiredRotation, InUnlockWhenReached, InterpolationTime);
}
}
/**
* ===========
* DEBUG STATES
* ===========
*/
state DEBUGSTATE
{
function BeginState( Name PreviousStateName )
{
//debug
`AILog( "BEGINSTATE"@PreviousStateName, 'State' );
}
function EndState( Name NextStateName )
{
//debug
`AILog( "ENDSTATE"@NextStateName, 'State' );
}
function PushedState()
{
//debug
`AILog( "PUSHED", 'State' );
}
function PoppedState()
{
//debug
`AILog( "POPPED", 'State' );
}
function ContinuedState()
{
//debug
`AILog( "CONTINUED", 'State' );
}
function PausedState()
{
//debug
`AILog( "PAUSED", 'State' );
}
}
/** @return a String with the Cmd and the State that the AI is in. **/
simulated final event string GetActionString()
{
local string ActionStr;
local GameAICommand ActiveCmd;
if( WorldInfo.IsPlayingDemo() )
{
return DemoActionString;
}
else
{
ActiveCmd = GetActiveCommand();
if( ActiveCmd != None )
{
ActionStr = string(ActiveCmd.Class)$":"$string(ActiveCmd.GetStateName());
}
else
{
ActionStr = string(default.Class)$":"$string(GetStateName());
}
return ActionStr;
}
}
defaultproperties
{
}

View File

@ -0,0 +1,41 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCameraBase extends Object
abstract
native(Camera)
config(Camera);
var transient GamePlayerCamera PlayerCamera;
/** resets camera interpolation. Set on first frame and teleports to prevent long distance or wrong camera interpolations. */
var transient bool bResetCameraInterpolation;
/** Called when the camera becomes active */
function OnBecomeActive( GameCameraBase OldCamera );
/** Called when the camera becomes inactive */
function OnBecomeInActive( GameCameraBase NewCamera );
/** Called to indicate that the next update should skip interpolation and snap to desired values. */
function ResetInterpolation()
{
bResetCameraInterpolation = TRUE;
}
/** Expected to fill in OutVT with new camera pos/loc/fov. */
function UpdateCamera(Pawn P, GamePlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT);
function ProcessViewRotation( float DeltaTime, Actor ViewTarget, out Rotator out_ViewRotation, out Rotator out_DeltaRot );
simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos);
function Init();
event ModifyPostProcessSettings(out PostProcessSettings PP);
defaultproperties
{
}

View File

@ -0,0 +1,24 @@
//=============================================================================
// GameCameraBlockingVolume:
// used to block the camera only (all other types of collision are ignored)
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//=============================================================================
class GameCameraBlockingVolume extends BlockingVolume
hidecategories(Collision)
native
placeable;
cpptext
{
// overidden to ignore blocking by anything except a camera actor
virtual UBOOL IgnoreBlockingBy( const AActor *Other) const;
virtual UBOOL ShouldTrace(UPrimitiveComponent* Primitive,AActor *SourceActor, DWORD TraceFlags);
#if WITH_EDITOR
virtual void SetCollisionForPathBuilding(UBOOL bNowPathBuilding);
#endif
};
defaultproperties
{
bWorldGeometry=false
}

View File

@ -0,0 +1,285 @@
//=============================================================================
// GameCheatManager
// Object within gameplayercontroller that manages "cheat" commands
// only spawned in single player mode
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//=============================================================================
class GameCheatManager extends CheatManager within GamePlayerController
config(Game)
native;
/** Debug camera - used to have independent camera without stopping gameplay */
var DebugCameraController DebugCameraControllerRef;
var class<DebugCameraController> DebugCameraControllerClass;
var config string DebugCameraControllerClassName;
function PatchDebugCameraController()
{
local class<DebugCameraController> TempCameraControllerClass;
if (DebugCameraControllerClassName != "")
{
TempCameraControllerClass = class<DebugCameraController>(DynamicLoadObject(DebugCameraControllerClassName, class'Class'));
if (TempCameraControllerClass != None)
{
DebugCameraControllerClass = TempCameraControllerClass;
}
}
}
/**
* Toggle between debug camera/player camera without locking gameplay and with locking
* local player controller input.
*/
exec function ToggleDebugCamera(optional bool bDrawDebugText = true)
{
local PlayerController PC;
local DebugCameraController DCC;
foreach WorldInfo.AllControllers(class'PlayerController', PC)
{
if ( PC.bIsPlayer && PC.IsLocalPlayerController() )
{
DCC = DebugCameraController(PC);
if( DCC!=none && DCC.OriginalControllerRef==none )
{
//dcc are disabled, so we are looking for normal player controller
continue;
}
break;
}
}
if( DCC!=none && DCC.OriginalControllerRef!=none )
{
DCC.DisableDebugCamera();
DCC.Destroy();
DCC = None;
}
else if( PC!=none )
{
EnableDebugCamera(bDrawDebugText);
}
}
/**
* Teleport the player's pawn to the location of the debug camera (and, by default, toggle the debug camera off).
* If not in the "Debug Camera" mode, print an error message and give up.
*/
exec function TeleportPawnToCamera(optional bool bToggleDebugCameraOff = true)
{
local PlayerController PC;
local DebugCameraController DCC;
local vector ViewLocation;
local rotator ViewRotation;
foreach WorldInfo.AllControllers(class'PlayerController', PC)
{
if ( PC.bIsPlayer && PC.IsLocalPlayerController() )
{
DCC = DebugCameraController(PC);
if( DCC!=none && DCC.OriginalControllerRef==none )
{
//dcc are disabled, so we are looking for normal player controller
continue;
}
break;
}
}
if ((DCC != none) && (DCC.OriginalControllerRef!=none))
{
if (DCC.OriginalControllerRef.Pawn != None)
{
GetPlayerViewPoint(ViewLocation, ViewRotation);
DCC.OriginalControllerRef.Pawn.SetLocation(ViewLocation);
DCC.OriginalControllerRef.Pawn.SetRotation(ViewRotation);
}
if (bToggleDebugCameraOff)
ToggleDebugCamera();
}
else
{
ClientMessage("TeleportPawnToCamera should be used in conjunction with the ToggleDebugCamera command. Failed.");
}
}
/**
* Switch controller to debug camera without locking gameplay and with locking
* local player controller input
*/
function EnableDebugCamera(bool bEnableDebugText)
{
local Player P;
local vector eyeLoc;
local rotator eyeRot;
local float CameraFOVAngle;
P = Player;
if( P!= none && Pawn != none && IsLocalPlayerController() )
{
PatchDebugCameraController();
if( DebugCameraControllerRef!=None )
{
DebugCameraControllerRef.Destroy();
}
CameraFOVAngle = GetFOVAngle();
DebugCameraControllerRef = Spawn(DebugCameraControllerClass);
DebugCameraControllerRef.PlayerInput = none;
DebugCameraControllerRef.OriginalPlayer = P;
DebugCameraControllerRef.OriginalControllerRef = outer;
GetPlayerViewPoint(eyeLoc,eyeRot);
DebugCameraControllerRef.SetLocation(eyeLoc);
DebugCameraControllerRef.SetRotation(eyeRot);
DebugCameraControllerRef.bDrawDebugText=bEnableDebugText;
P.SwitchController( DebugCameraControllerRef );
DebugCameraControllerRef.OnActivate( outer );
// Make sure the camera gets created and set it up.
DebugCameraControllerRef.GetPlayerViewPoint(eyeLoc,eyeRot);
if ( DebugCameraControllerRef.PlayerCamera != None )
{
DebugCameraControllerRef.PlayerCamera.SetFOV( CameraFOVAngle );
DebugCameraControllerRef.PlayerCamera.UpdateCamera(0.0);
}
else
{
DebugCameraControllerRef.FOVAngle = CameraFOVAngle;
}
}
}
/**
* Simple function to illustrate the use of the HttpRequest system.
*/
exec function TestHttp(string Verb, string Payload, string URL, optional bool bSendParallelRequest)
{
local HttpRequestInterface R;
// create the request instance using the factory (which handles
// determining the proper type to create based on config).
R = class'HttpFactory'.static.CreateRequest();
// always set a delegate instance to handle the response.
R.OnProcessRequestComplete = OnRequestComplete;
`log("Created request");
// you can make many requests from one request object.
R.SetURL(URL);
// Default verb is GET
if (Len(Verb) > 0)
{
R.SetVerb(Verb);
}
else
{
`log("No Verb given, using the defaults.");
}
// Default Payload is empty
if (Len(Payload) > 0)
{
R.SetContentAsString(Payload);
}
else
{
`log("No payload given.");
}
`log("Creating request for URL:"@URL);
// there is currently no way to distinguish keys that are empty from keys that aren't there.
`log("Key1 ="@R.GetURLParameter("Key1"));
`log("Key2 ="@R.GetURLParameter("Key2"));
`log("Key3NoValue ="@R.GetURLParameter("Key3NoValue"));
`log("NonexistentKey ="@R.GetURLParameter("NonexistentKey"));
// A header will not necessarily be present if you don't set one. Platform implementations
// may add things like Content-Length when you send the request, but won't necessarily
// be available in the Header.
`log("NonExistentHeader ="@R.GetHeader("NonExistentHeader"));
`log("CustomHeaderName ="@R.GetHeader("CustomHeaderName"));
`log("ContentType ="@R.GetContentType());
`log("ContentLength ="@R.GetContentLength());
`log("URL ="@R.GetURL());
`log("Verb ="@R.GetVerb());
// multiple ProcessRequest calls can be made from the same instance if desired.
if (!R.ProcessRequest())
{
`log("ProcessRequest failed. Unsuppress DevHttpRequest to see more details.");
}
else
{
`log("Request sent");
}
// send off a parallel request for testing.
if (bSendParallelRequest)
{
if (!class'HttpFactory'.static.CreateRequest()
.SetURL("http://www.epicgames.com")
.SetVerb("GET")
.SetHeader("Test", "Value")
.SetProcessRequestCompleteDelegate(OnRequestComplete)
.ProcessRequest())
{
`log("ProcessRequest for parallel request failed. Unsuppress DevHttpRequest to see more details.");
}
else
{
`log("Parallel Request sent");
}
}
}
/** Delegate to use for HttpResponses. */
function OnRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface Response, bool bDidSucceed)
{
local array<String> Headers;
local String Header;
local String Payload;
local int PayloadIndex;
`log("Got response!!!!!!! Succeeded="@bDidSucceed);
`log("URL="@OriginalRequest.GetURL());
// if we didn't succeed, we can't really trust the payload, so you should always really check this.
if (Response != None)
{
`log("ResponseURL="@Response.GetURL());
`log("Response Code="@Response.GetResponseCode());
Headers = Response.GetHeaders();
foreach Headers(Header)
{
`log("Header:"@Header);
}
// GetContentAsString will make a copy of the payload to add the NULL terminator,
// then copy it again to convert it to TCHAR, so this could be fairly inefficient.
// This call also assumes the payload is UTF8 right now, as truly determining the encoding
// is content-type dependent.
// You also can't trust the content-length as you don't always get one. You should instead
// always trust the length of the content payload you receive.
Payload = Response.GetContentAsString();
if (Len(Payload) > 1024)
{
PayloadIndex = 0;
`log("Payload:");
while (PayloadIndex < Len(Payload))
{
`log(" "@Mid(Payload, PayloadIndex, 1024));
PayloadIndex = PayloadIndex + 1024;
}
}
else
{
`log("Payload:"@Payload);
}
}
}
defaultproperties
{
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,215 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdAgentBehavior extends Object
native
abstract;
enum ECrowdBehaviorEvent
{
CBE_None,
CBE_Spawn,
CBE_Random,
CBE_SeePlayer,
CBE_EncounterAgent,
CBE_TakeDamage,
CBE_GroupWaiting,
CBE_Uneasy,
CBE_Alert,
CBE_Panic,
};
var ECrowdBehaviorEvent MyEventType;
/** If non-zero, how long before behavior auto terminates */
var() float DurationOfBehavior;
var float TimeUntilStopBehavior;
/** If true, agent should idle (not move between destinations)/ */
var() bool bIdleBehavior;
/** actor to aim at during actions */
var Actor ActionTarget;
/** Agent must be within this distance of the player to perform this behavior */
var() float MaxPlayerDistance;
/** If true, must face action target before starting behavior */
var() bool bFaceActionTargetFirst;
/** If true, pass on to agents encountered */
var() bool bIsViralBehavior;
var() ECrowdBehaviorEvent ViralBehaviorEvent;
var() float ViralRadius;
/**
* So for some behaviors we only want the original agents to be able to pass on the bViralBehavior flag.
* You will want to check for this flag in your specific behavior's event PropagateViralBehaviorTo.
*
* NOTE: Currently, there is no default implementation of that that we are are all calling super. to utilize that functionality
**/
var() bool bPassOnIsViralBehaviorFlag;
var() float DurationBeforeBecomesViral;
var transient float TimeToBecomeViral;
/**
* How long we should propagate the viral behavior. Basically, you can get into situations where the the behavior will never go away as it
* keeps getting propagated to others over and over and the various timers get started again.
*/
var() float DurationOfViralBehaviorPropagation;
/** This is the time we will stop propagating the bIsViralBehavior flag **/
var transient float TimeToStopPropagatingViralBehavior;
/** Agent currently implementing this behavior instance */
var GameCrowdAgent MyAgent;
var(Debug) Color DebugBehaviorColor;
/**
*
* if duration < 0 it is instant
* if duration == 0 it is eternal
* if duration > 0 it has a lifespan
*
**/
static native function GameCrowdBehaviorPoint TriggerCrowdBehavior( ECrowdBehaviorEvent EventType, Actor Instigator, Vector AtLocation, float InRange, float InDuration, optional Actor BaseActor, optional bool bRequireLOS );
/**
* Called every tick when agent is currently idle (because bIdleBehavior is true)
*
* @RETURN true if should end idle (bIdleBehavior should also become false)
*/
native function bool ShouldEndIdle();
/**
* Agent's current behavior gets ticked
*/
native event Tick(float DeltaTime);
/**
* This function is called on an archetype - do not modify any properties here!
*/
function bool CanBeUsedBy(GameCrowdAgent Agent, vector CameraLoc)
{
if( Agent.CurrentBehavior != None && Agent.CurrentBehavior.MyEventType == MyEventType )
{
return FALSE;
}
return VSizeSq(CameraLoc - Agent.Location) < MaxPlayerDistance*MaxPlayerDistance;
}
/**
* Event when agent is facing action target, called if bFaceActionTarget=true
*/
event FinishedTargetRotation();
/**
* Handles movement destination updating for agent.
*
* @RETURNS true if destination updating was handled
*/
native function bool HandleMovement();
/**
* Called when Agent activates this behavior
*/
function InitBehavior(GameCrowdAgent Agent)
{
MyAgent = Agent;
if( DurationBeforeBecomesViral > 0.f )
{
TimeToBecomeViral = MyAgent.WorldInfo.TimeSeconds + DurationBeforeBecomesViral;
}
if( DurationOfViralBehaviorPropagation > 0.0f )
{
TimeToStopPropagatingViralBehavior = MyAgent.WorldInfo.TimeSeconds + DurationOfViralBehaviorPropagation;
}
if( DurationOfBehavior > 0.0f )
{
TimeUntilStopBehavior = DurationOfBehavior;
}
}
/**
* Called when Agent stops this behavior
*/
function StopBehavior()
{
}
/**
* Anim end notification called by GameCrowdAgent.OnAnimEnd().
*/
event OnAnimEnd(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime);
/**
* Get debug string about agent behavior
*/
function string GetBehaviorString()
{
return "Behavior: "$self;
}
/**
* Notification that MyAgent is changing destinations
*/
function ChangingDestination(GameCrowdDestination NewDest);
/**
* Returns action agent wants behavior to be moving toward.
*/
function Actor GetDestinationActor()
{
return MyAgent.CurrentDestination;
}
/**
* Called if agent wants to provide an action target to its behavior.
*/
function ActivatedBy(Actor NewActionTarget)
{
ActionTarget = NewActionTarget;
}
function Actor GetBehaviorInstigator()
{
return ActionTarget;
}
/**
* When two agents encounter each other, and one has a viral behavior and the other doesn't,
* the viral behavior is called to have a chance to propagate itself to the uninfected OtherAgent.
*/
event PropagateViralBehaviorTo( GameCrowdAgent OtherAgent )
{
if( ViralBehaviorEvent != CBE_None )
{
OtherAgent.HandleBehaviorEvent( ViralBehaviorEvent, GetBehaviorInstigator(), TRUE, bPassOnIsViralBehaviorFlag );
}
}
/**
* Return true if agent is allowed to go to destination while performing this behavior
*/
function bool AllowThisDestination(GameCrowdDestination Destination)
{
return true;
}
/**
* return true if get kismet or new behavior from this destination
*/
function bool AllowBehaviorAt(GameCrowdDestination Destination)
{
return true;
}
defaultproperties
{
MaxPlayerDistance=10000.0
bPassOnIsViralBehaviorFlag=TRUE
ViralRadius=512.f
}

View File

@ -0,0 +1,73 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdAgentSM extends GameCrowdAgent
abstract
native;
var(Rendering) StaticMeshComponent Mesh;
var(Rendering) MaterialInstanceConstant MeshColor;
simulated function PostBeginPlay()
{
Super.PostBeginPlay();
MeshColor = Mesh.CreateAndSetMaterialInstanceConstant(0);
}
simulated function InitDebugColor()
{
Super.InitDebugColor();
ChangeDebugColor( DebugAgentColor );
}
simulated function ChangeDebugColor( Color InC )
{
local LinearColor C;
C.R = float(InC.R) / 255.f;
C.G = float(InC.G) / 255.f;
C.B = float(InC.B) / 255.f;
MeshColor.SetVectorParameterValue( 'CrowdCylinderColor', C );
}
function ActivateBehavior(GameCrowdAgentBehavior NewBehaviorArchetype, optional Actor LookAtActor )
{
Super.ActivateBehavior( NewBehaviorArchetype, LookAtActor );
if( CurrentBehavior != None )
{
ChangeDebugColor( CurrentBehavior.DebugBehaviorColor );
}
else
{
ChangeDebugColor( DebugAgentColor );
}
}
function StopBehavior()
{
Super.StopBehavior();
if( CurrentBehavior == None )
{
ChangeDebugColor( DebugAgentColor );
}
}
defaultproperties
{
Begin Object Class=StaticMeshComponent Name=StaticMeshComponent0
CollideActors=TRUE
BlockActors=TRUE
BlockZeroExtent=TRUE
BlockNonZeroExtent=FALSE
BlockRigidBody=FALSE
RBChannel=RBCC_GameplayPhysics
bCastDynamicShadow=FALSE
RBCollideWithChannels=(Default=TRUE,GameplayPhysics=TRUE,EffectPhysics=TRUE)
bAcceptsDynamicDecals=FALSE // for crowds there are so many of them probably not going to notice not getting decals on them. Each decal on them causes entire SkelMesh to be rerendered
End Object
Mesh=StaticMeshComponent0
Components.Add(StaticMeshComponent0)
}

View File

@ -0,0 +1,353 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdAgentSkeletal extends GameCrowdAgent
native
abstract;
/** SkeletalMeshComponent used for crowd member mesh */
var(Rendering) SkeletalMeshComponent SkeletalMeshComponent;
/** Cached pointer to speed blend node */
var AnimNodeBlend SpeedBlendNode;
/** Cached pointer to action blend node */
var AnimNodeSlot FullBodySlot;
/** Cached pointer to action animation player */
var AnimNodeSequence ActionSeqNode;
/** Cached pointer to walking animation player */
var AnimNodeSequence WalkSeqNode;
/** Cached pointer to running animation player */
var AnimNodeSequence RunSeqNode;
/** Cached pointer to AnimTree instance (SkeletalMeshComponent.Animations) */
var AnimTree AgentTree;
/** The names of the animation loops to use when moving slowly */
var(Rendering) array<name> WalkAnimNames;
/** The name of the animations to use when moving more quickly */
var(Rendering) array<name> RunAnimNames;
/** The name of the animations to use when not moving (and not playing a custom animation) */
var(Rendering) array<name> IdleAnimNames;
/** Set of possible animation names to play when agent dies */
var(Behavior) array<name> DeathAnimNames;
/** Below this speed, the walking animation is used (if the AnimTree has a SpeedBlendNode, and not using root motion) */
var(SpeedBlendAnim) float SpeedBlendStart;
/** Above this speed, the running animation is used. Between this and SpeedBlendStart the animations are blended (if the AnimTree has a SpeedBlendNode, and not using root motion)*/
var(SpeedBlendAnim) float SpeedBlendEnd;
/** This controls how the animation playback rate changes based on the speed of the agent (if not using root motion) */
var(SpeedBlendAnim) float AnimVelRate;
/** Limits how quickly blending between running and walking can happen. (if not using root motion) */
var(SpeedBlendAnim) float MaxSpeedBlendChangeSpeed;
/** Name of sync group for movement, whose rate is scaled (if not using root motion) */
var(SpeedBlendAnim) name MoveSyncGroupName;
/** Info about mesh we might want to use as an attachment. */
struct native GameCrowdAttachmentInfo
{
/** Pointer to mesh to attach */
var() StaticMesh StaticMesh;
/** Chance of choosing this attachment. */
var() float Chance;
/** Scaling applied to mesh when attached */
var() vector Scale3D;
structdefaultproperties
{
Chance=1.0
Scale3D=(X=1.0,Y=1.0,Z=1.0)
}
};
/** Info about things you can attach to one socket. */
struct native GameCrowdAttachmentList
{
/** Name of socket to attach mesh to */
var() name SocketName;
/** List of possible meshes to attach to this socket. */
var() array<GameCrowdAttachmentInfo> List;
};
/** List of sets of meshes to attach to agent. */
var(Rendering) array<GameCrowdAttachmentList> Attachments;
/** Maximum time to try to rotate toward a target before playing animation */
var(Behavior) float MaxTargetAcquireTime;
/** If true, clamp velocity based on root motion in movement animations */
var(Rendering) bool bUseRootMotionVelocity;
/** If true, then allow enabling/disable of the skeleton based on whether the crowd agent is ticking or not */
var(Rendering) bool bAllowSkeletonUpdateChangeBasedOnTickResult;
/** If true, always tick when not visible */
var(Tick) bool bTickWhenNotVisible;
/** If the crowd agent isn't visible for this length of time (in seconds), then stop ticking */
var(Tick) float NotVisibleDisableTickTime;
/** true if currently playing idle animation */
var bool bIsPlayingIdleAnimation;
/** true if currently playing death animation */
var bool bIsPlayingDeathAnimation;
/** If true, then the crowd agent is currently playing an important animation and should never try to disable the skeleton */
var bool bIsPlayingImportantAnimation;
/** Whether to perform animation updates this tick on this agent ( updated using ShouldPerformCrowdSimulation() )*/
var bool bAnimateThisTick;
/** Maximum distance from camera at which this agent should be animated */
var(LOD) float MaxAnimationDistance;
/** Keep square of MaxAnimationDistance for faster testing */
var float MaxAnimationDistanceSq;
cpptext
{
virtual void TickSpecial( FLOAT DeltaSeconds );
virtual void UpdatePendingVelocity( FLOAT DeltaTime );
}
simulated function PostBeginPlay()
{
super.PostBeginPlay();
if ( bDeleteMe )
{
return;
}
// Cache pointers to anim nodes
SpeedBlendNode = AnimNodeBlend(SkeletalMeshComponent.FindAnimNode('SpeedBlendNode'));
FullBodySlot = AnimNodeSlot(SkeletalMeshComponent.FindAnimNode('ActionBlendNode'));
ActionSeqNode = AnimNodeSequence(SkeletalMeshComponent.FindAnimNode('ActionSeqNode'));
WalkSeqNode = AnimNodeSequence(SkeletalMeshComponent.FindAnimNode('WalkSeqNode'));
RunSeqNode = AnimNodeSequence(SkeletalMeshComponent.FindAnimNode('RunSeqNode'));
AgentTree = AnimTree(SkeletalMeshComponent.Animations);
// Assign random walk/run cycle
if( (WalkSeqNode != None) && (WalkAnimNames.Length > 0) )
{
WalkSeqNode.SetAnim(WalkAnimNames[Rand(WalkAnimNames.length)]);
}
if( (RunSeqNode != None) && (RunAnimNames.Length > 0) )
{
RunSeqNode.SetAnim(RunAnimNames[Rand(RunAnimNames.length)]);
}
if( ActionSeqNode != None )
{
ActionSeqNode.bZeroRootTranslation = TRUE;
}
if ( bUseRootMotionVelocity )
{
SkeletalMeshComponent.RootMotionMode = RMM_Accel;
WalkSeqNode.SetRootBoneAxisOption(RBA_Translate, RBA_Translate, RBA_Translate);
RunSeqNode.SetRootBoneAxisOption(RBA_Translate, RBA_Translate, RBA_Translate);
}
MaxAnimationDistanceSq = MaxAnimationDistance * MaxAnimationDistance;
}
simulated function SetLighting(bool bEnableLightEnvironment, LightingChannelContainer AgentLightingChannel, bool bCastShadows)
{
Super.SetLighting(bEnableLightEnvironment, AgentLightingChannel, bCastShadows);
SkeletalMeshComponent.SetLightingChannels(AgentLightingChannel);
// Do attachments
CreateAttachments();
SkeletalMeshComponent.CastShadow = bCastShadows;
SkeletalMeshComponent.bCastDynamicShadow = bCastShadows;
SkeletalMeshComponent.ForceUpdate(FALSE);
/* if ( bEnableLightEnvironment )
{
LightEnvironment.bCastShadows = true;
}*/
}
/** Stop agent moving and play death anim */
native function PlayDeath(vector KillMomentum);
/**
* Enable or disable root motion for this agent
*/
native function SetRootMotion(bool bRootMotionEnabled);
/**
* Animation request from kismet
*/
simulated function OnPlayAgentAnimation(SeqAct_PlayAgentAnimation Action)
{
if ( Action.InputLinks[1].bHasImpulse )
{
Action.ActivateOutputLink(1);
// stop playing animations defined by action
StopBehavior(); // FIXMESTEVE - check matchup with action?
if ( CurrentDestination.ReachedByAgent(self, Location, false) )
{
CurrentDestination.ReachedDestination(self);
}
}
else
{
// play animations defined by action
Action.SetCurrentAnimationActionFor(self);
}
}
event ClearLatentAnimation()
{
ClearLatentAction(class'SeqAct_PlayAgentAnimation', false);
}
/**
* Play a looping idle animation
*/
simulated event PlayIdleAnimation()
{
bIsPlayingIdleAnimation = true;
FullBodySlot.PlayCustomAnim(IdleAnimNames[Rand(IdleAnimNames.length)], 1.0, 0.1, 0.1, true, false);
}
simulated event StopIdleAnimation()
{
FullBodySlot.StopCustomAnim(0.1);
bIsPlayingIdleAnimation = false;
}
event OnAnimEnd(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
{
// called because we asked for early notify
if ( CurrentBehavior != None )
{
CurrentBehavior.OnAnimEnd(SeqNode, PlayedTime, ExcessTime);
}
}
/** Create any attachments */
simulated function CreateAttachments()
{
local int AttachIdx, InfoIdx, PickedInfoIdx;
local float ChanceTotal, RandVal;
local StaticMeshComponent StaticMeshComp;
local bool bUseSocket, bUseBone;
// Iterate over each list/attachment point.
for(AttachIdx=0; AttachIdx < Attachments.length; AttachIdx++ )
{
// Skip over empty lists
if(Attachments[AttachIdx].List.length == 0)
{
continue;
}
// We need to choose one from he list, using the 'Chance' values.
// First we need to total of all of them
ChanceTotal = 0.0;
for(InfoIdx=0; InfoIdx < Attachments[AttachIdx].List.length; InfoIdx++)
{
ChanceTotal += Attachments[AttachIdx].List[InfoIdx].Chance;
}
// Now pick a value between 0.0 and ChanceTotal
RandVal = FRand() * ChanceTotal;
// Now go over list again - when we pass RandVal, that is our attachment
ChanceTotal = 0.0;
for(InfoIdx=0; InfoIdx < Attachments[AttachIdx].List.length; InfoIdx++)
{
ChanceTotal += Attachments[AttachIdx].List[InfoIdx].Chance;
if(ChanceTotal >= RandVal)
{
PickedInfoIdx = InfoIdx;
break;
}
}
// Ok, so now we know what we want to attach.
if( Attachments[AttachIdx].List[PickedInfoIdx].StaticMesh != None )
{
// See if name is a socket or a bone (if both, favours socket)
bUseSocket = (SkeletalMeshComponent.GetSocketByName(Attachments[AttachIdx].SocketName) != None);
bUseBone = (SkeletalMeshComponent.MatchRefBone(Attachments[AttachIdx].SocketName) != INDEX_NONE);
// See if we found valid attachment point
if(bUseSocket || bUseBone)
{
// Actually create the StaticMeshComponent
StaticMeshComp = new(self) class'StaticMeshComponent';
StaticMeshComp.SetStaticMesh( Attachments[AttachIdx].List[PickedInfoIdx].StaticMesh );
StaticMeshComp.SetActorCollision(FALSE, FALSE);
StaticMeshComp.SetScale3D( Attachments[AttachIdx].List[PickedInfoIdx].Scale3D );
StaticMeshComp.SetLightEnvironment(LightEnvironment);
// Attach it to socket or bone
if(bUseSocket)
{
SkeletalMeshComponent.AttachComponentToSocket(StaticMeshComp, Attachments[AttachIdx].SocketName);
}
else
{
SkeletalMeshComponent.AttachComponent(StaticMeshComp, Attachments[AttachIdx].SocketName);
}
}
else
{
`log("CrowdAgent: WARNING: Could not find socket or bone called '"$Attachments[AttachIdx].SocketName$"' for mesh '"@Attachments[AttachIdx].List[PickedInfoIdx].StaticMesh$"'");
}
}
}
}
defaultproperties
{
SpeedBlendStart=150.0
SpeedBlendEnd=180.0
AnimVelRate=0.0098
MaxSpeedBlendChangeSpeed=2.0
MoveSyncGroupName=MoveGroup
MaxTargetAcquireTime=5.0
MaxAnimationDistance=12000.0
NotVisibleDisableTickTime=0.2
bAllowSkeletonUpdateChangeBasedOnTickResult=true
Begin Object Class=SkeletalMeshComponent Name=SkeletalMeshComponent0
CollideActors=true
bEnableLineCheckWithBounds=TRUE
BlockActors=false
BlockZeroExtent=true
BlockNonZeroExtent=false
BlockRigidBody=false
LightEnvironment=MyLightEnvironment
RBChannel=RBCC_GameplayPhysics
bCastDynamicShadow=FALSE
RBCollideWithChannels=(Default=TRUE,GameplayPhysics=TRUE,EffectPhysics=TRUE)
bUpdateSkelWhenNotRendered=FALSE
bAcceptsDynamicDecals=FALSE // for crowds there are so many of them probably not going to notice not getting decals on them. Each decal on them causes entire SkelMesh to be rerendered
bTickAnimNodesWhenNotRendered=FALSE
End Object
SkeletalMeshComponent=SkeletalMeshComponent0
Components.Add(SkeletalMeshComponent0)
}

View File

@ -0,0 +1,91 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdBehaviorPoint extends GameCrowdInteractionPoint
native
placeable
config(Crowd)
dependson(GameCrowdAgentBehavior);
/** Raius of this event */
var config float RadiusOfBehaviorEvent;
/** Duration of how long this event will last **/
var config float DurationOfBehaviorEvent;
/** Event type to pass to crowd within radius */
var() ECrowdBehaviorEvent EventType;
var() bool bRequireLOS;
var Actor Initiator;
cpptext
{
virtual void TickSpecial( FLOAT DeltaSeconds );
virtual UBOOL IsOverlapping( AActor *Other, FCheckResult* Hit=NULL, UPrimitiveComponent* OtherPrimitiveComponent=NULL, UPrimitiveComponent* MyPrimitiveComponent=NULL );
}
event PostBeginPlay()
{
Super.PostBeginPlay();
if( RadiusOfBehaviorEvent > 0.f )
{
CylinderComponent.SetCylinderSize( RadiusOfBehaviorEvent, 200.0f );
}
if( DurationOfBehaviorEvent > 0.0f )
{
SetTimer( DurationOfBehaviorEvent, FALSE, nameof(DestroySelf) );
}
}
function DestroySelf()
{
LifeSpan = 0.001f;
}
event Touch( Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal )
{
local GameCrowdAgent Agent;
Agent = GameCrowdAgent(Other);
if( Agent != None )
{
if( !bRequireLOS || FastTrace( Other.Location, Location ) )
{
Agent.HandleBehaviorEvent( EventType, Initiator, FALSE, TRUE );
}
}
Super.Touch(Other, OtherComp, HitLocation, HitNormal);
}
defaultproperties
{
TickGroup=TG_DuringAsyncWork
bNoDelete=FALSE
bCollideActors=TRUE
Begin Object NAME=CollisionCylinder
CollideActors=TRUE
bDrawNonColliding=TRUE
CollisionRadius=512
CollisionHeight=200
CylinderColor=(R=0,G=255,B=0)
bDrawBoundingBox=TRUE
HiddenGame=FALSE
HiddenEditor=FALSE
End Object
Begin Object Name=Sprite
Sprite=Texture2D'EditorResources.Crowd.T_Crowd_Behavior'
HiddenGame=TRUE
HiddenEditor=FALSE
AlwaysLoadOnClient=FALSE
AlwaysLoadOnServer=FALSE
Scale=0.5
End Object
}

View File

@ -0,0 +1,223 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdBehavior_PlayAnimation extends GameCrowdAgentBehavior
native
placeable
dependsOn(GameCrowdAgent);
/** List of animations to play */
var() Array<name> AnimationList;
/** Time to blend into next animation. */
var() float BlendInTime;
/** Time to blend out of animation. */
var() float BlendOutTime;
/** Whether to use root motion. */
var() bool bUseRootMotion;
/** If true, face player before starting animation. */
var() bool bLookAtPlayer;
/** Used by kismet PlayAgentAnimation */
var Actor CustomActionTarget;
/** If true, loop the animation in the list specified by LoopIndex. */
var() bool bLooping;
/** Which animation to loop in AnimationList if bLooping == TRUE */
var() int LoopIndex;
/** How long to loop the animation if bLooping == TRUE, -1.f == infinite */
var() float LoopTime;
/** Whether should blend between animations in the list. Set True if they don't match at start/end */
var() bool bBlendBetweenAnims;
/** Kismet AnimSequence that spawned this behavior (optional) */
var SeqAct_PlayAgentAnimation AnimSequence;
/** Index into animationlist of current animation action */
var int AnimationIndex;
function InitBehavior(GameCrowdAgent Agent)
{
local PlayerController PC, ClosestPC;
local float ClosestDist, NewDist;
local GameCrowdAgentSkeletal SkAgent;
Super.InitBehavior(Agent);
if ( CustomActionTarget != None )
{
ActionTarget = CustomActionTarget;
}
else if ( bLookAtPlayer )
{
ClosestDist = 1000000.0;
// find local player, make it the action target
foreach Agent.LocalPlayerControllers(class'PlayerController', PC)
{
if ( PC.Pawn != None )
{
NewDist = VSize(PC.Pawn.Location - Agent.Location);
if ( NewDist < ClosestDist )
{
ClosestDist = NewDist;
ClosestPC = PC;
}
}
}
if ( ClosestPC != None )
{
ActionTarget = ClosestPC.Pawn;
}
}
SkAgent = GameCrowdAgentSkeletal(Agent);
if ( SKAgent == None )
{
`warn("PlayAnimation behavior "$self$" called on non-skeletal agent "$Agent);
return;
}
AnimationIndex = 0;
if ( !bFaceActionTargetFirst )
{
PlayAgentAnimationNow();
}
}
/**
* Facing target, so play animation
*/
event FinishedTargetRotation()
{
PlayAgentAnimationNow();
}
/**
* Set the "Out Agent" output of the current sequence to be MyAgent.
*/
native function SetSequenceOutput();
/**
* When an animation ends, play the next animation in the list.
* If done with list, if associated with a sequence, trigger its output.
*/
event OnAnimEnd(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
{
// called because we asked for early notify
AnimationIndex++;
if ( AnimationList.Length > AnimationIndex )
{
PlayAgentAnimationNow();
}
else
{
// see if there is another sequence attached to this one
if ( (AnimSequence != None) && (AnimSequence.OutputLinks[0].Links.Length > 0) )
{
SetSequenceOutput();
MyAgent.ClearLatentAction(class'SeqAct_PlayAgentAnimation', false);
AnimSequence.ActivateOutputLink(0);
}
MyAgent.StopBehavior();
}
}
/**
* Play the requested animation
*/
function PlayAgentAnimationNow()
{
local float CurrentBlendInTime, CurrentBlendOutTime;
local GameCrowdAgentSkeletal MySkAgent;
MySkAgent = GameCrowdAgentSkeletal(MyAgent);
bFaceActionTargetFirst = false;
MySkAgent.SetRootMotion(bUseRootMotion);
CurrentBlendInTime = 0.0;
CurrentBlendOutTime = 0.0;
// loop if bLooping set AND marked for looping
if ( bLooping && AnimationIndex == LoopIndex )
{
if ( bBlendBetweenAnims || (AnimationIndex == 0) )
{
CurrentBlendInTime = BlendInTime;
}
MySkAgent.FullBodySlot.PlayCustomAnim(AnimationList[AnimationIndex], 1.f, CurrentBlendInTime, CurrentBlendOutTime, bLooping, true);
if ( LoopTime > 0.0 )
{
MySkAgent.SetTimer(LoopTime,FALSE,nameof(OnAnimEnd));
}
}
else
{
if ( bBlendBetweenAnims )
{
CurrentBlendInTime = BlendInTime;
CurrentBlendOutTime = BlendOutTime;
}
else if ( AnimationIndex == 0 )
{
CurrentBlendInTime = BlendInTime;
}
MySkAgent.FullBodySlot.PlayCustomAnim(AnimationList[AnimationIndex], 1.f, CurrentBlendInTime, CurrentBlendOutTime, false, true);
MySkAgent.FullBodySlot.SetActorAnimEndNotification(true);
}
if ( AnimSequence != None )
{
AnimSequence.ActivateOutputLink(2);
}
}
function StopBehavior()
{
GameCrowdAgentSkeletal(MyAgent).FullBodySlot.StopCustomAnim(BlendOutTime);
GameCrowdAgentSkeletal(MyAgent).SetRootMotion(FALSE);
super.StopBehavior();
}
/**
* Get debug string about agent behavior
*/
function string GetBehaviorString()
{
local string BehaviorString;
BehaviorString = "Behavior: "$self;
if ( bFaceActionTargetFirst )
{
BehaviorString = BehaviorString@"Turning toward "$ActionTarget;
}
else if ( (AnimationList.length <= AnimationIndex) || (AnimationList[AnimationIndex] == '') )
{
BehaviorString = BehaviorString@"MISSING ANIMATION";
}
else
{
BehaviorString = BehaviorString@"Playing "$AnimationList[AnimationIndex];
}
return BehaviorString;
}
defaultproperties
{
bIdleBehavior=true
AnimationIndex=0
BlendInTime=0.2
BlendOutTime=0.2
bBlendBetweenAnims=false
LoopTime=-1.f
}

View File

@ -0,0 +1,87 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdBehavior_RunFromPanic extends GameCrowdAgentBehavior
native
placeable
dependsOn(GameCrowdAgent);
/** Actor who caused panic - if set, flee this actor */
var transient Actor PanicFocus;
function ActivatedBy(Actor NewActionTarget)
{
local GameCrowdDestination TempDest, PrevDest;
// don't pass action target to super - we don't want to look at it
PanicFocus = NewActionTarget;
PrevDest = MyAgent.PreviousDestination;
// see if already heading away from danger
if( MyAgent.CurrentDestination != None && AllowThisDestination(MyAgent.CurrentDestination) )
{
return;
}
else if( PrevDest != None && PrevDest.AllowableDestinationFor(MyAgent) )
{
// try heading back where agent was coming from
TempDest = MyAgent.CurrentDestination;
MyAgent.CurrentDestination.DecrementCustomerCount(MyAgent);
MyAgent.SetCurrentDestination(MyAgent.PreviousDestination);
MyAgent.PreviousDestination = TempDest;
MyAgent.UpdateIntermediatePoint();
}
}
function InitBehavior(GameCrowdAgent Agent)
{
super.InitBehavior(Agent);
MyAgent.bIsPanicked = TRUE;
MyAgent.SetMaxSpeed();
}
function StopBehavior()
{
Super.StopBehavior();
MyAgent.bIsPanicked = FALSE;
MyAgent.SetMaxSpeed();
}
function Actor GetBehaviorInstigator()
{
return PanicFocus;
}
/**
* Return true if agent is allowed to go to destination while panicked
*/
function bool AllowThisDestination(GameCrowdDestination Destination)
{
return !Destination.bAvoidWhenPanicked && !Destination.AtCapacity() && (Destination.bFleeDestination || (PanicFocus == None) || (((Destination.Location - MyAgent.Location) dot (MyAgent.Location - PanicFocus.Location)) > 0.0));
}
/**
* return true if get kismet or new behavior from this destination
*/
function bool AllowBehaviorAt(GameCrowdDestination Destination)
{
return !Destination.bSkipBehaviorIfPanicked && !Destination.bAvoidWhenPanicked;
}
function string GetBehaviorString()
{
return "Run from PANIC "@PanicFocus;
}
defaultproperties
{
bIsViralBehavior=TRUE
MaxPlayerDistance=20000.0
MyEventType=CBE_Panic
ViralBehaviorEvent=CBE_Alert
DebugBehaviorColor=(R=255,G=0,B=0)
}

View File

@ -0,0 +1,54 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdBehavior_WaitForGroup extends GameCrowdAgentBehavior
native
placeable
dependsOn(GameCrowdAgent);
function InitBehavior(GameCrowdAgent Agent)
{
Super.InitBehavior(Agent);
Agent.PlayIdleAnimation();
}
/**
* Get debug string about agent behavior
*/
function string GetBehaviorString()
{
local string BehaviorString;
BehaviorString = "Behavior: "$self;
if ( bFaceActionTargetFirst )
{
BehaviorString = BehaviorString@"Turning toward "$ActionTarget;
}
else
{
BehaviorString = BehaviorString@"Waiting For Group";
}
return BehaviorString;
}
/**
* Called every tick when agent is currently idle (because bIdleBehavior is true)
*
* @RETURN true if should end idle (bIdleBehavior should also become false)
*/
native function bool ShouldEndIdle();
function StopBehavior()
{
super.StopBehavior();
MyAgent.StopIdleAnimation();
}
defaultproperties
{
bIdleBehavior=true
bFaceActionTargetFirst=true
}

View File

@ -0,0 +1,72 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdBehavior_WaitInQueue extends GameCrowdAgentBehavior
native
notplaceable
dependsOn(GameCrowdAgent);
/** Keep from re-entering StopBehavior during queue clean up */
var bool bStoppingBehavior;
/** Current Queue position (associated with CurrentDestination */
var GameCrowdDestinationQueuePoint QueuePosition;
/**
* Handles movement destination updating for agent.
*
* @RETURNS true if destination updating was handled
*/
native function bool HandleMovement();
/**
* Notification that MyAgent is changing destinations
*/
function ChangingDestination(GameCrowdDestination NewDest)
{
if ( QueuePosition == None )
{
`warn(MyAgent$" should never have no QueuePosition");
}
MyAgent.StopBehavior();
}
function Actor GetDestinationActor()
{
return QueuePosition;
}
function string GetBehaviorString()
{
if ( QueuePosition != None )
{
return self$" Waiting in line at "$QueuePosition;
}
else
{
return self$" Queue Behavior with NO QUEUEPOSITION!";
}
}
native function bool ShouldEndIdle();
function StopBehavior()
{
if ( !bStoppingBehavior )
{
bStoppingBehavior = true;
super.StopBehavior();
if ( QueuePosition != None )
{
QueuePosition.ClearQueue(MyAgent);
}
QueuePosition = None;
MyAgent.StopIdleAnimation();
bStoppingBehavior = false;
}
}
defaultproperties
{
bIdleBehavior=true

View File

@ -0,0 +1,762 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Where crowd agent is going. Destinations can kill agents that reach them or route them to another destination
*
*/
class GameCrowdDestination extends GameCrowdInteractionPoint
implements(GameCrowdSpawnInterface)
implements(EditorLinkSelectionInterface)
dependsOn(GameCrowdAgent)
native;
/** If TRUE, kill crowd members when they reach this destination. */
var() bool bKillWhenReached;
// randomly pick from this list of active destinations
var() duplicatetransient array<GameCrowdDestination> NextDestinations;
/** queue point to use if this destination is at capacity */
var() duplicatetransient GameCrowdDestinationQueuePoint QueueHead;
// whether agents previous destination can be used as a destination if in list of NextDestinations
var() bool bAllowAsPreviousDestination;
/** How many agents can simultaneously have this as a destination */
var() int Capacity;
/** Adjusts the likelihood of agents to select this destination from list at previous destination*/
var() float Frequency;
/** Current number of agents using this destination */
var private int CustomerCount;
/** if set, only agents of this class can use this destination */
var(Restrictions) array<class<GameCrowdAgent> > SupportedAgentClasses;
/** if set, agents from this archetype can use this destination */
var(Restrictions) array<object> SupportedArchetypes;
/** if set, agents of this class cannot use this destination */
var(Restrictions) array<class<GameCrowdAgent> > RestrictedAgentClasses;
/** if set, agents from this archetype cannot use this destination */
var(Restrictions) array<object> RestrictedArchetypes;
/** Don't go to this destination if panicked */
var() bool bAvoidWhenPanicked;
/** Don't perform kismet or custom behavior at this destination if panicked */
var() bool bSkipBehaviorIfPanicked;
/** Always run toward this destination */
var() bool bFleeDestination;
/** Must reach this destination exactly - will force movement when close */
var() bool bMustReachExactly;
var float ExactReachTolerance;
/** True if has supported class/archetype restrictions */
var bool bHasRestrictions;
/** Type of interaction */
var() Name InteractionTag;
/** Time before an agent is allowed to attempt this sort of interaction again */
var() float InteractionDelay;
/** True if spawning permitted at this node */
var(Spawning) bool bAllowsSpawning;
var(Spawning) bool bAllowCloudSpawning;
var(Spawning) bool bAllowVisibleSpawning;
/** Spawn in a line rather than in a circle. */
var(Spawning) bool bLineSpawner;
/** Whether to spawn agents only at the edge of the circle, or at any point within the circle. */
var(Spawning) bool bSpawnAtEdge;
/** Agents reaching this destination will pick a behavior from this list */
var() array<BehaviorEntry> ReachedBehaviors;
/** Whether agent should stop on reach edge of destination radius (if not reach exactly), or have a "soft" perimeter */
var() bool bSoftPerimeter;
/** Agent currently coming to this destination. Not guaranteed to be correct/exhaustive. Used to allow agents to trade places with nearer agent for destinations with queueing */
var GameCrowdAgent AgentEnRoute;
//=========================================================
/** The following properties are set and used by the GameCrowdPopulationManager class for selecting at which destinations to spawn agents */
/** True if currently in line of sight of a player (may not be within view frustrum) */
var bool bIsVisible;
/** True if will become visible shortly based on player's current velocity */
var bool bWillBeVisible;
/** This destination is currently available for spawning */
var bool bCanSpawnHereNow;
/** This destination is beyond the maximum spawn distance */
var bool bIsBeyondSpawnDistance;
/** Cache that node is currently adjacent to a visible node */
var bool bAdjacentToVisibleNode;
/** Whether there is a valid NavigationMesh around this destination */
var bool bHasNavigationMesh;
/** Priority for spawning agents at this destination */
var float Priority;
/** Most recent time at which agent was spawned at this destination */
var float LastSpawnTime;
/** Population manager with which this destination is associated */
var transient GameCrowdPopulationManager MyPopMgr;
cpptext
{
/** EditorLinkSelectionInterface */
virtual void LinkSelection(USelection* SelectedActors);
virtual void UnLinkSelection(USelection* SelectedActors);
/**
* Function that gets called from within Map_Check to allow this actor to check itself
* for any potential errors and register them with map check dialog.
*/
#if WITH_EDITOR
virtual void CheckForErrors();
#endif
};
/**
* @PARAM Agent is the agent being checked
@PARAM Testposition is the position to be tested
@PARAM bTestExactly if true and GameCrowdDestination.bMustReachExactly is true means ReachedByAgent() only returns true if right on the destination
* @RETURNS TRUE if Agent has reached this destination
*/
native simulated function bool ReachedByAgent(GameCrowdAgent Agent, vector TestPosition, bool bTestExactly);
simulated function PostBeginPlay()
{
local int i;
local GameCrowdPopulationManager PopMgr;
super.PostBeginPlay();
bHasRestrictions = (SupportedAgentClasses.Length > 0) || (SupportedArchetypes.Length > 0) || (RestrictedAgentClasses.Length > 0) || (RestrictedArchetypes.Length > 0);
// don't allow automatic agent spawning at destinations with Queues, or small capacities
if( QueueHead != None || bKillWhenReached )
{
bAllowsSpawning = false;
}
// verify behavior lists
for ( i=0; i< ReachedBehaviors.Length; i++ )
{
if ( ReachedBehaviors[i].BehaviorArchetype == None )
{
`warn(self$" missing BehaviorArchetype at ReachedBehavior "$i);
ReachedBehaviors.remove(i,1);
i--;
}
}
// Add self to population manager list
PopMgr = GameCrowdPopulationManager(WorldInfo.PopulationManager);
if ( PopMgr != None )
{
PopMgr.AddSpawnPoint(self);
}
}
simulated function Destroyed()
{
super.Destroyed();
if ( MyPopMgr != None )
{
MyPopMgr.RemoveSpawnPoint(self);
}
}
/**
* Called after Agent reaches this destination
* Will be called every tick as long as ReachedByAgent() is true and agent is not idle, so should change
* Agent to avoid this (change current destination, make agent idle, or kill agent) )
*
* @PARAM Agent is the crowd agent that just reached this destination
* @PARAM bIgnoreKismet skips generating Kismet event if true.
*/
simulated event ReachedDestination(GameCrowdAgent Agent)
{
local int i,j;
local SeqEvent_CrowdAgentReachedDestination ReachedEvent;
local bool bEventActivated;
// check if kismet event on reaching this destination
for( i = 0; i < GeneratedEvents.Length; i++ )
{
ReachedEvent = SeqEvent_CrowdAgentReachedDestination(GeneratedEvents[i]);
for( j = 0; j < ReachedEvent.OutputLinks[0].Links.Length; j++ )
{
// HACKY - clear bActive on output ops so this agent can get in on an already active latent action
ReachedEvent.OutputLinks[0].Links[j].LinkedOp.bActive = FALSE;
}
bEventActivated = ReachedEvent.CheckActivate( self, Agent );
break;
}
// kill agent that reached me?
if( bKillWhenReached )
{
// If desired, kill actor when it reaches an attractor
DecrementCustomerCount(Agent);
Agent.CurrentDestination = None;
Agent.KillAgent();
return;
}
// mark the interaction if tagged
if( InteractionTag != '' )
{
i = Agent.RecentInteractions.Add(1);
Agent.RecentInteractions[i].InteractionTag = InteractionTag;
if( InteractionDelay > 0.f )
{
// mark the time to remove this interaction from history
Agent.RecentInteractions[i].InteractionDelay = WorldInfo.TimeSeconds + InteractionDelay;
}
}
// Can agent perform a custom behavior here
if( Agent.BehaviorDestination != self && (Agent.CurrentBehavior == None || Agent.CurrentBehavior.AllowBehaviorAt(self)) )
{
// Assign a reachedbehavior to the agent
if( ReachedBehaviors.Length > 0 )
{
Agent.PickBehaviorFrom(ReachedBehaviors);
}
if( ReachedEvent != None )
{
Agent.BehaviorDestination = self;
}
}
// choose next destination
if( !bEventActivated && NextDestinations.Length > 0 )
{
PickNewDestinationFor( Agent, FALSE );
if( Agent.CurrentDestination == None )
{
// if haven't been visible for a while, just kill
if( Agent.NotVisibleLifeSpan > 0.f && `TimeSince(Agent.LastRenderTime) > Agent.NotVisibleLifeSpan )
{
Agent.KillAgent();
}
else
{
// failed with restrictions, so pick any - FIXMESTEVE probably want more refined fallback
PickNewDestinationFor( Agent, TRUE );
}
}
}
// first in group to get new destination should update others
if( Agent.MyGroup != None )
{
Agent.MyGroup.UpdateDestinations(Agent.CurrentDestination);
}
}
/**
* Pick a new destination from this one for agent.
*/
simulated function PickNewDestinationFor(GameCrowdAgent Agent, bool bIgnoreRestrictions )
{
local int i;
local float DestinationFrequencySum, DestinationPickValue;
local array<GameCrowdDestination> DestOptions;
// Pick a new destination from available list
DecrementCustomerCount(Agent);
Agent.CurrentDestination = None;
Agent.BehaviorDestination = None;
// init DestinationFrequencySum
for( i=0; i< NextDestinations.Length; i++ )
{
if ( (NextDestinations[i] != None) && NextDestinations[i].bHasNavigationMesh && (bIgnoreRestrictions || NextDestinations[i].AllowableDestinationFor(Agent)) )
{
// bonus to this potential destination's frequency if current destination is not visible, and destination is, and agent prefers visible destinations
DestinationFrequencySum += NextDestinations[i].Frequency * ((!bIsVisible && Agent.bPreferVisibleDestination && (NextDestinations[i].bIsVisible || NextDestinations[i].bWillBeVisible)) ? 2.0 : 1.0);
DestOptions.AddItem( NextDestinations[i] );
}
}
DestinationPickValue = DestinationFrequencySum * FRand();
DestinationFrequencySum = 0.0;
for( i = 0; i < DestOptions.Length; i++ )
{
if( DestOptions[i] != None && DestOptions[i].bHasNavigationMesh )
{
// bonus to this potential destination's frequency if current destination is not visible, and destination is, and agent prefers visible destinations
DestinationFrequencySum += DestOptions[i].Frequency * ((!bIsVisible && Agent.bPreferVisibleDestination && (DestOptions[i].bIsVisible || DestOptions[i].bWillBeVisible)) ? 2.0 : 1.0);
if( DestinationPickValue < DestinationFrequencySum )
{
Agent.SetCurrentDestination(DestOptions[i]);
Agent.PreviousDestination = self;
Agent.UpdateIntermediatePoint();
break;
}
}
}
Agent.PreviousDestination = self;
}
/**
* Decrement customer count. Update Queue if have one
* Be sure to decrement customer count from old destination before setting a new one!
* FIXMESTEVE - should probably wrap decrement old customercount into GameCrowdAgent.SetDestination()
*/
simulated event DecrementCustomerCount(GameCrowdAgent DepartingAgent)
{
local GameCrowdDestinationQueuePoint QP;
local bool bIsInQueue;
// Check to make sure that the current destination is ourself, to prevent double decrementing
if( DepartingAgent.CurrentDestination == self )
{
// check if departing agent is in queue
for( QP = QueueHead; QP != None; QP = QP.NextQueuePosition )
{
if ( QP.QueuedAgent == DepartingAgent )
{
bIsInQueue = true;
QP.ClearQueue(DepartingAgent);
break;
}
}
if( !bIsInQueue )
{
// agent was customer, so clear him out
CustomerCount--;
if( QueueHead != None && QueueHead.HasCustomer() )
{
QueueHead.AdvanceCustomerTo(self);
}
}
}
}
/**
* Increment customer count, or add agent to queue if needed
*/
simulated event IncrementCustomerCount(GameCrowdAgent ArrivingAgent)
{
// if at capacity, or queue is about to move forward, add to queue rather than directly
if( AtCapacity() || (Queuehead != None && Queuehead.bPendingAdvance) )
{
// add to queue
if( QueueHead != None && QueueHead.HasSpace() )
{
// maybe switch with agent currently in route, if ArrivingAgent is closer
if ( (AgentEnRoute != None) && (AgentEnRoute.CurrentBehavior == None) && !ReachedByAgent(AgentEnRoute, AgentEnRoute.Location, false)
&& (VSizeSq(ArrivingAgent.Location - Location) < VSizeSq(AgentEnRoute.Location - Location)) )
{
// switch places
//`log("Switching "$ArrivingAgent$" for "$AgentEnRoute);
QueueHead.AddCustomer(AgentEnRoute,self);
AgentEnRoute = ArrivingAgent;
}
else
{
QueueHead.AddCustomer(ArrivingAgent,self);
}
}
else
{
if( QueueHead != None )
{
`warn(self$" added customer "$ArrivingAgent$" beyond capacity with queue "$QueueHead);
}
}
}
else
{
AgentEnRoute = ArrivingAgent;
CustomerCount++;
}
}
simulated function bool AtCapacity( optional byte CheckCnt )
{
return (CustomerCount + CheckCnt) >= Capacity;
}
/**
* Returns true if this destination is valid for Agent
*/
simulated event bool AllowableDestinationFor(GameCrowdAgent Agent)
{
local int i;
local bool bSupported;
if( !bHasNavigationMesh || !bIsEnabled )
{
return FALSE;
}
if( bIsBeyondSpawnDistance )
{
// FIXMESTEVE - maybe allow moving to beyond max spawn distance destination if currently close enough
return FALSE;
}
if( !bAllowAsPreviousDestination && Agent.PreviousDestination == self )
{
return FALSE;
}
// check if allowed by agent's behavior
if( Agent.CurrentBehavior != None && !Agent.CurrentBehavior.AllowThisDestination(self) )
{
return FALSE;
}
// check if destination has room - make sure there is room for the whole group
if( (Agent.MyGroup != None && AtCapacity(Agent.MyGroup.Members.Length-1)) || AtCapacity() || (QueueHead != None && !QueueHead.HasSpace()) )
{
return FALSE;
}
// check if this interaction is tagged
if( InteractionTag != '' )
{
i = Agent.RecentInteractions.Find('InteractionTag',InteractionTag);
if( i != INDEX_NONE && (Agent.RecentInteractions[i].InteractionDelay == 0.f || WorldInfo.TimeSeconds < Agent.RecentInteractions[i].InteractionDelay) )
{
return FALSE;
}
else if( i != INDEX_NONE )
{
// clear out the old interaction
Agent.RecentInteractions.Remove(i,1);
}
}
if( bHasRestrictions )
{
// make sure the agent class/archetype is supported
if( SupportedAgentClasses.Length > 0 || SupportedArchetypes.Length > 0 )
{
bSupported = FALSE;
for( i = 0; i < SupportedAgentClasses.Length; i++ )
{
if( ClassIsChildOf( Agent.Class, SupportedAgentClasses[i] ) )
{
bSupported = TRUE;
break;
}
}
// only check against supported archetypes if failed supported classes list
if( !bSupported )
{
for( i = 0; i < SupportedArchetypes.Length; i++ )
{
if( SupportedArchetypes[i] == Agent.MyArchetype )
{
bSupported = TRUE;
break;
}
}
}
if( !bSupported )
{
return FALSE;
}
}
// if passed the supported test, make sure not in restricted classes list
for( i = 0; i < RestrictedAgentClasses.Length; i++ )
{
if( ClassIsChildOf( Agent.Class, RestrictedAgentClasses[i] ) )
{
return FALSE;
}
}
for( i = 0; i < RestrictedArchetypes.Length; i++ )
{
if( RestrictedArchetypes[i] == Agent.MyArchetype )
{
return FALSE;
}
}
}
return TRUE;
}
simulated function float GetSpawnRadius()
{
return CylinderComponent.CollisionRadius;
}
// FIXMESTEVE - natively show the spawn line in the editor if bLineSpawner
simulated function GetSpawnPosition(SeqAct_GameCrowdSpawner Spawner, out vector SpawnPos, out rotator SpawnRot)
{
local vector SpawnLine;
local float RandScale;
// LINE SPAWN
if(bLineSpawner)
{
// Scale between -1.0 and 1.0
RandScale = -1.0 + (2.0 * FRand());
// Get line along which to spawn.
SpawnLine = vect(0,1,0) >> Rotation;
// Now make the position
SpawnPos = Location + (RandScale * SpawnLine * GetSpawnRadius());
// Always face same way as spawn location
SpawnRot.Yaw = Rotation.Yaw;
}
else
{
// CIRCLE SPAWN
SpawnRot = RotRand(false);
SpawnRot.Pitch = 0;
if(bSpawnAtEdge)
{
SpawnPos = Location + ((vect(1,0,0) * GetSpawnRadius()) >> SpawnRot);
}
else
{
SpawnPos = Location + ((vect(1,0,0) * FRand() * GetSpawnRadius()) >> SpawnRot);
}
}
}
simulated function bool AnalyzeSpawnPoint( const out array<CrowdSpawnerPlayerInfo> PlayerInfo, float MaxSpawnDistSq, bool bForceNavMeshPathing, NavigationHandle NavHandle )
{
local Actor HitActor;
local vector HitLocation, HitNormal;
local int NextIdx, PlayerIdx;
local GameCrowdDestination NextGCD;
local float DistFromView, DistFromPred;
bIsVisible = TRUE;
bAdjacentToVisibleNode = FALSE;
bWillBeVisible = FALSE;
Priority = 0.f;
bCanSpawnHereNow = FALSE;
bHasNavigationMesh = TRUE;
bIsBeyondSpawnDistance = TRUE;
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
{
DistFromView = VSizeSq(PlayerInfo[PlayerIdx].ViewLocation - Location);
DistFromPred = VSizeSq(PlayerInfo[PlayerIdx].PredictLocation - Location);
if( FMin( DistFromView, DistFromPred ) < MaxSpawnDistSq )
{
bIsBeyondSpawnDistance = FALSE;
break;
}
}
if( bIsEnabled && bAllowsSpawning )
{
if( bForceNavMeshPathing && NavHandle.LineCheck(Location, Location - vect(0,0,3)* CylinderComponent.CollisionHeight, vect(0,0,0)) )
{
// no nav mesh streamed in, so can't use for spawning
bHasNavigationMesh = FALSE;
}
else
{
if( !bIsBeyondSpawnDistance )
{
bCanSpawnHereNow = TRUE;
bIsVisible = FALSE;
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
{
HitActor = Trace(HitLocation, HitNormal, Location, PlayerInfo[PlayerIdx].ViewLocation, FALSE);
if( HitActor == None )
{
bIsVisible = TRUE;
break;
}
}
if( !bIsVisible )
{
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
{
HitActor = Trace(HitLocation, HitNormal, Location, PlayerInfo[PlayerIdx].PredictLocation, FALSE);
if( HitActor == None )
{
bWillBeVisible = TRUE;
break;
}
}
}
}
if( bIsVisible )
{
// Allow spawning at destinations beyond the max spawn dist if connected to visible destinations inside the spawn dist
for( NextIdx = 0; NextIdx < NextDestinations.Length; NextIdx++ )
{
NextGCD = NextDestinations[NextIdx];
if( NextGCD != None && NextGCD.bIsVisible && NextGCD.bCanSpawnHereNow && !NextGCD.bIsBeyondSpawnDistance )
{
bAdjacentToVisibleNode = TRUE;
if( bIsBeyondSpawnDistance )
{
bCanSpawnHereNow = TRUE;
}
}
}
}
}
return TRUE;
}
return FALSE;
}
simulated function PrioritizeSpawnPoint( const out array<CrowdSpawnerPlayerInfo> PlayerInfo, float MaxSpawnDist )
{
local float DistToSpawn;
local int PlayerIdx;
// Priority based on inverse of distance
DistToSpawn = 999999.f;
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
{
DistToSpawn = FMin( DistToSpawn, VSize(Location - PlayerInfo[PlayerIdx].ViewLocation));
}
Priority = 1.f - ((MaxSpawnDist - DistToSpawn) / MaxSpawnDist);
// prefer destinations that are about to become visible
if( bWillBeVisible )
{
Priority *= 10.f;
}
else if( bAdjacentToVisibleNode )
{
Priority *= 5.f;
}
// prefer destinations that haven't been used recently
Priority *= FMin(`TimeSince(LastSpawnTime), 10.0);
}
function float GetDestinationRadius()
{
return CylinderComponent.CollisionRadius;
}
simulated function DrawDebug( const out array<CrowdSpawnerPlayerInfo> PlayerInfo, optional bool bPresistent )
{
local int PlayerIdx;
local Vector Extent;
Extent.X = CylinderComponent.CollisionRadius;
Extent.Y = CylinderComponent.CollisionRadius;
Extent.Z = CylinderComponent.CollisionHeight * 2.f;
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
{
if( bIsBeyondSpawnDistance )
{
DrawDebugLine( Location, PlayerInfo[PlayerIdx].ViewLocation, 255, 0, 0, bPresistent );
if( PlayerIdx == 0 )
{
DrawDebugSphere( Location, 20, 20, 255, 0, 0, bPresistent );
}
}
else if( !bIsEnabled || !bAllowsSpawning )
{
if( PlayerIdx == 0 )
{
DrawDebugLine( Location, PlayerInfo[PlayerIdx].ViewLocation, 128, 0, 0, bPresistent );
}
DrawDebugSphere( Location, 20, 20, 128, 0, 0, bPresistent );
}
else if( bIsVisible )
{
if( PlayerIdx == 0 )
{
DrawDebugLine( Location, PlayerInfo[PlayerIdx].ViewLocation, 255, 0, 0, bPresistent );
}
DrawDebugBox( Location, Extent, 255, 0, 0, bPresistent );
}
else
{
if( PlayerIdx == 0 )
{
DrawDebugLine( Location, PlayerInfo[PlayerIdx].ViewLocation, 0, 255, 0, bPresistent );
}
DrawDebugBox( Location, Extent, 0, 255, 0, bPresistent );
}
}
if( bAdjacentToVisibleNode )
{
DrawDebugStar( Location, 8, 0, 255, 0, bPresistent );
}
if( bWillBeVisible )
{
DrawDebugStar( Location + vect(0,0,8), 8, 0, 0, 255, bPresistent );
}
if( bCanSpawnHereNow )
{
DrawDebugStar( Location + vect(0,0,16), 8, 255, 255, 255, bPresistent );
}
// `log( self@"Pri:"@Priority );
}
defaultproperties
{
Begin Object Name=Sprite
Sprite=Texture2D'EditorResources.Crowd.T_Crowd_Destination'
Scale=0.5
End Object
bAllowAsPreviousDestination=false
bAvoidWhenPanicked=false
bSkipBehaviorIfPanicked=true
Capacity=1000
Frequency=1.0
ExactReachTolerance=3.0
bAllowsSpawning=TRUE
bAllowCloudSpawning=TRUE
bSoftPerimeter=true
bStatic=true
bForceAllowKismetModification=true
bHasNavigationMesh=true
SupportedEvents.Empty
SupportedEvents(0)=class'SeqEvent_CrowdAgentReachedDestination'
Begin Object Class=GameDestinationConnRenderingComponent Name=ConnectionRenderer
End Object
Components.Add(ConnectionRenderer)
}

View File

@ -0,0 +1,214 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Where crowd agent is going. Destinations can kill agents that reach them or route them to another destination
*
*/
class GameCrowdDestinationQueuePoint extends GameCrowdInteractionPoint
native;
/** Position behind this one in line */
var() GameCrowdDestinationQueuePoint NextQueuePosition;
/** Position before this one in line */
var GameCrowdInteractionPoint PreviousQueuePosition;
/** Agent currently occupying this queue position */
var GameCrowdAgent QueuedAgent;
/** Which destination this queue is part of the line for. Used only for queue validation during error checking. */
var transient GameCrowdDestination QueueDestination;
/** Prevent ClearQueue() reentry */
var bool bClearingQueue;
/** Average pause time before agent reacts to queue movement */
var() float AverageReactionTime;
/** True if queued agent is still at this position, but about to advance */
var bool bPendingAdvance;
/** Queue behavior used by this queue point */
var class<GameCrowdBehavior_WaitInQueue> QueueBehaviorClass;
/**
* Returns true if agent at TestPosition is considered to have reached this queue point
*/
native function bool QueueReachedBy(GameCrowdAgent Agent, vector TestPosition);
/**
* Returns true if this queue has space
*/
simulated function bool HasSpace()
{
// If I don't have a queued agent, and there's not one about to advance to me, then I have space
if ( (QueuedAgent == None) && ((NextQueuePosition == None) || !NextQueuePosition.bPendingAdvance || (NextQueuePosition.QueuedAgent == None)) )
{
return true;
}
if ( NextQueuePosition == None )
{
return false;
}
return NextQueuePosition.HasSpace();
}
/**
* Called after Agent reaches this queue position
*
* @PARAM Agent is the crowd agent that just reached this queue position
*/
simulated event ReachedDestination(GameCrowdAgent Agent)
{
local GameCrowdDestinationQueuePoint QueuePoint;
// if agent in front of me hasn't reached yet and is further than me from the front, switch positions with him
// FIXMESTEVE - doesn't address the case where the GameCrowdDestination itself appears empty - may not need to be handled
for ( QueuePoint = Agent.CurrentDestination.QueueHead; QueuePoint!=None; QueuePoint= QueuePoint.NextQueuePosition)
{
if ( QueuePoint.NextQueuePosition == self )
{
if ( QueuePoint.QueuedAgent == None )
{
`warn(agent$"in queue behind empty spot at "$self);
}
else if ( !QueuePoint.QueueReachedBy(QueuePoint.QueuedAgent, QueuePoint.QueuedAgent.Location) && (VSizeSq(QueuePoint.Location - Agent.Location) < VSizeSq(QueuePoint.Location - QueuePoint.QueuedAgent.Location)) )
{
// switch places for agents
QueuedAgent = QueuePoint.QueuedAgent;
QueuePoint.QueuedAgent = Agent;
GameCrowdBehavior_WaitInQueue(QueuedAgent.CurrentBehavior).QueuePosition = self;
GameCrowdBehavior_WaitInQueue(QueuePoint.QueuedAgent.CurrentBehavior).QueuePosition = QueuePoint;
return;
}
}
}
// note - idle time will increase until next position opens up
GameCrowdBehavior_WaitInQueue(QueuedAgent.CurrentBehavior).bIdleBehavior = true;
QueuedAgent.PlayIdleAnimation();
}
/**
* Advance customer to next position in line, with a reaction time delay
*/
simulated function AdvanceCustomerTo(GameCrowdInteractionPoint FrontPosition)
{
PreviousQueuePosition = FrontPosition;
bPendingAdvance = true;
SetTimer(AverageReactionTime, false, 'ActuallyAdvance');
}
/**
* Actually advance the customer now
*/
private simulated function ActuallyAdvance()
{
local GameCrowdDestinationQueuePoint FrontQueuePosition;
local GameCrowdDestination QueueFront;
local GameCrowdAgent TempAgent;
bPendingAdvance = false;
if ( QueuedAgent != None )
{
TempAgent = QueuedAgent;
// FIXMESTEVE - creates queue behavior for every spot in line - should re-use
bClearingQueue = true;
QueuedAgent.StopBehavior();
bClearingQueue = false;
QueuedAgent = None;
FrontQueuePosition = GameCrowdDestinationQueuePoint(PreviousQueuePosition);
if ( FrontQueuePosition != None )
{
FrontQueuePosition.AddCustomer(TempAgent, None);
}
else
{
QueueFront = GameCrowdDestination(PreviousQueuePosition);
if ( QueueFront == None )
{
`warn("Illegal front position for queue "$self);
return;
}
QueueFront.IncrementCustomerCount(TempAgent);
}
if ( QueuedAgent != None )
{
`warn(self$" GOT QUEUED AGENT BACK - Head "$PreviousQueuePosition$" Tail "$NextQueuePosition);
}
else if ( NextQueuePosition != None )
{
NextQueuePosition.AdvanceCustomerTo(self);
}
}
}
/**
* Add customer to queue
*/
simulated function AddCustomer(GameCrowdAgent NewCustomer, GameCrowdInteractionPoint PreviousPosition)
{
if ( PreviousPosition != None )
{
PreviousQueuePosition = PreviousPosition;
}
if ( QueuedAgent == None )
{
QueuedAgent = NewCustomer;
NewCustomer.ActivateInstancedBehavior(new(NewCustomer) QueueBehaviorClass);
GameCrowdBehavior_WaitInQueue(NewCustomer.CurrentBehavior).QueuePosition = self;
GameCrowdBehavior_WaitInQueue(NewCustomer.CurrentBehavior).ActionTarget = PreviousQueuePosition;
}
else if ( NextQueuePosition != None )
{
NextQueuePosition.AddCustomer(NewCustomer, self);
}
else
{
`warn(self$" Attempted to add customer "$NewCustomer$" beyond end of queue");
}
}
/**
* Clear OldCustomer from this queue position
* Advance any customers in line
*/
simulated function ClearQueue(GameCrowdAgent OldCustomer)
{
if ( !bClearingQueue )
{
bClearingQueue = true;
if ( OldCustomer == QueuedAgent )
{
QueuedAgent.StopBehavior();
QueuedAgent = None;
if ( NextQueuePosition != None )
{
NextQueuePosition.AdvanceCustomerTo(self);
}
}
else
{
`warn("Attempted to clear "$OldCustomer$" from queue position with customer "$QueuedAgent);
}
bClearingQueue = false;
}
}
simulated function bool HasCustomer()
{
return (QueuedAgent != None);
}
defaultproperties
{
Begin Object NAME=CollisionCylinder
CollisionRadius=+0100.000000
CollisionHeight=+0040.000000
End Object
AverageReactionTime=0.7
QueueBehaviorClass=class'GameCrowdBehavior_WaitInQueue')
}

View File

@ -0,0 +1,37 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdGroup extends Object
native;
var array<GameCrowdAgent> Members;
function AddMember(GameCrowdAgent Agent)
{
Members[Members.Length] = Agent;
Agent.MyGroup = self;
}
function RemoveMember(GameCrowdAgent Agent)
{
Members.RemoveItem(Agent);
Agent.MyGroup = None;
}
function UpdateDestinations(GameCrowdDestination NewDestination)
{
local int i;
for ( i=0; i<Members.Length; i++ )
{
if ( (Members[i] != None) && (Members[i].CurrentDestination != NewDestination) )
{
Members[i].SetCurrentDestination(NewDestination);
Members[i].UpdateIntermediatePoint();
}
}
}
defaultproperties
{
}

View File

@ -0,0 +1,49 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdInfoVolume extends Volume
native
placeable;
/** List of all GameCrowdDestinations that are PotentialSpawnPoints */
var() array<GameCrowdDestination> PotentialSpawnPoints;
simulated function Touch( Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal )
{
local Pawn P;
local GameCrowdPopulationManager PopMgr;
Super.Touch( Other, OtherComp, HitLocation, HitNormal );
P = Pawn(Other);
if( P != None && P.IsHumanControlled() )
{
PopMgr = GameCrowdPopulationManager(WorldInfo.PopulationManager);
if( PopMgr != None )
{
PopMgr.SetCrowdInfoVolume( self );
}
}
}
simulated function UnTouch( Actor Other )
{
local Pawn P;
local GameCrowdPopulationManager PopMgr;
Super.UnTouch( Other );
P = Pawn(Other);
if( P != None && P.IsHumanControlled() )
{
PopMgr = GameCrowdPopulationManager(WorldInfo.PopulationManager);
if( PopMgr != None )
{
PopMgr.SetCrowdInfoVolume( None );
}
}
}
defaultproperties
{
}

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Convenience subclass, with typical settings for interaction points
*
*/
class GameCrowdInteractionDestination extends GameCrowdDestination;
defaultproperties
{
Begin Object Name=Sprite
Sprite=Texture2D'EditorResources.Crowd.T_Crowd_Destination'
End Object
Capacity=1
bAllowsSpawning=false
bAvoidWhenPanicked=true
bMustReachExactly=true
}

View File

@ -0,0 +1,91 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
*/
class GameCrowdInteractionPoint extends Actor
native
abstract
hidecategories(Advanced)
hidecategories(Collision)
hidecategories(Display)
hidecategories(Actor)
hidecategories(Movement)
hidecategories(Physics)
ClassGroup(Crowd)
placeable;
/** If this interactionpoint is currently enabled */
var() bool bIsEnabled;
/** Cylinder component */
var() CylinderComponent CylinderComponent;
cpptext
{
#if WITH_EDITOR
// AActor interface.
virtual void EditorApplyScale(const FVector& DeltaScale, const FMatrix& ScaleMatrix, const FVector* PivotLocation, UBOOL bAltDown, UBOOL bShiftDown, UBOOL bCtrlDown);
#endif
}
replication
{
if (bNoDelete)
bIsEnabled;
}
function OnToggle(SeqAct_Toggle action)
{
if (action.InputLinks[0].bHasImpulse)
{
// turn on
bIsEnabled = TRUE;
}
else if (action.InputLinks[1].bHasImpulse)
{
// turn off
bIsEnabled = FALSE;
}
else if (action.InputLinks[2].bHasImpulse)
{
// toggle
bIsEnabled = !bIsEnabled;
}
// Make this actor net relevant, and force replication, even if it now does not differ from class defaults.
ForceNetRelevant();
}
defaultproperties
{
TickGroup=TG_DuringAsyncWork
bIsEnabled=true
bCollideActors=FALSE
bNoDelete=true
Begin Object Class=CylinderComponent NAME=CollisionCylinder
CollideActors=FALSE
bDrawNonColliding=TRUE
CollisionRadius=+0200.000000
CollisionHeight=+0040.000000
CylinderColor=(R=0,G=255,B=0)
bDrawBoundingBox=FALSE
End Object
CylinderComponent=CollisionCylinder
CollisionComponent=CollisionCylinder
Components.Add(CollisionCylinder)
Begin Object Class=SpriteComponent Name=Sprite
Sprite=Texture2D'EditorResources.Crowd.T_Crowd_Behavior'
Scale=0.5
HiddenGame=TRUE
HiddenEditor=FALSE
AlwaysLoadOnClient=FALSE
AlwaysLoadOnServer=FALSE
End Object
Components.Add(Sprite)
}

View File

@ -0,0 +1,717 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Manages adding appropriate crowd population around player
* Agents will be spawned/recycled at any available active GameCrowdDestination
*
*/
class GameCrowdPopulationManager extends CrowdPopulationManagerBase
implements(Interface_NavigationHandle)
dependson(SeqAct_GameCrowdSpawner)
native;
var CrowdSpawnInfoItem CloudSpawnInfo;
var array<CrowdSpawnInfoItem> ScriptedSpawnInfo;
var GameCrowdInfoVolume ActiveCrowdInfoVolume;
var array<GameCrowdDestination> GlobalPotentialSpawnPoints;
/** How much to reduce number by in splitscreen */
var float SplitScreenNumReduction;
/** How far ahead to compute predicted player position for spawn prioritization */
var float PlayerPositionPredictionTime;
/** Offset used to validate spawn by checking above spawn location to see if head/torso would be visible */
var float HeadVisibilityOffset;
/** Navigation Handle used by agents requesting pathing */
var class<NavigationHandle> NavigationHandleClass;
var NavigationHandle NavigationHandle;
/** Agent requesting navigation handle use */
var GameCrowdAgent QueryingAgent;
var array<CrowdSpawnerPlayerInfo> PlayerInfo;
var float LastPlayerInfoUpdateTime;
var(Debug) bool bDebugSpawns;
var(Debug) bool bPauseCrowd;
cpptext
{
virtual UBOOL Tick( FLOAT DeltaTime, enum ELevelTick TickType );
virtual void TickSpawnInfo( FCrowdSpawnInfoItem& Item, FLOAT DeltaTime );
virtual void GetAlwaysRelevantDynamics( AGameCrowdAgent* Agent );
/** Interface_NavigationHandle implementation to grab search params */
virtual void SetupPathfindingParams( FNavMeshPathParams& out_ParamCache );
virtual void InitForPathfinding() {}
virtual INT ExtraEdgeCostToAddWhenActive(FNavMeshEdgeBase* Edge) { return 0; }
virtual FVector GetEdgeZAdjust(FNavMeshEdgeBase* Edge);
UBOOL GetSpawnInfoItem( USeqAct_GameCrowdPopulationManagerToggle* inAction, FCrowdSpawnInfoItem*& out_Spawner, UBOOL bCreateIfNotFound = 0 );
}
function PostBeginPlay()
{
local GameCrowdDestination GCD;
Super.PostBeginPlay();
if( !bDeleteMe )
{
WorldInfo.PopulationManager = self;
}
if( NavigationHandleClass != None )
{
NavigationHandle = new(self) NavigationHandleClass;
}
// add spawn points that have already begun play
foreach AllActors(class'GameCrowdDestination', GCD)
{
AddSpawnPoint(GCD);
}
}
// Interface_navigationhandle stub - called when path edge is deleted that this controller is using
event NotifyPathChanged();
function AddSpawnPoint( GameCrowdDestination GCD )
{
if( GCD.MyPopMgr != None || !GCD.bAllowCloudSpawning )
{
return;
}
GCD.MyPopMgr = self;
GlobalPotentialSpawnPoints[GlobalPotentialSpawnPoints.Length] = GCD;
if( ActiveCrowdInfoVolume == None )
{
CloudSpawnInfo.PotentialSpawnPoints[CloudSpawnInfo.PotentialSpawnPoints.Length] = GCD;
}
}
function RemoveSpawnPoint(GameCrowdDestination GCD)
{
local int Idx, AgentIdx;
GCD.MyPopMgr = None;
// remove from potential spawnpoints and prioritized spawn points list
// also remove agents moving toward unloaded spawn point
CloudSpawnInfo.PotentialSpawnPoints.RemoveItem( GCD );
CloudSpawnInfo.PrioritizedSpawnPoints.RemoveItem( GCD );
GlobalPotentialSpawnPoints.RemoveItem( GCD );
for( AgentIdx = 0; AgentIdx < CloudSpawnInfo.ActiveAgents.Length; AgentIdx++ )
{
if( CloudSpawnInfo.ActiveAgents[AgentIdx].CurrentDestination == GCD )
{
CloudSpawnInfo.ActiveAgents[AgentIdx].Destroy();
}
}
for( Idx = 0; Idx < ScriptedSpawnInfo.Length; Idx++ )
{
ScriptedSpawnInfo[Idx].PotentialSpawnPoints.RemoveItem( GCD );
ScriptedSpawnInfo[Idx].PrioritizedSpawnPoints.RemoveItem( GCD );
for( AgentIdx = 0; AgentIdx < ScriptedSpawnInfo[Idx].ActiveAgents.Length; AgentIdx++ )
{
if( ScriptedSpawnInfo[Idx].ActiveAgents[AgentIdx].CurrentDestination == GCD )
{
ScriptedSpawnInfo[Idx].ActiveAgents[AgentIdx].Destroy();
}
}
}
}
function SetCrowdInfoVolume( GameCrowdInfoVolume Vol )
{
if( Vol != ActiveCrowdInfoVolume )
{
ActiveCrowdInfoVolume = Vol;
if( Vol != None )
{
CloudSpawnInfo.PotentialSpawnPoints = Vol.PotentialSpawnPoints;
}
else
{
CloudSpawnInfo.PotentialSpawnPoints = GlobalPotentialSpawnPoints;
}
CloudSpawnInfo.PrioritizedSpawnPoints.Length = 0;
CloudSpawnInfo.PrioritizationIndex = 0;
CloudSpawnInfo.PrioritizationUpdateIndex = 0;
}
}
event int CreateSpawner( SeqAct_GameCrowdPopulationManagerToggle inAction )
{
local int Idx;
Idx = ScriptedSpawnInfo.Length;
ScriptedSpawnInfo.Length = Idx + 1;
ScriptedSpawnInfo[Idx].SeqSpawner = inAction;
return Idx;
}
/** Instantly destroy all active agents controlled by this manager. Useful for debugging. */
event FlushAgents( CrowdSpawnInfoItem Item )
{
local int AgentIdx;
for( AgentIdx = 0; AgentIdx < Item.ActiveAgents.Length; AgentIdx++ )
{
Item.ActiveAgents[AgentIdx].Destroy();
}
Item.ActiveAgents.Length = 0;
}
event FlushAllAgents()
{
local int Idx;
FlushAgents( CloudSpawnInfo );
for( Idx = 0; Idx < ScriptedSpawnInfo.Length; Idx++ )
{
FlushAgents( ScriptedSpawnInfo[Idx] );
}
}
function AgentDestroyed( GameCrowdAgent Agent )
{
local int SpawnerIdx;
local int i;
SpawnerIdx = ScriptedSpawnInfo.Find('SeqSpawner', SeqAct_GameCrowdPopulationManagerToggle(Agent.MySpawner));
if( SpawnerIdx >= 0 )
{
// now modify the CurrSpawned amount for this archetype since we just destroyed one
for( i = 0; i < ScriptedSpawnInfo[SpawnerIdx].AgentArchetypes.Length; i++ )
{
if( GameCrowdAgent(ScriptedSpawnInfo[SpawnerIdx].AgentArchetypes[i].AgentArchetype) == Agent.MyArchetype )
{
ScriptedSpawnInfo[SpawnerIdx].AgentArchetypes[i].CurrSpawned--;
//`log( GetFuncName() @ `showvar(AgentArchetypes[i].AgentArchetype) @ `showvar(AgentArchetypes[i].CurrSpawned) );
}
}
ScriptedSpawnInfo[SpawnerIdx].ActiveAgents.RemoveItem( Agent );
}
else if( Agent.MySpawner != None )
{
// now modify the CurrSpawned amount for this archetype since we just destroyed one
for( i = 0; i < CloudSpawnInfo.AgentArchetypes.Length; i++ )
{
if( GameCrowdAgent(CloudSpawnInfo.AgentArchetypes[i].AgentArchetype) == Agent.MyArchetype )
{
CloudSpawnInfo.AgentArchetypes[i].CurrSpawned--;
//`log( GetFuncName() @ `showvar(AgentArchetypes[i].AgentArchetype) @ `showvar(AgentArchetypes[i].CurrSpawned) );
}
}
CloudSpawnInfo.ActiveAgents.RemoveItem( Agent );
}
}
/**
* Use 'GameDebug' console command to show this debug info
* Useful to show general debug info not tied to a particular concrete actor.
*/
simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
{
local Canvas Canvas;
local int RenderedNum, LOSNum, SimNum, ActualCount, DistanceBucket[20], i, RVONum;
local Actor HitActor;
local vector HitNormal, HitLocation;
local GameCrowdAgent GCA;
local float Dist;
local array<GameCrowdAgent> AgentList;
local int PlayerIdx, SpawnIdx;
local bool bHasLOS;
local float BucketSize;
Canvas = HUD.Canvas;
Canvas.SetDrawColor(255,255,255);
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("---- GameCrowdPopulationManager ---");
out_YPos += out_YL;
if( !GetPlayerInfo() )
{
return;
}
// calculate number of agents being rendered, simulated, and in player's LOS
ForEach DynamicActors(class'GameCrowdAgent', GCA)
{
if( !GCA.bDeleteMe )
{
AgentList[AgentList.Length] = GCA;
}
}
BucketSize = (2.f*CloudSpawnInfo.MaxSpawnDist) / ArrayCount(DistanceBucket);
foreach AgentList(GCA)
{
ActualCount++;
if( GCA.Health > 0 )
{
if( GCA.bSimulateThisTick )
{
SimNum++;
}
if( `TimeSince(GCA.LastRenderTime) < 1.0 && (GCA.LastRenderTime != GCA.InitialLastRenderTime) )
{
bHasLOS = TRUE;
RenderedNum++;
}
else
{
bHasLOS = FALSE;
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
{
HitActor = Trace( HitLocation, HitNormal, GCA.Location, PlayerInfo[PlayerIdx].ViewLocation, FALSE );
if( HitActor == None )
{
bHasLOS = TRUE;
break;
}
}
}
if( bHasLOS )
{
LOSNum++;
if( GCA.bSimulateThisTick )
{
RVONum++;
}
}
GCA.bSimulateThisTick = FALSE;
}
Dist = 999999.f;
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
{
Dist = FMin(VSize(PlayerInfo[PlayerIdx].ViewLocation - GCA.Location), Dist);
}
DistanceBucket[Min(19, int(Dist/BucketSize))]++;
}
Canvas.DrawText("TotalCount: "$ActualCount );
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Cloud:"@CloudSpawnInfo.ActiveAgents.Length@"Active:"@CloudSpawnInfo.bSpawningActive );
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
for( SpawnIdx = 0; SpawnIdx < ScriptedSpawnInfo.Length; SpawnIdx++ )
{
Canvas.DrawText("Scripted: "$ScriptedSpawnInfo[SpawnIdx].ActiveAgents.Length@ScriptedSpawnInfo[SpawnIdx].SeqSpawner@"Active:"@ScriptedSpawnInfo[SpawnIdx].bSpawningActive );
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
}
Canvas.DrawText("Agents Rendered:"@RenderedNum);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Agents LOS:"@LOSNum);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Agents Simulated:"@SimNum);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Agents RVO:"@RVONum);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
Canvas.DrawText("Distance Buckets");
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
for ( i=0; i<19; i++ )
{
if( DistanceBucket[i] > 0 )
{
Canvas.DrawText(" (<"$BucketSize * (i+1)$")"$DistanceBucket[i]);
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
}
}
}
/** returns whether we want spawning to currently be active */
function bool IsSpawningActive()
{
local int SpawnerIdx;
if( CloudSpawnInfo.bSpawningActive )
{
return TRUE;
}
for( SpawnerIdx = 0; SpawnerIdx < ScriptedSpawnInfo.Length; SpawnerIdx++ )
{
if( ScriptedSpawnInfo[SpawnerIdx].bSpawningActive )
{
return TRUE;
}
}
return FALSE;
}
simulated function bool ShouldDebugDestinations()
{
return bDebugSpawns;
}
/**
* FIXMESTEVE - Nativize?
*/
function Tick( float DeltaTime )
{
`if(`notdefined(FINAL_RELEASE))
local GameCrowdDestination PickedSpawnPoint;
local int Idx;
local int PlayerIdx;
// local Color C;
`endif
`if(`notdefined(FINAL_RELEASE))
if( ShouldDebugDestinations() && GetPlayerInfo() )
{
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
{
DrawDebugBox( PlayerInfo[PlayerIdx].PredictLocation, vect(20,20,20), 255, 0, 0 );
DrawDebugBox( PlayerInfo[PlayerIdx].ViewLocation, vect(10,10,10), 255, 255, 255 );
DrawDebugLine( PlayerInfo[PlayerIdx].ViewLocation, PlayerInfo[PlayerIdx].ViewLocation + Vector(PlayerInfo[PlayerIdx].ViewRotation) * 64, 255, 255, 255 );
// DrawDebugSphere( PlayerInfo[PlayerIdx].ViewLocation, GetMaxSpawnDist(), 20, 255, 255, 255 );
}
for( Idx = 0; Idx < CloudSpawnInfo.PotentialSpawnPoints.Length; Idx++ )
{
PickedSpawnPoint = CloudSpawnInfo.PotentialSpawnPoints[Idx];
if( PickedSpawnPoint == None )
{
continue;
}
PickedSpawnPoint.AnalyzeSpawnPoint( PlayerInfo, CloudSpawnInfo.MaxSpawnDistSq, CloudSpawnInfo.bForceNavMeshPathing, NavigationHandle );
PickedSpawnPoint.PrioritizeSpawnPoint( PlayerInfo, CloudSpawnInfo.MaxSpawnDist );
PickedSpawnPoint.DrawDebug( PlayerInfo );
if( !ValidateSpawnAt( CloudSpawnInfo, PickedSpawnPoint) )
{
DrawDebugCylinder( PickedSpawnPoint.Location, PickedSpawnPoint.Location, PickedSpawnPoint.CylinderComponent.CollisionRadius, PickedSpawnPoint.CylinderComponent.CollisionHeight, 255, 0, 0 );
}
}
// for( Idx = 0; Idx < CloudSpawnInfo.ActiveAgents.Length; Idx++ )
// {
// C = CloudSpawnInfo.ActiveAgents[Idx].DebugAgentColor;
// DrawDebugLine( CloudSpawnInfo.ActiveAgents[Idx].DebugSpawnDest.Location, CloudSpawnInfo.ActiveAgents[Idx].Location, C.R, C.G, C.B );
// }
}
`endif
if( !bPauseCrowd && IsSpawningActive() )
{
UpdateAllSpawners( DeltaTime );
}
}
native function UpdateAllSpawners( float DeltaTime );
event bool UpdateSpawner( out CrowdSpawnInfoItem Item, float DeltaTime )
{
local GameCrowdDestination PickedSpawnPoint;
local GameCrowdAgent A;
local int NumSpawned;
if( !Item.bSpawningActive || Item.ActiveAgents.Length >= Item.SpawnNum )
{
return FALSE;
}
if( Item.SeqSpawner != None )
{
Item.SeqSpawner.LastSpawnedList.Length = 0;
}
Item.Remainder += FMin( DeltaTime, 0.05f ) * Item.SpawnRate;
if( Item.Remainder > 1.f )
{
// Prioritize based on potential visibility and recently spawned agents
PrioritizeSpawnPoints( Item, DeltaTime );
// Spawn new agents for this tick
while( Item.Remainder > 1.f && Item.ActiveAgents.Length < Item.SpawnNum)
{
PickedSpawnPoint = PickSpawnPoint( Item );
if( PickedSpawnPoint != None )
{
PickedSpawnPoint.LastSpawnTime = WorldInfo.TimeSeconds;
A = SpawnAgent( Item, PickedSpawnPoint);
if( A != None )
{
NumSpawned++;
if( Item.SeqSpawner != None )
{
Item.SeqSpawner.LastSpawnedList.AddItem( A );
}
}
Item.Remainder -= 1.f;
}
else
{
Item.Remainder = 0.0;
}
}
}
return (NumSpawned>0);
}
/**
* @RETURN best spawn point to spawn next crowd agent
*/
event GameCrowdDestination PickSpawnPoint( out CrowdSpawnInfoItem Item )
{
local int StartingIndex, SpawnIdx;
local GameCrowdDestination Candidate;
// Go down prioritized list, make sure currently valid (still not visible if not prioritize frame)
StartingIndex = Min(Item.PrioritizationIndex, Item.PrioritizedSpawnPoints.Length);
for( SpawnIdx = 0; SpawnIdx < Item.PrioritizedSpawnPoints.Length; SpawnIdx++ )
{
Item.PrioritizationIndex = (StartingIndex + SpawnIdx) % Item.PrioritizedSpawnPoints.Length;
Candidate = Item.PrioritizedSpawnPoints[Item.PrioritizationIndex];
if( ValidateSpawnAt( Item, Candidate ) )
{
return Candidate;
}
}
return None;
}
native simulated function bool GetPlayerInfo();
native static simulated function bool StaticGetPlayerInfo( out array<CrowdSpawnerPlayerInfo> out_PlayerInfo );
/**
* Prioritize GameCrowdDestinations as potential spawn points
*/
event PrioritizeSpawnPoints( out CrowdSpawnInfoItem Item, float DeltaTime )
{
local int UpdateNum;
if( Item.PotentialSpawnPoints.Length == 0 || !GetPlayerInfo() )
{
return;
}
// calculate number of potential spawn points to prioritize this tick
UpdateNum = Max(1, DeltaTime * float(Item.PotentialSpawnPoints.Length)/Item.SpawnPrioritizationInterval);
// Analyze and prioritize a number of spawn points
AnalyzeSpawnPoints( Item, Item.PrioritizationUpdateIndex, UpdateNum );
Item.PrioritizationUpdateIndex = (Item.PrioritizationUpdateIndex + UpdateNum) % Item.PotentialSpawnPoints.Length;
}
function AnalyzeSpawnPoints( out CrowdSpawnInfoItem Item, int StartIndex, int NumToUpdate )
{
local int UpdateIdx, Idx, NumUpdated;
local GameCrowdDestination GCD;
if( StartIndex >= Item.PotentialSpawnPoints.Length || !GetPlayerInfo() )
{
return;
}
// determine potential visibility of all GameCrowdDestinations
NumUpdated = 0;
for( UpdateIdx = 0; NumUpdated < NumToUpdate && UpdateIdx < Item.PotentialSpawnPoints.Length; UpdateIdx++ )
{
Idx = (StartIndex + UpdateIdx) % Item.PotentialSpawnPoints.Length;
GCD = Item.PotentialSpawnPoints[Idx];
if( GCD == None )
{
Item.PotentialSpawnPoints.Remove(UpdateIdx--,1);
continue;
}
Item.PrioritizedSpawnPoints.RemoveItem(GCD);
if( GCD.AnalyzeSpawnPoint( PlayerInfo, Item.MaxSpawnDistSq, Item.bForceNavMeshPathing, NavigationHandle ) )
{
NumUpdated++;
// add GCD back to list if is potential spawn point
if( GCD.bCanSpawnHereNow )
{
AddPrioritizedSpawnPoint( Item, GCD );
}
}
}
}
/**
* Prioritize passed in GameCrowdDestination and insert it into ordered PrioritizedSpawnPoints list, offset from current starting point
*/
function AddPrioritizedSpawnPoint( out CrowdSpawnInfoItem Item, GameCrowdDestination GCD )
{
local int SpawnIdx, StartingIndex;
GCD.PrioritizeSpawnPoint( PlayerInfo, Item.MaxSpawnDist );
// insert GCD into prioritized list
StartingIndex = Min(Item.PrioritizationIndex, Item.PrioritizedSpawnPoints.Length);
for( SpawnIdx = 0; SpawnIdx < Item.PrioritizedSpawnPoints.Length; SpawnIdx++ )
{
Item.PrioritizationIndex = (StartingIndex + SpawnIdx) % Item.PrioritizedSpawnPoints.Length;
if( Item.PrioritizedSpawnPoints[Item.PrioritizationIndex].Priority < GCD.Priority )
{
Item.PrioritizedSpawnPoints.Insert(Item.PrioritizationIndex, 1);
Item.PrioritizedSpawnPoints[Item.PrioritizationIndex] = GCD;
return;
}
}
// add right at current index (and increment index since this one should be last
Item.PrioritizedSpawnPoints.Insert(StartingIndex, 1);
Item.PrioritizedSpawnPoints[StartingIndex] = GCD;
Item.PrioritizationIndex = (StartingIndex + 1) % Item.PrioritizedSpawnPoints.Length;
}
/**
* Determine whether candidate spawn point is currently valid
*/
function bool ValidateSpawnAt( out CrowdSpawnInfoItem Item, GameCrowdDestination Candidate)
{
local Actor HitActor;
local vector HitLocation, HitNormal;
local float DistSq, MinDistFromViewSq;
local float DestDotView;
local int PlayerIdx;
// make sure candidate not at capacity
if( !Candidate.bIsEnabled || !Candidate.bAllowsSpawning || Candidate.AtCapacity() )
{
return FALSE;
}
if( Candidate.bAllowVisibleSpawning )
{
return TRUE;
}
// check that spawn point is not visible to player
if( GetPlayerInfo() )
{
MinDistFromViewSq = MaxInt;
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
{
// if candidate is beyond max (normal) spawn dist, it's a special case and we don't mind if it is visible
// also don't mind if far away and not in view frustrum
DistSq = VSizeSq(Candidate.Location - PlayerInfo[PlayerIdx].ViewLocation);
MinDistFromViewSq = FMin( DistSq, MinDistFromViewSq );
if( DistSq < Item.MaxSpawnDistSq )
{
DestDotView = Normal(Candidate.Location - PlayerInfo[PlayerIdx].ViewLocation) DOT Vector(PlayerInfo[PlayerIdx].ViewRotation);
if( DistSq < Item.MinBehindSpawnDistSq || DestDotView > 0.7f )
{
HitActor = Trace(HitLocation, HitNormal, Candidate.Location + HeadVisibilityOffset*vect(0,0,1), PlayerInfo[PlayerIdx].ViewLocation, FALSE,,, TRACEFLAG_Bullet);
if( HitActor == None )
{
return FALSE;
}
}
}
}
if( MinDistFromViewSq < Item.MaxSpawnDistSq )
{
return TRUE;
}
}
return FALSE;
}
/**
* Actually create a new CrowdAgent actor, and initialise it
*/
native function GameCrowdAgent SpawnAgentByIdx( int SpawnerIdx, GameCrowdDestination SpawnLoc );
native function GameCrowdAgent SpawnAgent( out CrowdSpawnInfoItem Item, GameCrowdDestination SpawnLoc );
native function bool Warmup( out CrowdSpawnInfoItem Item, int WarmupNum );
/**
* Create new GameCrowdAgent and initialize it
*/
event GameCrowdAgent CreateNewAgent( out CrowdSpawnInfoItem Item, GameCrowdDestination SpawnLoc, GameCrowdAgent AgentTemplate, GameCrowdGroup NewGroup)
{
local GameCrowdAgent Agent;
local rotator SpawnRot;
local vector SpawnPos;
local int i;
// GameCrowdSpawnInterface provides spawn location (can be line/circle/volume/etc. based)
GameCrowdSpawnInterface(SpawnLoc).GetSpawnPosition(none, SpawnPos, SpawnRot);
if( !GetPlayerInfo() )
{
return None;
}
Agent = Spawn( AgentTemplate.Class,,,SpawnPos,SpawnRot,AgentTemplate);
Agent.SetLighting(Item.bEnableCrowdLightEnvironment, Item.AgentLightingChannel, Item.bCastShadows);
if( Item.bForceObstacleChecking )
{
Agent.bCheckForObstacles = TRUE;
}
if( Item.bForceNavMeshPathing )
{
Agent.bUseNavMeshPathing = TRUE;
}
// don't prefer visible paths on spawn if on soon to be visible start
if( SpawnLoc.bWillBeVisible )
{
Agent.bPreferVisibleDestinationOnSpawn = Agent.bPreferVisibleDestination;
}
Agent.MySpawner = GameCrowdSpawnerInterface(Item.SeqSpawner);
Item.ActiveAgents[Item.ActiveAgents.Length] = Agent;
Agent.InitializeAgent(SpawnLoc, PlayerInfo, AgentTemplate, NewGroup, Item.AgentWarmUpTime*2.0*FRand(), (Item.AgentWarmupTime>0.f), TRUE );
// now find the archetype and update the CurrSpawned
for( i = 0; i < Item.AgentArchetypes.Length; i++ )
{
if( GameCrowdAgent(Item.AgentArchetypes[i].AgentArchetype) == Agent.MyArchetype )
{
Item.AgentArchetypes[i].CurrSpawned++;
}
}
return Agent;
}
defaultproperties
{
NavigationHandleClass=class'NavigationHandle'
SplitScreenNumReduction=0.5
PlayerPositionPredictionTime=5.0
HeadVisibilityOffset=40.0
RemoteRole=ROLE_None
NetUpdateFrequency=10
bHidden=TRUE
bOnlyDirtyReplication=TRUE
bSkipActorPropertyReplication=TRUE
}

View File

@ -0,0 +1,85 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdReplicationActor extends Actor
native;
/** Pointer to crowd spawning action we are replicating. */
var repnotify SeqAct_GameCrowdSpawner Spawner;
/** If crowd spawning is active. */
var repnotify bool bSpawningActive;
/** Use to replicate when we want to destroy all crowd agents. */
var repnotify int DestroyAllCount;
// FIXME - add native rep
replication
{
if(Role == Role_Authority)
Spawner, bSpawningActive, DestroyAllCount;
}
simulated event ReplicatedEvent(name VarName)
{
/*
//test
if(VarName == 'Spawner' || VarName == 'bSpawningActive')
{
if(Spawner != None)
{
Spawner.bSpawningActive = bSpawningActive;
// Cache spawner kismet vars on client
if(bSpawningActive)
{
Spawner.CacheSpawnerVars();
}
}
}
else if(VarName == 'DestroyAllCount')
{
Spawner.KillAgents();
Spawner.bSpawningActive = FALSE;
}
else
{
Super.ReplicatedEvent(VarName);
}
*/
}
auto state ReceivingReplication
{
simulated event Tick(float DeltaTime)
{
//test
/*
Super.Tick(DeltaTime);
if ( Role == ROLE_Authority )
{
GotoState('');
}
else if( Spawner != None && Spawner.bSpawningActive )
{
Spawner.UpdateSpawning(DeltaTime);
}
*/
}
}
defaultproperties
{
TickGroup=TG_DuringAsyncWork
bSkipActorPropertyReplication=true
bAlwaysRelevant=true
bReplicateMovement=false
bUpdateSimulatedPosition=false
bOnlyDirtyReplication=true
RemoteRole=ROLE_SimulatedProxy
NetPriority=2.7
NetUpdateFrequency=1.0
}

View File

@ -0,0 +1,9 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Used by actor classes which want to customize chosing the spawn position of agents spawned at their location.
*/
interface GameCrowdSpawnInterface;
function GetSpawnPosition(SeqAct_GameCrowdSpawner Spawner, out vector SpawnPos, out rotator SpawnRot);

View File

@ -0,0 +1,17 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowdSpawnRelativeActor extends Actor
native;
defaultproperties
{
Begin Object Class=SpriteComponent Name=Sprite
Sprite=Texture2D'EditorResources.S_NavP'
HiddenGame=FALSE
HiddenEditor=FALSE
AlwaysLoadOnClient=FALSE
AlwaysLoadOnServer=FALSE
End Object
Components.Add(Sprite)
}

View File

@ -0,0 +1,11 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Used by classes which spawn crowd agents and want to be notified when they are destroyed.
*/
interface GameCrowdSpawnerInterface;
function AgentDestroyed(GameCrowdAgent Agent);
function float GetMaxSpawnDist();

View File

@ -0,0 +1,18 @@
/**
* This is the class for list of GoreEffect
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameCrowd_ListOfAgents extends Object
hidecategories(Object)
placeable; // needed to show up in the Actor Classes list;
/** List of archetypes of agents to spawn when a population manager or crowd spawner is using this list. */
var() array<AgentArchetypeInfo> ListOfAgents;
defaultproperties
{
}

View File

@ -0,0 +1,25 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameDestinationConnRenderingComponent extends PrimitiveComponent
native
hidecategories(Object)
editinlinenew;
cpptext
{
/**
* Creates a new scene proxy for the path rendering component.
* @return Pointer to the FConnectionRenderingSceneProxy
*/
virtual FPrimitiveSceneProxy* CreateSceneProxy();
virtual void UpdateBounds();
};
defaultproperties
{
HiddenGame=true
AlwaysLoadOnClient=false
AlwaysLoadOnServer=false
}

View File

@ -0,0 +1,198 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameExplosion extends Object
native
editinlinenew;
//
// Gameplay parameters
//
/**
* If TRUE, this will be a "directional" explosion, meaning that all radial effects will be applied only
* within DirectionalExplosionAngleDeg degrees of the blast's facing direction (which should be supplied via Explode()).
*/
var() bool bDirectionalExplosion;
/** Half-angle, in degrees, of the cone that defines the effective area of a directional explosion. */
var() float DirectionalExplosionAngleDeg;
/** Delay before applying damage after spawning FX, 0.f == no delay */
var() float DamageDelay;
/** Amount of damage done at the epicenter. */
var() float Damage;
/** Damage range. */
var() float DamageRadius;
/** Defines how damage falls off. High numbers cause faster falloff, lower (closer to zero) cause slower falloff. 1 is linear. */
var() float DamageFalloffExponent;
/** Optional actor that does not receive any radial damage, to be specified at runtime */
var transient Actor ActorToIgnoreForDamage;
/** If set, ignore instigator when doing damage/effects. Can be set in addition to above */
var transient bool bIgnoreInstigator;
`if(`__TW_)
/** The actor class to ignore for damage from this explosion **/
var() class<Actor> ActorClassToIgnoreForDamage<AllowAbstract>;
`else
/** The actor class to ignore for damage from this explosion **/
var() class<Actor> ActorClassToIgnoreForDamage;
`endif
/** The actor class to ignore for knockdowns and cringes from this explosion **/
var() class<Actor> ActorClassToIgnoreForKnockdownsAndCringes;
/** True to allow teammates to cringe, regardless of friendly fire setting. */
var() bool bAllowTeammateCringes;
/** Unused? Option to force full damage to the attachee actor. */
var transient bool bFullDamageToAttachee;
/** What damagetype to use */
var() class<DamageType> MyDamageType<AllowAbstract>;
/** radius at which people will be knocked down/ragdolled by the projectile's explosion **/
var() float KnockDownRadius;
/** @fixme, base this on MomentumTransferScale? */
var() float KnockDownStrength;
/** radius at which people will cringe from the explosion **/
var() float CringeRadius;
/** duration of the cringe. X=duration at epicenter, Y=duration at CringeRadius. Values <0 mean use default cringe. */
var() vector2d CringeDuration;
/** Percentage of damagetype's momentum to apply. */
var() float MomentumTransferScale;
/** Whether or not we should attach something to the attachee **/
var() bool bAttachExplosionEmitterToAttachee;
//
// Particle effect parameters
//
/** Which particle effect to play. */
var() ParticleSystem ParticleEmitterTemplate;
/** Scalar for increasing/decreasing explosion effect size. */
var() float ExplosionEmitterScale;
/** Track if we've hit an actor, used to handle cases such as kidnapper protected from hostage damage */
var Actor HitActor;
/** We need the hit location and hit normal so we can trace down to the actor to apply the decal (e.g. hitting wall or floor) **/
var vector HitLocation;
var vector HitNormal;
//
// Audio parameters
//
`if(`__TW_WWISE_)
/** Audio to play at explosion time. */
var() AkBaseSoundObject ExplosionSound;
/** Audio to play at explosion time if at least one pawn got hurt. Does not work for delayed damage. As we typically don't want to delay the explosion sound. */
var() AkBaseSoundObject ExplosionSoundHurtSomeone;
`else
/** Audio to play at explosion time. */
var() SoundCue ExplosionSound;
/** Audio to play at explosion time if at least one pawn got hurt. Does not work for delayed damage. As we typically don't want to delay the explosion sound. */
var() SoundCue ExplosionSoundHurtSomeone;
`endif
//
// Dynamic light parameters
//
/** Defines the dynamic light cast by the explosion */
var() PointLightComponent ExploLight;
/** Dynamic Light fade out time, in seconds */
var() float ExploLightFadeOutTime;
`if(`__TW_)
/** Dynamic Light start fade out time, in seconds */
var() float ExploLightStartFadeOutTime;
/** Intensity of the light flicker */
var() float ExploLightFlickerIntensity;
/** How quickly the light will flicker */
var() float ExploLightFlickerInterpSpeed;
`endif // __TW_
/** If true, will perform an EffectIsRelevant check before spawning the radial blur */
var() bool bPerformRadialBlurRelevanceCheck;
/** Defines the blurred region for the explosion */
var() RadialBlurComponent ExploRadialBlur;
/** Radial blur fade out time, in seconds */
var() float ExploRadialBlurFadeOutTime;
/** Radial blur max blur amount */
var() float ExploRadialBlurMaxBlur;
//
// Fractured mesh parameters
//
/** Controls if this explosion will cause fracturing */
var() bool bCausesFracture;
/** How far away from explosion we break bits off */
var() float FractureMeshRadius;
/** How hard to throw broken off pieces */
var() float FracturePartVel;
/** If true, attempt to get effect information from the physical material system. If false or a physicalmaterial is unavailable, just use the information above. */
var() bool bAllowPerMaterialFX;
/** So for tagged grenades we need override the particle system but still want material based decals and such. **/
var() bool bParticleSystemIsBeingOverriddenDontUsePhysMatVersion;
/** If true, the PhysMat's default particle system will not override the one already set in the explosion **/
var() bool bSkipDefaultPhysMatParticleSystem;
/** This tells the explosion to look in the Map's MapSpecific info **/
var() bool bUseMapSpecificValues;
var() bool bUseOverlapCheck;
//
// Camera parameters
//
/** TRUE to rotate CamShake to play radially relative to the explosion. Left/Right/Rear will be ignored. */
var() bool bOrientCameraShakeTowardsEpicenter;
/** Shake to play when source is in front of the camera, or when directional variants are unspecified. */
var() editinline CameraShake CamShake;
/** Anim to play when the source event is to the left of the camera. If None, CamShake will be used instead. */
var() editinline CameraShake CamShake_Left;
/** Anim to play when the source event is to the right of the camera. If None, CamShake will be used instead. */
var() editinline CameraShake CamShake_Right;
/** Anim to play when the source event is behind of the camera. If None, CamShake will be used instead. */
var() editinline CameraShake CamShake_Rear;
/** Radius within which to play full-powered camera shake (will be scaled within radius) */
var() float CamShakeInnerRadius;
/** Between inner and outer radii, scale shake from full to zero */
var() float CamShakeOuterRadius;
/** Exponent for intensity falloff between inner and outer radii. */
var() float CamShakeFalloff;
/** TRUE to attempt to automatically do force feedback to match the camera shake */
var() bool bAutoControllerVibration;
/** Play this CameraLensEffect when ever damage of this type is given. This will primarily be used by explosions. But could be used for other impacts too! **/
var() class<EmitterCameraLensEffectBase> CameraLensEffect;
/** This is the radius to play the camera effect on **/
var() float CameraLensEffectRadius;
defaultproperties
{
ExplosionEmitterScale=1.f
MomentumTransferScale=1.f
bCausesFracture=TRUE
bPerformRadialBlurRelevanceCheck=false
ExploRadialBlurMaxBlur=2.0
CringeDuration=(X=-1.f,Y=-1.f)
CamShakeFalloff=2.f
bAutoControllerVibration=true
}

View File

@ -0,0 +1,787 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameExplosionActor extends Actor
abstract
config(Weapon)
native;
/** True if explosion has occurred. */
var protected transient bool bHasExploded;
/** True if this actor can explode more than once and doesn't die after an explosion.
* Used by placeable actors whose explosions are triggered via matinee
*/
var() protected bool bExplodeMoreThanOnce;
/** The actual light used for the explosion. */
var protected transient PointLightComponent ExplosionLight;
`if(`__TW_)
/** Temp data for light flickering. */
var protected transient float LastLightBrightness;
var protected transient float LightFlickerIntensity;
var protected transient float LightFlickerInterpSpeed;
/** Temp data for light fading. */
var protected transient float LightFadeStartTime;
`endif // __TW_
/** Radial blur for explosion */
var protected transient RadialBlurComponent ExplosionRadialBlur;
/** Temp data for light fading. */
var protected transient float LightFadeTime;
var protected transient float LightFadeTimeRemaining;
var protected transient float LightInitialBrightness;
/** Temp data for radial blur fading. */
var protected transient float RadialBlurFadeTime;
var protected transient float RadialBlurFadeTimeRemaining;
var protected transient float RadialBlurMaxBlurAmount;
/** Temp reference to the explosion template, used for delayed damage */
var GameExplosion ExplosionTemplate;
/**
* If TRUE, take the Explosion ParticleSystem lifespan into account when determining
* the lifespan of the GameExplosionActor. This is useful in cases where the GEA
* needs to do further processing while the particle system is active.
* For example, in the case of a smoke grenade, you would want to ensure that the
* explosion actor stayed around long enough to properly trigger coughing, etc. when
* a pawn enters/exits the smoke area.
*/
var bool bTrackExplosionParticleSystemLifespan;
/** Used to push physics when explosion goes off. */
var protected RB_RadialImpulseComponent RadialImpulseComponent;
/** player responsible for damage */
var Controller InstigatorController;
/** This the saved off hit actor and location from the GetPhysicalMaterial trace so we can see if it is a FluidSurfaceActor and then apply some forces to it **/
var Actor HitActorFromPhysMaterialTrace;
var vector HitLocationFromPhysMaterialTrace;
/** Are we attached to something? Used to attach FX for stuff like the smoke grenade. */
var Actor Attachee;
var Controller AttacheeController;
/** Minimum dot product for explosion to be able to affect a point. Used as an optimization for directional explosions. */
var transient float DirectionalExplosionMinDot;
/** Forward dir for directional explosions. */
var transient vector ExplosionDirection;
/** Toggles debug explosion rendering. */
`if(`__TW_)
var config bool bDrawDebug;
`else
var bool bDrawDebug;
`endif // __TW_
replication
{
if (bNetInitial)
ExplosionDirection;
}
cpptext
{
virtual void TickSpecial(FLOAT DeltaSeconds);
}
`if(`__TW_)
// Custom line trace for more specific TraceFlags control
function Actor TraceExplosive( vector TraceEnd, vector TraceStart )
{
return StaticTraceExplosive( TraceEnd, TraceStart, self );
}
native static function Actor StaticTraceExplosive(vector TraceEnd, vector TraceStart, Actor SourceActor);
`endif
event PreBeginPlay()
{
Super.PreBeginPlay();
if (Instigator != None && InstigatorController == None)
{
InstigatorController = Instigator.Controller;
}
}
/**
* Internal. Tries to find a physical material for the surface the explosion occurred upon.
* @note: It sucks doing an extra trace here. We could conceivably pass the physical material info around
* by changing the lower level physics code (e.g. processHitWall), but that's a big engine-level change.
*/
simulated protected function PhysicalMaterial GetPhysicalMaterial()
{
local PhysicalMaterial Retval;
local vector TraceStart, TraceDest, OutHitNorm, ExploNormal;
local TraceHitInfo OutHitInfo;
// here we have to do an additional trace shooting straight down to see if we are under water.
TraceStart = Location + (vect(0,0,1) * 256.f);
TraceDest = Location - (vect(0,0,1) * 16.f);
HitActorFromPhysMaterialTrace = Trace(HitLocationFromPhysMaterialTrace, OutHitNorm, TraceDest, TraceStart, TRUE, vect(0,0,0), OutHitInfo, TRACEFLAG_Bullet|TRACEFLAG_PhysicsVolumes);
//DrawDebugLine( TraceStart, TraceDest, 0, 255, 0, TRUE);
//`log("EXPLOSION SURFACE:"@HitActorFromPhysMaterialTrace);
//DrawDebugCoordinateSystem( TraceStart, Rotation, 10.0f, TRUE );
if( FluidSurfaceActor(HitActorFromPhysMaterialTrace) != None )
{
Retval = OutHitInfo.PhysMaterial;
return Retval;
}
ExploNormal = vector(Rotation);
TraceStart = Location + (ExploNormal * 8.f);
TraceDest = TraceStart - (ExploNormal * 64.f);
HitActorFromPhysMaterialTrace = Trace(HitLocationFromPhysMaterialTrace, OutHitNorm, TraceDest, TraceStart, TRUE, vect(0,0,0), OutHitInfo, TRACEFLAG_Bullet);
//DrawDebugLine( TraceStart, TraceDest, 0, 255, 0, TRUE);
//DrawDebugCoordinateSystem( TraceStart, Rotation, 10.0f, TRUE );
if( HitActorFromPhysMaterialTrace != None )
{
Retval = OutHitInfo.PhysMaterial;
}
return Retval;
}
simulated function bool DoFullDamageToActor(Actor Victim)
{
return (Victim.bStatic || Victim.IsA('KActor') || Victim.IsA('InterpActor') || Victim.IsA('FracturedStaticMeshPart'));
}
simulated protected function bool IsBehindExplosion(Actor A)
{
if (ExplosionTemplate.bDirectionalExplosion && !IsZero(ExplosionDirection))
{
// @todo, for certain types of actors (e.g. large actors), we may want to test a location other than the
// actor's location. Like a cone/bbox check or something.
// @todo, maybe use Actor's bbox center, like damage does below?
return (ExplosionDirection dot Normal(A.Location - Location)) < DirectionalExplosionMinDot;
}
return FALSE;
}
/**
* Returns distance from bounding box to point
*/
final static native function float BoxDistanceToPoint(vector Start, Box BBox);
/**
* Does damage modeling and application for explosions
* @PARAM bCauseDamage if true cause damage to actors within damage radius
* @PARAM bCauseEffects if true apply other affects to actors within appropriate radii
* @RETURN TRUE if at least one Pawn victim got hurt. (This is only valid if bCauseDamage == TRUE)
*/
protected simulated function bool DoExplosionDamage(bool bCauseDamage, bool bCauseEffects)
{
`if(`__TW_)
local Actor Victim, HitActor;
local vector BBoxCenter;
`else
local Actor Victim, HitActor;
local vector HitL, HitN, Dir, BBoxCenter;//, BBoxExtent;
`endif
local bool bDamageBlocked, bDoFullDamage, bCauseFractureEffects, bCausePawnEffects, bCauseDamageEffects, bHurtSomeone;
local float ColRadius, ColHeight, CheckRadius, VictimDist;
local array<Actor> VictimsList;
local Box BBox;
local Controller ModInstigator;
local GamePawn VictimPawn;
local FracturedStaticMeshActor FracActor;
local byte WantPhysChunksAndParticles;
local TraceHitInfo HitInfo;
local KActorFromStatic NewKActor;
local StaticMeshComponent HitStaticMesh;
// can pre-calculate this condition now
bCauseFractureEffects = bCauseEffects && WorldInfo.NetMode != NM_DedicatedServer && ExplosionTemplate.bCausesFracture;
bCauseEffects = bCauseEffects && WorldInfo.NetMode != NM_Client;
bHurtSomeone = FALSE;
// determine radius to check
CheckRadius = GetEffectCheckRadius(bCauseDamage, bCauseFractureEffects, bCauseEffects);
if ( CheckRadius > 0.0 )
{
foreach CollidingActors(class'Actor', Victim, CheckRadius, Location, ExplosionTemplate.bUseOverlapCheck,,HitInfo )
{
// check for static mesh that can become dynamic
if ( Victim.bWorldGeometry )
{
HitStaticMesh = StaticMeshComponent(HitInfo.HitComponent);
`if(`__TW_)
if ( (HitStaticMesh != None) && HitStaticMesh.CanBecomeDynamic() &&
!WorldInfo.bDropDetail && WorldInfo.GetDetailMode() > DM_Low )
`else
if ( (HitStaticMesh != None) && HitStaticMesh.CanBecomeDynamic() )
`endif
{
NewKActor = class'KActorFromStatic'.Static.MakeDynamic(HitStaticMesh);
if ( NewKActor != None )
{
Victim = NewKActor;
}
}
}
// Look for things that are not yourself and not world geom
if ( Victim != Self
&& (!Victim.bWorldGeometry || Victim.bCanBeDamaged)
&& (NavigationPoint(Victim) == None)
&& Victim != ExplosionTemplate.ActorToIgnoreForDamage
&& (!ExplosionTemplate.bIgnoreInstigator || Victim != Instigator)
&& !ClassIsChildOf(Victim.Class, ExplosionTemplate.ActorClassToIgnoreForDamage)
&& !IsBehindExplosion(Victim) )
{
// If attached to a pawn and victim is a pawn on other team
VictimPawn = GamePawn(Victim);
// check if visible, unless physics object
// note: using bbox center instead of location, because that's what visiblecollidingactors does
Victim.GetComponentsBoundingBox(BBox);
// adjust distance if using overlap check
if ( ExplosionTemplate.bUseOverlapCheck )
{
VictimDist = BoxDistanceToPoint(Location, BBox);
}
else
{
VictimDist = VSize(Location - Victim.Location);
}
// Do fracturing
if( bCauseFractureEffects && (VictimPawn == None) )
{
FracActor = FracturedStaticMeshActor(Victim);
if ( (FracActor != None)
&& (VictimDist < ExplosionTemplate.FractureMeshRadius)
&& (FracActor.Physics == PHYS_None)
&& FracActor.IsFracturedByDamageType(ExplosionTemplate.MyDamageType)
&& FracActor.FractureEffectIsRelevant( false, Instigator, WantPhysChunksAndParticles) )
{
// Let kismet know that we were hit by an explosion
FracActor.NotifyHitByExplosion(InstigatorController, ExplosionTemplate.Damage, ExplosionTemplate.MyDamageType);
FracActor.BreakOffPartsInRadius(Location, ExplosionTemplate.FractureMeshRadius, ExplosionTemplate.FracturePartVel, WantPhysChunksAndParticles == 1 ? true : false);
}
}
bCausePawnEffects = bCauseEffects && (VictimPawn != None) && !VictimPawn.InGodMode();
bCauseDamageEffects = bCauseDamage && (VictimDist < ExplosionTemplate.DamageRadius);
// skip line check for some objects
if ( DoFullDamageToActor(Victim) )
{
bDamageBlocked = FALSE;
bDoFullDamage = TRUE; // force full damage for these objects
}
else if ( bCausePawnEffects || bCauseDamageEffects )
{
BBoxCenter = (BBox.Min + BBox.Max) * 0.5f;
`if(`__TW_)
HitActor = TraceExplosive(BBoxCenter, Location + vect(0, 0, 20));
`else
HitActor = Trace(HitL, HitN, BBoxCenter, Location + vect(0, 0, 20), FALSE,,,TRACEFLAG_Bullet);
`endif
bDamageBlocked = (HitActor != None && HitActor != Victim);
//`endif
bDoFullDamage = FALSE;
}
if ( !bDamageBlocked )
{
if ( bCauseDamageEffects )
{
// apply damage
ModInstigator = InstigatorController;
// Same team check always returns FALSE if PRI is None
if (AttacheeController != None && AttacheeController.PlayerReplicationInfo != None && VictimPawn != None && !WorldInfo.GRI.OnSameTeam(AttacheeController, VictimPawn.Controller))
{
ModInstigator = AttacheeController; // Make the instigator the base pawn's controller
}
`if(`__TW_)
Victim.TakeRadiusDamage(ModInstigator, GetDamageFor(Victim), ExplosionTemplate.DamageRadius, ExplosionTemplate.MyDamageType, ExplosionTemplate.MomentumTransferScale, Location, bDoFullDamage, (Owner != None) ? Owner : self, ExplosionTemplate.DamageFalloffExponent);
`else
Victim.TakeRadiusDamage(ModInstigator, ExplosionTemplate.Damage, ExplosionTemplate.DamageRadius, ExplosionTemplate.MyDamageType, ExplosionTemplate.MomentumTransferScale, Location, bDoFullDamage, (Owner != None) ? Owner : self, ExplosionTemplate.DamageFalloffExponent);
`endif
VictimsList[VictimsList.Length] = Victim;
if( Victim.IsA('Pawn') )
{
bHurtSomeone = TRUE;
}
}
if ( bCausePawnEffects )
{
SpecialPawnEffectsFor(VictimPawn, VictimDist);
}
else if (bCauseEffects)
{
SpecialCringeEffectsFor(Victim, VictimDist);
}
}
}
`if(`__TW_)
//Allow the explosion to handle behavior related to actors in range that are ignored
HandleIgnoredVictim(Victim);
`endif
}
if (ExplosionTemplate.bFullDamageToAttachee && VictimsList.Find(Attachee) == INDEX_NONE)
{
Victim = Attachee;
Victim.GetBoundingCylinder(ColRadius, ColHeight);
`if(`__TW_)
Victim.TakeRadiusDamage(InstigatorController, GetDamageFor(Victim), ExplosionTemplate.DamageRadius, ExplosionTemplate.MyDamageType,
ExplosionTemplate.MomentumTransferScale, Location, true, (Owner != None) ? Owner : self);
`else
Dir = Normal(Victim.Location - Location);
Victim.TakeDamage( ExplosionTemplate.Damage, InstigatorController, Victim.Location - 0.5 * (ColHeight + ColRadius) * dir,
(ExplosionTemplate.MomentumTransferScale * Dir), ExplosionTemplate.MyDamageType,, (Owner != None) ? Owner : self );
`endif
}
}
return bHurtSomeone;
}
`if(`__TW_)
function HandleIgnoredVictim(Actor Victim);
`endif
/** Return the desired radius to check for actors which get effects from explosion */
function float GetEffectCheckRadius(bool bCauseDamage, bool bCauseFractureEffects, bool bCauseEffects)
{
local float CheckRadius;
if ( bCauseFractureEffects )
{
CheckRadius = ExplosionTemplate.FractureMeshRadius;
}
if ( bCauseDamage )
{
CheckRadius = FMax(CheckRadius, ExplosionTemplate.DamageRadius);
}
if ( bCauseEffects )
{
CheckRadius = FMax(CheckRadius, ExplosionTemplate.KnockDownRadius);
CheckRadius = FMax(CheckRadius, ExplosionTemplate.CringeRadius);
}
return CheckRadius;
}
`if(`__TW_)
/** Gets explosion damage for specific target (allows children to override) */
simulated function float GetDamageFor( Actor Victim )
{
return ExplosionTemplate.Damage;
}
`endif
/**
* Handle making pawns cringe or fall down from nearby explosions. Server only.
*/
protected function SpecialPawnEffectsFor(GamePawn VictimPawn, float VictimDist);
/**
* Handle applying cringe to non-pawn actors
*
* @param Victim - the actor hit
*
* @param VictimDist - the distance the victim was from the blast
*/
protected function SpecialCringeEffectsFor(Actor Victim, float VictimDist);
/**
* Internal. Extract what data we can from the physical material-based effects system
* and stuff it into the ExplosionTemplate.
* Data in the physical material will take precedence.
*
* We are also going to be checking for relevance here as when any of these params are "none" / invalid we do not
* play those effects in Explode(). So this way we avoid any work on looking things up in the physmaterial
*
*/
simulated protected function UpdateExplosionTemplateWithPerMaterialFX(PhysicalMaterial PhysMaterial);
simulated function SpawnExplosionParticleSystem(ParticleSystem Template);
simulated function SpawnExplosionDecal();
simulated function SpawnExplosionFogVolume();
/**
* @todo break this up into the same methods that <Game>Weapon uses (SpawnImpactEffects, SpawnImpactSounds, SpawnImpactDecal) as they are all
* orthogonal and so indiv subclasses can choose to have base functionality or override
*
* @param Direction For bDirectionalExplosion=true explosions, this is the forward direction of the blast.
**/
simulated function Explode(GameExplosion NewExplosionTemplate, optional vector Direction)
{
local float HowLongToLive;
local PhysicalMaterial PhysMat;
local bool bHurtSomeone;
// copy our significant data
ExplosionTemplate = NewExplosionTemplate;
if (ExplosionTemplate.bDirectionalExplosion)
{
ExplosionDirection = Normal(Direction);
DirectionalExplosionMinDot = Cos(ExplosionTemplate.DirectionalExplosionAngleDeg * DegToRad);
}
// by default, live just long enough to go boom
HowLongToLive = LifeSpan + ExplosionTemplate.DamageDelay + 0.01f;
if (!bHasExploded || bExplodeMoreThanOnce )
{
// maybe find the physical material and extract the properties we need
if (ExplosionTemplate.bAllowPerMaterialFX)
{
PhysMat = GetPhysicalMaterial();
`if(`__TW_)
// Go ahead and update anyway, if its none or not so that we KNOW that it's none and can handle appropriately
UpdateExplosionTemplateWithPerMaterialFX(PhysMat);
`else
if (PhysMat != None)
{
UpdateExplosionTemplateWithPerMaterialFX(PhysMat);
}
`endif // __TW_
}
// spawn explosion effects
if( WorldInfo.NetMode != NM_DedicatedServer )
{
if( ExplosionTemplate.ParticleEmitterTemplate != none )
{
SpawnExplosionParticleSystem(ExplosionTemplate.ParticleEmitterTemplate);
if (bTrackExplosionParticleSystemLifespan == TRUE)
{
// Let the particle system contribute to life span determination...
HowLongToLive = FMax(ExplosionTemplate.ParticleEmitterTemplate.GetMaxLifespan(0.0f) + 0.1f, HowLongToLive);
}
}
// spawn a decal
SpawnExplosionDecal();
// turn on the light
`if(`__TW_)
if (ExplosionTemplate.ExploLight != None && WorldInfo.bAllowExplosionLights && !WorldInfo.bDropDetail )
`else
if (ExplosionTemplate.ExploLight != None)
`endif
{
if( ExplosionLight != None )
{
// If there is already an explosion light, detach it
DetachComponent(ExplosionLight);
}
// construct a copy of the PLC, turn it on
ExplosionLight = new(self) class'PointLightComponent' (ExplosionTemplate.ExploLight);
if (ExplosionLight != None)
{
AttachComponent(ExplosionLight);
ExplosionLight.SetEnabled(TRUE);
SetTimer(ExplosionTemplate.ExploLightFadeOutTime);
LightFadeTime = ExplosionTemplate.ExploLightFadeOutTime;
LightFadeTimeRemaining = LightFadeTime;
`if(`__TW_)
LightFadeStartTime = ExplosionTemplate.ExploLightStartFadeOutTime;
LightFlickerIntensity = ExplosionTemplate.ExploLightFlickerIntensity;
LightFlickerInterpSpeed = ExplosionTemplate.ExploLightFlickerInterpSpeed;
HowLongToLive = FMax( LightFadeTime + LightFadeStartTime + 0.2f, HowLongToLive );
`else
HowLongToLive = FMax( LightFadeTime + 0.2f, HowLongToLive );
`endif // __TW_
LightInitialBrightness = ExplosionTemplate.ExploLight.Brightness;
}
}
// radial blur
if (ExplosionTemplate.ExploRadialBlur != None)
{
if ((ExplosionTemplate.bPerformRadialBlurRelevanceCheck == false) || ImpactEffectIsRelevant(Instigator, Location+vect(0,0,1), false, 4000.0f, 350.0f, true))
{
if( ExplosionRadialBlur != None )
{
// If there is already a radial blur, detach it
DetachComponent(ExplosionRadialBlur);
}
ExplosionRadialBlur = new(self) class'RadialBlurComponent' (ExplosionTemplate.ExploRadialBlur);
if (ExplosionRadialBlur != None)
{
AttachComponent(ExplosionRadialBlur);
RadialBlurFadeTime = ExplosionTemplate.ExploRadialBlurFadeOutTime;
RadialBlurFadeTimeRemaining = RadialBlurFadeTime;
RadialBlurMaxBlurAmount = ExplosionTemplate.ExploRadialBlurMaxBlur;
SetTimer(FMax(RadialBlurFadeTime,LightFadeTime));
HowLongToLive = FMax( RadialBlurFadeTime + 0.2f, HowLongToLive );
}
}
}
// cam shakes
DoExplosionCameraEffects();
// Apply impulse to physics stuff (before we do fracture)
`if(`__TW_)
// fixed grenade log spam
if (RadialImpulseComponent == None)
{
}
else
`endif
if (ExplosionTemplate.MyDamageType != None && ExplosionTemplate.MyDamageType.default.RadialDamageImpulse > 0.0)
{
RadialImpulseComponent.ImpulseRadius = FMax(ExplosionTemplate.DamageRadius, ExplosionTemplate.KnockDownRadius);
RadialImpulseComponent.ImpulseStrength = ExplosionTemplate.MyDamageType.default.RadialDamageImpulse;
RadialImpulseComponent.bVelChange = ExplosionTemplate.MyDamageType.default.bRadialDamageVelChange;
RadialImpulseComponent.ImpulseFalloff = RIF_Constant;
//`log("AA"@ExplosionTemplate.MyDamageType@RadialImpulseComponent.ImpulseStrength@RadialImpulseComponent.ImpulseRadius);
RadialImpulseComponent.FireImpulse(Location);
}
SpawnExplosionFogVolume();
if( FluidSurfaceActor(HitActorFromPhysMaterialTrace) != none )
{
FluidSurfaceActor(HitActorFromPhysMaterialTrace).FluidComponent.ApplyForce( HitLocationFromPhysMaterialTrace, 1024.0f, 20.0f, FALSE );
}
}
// do damage
// delay the damage if necessary,
bHurtSomeone = FALSE;
if ( ExplosionTemplate.Damage > 0.0 )
{
if (ExplosionTemplate.DamageDelay > 0.0)
{
// cause effects now, damage later
DoExplosionDamage(false, true);
SetTimer( ExplosionTemplate.DamageDelay, FALSE, nameof(DelayedExplosionDamage) );
}
else
{
// otherwise apply immediately
bHurtSomeone = DoExplosionDamage(true, true);
}
}
else
{
DoExplosionDamage(false, true);
}
// play the sound
if( WorldInfo.NetMode != NM_DedicatedServer )
{
if( bHurtSomeone && ExplosionTemplate.ExplosionSoundHurtSomeone != None)
{
//`log( "Playing Explosion Sound (debug left in to test distance)" @ ExplosionTemplate.ExplosionSound );
`if(`__TW_WWISE_)
// sound location can't be the same as sound player location, so add a little offset to make Wwise happy
PlaySoundBase( ExplosionTemplate.ExplosionSoundHurtSomeone, TRUE, TRUE, FALSE, Location + vect(0,0,0.1), TRUE );
`else
PlaySound( ExplosionTemplate.ExplosionSoundHurtSomeone, TRUE, TRUE, FALSE, Location, TRUE );
`endif // __TW_WWISE_
}
else if( ExplosionTemplate.ExplosionSound != None )
{
//`log( "Playing Explosion Sound (debug left in to test distance)" @ ExplosionTemplate.ExplosionSound );
`if(`__TW_WWISE_)
// sound location can't be the same as sound player location, so add a little offset to make Wwise happy
PlaySoundBase( ExplosionTemplate.ExplosionSound, TRUE, TRUE, FALSE, Location + vect(0,0,0.1), TRUE );
`else
PlaySound( ExplosionTemplate.ExplosionSound, TRUE, TRUE, FALSE, Location, TRUE );
`endif // __TW_WWISE_
}
}
if( Role == Role_Authority )
{
MakeNoise(1.0);
}
`if(`notdefined(FINAL_RELEASE))
if (bDrawDebug)
{
DrawDebug();
}
`endif
bHasExploded = TRUE;
// done with it
if (!bPendingDelete && !bDeleteMe)
{
// Live forever if this actor can explode more than once
LifeSpan = bExplodeMoreThanOnce ? 0.0 : HowLongToLive;
}
}
}
simulated function DelayedExplosionDamage()
{
DoExplosionDamage(true, false);
}
simulated function DrawDebug()
{
local Color C;
local float Angle;
// debug spheres
if (ExplosionTemplate.bDirectionalExplosion)
{
C.R = 255;
C.G = 128;
C.B = 16;
C.A = 255;
Angle = ExplosionTemplate.DirectionalExplosionAngleDeg * DegToRad;
DrawDebugCone(Location, ExplosionDirection, ExplosionTemplate.DamageRadius, Angle, Angle, 8, C, TRUE);
}
else
{
DrawDebugSphere(Location, ExplosionTemplate.DamageRadius, 10, 255, 128, 16, TRUE);
//DrawDebugLine(Location, Location + HitNormal*16, 255, 255, 255, TRUE);
}
}
simulated function DoExplosionCameraEffects()
{
local CameraShake Shake;
local float ShakeScale;
local PlayerController PC;
// do camera shake(s)
// note: intentionally letting directional explosions still shake everything
foreach WorldInfo.LocalPlayerControllers(class'PlayerController', PC)
{
if (PC.PlayerCamera != None)
{
Shake = ChooseCameraShake(Location, PC);
if (Shake != None)
{
ShakeScale = PC.PlayerCamera.CalcRadialShakeScale(PC.PlayerCamera, Location, ExplosionTemplate.CamShakeInnerRadius, ExplosionTemplate.CamShakeOuterRadius, ExplosionTemplate.CamShakeFalloff);
if (ExplosionTemplate.bOrientCameraShakeTowardsEpicenter)
{
PC.ClientPlayCameraShake(Shake, ShakeScale, ExplosionTemplate.bAutoControllerVibration, CAPS_UserDefined, rotator(Location - PC.ViewTarget.Location));
}
else
{
PC.ClientPlayCameraShake(Shake, ShakeScale, ExplosionTemplate.bAutoControllerVibration);
}
}
}
}
// do lens effects
SpawnCameraLensEffects();
}
/**
* Spawns the camera lens effect(s) if needed by this explosion
*/
simulated function SpawnCameraLensEffects()
{
local PlayerController PC;
if (ExplosionTemplate.CameraLensEffect != None)
{
foreach WorldInfo.LocalPlayerControllers(class'PlayerController', PC)
{
// splatter some blood on their camera if they are a human and decently close
if ( PC.Pawn != None &&
VSize(PC.Pawn.Location - Location) < ExplosionTemplate.CameraLensEffectRadius &&
PC.IsAimingAt(self, 0.1) && // if we are semi looking in the direction of the explosion
!IsBehindExplosion(PC.Pawn) )
{
PC.ClientSpawnCameraLensEffect(ExplosionTemplate.CameraLensEffect);
}
}
}
}
/**
* Internal. When using directional camera shakes, used to determine which anim to use.
* @todo: nativise for speed?
*/
protected simulated function CameraShake ChooseCameraShake(vector Epicenter, PlayerController PC)
{
local vector CamX, CamY, CamZ, ToEpicenter;
local float FwdDot, RtDot;
local CameraShake ChosenShake;
local Rotator NoPitchRot;
if (ExplosionTemplate.bOrientCameraShakeTowardsEpicenter)
{
return ExplosionTemplate.CamShake;
}
// expected to be false much of the time, so maybe bypass the math
else if ( (ExplosionTemplate.CamShake_Left != None) || (ExplosionTemplate.CamShake_Right != None) || (ExplosionTemplate.CamShake_Rear != None) )
{
ToEpicenter = Epicenter - PC.PlayerCamera.Location;
ToEpicenter.Z = 0.f;
ToEpicenter = Normal(ToEpicenter);
NoPitchRot = PC.PlayerCamera.Rotation;
NoPitchRot.Pitch = 0.f;
GetAxes(NoPitchRot, CamX, CamY, CamZ);
FwdDot = CamX dot ToEpicenter;
if (FwdDot > 0.707f)
{
// use forward
ChosenShake = ExplosionTemplate.CamShake;
}
else if (FwdDot > -0.707f)
{
// need to determine r or l
RtDot = CamY dot ToEpicenter;
ChosenShake = (RtDot > 0.f) ? ExplosionTemplate.CamShake_Right : ExplosionTemplate.CamShake_Left;
}
else
{
// use back
ChosenShake = ExplosionTemplate.CamShake_Rear;
}
}
if (ChosenShake == None)
{
// fall back to forward
ChosenShake = ExplosionTemplate.CamShake;
}
return ChosenShake;
}
defaultproperties
{
RemoteRole=ROLE_None
bExplodeMoreThanOnce = False;
Begin Object Class=RB_RadialImpulseComponent Name=ImpulseComponent0
End Object
RadialImpulseComponent=ImpulseComponent0
Components.Add(ImpulseComponent0)
//bDebug=TRUE
}

View File

@ -0,0 +1,81 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* This is a content only archetype object to allow artists to update explosion
* content without programmer intervention
*/
class GameExplosionContent extends Object
editinlinenew;
/** TRUE to attempt to automatically do force feedback to match the camera shake */
var() const bool bAutoControllerVibration;
//
// Audio parameters
//
`if(`__TW_WWISE_)
/** Audio to play at explosion time. */
var(Audio) const AkBaseSoundObject ExplosionSound;
`else
/** Audio to play at explosion time. */
var(Audio) const SoundCue ExplosionSound;
`endif
//
// Camera parameters
//
/** TRUE to rotate CamShake to play radially relative to the explosion. Left/Right/Rear will be ignored. */
var(Camera) const bool bOrientCameraShakeTowardsEpicenter;
/** Shake to play when source is in front of the camera, or when directional variants are unspecified. */
var(Camera) const CameraShake CamShake;
/** Anim to play when the source event is to the left of the camera. If None, CamShake will be used instead. */
var(Camera) const CameraShake CamShake_Left;
/** Anim to play when the source event is to the right of the camera. If None, CamShake will be used instead. */
var(Camera) const CameraShake CamShake_Right;
/** Anim to play when the source event is behind of the camera. If None, CamShake will be used instead. */
var(Camera) const CameraShake CamShake_Rear;
/** Radius within which to play full-powered camera shake (will be scaled within radius) const */
var(Camera) const float CamShakeInnerRadius;
/** Between inner and outer radii, scale shake from full to zero */
var(Camera) const float CamShakeOuterRadius;
/** Exponent for intensity falloff between inner and outer radii. */
var(Camera) const float CamShakeFalloff;
/** Play this CameraLensEffect when ever damage of this type is given. This will primarily be used by explosions. But could be used for other impacts too! **/
var(Camera) const class<EmitterCameraLensEffectBase> CameraLensEffect;
/** This is the radius to play the camera effect on **/
var(Camera) const float CameraLensEffectRadius;
//
// Dynamic light parameters
//
/** Defines the dynamic light cast by the explosion */
var(Light) const editinline PointLightComponent ExploLight;
/** Dynamic Light fade out time, in seconds */
var(Light) const float ExploLightFadeOutTime;
/** Defines the blurred region for the explosion */
var(Blur) const editinline RadialBlurComponent ExploRadialBlur;
/** Radial blur fade out time, in seconds */
var(Blur) const float ExploRadialBlurFadeOutTime;
/** Radial blur max blur amount */
var(Blur) const float ExploRadialBlurMaxBlur;
//
// Particle effect parameters
//
/** Which particle effect to play. */
var(Particle) const ParticleSystem ParticleEmitterTemplate;
//
// Fog volume parameters
//
/** The archetype that the artists set up that will be spawned with this explosion **/
var(Fog) const FogVolumeSphericalDensityInfo FogVolumeArchetype;

View File

@ -0,0 +1,56 @@
/**
* "Fixed" camera mode. Views through a CameraActor in the level.
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameFixedCamera extends GameCameraBase
config(Camera);
/** FOV to fall back to if we can't get one from somewhere else. */
var() const protected float DefaultFOV;
simulated function UpdateCamera(Pawn P, GamePlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT)
{
local CameraActor CamActor;
// are we looking at a camera actor?
CamActor = CameraActor(OutVT.Target);
if (CamActor != None)
{
// we're attached to a camactor, use it's FOV
OutVT.POV.FOV = CamActor.FOVAngle;
}
else
{
OutVT.POV.FOV = DefaultFOV;
}
// copy loc/rot from actor we're attached to
if (OutVT.Target != None)
{
OutVT.POV.Location = CamActor.Location;
OutVT.POV.Rotation = CamActor.Rotation;
}
// cameraanims, etc
PlayerCamera.ApplyCameraModifiers(DeltaTime, OutVT.POV);
// if we had to reset camera interpolation, then turn off flag once it's been processed.
bResetCameraInterpolation = FALSE;
}
/** Called when Camera mode becomes active */
function OnBecomeActive( GameCameraBase OldCamera )
{
// this will cause us to always snap to fixed cameras
bResetCameraInterpolation = TRUE;
super.OnBecomeActive( OldCamera );
}
defaultproperties
{
DefaultFOV=80
}

View File

@ -0,0 +1,77 @@
/**
* KActor used for spawning "effect" type of physics objects. (e.g. gun magazines)
* This will nice "fade to nothing when it is dying.
*
*
* So in PhysX2 when we use smallish collision on objects we will get tunneling and that will cause the
* object to either fall directly through the ground or bounce a little bit and then fall through the ground.
*
* CCD currently is slow, has some bugs, and is a global setting (as opposed to compartment)
*
* So for physx2 you need to make a larger than correct box/shape and that should stop these RigidBodyies from falling through
* the world.
*
* One way to do that is with the "Set Collision from Builder Brush" functionality. Make a builder brush around the object and use that for
* the collision!
*
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameKActorSpawnableEffect extends KActor
notplaceable;
// don't do normal KActor init
simulated event PostBeginPlay()
{
SetTimer(FMax(0.01,LifeSpan-1.1), false, 'StartScalingDown');
}
simulated event FellOutOfWorld( class<DamageType> dmgType )
{
Destroy();
}
simulated function StartScalingDown()
{
GotoState('ScalingDown');
}
simulated state ScalingDown
{
simulated event Tick( float DeltaTime )
{
Super.Tick( DeltaTime );
// if we are close to the end of our life start scaling to zero
if( LifeSpan < 1.0f )
{
SetDrawScale( LifeSpan );
}
}
}
defaultproperties
{
Begin Object Name=MyLightEnvironment
bCastShadows=FALSE
bEnabled=TRUE
bDynamic=TRUE
End Object
Begin Object Name=StaticMeshComponent0
CastShadow=FALSE
BlockActors=FALSE
bAcceptsStaticDecals=FALSE
bAcceptsDynamicDecals=FALSE
End Object
bNoDelete=FALSE
RemoteRole=ROLE_None
bBlocksNavigation=FALSE
bCollideWorld=FALSE
bCollideActors=FALSE
bBlockActors=FALSE
bNoEncroachCheck=TRUE
LifeSpan=30.0f
}

View File

@ -0,0 +1,92 @@
/**
* GamePawn
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GamePawn extends Pawn
config(Game)
native
abstract
notplaceable
nativereplication;
/** Was the last hit considered a head shot? Used to see if we need to pop off helmet/head */ // @TODO FIXMESTEVE SHOULD NOT BE HERE!
var transient bool bLastHitWasHeadShot;
/** Whether pawn responds to explosions or not (ie knocked down from explosions) */
var bool bRespondToExplosions;
cpptext
{
// Networking
INT* GetOptimizedRepList( BYTE* Recent, FPropertyRetirement* Retire, INT* Ptr, UPackageMap* Map, UActorChannel* Channel );
}
replication
{
// Replicated to ALL
if( Role == Role_Authority )
bLastHitWasHeadShot;
}
/** This will update the shadow settings for this pawn's mesh **/
simulated event UpdateShadowSettings( bool bInWantShadow )
{
local bool bNewCastShadow;
local bool bNewCastDynamicShadow;
if( Mesh != None )
{
bNewCastShadow = default.Mesh.CastShadow && bInWantShadow;
bNewCastDynamicShadow = default.Mesh.bCastDynamicShadow && bInWantShadow;
if( (bNewCastShadow != Mesh.CastShadow) || (bNewCastDynamicShadow != Mesh.bCastDynamicShadow) )
{
// if there is a pending Attach then this will set the shadow immediately as the flags have changed an a reattached has occurred
Mesh.CastShadow = bNewCastShadow;
Mesh.bCastDynamicShadow = bNewCastDynamicShadow;
// if we are in a poor framerate situation just change the settings even if people are looking at it
if( WorldInfo.bAggressiveLOD == TRUE )
{
ReattachMesh();
}
else
{
ReattachMeshWithoutBeingSeen();
}
}
}
}
/** reattaches the mesh component **/
simulated function ReattachMesh()
{
ClearTimer( nameof(ReattachMeshWithoutBeingSeen) );
ReattachComponent(Mesh);
}
/** reattaches the mesh component without being seen **/
simulated function ReattachMeshWithoutBeingSeen()
{
// defer so we do not pop from any settings we have changed (e.g. shadow settings)
if( LastRenderTime > WorldInfo.TimeSeconds - 1.0 )
{
SetTimer( 0.5 + FRand() * 0.5, FALSE, nameof(ReattachMeshWithoutBeingSeen) );
}
// we have not been rendered for a bit so go ahead and reattach
else
{
ReattachMesh();
}
}
defaultproperties
{
bCanBeAdheredTo=TRUE
bCanBeFrictionedTo=TRUE
}

View File

@ -0,0 +1,371 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GamePlayerCamera extends Camera
config(Camera)
native(Camera);
// NOTE FOR REFERENCE
// >> IS LOCAL->WORLD (no transpose)
// << IS WORLD->LOCAL (has the transpose)
/**
* Architectural overview
* GamePlayerCamera is an override of the Engine camera, and is the master camera that the player actually looks through. This camera
* will lean on whatever GameCameraBase(s) it is currently using to calc the final camera properties.
* GameCameraBase is a base class for defining specific camera algorithms (e.g. fixed, specatator, first person, third person). Certain
* instances of these can contain another layer of "modes", e.g. ...
* GameThirdPersonCameraModeBase is a base class for defining specific variants of the GameThirdPersonCamera.
*
* */
/////////////////////
// Camera Modes
/////////////////////
/** Implements typical third person camera. */
var(Camera) editinline transient GameCameraBase ThirdPersonCam;
/** Class to use for third person camera. */
var(Camera) protected const class<GameCameraBase> ThirdPersonCameraClass;
/** Implements fixed camera, used for viewing through pre-placed camera actors. */
var(Camera) editinline transient GameCameraBase FixedCam;
/** Class to use for third person camera. */
var(Camera) protected const class<GameCameraBase> FixedCameraClass;
/** Which camera is currently active. */
var(Camera) editinline transient GameCameraBase CurrentCamera;
/////////////////////
// FOV Overriding
/////////////////////
/** Should the FOV be overridden? */
var transient bool bUseForcedCamFOV;
/** If bUseForcedCamFOV is true, use this angle */
var transient float ForcedCamFOV;
/////////////////////
// Interpolation
/////////////////////
var transient bool bInterpolateCamChanges;
var transient private Actor LastViewTarget;
/** Indicates if we should reset interpolation on whichever active camera processes next. */
var transient private bool bResetInterp;
/////////////////////
// Camera Shakes
/////////////////////
/** Scalar applied to all screen shakes in splitscreen. Normally used to dampen, since shakes feel more intense in a smaller viewport. */
var() protected const float SplitScreenShakeScale;
///////////////////////
//// Shaky-cam management
///////////////////////
///** The pawn that was last used to cache the animnodes used for shakycam. Changing viewtarget pawns will trigger a re-cache. */
//var private transient Pawn ShakyCamAnimNodeCachePawn;
///** AnimNode names for the "standing idle" camera animation. */
//var() private const array<name> StandingIdleSequenceNodeNames;
///** Cached refs to the standing idle animations. */
//var private transient array<AnimNodeSequence> StandingIdleSequenceNodes;
/////////////////////
// Etc
/////////////////////
// dealing with situations where camera target is based on another actor
var transient protected Actor LastTargetBase;
var transient protected matrix LastTargetBaseTM;
cpptext
{
virtual void AddPawnToHiddenActorsArray( APawn *PawnToHide );
virtual void RemovePawnFromHiddenActorsArray( APawn *PawnToHide );
virtual void ModifyPostProcessSettings(FPostProcessSettings& PPSettings) const;
};
/**
* Internal. Creates and initializes a new camera of the specified class, returns the object ref.
*/
protected function GameCameraBase CreateCamera(class<GameCameraBase> CameraClass)
{
local GameCameraBase NewCam;
NewCam = new(Outer) CameraClass;
NewCam.PlayerCamera = self;
NewCam.Init();
return NewCam;
}
protected native function CacheLastTargetBaseInfo(Actor TargetBase);
function PostBeginPlay()
{
super.PostBeginPlay();
// Setup camera modes
if ( (ThirdPersonCam == None) && (ThirdPersonCameraClass != None) )
{
ThirdPersonCam = CreateCamera(ThirdPersonCameraClass);
}
if ( (FixedCam == None) && (FixedCameraClass != None) )
{
FixedCam = CreateCamera(FixedCameraClass);
}
}
// reset the camera to a good state
function Reset()
{
bUseForcedCamFOV = false;
}
/**
* Internal. Polls game state to determine best camera to use.
*/
protected function GameCameraBase FindBestCameraType(Actor CameraTarget)
{
local GameCameraBase BestCam;
// if not using a game-specific camera (i.e. not 'default'), we'll let the engine handle it
if (CameraStyle == 'default')
{
if (CameraActor(CameraTarget) != None)
{
// if attached to a CameraActor and not spectating, use Fixed
BestCam = FixedCam;
}
else
{
BestCam = ThirdPersonCam;
}
}
return BestCam;
}
/**
* Available for overriding.
*/
function bool ShouldConstrainAspectRatio()
{
return FALSE;
}
/**
* Query ViewTarget and outputs Point Of View.
*
* @param OutVT ViewTarget to use.
* @param DeltaTime Delta Time since last camera update (in seconds).
*/
function UpdateViewTarget(out TViewTarget OutVT, float DeltaTime)
{
local Pawn P;
local GameCameraBase NewCamera;
local CameraActor CamActor;
// Don't update outgoing viewtarget during an interpolation
if( PendingViewTarget.Target != None && OutVT == ViewTarget && BlendParams.bLockOutgoing )
{
return;
}
// Make sure we have a valid target
if( OutVT.Target == None )
{
`log("Camera::UpdateViewTarget OutVT.Target == None");
return;
}
P = Pawn(OutVT.Target);
// decide which camera to use
NewCamera = FindBestCameraType(OutVT.Target);
// handle a switch if necessary
if (CurrentCamera != NewCamera)
{
if (CurrentCamera != None)
{
CurrentCamera.OnBecomeInActive( NewCamera );
}
if (NewCamera != None)
{
NewCamera.OnBecomeActive( CurrentCamera );
}
CurrentCamera = NewCamera;
}
// update current camera
if (CurrentCamera != None)
{
// we wait to apply this here in case the above code changed currentcamera on us
if (bResetInterp && !bInterpolateCamChanges)
{
CurrentCamera.ResetInterpolation();
}
// Make sure overridden post process settings have a chance to get applied
CamActor = CameraActor(OutVT.Target);
if( CamActor != None )
{
CamActor.GetCameraView(DeltaTime, OutVT.POV);
// Check to see if we should be constraining the viewport aspect. We'll only allow aspect
// ratio constraints for fixed cameras (non-spectator)
if( CurrentCamera == FixedCam && CamActor.bConstrainAspectRatio )
{
// Grab aspect ratio from the CameraActor
bConstrainAspectRatio = true;
OutVT.AspectRatio = CamActor.AspectRatio;
}
// See if the CameraActor wants to override the PostProcess settings used.
CamOverridePostProcessAlpha = CamActor.CamOverridePostProcessAlpha;
if( CamOverridePostProcessAlpha > 0.f )
{
CamPostProcessSettings = CamActor.CamOverridePostProcess;
}
}
CurrentCamera.UpdateCamera(P,self,DeltaTime, OutVT);
if( CameraStyle == 'FreeCam_Default' )
{
Super.UpdateViewTarget(OutVT, DeltaTime);
}
}
else
{
// let the engine handle updating
super.UpdateViewTarget(OutVT, DeltaTime);
}
// check for forced fov
if (bUseForcedCamFOV)
{
OutVT.POV.FOV = ForcedCamFOV;
}
// adjust FOV for splitscreen, 4:3, whatever
OutVT.POV.FOV = AdjustFOVForViewport(OutVT.POV.FOV, P);
// set camera's location and rotation, to handle cases where we are not locked to view target
SetRotation(OutVT.POV.Rotation);
SetLocation(OutVT.POV.Location);
`if(`__TW_)
// Do not call UpdateCameraLensEffects() here. It is called from the
// overridden function in KFPlayerCamera after the FOV has been adjusted
`else
UpdateCameraLensEffects( OutVT );
`endif
// store info about the target's base, to handle target's that are standing on moving geometry
CacheLastTargetBaseInfo(OutVT.Target.Base);
bResetInterp = FALSE;
}
/** Update any attached camera lens effects (e.g. blood) **/
simulated function UpdateCameraLensEffects( const out TViewTarget OutVT )
{
local int Idx;
for (Idx=0; Idx<CameraLensEffects.length; ++Idx)
{
if (CameraLensEffects[Idx] != None)
{
CameraLensEffects[Idx].UpdateLocation(OutVT.POV.Location, OutVT.POV.Rotation, OutVT.POV.FOV);
}
}
}
simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
{
local Canvas Canvas;
Super.DisplayDebug(HUD, out_YL, out_YPos);
Canvas = HUD.Canvas;
Canvas.SetDrawColor(255,255,255);
Canvas.DrawText( " " @ `showvar(CurrentCamera));
out_YPos += out_YL;
Canvas.SetPos(4,out_YPos);
if( CurrentCamera != None )
{
CurrentCamera.DisplayDebug(HUD, out_YL, out_YPos);
}
}
/**
* Sets the new color scale
*/
simulated function SetColorScale( vector NewColorScale )
{
if( bEnableColorScaling == TRUE )
{
// set the default color scale
bEnableColorScaling = TRUE;
ColorScale = NewColorScale;
bEnableColorScaleInterp = false;
}
}
/** Stop interpolation for this frame and just let everything go to where it's supposed to be. */
simulated function ResetInterpolation()
{
bResetInterp = TRUE;
}
/**
* Give cameras a chance to influence player view rotation.
*/
function ProcessViewRotation(float DeltaTime, out rotator out_ViewRotation, out rotator out_DeltaRot)
{
if( CurrentCamera != None )
{
CurrentCamera.ProcessViewRotation(DeltaTime, ViewTarget.Target, out_ViewRotation, out_DeltaRot);
}
}
/**
* Given a horizontal FOV that assumes a 16:9 viewport, return an appropriately
* adjusted FOV for the viewport of the target pawn.
* Used to correct for splitscreen.
*/
final protected native function float AdjustFOVForViewport(float inHorizFOV, Pawn CameraTargetPawn) const;
defaultproperties
{
DefaultFOV=70.f
CameraStyle=Default
ThirdPersonCameraClass=class'GameThirdPersonCamera'
FixedCameraClass=class'GameFixedCamera'
}

View File

@ -0,0 +1,275 @@
/**
* GamePlayerController
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GamePlayerController extends PlayerController
dependson(GamePawn)
config(Game)
native
abstract;
/** If true, warn GameCrowdAgents to avoid pawn controlled by this player. Used when player will move through crowds */
var bool bWarnCrowdMembers;
/** If TRUE, draw debug info for crowd awareness checks */
var(Debug) bool bDebugCrowdAwareness;
/** Controls how far around a player to warn agents. */
var float AgentAwareRadius;
/** Name of the current sound mode that is active for the audio system **/
var transient protected name CurrentSoundMode;
/** If true we are in 'warmup paused' state to allow textures to stream in, and should not allow unpausing. */
var transient bool bIsWarmupPaused;
cpptext
{
virtual void TickSpecial( FLOAT DeltaSeconds );
}
function OnToggleMouseCursor( SeqAct_ToggleMouseCursor inAction )
{
local GameViewportClient GVC;
GVC = LocalPlayer(Player) != None ? LocalPlayer(Player).ViewportClient : None;
if ( GVC != None )
{
GVC.SetHardwareMouseCursorVisibility(inAction.InputLinks[0].bHasImpulse);
}
}
/**
* @return Returns the index of this PC in the GamePlayers array.
*/
native function int GetUIPlayerIndex();
exec function CrowdDebug(bool bEnabled)
{
local GameCrowdAgent GCA;
local int i, AgentCount;
local float DebugRadius;
`log("CROWDDEBUG"@MyHUD@bEnabled);
MyHUD.bShowOverlays = bEnabled;
// first remove currently enabled agents, so postrendered list doesn't grow too large and kill performance
for ( i=0; i<MyHUD.PostRenderedActors.Length; i++ )
{
GCA = GameCrowdAgent(MyHUD.PostRenderedActors[i]);
if ( GCA != None )
{
MyHUD.RemovePostRenderedActor(GCA);
}
}
if ( bEnabled )
{
// First count agents: don't want more than about 100 being postrendered
DebugRadius = 2000.0;
ForEach VisibleActors(class'GameCrowdAgent', GCA, DebugRadius, (Pawn != None) ? Pawn.Location : Location )
{
AgentCount++;
}
if( AgentCount > 100 )
{
// too many agents - reduce debugradius proportionally
DebugRadius *= sqrt(100.0/float(AgentCount));
}
ForEach VisibleActors(class'GameCrowdAgent', GCA, DebugRadius, (Pawn != None) ? Pawn.Location : Location )
{
MyHUD.AddPostRenderedActor(GCA);
}
}
}
/** Hook to let player know a refresh of crowd agent info is coming */
event NotifyCrowdAgentRefresh();
/** Hook to let player know crowd member is in range */
event NotifyCrowdAgentInRadius( GameCrowdAgent Agent );
/**
* Play forcefeedback to match the given screenshake.
*/
simulated protected function DoForceFeedbackForScreenShake( CameraShake ShakeData, float Scale )
{
local int ShakeLevel;
local float RotMag, LocMag, FOVMag;
if (ShakeData != None)
{
// figure out if we're "big", "medium", or nothing
RotMag = ShakeData.GetRotOscillationMagnitude() * Scale;
if (RotMag > 40.f)
{
ShakeLevel = 2;
}
else if (RotMag > 20.f)
{
ShakeLevel = 1;
}
if (ShakeLevel < 2)
{
LocMag = ShakeData.GetLocOscillationMagnitude() * Scale;
if (LocMag > 10.f)
{
ShakeLevel = 2;
}
else if (LocMag > 5.f)
{
ShakeLevel = 1;
}
FOVMag = ShakeData.FOVOscillation.Amplitude * Scale;
if (ShakeLevel < 2)
{
if (FOVMag > 5.f)
{
ShakeLevel = 2;
}
else if (FOVMag > 2.f)
{
ShakeLevel = 1;
}
}
}
//`log("level="@ShakeLevel@"rotmag"@VSize(ShakeData.RotAmplitude)@"locmag"@VSize(ShakeData.LocAmplitude)@"fovmag"@ShakeData.FOVAmplitude);
if (ShakeLevel == 2)
{
if( ShakeData.OscillationDuration <= 1 )
{
ClientPlayForceFeedbackWaveform(class'GameWaveForms'.default.CameraShakeBigShort);
}
else
{
ClientPlayForceFeedbackWaveform(class'GameWaveForms'.default.CameraShakeBigLong);
}
}
else if (ShakeLevel == 1)
{
if( ShakeData.OscillationDuration <= 1 )
{
ClientPlayForceFeedbackWaveform(class'GameWaveForms'.default.CameraShakeMediumShort);
}
else
{
ClientPlayForceFeedbackWaveform(class'GameWaveForms'.default.CameraShakeMediumLong);
}
}
}
}
/** Set the sound mode of the audio system for special EQing **/
simulated function SetSoundMode( Name InSoundModeName )
{
local AudioDevice Audio;
local bool bSet;
Audio = class'Engine'.static.GetAudioDevice();
if( Audio != None )
{
if( CurrentSoundMode != InSoundModeName )
{
bSet = Audio.SetSoundMode( InSoundModeName );
if( bSet == TRUE )
{
CurrentSoundMode = InSoundModeName;
}
}
}
}
/**
* Starts/stops the loading movie
*
* @param bShowMovie true to show the movie, false to stop it
* @param bPauseAfterHide (optional) If TRUE, this will pause the game/delay movie stop to let textures stream in
* @param PauseDuration (optional) allows overriding the default pause duration specified in .ini (only used if bPauseAfterHide is true)
* @param KeepPlayingDuration (optional) keeps playing the movie for a specified more seconds after it's supposed to stop
* @param bOverridePreviousDelays (optional) whether to cancel previous delayed StopMovie or not (defaults to FALSE)
*/
native static final function ShowLoadingMovie(bool bShowMovie, optional bool bPauseAfterHide, optional float PauseDuration, optional float KeepPlayingDuration, optional bool bOverridePreviousDelays);
/**
* Keep playing the loading movie if it's currently playing and abort any StopMovie calls that may be pending through the FDelayedUnpauser.
*/
native static final function KeepPlayingLoadingMovie();
/** starts playing the specified movie */
native final reliable client event ClientPlayMovie(string MovieName, int InStartOfRenderingMovieFrame, int InEndOfRenderingMovieFrame, bool bRestrictPausing , bool bPlayOnceFromStream, bool bOnlyBackButtonSkipsMovie);
/**
* Stops the currently playing movie
*
* @param DelayInSeconds number of seconds to delay before stopping the movie.
* @param bAllowMovieToFinish indicates whether the movie should be stopped immediately or wait until it's finished.
* @param bForceStopNonSkippable indicates whether a movie marked as non-skippable should be stopped anyway; only relevant if the specified
* movie is marked non-skippable (like startup movies).
* @param bForceStopLoadingMovie If false then don't stop the movie if it's the loading movie.
*/
native final reliable client event ClientStopMovie(float DelayInSeconds, bool bAllowMovieToFinish, bool bForceStopNonSkippable, bool bForceStopLoadingMovie);
/** returns the name of the currently playing movie, or an empty string if no movie is currently playing
* @todo: add an out param for current time in playback for synchronizing clients that join in the middle
*/
native final function GetCurrentMovie(out string MovieName);
/** Delegate used to control whether we can unpause during warmup. We never allow this. */
function bool CanUnpauseWarmup()
{
return !bIsWarmupPaused;
}
/** Function used by loading code to pause and unpause the game during texture-streaming warm-up. */
event WarmupPause(bool bDesiredPauseState)
{
local color FadeColor;
local PlayerController PC;
local string MovieName;
bIsWarmupPaused = bDesiredPauseState;
SetPause(bDesiredPauseState, CanUnpauseWarmup);
// When pausing, start a fade in
// the fade won't actually go away until after the pause is turned off,
// but starting it on pause instead of unpause prevents any delays between the loading movie vanishing and pause being disabled
// from causing us to be viewing some random place in the world for a few frames
if (!bDesiredPauseState)
{
GetCurrentMovie(MovieName);
if (MovieName != "")
{
foreach LocalPlayerControllers(class'PlayerController', PC)
{
GamePlayerController(PC).ClientColorFade(FadeColor, 255, 0, 2.0);
}
}
}
}
reliable client function ClientColorFade(Color FadeColor, byte FromAlpha, byte ToAlpha, float FadeTime);
defaultproperties
{
AgentAwareRadius=200.0
CheatClass=class'GameCheatManager'
}

View File

@ -0,0 +1,94 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Weapon recoil bone controller.
* Add a small recoil to a bone (similar to camera shakes).
*/
class GameSkelCtrl_Recoil extends SkelControlBase
native(Anim);
/** Recoil Start-up */
enum ERecoilStart
{
ERS_Zero, // Start with random offset (default)
ERS_Random, // Start with zero offset
};
/** Recoil params */
struct native RecoilParams
{
var() ERecoilStart X, Y, Z;
var transient const byte Padding;
};
/** Recoil definition */
struct native RecoilDef
{
/** Time in seconds to go until current recoil finished */
var transient float TimeToGo;
/** Duration in seconds of current recoil shake */
var() float TimeDuration;
/** Rotation amplitude */
var() vector RotAmplitude;
/** Rotation frequency */
var() vector RotFrequency;
/** Rotation Sine offset */
var vector RotSinOffset;
/** Rotation parameters */
var() RecoilParams RotParams;
/** Internal, Rotation offset for this frame. */
var transient Rotator RotOffset;
/** Loc offset amplitude */
var() vector LocAmplitude;
/** Loc offset frequency */
var() vector LocFrequency;
/** Loc offset Sine offset */
var vector LocSinOffset;
/** Loc parameters */
var() RecoilParams LocParams;
/** Internal, Location offset for this frame. */
var transient Vector LocOffset;
structdefaultproperties
{
TimeDuration=0.33f
}
};
/** If TRUE, Aim is ignored, and recoil is just applied in the local bone space. */
var() bool bBoneSpaceRecoil;
/** Recoil Information */
var() RecoilDef Recoil;
var() Vector2D Aim;
/** variable to play recoil */
var() transient bool bPlayRecoil;
var transient bool bOldPlayRecoil;
/** Internal, evaluates recoil is doing an effect and needs to be applied */
var transient bool bApplyControl;
cpptext
{
/** Pull aim information from Pawn */
virtual FVector2D GetAim(USkeletalMeshComponent* InSkelComponent);
/** Is skeleton currently mirrored */
virtual UBOOL IsMirrored(USkeletalMeshComponent* InSkelComponent);
// USkelControlBase interface
virtual void TickSkelControl(FLOAT DeltaSeconds, USkeletalMeshComponent* SkelComp);
virtual void GetAffectedBones(INT BoneIndex, USkeletalMeshComponent* SkelComp, TArray<INT>& OutBoneIndices);
virtual void CalculateNewBoneTransforms(INT BoneIndex, USkeletalMeshComponent* SkelComp, TArray<FBoneAtom>& OutBoneTransforms);
}
defaultproperties
{
CategoryDesc = "Recoil"
}

View File

@ -0,0 +1,200 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameSpecialMove extends Object
config(Pawn)
native(SpecialMoves)
abstract;
// C++ functions
cpptext
{
virtual void PrePerformPhysics(FLOAT DeltaTime);
virtual void PostProcessPhysics(FLOAT DeltaTime);
virtual void TickSpecialMove(FLOAT DeltaTime);
}
/** Owner of this special move */
var Pawn PawnOwner;
/** Named handle of this special move */
var Name Handle;
/** Last time CanDoSpecialMove was called. */
var transient float LastCanDoSpecialMoveTime;
/** Can we do the current special move? */
var private bool bLastCanDoSpecialMove;
/** Flag used when moving Pawn to a precise location */
var const bool bReachPreciseDestination;
/** Flag set when Pawn reached precise location */
var const bool bReachedPreciseDestination;
/** World location to reach */
var const vector PreciseDestination;
var const Actor PreciseDestBase;
var const vector PreciseDestRelOffset;
/** Flag used when rotating pawn to a precise rotation */
var const bool bReachPreciseRotation;
/** Flag set when pawn reached precise rotation */
var const bool bReachedPreciseRotation;
/** Time to interpolate Pawn's rotation */
var const float PreciseRotationInterpolationTime;
/** World rotation to face */
var const Rotator PreciseRotation;
/** PrecisePosition will not be enforced on non-owning instance unless this flag is set */
var bool bForcePrecisePosition;
function InitSpecialMove( Pawn InPawn, Name InHandle )
{
PawnOwner = GamePawn(InPawn);
Handle = InHandle;
}
/**
* Give special move a chance to set info flags
*/
function InitSpecialMoveFlags( out int out_Flags );
/**
* Give special move a chance to pull info out of flags
*/
function ExtractSpecialMoveFlags( int Flags );
/**
* Can the special move be chained after the current one finishes?
*/
function bool CanChainMove( Name NextMove )
{
return FALSE;
}
/**
* Can a new special move override this one before it is finished?
* This is only if CanDoSpecialMove() == TRUE && !bForce when starting it.
*/
function bool CanOverrideMoveWith( Name NewMove )
{
return FALSE;
}
/**
* Can this special move override InMove if it is currently playing?
*/
function bool CanOverrideSpecialMove( Name InMove )
{
return FALSE;
}
/**
* Public accessor to see if this special move can be done, handles caching the results for a single frame.
* @param bForceCheck - Allows you to skip the single frame condition (which will be incorrect on clients since LastCanDoSpecialMoveTime isn't replicated)
*/
final function bool CanDoSpecialMove( optional bool bForceCheck )
{
if( PawnOwner != None )
{
// update the cached value if outdated
if( bForceCheck || PawnOwner.WorldInfo.TimeSeconds != LastCanDoSpecialMoveTime )
{
bLastCanDoSpecialMove = InternalCanDoSpecialMove();
LastCanDoSpecialMoveTime = PawnOwner.WorldInfo.TimeSeconds;
}
// return the cached value
return bLastCanDoSpecialMove;
}
return FALSE;
}
/**
* Checks to see if this Special Move can be done.
*/
protected function bool InternalCanDoSpecialMove()
{
return TRUE;
}
/**
* Event called when Special Move is started.
*/
function SpecialMoveStarted( bool bForced, Name PrevMove );
/**
* Event called when Special Move is finished.
*/
function SpecialMoveEnded( Name PrevMove, Name NextMove );
/** Script Tick function. */
function Tick( float DeltaTime );
/** called when DoSpecialMove() is called again with this special move, but the special move flags have changed */
function SpecialMoveFlagsUpdated();
/** Should this special move be replicated to non-owning clients? */
function bool ShouldReplicate()
{
// by default all moves get replicated via GearPawn.ReplicatedSpecialMove
return TRUE;
}
/**
* Send Pawn to reach a precise destination.
* ReachedPrecisePosition() event will be called when Pawn reaches destination.
* This tries to get the Pawn as close as possible from DestinationToReach in 2D space (so Z is ignored).
* This doesn't use the path network, so PawnOwner should be already fairly close to destination.
* A TimeOut should be used to prevent the Pawn from being stuck.
* @param DestinationToReach point in world space to reach. (Z ignored).
* @param bCancel if TRUE, this will cancel any current PreciseDestination movement.
*/
native final function SetReachPreciseDestination(vector DestinationToReach, optional bool bCancel);
/**
* Force Pawn to face a specific rotation.
* @param RotationToFace Rotation for Pawn to face.
* @param InterpolationTime Time it takes for Pawn to face given rotation.
*/
native final function SetFacePreciseRotation(rotator RotationToFace, float InterpolationTime);
/**
* Event sent when Pawn has reached precise position.
* PreciseRotation or PreciseLocation, or Both.
* When both Rotation and Location are set, the event is fired just once,
* after the Pawn has reached both.
*/
event ReachedPrecisePosition();
/** Reset FacePreciseRotation related vars
* Otherwise, these vars will be carried over to next action
* vars are const in script - so need native interface
**/
native final function ResetFacePreciseRotation();
/**
* Generic function to send message events to SpecialMoves.
* Returns TRUE if message has been processed correctly.
*/
function bool MessageEvent(Name EventName, Object Sender)
{
`log(PawnOwner.WorldInfo.TimeSeconds @ PawnOwner @ class @ GetFuncName() @ "Received unhandled event!" @ `showvar(EventName) @ "from:" @ `showvar(Sender) );
ScriptTrace();
return FALSE;
}
/** Forces Pawn's rotation to a given Rotator */
`if(`__TW_)
final native function ForcePawnRotation(Pawn P, Rotator NewRotation, optional bool bIgnoreHumanController);
`else
final native function ForcePawnRotation(Pawn P, Rotator NewRotation);
`endif // __TW_
/**
* Turn a World Space location into an Actor Space relative location.
*/
native final function vector WorldToRelativeOffset(Rotator InRotation, Vector WorldSpaceOffset) const;
native final function vector RelativeToWorldOffset(Rotator InRotation, Vector RelativeSpaceOffset) const;
defaultproperties
{
}

View File

@ -0,0 +1,185 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Keeps track of current state as a game stats stream is parsed
*/
class GameStateObject extends GameplayEventsHandler
native(GameStats);
/** Types of game sessions the state object can handle */
enum GameSessionType
{
GT_SessionInvalid,
GT_SinglePlayer,
GT_Coop,
GT_Multiplayer
};
/** State variables related to a game team */
struct native TeamState
{
/** Team Index related to team metadata array */
var int TeamIndex;
/** Array of player indices that were ever on a given team */
var init array<int> PlayerIndices;
};
/** All teams present in the game over the course of the stream */
var native const Array_Mirror TeamStates{TArray<FTeamState*>};
/** Contains the notion of player state while parsing the stats stream */
struct native PlayerState
{
/** Player index related to player metadata array */
var int PlayerIndex;
/** Current team index (changes with TEAM_CHANGE event) */
var int CurrentTeamIndex;
/** Last time player spawned */
var float TimeSpawned;
/** If non-zero represents the time between a spawn and death event */
var float TimeAliveSinceLastDeath;
};
/** All players present in the game over the course of the stream */
var native const Array_Mirror PlayerStates{TArray<FPlayerState*>};
/** Type of game session we are parsing */
var GameSessionType SessionType;
/** Has the stream passed a match started event */
var bool bIsMatchStarted;
/** True if between round started and ended events */
var bool bIsRoundStarted;
/** Current round number reported by the last round started event */
var int RoundNumber;
/** Maximum round number reported during the match */
var int MaxRoundNumber;
cpptext
{
/** Return the round number in a given match, -1 if its not multiplayer */
INT GetRoundNumber() { if (SessionType == GT_Multiplayer) { return RoundNumber; } else { return -1; } }
/*
* Get a given team's current state, creating a new one if necessary
* @param TeamIndex - index of team to return state for
* @return State for given team
*/
virtual FTeamState* GetTeamState(INT TeamIndex)
{
INT TeamStateIdx = 0;
for (; TeamStateIdx < TeamStates.Num(); TeamStateIdx++)
{
if (TeamStates(TeamStateIdx)->TeamIndex == TeamIndex)
{
break;
}
}
//Create a new team if necessary
if (TeamStateIdx == TeamStates.Num())
{
FTeamState* NewTeamState = new FTeamState;
NewTeamState->TeamIndex = TeamIndex;
TeamStateIdx = TeamStates.AddItem(NewTeamState);
}
return TeamStates(TeamStateIdx);
}
/*
* Get a given player's current state, creating a new one if necessary
* @param PlayerIndex - index of player to return state for
* @return State for given player
*/
virtual FPlayerState* GetPlayerState(INT PlayerIndex)
{
INT PlayerStateIdx = 0;
for (; PlayerStateIdx < PlayerStates.Num(); PlayerStateIdx++)
{
if (PlayerStates(PlayerStateIdx)->PlayerIndex == PlayerIndex)
{
break;
}
}
//Create a new player if necessary
if (PlayerStateIdx == PlayerStates.Num())
{
FPlayerState* NewPlayerState = new FPlayerState;
NewPlayerState->PlayerIndex = PlayerIndex;
NewPlayerState->CurrentTeamIndex = INDEX_NONE;
NewPlayerState->TimeSpawned = 0;
NewPlayerState->TimeAliveSinceLastDeath = 0;
PlayerStateIdx = PlayerStates.AddItem(NewPlayerState);
}
return PlayerStates(PlayerStateIdx);
}
/*
* Get the team index for a given player
* @param PlayerIndex - player to get team index for
* @return Game specific team index
*/
INT GetTeamIndexForPlayer(INT PlayerIndex)
{
const FPlayerState* PlayerState = GetPlayerState(PlayerIndex);
return PlayerState->CurrentTeamIndex;
}
/** Handlers for parsing the game stats stream */
// Game Event Handling
virtual void HandleGameStringEvent(struct FGameEventHeader& GameEvent, struct FGameStringEvent* GameEventData);
virtual void HandleGameIntEvent(struct FGameEventHeader& GameEvent, struct FGameIntEvent* GameEventData);
virtual void HandleGameFloatEvent(struct FGameEventHeader& GameEvent, struct FGameFloatEvent* GameEventData);
virtual void HandleGamePositionEvent(struct FGameEventHeader& GameEvent, struct FGamePositionEvent* GameEventData);
// Team Event Handling
virtual void HandleTeamStringEvent(struct FGameEventHeader& GameEvent, struct FTeamStringEvent* GameEventData);
virtual void HandleTeamIntEvent(struct FGameEventHeader& GameEvent, struct FTeamIntEvent* GameEventData);
virtual void HandleTeamFloatEvent(struct FGameEventHeader& GameEvent, struct FTeamFloatEvent* GameEventData);
// Player Event Handling
virtual void HandlePlayerIntEvent(struct FGameEventHeader& GameEvent, struct FPlayerIntEvent* GameEventData);
virtual void HandlePlayerFloatEvent(struct FGameEventHeader& GameEvent, struct FPlayerFloatEvent* GameEventData);
virtual void HandlePlayerStringEvent(struct FGameEventHeader& GameEvent, struct FPlayerStringEvent* GameEventData);
virtual void HandlePlayerSpawnEvent(struct FGameEventHeader& GameEvent, struct FPlayerSpawnEvent* GameEventData);
virtual void HandlePlayerLoginEvent(struct FGameEventHeader& GameEvent, struct FPlayerLoginEvent* GameEventData);
virtual void HandlePlayerKillDeathEvent(struct FGameEventHeader& GameEvent, struct FPlayerKillDeathEvent* GameEventData);
virtual void HandlePlayerPlayerEvent(struct FGameEventHeader& GameEvent, struct FPlayerPlayerEvent* GameEventData);
virtual void HandlePlayerLocationsEvent(struct FGameEventHeader& GameEvent, struct FPlayerLocationsEvent* GameEventData);
virtual void HandleWeaponIntEvent(struct FGameEventHeader& GameEvent, struct FWeaponIntEvent* GameEventData);
virtual void HandleDamageIntEvent(struct FGameEventHeader& GameEvent, struct FDamageIntEvent* GameEventData);
virtual void HandleProjectileIntEvent(struct FGameEventHeader& GameEvent, struct FProjectileIntEvent* GameEventData);
/*
* Called when end of round event is parsed, allows for any current
* state values to be closed out (time alive, etc)
* @param TimeStamp - time of the round end event
*/
virtual void CleanupRoundState(FLOAT TimeStamp);
/*
* Called when end of match event is parsed, allows for any current
* state values to be closed out (round events, etc)
* @param TimeStamp - time of the match end event
*/
virtual void CleanupMatchState(FLOAT TimeStamp);
/*
* Cleanup a player state (round over/logout)
*/
virtual void CleanupPlayerState(INT PlayerIndex, FLOAT TimeStamp);
}
/** A chance to do something before the stream starts */
native event PreProcessStream();
/** Completely reset the game state object */
native function Reset();
defaultproperties
{
}

View File

@ -0,0 +1,614 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Aggregates data from a game session stored on disk
*/
class GameStatsAggregator extends GameplayEventsHandler
native(GameStats);
`include(Engine\Classes\GameStats.uci);
/* Aggregate data starts here */
const GAMEEVENT_AGGREGATED_DATA = 10000;
/** Player aggregates */
const GAMEEVENT_AGGREGATED_PLAYER_TIMEALIVE = 10001;
const GAMEEVENT_AGGREGATED_PLAYER_KILLS = 10002;
const GAMEEVENT_AGGREGATED_PLAYER_DEATHS = 10003;
const GAMEEVENT_AGGREGATED_PLAYER_MATCH_WON = 10004;
const GAMEEVENT_AGGREGATED_PLAYER_ROUND_WON = 10005;
const GAMEEVENT_AGGREGATED_DAMAGE_DEALT_NORMALKILL = 10006;
const GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_WASNORMALKILL = 10007;
/** Team aggregates */
const GAMEEVENT_AGGREGATED_TEAM_KILLS = 10100;
const GAMEEVENT_AGGREGATED_TEAM_DEATHS = 10101;
const GAMEEVENT_AGGREGATED_TEAM_GAME_SCORE = 10102;
const GAMEEVENT_AGGREGATED_TEAM_MATCH_WON = 10103;
const GAMEEVENT_AGGREGATED_TEAM_ROUND_WON = 10104;
/** Damage class aggregates */
const GAMEEVENT_AGGREGATED_DAMAGE_KILLS = 10200;
const GAMEEVENT_AGGREGATED_DAMAGE_DEATHS = 10201;
const GAMEEVENT_AGGREGATED_DAMAGE_DEALT_WEAPON_DAMAGE = 10202;
const GAMEEVENT_AGGREGATED_DAMAGE_DEALT_MELEE_DAMAGE = 10203;
const GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_WEAPON_DAMAGE = 10204;
const GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_MELEE_DAMAGE = 10205;
const GAMEEVENT_AGGREGATED_DAMAGE_DEALT_MELEEHITS = 10206;
const GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_WASMELEEHIT = 10207;
/** Weapon class aggregates */
const GAMEEVENT_AGGREGATED_WEAPON_FIRED = 10300;
/** Pawn class aggregates */
const GAMEEVENT_AGGREGATED_PAWN_SPAWN = 10400;
/** Game specific starts here */
const GAMEEVENT_AGGREGATED_GAME_SPECIFIC = 11000;
/** Current game state as the game stream is parsed */
var GameStateObject GameState;
/** Base container for a single stat aggregated over multiple time periods */
struct native GameEvent
{
var init array<float> EventCountByTimePeriod;
structcpptext
{
FGameEvent()
{}
FGameEvent(EEventParm)
{
appMemzero(this, sizeof(FGameEvent));
}
/**
* Accumulate data for a given time period
* @param TimePeriod - time period slot to use (0 - game total, 1+ round total)
* @param Value - value to accumulate
*/
void AddEventData(INT TimePeriod, FLOAT Value)
{
if (TimePeriod >= 0 && TimePeriod < 100) //sanity check
{
if (!EventCountByTimePeriod.IsValidIndex(TimePeriod))
{
EventCountByTimePeriod.AddZeroed(TimePeriod - EventCountByTimePeriod.Num() + 1);
}
check(EventCountByTimePeriod.IsValidIndex(TimePeriod));
EventCountByTimePeriod(TimePeriod) += Value;
}
else
{
debugf(TEXT("AddEventData: Timeperiod %d way out of range."), TimePeriod);
}
}
/*
* Get accumulated data for a given time period
* @param TimePeriod - time period slot to get (0 - game total, 1+ round total)
*/
FLOAT GetEventData(INT TimePeriod) const
{
if (EventCountByTimePeriod.IsValidIndex(TimePeriod))
{
return EventCountByTimePeriod(TimePeriod);
}
else
{
return 0.0f;
}
}
}
};
/** Container for game event stats stored by event ID */
struct native GameEvents
{
var const private native transient Map_Mirror Events{TMap<INT, FGameEvent>};
structcpptext
{
FGameEvents()
{}
/*
* Accumulate an event's data
* @param EventID - the event to record
* @param Value - the events recorded value
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddEvent(INT EventID, FLOAT Value, INT TimePeriod);
/** @return Number of events in the list */
INT Num() const
{
return Events.Num();
}
/** Clear out the contents */
void ClearEvents()
{
Events.Empty();
}
}
};
/** Base class for event storage */
struct native EventsBase
{
var GameEvents TotalEvents;
var array<GameEvents> EventsByClass;
structcpptext
{
FEventsBase()
{}
/** Clear out the contents */
void ClearEvents()
{
TotalEvents.ClearEvents();
for (INT i=0; i<EventsByClass.Num(); i++)
{
EventsByClass(i).ClearEvents();
}
EventsByClass.Empty();
}
}
};
/**
* Container for all weapon events
* Stores totals across all weapons plus individually by recorded weapon class metadata
*/
struct native WeaponEvents extends EventsBase
{
structcpptext
{
FWeaponEvents()
{}
/*
* Accumulate a weapon event's data
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddWeaponIntEvent(INT EventID, struct FWeaponIntEvent* GameEventData, INT TimePeriod);
}
};
/**
* Container for all projectile events
* Stores totals across all projectiles plus individually by recorded projectile class metadata
*/
struct native ProjectileEvents extends EventsBase
{
structcpptext
{
FProjectileEvents()
{}
/*
* Accumulate a projectile event's data
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddProjectileIntEvent(INT EventID, struct FProjectileIntEvent* GameEventData, INT TimePeriod);
}
};
/**
* Container for all damage events
* Stores totals across all damage plus individually by recorded damage class metadata
*/
struct native DamageEvents extends EventsBase
{
structcpptext
{
FDamageEvents()
{}
/*
* Accumulate a kill event for a given damage type
* @param EventID - the event to record
* @param KillTypeID - the ID of the kill type recorded
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddKillEvent(INT EventID, INT KillTypeID, struct FPlayerKillDeathEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a death event for a given damage type
* @param EventID - the event to record
* @param KillTypeID - the ID of the kill type recorded
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddDeathEvent(INT EventID, INT KillTypeID, struct FPlayerKillDeathEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a damage event for a given damage type
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddDamageIntEvent(INT EventID, struct FDamageIntEvent* GameEventData, INT TimePeriod);
}
};
/**
* Container for all pawn events
* Stores totals across all pawn plus individually by recorded pawn class metadata
*/
struct native PawnEvents extends EventsBase
{
structcpptext
{
FPawnEvents()
{}
/*
* Accumulate a pawn event for a given pawn type
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddPlayerSpawnEvent(INT EventID, struct FPlayerSpawnEvent* GameEventData, INT TimePeriod);
}
};
/**
* Container for all team events
* Stores totals across a single team plus all sub container types
*/
struct native TeamEvents
{
var GameEvents TotalEvents;
var WeaponEvents WeaponEvents;
var DamageEvents DamageAsPlayerEvents;
var DamageEvents DamageAsTargetEvents;
var ProjectileEvents ProjectileEvents;
var PawnEvents PawnEvents;
structcpptext
{
FTeamEvents()
{}
/**
* Accumulate data for a generic event
* @param EventID - the event to record
* @param TimePeriod - time period slot to use (0 - game total, 1+ round total)
* @param Value - value to accumulate
*/
void AddEvent(INT EventID, FLOAT Value, INT TimePeriod);
/*
* Accumulate a kill event for a given damage type
* @param EventID - the event to record
* @param KillTypeID - the ID of the kill type recorded
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddKillEvent(INT EventID, INT KillTypeID, struct FPlayerKillDeathEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a death event for a given damage type
* @param EventID - the event to record
* @param KillTypeID - the ID of the kill type recorded
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddDeathEvent(INT EventID, INT KillTypeID, struct FPlayerKillDeathEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a weapon event's data
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddWeaponIntEvent(INT EventID, struct FWeaponIntEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a damage event for a given damage type where the team member was the attacker
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddDamageDoneIntEvent(INT EventID, struct FDamageIntEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a damage event for a given damage type where the team member was the target
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddDamageTakenIntEvent(INT EventID, struct FDamageIntEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a pawn event for a given pawn type
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddPlayerSpawnEvent(INT EventID, struct FPlayerSpawnEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a projectile event's data
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddProjectileIntEvent(INT EventID, struct FProjectileIntEvent* GameEventData, INT TimePeriod);
/** Clear out the contents */
void ClearEvents()
{
TotalEvents.ClearEvents();
WeaponEvents.ClearEvents();
DamageAsPlayerEvents.ClearEvents();
DamageAsTargetEvents.ClearEvents();
ProjectileEvents.ClearEvents();
PawnEvents.ClearEvents();
}
}
};
/**
* Container for all player events
* Stores totals across a single player plus all sub container types
*/
struct native PlayerEvents
{
var GameEvents TotalEvents;
var WeaponEvents WeaponEvents;
var DamageEvents DamageAsPlayerEvents;
var DamageEvents DamageAsTargetEvents;
var ProjectileEvents ProjectileEvents;
var PawnEvents PawnEvents;
structcpptext
{
FPlayerEvents()
{}
/**
* Accumulate data for a generic event
* @param EventID - the event to record
* @param TimePeriod - time period slot to use (0 - game total, 1+ round total)
* @param Value - value to accumulate
*/
void AddEvent(INT EventID, FLOAT Value, INT TimePeriod);
/*
* Accumulate a kill event for a given damage type
* @param EventID - the event to record
* @param KillTypeID - the ID of the kill type recorded
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddKillEvent(INT EventID, INT KillTypeID, struct FPlayerKillDeathEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a death event for a given damage type
* @param EventID - the event to record
* @param KillTypeID - the ID of the kill type recorded
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddDeathEvent(INT EventID, INT KillTypeID, struct FPlayerKillDeathEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a weapon event's data
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddWeaponIntEvent(INT EventID, struct FWeaponIntEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a damage event for a given damage type where the player was the attacker
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddDamageDoneIntEvent(INT EventID, struct FDamageIntEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a damage event for a given damage type where the player was the target
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddDamageTakenIntEvent(INT EventID, struct FDamageIntEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a pawn event for a given pawn type
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddPlayerSpawnEvent(INT EventID, struct FPlayerSpawnEvent* GameEventData, INT TimePeriod);
/*
* Accumulate a projectile event's data
* @param EventID - the event to record
* @param GameEventData - the event data
* @param TimePeriod - a given time period (0 - game total, 1+ round total)
*/
void AddProjectileIntEvent(INT EventID, struct FProjectileIntEvent* GameEventData, INT TimePeriod);
/** Clear out the contents */
void ClearEvents()
{
TotalEvents.ClearEvents();
WeaponEvents.ClearEvents();
DamageAsPlayerEvents.ClearEvents();
DamageAsTargetEvents.ClearEvents();
ProjectileEvents.ClearEvents();
PawnEvents.ClearEvents();
}
}
};
struct native AggregateEventMapping
{
/** Recorded event ID */
var int EventID;
/** Mapping to the main aggregate event */
var int AggregateID;
/** Mapping to the aggregate event for the target if applicable*/
var int TargetAggregateID;
};
/** Array of all aggregates that require mappings when making an aggregate run */
var array<AggregateEventMapping> AggregatesList;
/** Mapping of event ID to its aggregate equivalents (created at runtime) */
var const private native transient Map_Mirror AggregateEventsMapping{TMap<INT, struct FAggregateEventMapping>};
/** The set of aggregate events that the aggregation supports */
var array<GameplayEventMetaData> AggregateEvents;
/** All aggregates generated this match */
var const array<int> AggregatesFound;
/** Aggregates of all recorded events */
var GameEvents AllGameEvents;
/** Aggregates of all recorded team events */
var const array<TeamEvents> AllTeamEvents;
/** Aggregates of all recorded player events */
var const array<PlayerEvents> AllPlayerEvents;
/** Aggregates of all recorded weapon events */
var const WeaponEvents AllWeaponEvents;
/** Aggregates of all recorded projectile events */
var const ProjectileEvents AllProjectileEvents;
/** Aggregates of all recorded pawn events */
var const PawnEvents AllPawnEvents;
/** Aggregates of all recorded damage events */
var const DamageEvents AllDamageEvents;
cpptext
{
/*
* Set the game state this aggregator will use
* @param InGameState - game state object to use
*/
virtual void SetGameState(class UGameStateObject* InGameState);
/*
* GameStatsFileReader Interface (handles parsing of the data stream)
*/
// Game Event Handling
virtual void HandleGameStringEvent(struct FGameEventHeader& GameEvent, struct FGameStringEvent* GameEventData);
virtual void HandleGameIntEvent(struct FGameEventHeader& GameEvent, struct FGameIntEvent* GameEventData);
virtual void HandleGameFloatEvent(struct FGameEventHeader& GameEvent, struct FGameFloatEvent* GameEventData);
virtual void HandleGamePositionEvent(struct FGameEventHeader& GameEvent, struct FGamePositionEvent* GameEventData);
// Team Event Handling
virtual void HandleTeamStringEvent(struct FGameEventHeader& GameEvent, struct FTeamStringEvent* GameEventData);
virtual void HandleTeamIntEvent(struct FGameEventHeader& GameEvent, struct FTeamIntEvent* GameEventData);
virtual void HandleTeamFloatEvent(struct FGameEventHeader& GameEvent, struct FTeamFloatEvent* GameEventData);
// Player Event Handling
virtual void HandlePlayerIntEvent(struct FGameEventHeader& GameEvent, struct FPlayerIntEvent* GameEventData);
virtual void HandlePlayerFloatEvent(struct FGameEventHeader& GameEvent, struct FPlayerFloatEvent* GameEventData);
virtual void HandlePlayerStringEvent(struct FGameEventHeader& GameEvent, struct FPlayerStringEvent* GameEventData);
virtual void HandlePlayerSpawnEvent(struct FGameEventHeader& GameEvent, struct FPlayerSpawnEvent* GameEventData);
virtual void HandlePlayerLoginEvent(struct FGameEventHeader& GameEvent, struct FPlayerLoginEvent* GameEventData);
virtual void HandlePlayerKillDeathEvent(struct FGameEventHeader& GameEvent, struct FPlayerKillDeathEvent* GameEventData);
virtual void HandlePlayerPlayerEvent(struct FGameEventHeader& GameEvent, struct FPlayerPlayerEvent* GameEventData);
virtual void HandlePlayerLocationsEvent(struct FGameEventHeader& GameEvent, struct FPlayerLocationsEvent* GameEventData);
virtual void HandleWeaponIntEvent(struct FGameEventHeader& GameEvent, struct FWeaponIntEvent* GameEventData);
virtual void HandleDamageIntEvent(struct FGameEventHeader& GameEvent, struct FDamageIntEvent* GameEventData);
virtual void HandleProjectileIntEvent(struct FGameEventHeader& GameEvent, struct FProjectileIntEvent* GameEventData);
/**
* Cleanup for a given player at the end of a round
* @param PlayerIndex - player to cleanup/record stats for
*/
virtual void AddPlayerEndOfRoundStats(INT PlayerIndex);
/** Triggered by the end of round event, adds any additional aggregate stats required */
virtual void AddEndOfRoundStats();
/** Triggered by the end of match event, adds any additional aggregate stats required */
virtual void AddEndOfMatchStats();
/** Returns the metadata associated with the given index, overloaded to access aggregate events not found in the stream directly */
virtual const FGameplayEventMetaData& GetEventMetaData(INT EventID) const;
/**
* Get the team event container for the given team
* @param TeamIndex - team of interest (-1/255 are considered same team)
*/
FTeamEvents& GetTeamEvents(INT TeamIndex) { if (TeamIndex >=0 && TeamIndex < 255) { return AllTeamEvents(TeamIndex); } else { return AllTeamEvents(AllTeamEvents.Num() - 1); } }
/**
* Get the player event container for the given player
* @param PlayerIndex - player of interest (-1 is valid and returns container for "invalid player")
*/
FPlayerEvents& GetPlayerEvents(INT PlayerIndex) { if (PlayerIndex >=0) { return AllPlayerEvents(PlayerIndex); } else { return AllPlayerEvents(AllPlayerEvents.Num() - 1); } }
/*
* Call Reset on destroy to make sure all native structs are cleaned up
*/
virtual void BeginDestroy()
{
Reset();
Super::BeginDestroy();
}
};
/** A chance to do something before the stream starts */
native event PreProcessStream();
/** A chance to do something after the stream ends */
native event PostProcessStream();
/** Cleanup/reset all data related to this aggregation */
native function Reset();
/*
* Get the mapping from an event ID to its equivalent aggregate IDs
* @param EventID - EventID to map
* @param AggregateID - Aggregate ID (main/player ID)
* @param TargetAggregateID - AggregateID that applies to the target (if applicable)
* @return TRUE if mapping found, FALSE otherwise
*/
native function bool GetAggregateMappingIDs(int EventID, out int AggregateID, out int TargetAggregateID);
defaultproperties
{
// Additional aggregate events added to the output as the game stats stream is parsed
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_PLAYER_TIMEALIVE,EventName="Player Time Alive",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_PlayerAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_PLAYER_KILLS,EventName="Kills",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_PlayerAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_PLAYER_DEATHS,EventName="Deaths",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_PlayerAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_DEALT_NORMALKILL,EventName="Normal Kill",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_PlayerAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_WASNORMALKILL,EventName="Was Normal Kill",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_PlayerAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_PLAYER_MATCH_WON,EventName="Match Won",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_PlayerAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_PLAYER_ROUND_WON,EventName="Round Won",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_PlayerAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_TEAM_KILLS,EventName="Kills",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_TeamAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_TEAM_DEATHS,EventName="Deaths",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_TeamAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_TEAM_GAME_SCORE,EventName="Team Score",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_TeamAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_TEAM_MATCH_WON,EventName="Matches Won",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_TeamAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_TEAM_ROUND_WON,EventName="Rounds Won",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_TeamAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_KILLS,EventName="Kills",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_DamageAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_DEATHS,EventName="Deaths",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_DamageAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_DEALT_WEAPON_DAMAGE,EventName="Weapon Damage Dealt",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_DamageAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_DEALT_MELEE_DAMAGE,EventName="Melee Damage Dealt",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_DamageAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_WEAPON_DAMAGE,EventName="Weapon Damage Received",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_DamageAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_MELEE_DAMAGE,EventName="Melee Damage Received",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_DamageAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_DEALT_MELEEHITS,EventName="Melee Hits",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_DamageAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_WASMELEEHIT,EventName="Was Melee Hit",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_DamageAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_WEAPON_FIRED,EventName="Weapon Fired",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_WeaponAggregate))
AggregateEvents.Add((EventID=GAMEEVENT_AGGREGATED_PAWN_SPAWN,EventName="Spawns",StatGroup=(Group=GSG_Aggregate,Level=1),EventDataType=`GET_PawnAggregate))
// Mapping from stream stat ID to aggregate stat ID (kill/death handle special)
AggregatesList.Add((EventID=GAMEEVENT_PLAYER_MATCH_WON,AggregateID=GAMEEVENT_AGGREGATED_PLAYER_MATCH_WON))
AggregatesList.Add((EventID=GAMEEVENT_PLAYER_ROUND_WON,AggregateID=GAMEEVENT_AGGREGATED_PLAYER_ROUND_WON))
AggregatesList.Add((EventID=GAMEEVENT_TEAM_GAME_SCORE,AggregateID=GAMEEVENT_AGGREGATED_TEAM_GAME_SCORE))
AggregatesList.Add((EventID=GAMEEVENT_TEAM_MATCH_WON,AggregateID=GAMEEVENT_AGGREGATED_TEAM_MATCH_WON))
AggregatesList.Add((EventID=GAMEEVENT_TEAM_ROUND_WON,AggregateID=GAMEEVENT_AGGREGATED_TEAM_ROUND_WON))
AggregatesList.Add((EventID=GAMEEVENT_WEAPON_DAMAGE,AggregateID=GAMEEVENT_AGGREGATED_DAMAGE_DEALT_WEAPON_DAMAGE,TargetAggregateID=GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_WEAPON_DAMAGE))
AggregatesList.Add((EventID=GAMEEVENT_WEAPON_DAMAGE_MELEE,AggregateID=GAMEEVENT_AGGREGATED_DAMAGE_DEALT_MELEE_DAMAGE,TargetAggregateID=GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_MELEE_DAMAGE))
AggregatesList.Add((EventID=GAMEEVENT_WEAPON_FIRED,AggregateID=GAMEEVENT_AGGREGATED_WEAPON_FIRED))
AggregatesList.Add((EventID=GAMEEVENT_PLAYER_SPAWN,AggregateID=GAMEEVENT_AGGREGATED_PAWN_SPAWN))
AggregatesList.Add((EventID=GAMEEVENT_PLAYER_KILL_NORMAL,AggregateID=GAMEEVENT_AGGREGATED_DAMAGE_DEALT_NORMALKILL,TargetAggregateID=GAMEEVENT_AGGREGATED_DAMAGE_RECEIVED_WASNORMALKILL))
}

View File

@ -0,0 +1,660 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameThirdPersonCamera extends GameCameraBase
config(Camera)
native(Camera);
/** obstruction pct from origin to worstloc origin */
var float WorstLocBlockedPct;
/** camera extent scale to use when calculating penetration for this segment */
var() float WorstLocPenetrationExtentScale;
/** Time to transition from blocked location to ideal position, after camera collision with geometry. */
var() float PenetrationBlendOutTime;
/** Time to transition from ideal location to blocked position, after camera collision with geometry. (used only by predictive feelers) */
var() float PenetrationBlendInTime;
/** Percentage of distance blocked by collision. From worst location, to desired location. */
var protected float PenetrationBlockedPct;
/** camera extent scale to use when calculating penetration for this segment */
var() float PenetrationExtentScale;
/**
* Last pawn relative offset, for slow offsets interpolation.
* This is because this offset is relative to the Pawn's rotation, which can change abruptly (when snapping to cover).
* Used to adjust the camera origin (evade, lean, pop up, blind fire, reload..)
*/
var transient vector LastActualOriginOffset;
/** Last actual camera origin position, for lazy cam interpolation. It's only applied to player's origin, not view offsets, for faster/smoother response */
var transient vector LastActualCameraOrigin;
/** Last actual camera origin rotation, for lazy cam interpolation. It's only applied to player's origin, not view offsets, for faster/smoother response */
var transient rotator LastActualCameraOriginRot;
/** origin offset interpolation speed */
var() float OriginOffsetInterpSpeed;
/** View relative offset. This offset is relative to Controller's rotation, mainly used for Pitch positioning. */
var transient vector LastViewOffset;
/** last CamFOV for war cam interpolation */
var transient float LastCamFOV;
/** Cached ideal pivot loc */
var transient vector LastIdealCameraOrigin;
/** Cached ideal pivot rot */
var transient rotator LastIdealCameraOriginRot;
/*********** CAMERA VARIABLES ***********/
/******* CAMERA MODES *******/
/** Base camera position when walking */
var() protected editinline GameThirdPersonCameraMode ThirdPersonCamDefault;
var() protected class<GameThirdPersonCameraMode> ThirdPersonCamDefaultClass;
//
//
// Player 'GearCam' camera mode system
//
/** Current GearCam Mode */
var() editinline transient GameThirdPersonCameraMode CurrentCamMode;
//
// Focus Point adjustment
//
/** last offset adjustment, for smooth blend out */
var transient float LastHeightAdjustment;
/** last adjusted pitch, for smooth blend out */
var transient float LastPitchAdjustment;
/** last adjusted Yaw, for smooth blend out */
var transient float LastYawAdjustment;
/** pitch adjustment when keeping target is done in 2 parts. this is the amount to pitch in part 2 (post view offset application) */
var transient float LeftoverPitchAdjustment;
/** move back pct based on move up */
var(Focus) float Focus_BackOffStrength;
/** Z offset step for every try */
var(Focus) float Focus_StepHeightAdjustment;
/** number of tries to have focus in view */
var(Focus) int Focus_MaxTries;
/** time it takes for fast interpolation speed to kick in */
var(Focus) float Focus_FastAdjustKickInTime;
/** Last time focus point changed (location) */
var transient protected float LastFocusChangeTime;
var transient protected vector ActualFocusPointWorldLoc;
/** Last focus point location */
var transient protected vector LastFocusPointLoc;
/** Camera focus point definition */
struct native CamFocusPointParams
{
/** Actor to focus on. */
var() Actor FocusActor;
/** Bone name to focus on. Ignored if FocusActor is None or has no SkeletalMeshComponent */
var() Name FocusBoneName;
/** Focus point location in world space. Ignored if FocusActor is not None. */
var() vector FocusWorldLoc;
/** If >0, FOV to force upon camera while looking at this point (degrees) */
var() float CameraFOV;
/** Interpolation speed (X=slow/focus loc moving, Y=fast/focus loc steady/blending out) */
var() vector2d InterpSpeedRange;
/** FOV where target is considered in focus, no correction is made. X is yaw tolerance, Y is pitch tolerance. */
var() vector2d InFocusFOV;
/** If FALSE, focus only if point roughly in view; if TRUE, focus no matter where player is looking */
var() bool bAlwaysFocus;
/** If TRUE, camera adjusts to keep player in view, if FALSE the camera remains fixed and just rotates in place */
var() bool bAdjustCamera;
/** If TRUE, ignore world trace to find a good spot */
var() bool bIgnoreTrace;
/** Offsets the pitch. e.g. 20 will look 20 degrees above the target */
var() float FocusPitchOffsetDeg;
};
/** current focus point */
var(Focus) CamFocusPointParams FocusPoint;
/** do we have a focus point set? */
var bool bFocusPointSet;
/** Internal. TRUE if the focus point was good and the camera looked at it, FALSE otherwise (e.g. failed the trace). */
var protected transient bool bFocusPointSuccessful;
/** Vars for code-driven camera turns */
var protected float TurnCurTime;
var bool bDoingACameraTurn;
var protected int TurnStartAngle;
var protected int TurnEndAngle;
var protected float TurnTotalTime;
var protected float TurnDelay;
var protected bool bTurnAlignTargetWhenFinished;
/** Saved data for camera turn "align when finished" functionality */
var protected transient int LastPostCamTurnYaw;
/** toggles debug mode */
var() bool bDrawDebug;
/** direct look vars */
var transient int DirectLookYaw;
var transient bool bDoingDirectLook;
var() float DirectLookInterpSpeed;
var() float WorstLocInterpSpeed;
var transient vector LastWorstLocationLocal;
var transient vector LastWorstLocation;
/** Last location and rotation of the camera, cached before camera modifiers are applied. */
var transient vector LastPreModifierCameraLoc;
var transient rotator LastPreModifierCameraRot;
/**
* Struct defining a feeler ray used for camera penetration avoidance.
*/
struct native PenetrationAvoidanceFeeler
{
/** rotator describing deviance from main ray */
var() Rotator AdjustmentRot;
/** how much this feeler affects the final position if it hits the world */
var() float WorldWeight;
/** how much this feeler affects the final position if it hits a Pawn (setting to 0 will not attempt to collide with pawns at all) */
var() float PawnWeight;
/** extent to use for collision when firing this ray */
var() vector Extent;
/** minimum frame interval between traces with this feeler if nothing was hit last frame */
var() int TraceInterval;
/** number of frames since this feeler was used */
var transient int FramesUntilNextTrace;
};
var() array<PenetrationAvoidanceFeeler> PenetrationAvoidanceFeelers;
/** Offset adjustment from last tick, used for interpolation. */
var protectedwrite transient vector LastOffsetAdjustment;
/** Change in camera mode happened this frame - reset on first call to PlayerUpdateCamera */
var(Debug) bool bDebugChangedCameraMode;
/** Set to TRUE when the Pivot makes a big jump if you want to keep the camera in place across that transition. */
var transient bool bDoSeamlessPivotTransition;
cpptext
{
protected:
/**
* Interpolates from previous location/rotation toward desired location/rotation
*/
virtual void InterpolateCameraOrigin( class APawn* TargetPawn, FLOAT DeltaTime, FVector& out_ActualCameraOrigin, FVector const& IdealCameraOrigin, FRotator& out_ActualCameraOriginRot, FRotator const& IdealCameraOriginRot );
/** Returns the focus location, adjusted to compensate for the third-person camera offset. */
FVector GetEffectiveFocusLoc(const FVector& CamLoc, const FVector& FocusLoc, const FVector& ViewOffset);
void AdjustToFocusPointKeepingTargetInView(class APawn* P, FLOAT DeltaTime, FVector& CamLoc, FRotator& CamRot, const FVector& ViewOffset);
void AdjustToFocusPoint(class APawn* P, FLOAT DeltaTime, FVector& CamLoc, FRotator& CamRot);
void PreventCameraPenetration(class APawn* P, class AGamePlayerCamera* CameraActor, const FVector& WorstLocation, FVector& DesiredLocation, FLOAT DeltaTime, FLOAT& DistBlockedPct, FLOAT CameraExtentScale, UBOOL bSingleRayOnly=FALSE);
void UpdateForMovingBase(class AActor* BaseActor);
/** Returns desired camera origin offset that should be applied AFTER the interpolation, if any. */
virtual FVector GetPostInterpCameraOriginLocationOffset(APawn* TargetPawn) const { return FVector::ZeroVector; }
virtual FRotator GetPostInterpCameraOriginRotationOffset(APawn* TargetPawn) const { return FRotator::ZeroRotator; }
virtual FMatrix GetWorstCaseLocTransform(APawn* P) const;
virtual UBOOL ShouldIgnorePenetrationHit(FCheckResult const* Hit, APawn* TargetPawn) const;
virtual UBOOL ShouldDoPerPolyPenetrationTests(APawn* TargetPawn) const { return FALSE; };
virtual UBOOL ShouldDoPredictavePenetrationAvoidance(APawn* TargetPawn) const;
virtual void HandlePawnPenetration(FTViewTarget& OutVT);
virtual UBOOL HandleCameraSafeZone( FVector& CameraOrigin, FRotator& CameraRotation, FLOAT DeltaTime ) { return FALSE; }
public:
};
/** Internal. */
protected function GameThirdPersonCameraMode CreateCameraMode(class<GameThirdPersonCameraMode> ModeClass)
{
local GameThirdPersonCameraMode NewMode;
NewMode = new(self) ModeClass;
NewMode.ThirdPersonCam = self;
NewMode.Init();
return NewMode;
}
// reset the camera to a good state
function Reset()
{
bResetCameraInterpolation = TRUE;
}
function Init()
{
// Setup camera modes
if (ThirdPersonCamDefault == None)
{
ThirdPersonCamDefault = CreateCameraMode(ThirdPersonCamDefaultClass);
}
}
/** returns camera mode desired FOV */
event float GetDesiredFOV( Pawn ViewedPawn )
{
if ( bFocusPointSet && (FocusPoint.CameraFOV > 0.f) && bFocusPointSuccessful )
{
return FocusPoint.CameraFOV;
}
return CurrentCamMode.GetDesiredFOV(ViewedPawn);
}
/**
* Player Update Camera code
*/
function UpdateCamera(Pawn P, GamePlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT)
{
if( P == None && OutVT.Target != None )
{
OutVT.Target.GetActorEyesViewPoint( OutVT.POV.Location, OutVT.POV.Rotation );
PlayerCamera.ApplyCameraModifiers(DeltaTime, OutVT.POV);
}
// give pawn chance to hijack the camera and do it's own thing.
else if( (P != None) && P.CalcCamera(DeltaTime, OutVT.POV.Location, OutVT.POV.Rotation, OutVT.POV.FOV) )
{
//@fixme, move this call up into GearPlayerCamera??? look into it.
PlayerCamera.ApplyCameraModifiers(DeltaTime, OutVT.POV);
return;
}
else
{
UpdateCameraMode(P);
if( CurrentCamMode != None )
{
PlayerUpdateCamera(P,CameraActor, DeltaTime, OutVT);
CurrentCamMode.UpdatePostProcess(OutVT, DeltaTime);
}
else
{
`warn(GetFuncName() @ "CameraMode == None!!!");
}
// prints local space camera offset (from pawnloc). useful for determining camera anim test offsets
//`log("***"@((OutVT.POV.Location - P.Location) << P.Controller.Rotation));
}
// if we had to reset camera interpolation, then turn off flag once it's been processed.
bResetCameraInterpolation = FALSE;
}
/** Internal camera updating code */
native protected function PlayerUpdateCamera(Pawn P, GamePlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT);
/******************************
* Camera turns
******************************/
/**
* Initiates a forced camera rotation.
* @param StartAngle Starting Yaw offset (in Rotator units)
* @param EndAngle Finishing Yaw offset (in Rotator units)
* @param TimeSec How long the rotation should take
* @param DelaySec How long to wait before starting the rotation
*/
function BeginTurn(int StartAngle, int EndAngle, float TimeSec, optional float DelaySec, optional bool bAlignTargetWhenFinished)
{
// We're not updating the camera on the server, bDoingACameraTurn will prevent firing
if (PlayerCamera.bUseClientSideCameraUpdates && !PlayerCamera.PCOwner.IsLocalPlayerController())
{
return;
}
bDoingACameraTurn = TRUE;
TurnTotalTime = TimeSec;
TurnDelay = DelaySec;
TurnCurTime = 0.f;
TurnStartAngle = StartAngle;
TurnEndAngle = EndAngle;
bTurnAlignTargetWhenFinished = bAlignTargetWhenFinished;
}
/**
* Stops a camera rotation.
*/
native function EndTurn();
/**
* Adjusts a camera rotation. Useful for situations where the basis of the rotation
* changes.
* @param AngleOffset Yaw adjustment to apply (in Rotator units)
*/
function AdjustTurn(int AngleOffset)
{
TurnStartAngle += AngleOffset;
TurnEndAngle += AngleOffset;
}
/******************************
* Focus Point functionality
******************************/
/** Tells camera to focus on the given world position. */
function SetFocusOnLoc
(
vector FocusWorldLoc,
Vector2d InterpSpeedRange,
Vector2d InFocusFOV,
optional float CameraFOV,
optional bool bAlwaysFocus,
optional bool bAdjustCamera,
optional bool bIgnoreTrace,
optional float FocusPitchOffsetDeg
)
{
// if replacing a bAdjustCamera focus point with a !bAdjustCamera focus point,
// do a clear first so the !bAdjustCamera one will work relative to where the first
// one was at the time of the interruption
if ( ((LastPitchAdjustment != 0) || (LastYawAdjustment != 0))
&& !bAdjustCamera
&& FocusPoint.bAdjustCamera )
{
ClearFocusPoint(TRUE);
}
FocusPoint.FocusWorldLoc = FocusWorldLoc;
FocusPoint.FocusActor = None;
FocusPoint.FocusBoneName = '';
FocusPoint.InterpSpeedRange = InterpSpeedRange;
FocusPoint.InFocusFOV = InFocusFOV;
FocusPoint.CameraFOV = CameraFOV;
FocusPoint.bAlwaysFocus = bAlwaysFocus;
FocusPoint.bAdjustCamera = bAdjustCamera;
FocusPoint.bIgnoreTrace = bIgnoreTrace;
FocusPoint.FocusPitchOffsetDeg = FocusPitchOffsetDeg;
bFocusPointSet = TRUE;
LastFocusChangeTime = PlayerCamera.WorldInfo.TimeSeconds;
LastFocusPointLoc = GetActualFocusLocation();
bFocusPointSuccessful = FALSE;
}
/** Tells camera to focus on the given actor. */
function SetFocusOnActor
(
Actor FocusActor,
Name FocusBoneName,
Vector2d InterpSpeedRange,
Vector2d InFocusFOV,
optional float CameraFOV,
optional bool bAlwaysFocus,
optional bool bAdjustCamera,
optional bool bIgnoreTrace,
optional float FocusPitchOffsetDeg
)
{
// if replacing a bAdjustCamera focus point with a !bAdjustCamera focus point,
// do a clear first so the !bAdjustCamera one will work relative to where the first
// one was at the time of the interruption
if ( ((LastPitchAdjustment != 0) || (LastYawAdjustment != 0))
&& !bAdjustCamera
&& FocusPoint.bAdjustCamera )
{
ClearFocusPoint(TRUE);
}
FocusPoint.FocusActor = FocusActor;
FocusPoint.FocusBoneName = FocusBoneName;
FocusPoint.InterpSpeedRange = InterpSpeedRange;
FocusPoint.InFocusFOV = InFocusFOV;
FocusPoint.CameraFOV = CameraFOV;
FocusPoint.bAlwaysFocus = bAlwaysFocus;
FocusPoint.bAdjustCamera = bAdjustCamera;
FocusPoint.bIgnoreTrace = bIgnoreTrace;
FocusPoint.FocusPitchOffsetDeg = FocusPitchOffsetDeg;
bFocusPointSet = TRUE;
LastFocusChangeTime = PlayerCamera.WorldInfo.TimeSeconds;
LastFocusPointLoc = GetActualFocusLocation();
bFocusPointSuccessful = FALSE;
}
/**
* Returns ref to the actor currently being used as a focus point, if any.
*/
function Actor GetFocusActor()
{
return bFocusPointSet ? FocusPoint.FocusActor : None;
}
/** Clear focus point */
function ClearFocusPoint(optional bool bLeaveCameraRotation)
{
bFocusPointSet = FALSE;
// note that bAdjustCamera must be true to leave camera rotation.
// otherwise, there will be a large camera jolt as the player and camera
// realign themselves (which they have to do, since the camera rotated away from
// the player)
if ( bLeaveCameraRotation && FocusPoint.bAdjustCamera )
{
LastPitchAdjustment = 0;
LastYawAdjustment = 0;
LeftoverPitchAdjustment = 0;
if (PlayerCamera.PCOwner != None)
{
PlayerCamera.PCOwner.SetRotation(LastPreModifierCameraRot);
}
}
}
/**
* Per-tick focus point processing, for polling gamestate and adjusting as desired.
* Override if you want other systems or criteria to set focus points.
*/
protected event UpdateFocusPoint( Pawn P )
{
if (bDoingACameraTurn)
{
// no POIs during camera turns
ClearFocusPoint();
}
// give the camera mode a crack at it
else if ( (CurrentCamMode == None) || (CurrentCamMode.SetFocusPoint(P) == FALSE) )
{
// Otherwise, clear focus point
ClearFocusPoint();
}
if (bFocusPointSet)
{
// store old focus loc
LastFocusPointLoc = ActualFocusPointWorldLoc;
ActualFocusPointWorldLoc = GetActualFocusLocation();
}
}
/** Internal. Returns the world space position of the current focus point. */
protected function vector GetActualFocusLocation()
{
local vector FocusLoc;
local SkeletalMeshComponent ComponentIt;
if (FocusPoint.FocusActor != None)
{
// actor's loc by default
FocusLoc = FocusPoint.FocusActor.Location;
// ... but use bone loc if possible
if (FocusPoint.FocusBoneName != '')
{
foreach FocusPoint.FocusActor.ComponentList(class'SkeletalMeshComponent',ComponentIt)
{
//`log( ComponentIt.Owner @ `showvar(ComponentIt) @ ComponentIt.SkeletalMesh );
// if the bone we are looking for exists use that otherwise keep looking
// as there could be multiple SkelComps on this FocusActor
if( ComponentIt.MatchRefBone(FocusPoint.FocusBoneName) != INDEX_NONE )
{
FocusLoc = ComponentIt.GetBoneLocation(FocusPoint.FocusBoneName);
break;
}
}
}
}
else
{
// focused world location, just use that
FocusLoc = FocusPoint.FocusWorldLoc;
}
return FocusLoc;
}
/**
* Use this if you keep the same focus point, but move the camera basis around underneath it
* e.g. you want the camera to hold steady focus, but the camera target is rotating
*/
function AdjustFocusPointInterpolation(rotator Delta)
{
if (bFocusPointSet && FocusPoint.bAdjustCamera)
{
Delta = Normalize(Delta);
LastYawAdjustment -= Delta.Yaw;
LastPitchAdjustment -= Delta.Pitch;
}
}
/**
* Evaluates the game state and returns the proper camera mode.
*
* @return new camera mode to use
*/
function GameThirdPersonCameraMode FindBestCameraMode(Pawn P)
{
if (P != None)
{
// Just stick to default here, games should override this with appropriate modes if desired.
return ThirdPersonCamDefault;
}
return None;
}
/**
* Update current camera modes. Pick Best, handle transitions, etc.
*/
final protected function UpdateCameraMode(Pawn P)
{
local GameThirdPersonCameraMode NewCamMode;
// Pick most suitable camera mode
NewCamMode = FindBestCameraMode(P);
if ( NewCamMode != CurrentCamMode )
{
// handle mode change
if( CurrentCamMode != None )
{
CurrentCamMode.OnBecomeInActive(P, NewCamMode);
}
if( NewCamMode != None )
{
NewCamMode.OnBecomeActive(P, CurrentCamMode);
}
`if(`notdefined(FINAL_RELEASE))
bDebugChangedCameraMode = TRUE;
//`log( "Camera Mode Changed"@`showvar(CurrentCamMode)@`showvar(NewCamMode), FALSE );
`endif
CurrentCamMode = NewCamMode;
}
}
/**
* Gives cameras a chance to change player view rotation
*/
function ProcessViewRotation( float DeltaTime, Actor ViewTarget, out Rotator out_ViewRotation, out Rotator out_DeltaRot )
{
// see if camera mode wants to manipulate the view rotation
if( CurrentCamMode != None )
{
CurrentCamMode.ProcessViewRotation(DeltaTime, ViewTarget, out_ViewRotation, out_DeltaRot);
}
}
/** Called when Camera mode becomes active */
function OnBecomeActive( GameCameraBase OldCamera )
{
if (!PlayerCamera.bInterpolateCamChanges)
{
Reset();
}
super.OnBecomeActive( OldCamera );
}
event ModifyPostProcessSettings(out PostProcessSettings PP)
{
if( CurrentCamMode != none )
{
CurrentCamMode.ModifyPostProcessSettings(PP);
}
}
function ResetInterpolation()
{
Super.ResetInterpolation();
LastHeightAdjustment = 0.f;
LastYawAdjustment = 0.f;
LastPitchAdjustment = 0.f;
LeftoverPitchAdjustment = 0.f;
}
defaultproperties
{
PenetrationBlendOutTime=0.15f
PenetrationBlendInTime=0.1f
PenetrationBlockedPct=1.f
PenetrationExtentScale=1.f
WorstLocPenetrationExtentScale=1.f
WorstLocInterpSpeed=8
bResetCameraInterpolation=TRUE // set to true by default, so first frame is never interpolated
OriginOffsetInterpSpeed=8
Focus_BackOffStrength=0.33f
Focus_StepHeightAdjustment= 64
Focus_MaxTries=4
Focus_FastAdjustKickInTime=0.5
bDoingACameraTurn=FALSE
DirectLookInterpSpeed=6.f
// ray 0 is the main ray
PenetrationAvoidanceFeelers(0)=(AdjustmentRot=(Pitch=0,Yaw=0,Roll=0),WorldWeight=1.f,PawnWeight=1.f,Extent=(X=14,Y=14,Z=14))
// horizontally offset
PenetrationAvoidanceFeelers(1)=(AdjustmentRot=(Pitch=0,Yaw=3072,Roll=0),WorldWeight=0.75f,PawnWeight=0.75f,Extent=(X=0,Y=0,Z=0), TraceInterval=3)
PenetrationAvoidanceFeelers(2)=(AdjustmentRot=(Pitch=0,Yaw=-3072,Roll=0),WorldWeight=0.75f,PawnWeight=0.75f,Extent=(X=0,Y=0,Z=0), TraceInterval=3)
PenetrationAvoidanceFeelers(3)=(AdjustmentRot=(Pitch=0,Yaw=6144,Roll=0),WorldWeight=0.5f,PawnWeight=0.5f,Extent=(X=0,Y=0,Z=0), TraceInterval=5)
PenetrationAvoidanceFeelers(4)=(AdjustmentRot=(Pitch=0,Yaw=-6144,Roll=0),WorldWeight=0.5f,PawnWeight=0.5f,Extent=(X=0,Y=0,Z=0), TraceInterval=5)
// vertically offset
PenetrationAvoidanceFeelers(5)=(AdjustmentRot=(Pitch=3640,Yaw=0,Roll=0),WorldWeight=1.f,PawnWeight=1.f,Extent=(X=0,Y=0,Z=0), TraceInterval=4)
PenetrationAvoidanceFeelers(6)=(AdjustmentRot=(Pitch=-3640,Yaw=0,Roll=0),WorldWeight=0.5f,PawnWeight=0.5f,Extent=(X=0,Y=0,Z=0), TraceInterval=4)
ThirdPersonCamDefaultClass=class'GameThirdPersonCameraMode_Default'
}

View File

@ -0,0 +1,476 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameThirdPersonCameraMode extends Object
config(Camera)
native(Camera);
/**
* Contains all the information needed to define a camera
* mode.
*/
/** Ref to the camera object that owns this mode object. */
var transient GameThirdPersonCamera ThirdPersonCam;
/** FOV for camera to use */
var() const config float FOVAngle;
/** Blend Time to and from this view mode */
var() float BlendTime;
/**
* True if, while in this mode, the camera should be tied to the viewtarget rotation.
* This is typical for the normal walking-around camera, since the controls rotate the controller
* and the camera follows. This can be false if you want free control of the camera, independent
* of the viewtarget's orient -- we use this for vehicles. Note that if this is false,
*/
var() const protected bool bLockedToViewTarget;
/**
* True if, while in this mode, looking around should be directly mapped to stick position
* as opposed to relative to previous camera positions.
*/
var() const protected bool bDirectLook;
/**
* True if, while in this mode, the camera should interpolate towards a following position
* in relation to the target and it's motion. Ignored if bLockedToViewTarget is set to true.
*/
var() const protected bool bFollowTarget;
/*
* How fast the camera should track to follow behind the viewtarget. 0.f for no following.
* Only used if bLockedToViewTarget is FALSE
*/
var() const protected float FollowingInterpSpeed_Pitch;
var() const protected float FollowingInterpSpeed_Yaw;
var() const protected float FollowingInterpSpeed_Roll;
/** Actual following interp speed gets scaled from FollowingInterpSpeed to zero between velocities of this value and zero. */
var() const protected float FollowingCameraVelThreshold;
/** True means camera will attempt to smoothly interpolate to its new position. False will snap it to it's new position. */
var() bool bInterpLocation;
/** Controls interpolation speed of location for camera origin. Ignored if bInterpLocation is false. */
var() protected float OriginLocInterpSpeed;
/**
* This is a special case of origin location interpolation. If true, interpolaton will be done on each axis independently, with the specified speeds.
* Ignored if bInterpLocation is false.
*/
var() protected bool bUsePerAxisOriginLocInterp;
var() protected vector PerAxisOriginLocInterpSpeed;
/** True means camera will attempt to smoothly interpolate to its new rotation. False will snap it to it's new rotation. */
var() bool bInterpRotation;
/** Controls interpolation speed of rotation for the camera origin. Ignored if bInterpRotation is false. */
var() protected float OriginRotInterpSpeed;
/** Whether rotation interpolation happens at constant speed or not */
var() bool bRotInterpSpeedConstant;
/** Adjustment vector to apply to camera view offset when target is strafing to the left */
var() const protected vector StrafeLeftAdjustment;
/** Adjustment vector to apply to camera view offset when target is strafing to the right */
var() const protected vector StrafeRightAdjustment;
/** Velocity at (and above) which the full adjustment should be applied. */
var() const protected float StrafeOffsetScalingThreshold;
/** Interpolation speed for interpolating to a NONZERO strafe offsets. Higher is faster/tighter interpolation. */
var() const protected float StrafeOffsetInterpSpeedIn;
/** Interpolation speed for interpolating to a ZERO strafe offset. Higher is faster/tighter interpolation. */
var() const protected float StrafeOffsetInterpSpeedOut;
/** Strafe offset last tick, used for interpolation. */
var protected transient vector LastStrafeOffset;
/** Adjustment vector to apply to camera view offset when target is moving forward */
var() const protected vector RunFwdAdjustment;
/** Adjustment vector to apply to camera view offset when target is moving backward */
var() const protected vector RunBackAdjustment;
/** Velocity at (and above) which the full adjustment should be applied. */
var() const protected float RunOffsetScalingThreshold;
/** Interpolation speed for interpolating to a NONZERO offset. Higher is faster/tighter interpolation. */
var() const protected float RunOffsetInterpSpeedIn;
/** Interpolation speed for interpolating to a ZERO offset. Higher is faster/tighter interpolation. */
var() const protected float RunOffsetInterpSpeedOut;
/** Run offset last tick, used for interpolation. */
var protected transient vector LastRunOffset;
/**
* An offset from the location of the viewtarget, in the viewtarget's local space.
* Used to calculate the "worst case" camera location, which is where the camera should retreat to
* if tightly obstructed.
*/
var() protected vector WorstLocOffset;
/** True to turn do predictive camera avoidance, false otherwise */
var() const bool bDoPredictiveAvoidance;
/** TRUE to do a raytrace from camera base loc to worst loc, just to be sure it's cool. False to skip it */
var() const bool bValidateWorstLoc;
/** If TRUE, all camera collision is disabled */
var() bool bSkipCameraCollision;
/** Offset, in the camera target's local space, from the camera target to the camera's origin. */
var() const protected vector TargetRelativeCameraOriginOffset;
/** Supported viewport configurations. */
enum ECameraViewportTypes
{
CVT_16to9_Full,
CVT_16to9_VertSplit,
CVT_16to9_HorizSplit,
CVT_4to3_Full,
CVT_4to3_HorizSplit,
CVT_4to3_VertSplit,
};
struct native ViewOffsetData
{
/** View point offset for high player view pitch */
var() vector OffsetHigh;
/** View point offset for medium (horizon) player view pitch */
var() vector OffsetMid;
/** View point offset for low player view pitch */
var() vector OffsetLow;
};
/** contains offsets from camera target to camera loc */
var() const protected ViewOffsetData ViewOffset;
/** TRUE to smooth the interp between high/mid/low, false to blend linearly between high <-> mid <-> low */
var() const protected bool bSmoothViewOffsetPitchChanges;
/** viewoffset adjustment vectors for each possible viewport type, so the game looks close to the same in each */
var() const protected ViewOffsetData ViewOffset_ViewportAdjustments[ECameraViewportTypes.EnumCount];
/** whether delta or actual view offset should be applied to the camera location */
var() bool bApplyDeltaViewOffset;
/** Optional parameters for DOF adjustments. */
var(DepthOfField) const protected bool bAdjustDOF;
var(DepthOfField) const protected float DOF_FalloffExponent;
var(DepthOfField) const protected float DOF_BlurKernelSize;
var(DepthOfField) const protected float DOF_FocusInnerRadius;
var(DepthOfField) const protected float DOF_MaxNearBlurAmount;
var(DepthOfField) const protected float DOF_MaxFarBlurAmount;
var transient protected bool bDOFUpdated;
var protected transient float LastDOFRadius;
var protected transient float LastDOFDistance;
var(DepthOfField) protected const float DOFDistanceInterpSpeed;
var(DepthOfField) protected const vector DOFTraceExtent;
/** Maps out how the DOF inner radius changes over distance. */
var(DepthOfField) protected const float DOF_RadiusFalloff;
var(DepthOfField) protected const vector2d DOF_RadiusRange;
var(DepthOfField) protected const vector2d DOF_RadiusDistRange;
/** Whether camera anims and other post processing should change the FOV */
var bool bNoFOVPostProcess;
/** If TRUE ViewOffset will only be interpolated between camera mode transitions, and then be instantaneous */
var() bool bInterpViewOffsetOnlyForCamTransition;
/** Keep track of our ViewOffset Interpolation factor */
var float ViewOffsetInterp;
/** We optionally interpolate the results of AdjustViewOffset() to prevent pops when a cameramode changes its adjustment suddenly. */
var() protected float OffsetAdjustmentInterpSpeed;
/** Keep track of the current viewport type as computed by GetViewOffset() */
var protected transient ECameraViewportTypes CurrentViewportType;
cpptext
{
// GearThirdPersonCameraMode interface
/**
* Calculates and returns the ideal view offset for the specified camera mode.
* The offset is relative to the Camera's pos/rot and calculated by interpolating
* 2 ideal view points based on the player view pitch.
*
* @param ViewedPawn Camera target pawn
* @param DeltaTime Delta time since last frame.
* @param ViewRotation Rot of the camera
*/
virtual FVector GetViewOffset(class APawn* ViewedPawn, FLOAT DeltaTime, const FVector& ViewOrigin, const FRotator& ViewRotation);
FLOAT GetViewOffsetInterpSpeed(class APawn* ViewedPawn, FLOAT DeltaTime);
virtual FRotator GetViewOffsetRotBase( class APawn* ViewedPawn, const FTViewTarget& VT );
/** Returns View relative offsets */
virtual void GetBaseViewOffsets(class APawn* ViewedPawn, BYTE ViewportConfig, FLOAT DeltaTime, FVector& out_Low, FVector& out_Mid, FVector& out_High);
/** Returns true if mode should be using direct-look mode, false otherwise */
virtual UBOOL UseDirectLookMode(class APawn* CameraTarget);
/** Returns true if mode should lock camera to view target, false otherwise */
virtual UBOOL LockedToViewTarget(class APawn* CameraTarget);
/**
* Returns true if this mode should do target following. If true is returned, interp speeds are filled in.
* If false is returned, interp speeds are not altered.
*/
virtual UBOOL ShouldFollowTarget(class APawn* CameraTarget, FLOAT& PitchInterpSpeed, FLOAT& YawInterpSpeed, FLOAT& RollInterpSpeed);
/** Returns an offset, in pawn-local space, to be applied to the camera origin. */
virtual FVector GetTargetRelativeOriginOffset(class APawn* TargetPawn);
/**
* Returns location and rotation, in world space, of the camera's basis point. The camera will rotate
* around this point, offsets are applied from here, etc.
*/
virtual void GetCameraOrigin(class APawn* TargetPawn, FVector& OriginLoc, FRotator& OriginRot);
/**
* Interpolates from previous location/rotation toward desired location/rotation
*/
virtual void InterpolateCameraOrigin( class APawn* TargetPawn, FLOAT DeltaTime, FVector& out_ActualCameraOrigin, FVector const& IdealCameraOrigin, FRotator& out_ActualCameraOriginRot, FRotator const& IdealCameraOriginRot );
/** Returns time to interpolate location/rotation changes. */
virtual FLOAT GetBlendTime(class APawn* Pawn);
/** Returns time to interpolate FOV changes. */
virtual FLOAT GetFOVBlendTime(class APawn* Pawn);
/*
* Interpolates a camera's origin from the last location to a new ideal location
* @CameraTargetRot - Rotation of the camera target, used to create a reference frame for interpolation
* @LastLoc - Last location of the camera
* @IdealLoc - Ideal location for the camera this frame
* @DeltaTime - time step
* @return if bInterpLocation is false, returns IdealLoc, otherwise if bUsePerAxisOriginLocInterp is TRUE it interpolates relative to the target axis via PerAxisOriginLocInterpSpeed, else via constant OriginLocInterpSpeed
*/
virtual FVector InterpolateCameraOriginLoc(class APawn* TargetPawn, FRotator const& CameraTargetRot, FVector const& LastLoc, FVector const& IdealLoc, FLOAT DeltaTime);
virtual FRotator InterpolateCameraOriginRot(class APawn* TargetPawn, FRotator const& LastRot, FRotator const& IdealRot, FLOAT DeltaTime);
virtual FVector ApplyViewOffset( class APawn* ViewedPawn, const FVector& CameraOrigin, const FVector& ActualViewOffset, const FVector& DeltaViewOffset, const FTViewTarget& OutVT );
protected:
virtual FLOAT GetViewPitch(APawn* TargetPawn, FRotator const& ViewRotation) const;
public:
};
/** Called when this mode is initially created/new'd */
function Init();
/** Called when Camera mode becomes active */
function OnBecomeActive(Pawn TargetPawn, GameThirdPersonCameraMode PrevMode)
{
// Setup BlendTime for ViewOffsets
if( BlendTime > 0.f )
{
ViewOffsetInterp = 1 / BlendTime;
}
else
{
ViewOffsetInterp = 0;
}
}
/** Called when camera mode becomes inactive */
function OnBecomeInActive(Pawn TargetPawn, GameThirdPersonCameraMode NewMode);
/**
* Allows mode to make any final situational adjustments to the base view offset.
*/
event vector AdjustViewOffset( Pawn P, vector Offset )
{
// no adjustment by default
return Offset;
}
/** Returns FOV that this camera mode desires. */
function float GetDesiredFOV( Pawn ViewedPawn )
{
return FOVAngle;
}
/**
* Returns the "worst case" camera location for this camera mode.
* This is the position that the camera penetration avoidance is based off of,
* so it should be a guaranteed safe place to put the camera.
* @param TargetPawn Pawn being viewed.
* @param CurrentViewTarget current desired camera POV. Minus penetration checks.
*/
simulated event vector GetCameraWorstCaseLoc(Pawn TargetPawn, TViewTarget CurrentViewTarget)
{
return TargetPawn.Location + (WorstLocOffset >> TargetPawn.Rotation);
}
/**
* Camera mode has a chance to set a focus point, if it so chooses.
* Return true if setting one, false if not.
*/
simulated function bool SetFocusPoint(Pawn ViewedPawn)
{
// no focus point by default
return FALSE;
}
simulated function ProcessViewRotation( float DeltaTime, Actor ViewTarget, out Rotator out_ViewRotation, out Rotator out_DeltaRot );
simulated protected function vector GetDOFFocusLoc(Actor TraceOwner, vector StartTrace, vector EndTrace)
{
return DOFTrace(TraceOwner, StartTrace, EndTrace);
}
/** Modeled after CalcWeaponFire to avoid triggers. Consider moving to native code and using a single line check call? */
simulated protected function vector DOFTrace(Actor TraceOwner, vector StartTrace, vector EndTrace)
{
local vector HitLocation, HitNormal;
local Actor HitActor;
// Perform trace to retrieve hit info
HitActor = TraceOwner.Trace(HitLocation, HitNormal, EndTrace, StartTrace, TRUE, DOFTraceExtent,, TraceOwner.TRACEFLAG_Bullet);
// If we didn't hit anything, then set the HitLocation as being the EndTrace location
if( HitActor == None )
{
HitLocation = EndTrace;
}
// `log("DOF trace hit"@HitActor);
// check to see if we've hit a trigger.
// In this case, we want to add this actor to the list so we can give it damage, and then continue tracing through.
if( HitActor != None )
{
if ( !HitActor.bBlockActors &&
( HitActor.IsA('Trigger') || HitActor.IsA('TriggerVolume') ) )
{
// disable collision temporarily for the trigger so that we can catch anything inside the trigger
HitActor.bProjTarget = false;
// recurse another trace
// `log("... recursing!");
HitLocation = DOFTrace(TraceOwner, HitLocation, EndTrace);
// and reenable collision for the trigger
HitActor.bProjTarget = true;
}
}
return HitLocation;
}
/** Gives mode a chance to adjust/override postprocess as desired. */
simulated function UpdatePostProcess(const out TViewTarget VT, float DeltaTime)
{
local vector FocusLoc, StartTrace, EndTrace, CamDir;
local float FocusDist, SubjectDist, Pct;
bDOFUpdated = FALSE;
if (bAdjustDOF)
{
// nudge forward a little, in case camera gets itself in a wierd place.
CamDir = vector(VT.POV.Rotation);
StartTrace = VT.POV.Location + CamDir * 10;
EndTrace = StartTrace + CamDir * 50000;
FocusLoc = GetDOFFocusLoc(VT.Target, StartTrace, EndTrace);
SubjectDist = VSize(FocusLoc - StartTrace);
if (!ThirdPersonCam.bResetCameraInterpolation)
{
FocusDist = FInterpTo(LastDOFDistance, SubjectDist, DeltaTime, DOFDistanceInterpSpeed);
}
else
{
FocusDist = SubjectDist;
}
LastDOFDistance = FocusDist;
// find focus radius
// simpler method, with better-feeling results than actual optics math
Pct = GetRangePctByValue(DOF_RadiusDistRange, FocusDist);
LastDOFRadius = GetRangeValueByPct(DOF_RadiusRange, FClamp(Pct, 0.f, 1.f)**DOF_RadiusFalloff);
bDOFUpdated = TRUE;
}
}
simulated function ModifyPostProcessSettings(out PostProcessSettings PP)
{
if (bDOFUpdated)
{
PP.bEnableDOF = TRUE;
`if(`__TW_POSTPROCESS_)
PP.LegacySettings.DOF_FalloffExponent = DOF_FalloffExponent;
PP.LegacySettings.DOF_BlurKernelSize = DOF_BlurKernelSize;
PP.LegacySettings.DOF_MaxNearBlurAmount = DOF_MaxNearBlurAmount;
PP.LegacySettings.DOF_MaxFarBlurAmount = DOF_MaxFarBlurAmount;
PP.LegacySettings.DOF_FocusType = FOCUS_Distance;
PP.LegacySettings.DOF_FocusInnerRadius = DOF_FocusInnerRadius;
PP.LegacySettings.DOF_FocusDistance = LastDOFDistance;
PP.LegacySettings.DOF_FocusInnerRadius = LastDOFRadius;
`else
PP.DOF_FalloffExponent = DOF_FalloffExponent;
PP.DOF_BlurKernelSize = DOF_BlurKernelSize;
PP.DOF_MaxNearBlurAmount = DOF_MaxNearBlurAmount;
PP.DOF_MaxFarBlurAmount = DOF_MaxFarBlurAmount;
PP.DOF_FocusType = FOCUS_Distance;
PP.DOF_FocusInnerRadius = DOF_FocusInnerRadius;
PP.DOF_FocusDistance = LastDOFDistance;
PP.DOF_FocusInnerRadius = LastDOFRadius;
`endif
bDOFUpdated = FALSE;
}
}
native final function SetViewOffset(const out ViewOffsetData NewViewOffset);
defaultproperties
{
BlendTime=0.67
bLockedToViewTarget=TRUE
bDoPredictiveAvoidance=TRUE
bValidateWorstLoc=TRUE
bInterpLocation=TRUE
OriginLocInterpSpeed=8
StrafeLeftAdjustment=(X=0,Y=0,Z=0)
StrafeRightAdjustment=(X=0,Y=0,Z=0)
StrafeOffsetInterpSpeedIn=12.f
StrafeOffsetInterpSpeedOut=20.f
RunFwdAdjustment=(X=0,Y=0,Z=0)
RunBackAdjustment=(X=0,Y=0,Z=0)
RunOffsetInterpSpeedIn=6.f
RunOffsetInterpSpeedOut=12.f
WorstLocOffset=(X=-8,Y=1,Z=90)
bInterpViewOffsetOnlyForCamTransition=TRUE
/** all offsets are hand-crafted for this mode, so they should theoretically be all zeroes for all modes */
ViewOffset_ViewportAdjustments(CVT_16to9_Full)={(
OffsetHigh=(X=0,Y=0,Z=0),
OffsetLow=(X=0,Y=0,Z=0),
OffsetMid=(X=0,Y=0,Z=0),
)}
bAdjustDOF=FALSE
DOF_FalloffExponent=1.f
DOF_BlurKernelSize=3.f
// DOF_FocusInnerRadius=2500.f
DOF_MaxNearBlurAmount=0.6f
DOF_MaxFarBlurAmount=1.f
DOFDistanceInterpSpeed=10.f
DOFTraceExtent=(X=0.f,Y=0.f,Z=0.f)
DOF_RadiusFalloff=1.f
DOF_RadiusRange=(X=2500,Y=60000)
DOF_RadiusDistRange=(X=1000,Y=50000)
OffsetAdjustmentInterpSpeed=12.f
}

View File

@ -0,0 +1,74 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameThirdPersonCameraMode_Default extends GameThirdPersonCameraMode
config(Camera)
native(Camera);
cpptext
{
/**
* Returns location and rotation, in world space, of the camera's basis point. The camera will rotate
* around this point, offsets are applied from here, etc.
*/
virtual void GetCameraOrigin(class APawn* TargetPawn, FVector& OriginLoc, FRotator& OriginRot);
};
/** Z adjustment to camera worst location if target pawn is in aiming stance */
var() protected const float WorstLocAimingZOffset;
var protected transient bool bTemporaryOriginRotInterp;
var() protected const float TemporaryOriginRotInterpSpeed;
defaultproperties
{
TemporaryOriginRotInterpSpeed=12.f
WorstLocOffset=(X=-8,Y=1,Z=95)
WorstLocAimingZOffset=-10
bValidateWorstLoc=FALSE
ViewOffset={(
OffsetHigh=(X=-128,Y=56,Z=40),
OffsetLow=(X=-160,Y=48,Z=56),
OffsetMid=(X=-160,Y=48,Z=16),
)}
ViewOffset_ViewportAdjustments(CVT_16to9_HorizSplit)={(
OffsetHigh=(X=0,Y=0,Z=-12),
OffsetLow=(X=0,Y=0,Z=-12),
OffsetMid=(X=0,Y=0,Z=-12),
)}
ViewOffset_ViewportAdjustments(CVT_16to9_VertSplit)={(
OffsetHigh=(X=0,Y=-20,Z=0),
OffsetLow=(X=0,Y=-20,Z=0),
OffsetMid=(X=0,Y=-20,Z=0),
)}
ViewOffset_ViewportAdjustments(CVT_4to3_Full)={(
OffsetHigh=(X=0,Y=0,Z=17),
OffsetLow=(X=0,Y=0,Z=17),
OffsetMid=(X=0,Y=0,Z=17),
)}
ViewOffset_ViewportAdjustments(CVT_4to3_HorizSplit)={(
OffsetHigh=(X=0,Y=0,Z=-15),
OffsetLow=(X=0,Y=0,Z=-15),
OffsetMid=(X=0,Y=0,Z=-15),
)}
ViewOffset_ViewportAdjustments(CVT_4to3_VertSplit)={(
OffsetHigh=(X=0,Y=0,Z=0),
OffsetLow=(X=0,Y=0,Z=0),
OffsetMid=(X=0,Y=0,Z=0),
)}
StrafeLeftAdjustment=(X=0,Y=-15,Z=0)
StrafeRightAdjustment=(X=0,Y=15,Z=0)
StrafeOffsetScalingThreshold=200
RunFwdAdjustment=(X=20,Y=0,Z=0)
RunBackAdjustment=(X=-30,Y=0,Z=0)
RunOffsetScalingThreshold=200
BlendTime=0.25
}

View File

@ -0,0 +1,289 @@
/**
* GameTypes
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameTypes extends Object
native;
/** the name of the movie to show while loading */
const LOADING_MOVIE = "LoadingMovie";
/** DEPRECATED. Defines a camera-animation-driven screenshake. */
struct native ScreenShakeAnimStruct
{
var CameraAnim Anim;
/** If TRUE, code will choose which anim to play based on relative location to the player. Anim is treated as "front" in this case. */
var bool bUseDirectionalAnimVariants;
var CameraAnim Anim_Left;
var CameraAnim Anim_Right;
var CameraAnim Anim_Rear;
var float AnimPlayRate;
var float AnimScale;
var float AnimBlendInTime;
var float AnimBlendOutTime;
/**
* If TRUE, play a random snippet of the animation of length RandomSegmentDuration. Implies bLoop and bRandomStartTime = TRUE.
* If FALSE, play the full anim once, non-looped.
*/
var bool bRandomSegment;
var float RandomSegmentDuration;
/** TRUE to only allow a single instance of the specified anim to play at any given time. */
var bool bSingleInstance;
structdefaultproperties
{
AnimPlayRate=1.f
AnimScale=1.f
AnimBlendInTime=0.2f
AnimBlendOutTime=0.2f
}
};
/** DEPRECATED. Shake start offset parameter */
enum EShakeParam
{
ESP_OffsetRandom, // Start with random offset (default)
ESP_OffsetZero, // Start with zero offset
};
/** DEPRECATED. Shake vector params */
struct native ShakeParams
{
var EShakeParam X, Y, Z;
var transient const byte Padding;
};
/** DEPRECATED. Defines a code-driven (sinusoidal) screenshake */
struct native ScreenShakeStruct
{
/** Time in seconds to go until current screen shake is finished */
var float TimeToGo;
/** Duration in seconds of current screen shake */
var float TimeDuration;
/** view rotation amplitude */
var vector RotAmplitude;
/** view rotation frequency */
var vector RotFrequency;
/** view rotation Sine offset */
var vector RotSinOffset;
/** rotation parameters */
var ShakeParams RotParam;
/** view offset amplitude */
var vector LocAmplitude;
/** view offset frequency */
var vector LocFrequency;
/** view offset Sine offset */
var vector LocSinOffset;
/** location parameters */
var ShakeParams LocParam;
/** FOV amplitude */
var float FOVAmplitude;
/** FOV frequency */
var float FOVFrequency;
/** FOV Sine offset */
var float FOVSinOffset;
/** FOV parameters */
var EShakeParam FOVParam;
/**
* Unique name for this shake. Only 1 instance of a shake with a particular
* name can be playing at once. Subsequent calls to add the shake will simply
* restart the existing shake with new parameters. This is useful for animating
* shake parameters.
*/
var Name ShakeName;
// @FIXME JF, remove these from GameFramework
/** True to use TargetingDampening multiplier while player is targeted, False to use global defaults (see TargetingAlpha). */
var bool bOverrideTargetingDampening;
/** Amplitude multiplier to apply while player is targeting. Ignored if bOverrideTargetingDampening == FALSE */
var float TargetingDampening;
structdefaultproperties
{
TimeDuration=1.f
RotAmplitude=(X=100,Y=100,Z=200)
RotFrequency=(X=10,Y=10,Z=25)
LocAmplitude=(X=0,Y=3,Z=5)
LocFrequency=(X=1,Y=10,Z=20)
FOVAmplitude=2
FOVFrequency=5
ShakeName=""
}
};
/** replicated information on a hit we've taken */
struct native TakeHitInfo
{
/** the location of the hit */
var vector HitLocation;
/** how much momentum was imparted */
var vector Momentum;
/** the damage type we were hit with */
var class<DamageType> DamageType;
/** the weapon that shot us */
var Pawn InstigatedBy;
/** the bone that was hit on our Mesh (if any) */
var byte HitBoneIndex;
/** the physical material that was hit on our Mesh (if any) */
var PhysicalMaterial PhysicalMaterial;
/** how much damage was delivered */
var float Damage;
/** For radial damage, this is the point of origin. If damage was not radial, will be the same as HitLocation. */
var vector RadialDamageOrigin;
};
/** Struct to map specialmove label/class and allow overrides via the same label key */
struct native GameSpecialMoveInfo
{
var() Name SpecialMoveName;
var() class<GameSpecialMove> SpecialMoveClass;
/** Instance of the special move class */
var() GameSpecialMove SpecialMoveInstance;
};
/** Container for all special move properties */
struct native SpecialMoveStruct
{
/** Special Move being performed. */
var Name SpecialMoveName;
/** Interaction Pawn */
var GamePawn InteractionPawn;
/** Optional Interaction Actor */
var Actor InteractionActor;
/** Additional Replicated Flags */
var INT Flags;
};
struct native AICmdHistoryItem
{
var class<GameAICommand> CmdClass;
var float TimeStamp;
var String VerboseString;
};
struct native NearbyDynamicItem
{
var() Actor Dynamic;
structcpptext
{
FORCEINLINE UBOOL operator==(const AActor* Other) const
{
return (Dynamic==Other);
}
}
};
struct native CrowdSpawnerPlayerInfo
{
var Vector ViewLocation;
var Rotator ViewRotation;
var Vector PredictLocation;
var PlayerController PC;
};
struct native AgentArchetypeInfo
{
var() object AgentArchetype;
/** added to selection rate. **/
var() float FrequencyModifier;
/**
* No matter the frequency, we want to limit the number of this type of crowd agent. Another knob to easily set.
* Basically, we often want to adjust the number of crowds members / density but don't want a certain Archetype to also grow/shrink
* Due to native struct properties not being properly updated in already existing instanced objects we say MaxAllowed of 0 means infi guys
**/
var() int MaxAllowed;
var transient int CurrSpawned;
/** additional agents to spawn with this one as part of group **/
var() array<object> GroupMembers;
structdefaultproperties
{
FrequencyModifier=+1.0
}
};
struct native CrowdSpawnInfoItem
{
var SeqAct_GameCrowdPopulationManagerToggle SeqSpawner;
/** Controls whether we are actively spawning agents. */
var bool bSpawningActive;
/** How many agents per second will be spawned at the target actor(s). */
var float SpawnRate;
/** The maximum number of agents alive at one time. If agents are destroyed, more will spawn to meet this number. */
var int SpawnNum;
/** Used by spawning code to accumulate partial spawning */
var float Remainder;
/** List of currently active agents */
var array<GameCrowdAgent> ActiveAgents;
/** Archetypes of agents spawned by this crowd spawner */
var array<AgentArchetypeInfo> AgentArchetypes;
/** Sum of agent types + frequency modifiers */
var float AgentFrequencySum;
/** Max distance allowed for spawns */
var float MaxSpawnDist;
var float MaxSpawnDistSq;
/** Square of min distance allowed for in line of sight but out of view frustrum agent spawns */
var float MinBehindSpawnDist;
var float MinBehindSpawnDistSq;
/** Average time to "warm up" spawned agents before letting them sleep if not rendered */
var float AgentWarmupTime;
/** If true, force obstacle checking for all agents from this spawner */
var bool bForceObstacleChecking;
/** If true, force nav mesh navigation for all agents from this spawner */
var bool bForceNavMeshPathing;
/** Whether to enable the light environment on crowd members. */
var bool bEnableCrowdLightEnvironment;
/** Whether agents from this spawner should cast shadows */
var bool bCastShadows;
/** Lighting channels to put the agents in. */
var LightingChannelContainer AgentLightingChannel;
var() int NumAgentsToTickPerFrame;
var int LastAgentTickedIndex;
/** List of all GameCrowdDestinations that are PotentialSpawnPoints */
var array<GameCrowdDestination> PotentialSpawnPoints;
/** How frequently to reprioritize GameCrowdDestinations as potential spawn points */
var float SpawnPrioritizationInterval;
/** Index into prioritization array for picking spawn points, incremented as agents are spawned at each point */
var int PrioritizationIndex;
/** Index into prioritization array for updating prioritization*/
var int PrioritizationUpdateIndex;
/** Ordered array of prioritized spawn GameCrowdDestinations */
var array<GameCrowdDestination> PrioritizedSpawnPoints;
/** How far ahead to compute predicted player position for spawn prioritization */
var float PlayerPositionPredictionTime;
structdefaultproperties
{
SpawnPrioritizationInterval=0.4
AgentWarmupTime=3.f
}
};
defaultproperties
{
}

View File

@ -0,0 +1,48 @@
/**
* This class is the base class for Gear waveforms
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class GameWaveForms extends Object;
/** The forcefeedback waveform to play with a particular camera shakes */
var ForceFeedbackWaveform CameraShakeMediumShort;
/** The forcefeedback waveform to play with a particular camera shakes */
var ForceFeedbackWaveform CameraShakeMediumLong;
/** The forcefeedback waveform to play with a particular camera shakes */
var ForceFeedbackWaveform CameraShakeBigShort;
/** The forcefeedback waveform to play with a particular camera shakes */
var ForceFeedbackWaveform CameraShakeBigLong;
defaultproperties
{
Begin Object Class=ForceFeedbackWaveform Name=ForceFeedbackWaveform7
Samples(0)=(LeftAmplitude=60,RightAmplitude=60,LeftFunction=WF_LinearDecreasing,RightFunction=WF_LinearDecreasing,Duration=0.500)
End Object
CameraShakeMediumShort=ForceFeedbackWaveform7
Begin Object Class=ForceFeedbackWaveform Name=ForceFeedbackWaveform8
Samples(0)=(LeftAmplitude=60,RightAmplitude=60,LeftFunction=WF_LinearDecreasing,RightFunction=WF_LinearDecreasing,Duration=1.500)
End Object
CameraShakeMediumLong=ForceFeedbackWaveform8
Begin Object Class=ForceFeedbackWaveform Name=ForceFeedbackWaveform9
Samples(0)=(LeftAmplitude=100,RightAmplitude=100,LeftFunction=WF_LinearDecreasing,RightFunction=WF_LinearDecreasing,Duration=0.500)
End Object
CameraShakeBigShort=ForceFeedbackWaveform9
Begin Object Class=ForceFeedbackWaveform Name=ForceFeedbackWaveform10
Samples(0)=(LeftAmplitude=100,RightAmplitude=100,LeftFunction=WF_LinearDecreasing,RightFunction=WF_LinearDecreasing,Duration=1.500)
End Object
CameraShakeBigLong=ForceFeedbackWaveform10
}

View File

@ -0,0 +1,184 @@
//-----------------------------------------------------------
// Mobile Debug Camera Controller
//
// * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//-----------------------------------------------------------
class MobileDebugCameraController extends DebugCameraController
DependsOn(MobilePlayerInput);
var int OldMobileGroup;
var MobilePlayerInput MPI;
/*
* Function called on activation debug camera controller
*/
function OnActivate( PlayerController PC )
{
MPI = MobilePlayerInput(OriginalControllerRef.PlayerInput);
if(MPI != none)
{
OldMobileGroup = MPI.CurrentMobileGroup;
MPI.CurrentMobileGroup = -1;
}
MPI = new(self) class'MobileDebugCameraInput';
super.OnActivate(PC);
MPI.InitInputSystem();
SetupDebugZones();
MPI.ActivateInputGroup("DebugGroup");
MobileDebugCameraHUD(myHUD).bDrawDebugText = bDrawDebugText;
}
/*
* Does any controller/input necessary initialization.
*/
function InitDebugInputSystem()
{
MPI.MobileInputGroups.Remove(0, MPI.MobileInputGroups.Length);
MPI.MobileInputZones.Remove(0, MPI.MobileInputZones.Length);
}
/**
* Function called on deactivation debug camera controller
*/
function OnDeactivate( PlayerController PC )
{
local MobilePlayerInput MobileInput;
MPI.CurrentMobileGroup = -1;
super.OnDeactivate(PC);
MobileInput = MobilePlayerInput(OriginalControllerRef.PlayerInput);
MobileInput.SwapZoneOwners();
MobileInput.CurrentMobileGroup = OldMobileGroup;
}
/**
* When we init the input system, find the TapToMove zone and hook up the delegate
*/
event InitInputSystem()
{
Super.InitInputSystem();
}
/**
* The main purpose of this function is to size and reset zones. There's a lot of specific code in
* here to reposition zones based on if it's an phone vs pad.
*/
function SetupDebugZones()
{
local float Ratio;
local float Spacer;
// Cache the MPI
local MobileInputZone StickMoveZone;
local MobileInputZone StickLookZone;
local Vector2D ViewportSize;
MPI.InitializeInputZones();
StickMoveZone = MPI.FindZone("DebugStickMoveZone");
StickLookZone = MPI.FindZone("DebugStickLookZone");
LocalPlayer(OriginalPlayer).ViewportClient.GetViewportSize(ViewportSize);
Ratio = ViewportSize.Y / ViewportSize.X;
// The values here were picked after a long process of trail and error. They basically
// represent the collective "it feels right". These work for EpicCitadel. You will want to
// choose values that work for you.
Spacer = (Ratio == 0.75) ? 96 : 64;
Spacer *= (ViewportSize.X / 1024);
if (StickMoveZone != none)
{
if (Ratio == 0.75)
{
StickMoveZone.SizeX = ViewportSize.X * 0.12;
StickMoveZone.SizeY = StickMoveZone.SizeX;
StickMoveZone.ActiveSizeX = StickMoveZone.SizeX;
StickMoveZone.ActiveSizeY = StickMoveZone.SizeY;
}
StickMoveZone.SizeX = Spacer + StickMoveZone.SizeX;
StickMoveZone.SizeY = Spacer + StickMoveZone.SizeY;
if (Ratio == 0.75)
{
StickMoveZone.SizeY *= 1.5;
}
StickMoveZone.X = 0;
StickMoveZone.Y = ViewportSize.Y - StickMoveZone.SizeY;
StickMoveZone.CurrentCenter.X = StickMoveZone.X + StickMoveZone.SizeX - (StickMoveZone.ActiveSizeX*0.5);
if (Ratio == 0.75)
{
StickMoveZone.CurrentCenter.Y = ViewportSize.Y - StickMoveZone.SizeY * 0.33;
}
else
{
StickMoveZone.CurrentCenter.Y = StickMoveZone.Y + StickMoveZone.ActiveSizeY * 0.5;
}
StickMoveZone.CurrentLocation = StickMoveZone.CurrentCenter;
StickMoveZone.InitialCenter = StickMoveZone.CurrentCenter;
StickMoveZone.bCenterOnEvent = true;
}
if (StickLookZone != none)
{
if (Ratio == 0.75)
{
StickLookZone.SizeX = ViewportSize.X * 0.12;
StickLookZone.SizeY = StickLookZone.SizeX;
StickLookZone.ActiveSizeX = StickLookZone.SizeX;
StickLookZone.ActiveSizeY = StickLookZone.SizeY;
}
StickLookZone.SizeX = Spacer + StickLookZone.SizeX;
StickLookZone.SizeY = Spacer + StickLookZone.SizeY;
if (Ratio == 0.75)
{
StickLookZone.SizeY *= 1.5;
}
StickLookZone.X = ViewportSize.X - StickLookZone.SizeX;
StickLookZone.Y = ViewportSize.Y - StickLookZone.SizeY;
StickLookZone.CurrentCenter.X = StickLookZone.X + (StickLookZone.ActiveSizeX*0.5);
if (Ratio == 0.75)
{
StickLookZone.CurrentCenter.Y = ViewportSize.Y - StickLookZone.SizeY * 0.33;
}
else
{
StickLookZone.CurrentCenter.Y = StickLookZone.Y + StickLookZone.ActiveSizeY * 0.5;
}
StickLookZone.CurrentLocation = StickLookZone.CurrentCenter;
StickLookZone.InitialCenter = StickLookZone.CurrentCenter;
StickLookZone.bCenterOnEvent = true;
}
}
defaultproperties
{
InputClass=class'GameFramework.MobileDebugCameraInput'
HUDClass=class'GameFramework.MobileDebugCameraHUD'
}

View File

@ -0,0 +1,151 @@
//-----------------------------------------------------------
//
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//-----------------------------------------------------------
class MobileDebugCameraHUD extends MobileHUD
config(Game);
/** When true the debug text is drawn. This is passed through from CheatManager's ToggleDebugCamera exec function. */
var bool bDrawDebugText;
simulated event PostBeginPlay()
{
super.PostBeginPlay();
}
function bool DisplayMaterials( float X, out float Y, float DY, MeshComponent MeshComp )
{
local int MaterialIndex;
local bool bDisplayedMaterial;
local MaterialInterface Material;
bDisplayedMaterial = false;
if ( MeshComp != None )
{
for ( MaterialIndex = 0; MaterialIndex < MeshComp.GetNumElements(); ++MaterialIndex )
{
Material = MeshComp.GetMaterial(MaterialIndex);
if ( Material != None )
{
Y += DY;
Canvas.SetPos( X + DY, Y );
Canvas.DrawText("Material: '" $ Material.Name $ "'" );
bDisplayedMaterial = true;
}
}
}
return bDisplayedMaterial;
}
event PostRender()
{
local MobileDebugCameraController DCC;
local float xl,yl,X,Y;
local String MyText;
local vector CamLoc, ZeroVec;
local rotator CamRot;
local TraceHitInfo HitInfo;
local Actor HitActor;
local MeshComponent MeshComp;
local vector HitLoc, HitNormal;
local bool bFoundMaterial;
local bool bOldForceMobileHUD;
bOldForceMobileHUD = bForceMobileHUD;
bForceMobileHUD = true;
super.PostRender();
DCC = MobileDebugCameraController( PlayerOwner );
if( DCC != none && bDrawDebugText )
{
Canvas.SetDrawColor(0, 0, 255, 255);
MyText = "MobileDebugCameraHUD";
Canvas.Font = class'Engine'.Static.GetSmallFont();
Canvas.StrLen(MyText, XL, YL);
X = Canvas.SizeX * 0.05f;
Y = YL;//*1.67;
YL += 2*Y;
Canvas.SetPos( X, YL);
Canvas.DrawText(MyText, true);
Canvas.SetDrawColor(128, 128, 128, 255);
//DCC.GetPlayerViewPoint( CamLoc, CamRot );
CamLoc = DCC.PlayerCamera.CameraCache.POV.Location;
CamRot = DCC.PlayerCamera.CameraCache.POV.Rotation;
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("CamLoc:" $ CamLoc @ "CamRot:" $ CamRot );
HitActor = Trace(HitLoc, HitNormal, vector(camRot) * 5000 * 20 + CamLoc, CamLoc, true, ZeroVec, HitInfo);
if( HitActor != None)
{
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("HitLoc:" $ HitLoc @ "HitNorm:" $ HitNormal );
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("HitDist:" @ VSize(CamLoc - HitLoc) );
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("HitActor: '" $ HitActor.Name $ "'" );
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText("PhysMat: '" $ HitInfo.PhysMaterial $ "'" );
bFoundMaterial = false;
if ( HitInfo.Material != None )
{
YL += Y;
Canvas.SetPos(X + Y,YL);
Canvas.DrawText("Material:" $ HitInfo.Material.Name );
bFoundMaterial = true;
}
else if ( HitInfo.HitComponent != None )
{
bFoundMaterial = DisplayMaterials( X, YL, Y, MeshComponent(HitInfo.HitComponent) );
}
else
{
foreach HitActor.AllOwnedComponents( class'MeshComponent', MeshComp )
{
bFoundMaterial = bFoundMaterial || DisplayMaterials( X, YL, Y, MeshComp );
}
}
if ( bFoundMaterial == false )
{
YL += Y;
Canvas.SetPos( X + Y, YL );
Canvas.DrawText("Material: NONE" );
}
DrawDebugLine( HitLoc, HitLoc+HitNormal*30, 255,255,1255 );
}
else
{
YL += Y;
Canvas.SetPos(X,YL);
Canvas.DrawText( "Not trace hit" );
}
if ( DCC.bShowSelectedInfo == true && DCC.SelectedActor != None )
{
YL += Y;
Canvas.SetPos( X, YL );
Canvas.DrawText( "Selected actor: '" $ DCC.SelectedActor.Name $ "'" );
DisplayMaterials( X, YL, Y, MeshComponent(DCC.SelectedComponent) );
}
}
bForceMobileHUD = bOldForceMobileHUD;
}
DefaultProperties
{
bHidden=false
bDrawDebugText=true
JoystickBackground=Texture2D'MobileResources.T_MobileControls_texture'
JoystickBackgroundUVs=(U=0,V=0,UL=126,VL=126)
JoystickHat=Texture2D'MobileResources.T_MobileControls_texture'
JoystickHatUVs=(U=128,V=0,UL=78,VL=78)
}

View File

@ -0,0 +1,44 @@
//-----------------------------------------------------------
// * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//-----------------------------------------------------------
class MobileDebugCameraInput extends MobilePlayerInput;
/**
* Process an input key event routed through unrealscript from another object. This method is assigned as the value for the
* OnRecievedNativeInputKey delegate so that native input events are routed to this unrealscript function.
*
* @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.
*
* @return true to consume the key event, false to pass it on.
*/
function bool InputKey( int ControllerId, name Key, EInputEvent Event, float AmountDepressed = 1.f, bool bGamepad = FALSE )
{
local PlayerController PC;
local MobileDebugCameraController DCC;
foreach WorldInfo.AllControllers(class'PlayerController', PC)
{
if ( PC.bIsPlayer && PC.IsLocalPlayerController() )
{
DCC = MobileDebugCameraController(PC);
if( DCC!=none && DCC.OriginalControllerRef==none )
{
//dcc are disabled, so we are looking for normal player controller
continue;
}
return DCC.NativeInputKey( ControllerId, Key, Event, AmountDepressed, bGamepad );
}
}
return false;
}
defaultproperties
{
OnReceivedNativeInputKey=InputKey
}

View File

@ -0,0 +1,597 @@
/**
* MobileHUD
* Extra floating always on top HUD for touch screen devices
*
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileHUD extends HUD
native
config(Game)
dependson(MobilePlayerInput);
/** If true, we want to display the normal hud. We need a third variable to support hiding the hud completly yet still supporting the ShowHud command */
var config bool bShowGameHud;
/** If true, we want to display the mobile hud (ie: Input zones. etc) */
var config bool bShowMobileHud;
/** Allow for enabling/disabling the Mobile HUD stuff on non-mobile platforms */
var globalconfig bool bForceMobileHUD;
/** Texture to fill the zones with */
var Texture2D JoystickBackground;
var TextureUVs JoystickBackgroundUVs;
var Texture2D JoystickHat;
var TextureUVs JoystickHatUVs;
var Texture2D ButtonImages[2];
var TextureUVs ButtonUVs[2];
var font ButtonFont;
var color ButtonCaptionColor;
var Texture2D TrackballBackground;
var TextureUVs TrackballBackgroundUVs;
var Texture2D TrackballTouchIndicator;
var TextureUVs TrackballTouchIndicatorUVs;
var Texture2D SliderImages[4];
var TextureUVs SliderUVs[4];
/** If true, this hud will display the device tilt */
var config bool bShowMobileTilt;
/** Hold the position data for displaying the tilt */
var config float MobileTiltX, MobileTiltY, MobileTiltSize;
/** If true, display debug information regarding the touches */
var config bool bDebugTouches;
/** If true, debug info about the various mobile input zones will be displayed */
var config bool bDebugZones;
/** If true, debug info about a mobile input zone will be displayed, but only on presses */
var config bool bDebugZonePresses;
/** If this is true, we will display debug information regarding motion data */
var config bool bShowMotionDebug;
var array<SeqEvent_HudRender> KismetRenderEvents;
/**
* Create a list of actors needing post renders for. Also Create the Hud Scene
*/
simulated function PostBeginPlay()
{
super.PostBeginPlay();
// If we are on the actual mobile platform or we are forcing the issue, then
// figure out if we want to show the game hud
if (WorldInfo.IsConsoleBuild(CONSOLE_Mobile) || bForceMobileHUD)
{
}
else // Not a mobile game so make sure we don't restrict the hud
{
bShowGameHud = true;
}
// Find any HudRender events that need to be tracked
RefreshKismetLinks();
}
/**
* The start of the rendering chain.
*/
function PostRender()
{
local MobilePlayerInput MPI;
super.PostRender();
// If no secondary screen is active, render input zones and mobile menus. Otherwise,
// they'll be drawn as part of the secondary viewport client PostRender.
if (class'GameEngine'.static.HasSecondaryScreenActive() == false)
{
if (ShowMobileHud())
{
DrawInputZoneOverlays();
}
RenderMobileMenu();
}
if (bShowMotionDebug)
{
MPI = MobilePlayerInput(PlayerOwner.PlayerInput);
if (MPI != none)
{
Canvas.SetDrawColor(255,255,255,255);
Canvas.SetPos(0,70);
DrawMobileDebugString(0,90,"[Mobile Motion]");
DrawMobileDebugString(0,110,"Attitude: Pitch=" $ MPI.aTilt.X @ "Yaw=" $ MPI.aTilt.Y @ "Roll=" $ MPI.aTilt.Z);
DrawMobileDebugString(0,130,"Rotation:" @ MPI.aRotationRate.X @ MPI.aRotationRate.Y @ MPI.aRotationRate.Z);
DrawMobileDebugString(0,150,"Gravity:" @ MPI.aGravity.X @ MPI.aGravity.Y @ MPI.aGravity.Z);
DrawMobileDebugString(0,170,"Accleration:" @ MPI.aAcceleration.X @ MPI.aAcceleration.Y @ MPI.aAcceleration.Z);
}
}
RenderKismetHud();
// @DEBUG - Remove if you wish to see all touch events
//MobilePlayerInput(PlayerOwner.PlayerInput).DrawTouchDebug(Canvas);
}
function DrawMobileDebugString(float XPos, float YPos,string Str)
{
Canvas.SetDrawColor(0,0,0,255);
Canvas.SetPos(XPos,YPos);
Canvas.DrawText(Str);
Canvas.SetPos(XPos+1,YPos+1);
Canvas.DrawColor = WhiteColor;
Canvas.DrawText(Str);
}
function bool ShowMobileHud()
{
// Show the mobile HUD if we are allowed to and if we don't have the HUD disabled via cinematic mode.
return bShowMobileHud && bShowHud;
}
/**
* Draw the Mobile hud
*/
function RenderMobileMenu()
{
local MobilePlayerInput MobileInput;
local float y;
local int i;
// Get a reference to the mobile player input. Quick out if it's not a mobile input
MobileInput = MobilePlayerInput(PlayerOwner.PlayerInput);
if (MobileInput == none)
{
return;
}
if (bDebugTouches)
{
Y=20;
Canvas.SetDrawColor(255,255,255,255);
for (i=0;i<5;i++)
{
Canvas.SetPos(0,Y);
Canvas.DrawText("" $ i @ MobileInput.Touches[i].bInUse @ MobileInput.Touches[i].State @ MobileInput.Touches[i].Zone @ MobileInput.Touches[i].Handle);
Y+=10;
}
}
MobileInput.RenderMenus(Canvas, WorldInfo.DeltaSeconds);
}
/**
* Draws the input zones on top of everything else
*/
function DrawInputZoneOverlays()
{
local int ZoneIndex;
local MobileInputZone Zone;
local float Fade;
local MobilePlayerInput MobileInput;
local array<MobileInputZone> Zones;
// Get a reference to the mobile player input. Quick out if it's not a mobile input
if (!bShowHUD)
{
return;
}
MobileInput = MobilePlayerInput(PlayerOwner.PlayerInput);
if (MobileInput == none)
{
return;
}
// reset the canvas state
Canvas.Reset();
Canvas.ClipX = Canvas.SizeX;
Canvas.ClipY = Canvas.SizeY;
Canvas.Font = class'Engine'.Static.GetSmallFont();
if (MobileInput.HasZones())
{
Zones = MobileInput.GetCurrentZones();
}
// get the current zones from the game
for (ZoneIndex = 0; ZoneIndex < Zones.Length; ZoneIndex++)
{
Zone = Zones[ZoneIndex];
if ( !Zone.bIsInvisible )
{
// Setup the DrawColor, take the states in to consideration
Canvas.DrawColor = Zone.RenderColor;
// Apply opacity from animated transition fades
Canvas.DrawColor.A *= Zone.AnimatingFadeOpacity;
switch (Zone.State)
{
case ZoneState_Inactive:
Canvas.DrawColor.A *= Zone.InactiveAlpha;
break;
case ZoneState_Activating:
Fade = Lerp(Zone.InactiveAlpha, 1.0, Zone.TransitionTime / Zone.ActivateTime);
Canvas.DrawColor.A *= Fade;
break;
case ZoneState_Deactivating:
Fade = Lerp(1.0, Zone.InactiveAlpha, Zone.TransitionTime / Zone.DeactivateTime);
Canvas.DrawColor.A *= Fade;
break;
}
if (Canvas.DrawColor.A <= 0)
{
continue;
}
// Give script a chance to override the zone
if (!Zone.OnPreDrawZone(Zone,Canvas))
{
switch (Zone.Type)
{
case ZoneType_Button:
DrawMobileZone_Button(Zone);
break;
case ZoneType_Joystick:
DrawMobileZone_Joystick(Zone);
break;
case ZoneType_Trackball:
DrawMobileZone_Trackball(Zone);
break;
case ZoneType_Slider:
DrawMobileZone_Slider(Zone);
break;
}
Zone.OnPostDrawZone(Zone,Canvas);
}
}
if (bShowMobileTilt)
{
DrawMobileTilt(MobileInput);
}
if (bDebugZones || (bDebugZonePresses && (Zone.State == ZoneState_Active || Zone.State == ZoneState_Activating)))
{
Canvas.SetDrawColor(0,255,255,255);
Canvas.SetPos(Zone.X, Zone.Y);
Canvas.DrawBox(Zone.SizeX, Zone.SizeY);
}
}
}
function DrawMobileZone_Button(MobileInputZone Zone)
{
local int Pressed;
local float X,Y,U,V,UL,VL,A;
local Texture2D Tex;
Pressed = int(Zone.State == ZoneState_Active);
if (ButtonImages[Pressed] != none)
{
Canvas.SetPos(Zone.X, Zone.Y);
// check for override textures
if (Pressed == 0 && Zone.OverrideTexture1 != none)
{
Tex = Zone.OverrideTexture1;
U = Zone.OverrideUVs1.U;
V = Zone.OverrideUVs1.V;
UL = Zone.OverrideUVs1.UL;
VL = Zone.OverrideUVs1.VL;
}
else if (Pressed == 1 && Zone.OverrideTexture2 != none)
{
Tex = Zone.OverrideTexture2;
U = Zone.OverrideUVs2.U;
V = Zone.OverrideUVs2.V;
UL = Zone.OverrideUVs2.UL;
VL = Zone.OverrideUVs2.VL;
}
else
{
Tex = ButtonImages[Pressed];
U = ButtonUVs[Pressed].U;
V = ButtonUVs[Pressed].V;
UL = ButtonUVs[Pressed].UL;
VL = ButtonUVs[Pressed].VL;
}
Canvas.DrawTile(Tex,Zone.ActiveSizeX, Zone.ActiveSizeY, U,V,UL,VL);;
// Draw the Caption
if (Zone.Caption != "")
{
if (ButtonFont != none)
{
Canvas.Font = ButtonFont;
}
Canvas.StrLen(Zone.Caption,UL,VL);
X = Zone.X + (Zone.SizeX /2) - (UL/2);
Y = zone.Y + (Zone.SizeY /2) - (VL/2);
Canvas.SetPos(X + Zone.CaptionXAdjustment,Y+Zone.CaptionYAdjustment);
A = Canvas.DrawColor.A;
Canvas.DrawColor = ButtonCaptionColor;
Canvas.DrawColor.A = A;
Canvas.DrawText(Zone.Caption);
}
}
}
function DrawMobileZone_Joystick(MobileInputZone Zone)
{
local int X, Y, Width, Height;
local Color LineColor;
local float ClampedX, ClampedY, Scale;
local Color TempColor;
if (Zone.OverrideTexture1 != none || JoystickBackground != none)
{
Width = Zone.ActiveSizeX;
Height = Zone.ActiveSizeY;
X = Zone.CurrentCenter.X - (Width /2);
Y = Zone.CurrentCenter.Y - (Height /2);
Canvas.SetPos(X,Y);
// check for override textures
if (Zone.OverrideTexture1 != none)
{
Canvas.DrawTile(Zone.OverrideTexture1, Width, Height, Zone.OverrideUVs1.U, Zone.OverrideUVs1.V, Zone.OverrideUVs1.UL, Zone.OverrideUVs1.VL);
}
else
{
Canvas.DrawTile(JoystickBackground, Width, Height, JoystickBackgroundUVs.U, JoystickBackgroundUVs.V, JoystickBackgroundUVs.UL, JoystickBackgroundUVs.VL);
}
}
// Draw the Hat
if (Zone.OverrideTexture2 != none || JoystickHat != none)
{
// Compute X and Y clamped to the size of the zone for the joystick
ClampedX = Zone.CurrentLocation.X - Zone.CurrentCenter.X;
ClampedY = Zone.CurrentLocation.Y - Zone.CurrentCenter.Y;
Scale = 1.0f;
if ( ClampedX != 0 || ClampedY != 0 )
{
Scale = Min( Zone.ActiveSizeX, Zone.ActiveSizeY ) / ( 2.0 * Sqrt(ClampedX * ClampedX + ClampedY * ClampedY) );
Scale = FMin( 1.0, Scale );
}
ClampedX = ClampedX * Scale + Zone.CurrentCenter.X;
ClampedY = ClampedY * Scale + Zone.CurrentCenter.Y;
if (Zone.bRenderGuides)
{
TempColor = Canvas.DrawColor;
LineColor.R = 128;
LineColor.G = 128;
LineColor.B = 128;
LineColor.A = 255;
Canvas.Draw2DLine(Zone.CurrentCenter.X, Zone.CurrentCenter.Y, ClampedX, ClampedY, LineColor);
Canvas.DrawColor = TempColor;
}
// The size of the indicator will be a fraction of the background's total size
Width = Zone.ActiveSizeX * 0.65;
Height = Zone.ActiveSizeY * 0.65;
Canvas.SetPos( ClampedX - Width / 2, ClampedY - Height / 2);
// check for override textures
if (Zone.OverrideTexture2 != none)
{
Canvas.DrawTile(Zone.OverrideTexture2, Width, Height, Zone.OverrideUVs2.U, Zone.OverrideUVs2.V, Zone.OverrideUVs2.UL, Zone.OverrideUVs2.VL);
}
else
{
Canvas.DrawTile(JoystickHat, Width, Height, JoystickHatUVs.U, JoystickHatUVs.V, JoystickHatUVs.UL, JoystickHatUVs.VL);
}
}
}
function DrawMobileZone_Trackball(MobileInputZone Zone)
{
local int Width, Height;
if (Zone.OverrideTexture1 != none || TrackballBackground != none)
{
Canvas.SetPos( Zone.X, Zone.Y);
// check for override textures
if (Zone.OverrideTexture1 != none)
{
Canvas.DrawTile(Zone.OverrideTexture1, Zone.SizeX, Zone.SizeY, Zone.OverrideUVs1.U, Zone.OverrideUVs1.V, Zone.OverrideUVs1.UL, Zone.OverrideUVs1.VL);
}
else
{
Canvas.DrawTile(TrackballBackground, Zone.SizeX, Zone.SizeY, TrackballBackgroundUVs.U, TrackballBackgroundUVs.V, TrackballBackgroundUVs.UL, TrackballBackgroundUVs.VL);
}
}
// Draw the Touch indicator
if ((Zone.OverrideTexture2 != none || TrackballTouchIndicator != none) && (Zone.State == ZoneState_Active || Zone.State == ZoneState_Activating))
{
// The size of the indicator will be a fraction of the background's total size
Width = Zone.ActiveSizeX * 0.65;
Height = Zone.ActiveSizeY * 0.65;
Canvas.SetPos(Zone.CurrentLocation.X - Width / 2, Zone.CurrentLocation.Y - Height / 2);
// check for override textures
if (Zone.OverrideTexture2 != none)
{
Canvas.DrawTile(Zone.OverrideTexture2, Width, Height, Zone.OverrideUVs2.U, Zone.OverrideUVs2.V, Zone.OverrideUVs2.UL, Zone.OverrideUVs2.VL);
}
else
{
Canvas.DrawTile(TrackballTouchIndicator, Width, Height, TrackballTouchIndicatorUVs.U, TrackballTouchIndicatorUVs.V, TrackballTouchIndicatorUVs.UL, TrackballTouchIndicatorUVs.VL);
}
}
}
function DrawMobileTilt(MobilePlayerInput MobileInput)
{
local float X, Y, Scale;
local float Yaw, Pitch;
Yaw = 2.0 * FClamp(MobileInput.MobileYaw - MobileInput.MobileYawCenter,-0.5, 0.5) * MobileInput.MobileYawMultiplier;
Pitch = 2.0 * FClamp(MobileInput.MobilePitch - MobileInput.MobilePitchCenter, -0.5, 0.5) * MobileInput.MobilePitchMultiplier;
// Compute X and Y clamped to the size of the zone for the joystick
X = (MobileTiltX + Yaw * MobileTiltSize /2) - MobileTiltX;
Y = (MobileTiltY + Pitch * MobileTiltSize/2) - MobileTiltY;
Scale = 1.0f;
if ( X != 0 || Y != 0 )
{
Scale = MobileTiltSize / ( 2.0 * Sqrt(X*X*Y*Y) );
Scale = FMin( 1.0, Scale );
}
X = X * Scale + MobileTiltX;
Y = Y * Scale + MobileTiltY;
Canvas.DrawColor = WhiteColor;
Canvas.Draw2DLine(MobileTiltX, MobileTiltY, X, Y, Canvas.DrawColor);
}
function DrawMobileZone_Slider(MobileInputZone Zone)
{
local float X,Y;
local TextureUVs UVs;
local Texture2D Tex;
// First, look up the Texture
// check for override textures
if (Zone.OverrideTexture1 != none)
{
Tex = Zone.OverrideTexture1;
UVs = Zone.OverrideUVs1;
}
else
{
Tex = SliderImages[int(Zone.SlideType)];
UVs = SliderUVs[int(Zone.SlideType)];
}
// Now, figure out where we have to draw.
X = (int(Zone.SlideType) > 1) ? Zone.CurrentLocation.X - (Zone.ActiveSizeX * 0.5) : Zone.X;
Y = (int(Zone.SlideType) > 1) ? Zone.Y : Zone.CurrentLocation.Y - (Zone.ActiveSizeY * 0.5);
Canvas.SetPos(X,Y);
Canvas.DrawTile(Tex,Zone.ActiveSizeX, Zone.ActiveSizeY, UVs.U, UVs.V, UVs.UL, UVs.VL);
}
/**
* The SeqEvent's from the level's kismet will have their RegisterEvent function called before the inputzones are
* configured. So just this once, have all of them try again.
*/
function RefreshKismetLinks()
{
local array<SequenceObject> HudEvents;
local Sequence GameSeq;
local int i;
GameSeq = WorldInfo.GetGameSequence();
if (GameSeq != None)
{
// Find all SeqEvent_HudRender objects anywhere and call RegisterEvent on them
GameSeq.FindSeqObjectsByClass(class'SeqEvent_HudRender', TRUE, HudEvents);
for (i=0;i< HudEvents.Length; i++)
{
AddKismetRenderEvent(SeqEvent_HudRender(HudEvents[i]));
}
}
}
/**
* Adds a listen to the mobile handler list.
*
* @param Handler the MobileMotion sequence event to add to the handler list
*/
function AddKismetRenderEvent(SeqEvent_HudRender NewEvent)
{
local int i;
//`log("HUD: Adding Kismet Render Event" @ NewEvent.Name);
// More sure this event handler isn't already in the array
for (i=0;i<KismetRenderEvents.Length;i++)
{
if (KismetRenderEvents[i] == NewEvent)
{
return; // Already Registered
}
}
// Look though the array and see if there is an empty sport. These empty sports
// can occur when a kismet sequence is streamed out.
for (i=0;i<KismetRenderEvents.Length;i++)
{
if (KismetRenderEvents[i] == none)
{
KismetRenderEvents[i] = NewEvent;
return;
}
}
KismetRenderEvents.AddItem(NewEvent);
}
/**
* Give all Kismet Render events a chance to render to the hud
*/
function RenderKismetHud()
{
local int i;
local array<byte> boolVars;
for (i=0;i<KismetRenderEvents.Length;i++)
{
boolVars.Length = 0;
KismetRenderEvents[i].GetBoolVars(BoolVars,"Active");
if ((BoolVars.Length == 0 || BoolVars[0] != 0) && KismetRenderEvents[i].bIsActive)
{
if (KismetRenderEvents[i] != none && KismetRenderEvents[i].bIsActive)
{
KismetRenderEvents[i].Render(Canvas,self);
}
}
}
}
defaultproperties
{
}

View File

@ -0,0 +1,427 @@
/**
* MobileInputZone
* Controls how mobile input is handled
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileInputZone extends object
PerObjectConfig
editinlinenew
Config(Game)
native;
/** Describes the type of zone */
enum EZoneType
{
ZoneType_Button,
ZoneType_Joystick,
ZoneType_Trackball,
ZoneType_Slider,
ZoneType_SubClassed,
};
/**
* Describes the state of the zone.
*/
enum EZoneState
{
ZoneState_Inactive,
ZoneState_Activating,
ZoneState_Active,
ZoneState_Deactivating,
};
/**
* Defines which way the zone slides
*/
enum EZoneSlideType
{
ZoneSlide_UpDown,
ZoneSlide_LeftRight,
};
/**
* Structure to allow easy storage of UVs for a rendered image
*/
struct native TextureUVs
{
var() float U, V, UL, VL;
// defaults to see something
structdefaultproperties
{
UL=64.0f
VL=64.0f
}
};
/** What type of zone is this. */
var (Zone) config EZoneType Type;
/** Which touchpad this zone will respond to */
var (Zone) byte TouchpadIndex;
/** State of the zone */
var EZoneState State;
/** For button zones, the Caption property will be displayed in the center of the button */
var (Zone) config string Caption;
/** Input to send to input subsystem on event (vertical input for analog, can be NAME_None) */
var (Input) config name InputKey;
/** Input to send for horizontal analog input (can be NAME_None) */
var (Input) config name HorizontalInputKey;
/** Input to send for tap input (e.g. for tap-to-fire) */
var (Input) config name TapInputKey;
/** Input to send from a double tap */
var (Input) config name DoubleTapInputKey;
/** Multiplier to scale the analog vertical input by */
var (Input) config float VertMultiplier;
/** Multiplier to scale the analog horizontal input by */
var (Input) config float HorizMultiplier;
/** How much acceleration to apply to Trackball or Joystick movement (0.0 for none, no upper bounds) */
var (Input) config float Acceleration;
/** How much input smoothing to apply to Trackball or Joystick movement (0.0 for none, 1.0 for max) */
var (Input) config float Smoothing;
/** How much escape velocity to use for Trackball movement (0.0 for none, 1.0 for max) */
var (Input) config float EscapeVelocityStrength;
/** If true, this control will use it's "strength" to scale the movement of the pawn */
var (Input) config bool bScalePawnMovement;
/** Top left corner */
var (Bounds) config float X, Y;
/** Size of the zone */
var (Bounds) config float SizeX, SizeY;
/** Size of active Zone. Note if it's set to 0, then SizeX/SizeY will be copied here.
This setting is used when you have a zone that has bCenterOnEvent set and defines the size of
zone when it's active */
var (Bounds) config float ActiveSizeX, ActiveSizeY;
/** Used to resize the zone, don't touch */
var const float InitialX, InitialY;
var const float InitialSizeX, InitialSizeY;
var const float InitialActiveSizeX, InitialActiveSizeY;
/** If any of the bReleative vars are true, then the corresponding X/Y/SizeX/SizeY will be consider a percentage of the viewport */
var (Bounds) config bool bRelativeX;
var (Bounds) config bool bRelativeY;
var (Bounds) config bool bRelativeSizeX;
var (Bounds) config bool bRelativeSizeY;
/** If this is true, then ActiveSizeY is relative to ActiveSizeX */
var (Bounds) config bool bActiveSizeYFromX;
/** If this is true, then SizeX is relative to SizeY */
var (Bounds) config bool bSizeYFromSizeX;
/** This is the scale factor you are authoring for. 2.0 is useful for Retina display resolution (960x640), 1.0 for iPads and older iPhones */
var (Bounds) config float AuthoredGlobalScale;
/** If true, then the Global Scale values will be applied to the active Sizes */
var (Bounds) config bool bApplyGlobalScaleToActiveSizes;
/** if true, then this zone will be centered around the original X value. NOTE: X will be updated to reflect it's actual position */
var (bounds) config bool bCenterX;
/** if true, then this zone will be centered around the original Y value. NOTE: Y will be updated to reflect it's actual position */
var (bounds) config bool bCenterY;
/** Border is an invisible region around the zone. The border is included in hit determination. */
var (Bounds) config float Border;
/** Do we draw anything on screen for this zone? */
var (Options) config bool bIsInvisible;
/** If true, then this double tap will be considered a quick press/release, other wise it's tap and hold */
var (Options) config bool bQuickDoubleTap;
/** If true, this zone will have it's "center" set when you touch it, otherwise the center will be set to the center of the zone */
var (Options) config bool bCenterOnEvent;
/** Slider type has a track that can be clicked on and button will center on click */
var (Options) config bool bSliderHasTrack;
/** If bCenterOnEvent is enabled and this is non zero, the center position will be reset to it's initial center after this period of inactivity */
var (Options) config float ResetCenterAfterInactivityTime;
/** If true, the tilt zone will float within the SizeX/SizeY */
var (Options) config bool bFloatingTiltZone;
/** Determines what type of slide it is */
var (Options) config EZoneSlideType SlideType;
var (Options) config float TapDistanceConstraint;
/** If true, the zone will gracefully transition from Inactive to Active and vice-versus. NOTE: transitions are strickly visual. A
zone becomes active or inactive the moment a touch is applied or removed */
var (Transitions) config bool bUseGentleTransitions;
/** How fast should a zone for from active to inactive */
var (Transitions) config float ActivateTime;
/** How fast a zone should go from inactive to active */
var (Transitions) config float DeactivateTime;
/** Unless enabled, the first movement delta for a trackball zone will be ignored. This is useful for devices with inconsistent 'dead zones' for initial touch deltas, however this will reduce responsiveness of trackball drags slightly. */
var (Advanced) config bool bAllowFirstDeltaForTrackballZone;
/** Zone Rendering Parameters */
/** If true, the zone will render little guide lines for debugging */
var (Rendering) config bool bRenderGuides;
/** Holds the color to use when drawing images */
var (Rendering) config color RenderColor;
/** Holds the alpha value to use if the zone is inactive */
var (Rendering) config float InactiveAlpha;
/** This is a fixed adjustment that will be added to the zone's caption's X. It's used to align fonts correctly */
var (Rendering) config float CaptionXAdjustment;
/** This is a fixed adjustment that will be added to the zone's caption's Y. It's used to align fonts correctly */
var (Rendering) config float CaptionYAdjustment;
/** Override texture (for buttons, this is the texture when not clicked; for joystick/trackball, it's the background; for sliders, it's the slider) */
var (Rendering) Texture2D OverrideTexture1;
/** Ini-controlled string that will be looked up at runtime and hooked up to OverrideTexture1 */
var config string OverrideTexture1Name;
/** UVs for override texture 1 (in texel units) */
var (Rendering) config TextureUVs OverrideUVs1;
/** Override texture (for buttons, this is the texture when clicked; for joystick/trackball, it's the 'hat'; for sliders, it's unused) */
var (Rendering) Texture2D OverrideTexture2;
/** Ini-controlled string that will be looked up at runtime and hooked up to OverrideTexture2 */
var config string OverrideTexture2Name;
/** UVs for override texture 2 (in texel units) */
var (Rendering) config TextureUVs OverrideUVs2;
/** Holds the Initialize location where the zone was touched */
var Vector2D InitialLocation;
/** For Joystick and Trackball, this is where in the zone the user is currently holding down */
var Vector2D CurrentLocation;
/** For Joystick, this is the center of the analog zone to calculate the analog values from */
var Vector2D CurrentCenter;
/** For Joystick, the initial center position (used only when resetting the joystick back to it's center) */
var Vector2D InitialCenter;
/** For Joystick and Trackball, array of previous locations so that we can smooth input over frames */
var Vector2D PreviousLocations[6];
/** For Joystick and Trackball, array of previous movement time deltas so that we can smooth input over frames */
var float PreviousMoveDeltaTimes[6];
/** For Joystick and Trackball, how many previous locations we're currently storing */
var int PreviousLocationCount;
/** Used to calculate a double tap on this zone */
var float LastTouchTime;
/** How long since we last repeated a tap */
var float TimeSinceLastTapRepeat;
/** Fade opacity, used for certain transient effects */
var float AnimatingFadeOpacity;
/** A Reference back to the Player Input that controls this array Input zone */
var MobilePlayerInput InputOwner;
/** Holds the current transition time */
var float TransitionTime;
/** This will be true if this tap was a double tap. It's required in order to make sure we release the DoubleTapInputKey if it was a tap and hold */
var bool bIsDoubleTapAndHold;
/** For Trackball, how much escape velocity is left to apply */
var Vector2D EscapeVelocity;
/** holds an list of MobileZone Sequence events associated with this zone */
var array<SeqEvent_MobileZoneBase> MobileSeqEventHandlers;
/** Holds cached versions of the last axis values */
var Vector2D LastAxisValues;
/** Used to track the amount of time a zone is active */
var float TotalActiveTime;
/** Holds the time this zone last went active */
var float LastWentActiveTime;
cpptext
{
/**
* Processes a touch event
*
* @param DeltaTime Much time has elapsed since the last processing
* @param Handle The unique ID of this touch
* @param EventType The type of event that occurred
* @param TouchLocation Where on the device has the touch occurred.
* @param TouchDuration How long since the touch started
* @param MoveDeltaTime Time delta between the movement events the last time this touch moved
*/
void virtual ProcessTouch(FLOAT DeltaTime, UINT Handle, ETouchType EventType, FVector2D TouchLocation, FLOAT TouchTotalMoveDistance, FLOAT TouchDuration, FLOAT MoveDeltaTime);
/**
* All zones that are in the active group get 'Ticked' at the end of MobilePlayerInput::Tick
*
* @param DeltaTime Much time has elapsed since the last processing
*/
void virtual TickZone(FLOAT DeltaTime);
/**
* Applies any remaining escape velocity for this zone
*
* @param DeltaTime Much time has elapsed since the last processing
*/
void ApplyEscapeVelocity( FLOAT DeltaTime );
/**
* This function will iterate over the MobileSeqEventHandles array and cause them to be updated. It get's called once per frame that the zone is active
*/
void UpdateListeners();
protected:
/**
* Computes average location and movement time for the zone's active touch
*
* @param InTimeToAverageOver How long a duration to average over (max)
* @param OutSmoothedLocation (Out) Average location
* @param OutSmoothedMoveDeltaTime (Out) Average movement delta time
*/
void ComputeSmoothedMovement( const FLOAT InTimeToAverageOver, FVector2D& OutSmoothedLocation, FLOAT& OutSmoothedMoveDeltaTime ) const;
}
/**
* Called to activate a zone.
*/
native function ActivateZone();
/**
* Called to deactivate a zone
*/
native function DeactivateZone();
/**
* A delegate that allows script to override the process. We use delegates here
*
* @param Zone The Mobile Input zone that triggered the delegate
* @param Handle The unique ID of this touch
* @param EventType The type of event that occurred
* @param Location Where on the device has the touch occurred.
* @returns true if we need to swallow the touch
*/
delegate bool OnProcessInputDelegate(MobileInputZone Zone, float DeltaTime, int Handle, ETouchType EventType, Vector2D TouchLocation);
/**
* A delegate that allows script to manage Tap events.
*
* @param Zone The mobile Input zone that triggered the delegate
* @param Location Where on the device has the touch occurred.
* @param EventType The type of event that occurred
* @returns true if we need to swallow the tap
*/
delegate bool OnTapDelegate(MobileInputZone Zone, ETouchType EventType, Vector2D TouchLocation);
/**
* A delegate that allows script to manage Double Tap events.
*
* @param Zone The mobile Input zone that triggered the delegate
* @param Location Where on the device has the touch occurred.
* @param EventType The type of event that occurred
* @returns true if we need to swallow the double tap
*/
delegate bool OnDoubleTapDelegate(MobileInputZone Zone, ETouchType EventType, Vector2D TouchLocation);
/**
* This is a delegate that allows script to get values of a slide zone.
*
* @param Zone The mobile Input zone that triggered the delegate
* @param EventType The type of event that occurred
* @param SlideVlaue Holds the offset value of the slides in a +/- range in pixels. So 0 = Resting at normal.
* @param ViewportSize Holds the size of the current viewport.
*/
delegate bool OnProcessSlide(MobileInputZone Zone, ETouchType EventType, int SlideValue, Vector2D ViewportSize);
/**
* Allows other actors to override the drawing of this zone. Note this is called before the default drawing code
* and if it returns true, will abort the drawing sequence
*
* @param Zone The mobile Input zone that triggered the delegate
* @param Canvas The canvas to draw on
* @returns true to stop the rendering
*/
delegate bool OnPreDrawZone(MobileInputZone Zone, Canvas Canvas);
/**
* Allows other actors to supplement the drawing of this zone. Note this is called after the default drawing code
*
* @param Zone The mobile Input zone that triggered the delegate
* @param Canvas The canvas to draw on
*/
delegate OnPostDrawZone(MobileInputZone Zone, Canvas Canvas);
/**
* Adds a new MobileInput Sequence Event to the handler list
*
* @param NewHandler The handler to add
*/
function AddKismetEventHandler(SeqEvent_MobileZoneBase NewHandler)
{
local int i;
//`log("ZONE: Adding Kismet handler " @ NewHandler.Name @ "for zone" @ name);
// More sure this event handler isn't already in the array
for (i=0;i<MobileSeqEventHandlers.Length;i++)
{
if (MobileSeqEventHandlers[i] == NewHandler)
{
return; // Already Registered
}
}
// Look though the array and see if there is an empty sport. These empty sports
// can occur when a kismet sequence is streamed out.
for (i=0;i<MobileSeqEventHandlers.Length;i++)
{
if (MobileSeqEventHandlers[i] == none)
{
MobileSeqEventHandlers[i] = NewHandler;
return;
}
}
MobileSeqEventHandlers.AddItem(NewHandler);
}
defaultproperties
{
}

View File

@ -0,0 +1,200 @@
/**
* MobileMenuBar
* A container of items that can be selected on a bar - think letters on side of contact list.
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuBar extends MobileMenuObject;
/** Vertical or horizontal list supported */
var(DefaultInit) bool bIsVertical;
/** Current selected index */
var(DefaultInit) int SelectedIndex;
/** First item to draw */
var int FirstItem;
/** List of items */
var array<MobileMenuBarItem> Items;
/** Do we need to update before rendering */
var bool bDirty;
/**
* InitMenuObject - Virtual override from base to init object.
*
* @param PlayerInput - A pointer to the MobilePlayerInput object that owns the UI system
* @param Scene - The scene this object is in
* @param ScreenWidth - The Width of the Screen
* @param ScreenHeight - The Height of the Screen
*/
function InitMenuObject(MobilePlayerInput PlayerInput, MobileMenuScene Scene, int ScreenWidth, int ScreenHeight, bool bIsFirstInitialization)
{
Super.InitMenuObject(PlayerInput, Scene, ScreenWidth, ScreenHeight, bIsFirstInitialization);
UpdateItemViewports();
}
function AddItem(MobileMenuBarItem Item, Int Index=-1)
{
if (Index < 0)
{
Index = Items.length + (Index + 1);
}
Items.InsertItem(Index, Item);
bDirty = true;
}
function int Num()
{
return Items.length;
}
function MobileMenuBarItem GetSelected()
{
if ((SelectedIndex >= 0) && (SelectedIndex < Items.length))
{
return Items[SelectedIndex];
}
return none;
}
/**
* This event is called when a "touch" event is detected on the object.
* If false is returned (unhanded) event will be passed to scene.
*
* @param EventType - type of event
* @param TouchX - The X location of the touch event
* @param TouchY - The Y location of the touch event
* @param ObjectOver - The Object that mouse is over (NOTE: May be NULL or another object!)
*/
event bool OnTouch(ETouchType EventType, float TouchX, float TouchY, MobileMenuObject ObjectOver, float DeltaTime)
{
if (bIsVertical)
{
TouchY -= Top;
for (SelectedIndex = FirstItem; SelectedIndex < Items.Length-1; SelectedIndex++)
{
TouchY -= Items[SelectedIndex].Height;
if (TouchY <= 0)
break;
}
}
else
{
TouchX -= Left;
for (SelectedIndex = 0; SelectedIndex < Items.Length-1; SelectedIndex++)
{
TouchX -= Items[SelectedIndex].Width;
if (TouchX <= 0)
break;
}
}
return true;
}
/**
* Render the widget
*
* @param Canvas - the canvas object for drawing
*/
function RenderObject(canvas Canvas, float DeltaTime)
{
local float OrgX, OrgY;
local int CurIndex;
if (bDirty)
{
UpdateItemViewports();
}
OrgX = Canvas.OrgX;
OrgY = Canvas.OrgY;
Canvas.SetOrigin(Left, Top);
// Now render up to (not including) selected, then backwards to and including selected.
// This is so if we render larger that our VP, the selected on will be top.
for (CurIndex = 0; CurIndex < Items.Length; CurIndex++)
{
RenderItem(Canvas, DeltaTime, CurIndex);
}
// Restore to not mess up next scene.
Canvas.OrgX = OrgX;
Canvas.OrgY = OrgY;
}
/**
* Inheriting class can overload each item itself and not bother with the MobileMenuBarItem doing the rendering
*
* @param Canvas - the canvas object for drawing
* @param DeltaTime - How much time as passed.
* @param Index - Index of the item.
*/
function RenderItem(Canvas Canvas, float DeltaTime, int ItemIndex)
{
Items[ItemIndex].RenderItem(self, Canvas, DeltaTime);
}
function SetFirstItem(int First)
{
FirstItem = First;
bDirty = true;
}
/** Recalculate size and position of each sub item */
function UpdateItemViewports()
{
local MobileMenuBarItem Item;
local float Pos;
local int CurIndex;
Pos = 0;
Width = 0;
Height = 0;
if (bIsVertical)
{
for (CurIndex = FirstItem; CurIndex < Items.Length; CurIndex++)
{
Item = Items[CurIndex];
Width = FMax(Width, Item.Width);
Height += Item.Height;
}
for (CurIndex = FirstItem; CurIndex < Items.Length; CurIndex++)
{
Item = Items[CurIndex];
Item.VpPos.X = 0;
Item.VpPos.Y = Pos;
Pos += Item.Height;
}
}
else
{
for (CurIndex = FirstItem; CurIndex < Items.Length; CurIndex++)
{
Item = Items[CurIndex];
Width += Item.Width;
Height = FMax(Height, Item.Height);
}
for (CurIndex = FirstItem; CurIndex < Items.Length; CurIndex++)
{
Item = Items[CurIndex];
Item.VpPos.X = Pos;
Item.VpPos.Y = 0;
Pos += Item.Width;
}
}
bDirty = false;
}
defaultproperties
{
bIsActive=true
bIsVertical=true
SelectedIndex=0
}

View File

@ -0,0 +1,29 @@
/**
* MobileMenuBarItem
* Interface for any object that is stored in a MobileMenuBar.
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuBarItem extends Object;
var int Width;
var int Height;
// Set before being rendered - if was not rendered, then it is invalid.
var Vector2D VpPos;
/* Render a visible item in the bar (only called if visible)
*
* @Param Bar - Bar that is rendering item.
* @param Canvas - Render system.
* @Param DeltaTime - Time since last render.
*/
function RenderItem(MobileMenuBar Bar, Canvas Canvas, float DeltaTime)
{
}
defaultproperties
{
}

View File

@ -0,0 +1,96 @@
/**
* MobileMenuButton
* This is a simple button. It's an image with 2 states
*
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuButton extends MobileMenuObject;
/** The 2 images that make up the button. [0] = the untouched, [1] = touched */
var Texture2D Images[2];
/** The UV Coordinates for the images. [0] = the untouched, [1] = touched */
var UVCoords ImagesUVs[2];
/** Holds the color override for the image */
var LinearColor ImageColor;
/** Localizable caption for the button */
var string Caption;
/** Holds the color for the caption */
var LinearColor CaptionColor;
function InitMenuObject(MobilePlayerInput PlayerInput, MobileMenuScene Scene, int ScreenWidth, int ScreenHeight, bool bIsFirstInitialization)
{
local int i;
Super.InitMenuObject(PlayerInput, Scene, ScreenWidth, ScreenHeight,bIsFirstInitialization);
for (i=0;i<2;i++)
{
if (!ImagesUVs[i].bCustomCoords && Images[i] != none)
{
ImagesUVs[i].U = 0.0f;
ImagesUVs[i].V = 0.0f;
ImagesUVs[i].UL = Images[i].SizeX;
ImagesUVs[i].VL = Images[i].SizeY;
}
}
}
/**
* Render the widget
*
* @param Canvas - the canvas object for drawing
*/
function RenderObject(canvas Canvas, float DeltaTime)
{
local int Idx;
local LinearColor DrawColor;
Idx = (bIsTouched || bIsHighlighted) ? 1 : 0;
SetCanvasPos(Canvas);
Drawcolor = ImageColor;
Drawcolor.A *= Opacity * OwnerScene.Opacity;
Canvas.DrawTile(Images[Idx], Width, Height,ImagesUVs[Idx].U, ImagesUVs[Idx].V, ImagesUVs[Idx].UL, ImagesUVs[Idx].VL, DrawColor);
RenderCaption(Canvas);
}
/**
* Render the optional caption on top of the widget
*
* @param Canvas - the canvas object for drawing
*/
function RenderCaption(canvas Canvas)
{
local float UL,VL;
if (Caption != "")
{
Canvas.Font = OwnerScene.SceneCaptionFont;
Canvas.TextSize(Caption,UL,VL);
SetCanvasPos(Canvas, (Width / 2) - (UL/2), (Height /2) - (VL/2));
Canvas.DrawColor.R = byte(CaptionColor.R * 255.0);
Canvas.DrawColor.G = byte(CaptionColor.G * 255.0);
Canvas.DrawColor.B = byte(CaptionColor.B * 255.0);
Canvas.DrawColor.A = byte(CaptionColor.A * 255.0);
Canvas.DrawText(Caption);
}
}
defaultproperties
{
ImageColor=(r=1.0,g=1.0,b=1.0,a=1.0)
CaptionColor=(r=0.0,g=0.0,b=0.0,a=1.0)
bIsActive=true;
}

View File

@ -0,0 +1,45 @@
/**
* MobileMenuElement
* Interface for any object that may be stored in some sort of MobileMenu container (List, Inventory)
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuElement extends Object;
/* Location of element in container */
var Vector2D VpPos;
var Vector2D VpSize;
/** Will RenderElement be called for this object? */
var bool bIsVisible;
/** Will be tested to see if finger is over */
var bool bIsActive;
/* Allow item to use touch instead of scrolling. If Item returns true on first touch, it will receive
* all input until released.
* @param EventType - type of event
* @param TouchX - The X location of the touch event
* @param TouchY - The Y location of the touch event
* @param DeltaTime - Time since last touch
*/
function bool OnTouch(ETouchType EventType, float TouchX, float TouchY, float DeltaTime)
{
return false;
}
/* Render a visible element of the container.
*
* @Param List - List that is rendering item.
* @param Canvas - Render system.
* @Param DeltaTime - Time since last render.
*/
function RenderElement(MobileMenuObject Owner, Canvas Canvas, float DeltaTime, float Opacity);
defaultproperties
{
bIsVisible=true
bIsActive=true
}

View File

@ -0,0 +1,63 @@
/**
* MobileMenuGame
* A replacement game type that pops up a menu
*
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuGame extends GameInfo;
var class<MobileMenuScene> InitialSceneToDisplayClass;
/**
* We override PostLogin and display the scene directly after the login process is finished.
*/
event PostLogin( PlayerController NewPlayer )
{
local MobilePlayerInput MI;
Super.PostLogin(NewPlayer);
`log("" $ Class $"::PostLogin" @ InitialSceneToDisplayClass);
if (InitialSceneToDisplayClass != none)
{
MI = MobilePlayerInput(NewPlayer.PlayerInput);
if (MI != none)
{
MI.OpenMenuScene(InitialSceneToDisplayClass);
}
else
{
`Log("MobileMenuGame.Login - Could not find a MobilePlayerInput to open the scene!");
}
}
else
{
`Log("MobileMenuGame.Login - No scene to open");
}
}
/**
* Never start a match in the menus
*/
function StartMatch()
{
}
/**
* Never restart a player in the menus
*/
function RestartPlayer(Controller NewPlayer)
{
}
defaultproperties
{
PlayerControllerClass=class'MobileMenuPlayerController'
HUDType=class'MobileHud'
}

View File

@ -0,0 +1,100 @@
/**
* MobileMenuImage
* This is a simple image.
*
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuImage extends MobileMenuObject
native;
/** Holds the texture to display */
var Texture2D Image;
/** Determines how the image is displayed */
enum MenuImageDrawStyle
{
IDS_Normal,
IDS_Stretched,
IDS_Tile,
};
var MenuImageDrawStyle ImageDrawStyle;
/** Holds the texture UVs. Note, after InitMenuObject(), these will hold the values to use regardless of the bUseCustomUVs flag */
var UVCoords ImageUVs;
/** Holds the color override for the image */
var LinearColor ImageColor;
/**
* Render the widget
*
* @param Canvas - the canvas object for drawing
*/
function RenderObject(canvas Canvas, float DeltaTime)
{
local float W, H, U, V, UL, VL;
local LinearColor DrawColor;
// Set the position
SetCanvasPos(Canvas);
// Calculate the default set of rendering params
if (ImageUVs.bCustomCoords)
{
U = ImageUVs.U;
V = ImageUVs.V;
UL = ImageUVs.UL;
VL = ImageUVs.VL;
}
else
{
U = 0;
V = 0;
UL = Image.SizeX;
VL = Image.SizeY;
}
// Determine how we render the image.
switch (ImageDrawStyle)
{
case IDS_Normal: // Clip it
W = Width > UL ? UL : Width;
H = Height > VL ? VL : Height;
UL = W;
VL = H;
break;
case IDS_Stretched: // Stretch it
W = Width;
H = Height;
break;
case IDS_Tile: // Tile it
W = Width;
H = Height;
UL = W;
VL = H;
break;
}
DrawColor = ImageColor;
DrawColor.A *= Opacity * OwnerScene.Opacity;
Canvas.DrawTile(Image, W,H, U, V, UL, VL, DrawColor);
}
defaultproperties
{
ImageColor=(r=1.0,g=1.0,b=1.0,a=1.0)
}

View File

@ -0,0 +1,378 @@
/**
* MobileMenuInventory
* A container of objects that can be scrolled through.
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuInventory extends MobileMenuObject;
struct RenderElementInfo
{
var bool bIsDragItem;
var int Index;
};
struct DragElementInfo
{
var bool bIsDragging;
var int IndexFrom;
var bool bIsOver;
var int IndexOver;
var bool bCanDropInOver;
var Vector2D OrigTouch;
var Vector2D CurTouch;
var ETouchType EventType;
};
/** Locations to put items */
var array<MobileMenuElement> Slots;
/** What items are in inventory - the index matches with what slot it is in */
var array<MobileMenuElement> Items;
/** Extra amount of distance on each side to detect touch */
var float SideLeewayPercent;
/** Info about the cur element being rendered. */
var RenderElementInfo CurrentElement;
/** Data about what is being drug by the finger - if any */
var DragElementInfo Drag;
/** How this was scaled if any other things are added */
var Vector2D ScaleSize;
/** If set to false, user should call RenderDragItem() after scene is drawn.*/
var bool bRenderDragItem;
/* Rather than inheriting, a class can use delegates to allow an item in a slot and be updated when it happens */
delegate OnUpdateItemInSlot(MobileMenuInventory FromInv, int SlotIndex);
delegate bool DoCanPutItemInSlot(MobileMenuInventory FromInv, MobileMenuElement Item, MobileMenuElement ToSlot, int ToIdx, int FromIdx);
delegate OnUpdateDrag(out const DragElementInfo Before, out const DragElementInfo After);
/**
* InitMenuObject - Virtual override from base to init object.
*
* @param PlayerInput - A pointer to the MobilePlayerInput object that owns the UI system
* @param Scene - The scene this object is in
* @param ScreenWidth - The Width of the Screen
* @param ScreenHeight - The Height of the Screen
*/
function InitMenuObject(MobilePlayerInput PlayerInput, MobileMenuScene Scene, int ScreenWidth, int ScreenHeight, bool bIsFirstInitialization)
{
local MobileMenuElement Element;
// Save before they are modified in InitMenuObject.
ScaleSize.X = Width;
ScaleSize.Y = Height;
Super.InitMenuObject(PlayerInput, Scene, ScreenWidth, ScreenHeight, bIsFirstInitialization);
// Now see how they were scaled and scale all Slot locations the same way.
ScaleSize.X = Width/ScaleSize.X;
ScaleSize.Y = Height/ScaleSize.Y;
foreach Slots(Element)
{
ScaleSlot(Element);
}
foreach Items(Element)
{
ScaleSlot(Element);
}
Items.Length = Slots.Length;
}
/** Add slots to the inventory system - return index */
function int AddSlot(MobileMenuElement Slot)
{
if (Slot != none)
{
Slots.AddItem(Slot);
if (bHasBeenInitialized)
{
ScaleSlot(Slot);
}
return Slots.Length - 1;
}
return -1;
}
/** Scale slot in same way this object was scaled - use same scaling this MobileMenuObject uses */
private function ScaleSlot(MobileMenuElement Slot)
{
Slot.VpPos.X *= ScaleSize.X;
Slot.VpPos.Y *= ScaleSize.Y;
Slot.VpSize.X *= ScaleSize.X;
Slot.VpSize.Y *= ScaleSize.Y;
}
/** Inheriting class will need to override this function if there are restrictions on what slot
* an element can be placed.
* Owning class can set the DoCanPutItemInSlot().
*
* @param Item - Item that wants to go into slot
* @param ToSlot - Slot to receive element
* @param ToIdx - Index of Slot - where we want to put element.
* @param FromIdx - Index of slot element is currently in - if any
*/
function bool CanPutItemInSlot(MobileMenuElement Item, MobileMenuElement ToSlot, int ToIdx, int FromIdx=-1)
{
if ((Item == none) || (FromIdx == ToIdx) || (ToIdx < 0))
return false;
if (DoCanPutItemInSlot != none)
{
return DoCanPutItemInSlot(self, Item, ToSlot, ToIdx, FromIdx);
}
return true;
}
/**
* This event is called when a "touch" event is detected on the object.
* If false is returned (unhanded) event will be passed to scene.
*
* @param EventType - type of event
* @param TouchX - The X location of the touch event
* @param TouchY - The Y location of the touch event
* @param ObjectOver - The Object that mouse is over (NOTE: May be NULL or another object!)
*/
event bool OnTouch(ETouchType EventType, float TouchX, float TouchY, MobileMenuObject ObjectOver, float DeltaTime)
{
local DragElementInfo OrigDrag;
OrigDrag = Drag;
Drag.EventType = EventType;
TouchX -= Left;
TouchY -= Top;
Drag.CurTouch.X = TouchX;
Drag.CurTouch.Y = TouchY;
switch (EventType)
{
case Touch_Began:
InitDragAt(TouchX, TouchY);
if (OnUpdateDrag != none)
OnUpdateDrag(OrigDrag, Drag);
return true;
case Touch_Moved:
case Touch_Stationary:
if (!Drag.bIsDragging)
{
InitDragAt(TouchX, TouchY);
}
else
{
Drag.IndexOver = FindSlotIndexAt(TouchX, TouchY);
Drag.bIsOver = Drag.IndexOver >= 0;
}
Drag.bCanDropInOver = Drag.bIsOver && CanPutItemInSlot(Items[Drag.IndexFrom], Slots[Drag.IndexOver], Drag.IndexOver, Drag.IndexFrom);
if (OnUpdateDrag != none)
OnUpdateDrag(OrigDrag, Drag);
return true;
case Touch_Ended:
if (Drag.bIsDragging)
{
// If we were not over something, try one last time,
// but do not remove it because sometimes when people lift they move too much.
if (!Drag.bIsOver)
{
Drag.IndexOver = FindSlotIndexAt(TouchX, TouchY);
Drag.bIsOver = Drag.IndexOver >= 0;
}
Drag.bCanDropInOver = Drag.bIsOver && CanPutItemInSlot(Items[Drag.IndexFrom], Slots[Drag.IndexOver], Drag.IndexOver, Drag.IndexFrom);
if (Drag.bCanDropInOver)
{
SwapItemsInSlots(Drag.IndexOver, Drag.IndexFrom);
}
}
break;
case Touch_Cancelled:
break;
}
// Only come here where we are done.
Drag.bIsDragging = false;
if (OnUpdateDrag != none)
OnUpdateDrag(OrigDrag, Drag);
// Don't change until after the OnUpdateDrag() because it will want to know last state.
Drag.bCanDropInOver = false;
Drag.bIsOver = false;
return true;
}
function bool SwapItemsInSlots(int Slot0, int Slot1)
{
local MobileMenuElement Element0, Element1;
Element0 = Items[Slot0];
Element1 = Items[Slot1];
if ((Element0 == none) || CanPutItemInSlot(Element0, Slots[Slot1], Slot1, Slot0))
{
if ((Element1 == none) || CanPutItemInSlot(Element1, Slots[Slot0], Slot0, Slot1))
{
Items[Slot0] = Element1;
Items[Slot1] = Element0;
UpdateItemInSlot(Slot0);
UpdateItemInSlot(Slot1);
return true;
}
}
return false;
}
function MobileMenuElement AddItemToSlot(MobileMenuElement Element, int ToSlot)
{
local MobileMenuElement PrevElement;
if (CanPutItemInSlot(Element, Slots[ToSlot], ToSlot))
{
PrevElement = Items[ToSlot];
Items[ToSlot] = Element;
UpdateItemInSlot(ToSlot);
return PrevElement;
}
return none;
}
protected function UpdateItemInSlot(int InSlot)
{
local MobileMenuElement Element, Slot;
Element = Items[InSlot];
if (Element != none)
{
Slot = Slots[InSlot];
Element.VpPos = Slot.VpPos;
Element.VpSize = Slot.VpSize;
}
if (OnUpdateItemInSlot != none)
{
OnUpdateItemInSlot(self, InSlot);
}
}
function InitDragAt(int TouchX, int TouchY)
{
Drag.IndexFrom = FindSlotIndexAt(TouchX, TouchY);
Drag.bIsDragging = (Drag.IndexFrom >= 0) && (Items[Drag.IndexFrom] != none);
Drag.IndexOver = Drag.IndexFrom;
Drag.bIsOver = (Drag.IndexFrom >= 0);
Drag.bCanDropInOver = false;
Drag.OrigTouch.X = TouchX;
Drag.OrigTouch.Y = TouchY;
}
function int FindSlotIndexAt(float X, float Y)
{
local MobileMenuElement Element;
local float ExtraX, ExtraY;
local int Idx;
Idx = -1;
foreach Slots(Element)
{
Idx++;
if (Element.bIsActive)
{
ExtraX = Element.VpSize.X * SideLeewayPercent;
ExtraY = Element.VpSize.Y * SideLeewayPercent;
if (X < (Element.VpPos.X - ExtraX))
continue;
if (Y < (Element.VpPos.Y - ExtraY))
continue;
if (X > ((Element.VpPos.X + Element.VpSize.X) + ExtraX))
continue;
if (Y > ((Element.VpPos.Y + Element.VpSize.Y) + ExtraY))
continue;
return Idx;
}
}
return -1;
}
function int GetIndexOfItem(MobileMenuElement Item)
{
return Items.Find(Item);
}
/**
* Render the widget
*
* @param Canvas - the canvas object for drawing
*/
function RenderObject(canvas Canvas, float DeltaTime)
{
local MobileMenuElement Element;
local float OrgX, OrgY;
OrgX = Canvas.OrgX;
OrgY = Canvas.OrgY;
CurrentElement.bIsDragItem = false;
CurrentElement.Index = 0;
foreach Slots(Element)
{
if (Element.bIsVisible)
{
Canvas.SetOrigin(Left + Element.VpPos.X, Top + Element.VpPos.Y);
Element.RenderElement(self, Canvas, DeltaTime, Opacity);
}
CurrentElement.Index++;
}
// ForEach does not work because it does not return null slots, therefore CurrentElement.Index cannot be calculated.
for (CurrentElement.Index = 0; CurrentElement.Index < Items.Length; CurrentElement.Index++)
{
Element = Items[CurrentElement.Index];
if (Element != none && Element.bIsVisible)
{
Canvas.SetOrigin(Left + Element.VpPos.X, Top + Element.VpPos.Y);
Element.RenderElement(self, Canvas, DeltaTime, Opacity);
}
}
// Restore to not mess up next scene.
Canvas.OrgX = OrgX;
Canvas.OrgY = OrgY;
if (bRenderDragItem)
RenderDragItem(Canvas, DeltaTime);
}
function RenderDragItem(canvas Canvas, float DeltaTime)
{
local MobileMenuElement Element;
local float OrgX, OrgY;
if (Drag.bIsDragging)
{
OrgX = Canvas.OrgX;
OrgY = Canvas.OrgY;
CurrentElement.bIsDragItem = true;
CurrentElement.Index = Drag.IndexFrom;
Element = Items[Drag.IndexFrom];
Canvas.SetOrigin(Left + Drag.CurTouch.X, Top + Drag.CurTouch.Y);
Element.RenderElement(self, Canvas, DeltaTime, Opacity);
// Restore to not mess up next scene.
Canvas.OrgX = OrgX;
Canvas.OrgY = OrgY;
}
}
defaultproperties
{
bIsActive=true
bRenderDragItem=true
SideLeewayPercent=0.1f
}

View File

@ -0,0 +1,72 @@
/**
* MobileMenuLabel
* This is a simple label. NOTE this label does not support
* word wrap or any additional functionality. It just renders text
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuLabel extends MobileMenuObject;
/** Holds the caption for this label */
var string Caption;
/** Holds the font that will be used to draw the text */
var font TextFont;
/** Hold the color that the font will be displayed in */
var color TextColor;
/** Holds the color of the font when pressed */
var color TouchedColor;
/** Holds the X scaling factor for the label */
var float TextXScale;
/** Holds the Y scaling factor for the label */
var float TextYScale;
/** If true, we will calculate the actual render bounds,etc upon draw */
var bool bAutoSize;
/**
* Render the widget
*
* @param Canvas - the canvas object for drawing
*/
function RenderObject(canvas Canvas, float DeltaTime)
{
local float CX,CY;
local float TX, TY;
CX = Canvas.ClipX;
CY = Canvas.ClipY;
Canvas.Font = TextFont;
if (bAutoSize)
{
Canvas.TextSize(Caption, TX, TY);
Width = TX * TextXScale;
Height = TY * TextYScale;
}
Canvas.DrawColor = bIsTouched ? TouchedColor : TextColor;
Canvas.DrawColor.A *= Opacity * OwnerScene.Opacity;
SetCanvasPos(Canvas);
Canvas.ClipX = Canvas.CurX + Width;
Canvas.ClipY = Canvas.CurY + Height;
Canvas.DrawText(Caption,,TextXScale, TextYScale);
Canvas.ClipX = CX;
Canvas.ClipY = CY;
}
defaultproperties
{
TextXScale=1.0
TextYScale=1.0
bAutoSize=true
}

View File

@ -0,0 +1,786 @@
/**
* MobileMenuList
* A container of objects that can be scrolled through.
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuList extends MobileMenuObject;
struct SelectedMenuItem
{
/** Currently 'Selected' based off of position */
var int Index;
/** The selected item may be not be right on the 'selected' location, this is its offset */
var float Offset;
/** Was the selected limited how far it dragged because it was end of the list **/
var bool bEndOfList;
};
struct DragHistoryData
{
var float TouchTime;
var float TouchCoord;
};
const NumInDragHistory=4;
struct MenuListDragInfo
{
/** Are we currently dragging? If not, still may be used if ScrollSpeed != 0*/
var bool bIsDragging;
/** Item that was initially pressed. If !bIsDragging, then this item will use all input */
var MobileMenuListItem TouchedItem;
/** Saved off to recalculate position */
var SelectedMenuItem OrigSelectedItem;
/** Where did user start to drag at? */
var Vector2D StartTouch;
/** Amount of time for the touch */
var float TouchTime;
/** How far from orig position we are at */
var float ScrollAmount;
/** Tracks if user moved up, then down - ScrollAmount might be 0, but AbsScrollAmount migh have a value*/
var float AbsScrollAmount;
/** To smooth out the release velocity */
var DragHistoryData UpdateHistory[NumInDragHistory];
/** Number of Update calls (not press or release) to index into History */
var int NumUpdates;
/** See if the selected one has changed, if not, treat it as a touch when release */
var bool bHasSelectedChanged;
};
struct MenuListMovementInfo
{
/* Are we automatically moving the list? */
var bool bIsMoving;
/** Saved off to recalculate position */
var SelectedMenuItem OrigSelectedItem;
/** How many pixels total to scroll */
var float FullMovement;
/** Total time until it is done scrolling */
var float TotalTime;
/** How much time we are at */
var float CurrentTime;
};
/** Vertical or horizontal list supported */
var(DefaultInit) bool bIsVerticalList;
/** On short list, might want to disable all scrolling */
var(DefaultInit) bool bDisableScrolling;
/** Offset from Top/Left of list that determines 'selected' item - init as percentage of Width/Height depending on bIsVerticalList */
var(DefaultInit) float SelectedOffset;
/** When user stops moving, should the closest item move to the selected position */
var(DefaultInit) bool bForceSelectedToLineup;
var array<MobileMenuListItem> Items;
/** Current position of our list */
var SelectedMenuItem SelectedItem;
/** User changing position of list */
var MenuListDragInfo Drag;
/** Automatic movement of list (after user drag) */
var MenuListMovementInfo Movement;
/** How fast to Deaccelerate when user releases - cannot be 0*/
var float Deacceleration;
/** How to we slow down while deaccelerate */
var float EaseOutExp;
/** Sometimes rendering item needs this */
var IntPoint ScreenSize;
/** When user taps on an options, should we scroll to it? Typically, when there is no obvious 'selected', you don't want to */
var bool bTapToScrollToItem;
/** Behaves like a slot machine wheel, continually loops. */
var bool bLoops;
/** Index of first and last visible according to what just rendered. */
var int FirstVisible, LastVisible;
/** To not allow scrolling past end of lists */
var int NumShowEndOfList;
/** How much to decrease scroll when at the end of a list. 1.0 is none, 0.5 is half, */
var float EndOfListSupression;
/**
* InitMenuObject - Virtual override from base to init object.
*
* @param PlayerInput - A pointer to the MobilePlayerInput object that owns the UI system
* @param Scene - The scene this object is in
* @param ScreenWidth - The Width of the Screen
* @param ScreenHeight - The Height of the Screen
*/
function InitMenuObject(MobilePlayerInput PlayerInput, MobileMenuScene Scene, int ScreenWidth, int ScreenHeight, bool bIsFirstInitialization)
{
ScreenSize.X = ScreenWidth;
ScreenSize.Y = ScreenHeight;
Super.InitMenuObject(PlayerInput, Scene, ScreenWidth, ScreenHeight, bIsFirstInitialization);
SelectedOffset *= (bIsVerticalList) ? Height : Width;
}
function AddItem(MobileMenuListItem Item, Int Index=-1)
{
if (Index < 0)
{
Index = Items.length + (Index + 1);
}
Items.InsertItem(Index, Item);
}
function int Num()
{
return Items.length;
}
function MobileMenuListItem GetSelected()
{
local MobileMenuListItem Item;
if ((SelectedItem.Index >= 0) && (SelectedItem.Index < Items.length))
{
Item = Items[SelectedItem.Index];
if (Item != none && !Item.bIsVisible)
Item = none;
return Item;
}
return none;
}
/*
* If item is selected, 0 > RetValue >= 1.0.
* Useful for changing alpha or size of selected item.
*/
function float GetAmountSelected(MobileMenuListItem Item)
{
local MobileMenuListItem Selected;
local float Half;
Selected = GetSelected();
if (Item == Selected)
{
Half = (bIsVerticalList ? Item.Height : Item.Width) * 0.5f;
return FMax(0.0001f, FMin(1.0f, 1.0f - (Abs(SelectedItem.Offset) / Half)));
}
return 0.0f;
}
/**
* Find the visible index of selected item. In other words, number of
* visible items before selected item.
*/
function int GetVisibleIndexOfSelected()
{
local MobileMenuListItem Item, Selected;
local int Index;
Selected = GetSelected();
Index = 0;
foreach Items(Item)
{
if (Item == Selected)
{
return Index;
}
if (Item.bIsVisible)
{
Index++;
}
}
return -1;
}
/**
* Set the selected item to the visible item with VisibleIndex visible items before it
*/
function int SetSelectedToVisibleIndex(int VisibleIndex)
{
local int Index;
for (Index = 0; Index < Items.Length; Index++)
{
if (Items[Index].bIsVisible)
{
if (VisibleIndex <= 0)
{
SelectedItem.Index = Index;
return Index;
}
VisibleIndex--;
}
}
SelectedItem.Index = -1;
return -1;
}
function int GetNumVisible()
{
local int Index, Count;
for (Index = 0; Index < Items.Length; Index++)
{
if (Items[Index].bIsVisible)
{
Count++;
}
}
return Count;
}
// A little hack, forcing all. Perhaps it should always be set to true,
// but this is nearly last bug before we ship.
function bool SetSelectedItem(int ItemIndex, bool bForceAll=false)
{
if ((ItemIndex >= 0) && (ItemIndex < Items.Length))
{
if (Items[ItemIndex].bIsVisible)
{
SelectedItem.Index = ItemIndex;
if (bForceAll)
{
Drag.OrigSelectedItem = SelectedItem;
Movement.OrigSelectedItem = SelectedItem;
}
return true;
}
}
return false;
}
/**
* This event is called when a "touch" event is detected on the object.
* If false is returned (unhanded) event will be passed to scene.
*
* @param EventType - type of event
* @param TouchX - The X location of the touch event
* @param TouchY - The Y location of the touch event
* @param ObjectOver - The Object that mouse is over (NOTE: May be NULL or another object!)
*/
event bool OnTouch(ETouchType EventType, float TouchX, float TouchY, MobileMenuObject ObjectOver, float DeltaTime)
{
local float Velocity, SwipeDelta, FinalScrollDist, CalcScrollDist, SwipeTime;
local MobileMenuListItem Selected;
local int Index, Index0;
local bool bUdpateTouchItem;
TouchX -= Left;
TouchY -= Top;
Drag.TouchTime+= DeltaTime;
//`log("EventType:" $ String(EventType) @ "Y:" $ TouchY @ "Time:" $ DeltaTime @ Drag.TouchTime);
if (EventType == Touch_Began)
{
Movement.bIsMoving = false;
Drag.bIsDragging = true;
Drag.OrigSelectedItem = SelectedItem;
Drag.StartTouch.X = TouchX;
Drag.StartTouch.Y = TouchY;
Drag.ScrollAmount = 0;
Drag.AbsScrollAmount = 0;
Drag.bHasSelectedChanged = false;
Drag.TouchTime = 0;
Drag.NumUpdates = 0;
for (Index = 0; Index < NumInDragHistory; Index++)
{
Drag.UpdateHistory[Index].TouchTime = 0;
}
Drag.TouchedItem = GetItemClickPosition(TouchX, TouchY);
if (Drag.TouchedItem != none)
{
Drag.bIsDragging = !Drag.TouchedItem.OnTouch(EventType, TouchX, TouchY, DeltaTime);
}
}
else if (!Drag.bIsDragging)
{
bUdpateTouchItem = true;
}
else if ((EventType == Touch_Ended) || (EventType == Touch_Cancelled))
{
bUdpateTouchItem = true;
Drag.bIsDragging = false;
Movement.bIsMoving = true;
Movement.CurrentTime = 0;
Movement.OrigSelectedItem = SelectedItem;
if (!Drag.bHasSelectedChanged && (Drag.StartTouch.X == TouchX) && (Drag.StartTouch.Y == TouchY))
{
Selected = GetSelected();
// Fix annoyance issue when you try to swipe, but do so very quick and therefore
// it appears to only be a touch and it moves to the item you touched.
if((Drag.TouchTime > 0.05f) && bTapToScrollToItem)
{
// Force to scroll to item selected.
if (bIsVerticalList)
FinalScrollDist = TouchY - (SelectedOffset + (Selected.Height/2));
else
FinalScrollDist = TouchX - (SelectedOffset + (Selected.Width/2));
}
}
else if (Drag.NumUpdates >= 2)
{
Index = (Drag.NumUpdates - 1) % NumInDragHistory;
Index0 = (Drag.NumUpdates - Min(Drag.NumUpdates, NumInDragHistory)) % NumInDragHistory;
SwipeDelta = -(Drag.UpdateHistory[Index].TouchCoord - Drag.UpdateHistory[Index0].TouchCoord);
SwipeTime = Drag.UpdateHistory[Index].TouchTime - Drag.UpdateHistory[Index0].TouchTime;
// Find the final velocity.
Velocity = (SwipeTime > 0) ? (SwipeDelta / SwipeTime) : 0.0f;
// Using acceleration formulas - find how far it should take to stop and how long that will take.
FinalScrollDist = Square(Velocity) / (2.0 * Deacceleration);
//`log("Delta:" $ SwipeDelta @ "Vel:" $ Velocity @ "Dist:" $ FinalScrollDist);
}
if (bDisableScrolling)
FinalScrollDist = 0;
// See how far we will really go since we want to lock in selected position (no adjust)
if (SwipeDelta < 0)
CalcScrollDist = CalculateSelectedItem(SelectedItem, -FinalScrollDist, true);
else
CalcScrollDist = CalculateSelectedItem(SelectedItem, FinalScrollDist, true);
// If we don't have to scroll to selected, then use our original scroll dist.
// We still have to call CalculateSelectedItem() because it will tell us if bEndOfList.
// In that case, allow to auto scroll back to selected item.
if (!bForceSelectedToLineup && !SelectedItem.bEndOfList)
{
if (SwipeDelta < 0)
CalcScrollDist = -FinalScrollDist;
else
CalcScrollDist = FinalScrollDist;
}
// Restore...since we were looking into the future.
SelectedItem = Movement.OrigSelectedItem;
// Given this desired distance, and a little algebra on D = (D/T)**2/(2A) -> T = sqrt(D /(2A))
Movement.TotalTime = Sqrt(Abs(CalcScrollDist) / (2.0 * Deacceleration));
Movement.FullMovement = CalcScrollDist;
//`log("FinalDist:" $ CalcScrollDist @ "Time:" $ Movement.TotalTime);
}
else
{
Drag.UpdateHistory[Drag.NumUpdates % NumInDragHistory].TouchTime = Drag.TouchTime;
Drag.UpdateHistory[Drag.NumUpdates % NumInDragHistory].TouchCoord = (bIsVerticalList) ? TouchY : TouchX;
Drag.NumUpdates++;
if (Drag.OrigSelectedItem.Index != SelectedItem.Index)
{
Drag.bHasSelectedChanged = true;
}
Drag.ScrollAmount = (bIsVerticalList) ? (Drag.StartTouch.Y - TouchY) : (Drag.StartTouch.X - TouchX);
Index = (Drag.NumUpdates - 1) % NumInDragHistory;
Index0 = (Drag.NumUpdates - Min(Drag.NumUpdates, NumInDragHistory)) % NumInDragHistory;
SwipeDelta = abs(Drag.UpdateHistory[Index].TouchCoord - Drag.UpdateHistory[Index0].TouchCoord);
if (bDisableScrolling)
{
Drag.ScrollAmount = 0;
SwipeDelta = 0;
}
Drag.AbsScrollAmount += SwipeDelta;
//`log(Drag.AbsScrollAmount);
}
if (bUdpateTouchItem)
{
if (Drag.TouchedItem != none)
{
// If user has moved off of item, still update it, but indicate that we are no longer over it with -1.
if (Drag.TouchedItem == GetItemClickPosition(TouchX, TouchY))
{
Drag.TouchedItem.OnTouch(EventType, TouchX, TouchY, DeltaTime);
}
else
{
Drag.TouchedItem.OnTouch(EventType, -1, -1, DeltaTime);
}
}
}
return true;
}
function MobileMenuListItem GetItemClickPosition(out float MouseX, out float MouseY)
{
local int ScrollAmount, CurIndex, ScrollSize;
local MobileMenuListItem Item;
ScrollAmount = (bIsVerticalList) ? MouseY : MouseX;
ScrollAmount -= SelectedOffset;
// First attempt to scroll list up/left (if user swiped down/right)
// ScrollSize needs to be size of SelectedIndex after loop...
CurIndex = fMax(0, SelectedItem.Index); // Avoid [] out of bounds.
if (CurIndex >= Items.Length)
return none;
Item = Items[CurIndex];
ScrollSize = ItemScrollSize(Item);
while (ScrollAmount < 0)
{
if (CurIndex > 0)
CurIndex--;
else if (bLoops)
CurIndex = Items.Length - 1;
else
break;
Item = Items[CurIndex];
if (Item.bIsVisible)
{
ScrollSize = ItemScrollSize(Item);
ScrollAmount += ScrollSize;
}
}
// Now see if we need to go other way (because user swiped up/left)
while (ScrollAmount > ScrollSize)
{
if (CurIndex < (Items.length - 1))
CurIndex++;
else if (bLoops)
CurIndex = 0;
else
break;
Item = Items[CurIndex];
if (Item.bIsVisible)
{
ScrollAmount -= ScrollSize;
ScrollSize = ItemScrollSize(Item);
}
}
if (bIsVerticalList)
{
MouseY = ScrollAmount;
if (ScrollAmount < 0 || ScrollAmount > Item.Height)
{
Item = none;
}
}
else
{
MouseX = ScrollAmount;
if (ScrollAmount < 0 || ScrollAmount > Item.Width)
{
Item = none;
}
}
return Item;
}
function float CalculateSelectedItem(out SelectedMenuItem Selected, float ScrollAmount, bool bForceZeroAdjustment)
{
local float AdjustValue, ScrollSize, Scrolled, HalfScroll;
local int CurIndex;
local MobileMenuListItem Item;
AdjustValue = Selected.Offset;
// First scroll so that selected item is even.
Scrolled = AdjustValue;
ScrollAmount -= AdjustValue;
// First attempt to scroll list up/left (if user swiped down/right)
// ScrollSize needs to be size of SelectedIndex after loop...
CurIndex = fMax(0, Selected.Index); // Avoid [] out of bounds.
if (CurIndex >= Items.Length)
return 0;
Item = Items[CurIndex];
ScrollSize = ItemScrollSize(Item);
Selected.bEndOfList = false;
while (ScrollAmount < 0)
{
if (CurIndex > 0)
{
CurIndex--;
}
else if (bLoops)
{
CurIndex = Items.Length - 1;
}
else
{
// We are at top item - cause dragging to be less effective.
ScrollAmount *= EndOfListSupression;
Selected.bEndOfList = true;
break;
}
Item = Items[CurIndex];
if (Item.bIsVisible)
{
ScrollSize = ItemScrollSize(Item);
ScrollAmount += ScrollSize;
Scrolled -= ScrollSize;
Selected.Index = CurIndex;
}
}
// Now see if we need to go other way (because user swiped up/left or we went too far above)
HalfScroll = (ScrollSize/2);
while (ScrollAmount > HalfScroll)
{
if (CurIndex < (Items.length - (NumShowEndOfList + 1)))
{
CurIndex++;
}
else if (bLoops)
{
CurIndex = 0;
}
else
{
// We are at bottom item - cause dragging to be less effective.
// Need to take out the half scroll so it does not jump on transition from when
// this code is not executed, and when it is.
ScrollAmount -= HalfScroll;
ScrollAmount *= EndOfListSupression;
ScrollAmount += HalfScroll;
Selected.bEndOfList = true;
break;
}
Item = Items[CurIndex];
if (Item.bIsVisible)
{
ScrollAmount -= ScrollSize;
Scrolled += ScrollSize;
Selected.Index = CurIndex;
ScrollSize = ItemScrollSize(Item);
}
}
if (bForceZeroAdjustment)
{
Selected.Offset = 0;
}
else
{
Selected.Offset = -ScrollAmount;
Scrolled -= ScrollAmount;
}
return Scrolled;
}
function UpdateScroll(float DeltaTime)
{
local float ScrollAmount;
if (Drag.bIsDragging)
{
SelectedItem = Drag.OrigSelectedItem;
ScrollAmount = Drag.ScrollAmount;
}
else if (Movement.bIsMoving)
{
SelectedItem = Movement.OrigSelectedItem;
Movement.CurrentTime += DeltaTime;
if (Movement.CurrentTime < Movement.TotalTime)
{
ScrollAmount = FInterpEaseOut(0, Movement.FullMovement, Movement.CurrentTime/Movement.TotalTime, EaseOutExp);
//`log(ScrollAmount $ "=" $ Movement.FullMovement @ "Fraction:" $ Movement.CurrentTime/Movement.TotalTime );
}
else
{
ScrollAmount = Movement.FullMovement;
Movement.bIsMoving = false;
}
}
else
{
return;
}
CalculateSelectedItem(SelectedItem, ScrollAmount, false);
}
/**
* Render the widget
*
* @param Canvas - the canvas object for drawing
*/
function RenderObject(canvas Canvas, float DeltaTime)
{
local MobileMenuListItem Item;
local float OrgX, OrgY; //, ClipX, ClipY;
local int VpEnd, CurIndex, First, Last, SelectedIdx, NumItems, RealIndex;
local Vector2D VpPos, VpSize;
NumItems = Items.Length;
if (NumItems == 0)
return;
UpdateScroll(DeltaTime);
VpSize.X = Width;
VpSize.Y = Height;
// Find top displayed visible item.
SelectedIdx = fMax(0, SelectedItem.Index); // Avoid [] out of bounds.
// If we loop, then we add NumItems for 0 compares but always mod to get real index.
if (bLoops)
SelectedIdx += NumItems;
First = SelectedIdx;
if (bIsVerticalList)
{
VpPos.X = Left;
VpPos.Y = Top + SelectedOffset + SelectedItem.Offset;
VpEnd = Top + Height;
while ((First > 0) && (VpPos.Y > Top))
{
First--;
Item = Items[First % NumItems];
if (Item.bIsVisible)
VpPos.Y -= Item.Height;
}
}
else
{
VpPos.X = Left + SelectedOffset + SelectedItem.Offset;
VpPos.Y = Top;
VpEnd = Left + Width;
while ((First > 0) && (VpPos.X > Left))
{
First--;
Item = Items[First % NumItems];
if (Item.bIsVisible)
VpPos.X -= Item.Width;
}
}
// Make sure our First is actually visible.
while ((First + 1) < NumItems)
{
Item = Items[First];
if (Item.bIsVisible)
break;
First++;
};
// Calculate viewports for everyone
Last = First;
for (CurIndex = 0; CurIndex < NumItems; CurIndex++)
{
RealIndex = (bLoops) ? ((First + CurIndex) % NumItems) : (First + CurIndex);
if (RealIndex >= NumItems)
{
break;
}
Item = Items[RealIndex];
if (Item.bIsVisible)
{
Last = First + CurIndex;
if (bIsVerticalList)
{
VpSize.Y = Item.Height;
Item.VpPos = VpPos;
Item.VpSize = VpSize;
VpPos.Y += VpSize.Y;
if (VpPos.Y >= VpEnd)
break;
}
else
{
VpSize.X = Item.Width;
Item.VpPos = VpPos;
Item.VpSize = VpSize;
VpPos.X += VpSize.X;
if (VpPos.X >= VpEnd)
break;
}
}
}
OrgX = Canvas.OrgX;
OrgY = Canvas.OrgY;
// Does not good :(
//ClipX = Canvas.ClipX;
//ClipY = Canvas.ClipY;
//Canvas.ClipX = Left + Width;
//Canvas.ClipY = Top + Height;
// Now render up to (not including) selected, then backwards to and including selected.
// This is so if we render larger that our VP, the selected on will be top.
// Normally these loop conditions do not kick it out, it is the check with RealIndex, the
// conditions are just safety checks (like a small list)
for (CurIndex = First; CurIndex < SelectedIdx; CurIndex++)
{
Item = Items[CurIndex % NumItems];
if (Item.bIsVisible)
{
Canvas.SetOrigin(Item.VpPos.X, Item.VpPos.Y);
Item.RenderItem(self, Canvas, DeltaTime);
}
}
for (CurIndex = Last; CurIndex >= SelectedIdx; CurIndex--)
{
Item = Items[CurIndex % NumItems];
if (Item.bIsVisible)
{
Canvas.SetOrigin(Item.VpPos.X, Item.VpPos.Y);
Item.RenderItem(self, Canvas, DeltaTime);
}
}
FirstVisible = First;
LastVisible = Last;
// Restore to not mess up next scene.
Canvas.OrgX = OrgX;
Canvas.OrgY = OrgY;
//Canvas.ClipX = ClipX;
//Canvas.ClipY = ClipY;
}
/** Amount of space item takes up in scroll direction */
function int ItemScrollSize(MobileMenuListItem Item)
{
return (bIsVerticalList) ? Item.Height : Item.Width;
}
defaultproperties
{
NumShowEndOfList=0
bIsActive=true
bIsVerticalList=true
bTapToScrollToItem=true
Deacceleration = 1500
EaseOutExp=4.0
EndOfListSupression=0.4f
SelectedItem=(Index=0, Offset=0)
SelectedOffset = 0
bForceSelectedToLineup=true
}

View File

@ -0,0 +1,28 @@
/**
* MobileMenuListItem
* Interface for any object that is stored in a MobileMenuList.
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuListItem extends MobileMenuElement;
/** User sets these values and the MobileMenuElement.VpPos and MobileMenuElement.VpSize will be set when (if) rendered */
var float Width;
var float Height;
/* Render a visible element of the container.
*
* @Param List - List that is rendering item.
* @param Canvas - Render system.
* @Param DeltaTime - Time since last render.
*/
function RenderItem(MobileMenuList List, Canvas Canvas, float DeltaTime);
defaultproperties
{
bIsVisible=true
}

View File

@ -0,0 +1,286 @@
/**
* MobileMenuObject
* This is the base of all Mobile UI Menu Widgets
*
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuObject extends object
native;
struct native UVCoords
{
var() bool bCustomCoords;
/** The UV coords. */
var() float U;
var() float V;
var() float UL;
var() float VL;
};
/** If true, the object has been initialized to the screen size (note, it will not work if the screen size changes) */
var transient bool bHasBeenInitialized;
/** The left position of the menu. */
var float Left;
/** The top position of the menu. */
var float Top;
/** The width of the menu. */
var float Width;
/** The height of the menu */
var float Height;
/** Initial location/size, used for resizing the menu */
var float InitialLeft;
var float InitialTop;
var float InitialWidth;
var float InitialHeight;
/** If any of the bRelativeXXXX vars are set, then the value will be considered a percentage of the viewport */
var bool bRelativeLeft;
var bool bRelativeTop;
var bool bRelativeWidth;
var bool bRelativeHeight;
/** If any of these are set, then the Global scsale will be applied */
var bool bApplyGlobalScaleLeft;
var bool bApplyGlobalScaleTop;
var bool bApplyGlobalScaleWidth;
var bool bApplyGlobalScaleHeight;
/** This is the scale factor you are authoring for. 2.0 is useful for Retina display resolution (960x640), 1.0 for iPads and older iPhones */
var (Bounds) float AuthoredGlobalScale;
/** The Leeway values all you to subtle adjust the hitbox for an object.*/
var float TopLeeway;
var float BottomLeeway;
var float LeftLeeway;
var float RightLeeway;
var bool bHeightRelativeToWidth;
/** The XOffset and YOffset can be used to shift the position of the widget within it's bounds. */
var float XOffset;
var float YOffset;
/** Unlike Left/Top/Width/Height the XOffset and YOffsets are assumed to be a percentage of the bounds. If you
wish to use actual offsets. set one of the variables below */
var bool bXOffsetIsActual;
var bool bYOffsetIsActual;
/** Holds the tag of this widget */
var string Tag;
/** If true, this control is considered to be active and accepts taps */
var bool bIsActive;
/** If true, this control is hidden and will not be rendered */
var bool bIsHidden;
/** If true, this control is being touched/pressed */
var bool bIsTouched;
/** If true, this control is highlighted (like a radio button) */
var bool bIsHighlighted;
/** A reference to the input owner */
var MobilePlayerInput InputOwner;
/** Holds the opacity of an object */
var float Opacity;
/** The scene this object is in */
var MobileMenuScene OwnerScene;
/** You can set RelativeToTag to the Tag of an object,
and this Left,Top is an offset to the Left,Top of RelativeTo*/
var String RelativeToTag;
var MobileMenuObject RelativeTo;
/** Tell scene before we are rendering this Object - Allows rendering between menu objects */
var bool bTellSceneBeforeRendering;
/**
* This event is called when a "touch" event is detected on the object.
* If false is returned (unhandled) event will be passed to scene.
*
* @param EventType - type of event
* @param TouchX - The X location of the touch event
* @param TouchY - The Y location of the touch event
* @param ObjectOver - The Object that mouse is over (NOTE: May be NULL or another object!)
* @param DeltaTime - Time since last update.
*/
event bool OnTouch(ETouchType EventType, float TouchX, float TouchY, MobileMenuObject ObjectOver, float DeltaTime)
{
return false;
}
/*
* Figure out real position of object taking in consideration OwnerScnen and RelativeTO
* @param PosX - True X Position
* @param PosY - True Y Position
*/
event GetRealPosition(out float PosX, out float PosY)
{
if (RelativeTo == none)
{
PosX = OwnerScene.Left + Left;
PosY = OwnerScene.Top + Top;
}
else
{
RelativeTo.GetRealPosition(PosX, PosY);
PosX += Left;
PosY += Top;
}
}
/**
* InitMenuObject - Perform any initialization here
*
* @param PlayerInput - A pointer to the MobilePlayerInput object that owns the UI system
* @param Scene - The scene this object is in
* @param ScreenWidth - The Width of the Screen
* @param ScreenHeight - The Height of the Screen
* @param bIsFirstInitialization - If True, this is the first time the menu is being initialized. If False, it's a result of the screen being resized
*/
function InitMenuObject(MobilePlayerInput PlayerInput, MobileMenuScene Scene, int ScreenWidth, int ScreenHeight, bool bIsFirstInitialization)
{
local int X,Y,W,H,oX,oY,RelativeIdx;
// First out the bounds.
InputOwner = PlayerInput;
OwnerScene = Scene;
if (Len(RelativeToTag) > 0)
{
RelativeIdx = int(RelativeToTag);
if (String(RelativeIdx) != RelativeToTag)
{
RelativeTo = Scene.FindMenuObject(RelativeToTag);
}
else
{
RelativeIdx += Scene.MenuObjects.find(self);
RelativeTo = Scene.MenuObjects[RelativeIdx];
}
}
// don't reinitialize the view coords
if (!bHasBeenInitialized || !bIsFirstInitialization)
{
if (bIsFirstInitialization)
{
InitialTop = Top;
InitialLeft = Left;
InitialWidth = Width;
InitialHeight = Height;
}
else
{
Top = InitialTop;
Left = InitialLeft;
Width = InitialWidth;
Height = InitialHeight;
}
X = bRelativeLeft ? Scene.Width * Left : Left;
Y = bRelativeTop ? Scene.Height * Top : Top;
W = bRelativeWidth ? Scene.Width * Width : Width;
if (bHeightRelativeToWidth)
{
H = W * Height;
}
else
{
H = bRelativeHeight ? Scene.Height * Height : Height;
}
if (bApplyGlobalScaleLeft)
{
X *= Scene.static.GetGlobalScaleX() / AuthoredGlobalScale;
}
if (bApplyGlobalScaleTop)
{
Y *= Scene.static.GetGlobalScaleY() / AuthoredGlobalScale;
}
if (bApplyGlobalScaleWidth)
{
W *= Scene.static.GetGlobalScaleX() / AuthoredGlobalScale;
}
if (bApplyGlobalScaleHeight)
{
H *= Scene.static.GetGlobalScaleY() / AuthoredGlobalScale;
}
if (RelativeTo == none)
{
if (X<0) X = Scene.Width + X;
if (Y<0) Y = Scene.Height + Y;
if (W<0) W = Scene.Width + W;
if (H<0) H = Scene.Height + H;
}
// Copy them back in to place
Left = X;
Top = Y;
Width = W;
Height = H;
// Now we figure out the render bounds. To figure out the render bounds, we need the
// position + offsets.
oX = bXOffsetIsActual ? XOffset : Width * XOffset;
oY = bYOffsetIsActual ? YOffset : Height * YOffset;
// Calculate the actual render bounds based on the data
Left -= oX;
Top -= oY;
//`log(" InitMenuObject::"$Tag@"["$ScreenWidth@ScreenHeight$"] ["@Left@Top@Width@Height$"]"@OwnerScene@Scene);
}
// mark as initialized
bHasBeenInitialized = TRUE;
}
/**
* Call Canvas.SetPos based on Left,Top and taking into consideration the OwnerScene and RelativeTo Object.
* @param OffsetX - Optional additional Offset to apply
* @param OffsetX - Optional additional Offset to apply
*/
function SetCanvasPos(Canvas Canvas, optional float OffsetX = 0, optional float OffsetY = 0)
{
local float PosX, PosY;
GetRealPosition(PosX, PosY);
Canvas.SetPos(PosX + OffsetX, PosY + OffsetY);
}
/**
* Render the widget
*
* @param Canvas - the canvas object for drawing
*/
function RenderObject(canvas Canvas, float DeltaTime)
{
// `log("Object " $ Class $ "." $ Name $ "needs to have a RenderObject function");
}
defaultproperties
{
Opacity=1.0
AuthoredGlobalScale=2.0
bTellSceneBeforeRendering=false
}

View File

@ -0,0 +1,43 @@
/**
* MobileMenuObjectProxy -
* Allow another class to handle touches and draws.
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuObjectProxy extends MobileMenuObject;
delegate bool OnTouchEvent(MobileMenuObjectProxy Proxy, ETouchType EventType, float TouchX, float TouchY, MobileMenuObject ObjectOver, float DeltaTime);
delegate OnRenderObject(MobileMenuObjectProxy Proxy, canvas Canvas, float DeltaTime);
/**
* This event is called when a "touch" event is detected on the object.
* If false is returned (unhandled) event will be passed to scene.
*
* @param EventType - type of event
* @param TouchX - The X location of the touch event
* @param TouchY - The Y location of the touch event
* @param ObjectOver - The Object that mouse is over (NOTE: May be NULL or another object!)
* @param DeltaTime - Time since last update.
*/
event bool OnTouch(ETouchType EventType, float TouchX, float TouchY, MobileMenuObject ObjectOver, float DeltaTime)
{
if (OnTouchEvent != none)
return OnTouchEvent(self, EventType, TouchX, TouchY, ObjectOver, DeltaTime);
return false;
}
/**
* Render the widget
*
* @param Canvas - the canvas object for drawing
*/
function RenderObject(canvas Canvas, float DeltaTime)
{
if (OnRenderObject != none)
OnRenderObject(self, Canvas, DeltaTime);
}
defaultproperties
{
}

View File

@ -0,0 +1,10 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuPlayerController extends GamePlayerController;
DefaultProperties
{
InputClass=class'MobilePlayerInput'
}

View File

@ -0,0 +1,282 @@
/**
* MobileMenuScene
* This is the base class for the mobile menu system
*
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileMenuScene extends object
native;
var (UI) string MenuName;
var (UI) instanced array<MobileMenuObject> MenuObjects;
/** Allows for a single font for all buttons in a scene */
var (UI) font SceneCaptionFont;
/** A reference to the input owner */
var (UI) MobilePlayerInput InputOwner;
var (UI) bool bSceneDoesNotRequireInput;
/** Which touchpad this menu will respond to */
var (UI) byte TouchpadIndex;
/** Positions and sizing */
var (Positions) float Left;
var (Positions) float Top;
var (Positions) float Width;
var (Positions) float Height;
/** Initial location/size, used for resizing the menu */
var float InitialLeft;
var float InitialTop;
var float InitialWidth;
var float InitialHeight;
var (Options) bool bRelativeLeft;
var (Options) bool bRelativeTop;
var (Options) bool bRelativeWidth;
var (Options) bool bRelativeHeight;
var (Options) bool bApplyGlobalScaleLeft;
var (Options) bool bApplyGlobalScaleTop;
var (Options) bool bApplyGlobalScaleWidth;
var (Options) bool bApplyGlobalScaleHeight;
/** This is the scale factor you are authoring for. 2.0 is useful for Retina display resolution (960x640), 1.0 for iPads and older iPhones */
var (Options) float AuthoredGlobalScale;
/** The general opacity of the scene */
var (Options) float Opacity;
/** Holds a reference to the sound to play when a touch occurs in the mobile menu system */
var (Sounds) SoundCue UITouchSound;
/** Holds a reference to the sound to play when a touch occurs in the mobile menu system */
var (Sounds) SoundCue UIUnTouchSound;
cpptext
{
/**
* Performs a hit test against all of the objects in the object stack.
*
* @param TouchX - The X location of the touch event
* @param TouchY - The Y location of the touch event
* @param Returns the object that hits.
*/
virtual UMobileMenuObject* HitTest(FLOAT TouchX, FLOAT TouchY);
}
/** Native functions to get the global scale to apply to UI elements that desire such */
native static final function float GetGlobalScaleX();
native static final function float GetGlobalScaleY();
/**
* Script events that allows for menu setup. It's called at the beginning of the native InitMenuScene. Nothing is set at this point and
* allows the scene to override default settings
* @param PlayerInput - A pointer to the MobilePlayerInput object that owns the UI scene
* @param ScreenWidth - The Width of the Screen
* @param ScreenHeight - The Height of the Screen
* @param bIsFirstInitialization - If True, this is the first time the menu is being initialized. If False, it's a result of the screen being resized
*/
event InitMenuScene(MobilePlayerInput PlayerInput, int ScreenWidth, int ScreenHeight, bool bIsFirstInitialization)
{
local int i,X,Y,W,H;
//`log("### InitMenuScene"@ MenuName @ PlayerInput @ ScreenWidth @ ScreenHeight @ MenuObjects.Length);
SceneCaptionFont = GetSceneFont();
InputOwner = PlayerInput;
if (bIsFirstInitialization)
{
InitialTop = Top;
InitialLeft = Left;
InitialWidth = Width;
InitialHeight = Height;
}
else
{
Top = InitialTop;
Left = InitialLeft;
Width = InitialWidth;
Height = InitialHeight;
}
X = (bRelativeLeft) ? ScreenWidth * Left : Left;
Y = (bRelativeTop) ? ScreenHeight * Top : Top;
W = (bRelativeWidth) ? ScreenWidth * Width : Width;
H = (bRelativeHeight) ? ScreenHeight * Height : Height;
if (bApplyGlobalScaleLeft)
{
X *= static.GetGlobalScaleX() / AuthoredGlobalScale;
}
if (bApplyGlobalScaleTop)
{
Y *= static.GetGlobalScaleY() / AuthoredGlobalScale;
}
if (bApplyGlobalScaleWidth)
{
W *= static.GetGlobalScaleX() / AuthoredGlobalScale;
}
if (bApplyGlobalScaleHeight)
{
H *= static.GetGlobalScaleY() / AuthoredGlobalScale;
}
// We now have the zone positions converted in to actual screen positions.
// If the zone position is negative, it's right/bottom justified so handle it and store the final values back
Left = X >= 0 ? X : X + ScreenWidth;
Top = Y >= 0 ? Y : Y + ScreenHeight;
Width = W >= 0 ? W : W + ScreenWidth;
Height = H >= 0 ? H : H + ScreenHeight;
for (i=0;i<MenuObjects.Length;i++)
{
MenuObjects[i].InitMenuObject(InputOwner,self,ScreenWidth,ScreenHeight,bIsFirstInitialization);
}
}
/**
* Override this function to change the font used by this Scene
*
* @return the Font to use for this scene
*/
function Font GetSceneFont()
{
return class'Engine'.Static.GetSmallFont();
}
/**
* Render the scene
*
* @param Canvas - the canvas object for drawing
*/
function RenderScene(Canvas Canvas,float RenderDelta)
{
local MobileMenuObject MenuObject;
foreach MenuObjects(MenuObject)
{
if (MenuObject.bTellSceneBeforeRendering)
{
PreRenderMenuObject(MenuObject, Canvas, RenderDelta);
}
if (!MenuObject.bIsHidden)
{
MenuObject.RenderObject(Canvas, RenderDelta);
}
}
}
/** Allow user to render between layers. */
function PreRenderMenuObject(MobileMenuObject MenuObject, Canvas Canvas,float RenderDelta)
{
}
/**
* This event is called when a touch event is detected on an object if control did not handle it.
*
* @param Sender - The Object that swallowed the touch
* @param EventType - type of event
* @param TouchX - The X location of the touch event
* @param TouchY - The Y location of the touch event
* @param Sender.bIsTouched - Is touch currently over the object
*/
event OnTouch(MobileMenuObject Sender, ETouchType EventType, float TouchX, float TouchY)
{
}
/**
* Allows the scene to manage touches that aren't sent to any given control.
* This will pass indicate if the touch was inside its bounds or not.
*/
event bool OnSceneTouch(ETouchType EventType, float TouchX, float TouchY, bool bInside)
{
// If inside, return true to swallow input.
return false;
}
/**
* Opened will be called after the scene is opened and initialized.
*
* @param Mode Optional string to pass to the scene for however it wants to use it
*/
function Opened(string Mode) {}
/**
* Called when this menu is the topmost menu (ie, when opened or when one of top was closed)
*/
function MadeTopMenu() {}
/**
* Closing will be called before the closing process really begins. Return false if
* you wish to override the closing process.
* @returns true if we can close
*/
function bool Closing()
{
return true;
}
/**
* Closed will be called when the closing process is done and the scene has been removed from the stack
*/
function Closed()
{
CleanUpScene();
}
native function CleanUpScene();
/**
* Search the menu stack for a object
*
* @param Tag - The name of the object to find.
* @returns the object
*/
function MobileMenuObject FindMenuObject(string Tag)
{
local int idx;
for (idx=0;idx<MenuObjects.Length;idx++)
{
if (Caps(MenuObjects[idx].Tag) == Caps(Tag))
{
return MenuObjects[idx];
}
}
return none;
}
/**
* Allows menus to handle exec commands
*
* @Param Command - The command to handle
* @returns true if handled
*/
function bool MobileMenuCommand(string Command)
{
return false;
}
defaultproperties
{
AuthoredGlobalScale=2.0
Opacity=1.0
bSceneDoesNotRequireInput=false;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
/**
* Example
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileSecondaryViewportClient extends SecondaryViewportClient
native;
cpptext
{
virtual void Draw(FViewport* Viewport,FCanvas* Canvas)
{
Super::Draw(Viewport, Canvas);
}
virtual UBOOL RequiresHitProxyStorage() { return 0; }
protected:
virtual void DrawSecondaryHUD(UCanvas* CanvasObject) { }
}
/**
* Called after rendering the player views and HUDs to render menus, the console, etc.
* This is the last rendering cal in the render loop
* @param Canvas - The canvas to use for rendering.
*/
event PostRender(Canvas Canvas)
{
local PlayerController PC;
local MobilePlayerInput MPI;
local MobileHUD MH;
// boost the HUD from main screen, for now
foreach class'Engine'.static.GetCurrentWorldInfo().LocalPlayerControllers(class'PlayerController', PC)
{
MPI = MobilePlayerInput(PC.PlayerInput);
if( MPI != none )
{
MH = MobileHUD(PC.myHUD);
if( MH != none )
{
MH.Canvas = Canvas;
MH.DrawInputZoneOverlays();
MH.RenderMobileMenu();
break;
}
}
}
}
defaultproperties
{
}

View File

@ -0,0 +1,67 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class MobileTouchInputVolume extends Volume
implements(TouchableElement3D)
placeable;
var bool bEnabled;
simulated function OnToggle( SeqAct_Toggle inAction )
{
if (inAction.InputLinks[0].bHasImpulse)
{
bEnabled = true;
}
else if (inAction.InputLinks[1].bHasImpulse)
{
bEnabled = false;
}
else if (inAction.InputLinks[2].bHasImpulse)
{
bEnabled = !bEnabled;
}
Super.OnToggle(inAction);
}
/** Handle being clicked by the user */
function HandleClick()
{
if( bEnabled )
{
TriggerEventClass( class'SeqEvent_MobileTouchInputVolume', self, 1);
}
}
/** Handle being double clicked by the user */
function HandleDoubleClick()
{
if( bEnabled )
{
TriggerEventClass( class'SeqEvent_MobileTouchInputVolume', self, 2);
}
}
/** Handle a touch moving over this object, and not necessarily tapping or releasing on it */
function HandleDragOver()
{
if( bEnabled )
{
TriggerEventClass( class'SeqEvent_MobileTouchInputVolume', self, 0);
}
}
defaultproperties
{
bBlockActors=false
bWorldGeometry=false
bStatic=false
bEnabled=true
bCollideActors=True
SupportedEvents.Empty
SupportedEvents(0)=class'SeqEvent_MobileTouchInputVolume'
}

View File

@ -0,0 +1,60 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class NavMeshGoal_OutOfViewFrom extends NavMeshPathGoalEvaluator
native;
// the polygon that contains our goal point
var private native pointer GoalPoly{FNavMeshPolyBase};
var vector OutOfViewLocation;
/** show debug lines **/
// TODO: prob should promote this
var bool bShowDebug;
cpptext
{
// Interface
virtual UBOOL InitializeSearch( UNavigationHandle* Handle, const FNavMeshPathParams& PathParams);
virtual UBOOL EvaluateGoal( PathCardinalType PossibleGoal, const FNavMeshPathParams& PathParams, PathCardinalType& out_GenGoal );
virtual void NotifyExceededMaxPathVisits( PathCardinalType BestGuess, PathCardinalType& out_GenGoal );
}
native function RecycleNative();
static function bool MustBeHiddenFromThisPoint( NavigationHandle NavHandle, Vector InOutOfViewLocation )
{
local NavMeshGoal_OutOfViewFrom Eval;
if( NavHandle != None )
{
Eval = NavMeshGoal_OutOfViewFrom(NavHandle.CreatePathGoalEvaluator(default.class));
if( Eval != None )
{
Eval.OutOfViewLocation = InOutOfViewLocation;
NavHandle.AddGoalEvaluator( Eval );
return TRUE;
}
}
return FALSE;
}
function Recycle()
{
RecycleNative();
Super.Recycle();
}
defaultproperties
{
bShowDebug=FALSE
}

View File

@ -0,0 +1,61 @@
/**
* This constraint is to make path searches be biased against choosing in the same set polys over and over.
* For Example: if we spawn in place a b c we do not want to spawn there again if there is a place d that matches
* our criteria for spawning of guys. We often use this for determining places that No One Has Spawned Near Here Before.
*
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class NavMeshPath_BiasAgainstPolysWithinDistanceOfLocations extends NavMeshPathConstraint
native;
/** Location to compare from **/
var transient vector Location;
var transient vector Rotation;
/** How far we want to spawn away from a previous spawn. **/
var transient float DistanceToCheck;
/** Set of places we have spawned before **/
var transient array<vector> LocationsToCheck;
cpptext
{
// Interface
virtual UBOOL EvaluatePath( FNavMeshEdgeBase* Edge, FNavMeshEdgeBase* PredecessorEdge, FNavMeshPolyBase* SrcPoly, FNavMeshPolyBase* DestPoly, const FNavMeshPathParams& PathParams, INT& out_PathCost, INT& out_HeuristicCost, const FVector& EdgePoint );
}
static function bool BiasAgainstPolysWithinDistanceOfLocations ( NavigationHandle NavHandle, const vector InLocation, const rotator InRotation, const float InDistanceToCheck, const array<vector> InLocationsToCheck )
{
local NavMeshPath_BiasAgainstPolysWithinDistanceOfLocations Con;
if( NavHandle != None )
{
Con = NavMeshPath_BiasAgainstPolysWithinDistanceOfLocations(NavHandle.CreatePathConstraint(default.class));
if( Con != None )
{
Con.Location = InLocation;
Con.Rotation = vector(InRotation);
Con.DistanceToCheck = InDistanceToCheck;
Con.LocationsToCheck = InLocationsToCheck;
NavHandle.AddPathConstraint( Con );
return TRUE;
}
}
return FALSE;
}
function Recycle()
{
Super.Recycle();
}
defaultproperties
{
}

View File

@ -0,0 +1,21 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class PMESTG_LeaveADecalBase extends ParticleModuleEventSendToGame
abstract
hidecategories(Object);
var class<PhysicalMaterialPropertyBase> PhysicalMaterialPropertyClass;
defaultproperties
{
}

View File

@ -0,0 +1,51 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class PlayerCollectorGame extends GameInfo;
/** The number of clients that are expected to join before we seamless travel */
var int NumberOfClientsToWaitFor;
/** URL of the actual game to travel to when all clients join */
var string URLToLoad;
event PlayerController Login(string Portal, string Options, const UniqueNetID UniqueID, out string ErrorMessage)
{
local PlayerController PC;
// perform default behavior to maket he controller
PC = super.Login(Portal, Options, UniqueID, ErrorMessage);
// if the PC failed to login, don't use it
if (PC == none)
{
return none;
}
// handle the server starting up
if (NumberOfClientsToWaitFor == 0)
{
// default to waiting on one client
NumberOfClientsToWaitFor = GetIntOption(Options, "NumClients", 1);
URLToLoad = ParseOption(Options, "SubMap");
URLToLoad = URLToLoad $ "?game=" $ ParseOption(Options, "SubGame");
}
else
{
NumberOfClientsToWaitFor--;
}
// when everyone has joined, seamless travel to the actual map
if (NumberOfClientsToWaitFor == 0)
{
`log("SEAMLESS TRAVELING TO " $ URLToLoad);
WorldInfo.SeamlessTravel(URLToLoad, true, );
}
return PC;
}
event GetSeamlessTravelActorList(bool bToEntry, out array<Actor> ActorList)
{
// add nothing we just want to start from scratch in this case
}

View File

@ -0,0 +1,32 @@
/**
* Example
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SecondaryViewportClient extends ScriptViewportClient
native;
cpptext
{
virtual void Draw(FViewport* Viewport,FCanvas* Canvas);
virtual UBOOL RequiresHitProxyStorage() { return 0; }
protected:
virtual UCanvas* InitCanvas(FViewport* Viewport, FCanvas* Canvas);
virtual void DrawSecondaryHUD(UCanvas* CanvasObject);
}
/**
* Called after rendering the player views and HUDs to render menus, the console, etc.
* This is the last rendering cal in the render loop
* @param Canvas - The canvas to use for rendering.
*/
event PostRender(Canvas Canvas)
{
}
defaultproperties
{
}

View File

@ -0,0 +1,47 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_ControlGameMovie extends SeqAct_Latent
native;
/** Which movie to play */
var(Movie) string MovieName;
/** When the fading in from just audio to audio and video should occur **/
var(Movie) int StartOfRenderingMovieFrame;
/** When the fading from audio and video to just audio should occur **/
var(Movie) int EndOfRenderingMovieFrame;
cpptext
{
/**
* Executes the action when it is triggered
*/
void Activated();
UBOOL UpdateOp(FLOAT deltaTime);
}
defaultproperties
{
ObjName="Control Game Movie"
ObjCategory="Cinematic"
bAutoActivateOutputLinks=FALSE
InputLinks(0)=(LinkDesc="Play")
InputLinks(1)=(LinkDesc="Stop")
OutputLinks(0)=(LinkDesc="Out")
OutputLinks(1)=(LinkDesc="Movie Completed")
VariableLinks.Empty
VariableLinks(0)=(ExpectedType=class'SeqVar_String',LinkDesc="MovieName",PropertyName=MovieName)
StartOfRenderingMovieFrame=-1
EndOfRenderingMovieFrame=-1
}

View File

@ -0,0 +1,44 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_Deproject extends SequenceAction
native;
/** The X location you wish to trace out from */
var() float ScreenX;
/** The Y location you wish to trace out from */
var() float ScreenY;
/** How far out should we trace */
var() float TraceDistance;
/** The object that was hit */
var object HitObject;
/** The location where the hit occured */
var vector HitLocation;
/** The hit normal */
var vector HitNormal;
cpptext
{
void Activated();
};
defaultproperties
{
ObjName="Deproject"
ObjCategory="Level"
VariableLinks(0)=(ExpectedType=class'SeqVar_float',LinkDesc="X",bWriteable=true,PropertyName=ScreenX)
VariableLinks(1)=(ExpectedType=class'SeqVar_float',LinkDesc="Y",bWriteable=true,PropertyName=ScreenY)
VariableLinks(2)=(ExpectedType=class'SeqVar_Object',LinkDesc="Hit Object",bWriteable=true,PropertyName=HitObject)
VariableLinks(3)=(ExpectedType=class'SeqVar_Vector',LinkDesc="Hit Location",bWriteable=true,PropertyName=HitLocation)
VariableLinks(4)=(ExpectedType=class'SeqVar_Vector',LinkDesc="Hit Normal",bWriteable=true,PropertyName=HitNormal)
TraceDistance=20480
}

View File

@ -0,0 +1,161 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_GameCrowdPopulationManagerToggle extends SequenceAction
implements(GameCrowdSpawnerInterface)
native;
/** Percentage of max population to immediately spawn when the population manager is toggled on (without respecting visibility checks). Range is 0.0 to 1.0 */
var() float WarmupPopulationPct;
/** List of Archetypes of agents for pop manager to spawn when this is toggled on */
var() GameCrowd_ListOfAgents CrowdAgentList;
/** If true, clear old population manager archetype list rather than adding to it with this toggle action's CrowdAgentList. */
var() bool bClearOldArchetypes;
/** The maximum number of agents alive at one time. */
var() int MaxAgents;
/** How many agents per second will be spawned at the target actor(s). */
var() float SpawnRate;
/** Whether to enable the light environment on crowd members. */
var() bool bEnableCrowdLightEnvironment;
/** Whether agents from this spawner should cast shadows */
var() bool bCastShadows;
/** Lighting channels to put the agents in. */
var LightingChannelContainer AgentLightingChannel;
/** Max distance allowed for spawns */
var() float MaxSpawnDist;
/** Square of min distance allowed for in line of sight but out of view frustrum agent spawns */
var float MinBehindSpawnDist;
/** List of all GameCrowdDestinations that are PotentialSpawnPoints */
var array<GameCrowdDestination> PotentialSpawnPoints;
var bool bFillPotentialSpawnPoints;
/** Average time to "warm up" spawned agents before letting them sleep if not rendered */
var float AgentWarmupTime;
var() int NumAgentsToTickPerFrame;
/** If true, force obstacle checking for all agents from this spawner */
var() bool bForceObstacleChecking;
/** If true, force nav mesh navigation for all agents from this spawner */
var() bool bForceNavMeshPathing;
var bool bIndividualSpawner;
var array<GameCrowdAgent> LastSpawnedList;
cpptext
{
virtual void Activated();
UBOOL UpdateOp(FLOAT DeltaTime);
};
static event int GetObjClassVersion()
{
return Super.GetObjClassVersion() + 5;
}
event FillCrowdSpawnInfoItem( out CrowdSpawnInfoItem out_Item, GameCrowdPopulationManager PopMgr )
{
local int i;
// if wanted, clear out current list of agent archetypes
if( bClearOldArchetypes )
{
out_Item.AgentArchetypes.Length = 0;
}
if( CrowdAgentList != None )
{
// get new agent archetypes to use from kismet action
for( i = 0; i < CrowdAgentList.ListOfAgents.Length; i++ )
{
out_Item.AgentArchetypes[out_Item.AgentArchetypes.Length] = CrowdAgentList.ListOfAgents[i];
}
}
out_Item.MaxSpawnDist = MaxSpawnDist;
out_Item.MaxSpawnDistSq = out_Item.MaxSpawnDist * out_Item.MaxSpawnDist;
out_Item.MinBehindSpawnDist = FMin( MinBehindSpawnDist, out_Item.MaxSpawnDist * 0.0625 );
out_Item.MinBehindSpawnDistSq = out_Item.MinBehindSpawnDist * out_Item.MinBehindSpawnDist;
out_Item.AgentWarmupTime = AgentWarmupTime;
out_Item.bCastShadows = bCastShadows;
out_Item.bEnableCrowdLightEnvironment = bEnableCrowdLightEnvironment;
out_Item.SpawnRate = SpawnRate;
out_Item.SpawnNum = MaxAgents;
if( class'Engine'.static.IsSplitScreen() )
{
out_Item.SpawnNum = PopMgr.SplitScreenNumReduction * float(out_Item.SpawnNum);
}
out_Item.bForceObstacleChecking = bForceObstacleChecking;
out_Item.bForceNavMeshPathing = bForceNavMeshPathing;
out_Item.NumAgentsToTickPerFrame = NumAgentsToTickPerFrame;
out_Item.LastAgentTickedIndex = -1;
if( bFillPotentialSpawnPoints )
{
out_Item.PotentialSpawnPoints = PotentialSpawnPoints;
}
}
/**
* GameCrowdSpawnerInterface
*/
function float GetMaxSpawnDist()
{
return MaxSpawnDist;
}
function AgentDestroyed( GameCrowdAgent Agent )
{
local GameCrowdPopulationManager PopMgr;
PopMgr = GameCrowdPopulationManager(Agent.WorldInfo.PopulationManager);
if( PopMgr != None )
{
PopMgr.AgentDestroyed( Agent );
}
}
defaultproperties
{
bCallHandler=FALSE
bLatentExecution=TRUE
ObjName="Population Manager Toggle"
ObjCategory="Crowd"
InputLinks(0)=(LinkDesc="Start")
InputLinks(1)=(LinkDesc="Stop")
InputLinks(2)=(LinkDesc="Warmup")
InputLinks(3)=(LinkDesc="Kill Agents")
InputLinks(4)=(LinkDesc="Stop & Kill")
OutputLinks.Empty
OutputLinks(0)=(LinkDesc="Spawned")
VariableLinks.Empty
VariableLinks(0)=(LinkDesc="Spawned List",ExpectedType=class'SeqVar_ObjectList',bWriteable=TRUE,MaxVars=1,PropertyName=LastSpawnedList)
SpawnRate=50
MaxAgents=700
AgentWarmupTime=2.0
MaxSpawnDist=10000.0
MinBehindSpawnDist=5000.0
NumAgentsToTickPerFrame=10
bForceObstacleChecking=FALSE
bForceNavMeshPathing=TRUE
AgentLightingChannel=(Crowd=TRUE,bInitialized=TRUE)
}

View File

@ -0,0 +1,22 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_GameCrowdSpawner extends SeqAct_GameCrowdPopulationManagerToggle
abstract
native;
static event int GetObjClassVersion()
{
return Super.GetObjClassVersion() + 5;
}
defaultproperties
{
ObjName="Game Scripted Crowd Spawner"
ObjCategory="Crowd"
VariableLinks(1)=(ExpectedType=class'SeqVar_Object',LinkDesc="Spawn Points",PropertyName=PotentialSpawnPoints)
bFillPotentialSpawnPoints=TRUE
bIndividualSpawner=TRUE
}

View File

@ -0,0 +1,26 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_MobileAddInputZones extends SequenceAction
native;
cpptext
{
void Activated();
};
/** Name for this zone, it will be used in Kismet zone input events */
var() name ZoneName;
/** All the details needed to set up a zone */
var() editinline MobileInputZone NewZone;
defaultproperties
{
ObjName="Add Input Zone"
ObjCategory="Mobile"
InputLinks(0)=(LinkDesc="In")
OutputLinks(0)=(LinkDesc="Out")
VariableLinks.Empty
}

View File

@ -0,0 +1,20 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_MobileClearInputZones extends SequenceAction
native;
cpptext
{
void Activated();
};
defaultproperties
{
ObjName="Clear Input Zones"
ObjCategory="Mobile"
InputLinks(0)=(LinkDesc="In")
OutputLinks(0)=(LinkDesc="Out")
VariableLinks.Empty
}

View File

@ -0,0 +1,22 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_MobileRemoveInputZone extends SequenceAction
native;
cpptext
{
void Activated();
};
var() string ZoneName;
defaultproperties
{
ObjName="Remove Input Zone"
ObjCategory="Mobile"
InputLinks(0)=(LinkDesc="In")
OutputLinks(0)=(LinkDesc="Out")
VariableLinks.Empty
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_MobileSaveLoadValue extends SequenceAction
native;
cpptext
{
void Activated();
void DeActivated();
};
/**
* Return the version number for this class. Child classes should increment this method by calling Super then adding
* a individual class version to the result. When a class is first created, the number should be 0; each time one of the
* link arrays is modified (VariableLinks, OutputLinks, InputLinks, etc.), the number that is added to the result of
* Super.GetObjClassVersion() should be incremented by 1.
*
* @return the version number for this specific class.
*/
static event int GetObjClassVersion()
{
return Super.GetObjClassVersion() + 1;
}
defaultproperties
{
ObjName="Save/Load Values"
ObjCategory="Mobile"
InputLinks(0)=(LinkDesc="Save")
InputLinks(1)=(LinkDesc="Load")
OutputLinks(0)=(LinkDesc="Saved")
OutputLinks(1)=(LinkDesc="Loaded")
VariableLinks.Empty
VariableLinks(0)=(ExpectedType=class'SeqVar_Int',LinkDesc="Int Vars")
VariableLinks(1)=(ExpectedType=class'SeqVar_Float',LinkDesc="Float Vars")
VariableLinks(2)=(ExpectedType=class'SeqVar_Bool',LinkDesc="Bool Vars")
VariableLinks(3)=(ExpectedType=class'SeqVar_Vector',LinkDesc="Vector Vars")
}

View File

@ -0,0 +1,40 @@
/**
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_ModifyProperty extends SequenceAction
native;
cpptext
{
#if WITH_EDITOR
virtual void CheckForErrors();
#endif
virtual void Activated();
};
/**
* Struct used to figure out which properties to modify.
*/
struct native PropertyInfo
{
/** Name of the property to modify */
var() Name PropertyName;
/** Should this property be modified? */
var() bool bModifyProperty;
/** New value to apply to the property */
var() string PropertyValue;
};
/** List of properties that can be modified */
var() editinline array<PropertyInfo> Properties;
defaultproperties
{
ObjName="Modify Property"
ObjCategory="Object Property"
}

View File

@ -0,0 +1,95 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqAct_PlayAgentAnimation extends SeqAct_Latent
dependson(GameCrowdAgent)
native;
/** List of animations to play while at this node */
var() Array<name> AnimationList;
var() float BlendInTime;
var() float BlendOutTime;
var() bool bUseRootMotion;
/** If true, face action target before starting animation */
var() bool bFaceActionTargetFirst;
/** If true, loop the last animation in the list forever */
var() bool bLooping;
/** Which animation to loop in AnimationList if bLooping == TRUE */
var() int LoopIndex;
/** How long to loop the animation if bLooping == TRUE, -1.f == infinite */
var() float LoopTime;
/** Whether should blend between animations in the list. Set True if they don't match at start/end */
var() bool bBlendBetweenAnims;
/** Optional other actor that actions should point at, instead of at the actual destination location. */
var actor ActionTarget;
static event int GetObjClassVersion()
{
return Super.GetObjClassVersion() + 1;
}
function SetCurrentAnimationActionFor(GameCrowdAgentSkeletal Agent)
{
local GameCrowdBehavior_PlayAnimation AnimBehavior;
local int i;
// init behavior properties
AnimBehavior = new(Agent) class'GameCrowdBehavior_PlayAnimation';
AnimBehavior.AnimSequence = self;
AnimBehavior.BlendInTime = BlendInTime;
AnimBehavior.BlendOutTime = BlendOutTime;
AnimBehavior.bUseRootMotion = bUseRootMotion;
AnimBehavior.bFaceActionTargetFirst = bFaceActionTargetFirst;
AnimBehavior.bLooping = bLooping;
AnimBehavior.LoopIndex = LoopIndex;
AnimBehavior.LoopTime = LoopTime;
AnimBehavior.bBlendBetweenAnims = bBlendBetweenAnims;
AnimBehavior.CustomActionTarget = ActionTarget;
for ( i=0; i<AnimationList.Length; i++ )
{
AnimBehavior.AnimationList[i] = AnimationList[i];
}
// activate behavior
Agent.ActivateInstancedBehavior(AnimBehavior);
}
cpptext
{
virtual void Activated();
virtual UBOOL UpdateOp(FLOAT DeltaTime);
}
defaultproperties
{
ObjName="Play Agent Animation"
ObjCategory="Crowd"
InputLinks(0)=(LinkDesc="Play")
InputLinks(1)=(LinkDesc="Stop")
OutputLinks(0)=(LinkDesc="Finished")
OutputLinks(1)=(LinkDesc="Stopped")
OutputLinks(2)=(LinkDesc="Started")
bAutoActivateOutputLinks=false
VariableLinks(1)=(ExpectedType=class'SeqVar_Object',LinkDesc="Action Focus",PropertyName=ActionTarget)
VariableLinks(2)=(ExpectedType=class'SeqVar_Object',LinkDesc="Out Agent",bWriteable=true)
BlendInTime=0.2
BlendOutTime=0.2
bBlendBetweenAnims=false
LoopTime=-1.f
}

View File

@ -0,0 +1,15 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
* To use the mobile input kismet actions with the mouse, set bFakeMobileTouches=true in the [GameFramework.MobilePlayerInput] section of your game.ini
*/
class SeqAct_ToggleMouseCursor extends SequenceAction;
defaultproperties
{
ObjName="Toggle Mouse Cursor"
ObjCategory="Input"
InputLinks(0)=(LinkDesc="Enable")
InputLinks(1)=(LinkDesc="Disable")
}

View File

@ -0,0 +1,25 @@
/**
* Originator: the pawn that owns this event
* Instigator: the pawn that was killed
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqEvent_CrowdAgentReachedDestination extends SequenceEvent
native;
cpptext
{
virtual UBOOL CheckActivate(AActor *InOriginator, AActor *InInstigator, UBOOL bTest=FALSE, TArray<INT>* ActivateIndices = NULL, UBOOL bPushTop = FALSE);
}
defaultproperties
{
ObjName="Agent Reached"
ObjCategory="Crowd"
MaxTriggerCount=0
ReTriggerDelay=0.f
bPlayerOnly=false
OutputLinks(0)=(LinkDesc="Agent Reached Destination")
VariableLinks(0)=(ExpectedType=class'SeqVar_Object',LinkDesc="Agent",bWriteable=true)
}

View File

@ -0,0 +1,56 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* This is the base class of all Mobile sequence events.
*/
class SeqEvent_HudRender extends SequenceEvent
native
abstract;
/** List of objects to call the handler function on */
var() array<Object> Targets;
var(HUD) bool bIsActive;
/** This is the scale factor you are authoring for. 2.0 is useful for Retina display resolution (960x640), 1.0 for iPads and older iPhones */
var(HUD) float AuthoredGlobalScale;
/**
* Whenever a SeqEvent_MobileBase sequence is created, it needs to find the PlayerInput that is assoicated with it and
* add it'self to the list of Kismet sequences looking for input
*/
event RegisterEvent()
{
local int i;
local GamePlayerController GPC;
local MobileHUD TargetHud;
for (i=0;i<Targets.Length;i++)
{
GPC = GamePlayerController(Targets[i]);
if (GPC != none)
{
TargetHud = MobileHud(GPC.MyHud);
if (TargetHud != none)
{
TargetHud.AddKismetRenderEvent(self);
break;
}
}
}
}
/**
* Perform the actual rendering
*/
function Render(Canvas TargetCanvas, Hud TargetHud)
{
}
defaultproperties
{
AuthoredGlobalScale=2.0
VariableLinks.Empty
VariableLinks(0)=(ExpectedType=class'SeqVar_Bool',LinkDesc="Active",PropertyName=bIsActive,MaxVars=1)
VariableLinks(1)=(ExpectedType=class'SeqVar_Object',LinkDesc="Target",PropertyName=Targets)
}

View File

@ -0,0 +1,58 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* This is the base class of all Mobile sequence events.
*/
class SeqEvent_HudRenderImage extends SeqEvent_HudRender;
/** The color to modulate the text by */
var(HUD) LinearColor DisplayColor;
/** The Location to display the text at */
var(HUD) vector DisplayLocation;
/** The texture to display */
var(HUD) Texture2D DisplayTexture;
/** The Size of the image to display */
var(HUD) float XL, YL;
/** The UVs */
var(HUD) float U,V,UL,VL;
/**
* Perform the actual rendering
*/
function Render(Canvas TargetCanvas, Hud TargetHud)
{
local float UsedX, UsedY, UsedXL, UsedYL;
local float GlobalScaleX, GlobalScaleY;
if (bIsActive)
{
PublishLinkedVariableValues();
// cache the global scales
GlobalScaleX = class'MobileMenuScene'.static.GetGlobalScaleX() / AuthoredGlobalScale;
GlobalScaleY = class'MobileMenuScene'.static.GetGlobalScaleY() / AuthoredGlobalScale;
// for floating point values, just multiply it by the canvas size
// otherwise, apply GlobalScaleFactor, while undoing the scale factor they author at
UsedX = (DisplayLocation.X < 1.0f) ? DisplayLocation.X * TargetCanvas.SizeX : (DisplayLocation.X * GlobalScaleX);
UsedY = (DisplayLocation.Y < 1.0f) ? DisplayLocation.Y * TargetCanvas.SizeY : (DisplayLocation.Y * GlobalScaleY);
UsedXL = (XL <= 1.0f) ? XL * TargetCanvas.SizeX : (XL * GlobalScaleX);
UsedYL = (YL <= 1.0f) ? YL * TargetCanvas.SizeX : (YL * GlobalScaleY);
TargetCanvas.SetPos(UsedX, UsedY);
TargetCanvas.DrawTile(DisplayTexture, UsedXL, UsedYL, U, V, UL, VL, DisplayColor);
}
}
defaultproperties
{
ObjName="Draw Image"
ObjCategory="HUD"
VariableLinks(2)=(ExpectedType=class'SeqVar_Vector',LinkDesc="Display Location",bWriteable=true,PropertyName=DisplayLocation,MaxVars=1)
}

View File

@ -0,0 +1,100 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* This is the base class of all Mobile sequence events.
*/
class SeqEvent_HudRenderText extends SeqEvent_HudRender;
enum ETextDrawMethod
{
DRAW_CenterText,
DRAW_WrapText,
};
/** The Font to draw */
var(HUD) font DisplayFont;
/** The Color to draw the text in */
var(HUD) color DisplayColor;
/** The Location to display the text at */
var(HUD) vector DisplayLocation;
/** The text to draw. NOTE: You can set this via the variable link */
var(HUD) string DisplayText;
/** Whether the text should be centered at the display location */
var(HUD) ETextDrawMethod TextDrawMethod;
/**
* Perform the actual rendering
*/
function Render(Canvas TargetCanvas, Hud TargetHud)
{
local float XL,YL;
local float UsedX, UsedY, UsedScaleX, UsedScaleY;
local float GlobalScaleX, GlobalScaleY;
if (bIsActive)
{
PublishLinkedVariableValues();
if (DisplayFont != none)
{
TargetCanvas.Font = DisplayFont;
}
// cache the global scales
GlobalScaleX = class'MobileMenuScene'.static.GetGlobalScaleX() / AuthoredGlobalScale;
GlobalScaleY = class'MobileMenuScene'.static.GetGlobalScaleY() / AuthoredGlobalScale;
// for floating point values, just multiply it by the canvas size
// otherwise, apply GlobalScaleFactor, while undoing the scale factor they author at
UsedX = (DisplayLocation.X < 1.0f) ? DisplayLocation.X * TargetCanvas.SizeX : (DisplayLocation.X * GlobalScaleX);
UsedY = (DisplayLocation.Y < 1.0f) ? DisplayLocation.Y * TargetCanvas.SizeY : (DisplayLocation.Y * GlobalScaleY);
UsedScaleX = GlobalScaleX;
UsedScaleY = GlobalScaleY;
TargetCanvas.DrawColor = DisplayColor;
if( TextDrawMethod == DRAW_WrapText )
{
TargetCanvas.SetPos(UsedX, USedY);
TargetCanvas.DrawText(DisplayText,, UsedScaleX, UsedScaleY);
}
else if( TextDrawMethod == DRAW_CenterText )
{
TargetCanvas.TextSize(DisplayText,XL,YL);
XL *= UsedScaleX;
TargetCanvas.SetPos(UsedX - XL / 2, UsedY);
TargetCanvas.DrawText(DisplayText,, UsedScaleX, UsedScaleY);
}
}
}
/**
* Return the version number for this class. Child classes should increment this method by calling Super then adding
* a individual class version to the result. When a class is first created, the number should be 0; each time one of the
* link arrays is modified (VariableLinks, OutputLinks, InputLinks, etc.), the number that is added to the result of
* Super.GetObjClassVersion() should be incremented by 1.
*
* @return the version number for this specific class.
*/
static event int GetObjClassVersion()
{
return Super.GetObjClassVersion() + 1;
}
defaultproperties
{
ObjName="Draw Text"
ObjCategory="HUD"
DisplayColor=(R=255,G=255,B=255,A=255)
VariableLinks(2)=(ExpectedType=class'SeqVar_Vector',LinkDesc="Display Location",PropertyName=DisplayLocation,MaxVars=1)
VariableLinks(3)=(ExpectedType=class'SeqVar_String',LinkDesc="Display Text",PropertyName=DisplayText,MaxVars=1)
TextDrawMethod=DRAW_WrapText
}

View File

@ -0,0 +1,58 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* This is the base class of all Mobile sequence events.
*/
class SeqEvent_MobileBase extends SequenceEvent
native
abstract;
cpptext
{
/**
* Called each frame.
* @param Originator is a reference to the PC that caused the input
* @param OriginatorInput is a reference to the mobile player input assoicated with this object
*/
virtual void Update(APlayerController* Originator, UMobilePlayerInput* OriginatorInput);
}
/**
* Whenever a SeqEvent_MobileBase sequence is created, it needs to find the PlayerInput that is assoicated with it and
* add it'self to the list of Kismet sequences looking for input
*/
event RegisterEvent()
{
local WorldInfo WI;
local GamePlayerController GPC;
local MobilePlayerInput MPI;
// Use the WorldInfo to find the current local player. TODO: Add support for specifying which Player to use via Kismet
WI = class'WorldInfo'.static.GetWorldInfo();
if (WI != none)
{
foreach WI.LocalPlayerControllers(class'GamePlayerController', GPC)
{
MPI = MobilePlayerInput(GPC.PlayerInput);
if (MPI != none)
{
AddToMobileInput(MPI);
break;
}
}
}
}
/**
* Tell the MPI to attach itself to it's list of events
*/
event AddToMobileInput(MobilePlayerInput MPI)
{
MPI.AddKismetEventHandler(self);
}
defaultproperties
{
MaxTriggerCount=0
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqEvent_MobileButton extends SeqEvent_MobileZoneBase
native;
cpptext
{
/**
* Called each frame.
* @param Originator is a reference to the PC that caused the input
* @param OriginatorInput is a reference to the mobile player input assoicated with this object
* @param OriginatorZone is a reference to the zone that caused this update
*/
void UpdateZone(APlayerController* Originator, UMobilePlayerInput* OriginatorInput, UMobileInputZone* OriginatorZone);
}
/** TRUE if the zone was active last frame (for tracking edges) */
var bool bWasActiveLastFrame;
/** If TRUE, the Input Pressed output will only trigger when a touch first happens, not every frame */
var () bool bSendPressedOnlyOnTouchDown;
/** If TRUE, the Input Pressed output will only trigger when a touch ends, not every frame. MAKE SURE RETRIGGER DELAY IS 0!!! */
var () bool bSendPressedOnlyOnTouchUp;
defaultproperties
{
ObjName="Mobile Button Access"
ObjCategory="Input"
OutputLinks(0)=(LinkDesc="Input Pressed")
OutputLinks(1)=(LinkDesc="Input Not Pressed")
}

View File

@ -0,0 +1,43 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqEvent_MobileInput extends SeqEvent_MobileZoneBase
native;
cpptext
{
/**
* Called each frame.
* @param Originator is a reference to the PC that caused the input
* @param OriginatorInput is a reference to the mobile player input assoicated with this object
* @param OriginatorZone is a reference to the zone that caused this update
*/
void UpdateZone(APlayerController* Originator, UMobilePlayerInput* OriginatorInput, UMobileInputZone* OriginatorZone);
}
/** Holds the current axis values for the device */
var float XAxisValue;
var float YAxisValue;
var float CenterX;
var float CenterY;
var float CurrentX;
var float CurrentY;
defaultproperties
{
ObjName="Mobile Input Access"
ObjCategory="Input"
OutputLinks(0)=(LinkDesc="Input Active")
OutputLinks(1)=(LinkDesc="Input Inactive")
VariableLinks(0)=(ExpectedType=class'SeqVar_Float',LinkDesc="X-Axis",bWriteable=false,PropertyName=XAxisValue)
VariableLinks(1)=(ExpectedType=class'SeqVar_Float',LinkDesc="Y-Axis",bWriteable=false,PropertyName=YAxisValue)
VariableLinks(2)=(ExpectedType=class'SeqVar_Float',LinkDesc="Center.X",bWriteable=false,PropertyName=CenterX)
VariableLinks(3)=(ExpectedType=class'SeqVar_Float',LinkDesc="Center.Y",bWriteable=false,PropertyName=CenterY)
VariableLinks(4)=(ExpectedType=class'SeqVar_Float',LinkDesc="Current.X",bWriteable=false,PropertyName=CurrentX)
VariableLinks(5)=(ExpectedType=class'SeqVar_Float',LinkDesc="Current.Y",bWriteable=false,PropertyName=CurrentY)
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqEvent_MobileLook extends SeqEvent_MobileZoneBase
native;
cpptext
{
/**
* Called each frame.
* @param Originator is a reference to the PC that caused the input
* @param OriginatorInput is a reference to the mobile player input assoicated with this object
* @param OriginatorZone is a reference to the zone that caused this update
*/
void UpdateZone(APlayerController* Originator, UMobilePlayerInput* OriginatorInput, UMobileInputZone* OriginatorZone);
}
/** Holds the current axis values for the device */
var float Yaw;
var float StickStrength;
var vector RotationVector;
defaultproperties
{
ObjName="Mobile Look"
ObjCategory="Input"
OutputLinks(0)=(LinkDesc="Input Active")
OutputLinks(1)=(LinkDesc="Input Inactive")
VariableLinks(0)=(ExpectedType=class'SeqVar_Float',LinkDesc="Yaw",bWriteable=false,PropertyName=Yaw)
VariableLinks(1)=(ExpectedType=class'SeqVar_Float',LinkDesc="Strength",bWriteable=false,PropertyName=StickStrength)
VariableLinks(2)=(ExpectedType=class'SeqVar_Vector',LinkDesc="Rotation Vector",bWriteable=false,PropertyName=RotationVector)
}

View File

@ -0,0 +1,52 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqEvent_MobileMotion extends SeqEvent_MobileBase
native;
cpptext
{
/**
* Called each frame.
* @param Originator is a reference to the PC that caused the input
* @param OriginatorInput is a reference to the mobile player input assoicated with this object
*/
void Update(APlayerController* Originator, UMobilePlayerInput* OriginatorInput);
}
var float Roll;
var float Pitch;
var float Yaw;
var float DeltaRoll;
var float DeltaPitch;
var float DeltaYaw;
/**
* Return the version number for this class. Child classes should increment this method by calling Super then adding
* a individual class version to the result. When a class is first created, the number should be 0; each time one of the
* link arrays is modified (VariableLinks, OutputLinks, InputLinks, etc.), the number that is added to the result of
* Super.GetObjClassVersion() should be incremented by 1.
*
* @return the version number for this specific class.
*/
static event int GetObjClassVersion()
{
return Super.GetObjClassVersion() + 1;
}
defaultproperties
{
ObjName="Mobile Motion Access [Old]"
ObjCategory="Input"
OutputLinks(0)=(LinkDesc="Input Active")
VariableLinks(0)=(ExpectedType=class'SeqVar_Float',LinkDesc="Pitch",bWriteable=false,PropertyName=Pitch)
VariableLinks(1)=(ExpectedType=class'SeqVar_Float',LinkDesc="Yaw",bWriteable=false,PropertyName=Yaw)
VariableLinks(2)=(ExpectedType=class'SeqVar_Float',LinkDesc="Roll",bWriteable=false,PropertyName=Roll)
VariableLinks(3)=(ExpectedType=class'SeqVar_Float',LinkDesc="Delta Pitch",bWriteable=false,PropertyName=DeltaPitch)
VariableLinks(4)=(ExpectedType=class'SeqVar_Float',LinkDesc="Delta Yaw",bWriteable=false,PropertyName=DeltaYaw)
VariableLinks(5)=(ExpectedType=class'SeqVar_Float',LinkDesc="Delta Roll",bWriteable=false,PropertyName=DeltaRoll)
}

View File

@ -0,0 +1,46 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class SeqEvent_MobileObjectPicker extends SeqEvent_MobileRawInput
native;
cpptext
{
/**
* Handle a touch event coming from the device.
*
* @param Originator is a reference to the PC that caused the input
* @param Handle the id of the touch
* @param Type What type of event is this
* @param TouchpadIndex The touchpad this touch came from
* @param TouchLocation Where the touch occurred
* @param DeviceTimestamp Input event timestamp from the device
*/
void InputTouch(APlayerController* Originator, UINT Handle, UINT TouchpadIndex, BYTE Type, FVector2D TouchLocation, DOUBLE DeviceTimestamp);
}
/** How far should this object track out to hit something */
var(mobile) float TraceDistance;
/** Should we check on touch/move as well */
var(mobile) bool bCheckonTouch;
var vector FinalTouchLocation;
var vector FinalTouchNormal;
var object FinalTouchObject;
/** List of objects that we are looking for touches on */
var() array<Object> Targets;
defaultproperties
{
ObjName="Mobile Object Picker"
ObjCategory="Input"
MaxTriggerCount=0
TraceDistance=20480
OutputLinks.Empty
OutputLinks(0)=(LinkDesc="Success")
OutputLinks(1)=(LinkDesc="Fail")
VariableLinks.Empty
VariableLinks(0)=(ExpectedType=class'SeqVar_Object',LinkDesc="Target",PropertyName=Targets)
}

Some files were not shown because too many files have changed in this diff Show More