The purpose of adding a customizable state to PlayerPawn is to allow extending the functionality of PlayerPawn without writing special subclasses of PlayerPawn. The main problem of subclassing from such basic things as PlayerPawn is a lack of scalability: two mods may be incompatible with each other due to requiring their own mutually exclusive specific sets of subclasses. Moreover, writing subclasses of PlayerPawn would be an overkill for tiny mods such as a custom inventory like jetpack (or flight boots).
When extending the functionality of PlayerPawn can be done by defining a new state, state functions might delegate the underlying operations to member functions of a special actor.
Consider jetpack as an example. State PlayerPawn.PlayerFlying seems to be close to the suitable implementation of the flight movement:
Code: Select all
state PlayerFlying
{
ignores SeePlayer, HearNoise, Bump, StartClimbing;
function AnimEnd()
{
PlaySwimming();
}
event PlayerTick( float DeltaTime )
{
if ( bUpdatePosition )
ClientUpdatePosition();
PlayerMove(DeltaTime);
}
function PlayerMove(float DeltaTime)
{
local vector X,Y,Z;
GetAxes(ViewRotation,X,Y,Z);
aForward *= 0.2;
aStrafe *= 0.2;
aLookup *= 0.24;
aTurn *= 0.24;
Acceleration = aForward*X + aStrafe*Y + aUp*Z;
if ( bPressedJump && aUp<=0.01 )
bPressedJump = False;
// Update rotation.
UpdateRotation(DeltaTime, 2);
if ( Role < ROLE_Authority ) // then save this move and replicate it
ReplicateMove(DeltaTime, Acceleration, DODGE_None, rot(0,0,0));
else
ProcessMove(DeltaTime, Acceleration, DODGE_None, rot(0,0,0));
}
event BeginState()
{
SetPhysics(PHYS_Flying);
if (!IsAnimating())
PlaySwimming();
bCanFly = true;
//log("player flying");
}
event EndState()
{
bCanFly = false;
}
}
1. Holding jump/crouch buttons should affect only vertical movement (along the z-axis):
Code: Select all
function PlayerMove(float DeltaTime)
{
local vector X,Y,Z;
GetAxes(ViewRotation,X,Y,Z);
aForward *= 0.2;
aStrafe *= 0.2;
aLookup *= 0.24;
aTurn *= 0.24;
- Acceleration = aForward*X + aStrafe*Y + aUp*Z;
+ Acceleration = aForward * X + aStrafe * Y + aUp * vect(0, 0, 1);
Code: Select all
+ function HandleWalking()
+ {
+ bIsWalking = false;
+ }
Code: Select all
function AnimEnd()
{
- PlaySwimming();
+ bAnimTransition = false;
+
+ if (!Region.Zone.bWaterZone || Acceleration Dot vector(ViewRotation) <= 0)
+ {
+ if (GetAnimGroup(AnimSequence) == 'TakeHit')
+ {
+ bAnimTransition = true;
+ TweenToWaiting(0.2);
+ }
+ else
+ PlayWaiting();
+ }
+ else
+ {
+ if (GetAnimGroup(AnimSequence) == 'TakeHit')
+ {
+ bAnimTransition = true;
+ TweenToSwimming(0.2);
+ }
+ else
+ PlaySwimming();
+ }
}
Code: Select all
function PlayerMove(float DeltaTime)
{
....
+
+ if (Region.Zone.bWaterZone)
+ SwimAnimUpdate(vector(ViewRotation) Dot Acceleration <= 0);
}
Code: Select all
event BeginState()
{
....
+ if (!IsAnimating())
+ PlayWaiting();
}
Code: Select all
+ event ZoneChange(ZoneInfo NewZone)
+ {
+ if (NewZone.bWaterZone != Region.Zone.bWaterZone)
+ {
+ if (!NewZone.bWaterZone && Region.Zone.bWaterZone && GetAnimGroup(AnimSequence) != 'Waiting')
+ TweenToWaiting(0.1);
+ }
+ global.ZoneChange(NewZone);
+ }
Code: Select all
+ static function SetFlightProperties(Pawn P)
+ {
+ P.bCanFly = true;
+
+ if (P.Physics != PHYS_Flying)
+ P.SetPhysics(PHYS_Flying);
+
+ if (P.FootRegion.Zone.bWaterZone || P.Region.Zone.bWaterZone)
+ P.AirSpeed = 400;
+ else
+ P.AirSpeed = 600;
+ }
Code: Select all
event BeginState()
{
- SetPhysics(PHYS_Flying);
- if (!IsAnimating())
- PlaySwimming();
- bCanFly = true;
- //log("player flying");
+ SetFlightProperties(self);[code]
[code]
event EndState()
{
+ AirSpeed = default.AirSpeed;
+ bCanFly = default.bCanFly;
- bCanFly = false;
}
Code: Select all
+ event FootZoneChange(ZoneInfo NewZone)
+ {
+ if (NewZone.bWaterZone != FootRegion.Zone.bWaterZone)
+ SetFlightProperties(self);
+ }
+
+ event ZoneChange(ZoneInfo NewZone)
+ {
+ if (NewZone.bWaterZone != Region.Zone.bWaterZone)
+ {
+ SetFlightProperties(self);
Code: Select all
state PlayerUsingJetpack
{
- ignores SeePlayer, HearNoise, Bump, StartClimbing;
-
function AnimEnd()
{
- PlaySwimming();
+ bAnimTransition = false;
+
+ if (!Region.Zone.bWaterZone || Acceleration Dot vector(ViewRotation) <= 0)
+ {
+ if (GetAnimGroup(AnimSequence) == 'TakeHit')
+ {
+ bAnimTransition = true;
+ TweenToWaiting(0.2);
+ }
+ else
+ PlayWaiting();
+ }
+ else
+ {
+ if (GetAnimGroup(AnimSequence) == 'TakeHit')
+ {
+ bAnimTransition = true;
+ TweenToSwimming(0.2);
+ }
+ else
+ PlaySwimming();
+ }
}
event PlayerTick( float DeltaTime )
{
if ( bUpdatePosition )
ClientUpdatePosition();
PlayerMove(DeltaTime);
}
function PlayerMove(float DeltaTime)
{
local vector X,Y,Z;
GetAxes(ViewRotation,X,Y,Z);
aForward *= 0.2;
aStrafe *= 0.2;
aLookup *= 0.24;
aTurn *= 0.24;
- Acceleration = aForward*X + aStrafe*Y + aUp*Z;
+ Acceleration = aForward * X + aStrafe * Y + aUp * vect(0, 0, 1);
- if ( bPressedJump && aUp<=0.01 )
bPressedJump = False;
// Update rotation.
UpdateRotation(DeltaTime, 2);
if ( Role < ROLE_Authority ) // then save this move and replicate it
ReplicateMove(DeltaTime, Acceleration, DODGE_None, rot(0,0,0));
else
ProcessMove(DeltaTime, Acceleration, DODGE_None, rot(0,0,0));
+
+ if (Region.Zone.bWaterZone)
+ SwimAnimUpdate(vector(ViewRotation) Dot Acceleration <= 0);
}
event BeginState()
{
- SetPhysics(PHYS_Flying);
- if (!IsAnimating())
- PlaySwimming();
- bCanFly = true;
- //log("player flying");
+ SetFlightProperties(self);
+ if (!IsAnimating())
+ PlayWaiting();
}
event EndState()
{
+ AirSpeed = default.AirSpeed;
+ bCanFly = default.bCanFly;
- bCanFly = false;
}
+ event FootZoneChange(ZoneInfo NewZone)
+ {
+ if (NewZone.bWaterZone != FootRegion.Zone.bWaterZone)
+ SetFlightProperties(self);
+ }
+
+ event ZoneChange(ZoneInfo NewZone)
+ {
+ if (NewZone.bWaterZone != Region.Zone.bWaterZone)
+ {
+ SetFlightProperties(self);
+ if (!NewZone.bWaterZone && Region.Zone.bWaterZone && GetAnimGroup(AnimSequence) != 'Waiting')
+ TweenToWaiting(0.1);
+ }
+ global.ZoneChange(NewZone);
+ }
+
+ function HandleWalking()
+ {
+ bIsWalking = false;
+ }
+
+ static function SetFlightProperties(Pawn P)
+ {
+ P.bCanFly = true;
+
+ if (P.Physics != PHYS_Flying)
+ P.SetPhysics(PHYS_Flying);
+
+ if (P.FootRegion.Zone.bWaterZone || P.Region.Zone.bWaterZone)
+ P.AirSpeed = 400;
+ else
+ P.AirSpeed = 600;
+ }
}
Code: Select all
class CustomPlayerStateInfo expands Info;
var PlayerPawn PlayerOwner;
var float InactiveTime;
auto state Inactive
{
event BeginState()
{
InactiveTime = 0;
}
// When RemoteRole == ROLE_AutonomousProxy, Tick is called only client-side
event Tick(float DeltaTime)
{
if (Role < ROLE_Authority && PlayerOwner != none && PlayerOwner.IsInState('CustomPlayerState'))
{
if (PlayerOwner.CustomPlayerStateInfo == none || PlayerOwner.CustomPlayerStateInfo.bDeleteMe)
PlayerOwner.SetCustomPlayerStateInfo(self);
else if (PlayerOwner.CustomPlayerStateInfo.Class != Class)
{
InactiveTime += DeltaTime / FMax(0.1, Level.TimeDilation);
if (InactiveTime >= 0.5)
PlayerOwner.SetCustomPlayerStateInfo(self);
}
else
InactiveTime = 0;
}
}
}
state Active
{
event BeginState()
{
Handle_BeginState();
}
event EndState()
{
Handle_EndState();
}
}
event PostNetBeginPlay()
{
PlayerOwner = PlayerPawn(Owner);
if (PlayerOwner != none)
PlayerOwner.SetCustomPlayerStateInfo(self);
}
final static function Default_PlayerTick(PlayerPawn Player, float DeltaTime)
{
if (Player.bUpdatePosition)
Player.ClientUpdatePosition();
if (Player.Role < ROLE_Authority)
Player.ReplicateMove(DeltaTime, vect(0, 0, 0), DODGE_None, rot(0, 0, 0));
else
Player.ProcessMove(DeltaTime, vect(0, 0, 0), DODGE_None, rot(0, 0, 0));
}
// -----------------------------------------------------------------------------
// Handle events
function Handle_AnimEnd()
{
PlayerOwner.GlobalFunc_AnimEnd();
}
function Handle_BaseChange()
{
PlayerOwner.GlobalFunc_BaseChange();
}
function Handle_BeginState();
function Handle_Bump(Actor A)
{
PlayerOwner.GlobalFunc_Bump(A);
}
function Handle_EndState();
function Handle_Falling()
{
PlayerOwner.GlobalFunc_Falling();
}
function Handle_FootZoneChange(ZoneInfo NewZone)
{
PlayerOwner.GlobalFunc_FootZoneChange(NewZone);
}
function Handle_HeadZoneChange(ZoneInfo NewZone)
{
PlayerOwner.GlobalFunc_HeadZoneChange(NewZone);
}
function Handle_Landed(vector HitNormal)
{
PlayerOwner.GlobalFunc_Landed(HitNormal);
}
function Handle_PainTimer()
{
PlayerOwner.GlobalFunc_PainTimer();
}
function Handle_PlayerCalcView(out actor ViewActor, out vector CameraLocation, out rotator CameraRotation)
{
PlayerOwner.GlobalFunc_PlayerCalcView(ViewActor, CameraLocation, CameraRotation);
}
function Handle_PlayerTick(float DeltaTime)
{
Default_PlayerTick(PlayerOwner, DeltaTime);
}
function Handle_RenderOverlays(Canvas Canvas)
{
PlayerOwner.GlobalFunc_RenderOverlays(Canvas);
}
function Handle_Touch(Actor A)
{
PlayerOwner.GlobalFunc_Touch(A);
}
function Handle_UnTouch(Actor A)
{
PlayerOwner.GlobalFunc_UnTouch(A);
}
function Handle_UpdateEyeHeight(float DeltaTime)
{
PlayerOwner.GlobalFunc_UpdateEyeHeight(DeltaTime);
}
function Handle_ZoneChange(ZoneInfo NewZone)
{
PlayerOwner.GlobalFunc_ZoneChange(NewZone);
}
// -----------------------------------------------------------------------------
// Handle exec functions
function Handle_ActivateInventoryItem(class InvItem)
{
PlayerOwner.GlobalFunc_ActivateInventoryItem(InvItem);
}
function Handle_ActivateItem()
{
PlayerOwner.GlobalFunc_ActivateItem();
}
function Handle_AltFire(optional float F)
{
PlayerOwner.GlobalFunc_AltFire(F);
}
function Handle_FeignDeath();
function Handle_Fire(optional float F)
{
PlayerOwner.GlobalFunc_Fire(F);
}
function Handle_Grab()
{
PlayerOwner.GlobalFunc_Grab();
}
function Handle_Suicide()
{
PlayerOwner.GlobalFunc_Suicide();
}
function Handle_Taunt(name Sequence)
{
PlayerOwner.GlobalFunc_Taunt(Sequence);
}
function Handle_ThrowWeapon()
{
PlayerOwner.GlobalFunc_ThrowWeapon();
}
function Handle_Walk()
{
PlayerOwner.GlobalFunc_Walk();
}
// -----------------------------------------------------------------------------
// Handle regular functions
function Handle_AddVelocity(vector V)
{
PlayerOwner.GlobalFunc_AddVelocity(V);
}
simulated function bool Handle_AdjustHitLocation(out vector HitLocation, vector TraceDir)
{
return PlayerOwner.GlobalFunc_AdjustHitLocation(HitLocation, TraceDir);
}
function Handle_ChangedWeapon()
{
PlayerOwner.GlobalFunc_ChangedWeapon();
}
function Handle_Died(Pawn Killer, name DamageType, vector HitLocation)
{
PlayerOwner.GlobalFunc_Died(Killer, DamageType, HitLocation);
}
function Handle_DoJump(optional float F)
{
PlayerOwner.GlobalFunc_DoJump(F);
}
function Handle_HandleWalking()
{
PlayerOwner.GlobalFunc_HandleWalking();
}
function Handle_PlayChatting()
{
PlayerOwner.GlobalFunc_PlayChatting();
}
function Handle_ProcessMove(float DeltaTime, vector NewAccel, eDodgeDir DodgeMove, rotator DeltaRot)
{
PlayerOwner.GlobalFunc_ProcessMove(DeltaTime, NewAccel, DodgeMove, DeltaRot);
}
function Handle_StartClimbing(LadderTrigger Ladder)
{
PlayerOwner.GlobalFunc_StartClimbing(Ladder);
}
function Handle_TakeDamage(int Damage, Pawn InstigatedBy, vector HitLocation, vector Momentum, name DamageType)
{
PlayerOwner.GlobalFunc_TakeDamage(Damage, InstigatedBy, HitLocation, Momentum, DamageType);
}
function Handle_UpdateRotation(float DeltaTime, float MaxPitch)
{
PlayerOwner.GlobalFunc_UpdateRotation(DeltaTime, MaxPitch);
}
defaultproperties
{
RemoteRole=ROLE_AutonomousProxy
}
Code: Select all
state CustomPlayerState
{
event AnimEnd()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_AnimEnd();
}
singular event BaseChange()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_BaseChange();
}
event BeginState()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.GotoState('Active');
}
event Bump(Actor A)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Bump(A);
}
event EndState()
{
if (CustomPlayerStateInfo != none)
{
if (CustomPlayerStateInfo.Role < ROLE_Authority)
CustomPlayerStateInfo.GotoState('Inactive');
else
CustomPlayerStateInfo.Destroy();
CustomPlayerStateInfo = none;
}
}
event Falling()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Falling();
}
event FootZoneChange(ZoneInfo NewZone)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_FootZoneChange(NewZone);
}
event HeadZoneChange(ZoneInfo NewZone)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_HeadZoneChange(NewZone);
}
event Landed(vector HitNormal)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Landed(HitNormal);
}
event PainTimer()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_PainTimer();
}
event PlayerCalcView(out actor ViewActor, out vector CameraLocation, out rotator CameraRotation)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_PlayerCalcView(ViewActor, CameraLocation, CameraRotation);
else
global.PlayerCalcView(ViewActor, CameraLocation, CameraRotation);
}
event PlayerTick(float DeltaTime)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_PlayerTick(DeltaTime);
else
class'CustomPlayerStateInfo'.static.Default_PlayerTick(self, DeltaTime);
}
event RenderOverlays(Canvas Canvas)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_RenderOverlays(Canvas);
else
global.RenderOverlays(Canvas);
}
event Touch(Actor A)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Touch(A);
}
event UnTouch(Actor A)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_UnTouch(A);
}
event UpdateEyeHeight(float DeltaTime)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_UpdateEyeHeight(DeltaTime);
else
global.UpdateEyeHeight(DeltaTime);
}
event ZoneChange(ZoneInfo NewZone)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_ZoneChange(NewZone);
}
exec function ActivateInventoryItem(class InvItem)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_ActivateInventoryItem(InvItem);
}
exec function ActivateItem()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_ActivateItem();
}
exec function AltFire(optional float F)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_AltFire(F);
}
exec function FeignDeath()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_FeignDeath();
}
exec function Fire(optional float F)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Fire(F);
}
exec function Grab()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Grab();
}
exec function Suicide()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Suicide();
}
exec function Taunt(name Sequence)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Taunt(Sequence);
}
exec function ThrowWeapon()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_ThrowWeapon();
}
exec function Walk()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Walk();
}
function AddVelocity(vector V)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_AddVelocity(V);
}
simulated function bool AdjustHitLocation(out vector HitLocation, vector TraceDir)
{
if (CustomPlayerStateInfo != none)
return CustomPlayerStateInfo.Handle_AdjustHitLocation(HitLocation, TraceDir);
return global.AdjustHitLocation(HitLocation, TraceDir);
}
function ChangedWeapon()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_ChangedWeapon();
}
function Died(Pawn Killer, name DamageType, vector HitLocation)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_Died(Killer, DamageType, HitLocation);
}
function DoJump(optional float F)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_DoJump(F);
}
function HandleWalking()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_HandleWalking();
}
function PlayChatting()
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_PlayChatting();
}
function ProcessMove(float DeltaTime, vector NewAccel, eDodgeDir DodgeMove, rotator DeltaRot)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_ProcessMove(DeltaTime, NewAccel, DodgeMove, DeltaRot);
}
function StartClimbing(LadderTrigger Ladder)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_StartClimbing(Ladder);
}
function TakeDamage(int Damage, Pawn InstigatedBy, vector HitLocation, vector Momentum, name DamageType)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_TakeDamage(Damage, InstigatedBy, HitLocation, Momentum, DamageType);
}
function UpdateRotation(float DeltaTime, float MaxPitch)
{
if (CustomPlayerStateInfo != none)
CustomPlayerStateInfo.Handle_UpdateRotation(DeltaTime, MaxPitch);
}
}
Code: Select all
var CustomPlayerStateInfo CustomPlayerStateInfo;
Code: Select all
event ZoneChange(ZoneInfo NewZone)
{
if (NewZone.bWaterZone != Region.Zone.bWaterZone)
{
SetFlightProperties(self);
if (!NewZone.bWaterZone && Region.Zone.bWaterZone && GetAnimGroup(AnimSequence) != 'Waiting')
TweenToWaiting(0.1);
}
global.ZoneChange(NewZone);
}
Code: Select all
function Handle_ZoneChange(ZoneInfo NewZone)
{
if (NewZone.bWaterZone != PlayerOwner.Region.Zone.bWaterZone)
{
SetFlightProperties(PlayerOwner);
if (!NewZone.bWaterZone && PlayerOwner.Region.Zone.bWaterZone && PlayerOwner.GetAnimGroup(PlayerOwner.AnimSequence) != 'Waiting')
PlayerOwner.TweenToWaiting(0.1);
}
PlayerOwner.GlobalFunc_ZoneChange(NewZone);
}
1. The name of the function is prefixed by Handle_
F.e., ZoneChange becomes Handle_ZoneChange
2. Every occurrence of global. is replaced by GlobalFunc_
F.e., global.ZoneChange(NewZone) becomes GlobalFunc_ZoneChange(NewZone)
3. Every access to a variable or a global-scope non-static member function of PlayerPawn without using the self keyword explicitly is transformed into the corresponding member access using the self keyword.
F.e., GlobalFunc_ZoneChange(NewZone) becomes self.GlobalFunc_ZoneChange(NewZone)
4. Every use of the self keyword is replaced with variable name PlayerOwner.
F.e. self.GlobalFunc_ZoneChange(NewZone) becomes PlayerOwner.GlobalFunc_ZoneChange(NewZone)
Here's the transformed implementation of our state PlayerUsingJetpack:
Code: Select all
class UsingJetpack expands CustomPlayerStateInfo;
function Handle_AnimEnd()
{
PlayerOwner.bAnimTransition = false;
if (!PlayerOwner.Region.Zone.bWaterZone || PlayerOwner.Acceleration Dot vector(PlayerOwner.ViewRotation) <= 0)
{
if (PlayerOwner.GetAnimGroup(PlayerOwner.AnimSequence) == 'TakeHit')
{
PlayerOwner.bAnimTransition = true;
PlayerOwner.TweenToWaiting(0.2);
}
else
PlayerOwner.PlayWaiting();
}
else
{
if (PlayerOwner.GetAnimGroup(PlayerOwner.AnimSequence) == 'TakeHit')
{
PlayerOwner.bAnimTransition = true;
PlayerOwner.TweenToSwimming(0.2);
}
else
PlayerOwner.PlaySwimming();
}
}
function Handle_PlayerTick(float DeltaTime)
{
if (PlayerOwner.bUpdatePosition)
PlayerOwner.ClientUpdatePosition();
PlayerMove(DeltaTime);
}
function PlayerMove(float DeltaTime)
{
local vector X, Y, Z;
GetAxes(PlayerOwner.ViewRotation, X, Y, Z);
PlayerOwner.aForward *= 0.2;
PlayerOwner.aStrafe *= 0.2;
PlayerOwner.aLookup *= 0.24;
PlayerOwner.aTurn *= 0.24;
PlayerOwner.Acceleration = PlayerOwner.aForward * X + PlayerOwner.aStrafe * Y + PlayerOwner.aUp * vect(0, 0, 1);
PlayerOwner.bPressedJump = False;
// Update rotation.
PlayerOwner.UpdateRotation(DeltaTime, 2);
if (PlayerOwner.Role < ROLE_Authority) // then save this move and replicate it
PlayerOwner.ReplicateMove(DeltaTime, PlayerOwner.Acceleration, DODGE_None, rot(0,0,0));
else
PlayerOwner.ProcessMove(DeltaTime, PlayerOwner.Acceleration, DODGE_None, rot(0,0,0));
if (PlayerOwner.Region.Zone.bWaterZone)
PlayerOwner.SwimAnimUpdate(vector(PlayerOwner.ViewRotation) Dot PlayerOwner.Acceleration <= 0);
}
function Handle_BeginState()
{
SetFlightProperties(PlayerOwner);
if (!PlayerOwner.IsAnimating())
PlayerOwner.PlayWaiting();
}
function Handle_EndState()
{
PlayerOwner.AirSpeed = PlayerOwner.default.AirSpeed;
PlayerOwner.bCanFly = PlayerOwner.default.bCanFly;
}
function Handle_FootZoneChange(ZoneInfo NewZone)
{
if (NewZone.bWaterZone != PlayerOwner.FootRegion.Zone.bWaterZone)
SetFlightProperties(PlayerOwner);
}
function Handle_ZoneChange(ZoneInfo NewZone)
{
if (NewZone.bWaterZone != PlayerOwner.Region.Zone.bWaterZone)
{
SetFlightProperties(PlayerOwner);
if (!NewZone.bWaterZone && PlayerOwner.Region.Zone.bWaterZone && PlayerOwner.GetAnimGroup(PlayerOwner.AnimSequence) != 'Waiting')
PlayerOwner.TweenToWaiting(0.1);
}
PlayerOwner.GlobalFunc_ZoneChange(NewZone);
}
function Handle_HandleWalking()
{
PlayerOwner.bIsWalking = false;
}
static function SetFlightProperties(Pawn P)
{
P.bCanFly = true;
if (P.Physics != PHYS_Flying)
P.SetPhysics(PHYS_Flying);
if (P.FootRegion.Zone.bWaterZone || P.Region.Zone.bWaterZone)
P.AirSpeed = 400;
else
P.AirSpeed = 600;
}
Other handlers may use the default implementation that generally just calls the corresponding global function of Engine.PlayerPawn:
Code: Select all
final function GlobalFunc_ActivateInventoryItem(class InvItem)
{
global.ActivateInventoryItem(InvItem);
}
final function GlobalFunc_ActivateItem()
{
global.ActivateItem();
}
final function GlobalFunc_AddVelocity(vector V)
{
global.AddVelocity(V);
}
final simulated function bool GlobalFunc_AdjustHitLocation(out vector HitLocation, vector TraceDir)
{
return global.AdjustHitLocation(HitLocation, TraceDir);
}
final function GlobalFunc_AltFire(optional float F)
{
global.AltFire(F);
}
final function GlobalFunc_AnimEnd()
{
global.AnimEnd();
}
final function GlobalFunc_BaseChange()
{
global.BaseChange();
}
final function GlobalFunc_Bump(Actor A)
{
global.Bump(A);
}
final function GlobalFunc_ChangedWeapon()
{
global.ChangedWeapon();
}
final function GlobalFunc_Died(Pawn Killer, name DamageType, vector HitLocation)
{
global.Died(Killer, DamageType, HitLocation);
}
final function GlobalFunc_DoJump(optional float F)
{
global.DoJump(F);
}
final function GlobalFunc_Falling()
{
global.Falling();
}
final function GlobalFunc_Fire(optional float F)
{
global.Fire(F);
}
final function GlobalFunc_FootZoneChange(ZoneInfo NewZone)
{
global.FootZoneChange(NewZone);
}
final function GlobalFunc_Grab()
{
global.Grab();
}
final function GlobalFunc_HeadZoneChange(ZoneInfo NewZone)
{
global.HeadZoneChange(NewZone);
}
final function GlobalFunc_HandleWalking()
{
global.HandleWalking();
}
final function GlobalFunc_Landed(vector HitNormal)
{
global.Landed(HitNormal);
}
final function GlobalFunc_PainTimer()
{
global.PainTimer();
}
final function GlobalFunc_PlayerCalcView(out actor ViewActor, out vector CameraLocation, out rotator CameraRotation)
{
global.PlayerCalcView(ViewActor, CameraLocation, CameraRotation);
}
final function GlobalFunc_PlayChatting()
{
global.PlayChatting();
}
final function GlobalFunc_ProcessMove(float DeltaTime, vector NewAccel, eDodgeDir DodgeMove, rotator DeltaRot)
{
global.ProcessMove(DeltaTime, NewAccel, DodgeMove, DeltaRot);
}
final simulated function GlobalFunc_RenderOverlays(Canvas Canvas)
{
global.RenderOverlays(Canvas);
}
final function GlobalFunc_StartClimbing(LadderTrigger Ladder)
{
global.StartClimbing(Ladder);
}
final function GlobalFunc_Suicide()
{
global.Suicide();
}
final function GlobalFunc_TakeDamage(int Damage, Pawn InstigatedBy, vector HitLocation, vector Momentum, name DamageType)
{
global.TakeDamage(Damage, InstigatedBy, HitLocation, Momentum, DamageType);
}
final function GlobalFunc_Taunt(name Sequence)
{
global.Taunt(Sequence);
}
final function GlobalFunc_ThrowWeapon()
{
global.ThrowWeapon();
}
final function GlobalFunc_Touch(Actor A)
{
global.Touch(A);
}
final function GlobalFunc_UnTouch(Actor A)
{
global.UnTouch(A);
}
final function GlobalFunc_UpdateEyeHeight(float DeltaTime)
{
global.UpdateEyeHeight(DeltaTime);
}
final function GlobalFunc_UpdateRotation(float DeltaTime, float MaxPitch)
{
global.UpdateRotation(DeltaTime, MaxPitch);
}
final function GlobalFunc_Walk()
{
global.Walk();
}
final function GlobalFunc_ZoneChange(ZoneInfo NewZone)
{
global.ZoneChange(NewZone);
}
Code: Select all
function bool GotoCustomPlayerState(class<CustomPlayerStateInfo> PlayerStateInfoClass)
{
if (PlayerStateInfoClass == none)
return false;
if (CustomPlayerStateInfo == none || CustomPlayerStateInfo.bDeleteMe || CustomPlayerStateInfo.Class != PlayerStateInfoClass)
return SetCustomPlayerStateInfo(Spawn(PlayerStateInfoClass, self));
if (GetStateName() != 'CustomPlayerState')
GotoState('CustomPlayerState');
else if (!CustomPlayerStateInfo.IsInState('Active'))
CustomPlayerStateInfo.GotoState('Active');
else
return false;
return true;
}
The code of activation of Jetpack could look like this:
Code: Select all
class Jetpack expands Pickup;
#exec AUDIO IMPORT FILE="Sounds\Jetpack.wav" NAME="JetpackSnd"
var() sound FlightSound;
var Actor EffectsActor;
state Activated
{
event BeginState()
{
local Pawn OwnerPawn;
OwnerPawn = Pawn(Owner);
if (OwnerPawn == none || --Charge <= 0)
{
UsedUp();
return;
}
if (PlayerPawn(Owner) != none)
{
// if the owner is a PlayerPawn, let it use the special state
PlayerPawn(Owner).GotoCustomPlayerState(class'UsingJetpack');
if (UsingJetpack(PlayerPawn(Owner).CustomPlayerStateInfo) == none)
{
// if we failed to enter the special state for some reason, deactivate the Jetpack
// and keep the owner's properties intact
Activate();
return;
}
}
else
{
// if the owner is not a PlayerPawn, modify its properties to make it flying
SetFlightProperties(OwnerPawn);
}
EnableInventoryEffects();
bActive = true;
SetTimer(0.1, true);
}
static function SetFlightProperties(Pawn P)
{
P.bCanFly = true;
if (P.Physics != PHYS_Flying)
P.SetPhysics(PHYS_Flying);
if (P.FootRegion.Zone.bWaterZone || P.Region.Zone.bWaterZone)
P.AirSpeed = 400;
else
P.AirSpeed = 600;
}
We should support two ways to deactivate Jetpack: by leaving the state Jetpack.Activated (as a result of calling Activate or UsedUp) and by leaving the special state UsingJetpack (as a result of setting the PlayerPawn to use other state). In either case deactivation PlayerPawn's Jetpack should imply end of the period when Jetpack.Activated and the special player state are used, but leaving these states can be done in a different order, so we should ensure that leaving any of these states invokes an appropriate change of the other state (and, of course, an infinite recursion should be avoided). In case of Jetpack.Activated, we can define EndState as follows:
Code: Select all
event EndState()
{
local Pawn OwnerPawn;
DisableInventoryEffects();
OwnerPawn = Pawn(Owner);
if (OwnerPawn == none)
{
bActive = false;
return;
}
OwnerPawn.AirSpeed = OwnerPawn.default.AirSpeed;
OwnerPawn.bCanFly = OwnerPawn.default.bCanFly;
if (bActive)
{
bActive = false;
if (Owner.Region.Zone.bWaterZone)
{
Owner.SetPhysics(PHYS_Swimming);
Owner.GoToState('PlayerSwimming');
}
else
{
Owner.SetPhysics(PHYS_Falling);
Owner.GoToState('PlayerWalking');
}
}
}
UsingJetpack.Handle_EndState can be defined as:
Code: Select all
function Handle_EndState()
{
local Inventory Inv;
if (Level.NetMode == NM_Client)
return;
for (Inv = PlayerOwner.Inventory; Inv != none; Inv = Inv.Inventory)
if (Inv.Class == class'Jetpack' && Inv.bActive)
{
Inv.bActive = false;
Inv.Activate();
}
}
This is how we can define Jetpack.uc:
Code: Select all
class Jetpack expands Pickup;
#exec AUDIO IMPORT FILE="Sounds\Jetpack.wav" NAME="JetpackSnd"
var() sound FlightSound;
var Actor EffectsActor;
state Activated
{
event BeginState()
{
local Pawn OwnerPawn;
OwnerPawn = Pawn(Owner);
if (OwnerPawn == none || --Charge <= 0)
{
UsedUp();
return;
}
if (PlayerPawn(Owner) != none)
{
PlayerPawn(Owner).GotoCustomPlayerState(class'UsingJetpack');
if (UsingJetpack(PlayerPawn(Owner).CustomPlayerStateInfo) == none)
{
Activate();
return;
}
}
SetFlightProperties(OwnerPawn);
EnableInventoryEffects();
bActive = true;
SetTimer(0.1, true);
}
event EndState()
{
local Pawn OwnerPawn;
DisableInventoryEffects();
OwnerPawn = Pawn(Owner);
if (OwnerPawn == none)
{
bActive = false;
return;
}
OwnerPawn.AirSpeed = OwnerPawn.default.AirSpeed;
OwnerPawn.bCanFly = OwnerPawn.default.bCanFly;
if (bActive)
{
bActive = false;
if (Owner.Region.Zone.bWaterZone)
{
Owner.SetPhysics(PHYS_Swimming);
Owner.GoToState('PlayerSwimming');
}
else
{
Owner.SetPhysics(PHYS_Falling);
Owner.GoToState('PlayerWalking');
}
}
}
event Timer()
{
if (Pawn(Owner) == none || --Charge <= 0)
UsedUp();
else
SetFlightProperties(Pawn(Owner));
}
}
state DeActivated
{
Begin:
}
function Activate()
{
if (Owner.bCollideWorld && !Owner.IsInState('FeigningDeath') &&
(Owner.Physics == PHYS_Walking || Owner.Physics == PHYS_Falling || Owner.Physics == PHYS_Swimming || Owner.Physics == PHYS_Flying))
{
super.Activate();
}
}
static function SetFlightProperties(Pawn P)
{
P.bCanFly = true;
if (P.Physics != PHYS_Flying)
P.SetPhysics(PHYS_Flying);
if (P.FootRegion.Zone.bWaterZone || P.Region.Zone.bWaterZone)
P.AirSpeed = 400;
else
P.AirSpeed = 600;
}
function EnableInventoryEffects()
{
EffectsActor = Spawn(class'Triggers', Owner,, Owner.Location);
if (EffectsActor != none)
{
EffectsActor.SetPhysics(PHYS_Trailer);
EffectsActor.RemoteRole = ROLE_SimulatedProxy;
EffectsActor.AmbientSound = FlightSound;
}
}
function DisableInventoryEffects()
{
if (EffectsActor != none)
{
EffectsActor.Destroy();
EffectsActor = none;
}
}
defaultproperties
{
ActivateSound=None
AmbientGlow=64
bActivatable=True
bAutoActivate=True
bDisplayableInv=True
bMeshEnviroMap=True
Charge=600
CollisionRadius=22.000000
CollisionHeight=7.000000
ExpireMessage="Jetpack ran out of fuel"
FlightSound=Sound'Jetpack.JetpackSnd'
Icon=Texture'Jetpack.JetpackIcon'
ItemName="Jetpack"
MaxDesireability=0.500000
Mesh=LodMesh'UnrealShare.KevSuit'
PickupMessage="You picked up a Jetpack"
PickupSound=Sound'UnrealShare.Pickups.GenPickSnd'
PickupViewMesh=LodMesh'UnrealShare.KevSuit'
RemoteRole=ROLE_DumbProxy
RespawnTime=120.000000
SwimSound=Sound'Jetpack.JetpackBubblesSnd'
Texture=Texture'UnrealShare.NewGreen'
}