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 }