Mostly Harmless

Legacy:KShip

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

Well this is basically my code for a space ship, thanks a lot to Daid for lending me his replication code (which turned out to come from KCar but i thank him non the less, great guy he is :D)

I currently use a different, more client oriented replication system, but this one here is a lot better, i havent incorporated my ship to it yet because i have some other problems that come with it that i have to solve, but since the "core" ship here is simple, ill use the better replication for you guys :)

(Btw i deal with all the entering/leaving of the vehicle in a parent class, this is really irrelevant to physics and is easy to do, so i left it out just so there will be less and easier to read code)

var float			Roll, CThrust, TempThrust, OldThrust; 
// Throttle and Steering are already declared in KVehicle, so all we need is roll, CThrust, TempThrust, OldThrust will be explained soon
// BTW note that i use Throttle for Pitch alteration because it is already bound by other vehicles to the forward/backward key
// ok i have also incorporated a system that allows for various thrust managments, 
//if you simply want to move forward full speed only while you press the the thrust button, you would use TempThrust. 
//when you release the thrust button you though you should always set TempThrust to -1
// if you want to have a constant speed, assign it to CThrust, so every time you release the thrust button (the one that changes TempThrust) 
// you would automatically start going at that speed (good for stuff like dogifghting and matching speeds)
// and lastly i added an afterburner effect, ill include it here for you guys to see although it isnt hard to do.
// also what i did with it was that when you afterburn and you run out of energy, while you still hold the afterburn key
// you would keep going at your max possible speed, and once you release it you would start going at your constant wanted speed (CThrust)
// Thats why i have 2 vars, bAfterburn is for after burning (holding key and having enough E), bBurn is for only holding the afterburn key with no regard to its depletion
var bool			bAfterburn, bBurn; // note that you should set both of those to true when you want to afterburn
var() float			ForwardTrust, TurnRate, AfterBurnThrust; // This is basically how fast our ship turns/moves
var float			CurRoll, CurSteering, CurThrottle, UnitAccRate; // This is used for acceleration
// CThrust is used for ships that can assign a specific throttle value, like 75% of max speed or whatever...
// UnitAccRate is how much each value increases per second
var vector			ExtraForce, ExtraTorque; // Used for Karama forces pileup
 
// Ship replication vars and functions, thanks daid!
struct StructShipState
{
	var KRBVec				ChassisPosition;
	var Quat				ChassisQuaternion;
	var KRBVec				ChassisLinVel;
	var KRBVec				ChassisAngVel;
 
	var float				ServerSteering;
	var float				ServerThrottle;
	var float				ServerRoll;
 
	var bool				ServerbAfterburn;
	var bool				ServerbBurn;
 
	var bool				bNewState; // Set to true whenever a new state is received and should be processed
};
 
var KRigidBodyState		ChassisState;
 
var StructShipState		ShipState; // This is replicated to the ship, and processed to update all the parts.
var bool			bNewShipState; // Indicated there is new data processed, and chassis RBState should be updated.
 
var float			NextNetUpdateTime;	// Next time we should force an update of vehicles state.
var() float			MaxNetUpdateInterval;
 
var int AVar;//Just for replication, else the ShipState doesn't get replicated
 
Replication
{
	unreliable if(Role == ROLE_Authority)
		ShipState, AVar;
}
 
simulated event VehicleStateReceived()
{
	if(!ShipState.bNewState)
		return;
 
	// Get root chassis info
	ChassisState.Position = ShipState.ChassisPosition;
	ChassisState.Quaternion = ShipState.ChassisQuaternion;
	ChassisState.LinVel = ShipState.ChassisLinVel;
	ChassisState.AngVel = ShipState.ChassisAngVel;
 
	// Update control inputs
	Steering = ShipState.ServerSteering;
	Throttle = ShipState.ServerThrottle;
	Roll = ShipState.ServerRoll;
 
	// Afterburner
	bAfterburn = ShipState.ServerbAfterburn;
	bBurn = ShipState.ServerbBurn;
 
	// Update flags
	ShipState.bNewState = false;
	bNewShipState = true;
}
 
simulated event bool KUpdateState(out KRigidBodyState newState)
{
	// This should never get called on the server - but just in case!
	if(Role == ROLE_Authority || !bNewShipState)
		return false;
 
	// Apply received data as new position of ship chassis.
	newState = ChassisState;
	bNewShipState = false;
 
	return true;
}
 
function PackState()
{
	local vector chassisPos, chassisLinVel, chassisAngVel;
	local vector oldPos, oldLinVel;
	local KRigidBodyState ChassisState;
 
	// Get chassis state.
	KGetRigidBodyState(ChassisState);
 
	chassisPos = KRBVecToVector(ChassisState.Position);
	chassisLinVel = KRBVecToVector(ChassisState.LinVel);
	chassisAngVel = KRBVecToVector(ChassisState.AngVel);
 
	// Last position we sent
	oldPos = KRBVectoVector(ShipState.ChassisPosition);
	oldLinVel = KRBVectoVector(ShipState.ChassisLinVel);
 
	// See if state has changed enough, or enough time has passed, that we 
	// should send out another update by updating the state struct.
	if( !KIsAwake() )
	{
		return; // Never send updates if physics is at rest
	}
 
	if( VSize(oldPos - chassisPos) > 5 ||
		VSize(oldLinVel - chassisLinVel) > 1 ||
		Abs(ShipState.ServerThrottle - Throttle) > 0.1 ||
		Abs(ShipState.ServerSteering - Steering) > 0.1 ||
		Abs(ShipState.ServerRoll - Roll) > 0.1 ||
		bAfterburn != ShipState.ServerbAfterburn ||
		bBurn != ShipState.ServerbBurn ||
		Level.TimeSeconds > NextNetUpdateTime )
	{
		NextNetUpdateTime = Level.TimeSeconds + MaxNetUpdateInterval;
	}
	else
	{
		return;
	}
 
	ShipState.ChassisPosition = ChassisState.Position;
	ShipState.ChassisQuaternion = ChassisState.Quaternion;
	ShipState.ChassisLinVel = ChassisState.LinVel;
	ShipState.ChassisAngVel = ChassisState.AngVel;
 
	// Player Input
	ShipState.ServerSteering = Steering;
	ShipState.ServerThrottle = Throttle;
	ShipState.ServerRoll = Roll;
 
	// AfterBurner
	ShipState.ServerbAfterburn = bAfterburn;
	ShipState.ServerbBurn = bBurn;
 
	// This flag lets the client know this data is new.
	ShipState.bNewState = true;
	//Make sure ShipState gets replicated
	AVar++;
	if (AVar > 10)
		AVar=0;
}
 
simulated function Tick(float DeltaTime)
{
	Super.Tick(DeltaTime);
	if(!KIsAwake() && Controller!=None)
		KWake();
	if(Role == ROLE_Authority && Level.NetMode != NM_StandAlone)
		PackState();
	UpdateAcceleration(DeltaTime);
	UpdateExtraForce(DeltaTime);
}
simulated function UpdateAcceleration(float Delta)
{
	CurSteering = CurSteering + Steering * UnitAccRate * Delta;
	CurThrottle = CurThrottle + Throttle * UnitAccRate * Delta;
	CurRoll     = CurRoll     + Roll     * UnitAccRate * Delta;
	if(Steering==0)
		CurSteering=0;
	if(Throttle==0)
		CurThrottle=0;
	if(Roll==0)
		CurRoll=0;
 
	if(Abs(CurSteering)>Abs(Steering))
		CurSteering=Steering;
	if(Abs(CurThrottle)>Abs(Throttle))
		CurThrottle=Throttle;
	if(Abs(CurRoll)>Abs(Roll))
		CurRoll=Roll;
 
	if(bAfterBurn)
	{
		ReduceAfterBurnEnergy(); // just made this up, put it here incase you have something like this
		if(GetAfterburnEnergy()<=0) // Another fictional function...
		{
			bAfterBurn=False;
		}
		else
		{
			CThrust=AfterBurnThrust;
		}
	}
	if(bBurn && !bAfterBurn && CThrust!=1)
	{
		CThrust=1;
	}
	else if(!bBurn && !bAfterBurn && CThrust!=OldThrust)
	{
		CThrust=OldThrust;
	}
 
	if(TempThrust!=-1 && !bAfterBurn)
	{
		if(Abs(CurThrust-TempThrust)<0.01)
			CurThrust=TempThrust;
		if(CurThrust>TempThrust)
			CurThrust = CurThrust - UnitAccRate * Delta / 4; 
			// I made linear acceleration 4 times slower than rotation, you can do whatever you want though
		else if(CurThrust<TempThrust)
			CurThrust = CurThrust + UnitAccRate * Delta / 4;
	}
	else if(CurThrust!=CThrust && !bAfterBurn)
	{
		if(Abs(CurThrust-CThrust)<0.01)
			CurThrust=CThrust;
		else if(CurThrust>CThrust)
			CurThrust = CurThrust - UnitAccRate * Delta / 4;
		else if(CurThrust<CThrust)
			CurThrust = CurThrust + UnitAccRate * Delta / 4;
	}
	else if(CurThrust!=CThrust && bAfterBurn)
	{
		if(Abs(CurThrust-CThrust)<0.01)
			CurThrust=CThrust;
		else if(CurThrust>CThrust)
			CurThrust = CurThrust - UnitAccRate * Delta / 2.2; // Acceleration with afterburner a lot faster
		else if(CurThrust<CThrust)
			CurThrust = CurThrust + UnitAccRate * Delta / 2.2;
	}
}
 
simulated function UpdateExtraForce(float Delta)
{
	local vector worldForward, worldDown, worldLeft;
 
	worldForward = vect(1, 0, 0) >> Rotation;
	worldDown = vect(0, 0, -1) >> Rotation;
	worldLeft = vect(0, -1, 0) >> Rotation;
 
	ExtraForce = ExtraForce + worldForward * ForwardThrust * Delta * CurThrust; // Speed
	ExtraTorque = ExtraTorque + worldDown * TurnRate * Delta * CurSteering; // Yaw
	ExtraTorque = ExtraTorque + worldLeft * TurnRate * Delta * CurThrottle; // Pitch
	ExtraTorque = ExtraTorque + worldForward * TurnRate * Delta * -CurRoll; // Roll
}
 
simulated event KApplyForce(out vector Force, out vector Torque)
{
	// This actually does the applying of the piled up force
	Force = ExtraForce;
	Torque = ExtraTorque;
	ExtraForce = vect(0,0,0);
	ExtraTorque = vect(0,0,0);
}
 
DefaultProperties
{
     // Create Karma collision Params for ourselves, you can change whatever you want here
     Begin Object Class=KarmaParamsRBFull Name=KParams0
         KLinearDamping=2.000;
         KAngularDamping=2.000;
         KStartEnabled=True
         bHighDetailOnly=False
         bClientOnly=False
         bKDoubleTickRate=False
         KFriction=1.600000
         KActorGravScale = 0.0;
	 KMass=2;
         Name="KParams0"
     End Object
     KParams=KarmaParamsRBFull'<MyPackage>.<MyShipClass>.KParams0'
 
     // And just some other variables:
     TurnRate=8
     ForwardThrust=5000
     UnitAccRate=2.0
     AfterBurnThrust=1.20 // basically means that afterburner goes at 120% of regular maximum speed
}

and that should be it, i hope i didnt miss anything...

Spark: KParams=KarmaParamsRBFull'<MyPackage>.<MyShipClass>.KParams0' <– Is this supposed to be a trap? ;) Took me a while to figure out that my ship didn't fly because this wasn't set to KParams0 and thus wasn't enabled at all. :)

Sir_Brizz: Zep, did you end up getting your Quaternion working for the indicator? I have a few ideas that work if you want to know what they are.

ProjectX: I'm a n00b to coding, but is it possible to take this code, and change it to create a "hovering vehicles" class, that basicly, wont go a certan height above ground, but, from high jumps, etc., go lower to the ground? I also saw that there was 3 types of thrust, is it possible to bind a key that will cycle through the flying types (for more precise flying, eg. for dog matches and realism)?

NickR: Does anyone have a working example of a flying vehicle?

Foxpaw: I do, but my mod is still quite a ways from release. The code above allegedly is working code for a flying vehicle, though I've never tested it.

LiKeMiKeS: I've tried many times compiling this KShip script and have ended up with an error for the ShipState 'struct' & 'var' references obscuring each other and haven't been able to figure out a clean fix so it will compile - without creating another unneeded var instance or reference. I'm still learning the nuances of uscript, so it's difficult to debug atm.

Brold9999: I tried the above code long ago and it didn't seem to work. I have gotten a lot of questions on my mod, UT Space Battle, as to how I implemented the vehicles. I did not use a Karma or PhysX based approach but that is what most of the people who have asked me about this seem to be looking for. I have taken the code above and hammered away at it until it works. I ripped out many unnecessary sections (such as afterburner) which only cluttered the salient concepts of implementing a 6DOF vehicle. The new version should compile and work out-of-the-box and has (mostly) working netcode. Some tweaks are still necessary to make a vehicle suitable for a mod but this one does the basics that seem to trip most people up when they try to implement a 6DOF vehicle. KShip is the base 6DOF vehicle, FlyingDog is a butchered Bulldog which is based on KShip. Code follows:

class KShip extends KVehicle;
 
// Used for Karma forces pileup
// Add to these variables to apply force and torque to the ship.
var vector extraForce, extraTorque;
 
// Amount to increment/decrement shipSteering.roll for every click of the mouse wheel.
var float rollUnit;
// Desired rotation torque is reduced each tick. This factor controls how rapidly old inputs decay.
var float turnDecayTime;
// Amount of rotation torque to apply for a given amount of mouse movement.
var float turnSensitivity;
// Maximum amount of rotation torque that can be accumulated.
var float maximumTurnRate;
// Maximum linear thrust the ship can apply.
var float maximumThrust;
 
// Desired torque on the ship - relative to it's current rotation.
var rotator shipSteering;
// Desired linear thrust to apply to the ship - not relative to it's current rotation.
var vector shipThrust;
 
// Ship replication vars and functions, thanks daid!
struct StructShipState
{
  var KRBVec  ChassisPosition;
  var Quat    ChassisQuaternion;
  var KRBVec  ChassisLinVel;
  var KRBVec  ChassisAngVel;
 
  var rotator serverSteering;
  var vector  serverThrust;
 
  var bool    bNewState; // Set to true whenever a new state is received and should be processed
};
 
var KRigidBodyState ChassisState;
var StructShipState ShipState; // This is replicated to the ship, and processed to update all the parts.
var bool bNewShipState; // Indicated there is new data processed, and chassis RBState should be updated.
 
var float NextNetUpdateTime;  // Next time we should force an update of vehicles state.
var() float MaxNetUpdateInterval;
 
var int AVar;//Just for replication, else the ShipState doesn't get replicated
 
replication
{
  unreliable if(Role == ROLE_Authority)
    ShipState, AVar;
}
 
simulated event VehicleStateReceived()
{
  if(!ShipState.bNewState)
    return;
 
  // Get root chassis info
  ChassisState.Position = ShipState.ChassisPosition;
  ChassisState.Quaternion = ShipState.ChassisQuaternion;
  ChassisState.LinVel = ShipState.ChassisLinVel;
  ChassisState.AngVel = ShipState.ChassisAngVel;
 
  // Update control inputs
  shipSteering = ShipState.serverSteering;
  shipThrust = ShipState.serverThrust;
 
  // Update flags
  ShipState.bNewState = false;
  bNewShipState = true;
}
 
simulated event bool KUpdateState(out KRigidBodyState newState)
{
  // This should never get called on the server - but just in case!
  if(Role == ROLE_Authority || !bNewShipState)
    return false;
 
  // Apply received data as new position of ship chassis.
  newState = ChassisState;
  bNewShipState = false;
 
  return true;
}
 
function PackState()
{
  local vector chassisPos, chassisLinVel, chassisAngVel;
  local vector oldPos, oldLinVel;
  local KRigidBodyState localChassisState;
 
  // Get chassis state.
  KGetRigidBodyState(localChassisState);
 
  chassisPos = KRBVecToVector(localChassisState.Position);
  chassisLinVel = KRBVecToVector(localChassisState.LinVel);
  chassisAngVel = KRBVecToVector(localChassisState.AngVel);
 
  // Last position we sent
  oldPos = KRBVectoVector(ShipState.ChassisPosition);
  oldLinVel = KRBVectoVector(ShipState.ChassisLinVel);
 
  // See if state has changed enough, or enough time has passed, that we 
  // should send out another update by updating the state struct.
  if( !KIsAwake() )
  {
    return; // Never send updates if physics is at rest
  }
 
  if( VSize(oldPos - chassisPos) > 5 ||
    VSize(oldLinVel - chassisLinVel) > 1 ||
    abs(shipState.serverSteering.yaw - shipSteering.yaw) > 0.1 ||
    abs(shipState.serverSteering.pitch - shipSteering.pitch) > 0.1 ||
    abs(shipState.serverSteering.roll - shipSteering.roll) > 0.1 ||
    VSize(shipState.serverThrust - shipThrust) > 1 ||
    Level.TimeSeconds > NextNetUpdateTime )
  {
    NextNetUpdateTime = Level.TimeSeconds + MaxNetUpdateInterval;
  }
  else
  {
    return;
  }
 
  ShipState.ChassisPosition = localChassisState.Position;
  ShipState.ChassisQuaternion = localChassisState.Quaternion;
  ShipState.ChassisLinVel = localChassisState.LinVel;
  ShipState.ChassisAngVel = localChassisState.AngVel;
 
  shipState.serverSteering = shipSteering;
  shipState.serverThrust = shipThrust;
 
  // This flag lets the client know this data is new.
  ShipState.bNewState = true;
  //Make sure ShipState gets replicated
  AVar++;
  if (AVar > 10)
    AVar=0;
}
 
simulated function setInitialState() {
  super.setInitialState();
 
  // don't disable my tick!
  enable('tick');
}
 
 
simulated event drivingStatusChanged() {
  super.drivingStatusChanged();
 
  // don't disable my tick!
  enable('tick');
}
 
function KDriverEnter(Pawn P) {
  super.KDriverEnter(p);
 
  if (PlayerController(controller) != none)
    controller.gotoState(landMovementState);
}
 
simulated function prevWeapon() {
  shipSteering.roll = fclamp(shipSteering.roll - (rollUnit * turnSensitivity), -maximumTurnRate, maximumTurnRate);
}
 
simulated function nextWeapon() {
  shipSteering.roll = fclamp(shipSteering.roll + (rollUnit * turnSensitivity), -maximumTurnRate, maximumTurnRate);
}
 
simulated function updateRocketAcceleration(float deltaTime, float yawChange, float pitchChange) {
  if (deltaTime >= turnDecayTime)
    shipSteering = rot(0,0,0);
  else
    shipSteering *= (turnDecayTime-deltaTime) / turnDecayTime;
 
  // / 6000 because the aForward, etc. are 6000 by default when the appropriate button is pressed.
  shipThrust = (((vect(1,0,0) * PlayerController(Controller).aForward) + (vect(0,1,0) * PlayerController(Controller).aStrafe) + (vect(0,0,1) * PlayerController(Controller).aUp)) / 6000) >> rotation;
  if (vsize(shipThrust) > 1)
    shipThrust = normal(shipThrust);
 
  shipSteering.yaw = fclamp(shipSteering.yaw - (yawChange * turnSensitivity), -maximumTurnRate, maximumTurnRate);
  shipSteering.pitch = fclamp(shipSteering.pitch + (pitchChange * turnSensitivity), -maximumTurnRate, maximumTurnRate);
}
 
simulated function tick(float delta)
{  super.tick(delta);
 
  if (controller == none) {
    shipThrust = vect(0,0,0);
    shipSteering = rot(0,0,0);
  }
 
  if(!KIsAwake() && controller != none)
    KWake();
 
  if(Role == ROLE_Authority && Level.NetMode != NM_StandAlone)
    PackState();
 
  updateExtraForce(delta);
}
 
simulated function updateExtraForce(float delta)
{
  local vector worldForward, worldDown, worldLeft;
 
  worldForward = vect(1, 0, 0) >> Rotation;
  worldDown = vect(0, 0, -1) >> Rotation;
  worldLeft = vect(0, -1, 0) >> Rotation;
 
  ExtraForce = ExtraForce + shipThrust * maximumThrust * delta; // Speed
  ExtraTorque = ExtraTorque + worldDown * shipSteering.yaw * delta; // Yaw
  ExtraTorque = ExtraTorque - worldLeft * -shipSteering.pitch * delta; // Pitch
  ExtraTorque = ExtraTorque + worldForward * -shipSteering.roll * delta; // Roll
}
 
simulated event KApplyForce(out vector Force, out vector Torque)
{
  // This actually does the applying of the piled up force
  Force = ExtraForce;
  Torque = ExtraTorque;
  ExtraForce = vect(0,0,0);
  ExtraTorque = vect(0,0,0);
}
 
DefaultProperties
{
  landMovementState=PlayerSpaceFlying
 
  rollUnit=1024
 
  turnDecayTime=0.5
  turnSensitivity=0.5
  maximumTurnRate=5000
  maximumThrust=2500
}
class FlyingDog extends KShip placeable CacheExempt;
 
// triggers used to get into the FlyingDog
var const vector  FrontTriggerOffset;
var SVehicleTrigger  FLTrigger, FRTrigger;
 
// Maximum speed at which you can get in the vehicle.
var (FlyingDog) float TriggerSpeedThresh;
var bool      TriggerState; // true for on, false for off.
 
// Destroyed Buggy
var (FlyingDog) class<Actor>  DestroyedEffect;
var (FlyingDog) sound         DestroyedSound;
 
// Weapon
var       float FireCountdown;
 
var (FlyingDog) float   FireInterval;
var (FlyingDog) vector  weaponFireOffset;
 
simulated function PostNetBeginPlay()
{
  local vector RotX, RotY, RotZ;
 
  Super.PostNetBeginPlay();
 
  GetAxes(Rotation,RotX,RotY,RotZ);
 
  // Only have triggers on server
  if(Level.NetMode != NM_Client)
  {
    // Create triggers for gettting into the FlyingDog
    FLTrigger = spawn(class'SVehicleTrigger', self,, Location + FrontTriggerOffset.X * RotX + FrontTriggerOffset.Y * RotY + FrontTriggerOffset.Z * RotZ);
    FLTrigger.SetBase(self);
    FLTrigger.SetCollision(true, false, false);
 
    FRTrigger = spawn(class'SVehicleTrigger', self,, Location + FrontTriggerOffset.X * RotX - FrontTriggerOffset.Y * RotY + FrontTriggerOffset.Z * RotZ);
    FRTrigger.SetBase(self);
    FRTrigger.SetCollision(true, false, false);
 
    TriggerState = true;
  }
 
  // If this is not 'authority' version - don't destroy it if there is a problem.
  // The network should sort things out.
  if(Role != ROLE_Authority) {
    KarmaParams(KParams).bDestroyOnSimError = False;
  }
}
 
simulated event Destroyed()
{
  // Clean up random stuff attached to the car
  if(Level.NetMode != NM_Client)
  {
    FLTrigger.Destroy();
    FRTrigger.Destroy();
  }
 
  Super.Destroyed();
 
  // Trigger destroyed sound and effect
  if(Level.NetMode != NM_DedicatedServer)
  {
    spawn(DestroyedEffect, self, , Location );
    PlaySound(DestroyedSound);
  }
}
 
function KDriverEnter(Pawn p)
{
  Super.KDriverEnter(p);
 
  p.bHidden = True;
  ReceiveLocalizedMessage(class'Vehicles.BulldogMessage', 1);
}
 
function bool KDriverLeave(bool bForceLeave)
{
  local Pawn OldDriver;
 
  OldDriver = Driver;
 
  // If we succesfully got out of the car, make driver visible again.
  if( Super.KDriverLeave(bForceLeave) )
  {
    OldDriver.bHidden = false;
    AmbientSound = None;
    return true;
  }
  else
    return false;
}
 
function fireWeapons(bool bWasAltFire)
{
  local vector FireLocation;
  local PlayerController PC;
 
  // Client can't do firing
  if(Role != ROLE_Authority)
    return;
 
  FireLocation = Location + (weaponFireOffset >> Rotation);
 
  while(FireCountdown <= 0)
  {
    if(!bWasAltFire)
    {
      spawn(class'PROJ_TurretSkaarjPlasma', self, , FireLocation, rotation);
 
      // Play firing noise
      PlaySound(Sound'ONSVehicleSounds-S.Laser02', SLOT_None,,,,, false);
      PC = PlayerController(Controller);
      if (PC != None && PC.bEnableWeaponForceFeedback)
        PC.ClientPlayForceFeedback("RocketLauncherFire");
    }
    else
    {
      spawn(class'PROJ_SpaceFighter_Rocket', self, , FireLocation, rotator(vector(rotation) + Vrand() * 0.05));
 
      // Play firing noise
      PlaySound(Sound'AssaultSounds.HnShipFire01', SLOT_None,,,,, false);
      PC = PlayerController(Controller);
      if (PC != None && PC.bEnableWeaponForceFeedback)
        PC.ClientPlayForceFeedback("RocketLauncherFire");
    }
 
    FireCountdown += FireInterval;
  }
}
 
// Fire a rocket (if we've had time to reload!)
function VehicleFire(bool bWasAltFire)
{
  Super.VehicleFire(bWasAltFire);
 
  if(FireCountdown < 0)
  {
    FireCountdown = 0;
    fireWeapons(bWasAltFire);
  }
}
 
simulated function Tick(float Delta)
{
  Local float VMag;
 
  Super.Tick(Delta);
 
  // Weapons (run on server and replicated to client)
  if(Role == ROLE_Authority)
  {
    // Countdown to next shot
    FireCountdown -= Delta;
 
    // This is for sustained barrages.
    // Primary fire takes priority
    if(bVehicleIsFiring)
      fireWeapons(false);
    else if(bVehicleIsAltFiring)
      fireWeapons(true);
  }
 
  // Dont have triggers on network clients.
  if(Level.NetMode != NM_Client)
  {
    // If vehicle is moving, disable collision for trigger.
    VMag = VSize(Velocity);
 
    if(VMag < TriggerSpeedThresh && TriggerState == false)
    {
      FLTrigger.SetCollision(true, false, false);
      FRTrigger.SetCollision(true, false, false);
      TriggerState = true;
    }
    else if(VMag > TriggerSpeedThresh && TriggerState == true)
    {
      FLTrigger.SetCollision(false, false, false);
      FRTrigger.SetCollision(false, false, false);
      TriggerState = false;
    }
  }
}
 
// Really simple at the moment!
function TakeDamage(int Damage, Pawn instigatedBy, Vector hitlocation,
            Vector momentum, class<DamageType> damageType)
{
  // Avoid damage healing the car!
  if(Damage < 0)
    return;
 
  if(damageType == class'DamTypeSuperShockBeam')
    Health -= 100; // Instagib doesn't work on vehicles
  else
    Health -= 0.5 * Damage; // Weapons do less damage
 
  // The vehicle is dead!
  if(Health <= 0)
  {
    if ( Controller != None )
    {
      if( Controller.bIsPlayer )
      {
        ClientKDriverLeave(PlayerController(Controller)); // Just to reset HUD etc.
        Controller.PawnDied(self); // This should unpossess the controller and let the player respawn
      }
      else
        Controller.Destroy();
    }
 
    Destroy(); // Destroy the vehicle itself (see Destroyed below)
  }
 
    //KAddImpulse(momentum, hitlocation);
}
 
// AI Related code
function Actor GetBestEntry(Pawn P)
{
  if ( VSize(P.Location - FLTrigger.Location) < VSize(P.Location - FRTrigger.Location) )
    return FLTrigger;
  return FRTrigger;
}
 
defaultproperties
{
  DrawType=DT_StaticMesh
  StaticMesh=StaticMesh'BulldogMeshes.Simple.S_Chassis'
 
  Begin Object Class=KarmaParamsRBFull Name=KParams0
    KActorGravScale=0
    KInertiaTensor(0)=20
    KInertiaTensor(1)=0
    KInertiaTensor(2)=0
    KInertiaTensor(3)=30
    KInertiaTensor(4)=0
    KInertiaTensor(5)=48
    KCOMOffset=(X=0.8,Y=0.0,Z=-0.7)
    KStartEnabled=True
    KFriction=1.6
    KLinearDamping=1
    KAngularDamping=10
    bKNonSphericalInertia=False
    bHighDetailOnly=False
    bClientOnly=False
    bKDoubleTickRate=True
    Name="KParams0"
  End Object
  KParams=KarmaParams'KParams0'
 
  DrawScale=0.4
  drawScale3D=(x=-1,y=1,z=1)
 
  DestroyedEffect=class'XEffects.RocketExplosion'
  DestroyedSound=sound'WeaponSounds.P1RocketLauncherAltFire'
 
  FrontTriggerOffset=(X=0,Y=165,Z=10)
 
  TriggerSpeedThresh=40
 
  // Weaponry
  FireInterval=0.1
  weaponFireOffset=(X=0,Y=0,Z=80)
 
  // Driver positions
  ExitPositions(0)=(X=0,Y=200,Z=100)
  ExitPositions(1)=(X=0,Y=-200,Z=100)
  ExitPositions(2)=(X=350,Y=0,Z=100)
  ExitPositions(3)=(X=-350,Y=0,Z=100)
 
  DrivePos=(X=-165,Y=0,Z=-100)
 
  Health=800
  HealthMax=800
 
  SoundRadius=255
}