//=============================================================================
// WoEWalkerCraft - Vehicle with multiple legs. Based on ONSHoverCraft code.
// [url]http://come.to/MrEvil[/url]
//=============================================================================
class WoEWalkerCraft extends ONSVehicle
abstract;
var() array<vector> ThrusterOffsets;
var() float HoverSoftness;
var() float HoverPenScale;
var() float HoverCheckDist;
var() float UprightStiffness;
var() float UprightDamping;
var() float MaxThrustForce;
var() float LongDamping;
var() float MaxStrafeForce;
var() float LatDamping;
var() float MaxRiseForce;
var() float UpDamping;
var() float TurnTorqueFactor;
var() float TurnTorqueMax;
var() float MaxYawRate;
var() float LegCheckDist;
// Internal
var float TargetHeading;
var float TargetPitch;
var bool bHeadingInitialized;
var float OutputThrust;
var float OutputStrafe;
var Pawn OldDriver;
// Replicated
struct native HoverCraftState
{
var vector ChassisPosition;
var Quat ChassisQuaternion;
var vector ChassisLinVel;
var vector ChassisAngVel;
var byte ServerThrust;
var byte ServerStrafe;
var int ServerViewPitch;
var int ServerViewYaw;
};
var HoverCraftState HoverState, OldHoverState;
var KRigidBodyState ChassisState;
var bool bNewHoverState;
var vector WForce, WTorque;
var byte FudgeByte; //It seems that structs won't replicate properly on their own, so replicate this too.
replication
{
reliable if(Role == ROLE_Authority)
HoverState, FudgeByte;
}
simulated event bool KUpdateState(out KRigidBodyState newState)
{
// This should never get called on the server - but just in case!
if(Role == ROLE_Authority || !bNewHoverState)
return false;
newState = ChassisState;
bNewHoverState = false;
return true;
}
simulated function PostNetBeginPlay()
{
local vector RotX, RotY, RotZ;
local KarmaParams kp;
local KRepulsor rep;
local int i;
GetAxes(Rotation,RotX,RotY,RotZ);
// Spawn and assign 'repulsors' to hold bike off the ground
kp = KarmaParams(KParams);
kp.Repulsors.Length = ThrusterOffsets.Length;
for(i=0;i<ThrusterOffsets.Length;i++)
{
rep = spawn(class'KRepulsor', self,, Location + ThrusterOffsets[i].X * RotX + ThrusterOffsets[i].Y * RotY + ThrusterOffsets[i].Z * RotZ);
rep.SetBase(self);
rep.bHidden = true;
kp.Repulsors[i] = rep;
}
Super.PostNetBeginPlay();
}
function Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
local KarmaParams kp;
local int i;
//Destroy repulsors
kp = KarmaParams(KParams);
for(i=0;i<kp.Repulsors.Length;i++)
kp.Repulsors[i].Destroy();
kp.Repulsors.Length = 0;
Super.Died(Killer, damageType, HitLocation);
}
simulated event SVehicleUpdateParams()
{
local KarmaParams kp;
local int i;
Super.SVehicleUpdateParams();
kp = KarmaParams(KParams);
for(i=0;i<kp.Repulsors.Length;i++)
{
kp.Repulsors[i].CheckDist = HoverCheckDist;
kp.Repulsors[i].PenScale = HoverPenScale;
kp.Repulsors[i].Softness = HoverSoftness;
}
KSetStayUprightParams(UprightStiffness, UprightDamping);
}
simulated function SpecialCalcFirstPersonView(PlayerController PC, out actor ViewActor, out vector CameraLocation, out rotator CameraRotation )
{
ViewActor = self;
CameraLocation = Location + (FPCamPos >> Rotation);
}
//Returns true if one or more legs is close enough to the ground to be considered able to walk.
simulated function bool IsOnGround()
{
local int i;
local vector StartTrace, EndTrace;
local KarmaParams KP;
//Do some quick traces first, down from the thruster locations towards the ground.
for(i = 0; i < ThrusterOffsets.Length; i++)
{
StartTrace = Location;
StartTrace += ThrusterOffsets[i] >> Rotation;
EndTrace = StartTrace;
EndTrace += (vect(0, 0, -1) >> Rotation) * HoverCheckDist * LegCheckDist;
if(!FastTrace(EndTrace, StartTrace))
return true;
}
//If the traces fail, see if the thrusters are actually in contact.
//TODO: Shouldn't this check be done before the traces? Which order would be faster?
KP = KarmaParams(KParams);
for(i = 0; i < KP.Repulsors.Length; i++)
{
if(KP.Repulsors[i].bRepulsorInContact)
return true;
}
return false;
}
//From here on is the stuff that was in ONSHoverCraft.cpp
function float HeadingAngle(Vector dir)
{
local float angle;
angle = Acos(dir.X);
if(dir.Y < 0.0)
angle *= -1.0;
return angle;
}
function float FindDeltaAngle(float a1, float a2)
{
local float delta;
delta = a2 - a1;
if(delta > PI)
delta = delta - (PI * 2.0);
else if(delta < -PI)
delta = delta + (PI * 2.0);
return delta;
}
function float UnwindHeading(float a)
{
while(a > PI)
a -= (PI * 2.0);
while(a < -PI)
a += (PI * 2.0);
return a;
}
//Calculate forces for thrust/turning etc. and apply them.
//TODO: Tidy this up (reduce number of variables etc).
simulated function UpdateVehicle(float DeltaTime)
{
local KarmaParams KP;
local vector DirX, DirY, DirZ;
local Vector Forward, Right, Up;
local KRigidBodyState rbState;
local vector AngVel;
local float TurnAngVel, RollAngVel, PitchAngVel;
local float ForwardVelMag, RightVelMag;
local vector Force, Torque;
local rotator LookRot;
local vector LookDir;
local vector PlaneLookDir;
local float CurrentHeading, DesiredHeading;
local float DeltaTargetHeading, MaxDeltaHeading;
local float DeltaHeading, TurnTorqueMag;
// Dont go adding forces if vehicle is asleep.
if(!KIsAwake())
return;
KP = KarmaParams(KParams);
if(KP == None)
return;
WForce = vect(0, 0, 0);
WTorque = vect(0, 0, 0);
if(Controller != None)
{
//Calc up (z), right(y) and forward (x) vectors
GetAxes(Rotation, DirX, DirY, DirZ);
if(!IsOnGround())
return;
Forward = DirX;
Right = DirY;
Up = DirZ;
// Get body angular velocity
KGetRigidBodyState(rbState);
AngVel = KRBVecToVector(rbState.AngVel);
TurnAngVel = AngVel dot Up;
RollAngVel = AngVel dot DirX;
PitchAngVel = AngVel dot DirY;
ForwardVelMag = Velocity dot Forward;
RightVelMag = Velocity dot Right;
// Thrust
Force += (OutputThrust * MaxThrustForce * Forward);
Force -= ( (1.0 - Abs(OutputThrust)) * LongDamping * ForwardVelMag * Forward);
// Strafe
Force += (-OutputStrafe * MaxStrafeForce * Right);
Force -= ( (1.0 - Abs(OutputStrafe)) * LatDamping * RightVelMag * Right);
LookRot.Pitch = DriverViewPitch;
LookRot.Yaw = DriverViewYaw;
LookDir = vector(LookRot);
// We try to turn the HoverCraft to match the way the camera is facing.
//// YAW ////
// Project Look dir into z-plane
PlaneLookDir = LookDir;
PlaneLookDir.Z = 0.0;
PlaneLookDir = Normal(PlaneLookDir);
Forward.Z = 0.0;
Forward = Normal(Forward);
CurrentHeading = HeadingAngle(Forward);
DesiredHeading = HeadingAngle(PlaneLookDir);
if(!bHeadingInitialized)
{
TargetHeading = CurrentHeading;
bHeadingInitialized = true;
}
// Move 'target heading' towards 'desired heading' as fast as MaxYawRate allows.
DeltaTargetHeading = FindDeltaAngle(TargetHeading, DesiredHeading);
MaxDeltaHeading = DeltaTime * MaxYawRate;
DeltaTargetHeading = FClamp(DeltaTargetHeading, -MaxDeltaHeading, MaxDeltaHeading);
TargetHeading = UnwindHeading(TargetHeading + DeltaTargetHeading);
// Then put a 'spring' on the copter to target heading.
DeltaHeading = FindDeltaAngle(CurrentHeading, TargetHeading);
TurnTorqueMag = (DeltaHeading / PI) * TurnTorqueFactor;
TurnTorqueMag = FClamp(TurnTorqueMag, -TurnTorqueMax, TurnTorqueMax);
Torque += (TurnTorqueMag * Up);
// Apply force/torque to body.
WForce = Force;
WTorque = Torque;
}
}
simulated function KApplyForce(out vector Force, out vector Torque)
{
Super.KApplyForce(Force, Torque);
Force += WForce;
Torque += WTorque;
}
simulated function Tick(float DeltaTime)
{
Super.Tick(DeltaTime);
// If the server, process input and pack updated car info into struct.
if(Role == ROLE_Authority)
{
if(bDriving)
{
OutputThrust = Throttle;
OutputStrafe = Steering;
KWake(); // keep vehicle alive while driving
}
PackState();
}
}
function PackState()
{
local KRigidBodyState RBState;
local rotator ViewRot;
if(!KIsAwake())
return;
KGetRigidBodyState(RBState);
HoverState.ChassisPosition.X = RBState.Position.X;
HoverState.ChassisPosition.Y = RBState.Position.Y;
HoverState.ChassisPosition.Z = RBState.Position.Z;
HoverState.ChassisQuaternion = RBState.Quaternion;
HoverState.ChassisLinVel.X = 10.0 * RBState.LinVel.X;
HoverState.ChassisLinVel.Y = 10.0 * RBState.LinVel.Y;
HoverState.ChassisLinVel.Z = 10.0 * RBState.LinVel.Z;
HoverState.ChassisAngVel.X = 1000.0 * RBState.AngVel.X;
HoverState.ChassisAngVel.Y = 1000.0 * RBState.AngVel.Y;
HoverState.ChassisAngVel.Z = 1000.0 * RBState.AngVel.Z;
HoverState.ServerThrust = FloatToRangeByte(OutputThrust);
HoverState.ServerStrafe = FloatToRangeByte(OutputStrafe);
if(Controller != None)
{
if(IsHumanControlled())
{
DriverViewPitch = Controller.Rotation.Pitch;
DriverViewYaw = Controller.Rotation.Yaw;
}
else
{
ViewRot = rotator(Controller.FocalPoint - Location);
DriverViewPitch = ViewRot.Pitch;
DriverViewYaw = ViewRot.Yaw;
}
}
else
{
DriverViewPitch = Rotation.Pitch;
DriverViewYaw = Rotation.Yaw;
}
HoverState.ServerViewPitch = DriverViewPitch;
HoverState.ServerViewYaw = DriverViewYaw;
FudgeByte++;
}
// Deal with new infotmation about the arriving from the server
simulated function PostNetReceive()
{
Super.PostNetReceive();
if (Driver != OldDriver)
{
SVehicleUpdateParams();
OldDriver = Driver;
}
if( OldHoverState.ChassisPosition == HoverState.ChassisPosition &&
OldHoverState.ChassisQuaternion.X == HoverState.ChassisQuaternion.X &&
OldHoverState.ChassisQuaternion.Y == HoverState.ChassisQuaternion.Y &&
OldHoverState.ChassisQuaternion.Z == HoverState.ChassisQuaternion.Z &&
OldHoverState.ChassisQuaternion.W == HoverState.ChassisQuaternion.W &&
OldHoverState.ChassisLinVel == HoverState.ChassisLinVel &&
OldHoverState.ChassisAngVel == HoverState.ChassisAngVel &&
OldHoverState.ServerThrust == HoverState.ServerThrust &&
OldHoverState.ServerStrafe == HoverState.ServerStrafe &&
OldHoverState.ServerViewPitch == HoverState.ServerViewPitch &&
OldHoverState.ServerViewYaw == HoverState.ServerViewYaw )
return;
ChassisState.Position.X = HoverState.ChassisPosition.X;
ChassisState.Position.Y = HoverState.ChassisPosition.Y;
ChassisState.Position.Z = HoverState.ChassisPosition.Z;
ChassisState.Quaternion = HoverState.ChassisQuaternion;
ChassisState.LinVel.X = 0.1f * HoverState.ChassisLinVel.X;
ChassisState.LinVel.Y = 0.1f * HoverState.ChassisLinVel.Y;
ChassisState.LinVel.Z = 0.1f * HoverState.ChassisLinVel.Z;
ChassisState.AngVel.X = 0.001f * HoverState.ChassisAngVel.X;
ChassisState.AngVel.Y = 0.001f * HoverState.ChassisAngVel.Y;
ChassisState.AngVel.Z = 0.001f * HoverState.ChassisAngVel.Z;
bNewHoverState = true;
// Set OldHoverState to HoverState
OldHoverState.ChassisPosition = HoverState.ChassisPosition;
OldHoverState.ChassisQuaternion = HoverState.ChassisQuaternion;
OldHoverState.ChassisLinVel = HoverState.ChassisLinVel;
OldHoverState.ChassisAngVel = HoverState.ChassisAngVel;
OldHoverState.ServerThrust = HoverState.ServerThrust;
OldHoverState.ServerStrafe = HoverState.ServerStrafe;
OldHoverState.ServerViewPitch = HoverState.ServerViewPitch;
OldHoverState.ServerViewYaw = HoverState.ServerViewYaw;
OutputThrust = RangeByteToFloat(HoverState.ServerThrust);
OutputStrafe = RangeByteToFloat(HoverState.ServerStrafe);
DriverViewPitch = HoverState.ServerViewPitch;
DriverViewYaw = HoverState.ServerViewYaw;
}
//I hope this is correct, it's a guess at what the function used in the native code does.
simulated function byte FloatToRangeByte(float f)
{
f= FClamp(f, 0, 1);
f = Round(f * 255);
return byte(f);
}
//The opposite of the above function.
simulated function float RangeByteToFloat(byte b)
{
local float f;
f = b;
f /= 255;
return f;
}
defaultproperties
{
bNetNotify=true
bZeroPCRotOnEntry=False
bFollowLookDir=True
bPCRelativeFPRotation=true
LegCheckDist=1.5
}