This code has arisen from the need to have a vehicle where the player can still be seen and shot whilst in control of a vehicle (for the artillery and horses in the LawDogs mod). Instead of the player's original Pawn being hidden and the vehicle possessed, the original pawn is attached to the vehicle and the vehicle controlled indirectly.
This has the advantage not only of being able to kill the player without damaging the vehicle and vice versa, but also it allows the player to potentially fire their normal weapons whilst on the vehicle.
The code shown here has only the code relevant to this control method present; It is a long way from a complete vehicle. There is no bot support.
First the vehicle class:
class LawDogsVehicle extends KVehicle abstract; var Pawn RepDriver; //Replicated 'Driver'. replication { reliable if(Role == ROLE_Authority) RepDriver; } simulated function ClientKDriverEnter(PlayerController pc) { PlayerController(RepDriver.Controller).bBehindView = bUseBehindView; } function KDriverEnter(Pawn p) { Driver = p; RepDriver = Driver; Driver.bCollideWorld = false; Driver.bPhysicsAnimUpdate = false; Driver.Velocity = vect(0,0,0); Driver.SetPhysics(PHYS_None); Driver.SetBase(self); Driver.SetRelativeLocation(DrivePos); Driver.SetRelativeRotation(rot(0, 0, 0)); LawDogsPlayer(Driver.Controller).Vehicle = self; LawDogsPlayer(Driver.Controller).GotoState('PlayerRiding'); SetOwner(Driver.Controller); ClientKDriverEnter(PlayerController(Driver.Controller)); } simulated function ClientKDriverLeave(PlayerController pc) { pc.bBehindView = pc.Default.bBehindView; } function bool KDriverLeave(bool bForceLeave) { local int i; local bool havePlaced; local vector HitLocation, HitNormal, tryPlace; if(Driver == None) return false; Driver.bCollideWorld = true; for(i = 0; i < ExitPositions.Length && havePlaced == false; i++) { tryPlace = Location + (ExitPositions[i] >> Rotation); if(Trace(HitLocation, HitNormal, tryPlace, Location, false) != None) continue; if(!Driver.SetLocation(tryPlace)) continue; havePlaced = true; } if(!havePlaced && !bForceLeave) { Driver.bCollideWorld = false; return false; } Driver.PlayWaiting(); Driver.bPhysicsAnimUpdate = Driver.Default.bPhysicsAnimUpdate; Driver.SetBase(None); Driver.Acceleration = vect(0, 0, 24000); if(Driver.TouchingWaterVolume()) Driver.SetPhysics(PHYS_Swimming); else Driver.SetPhysics(PHYS_Falling); Driver.Controller.Restart(); LawDogsPlayer(Driver.Controller).Vehicle = None; ClientKDriverLeave(PlayerController(Driver.Controller)); Driver = None; RepDriver = None; SetOwner(None); Throttle=0; Steering=0; return true; } function TakeDamage(int Damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class<DamageType> damageType) { Health -= Damage; if(Health <= 0) { //Make sure driver leaves when vehicle is destroyed. if(Driver != None) KDriverLeave(true); Destroy(); } } simulated function Tick(float DeltaTime) { local rotator DriverRot; if(RepDriver != None) { //So taking damage doesn't make the driver start falling. if(RepDriver.Physics != PHYS_None) RepDriver.SetPhysics(PHYS_None); //Make very sure the driver stays still. RepDriver.SetRelativeLocation(DrivePos); DriverRot.Yaw = RepDriver.RelativeRotation.Yaw; RepDriver.SetRelativeRotation(DriverRot); } } //Allow camera to rotate relative to the vehicle, within limits. simulated function bool SpecialCalcView(out actor ViewActor, out vector CameraLocation, out rotator CameraRotation) { if(RepDriver == None || RepDriver.Controller == None) return false; ViewActor = RepDriver; if(CameraRotation.Yaw ClockwiseFrom (Rotation.Yaw + MaxYaw)) CameraRotation.Yaw = Rotation.Yaw + MaxYaw; else if(!(CameraRotation.Yaw ClockwiseFrom (Rotation.Yaw + MinYaw))) CameraRotation.Yaw = Rotation.Yaw + MinYaw; CameraRotation = normalize(CameraRotation); RepDriver.SetTwistLook(normalize(CameraRotation - Rotation).Yaw, CameraRotation.Pitch); RepDriver.Controller.SetRotation(CameraRotation); CameraLocation = RepDriver.Location + RepDriver.EyePosition(); if(PlayerController(RepDriver.Controller).bBehindView) CameraLocation += BehindViewOffset >> Rotation; return true; }
And the new stuff for the PlayerController class:
class LawDogsPlayer extends xPlayer config(user); var KVehicle Vehicle; replication { reliable if(Role == ROLE_Authority) Vehicle; } //Fix dying whilst controling a vehicle. function WasKilledBy(Controller Other) { if(Vehicle != None) Vehicle.KDriverLeave(true); Super.WasKilledBy(Other); } //Allow vehicle to handle camera. event PlayerCalcView(out actor ViewActor, out vector CameraLocation, out rotator CameraRotation) { if(Vehicle != None && Vehicle.bSpecialCalcView && Vehicle.SpecialCalcView(ViewActor, CameraLocation, CameraRotation)) return; Super.PlayerCalcView(ViewActor, CameraLocation, CameraRotation); } //Using a LawDogsVehicle. state PlayerRiding { ignores SeePlayer, HearNoise, Bump; function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot){} //Pressing 'use' will exit vehicle (to free up the jump key for other purposes). function ServerUse() { if(Level.Pauser == PlayerReplicationInfo) { SetPause(false); return; } if(Pawn == None) return; if(Vehicle != None) Vehicle.bGetOut = true; } exec function Fire(optional float F) { if(Vehicle != None) { Vehicle.VehicleFire(false); Vehicle.bVehicleIsFiring = true; } } exec function AltFire(optional float F) { if(Vehicle != None) { Vehicle.VehicleFire(true); Vehicle.bVehicleIsAltFiring = true; } } // Set the throttle, steering etc. for the vehicle based on the input provided function ProcessDrive(float InForward, float InStrafe, bool InJump) { if(Vehicle == None) { log("PlayerRiding.PlayerMove: No Vehicle"); return; } //Horses can jump. if(bPressedJump) { Vehicle.DoJump(bUpdating); bPressedJump = false; } if(InForward > 1) Vehicle.Throttle = 1; else if(InForward < -1) Vehicle.Throttle = -1; else Vehicle.Throttle = 0; if(InStrafe < -1) Vehicle.Steering = -1; else if(InStrafe > 1) Vehicle.Steering = 1; else Vehicle.Steering = 0; } function PlayerMove(float DeltaTime) { //Only servers can actually do the driving logic. if(Role < ROLE_Authority) ServerDrive(aForward, aStrafe, bPressedJump); else ProcessDrive(aForward, aStrafe, bPressedJump); if(Vehicle != None) { if(bFire == 0 && Vehicle.bVehicleIsFiring) { Vehicle.VehicleCeaseFire(false); Vehicle.bVehicleIsFiring = false; } if(bAltFire == 0 && Vehicle.bVehicleIsAltFiring) { Vehicle.VehicleCeaseFire(true); Vehicle.bVehicleIsAltFiring = false; } } //update 'looking' rotation - no affect on driving UpdateRotation(DeltaTime, 2); } function BeginState() { CleanOutSavedMoves(); } function EndState() { CleanOutSavedMoves(); } }
One thing that you may find necessary but which is not shown here is that to stop a player from firing their own weapons, you need to give them a non-functional dummy weapon whilst they are in control of the vehicle.
Another potential problem is that the player's pawn can encroach on other pawns whilst it is attached to the vehicle, telefragging them instantly. This needs to be fixed in the EncroachedBy function in the pawn.
Foxpaw: From what I've heard, this is already part of the vehicle code for UT2004. But I haven't played UT2004 so I can't say that with certainty.
Mr Evil: That's what I've heard too, but I wanted it now, so I did it.