PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 8 | Next >>


  • Home/News
  • ModSource
  • Compiling
  • Help!!!
  • Submission
  • Contributors
  • Staff
  • Downloads

    < Index >
    1. Mod making 101
    2. Up 'n running
    3. Hello, QWorld!
    4. Infinite Haste
    5. Armor Piercing Rails
    6. Bouncing Rockets
    7. Cloaking
    8. Ladders
    9. Favourite Server
    10. Flame Thrower
    11. Vortex Grenades
    12. Grapple
    13. Lightning Discharge
    14. Locational Damage
    15. Leg Shots
    16. Weapon Switching
    17. Scoreboard frag-rate
    18. Vortex Grenades II
    19. Vulnerable Missiles
    20. Creating Classes
    21. Scrolling Credits
    22. Weapon Dropping
    23. Anti-Gravity Boots
    24. HUD scoreboard
    25. Flashlight and laser
    26. Weapon Positioning
    27. Weapon Reloading
    28. Progressive Zooming
    29. Rotating Doors
    30. Beheading (headshot!)
    31. Alt Weapon Fire
    32. Popup Menus I
    33. Popup Menus II
    34. Cluster Grenades
    35. Homing Rockets
    36. Spreadfire Powerup
    37. Instagib gameplay
    38. Accelerating rockets
    39. Server only Instagib
    40. Advanced Grapple Hook
    41. Unlagging your mod

    < Index >
    1. Entities
    2. Vectors
    3. Good Coding
    4. Compilers I
    5. Compilers II
    6. UI Menu Primer I
    7. UI Menu Primer II
    8. UI Menu Primer III
    9. QVM Communication, Cvars, commands
    10. Metrowerks CodeWarrior
    11. 1.27g code, bugs, batch


  • Quake3 Files
  • Quake3 Forums
  • Q3A Editing Message Board
  • Quake3 Editing


  • SumFuka
  • Calrathan
  • HypoThermia
  • WarZone

    Site Design by:
    ICEmosis Design

    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!


    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.


    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;


    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.

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


    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
    	if ( pm->ps->pm_type == PM_DEAD ) {
    		PM_DeadMove ();
    	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
    	} else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) {
    		// We can wiggle a bit
    	} else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) {
    	} else if ( pm->waterlevel > 1 ) {
    		// swimming
    	} else if (pml.ladder) {	
    	} else if ( pml.walking ) {
    		// walking on ground
    	} else {
    		// airborne


    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:
    	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. =)


    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".

    PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 8 | Next >>