Player Speeds - the mystery revealed


 

Posted by Doom Trooper (151.198.110.*) on April 26, 1999 at 00:19:23:

so, ya wanna add variable player speeds to your class-based mod?

read on.

first, some basics on how speeds are handled under the Q2 engine.

the cl_forwardspeed and cl_sidespeed console variables hold the walking speed of
the player for forward and side (strafing) movement. When the player holds down his movement keys, the value of these two cl_ variables is transmitted to the server, placed in the ucmd structure (in ClientThink in the p_client.c file) and used for prediction and moving the player. If the player is running, double the cl_ speeds are transmitted... so a player with a cl_forwardspeed of 200 will transmit a movement value of 200 while walking and 400 while running.

okay. now that that's outta the way, you're ready to modify these values. You'll need the stuffcmd function to make this work. it can be found in the CTF code, and may (I don't use 3.20 so I don't know for sure) be found in the 3.20 source code as well.

you'll need a single variable placed in the respawn structure of your entity's client structure. so, go into g_local.h and declare it like so;

int max_speed;

now, when initializing your player's data (when he changes classes, enters the server, whatever), set this value equal to the running speed you wish your player to have. like so;

ent->client->resp.max_speed = 300; // 300 is a slower running speed than max

next, use stuffcmd to force a new set of cl_ speeds on the player.

stuffcmd (ent, "cl_forwardspeed 150\n");
stuffcmd (ent, "cl_sidespeed 150\n");

then, in ClientThink, perform the following check;

if ((ucmd->forwardmove > ent->client->resp.max_speed) || (ucmd->sidemove > ent->client->resp.max_speed) )
{
// cap the ucmd at the max speed
ucmd->forwardmove = ent->client->resp.max_speed;
ucmd->sidemove = ent->client->resp.max_speed;
// re-stuff the client variables
stuffcmd (ent, "cl_forwardspeed 150\n");
stuffcmd (ent, "cl_sidespeed 150\n");
}

you can modify this code to handle multiple speeds. just define the classes' speed somewhere and use multiple stuffcmds, a separate function to stuff speeds based on class, or whatever. basically, you need to re-stuff the speed and cap the ucmd-> if the player's speed is faster than the maximum allowed.

Okay, now how to make a player move faster? tougher on a number of levels. You can use multiple Pmove calls, but it makes the player fall faster too, and screws up the footsteps (without altering the bob cycles, that is)...

 


Follow Ups

TUTORIAL: Player Speeds (with bugfixes!)


 

Posted by Doom Trooper (151.198.110.*) on May 04, 1999 at 15:24:08:

 

okay... turns out there *was* a slightly massive bug in the speed tutorial
I posted. The max_speed test failed for backward or reverse-strafe movement if the client attempted to change his clientside speed variables because
these commands transmitted a negative value to the server. So... without further ado,
here is the revised player speed code. This should work flawlessly.

first, some background. the client-side variables cl_forwardspeed and cl_sidespeed
control how fast a player moves. These values (normally 200) are transmitted to the
server, which places them into the ucmd->forwardmove and ucmd->sidemove variables in ClientThink (p_client.c) . If the player is running, double these values (max 400, capped by the .exe) are sent. *However*, if the player is strafing to the right (?) or moving backward, a NEGATIVE value is sent; -200 for walking or -400 for running.

What this code does is sets a new maximum value for the player to move at. Then, it sends this value to the client as their new cl_ speed values. It then checks the value sent by the player and if this value is not equal to the cl_ speed variables, it caps the value at the maximum speed and re-forces the values to the client.

this tutorial assumes you want to use this slowdown code as part of a class-based mod,
so the majority of the code is ripped straight from my own mod, Future Wars (plug! plug!).
You can modify it for your own purposes, especially if you're not implementing classes.

'k? 'k. let's do this.

first, add a variable to your client's resp structure.

int max_speed;

then, in PutClientInServer, set up your speed caps;

StuffSpeedByClass( ent );
ent->client->resp.class_speed = AssignClassSpeed( ent );

"Hold up," you may say... "What's that StuffSpeedByClass function?
And that AssignClassSpeed one?"
Well, funny you should ask. Here it is. Stick it where you want it. I've
got it in a file called 'futurecmds'. You'll need the StuffCmd function, so if it's not included in your source, I've provided that too.

void stuffcmd(edict_t *ent, char *s)
{
gi.WriteByte (11);
gi.WriteString (s);
gi.unicast (ent, true);
}

int AssignClassSpeed(edict_t *ent)
{
switch( ent->client->resp.character_class )
{
case SCOUT: return 400;
break;
case FIELD_TECH: return 400;
break;
case MEDIC: return 400;
break;
case BERZERKER: return 250;
break;
case INFILTRATOR: return 400;
break;
case COMMANDO: return 400;
break;
case SOLDIER: return 300;
break;
case SNIPER: return 400;
break;
case TERMINATOR: return 300;
break;
case DOOM_TROOPER: return 200;
break;
case SHOCK_TROOPER: return 250;
break;
case M_66: return 400;
break;
case CYBORG: return 400;
break;
case HUNTER_KILLER: return 300;
break;
case VALKYRIE: return 400;
break;
}
}

void StuffSpeedByClass( edict_t *ent )
{
switch ( ent->client->resp.character_class )
{
case SCOUT: stuffcmd( ent, "cl_forwardspeed 200\n" );
stuffcmd( ent, "cl_sidespeed 200\n" );
break;
case FIELD_TECH: stuffcmd( ent, "cl_forwardspeed 200\n" );
stuffcmd( ent, "cl_sidespeed 200\n" );
break;
case MEDIC: stuffcmd( ent, "cl_forwardspeed 200\n" );
stuffcmd( ent, "cl_sidespeed 200\n" );
break;
case BERZERKER: stuffcmd( ent, "cl_forwardspeed 125\n" );
stuffcmd( ent, "cl_sidespeed 125\n" );
break;
case INFILTRATOR: stuffcmd( ent, "cl_forwardspeed 200\n" );
stuffcmd( ent, "cl_sidespeed 200\n" );
break;
case COMMANDO: stuffcmd( ent, "cl_forwardspeed 200\n" );
stuffcmd( ent, "cl_sidespeed 200\n" );
break;
case SOLDIER: stuffcmd( ent, "cl_forwardspeed 150\n" );
stuffcmd( ent, "cl_sidespeed 150\n" );
break;
case SNIPER: stuffcmd( ent, "cl_forwardspeed 200\n" );
stuffcmd( ent, "cl_sidespeed 200\n" );
break;
case TERMINATOR: stuffcmd( ent, "cl_forwardspeed 150\n" );
stuffcmd( ent, "cl_sidespeed 150\n" );
break;
case DOOM_TROOPER: stuffcmd( ent, "cl_forwardspeed 100\n" );
stuffcmd( ent, "cl_sidespeed 100\n" );
break;
case SHOCK_TROOPER: stuffcmd( ent, "cl_forwardspeed 125\n" );
stuffcmd( ent, "cl_sidespeed 125\n" );
break;
case M_66: stuffcmd( ent, "cl_forwardspeed 200\n" );
stuffcmd( ent, "cl_sidespeed 200\n" );
break;
case CYBORG: stuffcmd( ent, "cl_forwardspeed 200\n" );
stuffcmd( ent, "cl_sidespeed 200\n" );
break;
case HUNTER_KILLER: stuffcmd( ent, "cl_forwardspeed 150\n" );
stuffcmd( ent, "cl_sidespeed 150\n" );
break;
case VALKYRIE: stuffcmd( ent, "cl_forwardspeed 200\n" );
stuffcmd( ent, "cl_sidespeed 200\n" );
break;
}
}

note that you're stuffing *half* the value of max_speed in cl_forward and side.

'k, that's the easy part. now, slap this code in ClientThink, just after the block of code that sets the player's ps.move state (these lines;)

// set up for pmove
memset (&pm, 0, sizeof(pm));

// speed cap code

if ( (ucmd->forwardmove > ent->client->resp.class_speed) || (ucmd->sidemove > ent->client->resp.class_speed) )
{
ucmd->forwardmove = ent->client->resp.class_speed;
ucmd->sidemove = ent->client->resp.class_speed;
StuffSpeedByClass( ent );
}

// negative speed (left/back movement) check
if (ucmd->forwardmove < 0)
{
backspeed = ucmd->forwardmove + ent->client->resp.class_speed;

if ( backspeed != 0 )
{
ucmd->forwardmove = 0 - ent->client->resp.class_speed;
StuffSpeedByClass( ent );
}
}

if (ucmd->sidemove < 0)
{
sidespeed = ucmd->sidemove + ent->client->resp.class_speed;

if ( sidespeed != 0 )
{
ucmd->sidemove = 0 - ent->client->resp.class_speed;
StuffSpeedByClass( ent );
}
}

at the top of ClientThink, you have to define sidespeed and backspeed as ints, by the way.

okay! that should do it. Get out there and start slowin' your players down.

 


Follow Ups

Re: backward speed fixed


 

Posted by Doom Trooper (151.198.110.*) on May 04, 1999 at 15:45:34:

In Reply to: TUTORIAL: Player Speeds (with bugfixes!) posted by Doom Trooper on May 04, 1999 at 15:24:08:

 

'k, here's a fix for the walking/running backward test bug.

replace the section marked with;

// negative speed (left/back movement) check

with;

if (ucmd->forwardmove < 0)
{
backspeed = ucmd->forwardmove + ent->client->resp.class_speed;

if ( backspeed != 0 )
{
if ( backspeed != walkspeed )
{
ucmd->forwardmove = 0 - ent->client->resp.class_speed;
StuffSpeedByClass( ent );
}
}
}

if (ucmd->sidemove < 0)
{
sidespeed = ucmd->sidemove + ent->client->resp.class_speed;

if ( sidespeed != 0 )
{
if (sidespeed != walkspeed)
{
ucmd->sidemove = 0 - ent->client->resp.class_speed;
StuffSpeedByClass( ent );
}
}
}

define walkspeed as a local int at the beginning of ClientThink.

*now* it works fine for both running and walking, forward or backward. Good luck!


Follow Ups

Re: whoops... assign walkspeed to this value;


 

Posted by Doom Trooper (151.198.110.*) on May 04, 1999 at 15:47:41:

In Reply to: TUTORIAL: Player Speeds (with bugfixes!) posted by Doom Trooper on May 04, 1999 at 15:24:08:


assign walkspeed this value prior to the test code;

walkspeed = ent->client->resp.class_speed / 2;