]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/clientcommands.qc
Merge remote branch 'origin/master' into samual/updatecommands
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / clientcommands.qc
index de37d377d38eb95a38810ec1483a5c8c4279c76a..fde3fd943e9a442cd1cb27fb38ed486a2d82725b 100644 (file)
@@ -1,26 +1,18 @@
-// =======================================================
-//  Server side client commands code, reworked by Samual
-//  Last updated: August 4th, 2011
-// =======================================================
+// =========================================================
+//  Server side networked commands code, reworked by Samual
+//  Last updated: November 26th, 2011
+// =========================================================
 
-#define CC_REQUEST_HELP 1
-#define CC_REQUEST_COMMAND 2
-#define CC_REQUEST_USAGE 3
-
-entity nagger;
+#define CC_REQUEST_COMMAND 1
+#define CC_REQUEST_USAGE 2
 
 .float cmd_floodtime;
 .float cmd_floodcount;
 .float checkfail;
 .float lms_spectate_warning;
 
-float readyrestart_happened;
-float readycount;
-
 string MapVote_Suggest(string m);
 
-void ReadyCount();
-
 
 // ============================
 //  Misc. Supporting Functions
@@ -45,216 +37,6 @@ float SV_ParseClientCommand_floodcheck()
        return TRUE; // continue, as we're not flooding yet
 }
 
-float Nagger_SendEntity(entity to, float sendflags)
-{
-       float nags, i, f, b;
-       entity e;
-       WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
-
-       // bits:
-       //   1 = ready
-       //   2 = player needs to ready up
-       //   4 = vote
-       //   8 = player needs to vote
-       //  16 = warmup
-       // sendflags:
-       //  64 = vote counts
-       // 128 = vote string
-
-       nags = 0;
-       if(readycount)
-       {
-               nags |= 1;
-               if(to.ready == 0)
-                       nags |= 2;
-       }
-       if(votecalled)
-       {
-               nags |= 4;
-               if(to.vote_vote == 0)
-                       nags |= 8;
-       }
-       if(inWarmupStage)
-               nags |= 16;
-
-       if(sendflags & 64)
-               nags |= 64;
-
-       if(sendflags & 128)
-               nags |= 128;
-
-       if(!(nags & 4)) // no vote called? send no string
-               nags &~= (64 | 128);
-
-       WriteByte(MSG_ENTITY, nags);
-
-       if(nags & 64)
-       {
-               WriteByte(MSG_ENTITY, vote_yescount);
-               WriteByte(MSG_ENTITY, vote_nocount);
-               WriteByte(MSG_ENTITY, vote_needed_absolute);
-               WriteChar(MSG_ENTITY, to.vote_vote);
-       }
-
-       if(nags & 128)
-               WriteString(MSG_ENTITY, votecalledvote_display);
-
-       if(nags & 1)
-       {
-               for(i = 1; i <= maxclients; i += 8)
-               {
-                       for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
-                               if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
-                                       f |= b;
-                       WriteByte(MSG_ENTITY, f);
-               }
-       }
-
-       return TRUE;
-}
-void Nagger_Init()
-{
-       Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
-}
-void Nagger_VoteChanged()
-{
-       if(nagger)
-               nagger.SendFlags |= 128;
-}
-void Nagger_VoteCountChanged()
-{
-       if(nagger)
-               nagger.SendFlags |= 64;
-}
-void Nagger_ReadyCounted()
-{
-       if(nagger)
-               nagger.SendFlags |= 1;
-}
-
-void ReadyRestartForce()
-{
-       local entity e;
-
-       bprint("^1Server is restarting...\n");
-
-       VoteReset();
-
-       // clear overtime
-       if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) {
-               //we have to decrease timelimit to its original value again!!
-               float newTL;
-               newTL = autocvar_timelimit;
-               newTL -= checkrules_overtimesadded * autocvar_timelimit_overtime;
-               cvar_set("timelimit", ftos(newTL));
-       }
-
-       checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
-
-
-       readyrestart_happened = 1;
-       game_starttime = time;
-       if(!g_ca && !g_arena)
-               game_starttime += RESTART_COUNTDOWN;
-       restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
-
-       inWarmupStage = 0; //once the game is restarted the game is in match stage
-
-       //reset the .ready status of all players (also spectators)
-       FOR_EACH_CLIENTSLOT(e)
-               e.ready = 0;
-       readycount = 0;
-       Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
-
-       if(autocvar_teamplay_lockonrestart && teamplay) {
-               lockteams = 1;
-               bprint("^1The teams are now locked.\n");
-       }
-
-       //initiate the restart-countdown-announcer entity
-       if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena)
-       {
-               restartTimer = spawn();
-               restartTimer.think = restartTimer_Think;
-               restartTimer.nextthink = game_starttime;
-       }
-
-       //after a restart every players number of allowed timeouts gets reset, too
-       if(autocvar_sv_timeout)
-       {
-               FOR_EACH_REALPLAYER(e)
-                       e.allowedTimeouts = autocvar_sv_timeout_number;
-       }
-
-       //reset map immediately if this cvar is not set
-       if (!autocvar_sv_ready_restart_after_countdown)
-               reset_map(TRUE);
-
-       if(autocvar_sv_eventlog)
-               GameLogEcho(":restart");
-}
-
-void ReadyRestart()
-{
-       // no arena, assault support yet...
-       if(g_arena | g_assault | gameover | intermission_running | race_completing)
-               localcmd("restart\n");
-       else
-               localcmd("\nsv_hook_gamerestart\n");
-
-       ReadyRestartForce();
-
-       // reset ALL scores, but only do that at the beginning
-       //of the countdown if sv_ready_restart_after_countdown is off!
-       //Otherwise scores could be manipulated during the countdown!
-       if (!autocvar_sv_ready_restart_after_countdown)
-               Score_ClearAll();
-}
-
-/**
- * Counts how many players are ready. If not enough players are ready, the function
- * does nothing. If all players are ready, the timelimit will be extended and the
- * restart_countdown variable is set to allow other functions like PlayerPostThink
- * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
- * is not set the map will be resetted.
- *
- * Function is called after the server receives a 'ready' sign from a player.
- */
-void ReadyCount()
-{
-       local entity e;
-       local float r, p;
-
-       r = p = 0;
-
-       FOR_EACH_REALPLAYER(e)
-       {
-               p += 1;
-               if(e.ready)
-                       r += 1;
-       }
-
-       readycount = r;
-
-       Nagger_ReadyCounted();
-
-       if(r) // at least one is ready
-       if(r == p) // and, everyone is ready
-               ReadyRestart();
-}
-
-/**
- * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
- * is set)
- */
-void restartTimer_Think() {
-       restart_mapalreadyrestarted = 1;
-       reset_map(TRUE);
-       Score_ClearAll();
-       remove(self);
-       return;
-}
-
 
 // =======================
 //  Command Sub-Functions
@@ -264,20 +46,20 @@ void ClientCommand_autoswitch(float request, float argc)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2autoswitch^7: Whether or not to switch automatically when getting a better weapon\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        self.autoswitch = ("0" != argv(1));
                        sprint(self, strcat("^1autoswitch is currently turned ", (self.autoswitch ? "on" : "off"), ".\n"));
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd autoswitch selection\n");
                        sprint(self, "  Where 'selection' is 1 or 0 for on or off.\n"); 
                        return;
+               }
        }
 }
 
@@ -285,20 +67,20 @@ void ClientCommand_checkfail(float request, string command) // used only by clie
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2checkfail^7: Report if a client-side check failed\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        print(sprintf("CHECKFAIL: %s (%s) epically failed check %s\n", self.netname, self.netaddress, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1))));
                        self.checkfail = 1;
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd checkfail message\n");
                        sprint(self, "  Where 'message' is the message reported by client about the fail.\n");
                        return;
+               }
        }
 }
 
@@ -306,11 +88,8 @@ void ClientCommand_clientversion(float request, float argc) // used only by clie
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2clientversion^7: Release version of the game\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        if(self.flags & FL_CLIENT)
                        {
                                self.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
@@ -331,12 +110,15 @@ void ClientCommand_clientversion(float request, float argc) // used only by clie
                                }
                        }
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd clientversion version\n");
                        sprint(self, "  Where 'version' is the game version reported by self.\n");
                        return;
+               }
        }
 }
 
@@ -344,20 +126,20 @@ void ClientCommand_cvar_changes(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2cvar_changes^7: Prints a list of all changed server cvars\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        sprint(self, cvar_changes);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 sv_cmd cvar_changes\n");
                        sprint(self, "  No arguments required.\n");
                        //sprint(self, "See also: ^2cvar_purechanges^7\n");
                        return;
+               }
        }
 }
 
@@ -365,46 +147,47 @@ void ClientCommand_cvar_purechanges(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2cvar_purechanges^7: Prints a list of all changed gameplay cvars\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        sprint(self, cvar_purechanges);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 sv_cmd cvar_purechanges\n");
                        sprint(self, "  No arguments required.\n");
                        //sprint(self, "See also: ^2cvar_changes^7\n");
                        return;
+               }
        }
 }
 
 void ClientCommand_info(float request, float argc)
-{
-       string command;
-       
+{      
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2info^7: Request for unique server information set up by admin\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
-                       command = cvar_string_builtin(strcat("sv_info_", argv(1))); 
+               {
+                       string command;
+                       
+                       command = builtin_cvar_string(strcat("sv_info_", argv(1))); 
                        if(command)
                                wordwrap_sprint(command, 1111); // why 1111?
                        else
                                sprint(self, "ERROR: unsupported info command\n");
+                               
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd info request\n");
                        sprint(self, "  Where 'request' is the suffixed string appended onto the request for cvar.\n");
                        return;
+               }
        }
 }
 
@@ -412,11 +195,8 @@ void ClientCommand_join(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2join^7: Become a player in the game\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        if(self.flags & FL_CLIENT)
                        {
                                if(self.classname != "player" && !lockteams && !g_arena)
@@ -439,12 +219,15 @@ void ClientCommand_join(float request)
                                }
                        }
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd join\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
@@ -452,19 +235,19 @@ void ClientCommand_ladder(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2ladder^7: Get information about top players if supported\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        sprint(self, ladder_reply);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd ladder\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
@@ -472,19 +255,19 @@ void ClientCommand_lsmaps(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2lsmaps^7: List maps which can be used with the current game mode\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        sprint(self, lsmaps_reply);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd lsmaps\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
@@ -492,19 +275,19 @@ void ClientCommand_lsnewmaps(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2lsnewmaps^7: List maps which TODO\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        sprint(self, lsnewmaps_reply);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd lsnewmaps\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
@@ -512,19 +295,19 @@ void ClientCommand_maplist(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2maplist^7: Full server maplist reply\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        sprint(self, maplist_reply);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd maplist\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
@@ -532,19 +315,19 @@ void ClientCommand_rankings(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2rankings^7: Print information about rankings\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        sprint(self, rankings_reply);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd rankings\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
@@ -552,11 +335,8 @@ void ClientCommand_ready(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2ready^7: Qualify as ready to end warmup stage (or restart server if allowed)\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        if(self.flags & FL_CLIENT)
                        {
                                if(inWarmupStage || autocvar_sv_ready_restart || g_race_qualifying == 2)
@@ -583,50 +363,51 @@ void ClientCommand_ready(float request)
                                }
                        }
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd ready\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
 void ClientCommand_records(float request) // TODO: Isn't this flooding with the sprint messages? Old code, but perhaps bad?
-{
-       float i;
-       
+{      
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2records^7: List top 10 records for the current map\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
+                       float i;
+                       
                        for(i = 0; i < 10; ++i)
                                sprint(self, records_reply[i]);
+                               
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd records\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
 void ClientCommand_reportcvar(float request, float argc, string command) // TODO: confirm this works
-{
-       float tokens;
-       string s;
-       
+{      
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2reportcvar^7: Old system for sending a client cvar to the server\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
+                       float tokens;
+                       string s;
+                       
                        if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
                        {
                                s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
@@ -634,12 +415,15 @@ void ClientCommand_reportcvar(float request, float argc, string command) // TODO
                        }
                        GetCvars(1);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd reportcvar <cvar>\n");
                        sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
                        return;
+               }
        }
 }
 
@@ -647,20 +431,19 @@ void ClientCommand_say(float request, float argc, string command)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2say^7: Print a message to chat to all players\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
-                       if(argc >= 2)
-                               Say(self, FALSE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
+               {
+                       if(argc >= 2) { Say(self, FALSE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd say <message>\n");
                        sprint(self, "  Where 'message' is the string of text to say.\n");
                        return;
+               }
        }
 }
 
@@ -668,34 +451,30 @@ void ClientCommand_say_team(float request, float argc, string command)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2say_team^7: Print a message to chat to all team mates\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
-                       if(argc >= 2)
-                               Say(self, TRUE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
+               {
+                       if(argc >= 2) { Say(self, TRUE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd say_team <message>\n");
                        sprint(self, "  Where 'message' is the string of text to say.\n");
                        return;
+               }
        }
 }
 
 void ClientCommand_selectteam(float request, float argc) // TODO: Update the messages for this command
 {
-       float selection;
-       
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2selectteam^7: Attempt to choose a team to join into\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
+                       float selection;
+                       
                        if (self.flags & FL_CLIENT)
                        {
                                if(teamplay)
@@ -729,29 +508,29 @@ void ClientCommand_selectteam(float request, float argc) // TODO: Update the mes
                                        sprint(self, "^7selectteam can only be used in teamgames\n");
                        }
                        return; // never fall through to usage
+               }
 
                default:
                case CC_REQUEST_USAGE:
+               {
                        //sprint(self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
                        sprint(self, "\nUsage:^3 cmd selectteam team\n");
                        sprint(self, "  Where 'team' is the prefered team to try and join.\n");
                        sprint(self, "  Full list of options here: \"red, blue, yellow, pink, auto\"\n");
                        return;
+               }
        }
 }
 
 void ClientCommand_sentcvar(float request, float argc, string command)
 {
-       float tokens;
-       string s;
-       
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2sentcvar^7: New system for sending a client cvar to the server\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
+                       float tokens;
+                       string s;
+                       
                        if(argc == 2) // undefined cvar: use the default value on the server then
                        {
                                s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
@@ -759,12 +538,15 @@ void ClientCommand_sentcvar(float request, float argc, string command)
                        }
                        GetCvars(1);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd sentcvar <cvar>\n");
                        sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
                        return;
+               }
        }
 }
 
@@ -772,11 +554,8 @@ void ClientCommand_spectate(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2spectate^7: Become an observer\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        if(self.flags & FL_CLIENT)
                        {
                                if(g_arena) { return; } 
@@ -807,12 +586,15 @@ void ClientCommand_spectate(float request)
                                }
                        }
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd spectate\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
@@ -820,19 +602,19 @@ void ClientCommand_suggestmap(float request, float argc)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2suggestmap^7: Suggest a map to the mapvote at match end\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd suggestmap map\n");
                        sprint(self, "  Where 'map' is the name of the map to suggest.\n");
                        return;
+               }
        }
 }
 
@@ -840,34 +622,30 @@ void ClientCommand_teamstatus(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2teamstatus^7: Print detailed score information for all players\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        Score_NicePrint(self);
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd teamstatus\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
 void ClientCommand_tell(float request, float argc, string command)
 {
-       entity e;
-       
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2tell^7: Send a message directly to a player\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
-                       e = GetCommandPlayerSlotTargetFromTokenizedCommand(argc, 1);
+               {
+                       entity e = GetCommandPlayerSlotTargetFromTokenizedCommand(argc, 1);
+                       
                        if(e && argc > ParseCommandPlayerSlotTarget_firsttoken)
                        {
                                Say(self, FALSE, e, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), TRUE);
@@ -878,12 +656,15 @@ void ClientCommand_tell(float request, float argc, string command)
                                        trigger_magicear_processmessage_forallears(self, -1, world, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)));
                        }
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd tell playerid <message>\n");
                        sprint(self, "  Where 'playerid' is the entity number of the player to send the 'message' to.\n");
                        return;
+               }
        }
 }
 
@@ -891,11 +672,8 @@ void ClientCommand_timein(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2timein^7: Resume the game from being paused with a timeout\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        if(self.flags & FL_CLIENT)
                        {
                                if(autocvar_sv_timeout)
@@ -926,12 +704,15 @@ void ClientCommand_timein(float request)
                                }
                        }
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd timein\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
@@ -939,11 +720,8 @@ void ClientCommand_timeout(float request) // DEAR GOD THIS COMMAND IS TERRIBLE.
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2timeout^7: Call a timeout which pauses the game for certain amount of time unless unpaused\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        if(self.flags & FL_CLIENT)
                        {
                                if(autocvar_sv_timeout) 
@@ -1002,12 +780,15 @@ void ClientCommand_timeout(float request) // DEAR GOD THIS COMMAND IS TERRIBLE.
                                }
                        }
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd timeout\n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 
@@ -1015,48 +796,116 @@ void ClientCommand_voice(float request, float argc, string command)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2voice^7: Send voice message via sound\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        if(argc >= 3)
                                VoiceMessage(argv(1), substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
                        else
                                VoiceMessage(argv(1), "");
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd voice\n");
                        sprint(self, "  FIXME ARGUMENTS UNKNOWN.\n");
                        return;
+               }
        }
 }
 
-/*
+/* use this when creating a new command, making sure to place it in alphabetical order.
 void ClientCommand_(float request)
 {
        switch(request)
        {
-               case CC_REQUEST_HELP:
-                       sprint(self, "  ^2blah^7: foobar\n");
-                       return;
-                       
                case CC_REQUEST_COMMAND:
+               {
                        
                        return; // never fall through to usage
+               }
                        
                default:
                case CC_REQUEST_USAGE:
+               {
                        sprint(self, "\nUsage:^3 cmd \n");
                        sprint(self, "  No arguments required.\n");
                        return;
+               }
        }
 }
 */
 
 
+// =====================================
+//  Macro system for networked commands
+// =====================================
+
+// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
+#define CLIENT_COMMANDS(request,arguments,command) \
+       CLIENT_COMMAND("autoswitch", ClientCommand_autoswitch(request, arguments), "Whether or not to switch automatically when getting a better weapon") \
+       CLIENT_COMMAND("checkfail", ClientCommand_checkfail(request, command), "Report if a client-side check failed") \
+       CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \
+       CLIENT_COMMAND("cvar_changes", ClientCommand_cvar_changes(request), "Prints a list of all changed server cvars") \
+       CLIENT_COMMAND("cvar_purechanges", ClientCommand_cvar_purechanges(request), "Prints a list of all changed gameplay cvars") \
+       CLIENT_COMMAND("info", ClientCommand_info(request, arguments), "Request for unique server information set up by admin") \
+       CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \
+       CLIENT_COMMAND("ladder", ClientCommand_ladder(request), "Get information about top players if supported") \
+       CLIENT_COMMAND("lsmaps", ClientCommand_lsmaps(request), "List maps which can be used with the current game mode") \
+       CLIENT_COMMAND("lsnewmaps", ClientCommand_lsnewmaps(request), "List maps which TODO") \
+       CLIENT_COMMAND("maplist", ClientCommand_maplist(request), "Full server maplist reply") \
+       CLIENT_COMMAND("rankings", ClientCommand_rankings(request), "Print information about rankings") \
+       CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
+       CLIENT_COMMAND("records", ClientCommand_records(request), "List top 10 records for the current map") \
+       CLIENT_COMMAND("reportcvar", ClientCommand_reportcvar(request, arguments, command), "Old system for sending a client cvar to the server") \
+       CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
+       CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \
+       CLIENT_COMMAND("selectteam", ClientCommand_selectteam(request, arguments), "Attempt to choose a team to join into") \
+       CLIENT_COMMAND("sentcvar", ClientCommand_sentcvar(request, arguments, command), "New system for sending a client cvar to the server") \
+       CLIENT_COMMAND("spectate", ClientCommand_spectate(request), "Become an observer") \
+       CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(request, arguments), "Suggest a map to the mapvote at match end") \
+       CLIENT_COMMAND("teamstatus", ClientCommand_teamstatus(request), "Print detailed score information for all players") \
+       CLIENT_COMMAND("tell", ClientCommand_tell(request, arguments, command), "Send a message directly to a player") \
+       CLIENT_COMMAND("timein", ClientCommand_timein(request), "Resume the game from being paused with a timeout") \
+       CLIENT_COMMAND("timeout", ClientCommand_timeout(request), "Call a timeout which pauses the game for certain amount of time unless unpaused") \
+       CLIENT_COMMAND("voice", ClientCommand_voice(request, arguments, command), "Send voice message via sound") \
+       /* nothing */
+       
+void ClientCommand_macro_help()
+{
+       #define CLIENT_COMMAND(name,function,description) \
+               { print("  ^2", name, "^7: ", description, "\n"); }
+               
+       CLIENT_COMMANDS(0, 0, "")
+       #undef CLIENT_COMMAND
+       
+       return;
+}
+
+float ClientCommand_macro_command(float argc, string command)
+{
+       #define CLIENT_COMMAND(name,function,description) \
+               { if(name == strtolower(argv(0))) { function; return TRUE; } }
+               
+       CLIENT_COMMANDS(CC_REQUEST_COMMAND, argc, command)
+       #undef CLIENT_COMMAND
+       
+       return FALSE;
+}
+
+float ClientCommand_macro_usage(float argc, string command)
+{
+       #define CLIENT_COMMAND(name,function,description) \
+               { if(name == strtolower(argv(1))) { function; return TRUE; } }
+               
+       CLIENT_COMMANDS(CC_REQUEST_USAGE, argc, command)
+       #undef CLIENT_COMMAND
+       
+       return FALSE;
+}
+
+
 // ======================================
 //  Main Function Called By Engine (cmd)
 // ======================================
@@ -1064,7 +913,6 @@ void ClientCommand_(float request)
 
 void SV_ParseClientCommand(string command)
 {
-       float search_request_type;
        float argc = tokenize_console(command);
        
        // for floodcheck
@@ -1091,39 +939,20 @@ void SV_ParseClientCommand(string command)
                if(argc == 1) 
                {
                        sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are:\n");
-                       ClientCommand_autoswitch(CC_REQUEST_HELP, 0);
-                       ClientCommand_checkfail(CC_REQUEST_HELP, "");
-                       ClientCommand_clientversion(CC_REQUEST_HELP, 0);
-                       ClientCommand_cvar_changes(CC_REQUEST_HELP);
-                       ClientCommand_cvar_purechanges(CC_REQUEST_HELP);
-                       ClientCommand_info(CC_REQUEST_HELP, 0);
-                       ClientCommand_join(CC_REQUEST_HELP); 
-                       ClientCommand_ladder(CC_REQUEST_HELP);
-                       ClientCommand_lsmaps(CC_REQUEST_HELP);
-                       ClientCommand_lsnewmaps(CC_REQUEST_HELP);
-                       ClientCommand_maplist(CC_REQUEST_HELP);
-                       ClientCommand_rankings(CC_REQUEST_HELP);
-                       ClientCommand_ready(CC_REQUEST_HELP);
-                       ClientCommand_records(CC_REQUEST_HELP);
-                       ClientCommand_reportcvar(CC_REQUEST_HELP, 0, "");
-                       ClientCommand_say(CC_REQUEST_HELP, 0, "");
-                       ClientCommand_say_team(CC_REQUEST_HELP, 0, "");
-                       ClientCommand_selectteam(CC_REQUEST_HELP, 0);
-                       ClientCommand_sentcvar(CC_REQUEST_HELP, 0, "");
-                       ClientCommand_spectate(CC_REQUEST_HELP);
-                       ClientCommand_suggestmap(CC_REQUEST_HELP, 0);
-                       ClientCommand_teamstatus(CC_REQUEST_HELP);
-                       ClientCommand_tell(CC_REQUEST_HELP, 0, "");
-                       ClientCommand_timein(CC_REQUEST_HELP);
-                       ClientCommand_timeout(CC_REQUEST_HELP);
-                       ClientCommand_voice(CC_REQUEST_HELP, 0, "");
+                       ClientCommand_macro_help;
                        sprint(self, "For help about specific commands, type cmd help COMMAND\n");
                        return;
                } 
-               else
-                       search_request_type = CC_REQUEST_USAGE; // Instead of trying to call a command, we're going to see detailed information about it
+               else if(ClientCommand_macro_usage(argc, command)) // Instead of trying to call a command, we're going to see detailed information about it
+               {
+                       return;
+               }
        } 
-       else*/ if(GameCommand_Vote(command, self)) 
+       else*/ if(MUTATOR_CALLHOOK(SV_ParseClientCommand))
+       {
+               return; // handled by a mutator
+       }
+       else if(GameCommand_Vote(command, self)) 
        {
                return; // handled by server/vote.qc 
        }
@@ -1135,42 +964,10 @@ void SV_ParseClientCommand(string command)
        {
                return; // handled by server/cheats.qc
        }
-       else
-               search_request_type = CC_REQUEST_COMMAND; // continue as usual and scan for normal commands
-       
-       switch(strtolower((search_request_type == CC_REQUEST_USAGE) ? argv(1) : argv(0)))
+       else if(ClientCommand_macro_command(argc, command)) // continue as usual and scan for normal commands
        {
-               // Do not hard code aliases for these, instead create them in defaultXonotic.cfg
-               // also: keep in alphabetical order, please ;)
-               
-               case "autoswitch": ClientCommand_autoswitch(search_request_type, argc); break;
-               case "checkfail": ClientCommand_checkfail(search_request_type, command); break;
-               case "clientversion": ClientCommand_clientversion(search_request_type, argc); break;
-               case "cvar_changes": ClientCommand_cvar_changes(search_request_type); break;
-               case "cvar_purechanges": ClientCommand_cvar_purechanges(search_request_type); break;
-               case "info": ClientCommand_info(search_request_type, argc); break;
-               case "join": ClientCommand_join(search_request_type); break;
-               case "ladder": ClientCommand_ladder(search_request_type); break;
-               case "lsmaps": ClientCommand_lsmaps(search_request_type); break;
-               case "lsnewmaps": ClientCommand_lsnewmaps(search_request_type); break;
-               case "maplist": ClientCommand_maplist(search_request_type); break;
-               case "rankings": ClientCommand_rankings(search_request_type); break;
-               case "ready": ClientCommand_ready(search_request_type); break;
-               case "records": ClientCommand_records(search_request_type); break;
-               case "reportcvar": ClientCommand_reportcvar(search_request_type, argc, command); break;
-               case "say": ClientCommand_say(search_request_type, argc, command); break;
-               case "say_team": ClientCommand_say_team(search_request_type, argc, command); break;
-               case "selectteam": ClientCommand_selectteam(search_request_type, argc); break;
-               case "sentcvar": ClientCommand_sentcvar(search_request_type, argc, command); break;
-               case "spectate": ClientCommand_spectate(search_request_type); break;
-               case "suggestmap": ClientCommand_suggestmap(search_request_type, argc); break;
-               case "teamstatus": ClientCommand_teamstatus(search_request_type); break;
-               case "tell": ClientCommand_tell(search_request_type, argc, command); break;
-               case "timein": ClientCommand_timein(search_request_type); break;
-               case "timeout": ClientCommand_timeout(search_request_type); break;
-               case "voice": ClientCommand_voice(search_request_type, argc, command); break;
-               
-               default:
-                       clientcommand(self, command); //print("Invalid command. For a list of supported commands, try cmd help.\n");
+               return; // handled by one of the above GameCommand_* functions
        }
+       else
+               clientcommand(self, command);
 }
\ No newline at end of file