Created By: | Achilles |
eMail: | achilles@inside3d.com |
Difficulty Scale: | Intermediate |
Alright, what were going to do in this Tutorial is add a Team Radio feature. What this means is the players can now use pre-recorded .WAV files to send audio queues to eachother. Ranging from taunts, stupidity, to team based sounds.I will make a couple of asumptions though.
You are familiar with your Compiler and its interface You can find aspects of the code based on Function and not simply line numbers. You are using the v3.20 source codebase from id Software The first things we want to accomplish is add a functionality to turn the radio on and off. This is accomplished by adding a new value to the Player's info that survives a player respawn, i.e. we don't want it to reset everytime the player gets killed.
In g_local.h find the client_respawn_t structure, it should look somthing like this:
Underneath int score; you need to add our value for the Radio toggle.typedef struct { client_persistant_t coop_respawn; // what to set client->pers to on a respawn int enterframe; // level.framenum the client entered the game int score; // frags, etc vec3_t cmd_angles; // angles sent over in the last command qboolean spectator; // client is a spectator } client_respawn_t;Now we the data we need built into the player that allows the radio to be turned on and off. In the code it can now be accessed via the ent->client->resp.radio_power value. Since its a toggle it can be true or false.// Inside3D Team Radio Tutorial - begin int radio_power; // Its a toggle that functions as the power // Inside3D Team Radio Tutorial - endWere all done with g_local.h
We now need to set the value of ent->client->resp.radio_power when the client connects.
Open up p_client.c
Your looking for the the ClientConnect Subroutine, the header for the routine looks like this:
What we need to do is find where exactly in this routine the value can be set. It just so happen I did this for you./* =========== ClientConnect Called when a player begins connecting to the server. The game can refuse entrance to a client by returning false. If the client is allowed, the connection process will continue and eventually get to ClientBegin() Changing levels will NOT cause this to be called again, but loadgames will. ============ */ qboolean ClientConnect (edict_t *ent, char *userinfo) { char *value; // check to see if they are on the banned IP list value = Info_ValueForKey (userinfo, "ip"); if (SV_FilterPacket(value)) { Info_SetValueForKey(userinfo, "rejmsg", "Banned."); return false; } . . .Directly underneath the following If statement contained in the ClientConnect function.
Simply add the following. . . if (ent->inuse == false) { InitClientResp (ent->client); if (!game.autosaved || !ent->client->pers.weapon) InitClientPersistant (ent->client); }For a quick check of your code, go ahead and compile it now, no actual differences were made that can be used yet, but you should get a nice clean compile, if not re-check your steps. Now go grab a Coke, Pepsi or Mountain Dew. Nice work!// Inside3D Team Radio Tutorial - begin ent->client->resp.radio_power = 1; // Inside3D Team Radio Tutorial - endNow is when the tutorial gets a bit tricky, we need to add two new files to the Project. This is done slightly different in every compiler out there. Simply add both files and make sure they contained in the make file.
The files we want to add is called p_radio.c and p_radio.h
p_radio.h containes the following
That file contains our prototypes for all our radio functions so we can call them up easily.// These are functions for the team radio #define for_each_player(PLAYER,INDEX) \ for(INDEX=1;INDEX<=maxclients->value;INDEX++) \ if ((PLAYER=&g_edicts[i]) && PLAYER->inuse) void RadioToggle_f(edict_t *self); void Radio_f(edict_t *self, char *channel, char *msg);Now the big file, p_radio.c contains the follow:
Radio_Toggle_F is simply a toggle function, when it is called it changes the value of ent->client->resp.radio_power#include "g_local.h" #include "p_radio.h" void RadioToggle_f(edict_t *self) { if (self->client->resp.radio_power) { self->client->resp.radio_power = 0; gi.cprintf(self, PRINT_HIGH, "Radio OFF\n"); } else { self->client->resp.radio_power = 1; gi.cprintf(self, PRINT_HIGH, "Radio ON\n"); } } // this functions is used to send commands to the clients. void stuffcmd(edict_t *ent, char *s) { gi.WriteByte (11); gi.WriteString (s); gi.unicast (ent, true); } void Radio_f(edict_t *self, char *channel, char *msg) { edict_t *player; int i; char *cmd, *pos; char *prefix; char *info; cmd = "\0"; // if the players radio is off, lets not bother doing anything if (!self->client->resp.radio_power) return; // Well this is where we check the model to see what sound to send out. // Based this check from the IsFemale() Subroutine info = Info_ValueForKey (self->client->pers.userinfo, "skin"); if (info[0] == 'f' || info[0] == 'F') prefix = "fem_"; else if (info[0] == 'c' || info[0] == 'C') { if (info[1] == 'y' || info[1] == 'Y') prefix = "cyb_"; if (info[1] == 'r' || info[1] == 'R') prefix = "crk_"; } // If its an unidentifiable model, use the male sounds by default else prefix = "male_"; // Okay gotta make sure people can't stuff extra commands down to kill someone. // This is also why i send everything to the guy that sent it originally also if (pos = strstr(msg,";")) pos[0] = 0; // If the sound is bound for all players in the game this is what is executed // .WAVs are stored in sound/world/*.wav if (Q_stricmp (channel, "ALL") == 0) { for_each_player(player, i) { if (player->client->resp.radio_power) { sprintf(cmd, "play world/%s%s\n", prefix, msg); stuffcmd(player, cmd); } } return; } // If it's only based for players nearby AKA the player is yelling to people in game this is ran. // .WAVs are stored in sound/voice/*.wav else if (Q_stricmp (channel, "ROOM") == 0) { sprintf(cmd, "voice/%s%s.wav", prefix, msg); gi.sound (self, CHAN_VOICE, gi.soundindex(cmd), 1, ATTN_NORM, 0); return; } }Radio_F is commented heavily and should be fairly self-explanatory.
Now again lets see if the code will compile cleanly. All the functions are there and ready to go, just nothing is being called. This is a good place to check to see if the files were added to the project correctly and so forth. We'll get into the actual usage in a moment, right now, lets see if what we have so far is good and clean.
I am assuming since your going on everything compiled and the new files are being included in the compile.
Basically what happens is when a sound is played, the Radio_F routine check to find all players that should receive the sound. All players that are receiving the sound are sent a command that is executed on the local console. Example
play male_buzzoff.wavIt simply plays the .WAV to the speakers. Because I am using the players console, there is a check to prevent other things from being sent like Kill and quit commands.Now the last part of our tutorial, adding the command so these routines actually get used.
This is done in g_cmds.c
At the bottom of the file in the ClientCommand Subroutine you need to add some code after these lines
you need to add the following lineselse if (Q_stricmp(cmd, "playerlist") == 0) Cmd_PlayerList_f(ent);this adds a couple extra command for the players to bind.// Inside3D Team Radio Tutorial - begin else if (Q_stricmp (cmd, "radio") == 0) // Radio Toggle RadioToggle_f(ent); else if (Q_stricmp (cmd, "play_world") == 0 && !ent->client->resp.spectator) // Radio to everybody Radio_f(ent, "ALL", gi.argv(1)); else if (Q_stricmp (cmd, "play_voice") == 0 && !ent->client->resp.spectator) // Talk to Everyone within a Earshot Radio_f(ent, "ROOM", gi.argv(1)); // Inside3D Team Radio Tutorial - endRADIO all by itself will toggle the radio on and off. Off simply means he will not hear any of the sounds sent by other players.
play_world command will play a command to every player in the game with the radio on. Command in reality is part of the name in the WAV file. IE play_world buzzoff wil actually play a file called male_buzzoff.wav etc...
play_voice is identical to play_word, except it only players to players in close proximity to the sender.
The !ent->client->resp.spectator is a check so Spectators to a game cannot send audio commands to the players in game.