TUTORIAL 8 - Ladders!
by Calrathan
If you've played Quake2, HalfLife, or a number of other newer games, you've seen and used ladders.
What better way is there of going up and down? iD software proposed jump pads, but if you're like
the hundreds of other mod authors out there who want a touch of realism, jump and accelerator pads
just won't do! So why are you here? To get ladders working in Quake3 of course!
1. PLAYER MOVEMENT CODE
For those of you who aren't really well versed in the code, I'll go into a little background on
bg_pmove.c. This is the file that controls all of the different ways a client can move.
Because we're allowed to change it, it is included in both the cgame [for prediction] module, and
the game [for actual movement] module. Just remember to compile BOTH of these projects after we're
done, or Ranger / Mynx / Bitterman / Whoever will have a major case of the jitters. =)
The player movement code basically has two main parts. Almost all of the movement code is called from
void PmoveSingle (pmove_t *pmove). This function, with the help of a bunch of
if statements, takes the state of the player and determines which type of movement they're allowed
to make. The different movement functions are: PM_NoclipMove, PM_DeadMove, PM_FlyMove, PM_GrappleMove,
PM_AirMove, PM_WaterJumpMove, PM_WaterMove, PM_WalkMove & PM_AirMove.
What this tutorial [hopefully] will show you, is how to make PM_LadderMove, as well as the functions
and modifications needed to check and see if we're on the ladder.
2. THE DEFINITIONS
As with every C/C++ program, before you use something, you need to define it. So let's do just that.
We'll need a flag that we can set somewhere when we really ARE on the ladder, so we don't need to check more than
once per client frame. In bg_pmove.c there is a global definition of a a structure "pml" of type "pml_t".
This player movement structure is just the place to add this flag. Go ahead and open up
bg_local.h and look for the structure definiton. When you get there, add the
hilighted line below.
typedef struct {
vec3_t forward, right, up;
float frametime;
int msec;
qboolean walking;
qboolean groundPlane;
trace_t groundTrace;
qboolean ladder; // We'll use this to tell when the player is on a ladder
float impactSpeed;
vec3_t previous_origin;
vec3_t previous_velocity;
int previous_waterlevel;
} pml_t;
Sorry, but we're not quite done with our definitions. Open up bg_pmove.c and look
at the "movement parameters" section. You'll notice that there seem to be different settings for different
types of motion. the scale variables basically set the maximum velocity. The accelerate variables determine
how quickly you reach maximum velocity, and the friction values determine how quickly you stop. Because we're
moving vertically, we obviously can't go as fast as normal running. Ladders aren't like running surfaces which
we slip and slide on. Movement is very discrete on ladders, so we want to reach our maxiumum speed right away,
and we want to stop right away. [ Can you imagine floating upward on a ladder even after you stopped moving? ]
Because of all these factors, I chose the following values for our ladder. Feel free to play with them and see
what happens.
// movement parameters
float pm_stopspeed = 100;
float pm_duckScale = 0.25;
float pm_swimScale = 0.50;
float pm_wadeScale = 0.70;
float pm_ladderScale = 0.50; // Set the max movement speed to HALF of normal
float pm_accelerate = 10;
float pm_airaccelerate = 1;
float pm_ladderAccelerate = 3000; // The acceleration to friction ratio is 1:1
float pm_wateraccelerate = 4;
float pm_flyaccelerate = 8;
float pm_ladderfriction = 3000; // Friction is high enough so you don't slip down
float pm_friction = 6;
float pm_waterfriction = 1;
float pm_flightfriction = 3;
3. FRICTION!
Now that we've made our little friction, acceleration and scale variables, it's time to implement them. Make
sence? Good. Scroll down to around line 200 in bg_pmove.c. This should put you
smack dab in the center of static void PM_Friction( void ). Adding the following hilighted lines will
add our specified friction iff we're on the ladder.
// apply flying friction
if ( pm->ps->powerups[PW_FLIGHT] || pm->ps->pm_type == PM_SPECTATOR ) {
drop += speed*pm_flightfriction*pml.frametime;
}
if ( pml.ladder ) // If they're on a ladder...
{
drop += speed*pm_ladderfriction*pml.frametime; // Add ladder friction!
}
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0) {
newspeed = 0;
}
newspeed /= speed;
4. DOIN' THE MOVE
We're at the point were we need to make our actual functions. The first one will perform the actual
movement physics, and the second one is used to check if we're on the ladder. The order doesn't really matter at
this point, we just need to make sure we paste these functions into bg_pmove.c
somewhere BEFORE the function void PmoveSingle (pmove_t *pmove). I can't stress
this enough. I've already recieved emails from people who put it in past that function, and got compiler errors
complaining of a function "redefinition".
If you don't care about how this works, just go ahead and grab the code below. If you do, I'll go into it a little.
First off, I swiped the PM_WaterMove() code for PM_LadderMove(). Why? Because its basically the same idea. In
water you have full 360� motion; we have 360� motion here. You might not believe me when I say that, but it's true
so long as you're on the ladder. That brings us to the CheckLadder() function.
This one is fairly simple. We trace a box forward the size of our playermodel. If any point on this
box hits a ladder, then we're concidered on it. It sets the flag and returns.
/*
===================
PM_LadderMove()
by: Calrathan [Arthur Tomlin]
Right now all I know is that this works for VERTICAL ladders.
Ladders with angles on them (urban2 for AQ2) haven't been tested.
===================
*/
static void PM_LadderMove( void ) {
int i;
vec3_t wishvel;
float wishspeed;
vec3_t wishdir;
float scale;
float vel;
PM_Friction ();
scale = PM_CmdScale( &pm->cmd );
// user intentions [what the user is attempting to do]
if ( !scale ) {
wishvel[0] = 0;
wishvel[1] = 0;
wishvel[2] = 0;
}
else { // if they're trying to move... lets calculate it
for (i=0 ; i<3 ; i++)
wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove +
scale * pml.right[i]*pm->cmd.rightmove;
wishvel[2] += scale * pm->cmd.upmove;
}
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
if ( wishspeed > pm->ps->speed * pm_ladderScale ) {
wishspeed = pm->ps->speed * pm_ladderScale;
}
PM_Accelerate (wishdir, wishspeed, pm_ladderAccelerate);
// This SHOULD help us with sloped ladders, but it remains untested.
if ( pml.groundPlane && DotProduct( pm->ps->velocity,
pml.groundTrace.plane.normal ) < 0 ) {
vel = VectorLength(pm->ps->velocity);
// slide along the ground plane [the ladder section under our feet]
PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal,
pm->ps->velocity, OVERCLIP );
VectorNormalize(pm->ps->velocity);
VectorScale(pm->ps->velocity, vel, pm->ps->velocity);
}
PM_SlideMove( qfalse ); // move without gravity
}
/*
=============
CheckLadder [ ARTHUR TOMLIN ]
=============
*/
void CheckLadder( void )
{
vec3_t flatforward,spot;
trace_t trace;
pml.ladder = qfalse;
// check for ladder
flatforward[0] = pml.forward[0];
flatforward[1] = pml.forward[1];
flatforward[2] = 0;
VectorNormalize (flatforward);
VectorMA (pm->ps->origin, 1, flatforward, spot);
pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, spot,
pm->ps->clientNum, MASK_PLAYERSOLID);
if ((trace.fraction < 1) && (trace.surfaceFlags & SURF_LADDER))
pml.ladder = qtrue;
}
5. FINISHING OFF
The last step is to modify our void PmoveSingl e (pmove_t *pmove) function [you know,
the one that controlls almost all of the player movement stuff?] to include our ladders. You'll find the
function around line 1,900. This part if fairly simple. We just run our function to check if we're on a
ladder, and then if we are, we run the ladder physics. We place this after water move, so if you're in
the water it ignores the ladder altogether. Add the red lines. Have fun!
// set groundentity
PM_GroundTrace();
if ( pm->ps->pm_type == PM_DEAD ) {
PM_DeadMove ();
}
PM_DropTimers();
CheckLadder(); // ARTHUR TOMLIN check and see if they're on a ladder
if ( pm->ps->powerups[PW_FLIGHT] ) {
// flight powerup doesn't allow jump and has different friction
PM_FlyMove();
} else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) {
PM_GrappleMove();
// We can wiggle a bit
PM_AirMove();
} else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) {
PM_WaterJumpMove();
} else if ( pm->waterlevel > 1 ) {
// swimming
PM_WaterMove();
} else if (pml.ladder) {
PM_LadderMove();
} else if ( pml.walking ) {
// walking on ground
PM_WalkMove();
} else {
// airborne
PM_AirMove();
}
6. PUTTING LADDERS IN MAPS
There we go, the Ladder code is done. But, we still have a problem. Ladders don't seem to work in your maps, even if you did a direct .BSP conversion! Oh no!
Easy fix. Have your map designers [or for the versitile, do it yourself] make a box around your ladders. This box should be given a texture of "common/ladderclip". Edit your "quake3\baseq3\scripts\common.shader" file, and insert the lines:
textures/common/ladderclip
{
qer_trans 0.40
surfaceparm nolightmap
surfaceparm nomarks
surfaceparm nodraw
surfaceparm nonsolid
surfaceparm playerclip
surfaceparm noimpact
surfaceparm ladder
}
Before you leave, I'd like to remind you to rebuild BOTH the "game" and "cgame" module. This will the client side prediction work properly with ladders [ aka no shaking like a mofo ]. Compile your map, and there we have it. Working ladders. =)
7. ADDENDUM
Known issue: Player's legs stay in previous state, sometimes as if they're running in midair.
When we get a player animation tutorial up, I'll update this to reflect the "fix".
|