Skip to content

Script: Different movement

Victor Luchits edited this page Aug 28, 2019 · 4 revisions

This small tutorial is mirrored from here


This is the first of a series of step by step tutorials I intend to make on how to achieve results with gametype plugins. They'll be specially dedicated to things we didn't use in the default gametypes, which makes them harder to know. This tutorial will show how the scripts are able to assign different movement abilities to players, and for it we will use the existing CA gametype, making each class move differently. It is very easy.

But please, even when my guide will point you to the ca file, do never modify the ca file itself. Copy and rename it to whatever you want before modifying it. The original gametypes must always stay unmodified.

So let's start:

The movement features of each player are always reset to defaults at respawning by the internal code. So, for assigning different abilities from the gametype plugins, they have to always be set right after respawning.

So what we do is open basewsw/progs/gametypes/ca.as and find the function GT_playerRespawn.

Inside that function you will find first a check from changing team. After that a check for being ghosting (being ghosting happens when spectating or dead). We will add our code after the ghosting check. Here:

void GT_playerRespawn( Entity @ent, int old_team, int new_team )
{
    if ( old_team != new_team )
    {
        // show the class selection menu
        if ( old_team == TEAM_SPECTATOR && !gametype.isInstagib() )
        {
            if ( @ent.client.getBot() != null )
                GENERIC_SetClientClassIndex( ent.client, brandom( 0, 3 ) );
            else
                ent.client.addGameCommand( "mecu \"Select Class\" Grunt \"class grunt\" Camper \"class camper\" Spammer \"class spammer\"" );
        }
    }

    if ( ent.isGhosting() )
        return;

   // assign player movement abilities here

   // function code continues...

The first thing we will want, is to know what class is our player. The generic scripts contain a playerclass manager that will solve our problem easy. It contains this function: int GENERIC_GetClientClass( Client @client )

All we have to do is call it and it will return us the value which corresponds to the player class. This values are defined at the top of the file basewsw/progs/gametypes/generic/playerclasses.as, but for making it simple, I copy paste them here:

const int PLAYERCLASS_GRUNT = 0;
const int PLAYERCLASS_CAMPER = 1;
const int PLAYERCLASS_SPAMMER = 2;

So what we do is ask that function for our player class. Just like this:

void GT_playerRespawn( Entity @ent, int old_team, int new_team )
{
    if ( old_team != new_team )
    {
        // show the class selection menu
        if ( old_team == TEAM_SPECTATOR && !gametype.isInstagib() )
        {
            if ( @ent.client.getBot() != null )
                GENERIC_SetClientClassIndex( ent.client, brandom( 0, 3 ) );
            else
                ent.client.addGameCommand( "mecu \"Select Class\" Grunt \"class grunt\" Camper \"class camper\" Spammer \"class spammer\"" );
        }
    }

    if ( ent.isGhosting() )
        return;

   // assign player movement abilities here
    int myPlayerClass = GENERIC_GetClientClass( ent.client );

    if( myPlayerClass == PLAYERCLASS_SPAMMER )
    {
        G_Print( "I am a Spammer" );
    }
    else if( myPlayerClass == PLAYERCLASS_CAMPER )
    {
        G_Print( "I am a Camper" );
    }
    else if( myPlayerClass == PLAYERCLASS_GRUNT )
    {
        G_Print( "I am a Grunt" );
    }

   // function code continues...

So we now know what class is this player. Now let's make them move differently. There are 2 main options we can choose, changing its movement speeds, or disabling his movement features. The movement features are things like walljump, crouch, dash, +forward bunnyhopping, or even +strafe bunnyhopping. I'll put the list later. The movement speeds that can be set are, max movement speed (the famous 320 speed limit), the dashing speed, and the jumping speed, which is vertical so it basically equals the jump height.

Let say that grunt will be a standard mover, but it won't be able to walljump. So let's apply the change. Walljumping is a movement feature change, so we use the Client method Client::setPMoveFeatures( int featureMask ).

We have to keep in mind that the difference between cg_oldmovement 1 and 0 is a pmove feature, so, if we don't want to break the user settings, we have to respect the original bitmask (unless we want to make a gametype that enforces new or old movement to everyone).

So removing the walljump ability from grunt would look like this:

    ent.client.setPMoveFeatures( ent.client.pmoveFeatures & ~int(PMFEAT_WALLJUMP) );

and adding it back like this:

    ent.client.setPMoveFeatures( ent.client.pmoveFeatures | int(PMFEAT_WALLJUMP) );

Notice, some bit operations using predefined enum values in Angelscript require casting the enum to int before the operation is done.

Let's add it to the respawn function then:

void GT_playerRespawn( Entity @ent, int old_team, int new_team )
{
    if ( old_team != new_team )
    {
        // show the class selection menu
        if ( old_team == TEAM_SPECTATOR && !gametype.isInstagib() )
        {
            if ( @ent.client.getBot() != null )
                GENERIC_SetClientClassIndex( ent.client, brandom( 0, 3 ) );
            else
                ent.client.addGameCommand( "mecu \"Select Class\" Grunt \"class grunt\" Camper \"class camper\" Spammer \"class spammer\"" );
        }
    }

    if ( ent.isGhosting() )
        return;

    int myPlayerClass = GENERIC_GetClientClass( ent.client );

    if ( myPlayerClass == PLAYERCLASS_SPAMMER )
    {
        G_Print( "I am a Spammer" );
    }
    else if ( myPlayerClass == PLAYERCLASS_CAMPER )
    {
        G_Print( "I am a Camper" );
    }
    else if ( myPlayerClass == PLAYERCLASS_GRUNT )
    {
        // grunt can't walljump
        ent.client.setPMoveFeatures( ent.client.pmoveFeatures & ~int(PMFEAT_WALLJUMP) );
    }

   // function code continues...

This is the list of pmove features that can be toggled:

PMFEAT_CROUCH
PMFEAT_WALK
PMFEAT_JUMP
PMFEAT_DASH
PMFEAT_WALLJUMP
PMFEAT_FWDBUNNY
PMFEAT_AIRCONTROL
PMFEAT_ZOOM
PMFEAT_GHOSTMOVE

Yes, zoom is a pmove feature. And what ghostmove does is allowing players to walk through other players. The rest are self explanatory.

Now, we will make the other two classes move at different speeds instead of enabling or disabling movements. These are done with the Client methods: Client::setPMoveMaxSpeed( int speed ), Client::setPMoveJumpSpeed( int speed ) and Client::setPMoveDashSpeed( int speed ).

Let's make the camper move very slow but jump very high, and the spammer move very fast. It's as simple as this:

void GT_playerRespawn( Entity @ent, int old_team, int new_team )
{
    if ( old_team != new_team )
    {
        // show the class selection menu
        if ( old_team == TEAM_SPECTATOR && !gametype.isInstagib() )
        {
            if ( @ent.client.getBot() != null )
                GENERIC_SetClientClassIndex( ent.client, brandom( 0, 3 ) );
            else
                ent.client.addGameCommand( "mecu \"Select Class\" Grunt \"class grunt\" Camper \"class camper\" Spammer \"class spammer\"" );
        }
    }

    if ( ent.isGhosting() )
        return;

    int myPlayerClass = GENERIC_GetClientClass( ent.client );

    if ( myPlayerClass == PLAYERCLASS_SPAMMER )
    {
        // spammer is fast
        ent.client.setPMoveDashSpeed( 600 );
        ent.client.setPMoveMaxSpeed( 500 );
    }
    else if ( myPlayerClass == PLAYERCLASS_CAMPER )
    {
        // camper jumps high, and is slow
        ent.client.setPMoveDashSpeed( 250 );
        ent.client.setPMoveMaxSpeed( 250 );
        ent.client.setPMoveJumpSpeed( 450 );
    }
    else if ( myPlayerClass == PLAYERCLASS_GRUNT )
    {
        // grunt can't walljump
        ent.client.setPMoveFeatures( ent.client.pmoveFeatures & ~int(PMFEAT_WALLJUMP) );
    }

   // function code continues...

That's it. You can save your changes and go testing your brand new gametype already!

Clone this wiki locally