[Français]

NEWS
INTRODUCTION
TERMINOLOGY
THE SOURCE CODE
THE BUGS
TUTORIALS
DOWNLOADS
THE AUTHOR

  THE MODS: ADDING A CONSOLE COMMAND
("KickPlayer" function written by Christoffer "Eldritch" Lundberg,
base concept provided by Astro Muppet)


We'll start this modding tutorials with something that players usually like to play with: the console commands.

1) What are AvP commands used for ?

AvP's inner console commands can allow what most of 3D games usually do:
  • Concerning gamers, you can modify the gameplay in general, by using cheats, or change directly your configuration (by binding a key to a command, for example, to make shortcuts). Normaly, you don't need to play with this unless you're a hardcore gamer (like in Quake III community).
  • Concerning programmers, you can alterate the default values of the variables in the game, making the gameplay different. The major use of this is to test your mods: you create commands to view your characters in wireframe mode, for example, to debug your work (actually an existing command), and that's the primary purpose of a console.
Both of these aspects will be covered in the tutorials, because I consider that they're both interresting, to a programming point of view.

So please, no "you're bad guy, 'cause people will cheat with these tuts". I know that cheats sucks, but it's a part of games.

2) How to make commands ?

Making commands is really simple in AvP. It can be done in 3 steps:
a) You create the interface of the command, (ie it's name, it's associated help text, the function to execute when calling this command and the indicator of a cheat command).
b) You create the prototype of the function to let the compiler finding used files
c) You create the function (what the command will do in AvP).
Now, to create commands, you need to find a suitable place in the sources.
  • You can find primary interresting commands in the file CConvars.cpp, but if you want to add yours in it, you should only put your debugging commands here, because it was made for the Debug and the Win32 Release modes of AvP. (It's recommended to compile the sources with the Release For Fox mode).
  • The file Davehook.cpp is reserved for the game's architecture (contains basic console commands).
  • In Gamevars.cpp, you can find 3 commands to support morphing of species while playing (reserved for debugging stuff, you shoudn't put yours here...)
  • Finally, the last and the most interresting file for modding is Gamecmds.cpp, which contains all major gameplay commands. This is the good place to edit, then the one we'll use.
3) One example: the "KICK" command

To illustrate this tutorial, I'll show you how to make a "KICK" command.
For those that don't know it, it's purpose's to disconnect a player while in a network game. This way, you can avoid cheaters. Let's begin...

a) The interface

In Gamecmds.cpp, add this to the list of commands (preferably at the end of the list):
ConsoleCommand::Make
(
"KICK", // This is the command to type in the console
"Kick a player.", // The help text displayed in the console
KickPlayer // The fonction associated with the command
);
b) The prototype

You just have to add the following near the other prototypes (always in Gamecmds.cpp):
extern void KickPlayer(int Index);
This will tell the compiler to find the function "KickPlayer" in another file.

c) The function

Now, you'll create the function in the file Pldnet.c (multiplayer support). For this, you must first create a prototype near the others:
void KickPlayer(int Index);
Then you can add the following at the bottom of the file:
HRESULT KickThisPlayer(LPDIRECTPLAY4 lpDP2A, DPID DP_PlayerId) // My little function ;)
{
// Let's create a game message (see note 1)
NETMESSAGEHEADER *headerPtr;
int headerSize = sizeof(NETMESSAGEHEADER);

headerPtr = (NETMESSAGEHEADER *) endSendBuffer;
endSendBuffer += headerSize;

// Initialise the message type (end a player's game)
headerPtr->type = (unsigned char) NetMT_EndGame;

// Now, we'll send the message to the target player (see note 2 for details)
return DpExtSend(lpDP2A, AVPDPNetID, DP_PlayerId, DPSEND_GUARANTEED, &sendBuffer, headerSize);
}

void KickPlayer(int Index) // The fonction linked with the KICK command
{
DPID DP_PlayerId;
HRESULT result;
char msg[100];

// Kick player from network game
if (AvP.Network == I_No_Network)
{
NewOnScreenMessage("Who are you trying to kick? The AI?");
return;
}
if ((Index <= 0) || (Index >= NET_MAXPLAYERS))
{
NewOnScreenMessage("Invalid player number");
return;
}

// Get the player's id (see note 3)
DP_PlayerId = netGameData.playerData[Index].playerId;

if (!DP_PlayerId)
{
NewOnScreenMessage("Invalid player number");
return;
}
if (glpDP == NULL)
{
NewOnScreenMessage("ERROR: glpDP is non-existant!");
return;
}

// Then the player goes BOOM !
if(AvP.Network==I_Host)
{
result = KickThisPlayer(glpDP,DP_PlayerId);

if(result == DP_OK)
{
// Inform other players that you've kicked someone...
sprintf(msg,"%s (#%d) has been kicked!", netGameData.playerData[Index].name, Index);
NewOnScreenMessage(msg);

sprintf(msg,"%s has been kicked!", netGameData.playerData[Index].name);
AddNetMsg_ChatBroadcast(msg, FALSE);
}
}
else
NewOnScreenMessage("You're a client, dude !");
}
Okay, so what does all this means ?

First, we've got the function "KickThisPlayer" that actually manages the technical part of the kick process, that is by using DirectX (see notes for that). This function is the true core of the whole console command.

Then, we've got the second function: this is the one we declared in step a). The process is really simple: you bring down the console in the game, then you enter "kick x", where x is the player's id (see notes).

What you've seen here is the use of 2 functions, but you can use only one or more to achieve your ideas.

4) A little bonus

Kicking a player with his id is good, but knowing player's id is better !
That's why there's this small bonus: you'll display all player's id while playing in network as the host (by pressing the TAB button). I won't publish the whole listing, so pay close attention to the following:
  • Locate the function "DoMultiplayerEndGameScreen" in Pldnet.c
  • Add the code in blue:
    for(i=0;i<NET_MAXPLAYERS;i++)
    {
    if(netGameData.playerData[i].playerId)
    {
    RenderStringVertically(netGameData.playerData[i].name,x,y,0xffffffff);
    x+=20;
    }
    }

    if(AvP.Network==I_Host)
    RenderStringVertically("ID",x,y,0xffffffff);
    to display the title.
  • Finally, do the same with this:
    x=120;
    for(j=0;j<NET_MAXPLAYERS;j++)
    {
    if(netGameData.playerData[j].playerId)
    {
    sprintf(text,"%d",netGameData.playerData[i].playerFrags[j]);
    if(i==j)
    RenderStringCentred(text,x,y,0xffff0000);
    else
    RenderStringCentred(text,x,y,0xff00ff00);
    x+=20;
    }
    }
    if(AvP.Network==I_Host)
    {
    sprintf(text, "%d", i);
    RenderStringCentred(text,x,y,0xffffffff);
    }
    to display the id's.
At this point you're ready to make your own console commands.
Now see the notes if you're more curious...

Note 1:
  • when you use DirectX to manage networks, you use the DirectPlay interface. What you must understand is that DirecPlay uses messages to make the pc communicating.
  • There are 2 types of messages: system messages & game messages.
  • System messages are handled by DirecPlay, but the game can use these (ex.: the message "DPSYS_HOST" in AvP means that you become the host). Game messages are specific to the game (ex.: in AvP, the message "NetMT_StartGame" to start a game).
Note 2:
  • to simulate a kick message, that wasn't coded in AvP, I just had to send a message to the client with this function.
  • lpDP2A is the DirectPlay object of AvP.
  • AVPDPNetID is the id of the source of the message (in this case, the host).
  • DP_PlayerId is the id of the destination (in this case, the client to kick).
  • DPSEND_GUARANTEED means that were're sure that the client will receive the message (not sure by default).
  • &sendBuffer is the address of the buffer containing the message to send to the client.
  • headerSize is the size of this buffer.
Note 3:
  • in DirectPlay, all players are identified by a unique id that is a number.
  • In AvP, your id is always AVPDPNetID and for the other players, it depends in which order they connected to your session.