Calrathan's Quake3 Ladder Code

Source Author: Calrathan

Tutorial Author: Calrathan

Difficulty: Medium / Hard

Okay, for all you guys/girls out there who have quake2 maps that you want to convert to q3, or for those who are sick of all the jump pads, I've got the solution! Thanks to Zoid for the help he gave me with the elusive "ladder" surface tag.

I'd first like to mention that I've seen all the "simple" tutorials out there for minor changes, and so I decided I'd write this to get something more substantial out there. This is meant for mod authors who know what they're doing and want ladders. I've tried to comment everything so its understandable, but this is NOT MEANT FOR PEOPLE TRYING TO LEARN HOW TO PROGRAM. This is for people who want ladders. =)

Let's get started, shall we?

-------------------------------------------------------------------------------------------------------------------------------------

Okay, first things first. We need to declare variables we'll be using througout the physics functions. This first thing we'll add is a flag that we can toggle if our client is on a ladder. We're doing this so we don't have to call the function we'll be making each time we want to know their ladder status. So, down to the nitty gritty. Feel free to open up bg_local.h. We're going to modify the pml_t (thats an L not a 1) structure. This structure is used as a global in the player physics file, and so it can be access anywhere. Add the hi-lighted 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 flag to tell when the player is on a ladder

        float impactSpeed;

        vec3_t previous_origin;
        vec3_t previous_velocity;
        int previous_waterlevel;
} pml_t;

Okay, feel free to close that file, we're all done with it.

-------------------------------------------------------------------------------------------------------------------------------------

Our next task is to add a couple variables for the standard physics code. Go ahead and open up bg_pmove.c.
Near the top of the file you should see the following. Add in the three global variables I've hi-lighted.

=
=
=
=
=
+
=
=
=
+
=
=
=
+
=
=
=
=


        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;

Now, that wasn't too hard, was it?

-------------------------------------------------------------------------------------------------------------------------------------

Wheee, okay. Now that we've made our little friction and 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 ).

=
=
=
=
=
=
+
+
+
+
=
=
=
=
=
=
=
=


        // 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;

-------------------------------------------------------------------------------------------------------------------------------------

Now that we're done with that, lets go to the end of the function. At this point we need to add our actual functions to handle checking for the ladder, and also to handle the physics for people on ladders.

At this point, add the following.

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


/*
===================
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;

}

-------------------------------------------------------------------------------------------------------------------------------------

OKAY! Final step. Last thing to do is go into void PmoveSingle (pmove_t *pmove) and make sure we use the ladder if we're on it. This should be around line 1900

=
=
=
=
=
=
=
=
=
+
=
=
=
=
=
=
=
=
=
=
=
=
=
+
+

=
=
=
=
=
=
=
=


        // 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();
        }

-------------------------------------------------------------------------------------------------------------------------------------

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 we finish, 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. =)

Addendum! Known issue: Player's legs stay in previous state, sometimes as if they're running in midair

If you use this tutorial, please give credit to the author,Calrathan.

Tutorial by Calrathan

This site, and all content and graphics displayed on it,
are Šopyrighted to the respective author. All rights reserved.
This site is best viewed in 1024x768x16bit or greater.


The color scheme/setup was taken from the QDevels tutorial site [Without permission].