Legacy:KShip

From Unreal Wiki, The Unreal Engine Documentation Site
Revision as of 08:32, 17 April 2010 by 99.236.204.143 (talk) (Added an implmentation for KShip which should work out-of-the-box, and a sample vehicle (FlyingDog) based on it.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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)

<uscript> 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

} </uscript>

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:

<uscript> 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

} </uscript>

<uscript> 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

} </uscript>