]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - host_cmd.c
improved shadowmap side culling
[xonotic/darkplaces.git] / host_cmd.c
index ca619b7fe294862f8b3e2f014f296e0800570d40..5c1f0969d90e16558938df19c185739895a1e628 100644 (file)
@@ -19,12 +19,31 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
 
 #include "quakedef.h"
+#include "sv_demo.h"
+#include "image.h"
+
+// for secure rcon authentication
+#include "hmac.h"
+#include "mdfour.h"
+#include <time.h>
 
 int current_skill;
-cvar_t sv_cheats = {0, "sv_cheats", "0"};
+cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
+cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
+cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
+cvar_t sv_status_show_qcstatus = {CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."};
+cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
+cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
+cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
+cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
+cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
+cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
+cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
+cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
 qboolean allowcheats = false;
 
-mfunction_t *ED_FindFunction (char *name);
+extern qboolean host_shuttingdown;
+extern cvar_t developer_entityparsing;
 
 /*
 ==================
@@ -32,17 +51,14 @@ Host_Quit_f
 ==================
 */
 
-extern qboolean host_shuttingdown;
 void Host_Quit_f (void)
 {
-       host_shuttingdown = true;
-       CL_Disconnect ();
-       Host_ShutdownServer(false);
-
-       Sys_Quit ();
+       if(host_shuttingdown)
+               Con_Printf("shutting down already!\n");
+       else
+               Sys_Quit (0);
 }
 
-
 /*
 ==================
 Host_Status_f
@@ -50,14 +66,17 @@ Host_Status_f
 */
 void Host_Status_f (void)
 {
-       const char *protocolname;
+       char qcstatus[256];
        client_t *client;
-       int seconds, minutes, hours = 0, j, players;
+       int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
        void (*print) (const char *fmt, ...);
+       char ip[22];
+       int frags;
 
        if (cmd_source == src_command)
        {
-               if (!sv.active)
+               // if running a client, try to send over network so the client's status report parser will see the report
+               if (cls.state == ca_connected)
                {
                        Cmd_ForwardToServer ();
                        return;
@@ -67,42 +86,107 @@ void Host_Status_f (void)
        else
                print = SV_ClientPrintf;
 
-       for (players = 0, j = 0;j < svs.maxclients;j++)
-               if (svs.clients[j].active)
+       if (!sv.active)
+               return;
+       
+       if(cmd_source == src_command)
+               SV_VM_Begin();
+       
+       in = 0;
+       if (Cmd_Argc() == 2)
+       {
+               if (strcmp(Cmd_Argv(1), "1") == 0)
+                       in = 1;
+               else if (strcmp(Cmd_Argv(1), "2") == 0)
+                       in = 2;
+       }
+
+       for (players = 0, i = 0;i < svs.maxclients;i++)
+               if (svs.clients[i].active)
                        players++;
-       print ("host:    %s\n", Cvar_VariableString ("hostname"));
-       print ("version: %s build %s\n", gamename, buildstring);
-       switch(sv.protocol)
-       {
-               case PROTOCOL_QUAKE: protocolname = sv.netquakecompatible ? "QUAKE" : "QUAKEDP";break;
-               case PROTOCOL_DARKPLACES1: protocolname = "PROTOCOL_DARKPLACES1";break;
-               case PROTOCOL_DARKPLACES2: protocolname = "PROTOCOL_DARKPLACES2";break;
-               case PROTOCOL_DARKPLACES3: protocolname = "PROTOCOL_DARKPLACES3";break;
-               case PROTOCOL_DARKPLACES4: protocolname = "PROTOCOL_DARKPLACES4";break;
-               case PROTOCOL_DARKPLACES5: protocolname = "PROTOCOL_DARKPLACES5";break;
-               default: protocolname = "PROTOCOL_UNKNOWN";break;
-       }
-       print ("protocol: %i (%s)\n", sv.protocol, protocolname);
-       print ("map:     %s\n", sv.name);
-       print ("players: %i active (%i max)\n\n", players, svs.maxclients);
-       for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++)
+       print ("host:     %s\n", Cvar_VariableString ("hostname"));
+       print ("version:  %s build %s\n", gamename, buildstring);
+       print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
+       print ("map:      %s\n", sv.name);
+       print ("timing:   %s\n", Host_TimingReport());
+       print ("players:  %i active (%i max)\n\n", players, svs.maxclients);
+
+       if (in == 1)
+               print ("^2IP                   %%pl ping  time   frags  no   name\n");
+       else if (in == 2)
+               print ("^5IP                    no   name\n");
+
+       for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
        {
                if (!client->active)
                        continue;
-               seconds = (int)(realtime - client->netconnection->connecttime);
-               minutes = seconds / 60;
-               if (minutes)
+
+               ++k;
+
+               if (in == 0 || in == 1)
                {
-                       seconds -= (minutes * 60);
-                       hours = minutes / 60;
-                       if (hours)
-                               minutes -= (hours * 60);
+                       seconds = (int)(realtime - client->connecttime);
+                       minutes = seconds / 60;
+                       if (minutes)
+                       {
+                               seconds -= (minutes * 60);
+                               hours = minutes / 60;
+                               if (hours)
+                                       minutes -= (hours * 60);
+                       }
+                       else
+                               hours = 0;
+                       
+                       packetloss = 0;
+                       if (client->netconnection)
+                               for (j = 0;j < NETGRAPH_PACKETS;j++)
+                                       if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
+                                               packetloss++;
+                       packetloss = packetloss * 100 / NETGRAPH_PACKETS;
+                       ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
                }
+
+               if(sv_status_privacy.integer && cmd_source != src_command)
+                       strlcpy(ip, client->netconnection ? "hidden" : "botclient" , 22);
                else
-                       hours = 0;
-               print ("#%-2u %-16.16s  %3i  %2i:%02i:%02i\n", j+1, client->name, (int)client->edict->v->frags, hours, minutes, seconds);
-               print ("   %s\n", client->netconnection->address);
+                       strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 22);
+
+               frags = client->frags;
+
+               if(sv_status_show_qcstatus.integer && prog->fieldoffsets.clientstatus >= 0)
+               {
+                       const char *str = PRVM_E_STRING(PRVM_EDICT_NUM(i + 1), prog->fieldoffsets.clientstatus);
+                       if(str && *str)
+                       {
+                               char *p;
+                               const char *q;
+                               p = qcstatus;
+                               for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
+                                       if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
+                                               *p++ = *q;
+                               *p = 0;
+                               if(*qcstatus)
+                                       frags = atoi(qcstatus);
+                       }
+               }
+               
+               if (in == 0) // default layout
+               {
+                       print ("#%-3u %-16.16s  %3i  %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
+                       print ("  %s\n", ip);
+               }
+               else if (in == 1) // extended layout
+               {
+                       print ("%s%-21s %2i %4i %2i:%02i:%02i %4i  #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name);
+               }
+               else if (in == 2) // reduced layout
+               {
+                       print ("%s%-21s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
+               }
        }
+
+       if(cmd_source == src_command)
+               SV_VM_End();
 }
 
 
@@ -115,23 +199,14 @@ Sets client to godmode
 */
 void Host_God_f (void)
 {
-       if (cmd_source == src_command)
-       {
-               Cmd_ForwardToServer ();
-               return;
-       }
-
-       if (!sv_player)
-               return;
-
        if (!allowcheats)
        {
                SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
                return;
        }
 
-       sv_player->v->flags = (int)sv_player->v->flags ^ FL_GODMODE;
-       if (!((int)sv_player->v->flags & FL_GODMODE) )
+       host_client->edict->fields.server->flags = (int)host_client->edict->fields.server->flags ^ FL_GODMODE;
+       if (!((int)host_client->edict->fields.server->flags & FL_GODMODE) )
                SV_ClientPrint("godmode OFF\n");
        else
                SV_ClientPrint("godmode ON\n");
@@ -139,23 +214,14 @@ void Host_God_f (void)
 
 void Host_Notarget_f (void)
 {
-       if (cmd_source == src_command)
-       {
-               Cmd_ForwardToServer ();
-               return;
-       }
-
-       if (!sv_player)
-               return;
-
        if (!allowcheats)
        {
                SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
                return;
        }
 
-       sv_player->v->flags = (int)sv_player->v->flags ^ FL_NOTARGET;
-       if (!((int)sv_player->v->flags & FL_NOTARGET) )
+       host_client->edict->fields.server->flags = (int)host_client->edict->fields.server->flags ^ FL_NOTARGET;
+       if (!((int)host_client->edict->fields.server->flags & FL_NOTARGET) )
                SV_ClientPrint("notarget OFF\n");
        else
                SV_ClientPrint("notarget ON\n");
@@ -165,31 +231,22 @@ qboolean noclip_anglehack;
 
 void Host_Noclip_f (void)
 {
-       if (cmd_source == src_command)
-       {
-               Cmd_ForwardToServer ();
-               return;
-       }
-
-       if (!sv_player)
-               return;
-
        if (!allowcheats)
        {
                SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
                return;
        }
 
-       if (sv_player->v->movetype != MOVETYPE_NOCLIP)
+       if (host_client->edict->fields.server->movetype != MOVETYPE_NOCLIP)
        {
                noclip_anglehack = true;
-               sv_player->v->movetype = MOVETYPE_NOCLIP;
+               host_client->edict->fields.server->movetype = MOVETYPE_NOCLIP;
                SV_ClientPrint("noclip ON\n");
        }
        else
        {
                noclip_anglehack = false;
-               sv_player->v->movetype = MOVETYPE_WALK;
+               host_client->edict->fields.server->movetype = MOVETYPE_WALK;
                SV_ClientPrint("noclip OFF\n");
        }
 }
@@ -203,29 +260,20 @@ Sets client to flymode
 */
 void Host_Fly_f (void)
 {
-       if (cmd_source == src_command)
-       {
-               Cmd_ForwardToServer ();
-               return;
-       }
-
-       if (!sv_player)
-               return;
-
        if (!allowcheats)
        {
                SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
                return;
        }
 
-       if (sv_player->v->movetype != MOVETYPE_FLY)
+       if (host_client->edict->fields.server->movetype != MOVETYPE_FLY)
        {
-               sv_player->v->movetype = MOVETYPE_FLY;
+               host_client->edict->fields.server->movetype = MOVETYPE_FLY;
                SV_ClientPrint("flymode ON\n");
        }
        else
        {
-               sv_player->v->movetype = MOVETYPE_WALK;
+               host_client->edict->fields.server->movetype = MOVETYPE_WALK;
                SV_ClientPrint("flymode OFF\n");
        }
 }
@@ -237,29 +285,40 @@ Host_Ping_f
 
 ==================
 */
+void Host_Pings_f (void); // called by Host_Ping_f
 void Host_Ping_f (void)
 {
-       int             i, j;
-       float   total;
-       client_t        *client;
+       int i;
+       client_t *client;
+       void (*print) (const char *fmt, ...);
 
        if (cmd_source == src_command)
        {
-               Cmd_ForwardToServer ();
-               return;
+               // if running a client, try to send over network so the client's ping report parser will see the report
+               if (cls.state == ca_connected)
+               {
+                       Cmd_ForwardToServer ();
+                       return;
+               }
+               print = Con_Printf;
        }
+       else
+               print = SV_ClientPrintf;
+
+       if (!sv.active)
+               return;
 
-       SV_ClientPrint("Client ping times:\n");
+       print("Client ping times:\n");
        for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
        {
                if (!client->active)
                        continue;
-               total = 0;
-               for (j=0 ; j<NUM_PING_TIMES ; j++)
-                       total+=client->ping_times[j];
-               total /= NUM_PING_TIMES;
-               SV_ClientPrintf("%4i %s\n", (int)(total*1000), client->name);
+               print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
        }
+
+       // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report)
+       // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
+       //Host_Pings_f();
 }
 
 /*
@@ -289,22 +348,29 @@ void Host_Map_f (void)
                return;
        }
 
-       if (cmd_source != src_command)
-               return;
+       // GAME_DELUXEQUAKE - clear warpmark (used by QC)
+       if (gamemode == GAME_DELUXEQUAKE)
+               Cvar_Set("warpmark", "");
 
-       SCR_BeginLoadingPlaque ();
        cls.demonum = -1;               // stop demo loop in case this fails
 
        CL_Disconnect ();
-       Host_ShutdownServer(false);
+       Host_ShutdownServer();
+
+       if(svs.maxclients != svs.maxclients_next)
+       {
+               svs.maxclients = svs.maxclients_next;
+               if (svs.clients)
+                       Mem_Free(svs.clients);
+               svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
+       }
 
-       // remove console or menu
+       // remove menu
        key_dest = key_game;
-       key_consoleactive = 0;
 
        svs.serverflags = 0;                    // haven't completed an episode yet
        allowcheats = sv_cheats.integer != 0;
-       strcpy(level, Cmd_Argv(1));
+       strlcpy(level, Cmd_Argv(1), sizeof(level));
        SV_SpawnServer(level);
        if (sv.active && cls.state == ca_disconnected)
                CL_EstablishConnection("local:1");
@@ -320,28 +386,26 @@ Goes to a new map, taking all clients along
 void Host_Changelevel_f (void)
 {
        char level[MAX_QPATH];
-       
+
        if (Cmd_Argc() != 2)
        {
                Con_Print("changelevel <levelname> : continue game on a new level\n");
                return;
        }
-       if (!sv.active || cls.demoplayback)
-       {
-               Con_Print("Only the server may changelevel\n");
+       // HACKHACKHACK
+       if (!sv.active) {
+               Host_Map_f();
                return;
        }
-       if (cmd_source != src_command)
-               return;
 
-       // remove console or menu
+       // remove menu
        key_dest = key_game;
-       key_consoleactive = 0;
 
+       SV_VM_Begin();
        SV_SaveSpawnparms ();
-       SCR_BeginLoadingPlaque();
+       SV_VM_End();
        allowcheats = sv_cheats.integer != 0;
-       strcpy(level, Cmd_Argv(1));
+       strlcpy(level, Cmd_Argv(1), sizeof(level));
        SV_SpawnServer(level);
        if (sv.active && cls.state == ca_disconnected)
                CL_EstablishConnection("local:1");
@@ -357,27 +421,23 @@ Restarts the current server for a dead player
 void Host_Restart_f (void)
 {
        char mapname[MAX_QPATH];
-       
+
        if (Cmd_Argc() != 1)
        {
                Con_Print("restart : restart current level\n");
                return;
        }
-       if (!sv.active || cls.demoplayback)
+       if (!sv.active)
        {
                Con_Print("Only the server may restart\n");
                return;
        }
-       if (cmd_source != src_command)
-               return;
 
-       // remove console or menu
+       // remove menu
        key_dest = key_game;
-       key_consoleactive = 0;
 
-       SCR_BeginLoadingPlaque();
        allowcheats = sv_cheats.integer != 0;
-       strcpy(mapname, sv.name);
+       strlcpy(mapname, sv.name, sizeof(mapname));
        SV_SpawnServer(mapname);
        if (sv.active && cls.state == ca_disconnected)
                CL_EstablishConnection("local:1");
@@ -393,18 +453,50 @@ This is sent just before a server changes levels
 */
 void Host_Reconnect_f (void)
 {
-       if (Cmd_Argc() != 1)
-       {
-               Con_Print("reconnect : wait for signon messages again\n");
+       char temp[128];
+       // if not connected, reconnect to the most recent server
+       if (!cls.netcon)
+       {
+               // if we have connected to a server recently, the userinfo
+               // will still contain its IP address, so get the address...
+               InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
+               if (temp[0])
+                       CL_EstablishConnection(temp);
+               else
+                       Con_Printf("Reconnect to what server?  (you have not connected to a server yet)\n");
                return;
        }
-       if (!cls.signon)
+       // if connected, do something based on protocol
+       if (cls.protocol == PROTOCOL_QUAKEWORLD)
        {
-               Con_Print("reconnect: no signon, ignoring reconnect\n");
-               return;
+               // quakeworld can just re-login
+               if (cls.qw_downloadmemory)  // don't change when downloading
+                       return;
+
+               S_StopAllSounds();
+
+               if (cls.state == ca_connected && cls.signon < SIGNONS)
+               {
+                       Con_Printf("reconnecting...\n");
+                       MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
+                       MSG_WriteString(&cls.netcon->message, "new");
+               }
+       }
+       else
+       {
+               // netquake uses reconnect on level changes (silly)
+               if (Cmd_Argc() != 1)
+               {
+                       Con_Print("reconnect : wait for signon messages again\n");
+                       return;
+               }
+               if (!cls.signon)
+               {
+                       Con_Print("reconnect: no signon, ignoring reconnect\n");
+                       return;
+               }
+               cls.signon = 0;         // need new connection messages
        }
-       SCR_BeginLoadingPlaque();
-       cls.signon = 0;         // need new connection messages
 }
 
 /*
@@ -421,6 +513,9 @@ void Host_Connect_f (void)
                Con_Print("connect <serveraddress> : connect to a multiplayer game\n");
                return;
        }
+       // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
+       if(!rcon_secure.integer)
+               Cvar_SetQuick(&rcon_password, "");
        CL_EstablishConnection(Cmd_Argv(1));
 }
 
@@ -435,30 +530,101 @@ LOAD / SAVE GAME
 
 #define        SAVEGAME_VERSION        5
 
-/*
-===============
-Host_SavegameComment
-
-Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current
-===============
-*/
-void Host_SavegameComment (char *text)
+void Host_Savegame_to (const char *name)
 {
-       int             i;
-       char    kills[20];
+       qfile_t *f;
+       int             i, lightstyles = 64;
+       char    comment[SAVEGAME_COMMENT_LENGTH+1];
+       qboolean isserver;
 
+       // first we have to figure out if this can be saved in 64 lightstyles
+       // (for Quake compatibility)
+       for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
+               if (sv.lightstyles[i][0])
+                       lightstyles = i+1;
+
+       isserver = !strcmp(PRVM_NAME, "server");
+
+       Con_Printf("Saving game to %s...\n", name);
+       f = FS_OpenRealFile(name, "wb", false);
+       if (!f)
+       {
+               Con_Print("ERROR: couldn't open.\n");
+               return;
+       }
+
+       FS_Printf(f, "%i\n", SAVEGAME_VERSION);
+
+       memset(comment, 0, sizeof(comment));
+       if(isserver)
+               dpsnprintf(comment, sizeof(comment), "%-21.21s kills:%3i/%3i", PRVM_GetString(prog->edicts->fields.server->message), (int)prog->globals.server->killed_monsters, (int)prog->globals.server->total_monsters);
+       else
+               dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", PRVM_NAME);
+       // convert space to _ to make stdio happy
+       // LordHavoc: convert control characters to _ as well
        for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
-               text[i] = ' ';
-       memcpy (text, cl.levelname, strlen(cl.levelname));
-       sprintf (kills,"kills:%3i/%3i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]);
-       memcpy (text+22, kills, strlen(kills));
-// convert space to _ to make stdio happy
-       for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
-               if (text[i] == ' ')
-                       text[i] = '_';
-       text[SAVEGAME_COMMENT_LENGTH] = '\0';
-}
+               if (ISWHITESPACEORCONTROL(comment[i]))
+                       comment[i] = '_';
+       comment[SAVEGAME_COMMENT_LENGTH] = '\0';
+
+       FS_Printf(f, "%s\n", comment);
+       if(isserver)
+       {
+               for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+                       FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
+               FS_Printf(f, "%d\n", current_skill);
+               FS_Printf(f, "%s\n", sv.name);
+               FS_Printf(f, "%f\n",sv.time);
+       }
+       else
+       {
+               for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+                       FS_Printf(f, "(dummy)\n");
+               FS_Printf(f, "%d\n", 0);
+               FS_Printf(f, "%s\n", "(dummy)");
+               FS_Printf(f, "%f\n", realtime);
+       }
+
+       // write the light styles
+       for (i=0 ; i<lightstyles ; i++)
+       {
+               if (isserver && sv.lightstyles[i][0])
+                       FS_Printf(f, "%s\n", sv.lightstyles[i]);
+               else
+                       FS_Print(f,"m\n");
+       }
+
+       PRVM_ED_WriteGlobals (f);
+       for (i=0 ; i<prog->num_edicts ; i++)
+       {
+               FS_Printf(f,"// edict %d\n", i);
+               //Con_Printf("edict %d...\n", i);
+               PRVM_ED_Write (f, PRVM_EDICT_NUM(i));
+       }
+
+#if 1
+       FS_Printf(f,"/*\n");
+       FS_Printf(f,"// DarkPlaces extended savegame\n");
+       // darkplaces extension - extra lightstyles, support for color lightstyles
+       for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+               if (isserver && sv.lightstyles[i][0])
+                       FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
 
+       // darkplaces extension - model precaches
+       for (i=1 ; i<MAX_MODELS ; i++)
+               if (sv.model_precache[i][0])
+                       FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
+
+       // darkplaces extension - sound precaches
+       for (i=1 ; i<MAX_SOUNDS ; i++)
+               if (sv.sound_precache[i][0])
+                       FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
+       FS_Printf(f,"*/\n");
+#endif
+
+       FS_Close (f);
+       Con_Print("done.\n");
+}
 
 /*
 ===============
@@ -467,42 +633,31 @@ Host_Savegame_f
 */
 void Host_Savegame_f (void)
 {
-       char    name[256];
-       qfile_t *f;
-       int             i;
-       char    comment[SAVEGAME_COMMENT_LENGTH+1];
+       char    name[MAX_QPATH];
 
-       if (cmd_source != src_command)
-               return;
-
-       if (cls.state != ca_connected || !sv.active)
+       if (!sv.active)
        {
-               Con_Print("Not playing a local game.\n");
+               Con_Print("Can't save - no server running.\n");
                return;
        }
 
-       if (cl.intermission)
+       if (cl.islocalgame)
        {
-               Con_Print("Can't save in intermission.\n");
-               return;
-       }
+               // singleplayer checks
+               if (cl.intermission)
+               {
+                       Con_Print("Can't save in intermission.\n");
+                       return;
+               }
 
-       for (i = 0;i < svs.maxclients;i++)
-       {
-               if (svs.clients[i].active)
+               if (svs.clients[0].active && svs.clients[0].edict->fields.server->deadflag)
                {
-                       if (i > 0)
-                       {
-                               Con_Print("Can't save multiplayer games.\n");
-                               return;
-                       }
-                       if (svs.clients[i].edict->v->deadflag)
-                       {
-                               Con_Print("Can't savegame with a dead player\n");
-                               return;
-                       }
+                       Con_Print("Can't savegame with a dead player\n");
+                       return;
                }
        }
+       else
+               Con_Print("Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n");
 
        if (Cmd_Argc() != 2)
        {
@@ -519,47 +674,12 @@ void Host_Savegame_f (void)
        strlcpy (name, Cmd_Argv(1), sizeof (name));
        FS_DefaultExtension (name, ".sav", sizeof (name));
 
-       Con_Printf("Saving game to %s...\n", name);
-       f = FS_Open (name, "w", false);
-       if (!f)
-       {
-               Con_Print("ERROR: couldn't open.\n");
-               return;
-       }
-
-       FS_Printf(f, "%i\n", SAVEGAME_VERSION);
-       Host_SavegameComment (comment);
-       FS_Printf(f, "%s\n", comment);
-       for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
-               FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
-       FS_Printf(f, "%d\n", current_skill);
-       FS_Printf(f, "%s\n", sv.name);
-       FS_Printf(f, "%f\n",sv.time);
-
-// write the light styles
-
-       for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
-       {
-               if (sv.lightstyles[i])
-                       FS_Printf(f, "%s\n", sv.lightstyles[i]);
-               else
-                       FS_Print(f,"m\n");
-       }
-
-
-       ED_WriteGlobals (f);
-       for (i=0 ; i<sv.num_edicts ; i++)
-       {
-               ED_Write (f, EDICT_NUM(i));
-               FS_Flush (f);
-       }
-       FS_Close (f);
-       Con_Print("done.\n");
+       SV_VM_Begin();
+       Host_Savegame_to(name);
+       SV_VM_End();
 }
 
 
-extern mempool_t *edictstring_mempool;
-
 /*
 ===============
 Host_Loadgame_f
@@ -567,216 +687,527 @@ Host_Loadgame_f
 */
 void Host_Loadgame_f (void)
 {
-       qfile_t *f;
        char filename[MAX_QPATH];
        char mapname[MAX_QPATH];
-       float time, tfloat;
-       char buf[32768];
+       float time;
        const char *start;
-       char *str;
-       int i, r;
-       edict_t *ent;
+       const char *end;
+       const char *t;
+       char *text;
+       prvm_edict_t *ent;
+       int i;
        int entnum;
        int version;
        float spawn_parms[NUM_SPAWN_PARMS];
 
-       if (cmd_source != src_command)
-               return;
-
        if (Cmd_Argc() != 2)
        {
                Con_Print("load <savename> : load a game\n");
                return;
        }
 
-       strcpy (filename, Cmd_Argv(1));
+       strlcpy (filename, Cmd_Argv(1), sizeof(filename));
        FS_DefaultExtension (filename, ".sav", sizeof (filename));
 
        Con_Printf("Loading game from %s...\n", filename);
 
+       // stop playing demos
+       if (cls.demoplayback)
+               CL_Disconnect ();
+
+       // remove menu
+       key_dest = key_game;
+
        cls.demonum = -1;               // stop demo loop in case this fails
 
-       f = FS_Open (filename, "r", false);
-       if (!f)
+       t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
+       if (!text)
        {
                Con_Print("ERROR: couldn't open.\n");
                return;
        }
 
-       str = FS_Getline (f);
-       sscanf (str, "%i\n", &version);
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading version\n");
+
+       // version
+       COM_ParseToken_Simple(&t, false, false);
+       version = atoi(com_token);
        if (version != SAVEGAME_VERSION)
        {
-               FS_Close (f);
+               Mem_Free(text);
                Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
                return;
        }
 
-       SCR_BeginLoadingPlaque ();
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading description\n");
+
+       // description
+       COM_ParseToken_Simple(&t, false, false);
 
-       str = FS_Getline (f);
        for (i = 0;i < NUM_SPAWN_PARMS;i++)
        {
-               str = FS_Getline (f);
-               sscanf (str, "%f\n", &spawn_parms[i]);
+               COM_ParseToken_Simple(&t, false, false);
+               spawn_parms[i] = atof(com_token);
        }
+       // skill
+       COM_ParseToken_Simple(&t, false, false);
 // this silliness is so we can load 1.06 save files, which have float skill values
-       str = FS_Getline (f);
-       sscanf (str, "%f\n", &tfloat);
-       current_skill = (int)(tfloat + 0.1);
+       current_skill = (int)(atof(com_token) + 0.5);
        Cvar_SetValue ("skill", (float)current_skill);
 
-       strcpy (mapname, FS_Getline (f));
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading mapname\n");
+
+       // mapname
+       COM_ParseToken_Simple(&t, false, false);
+       strlcpy (mapname, com_token, sizeof(mapname));
 
-       str = FS_Getline (f);
-       sscanf (str, "%f\n",&time);
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading time\n");
+
+       // time
+       COM_ParseToken_Simple(&t, false, false);
+       time = atof(com_token);
 
        allowcheats = sv_cheats.integer != 0;
+
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: spawning server\n");
+
        SV_SpawnServer (mapname);
        if (!sv.active)
        {
+               Mem_Free(text);
                Con_Print("Couldn't load map\n");
                return;
        }
        sv.paused = true;               // pause until all clients connect
        sv.loadgame = true;
 
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: loading light styles\n");
+
 // load the light styles
 
+       SV_VM_Begin();
+       // -1 is the globals
+       entnum = -1;
+
        for (i = 0;i < MAX_LIGHTSTYLES;i++)
        {
-               str = FS_Getline (f);
-               sv.lightstyles[i] = Mem_Alloc(edictstring_mempool, strlen(str)+1);
-               strcpy (sv.lightstyles[i], str);
+               // light style
+               start = t;
+               COM_ParseToken_Simple(&t, false, false);
+               // if this is a 64 lightstyle savegame produced by Quake, stop now
+               // we have to check this because darkplaces may save more than 64
+               if (com_token[0] == '{')
+               {
+                       t = start;
+                       break;
+               }
+               strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
        }
 
-// load the edicts out of the savegame file
-       // -1 is the globals
-       entnum = -1;
-       while (!FS_Eof (f))
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: skipping until globals\n");
+
+       // now skip everything before the first opening brace
+       // (this is for forward compatibility, so that older versions (at
+       // least ones with this fix) can load savegames with extra data before the
+       // first brace, as might be produced by a later engine version)
+       for (;;)
        {
-               for (i = 0;i < (int)sizeof(buf) - 1;i++)
+               start = t;
+               if (!COM_ParseToken_Simple(&t, false, false))
+                       break;
+               if (com_token[0] == '{')
                {
-                       r = FS_Getc (f);
-                       if (r == EOF || !r)
-                               break;
-                       buf[i] = r;
-                       if (r == '}')
-                       {
-                               i++;
-                               break;
-                       }
+                       t = start;
+                       break;
                }
-               if (i == sizeof(buf)-1)
-                       Host_Error ("Loadgame buffer overflow");
-               buf[i] = 0;
-               start = buf;
-               if (!COM_ParseToken(&start, false))
+       }
+
+// load the edicts out of the savegame file
+       end = t;
+       for (;;)
+       {
+               start = t;
+               while (COM_ParseToken_Simple(&t, false, false))
+                       if (!strcmp(com_token, "}"))
+                               break;
+               if (!COM_ParseToken_Simple(&start, false, false))
                {
                        // end of file
                        break;
                }
                if (strcmp(com_token,"{"))
+               {
+                       Mem_Free(text);
                        Host_Error ("First token isn't a brace");
+               }
 
                if (entnum == -1)
                {
+                       if(developer_entityparsing.integer)
+                               Con_Printf("Host_Loadgame_f: loading globals\n");
+
                        // parse the global vars
-                       ED_ParseGlobals (start);
+                       PRVM_ED_ParseGlobals (start);
                }
                else
                {
                        // parse an edict
                        if (entnum >= MAX_EDICTS)
-                               Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)\n", MAX_EDICTS);
-                       while (entnum >= sv.max_edicts)
-                               SV_IncreaseEdicts();
-                       ent = EDICT_NUM(entnum);
-                       memset (ent->v, 0, progs->entityfields * 4);
-                       ent->e->free = false;
-                       ED_ParseEdict (start, ent);
+                       {
+                               Mem_Free(text);
+                               Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
+                       }
+                       while (entnum >= prog->max_edicts)
+                               PRVM_MEM_IncreaseEdicts();
+                       ent = PRVM_EDICT_NUM(entnum);
+                       memset (ent->fields.server, 0, prog->progs->entityfields * 4);
+                       ent->priv.server->free = false;
+
+                       if(developer_entityparsing.integer)
+                               Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
+
+                       PRVM_ED_ParseEdict (start, ent);
 
                        // link it into the bsp tree
-                       if (!ent->e->free)
-                               SV_LinkEdict (ent, false);
+                       if (!ent->priv.server->free)
+                               SV_LinkEdict(ent);
                }
 
+               end = t;
                entnum++;
        }
 
-       sv.num_edicts = entnum;
+       prog->num_edicts = entnum;
        sv.time = time;
 
-       FS_Close (f);
-
        for (i = 0;i < NUM_SPAWN_PARMS;i++)
                svs.clients[0].spawn_parms[i] = spawn_parms[i];
 
-       // make sure we're connected to loopback
-       if (cls.state == ca_disconnected || !(cls.state == ca_connected && cls.netcon != NULL && LHNETADDRESS_GetAddressType(&cls.netcon->peeraddress) == LHNETADDRESSTYPE_LOOP))
-               CL_EstablishConnection("local:1");
-}
-
-//============================================================================
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: skipping until extended data\n");
 
-/*
-======================
-Host_Name_f
-======================
+       // read extended data if present
+       // the extended data is stored inside a /* */ comment block, which the
+       // parser intentionally skips, so we have to check for it manually here
+       if(end)
+       {
+               while (*end == '\r' || *end == '\n')
+                       end++;
+               if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
+               {
+                       if(developer_entityparsing.integer)
+                               Con_Printf("Host_Loadgame_f: loading extended data\n");
+
+                       Con_Printf("Loading extended DarkPlaces savegame\n");
+                       t = end + 2;
+                       memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
+                       memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
+                       memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
+                       while (COM_ParseToken_Simple(&t, false, false))
+                       {
+                               if (!strcmp(com_token, "sv.lightstyles"))
+                               {
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       i = atoi(com_token);
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       if (i >= 0 && i < MAX_LIGHTSTYLES)
+                                               strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
+                                       else
+                                               Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
+                               }
+                               else if (!strcmp(com_token, "sv.model_precache"))
+                               {
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       i = atoi(com_token);
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       if (i >= 0 && i < MAX_MODELS)
+                                       {
+                                               strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
+                                               sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.modelname : NULL);
+                                       }
+                                       else
+                                               Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
+                               }
+                               else if (!strcmp(com_token, "sv.sound_precache"))
+                               {
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       i = atoi(com_token);
+                                       COM_ParseToken_Simple(&t, false, false);
+                                       if (i >= 0 && i < MAX_SOUNDS)
+                                               strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
+                                       else
+                                               Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
+                               }
+                               // skip any trailing text or unrecognized commands
+                               while (COM_ParseToken_Simple(&t, true, false) && strcmp(com_token, "\n"))
+                                       ;
+                       }
+               }
+       }
+       Mem_Free(text);
+
+       if(developer_entityparsing.integer)
+               Con_Printf("Host_Loadgame_f: finished\n");
+
+       SV_VM_End();
+
+       // make sure we're connected to loopback
+       if (sv.active && cls.state == ca_disconnected)
+               CL_EstablishConnection("local:1");
+}
+
+//============================================================================
+
+/*
+======================
+Host_Name_f
+======================
 */
-cvar_t cl_name = {CVAR_SAVE, "_cl_name", "player"};
+cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
 void Host_Name_f (void)
 {
        int i, j;
+       qboolean valid_colors;
+       const char *newNameSource;
        char newName[sizeof(host_client->name)];
 
        if (Cmd_Argc () == 1)
        {
-               Con_Printf("\"name\" is \"%s\"\n", cl_name.string);
+               Con_Printf("name: %s\n", cl_name.string);
                return;
        }
 
        if (Cmd_Argc () == 2)
-               strlcpy (newName, Cmd_Argv(1), sizeof (newName));
+               newNameSource = Cmd_Argv(1);
        else
-               strlcpy (newName, Cmd_Args(), sizeof (newName));
+               newNameSource = Cmd_Args();
 
-       for (i = 0, j = 0;newName[i];i++)
-               if (newName[i] != '\r' && newName[i] != '\n')
-                       newName[j++] = newName[i];
-       newName[j] = 0;
+       strlcpy(newName, newNameSource, sizeof(newName));
 
        if (cmd_source == src_command)
        {
                Cvar_Set ("_cl_name", newName);
-               if (cls.state == ca_connected)
-                       Cmd_ForwardToServer ();
+               if (strlen(newNameSource) >= sizeof(newName)) // overflowed
+               {
+                       Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
+                       Con_Printf("name: %s\n", cl_name.string);
+               }
                return;
        }
 
-       if (sv.time < host_client->nametime)
+       if (realtime < host_client->nametime)
        {
                SV_ClientPrintf("You can't change name more than once every 5 seconds!\n");
                return;
        }
-       
-       host_client->nametime = sv.time + 5;
 
-       if (strcmp(host_client->name, newName) && host_client->name[0] && strcmp(host_client->name, "unconnected"))
-               SV_BroadcastPrintf("%s changed name to %s\n", host_client->name, newName);
-       strcpy(host_client->name, newName);
-       strcpy(host_client->old_name, newName);
-       if (sv_player)
-               sv_player->v->netname = PR_SetString(host_client->name);
-       //Con_Printf("Host_Name_f: host_client->edict->netname = %s, sv_player->netname = %s, host_client->name = %s\n", PR_GetString(host_client->edict->v->netname), PR_GetString(sv_player->v->netname), host_client->name);
+       host_client->nametime = realtime + 5;
+
+       // point the string back at updateclient->name to keep it safe
+       strlcpy (host_client->name, newName, sizeof (host_client->name));
+
+       for (i = 0, j = 0;host_client->name[i];i++)
+               if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
+                       host_client->name[j++] = host_client->name[i];
+       host_client->name[j] = 0;
+
+       if(host_client->name[0] == 1 || host_client->name[0] == 2)
+       // may interfere with chat area, and will needlessly beep; so let's add a ^7
+       {
+               memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
+               host_client->name[sizeof(host_client->name) - 1] = 0;
+               host_client->name[0] = STRING_COLOR_TAG;
+               host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
+       }
+
+       COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
+       if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
+       {
+               size_t l;
+               l = strlen(host_client->name);
+               if(l < sizeof(host_client->name) - 1)
+               {
+                       // duplicate the color tag to escape it
+                       host_client->name[i] = STRING_COLOR_TAG;
+                       host_client->name[i+1] = 0;
+                       //Con_DPrintf("abuse detected, adding another trailing color tag\n");
+               }
+               else
+               {
+                       // remove the last character to fix the color code
+                       host_client->name[l-1] = 0;
+                       //Con_DPrintf("abuse detected, removing a trailing color tag\n");
+               }
+       }
+
+       // find the last color tag offset and decide if we need to add a reset tag
+       for (i = 0, j = -1;host_client->name[i];i++)
+       {
+               if (host_client->name[i] == STRING_COLOR_TAG)
+               {
+                       if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
+                       {
+                               j = i;
+                               // if this happens to be a reset  tag then we don't need one
+                               if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
+                                       j = -1;
+                               i++;
+                               continue;
+                       }
+                       if (host_client->name[i+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(host_client->name[i+2]) && isxdigit(host_client->name[i+3]) && isxdigit(host_client->name[i+4]))
+                       {
+                               j = i;
+                               i += 4;
+                               continue;
+                       }
+                       if (host_client->name[i+1] == STRING_COLOR_TAG)
+                       {
+                               i++;
+                               continue;
+                       }
+               }
+       }
+       // does not end in the default color string, so add it
+       if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
+               memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
+
+       host_client->edict->fields.server->netname = PRVM_SetEngineString(host_client->name);
+       if (strcmp(host_client->old_name, host_client->name))
+       {
+               if (host_client->spawned)
+                       SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
+               strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
+               // send notification to all clients
+               MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
+               MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
+               MSG_WriteString (&sv.reliable_datagram, host_client->name);
+               SV_WriteNetnameIntoDemo(host_client);
+       }
+}
+
+/*
+======================
+Host_Playermodel_f
+======================
+*/
+cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz (changed by playermodel command)"};
+// the old cl_playermodel in cl_main has been renamed to __cl_playermodel
+void Host_Playermodel_f (void)
+{
+       int i, j;
+       char newPath[sizeof(host_client->playermodel)];
+
+       if (Cmd_Argc () == 1)
+       {
+               Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
+               return;
+       }
+
+       if (Cmd_Argc () == 2)
+               strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
+       else
+               strlcpy (newPath, Cmd_Args(), sizeof (newPath));
+
+       for (i = 0, j = 0;newPath[i];i++)
+               if (newPath[i] != '\r' && newPath[i] != '\n')
+                       newPath[j++] = newPath[i];
+       newPath[j] = 0;
+
+       if (cmd_source == src_command)
+       {
+               Cvar_Set ("_cl_playermodel", newPath);
+               return;
+       }
+
+       /*
+       if (realtime < host_client->nametime)
+       {
+               SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
+               return;
+       }
 
-// send notification to all clients
+       host_client->nametime = realtime + 5;
+       */
 
-       MSG_WriteByte(&sv.reliable_datagram, svc_updatename);
-       MSG_WriteByte(&sv.reliable_datagram, host_client->number);
-       MSG_WriteString(&sv.reliable_datagram, host_client->name);
+       // point the string back at updateclient->name to keep it safe
+       strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
+       if( prog->fieldoffsets.playermodel >= 0 )
+               PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playermodel)->string = PRVM_SetEngineString(host_client->playermodel);
+       if (strcmp(host_client->old_model, host_client->playermodel))
+       {
+               strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
+               /*// send notification to all clients
+               MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
+               MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
+               MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
+       }
 }
 
+/*
+======================
+Host_Playerskin_f
+======================
+*/
+cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz (changed by playerskin command)"};
+void Host_Playerskin_f (void)
+{
+       int i, j;
+       char newPath[sizeof(host_client->playerskin)];
+
+       if (Cmd_Argc () == 1)
+       {
+               Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
+               return;
+       }
+
+       if (Cmd_Argc () == 2)
+               strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
+       else
+               strlcpy (newPath, Cmd_Args(), sizeof (newPath));
+
+       for (i = 0, j = 0;newPath[i];i++)
+               if (newPath[i] != '\r' && newPath[i] != '\n')
+                       newPath[j++] = newPath[i];
+       newPath[j] = 0;
+
+       if (cmd_source == src_command)
+       {
+               Cvar_Set ("_cl_playerskin", newPath);
+               return;
+       }
+
+       /*
+       if (realtime < host_client->nametime)
+       {
+               SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
+               return;
+       }
+
+       host_client->nametime = realtime + 5;
+       */
+
+       // point the string back at updateclient->name to keep it safe
+       strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
+       if( prog->fieldoffsets.playerskin >= 0 )
+               PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playerskin)->string = PRVM_SetEngineString(host_client->playerskin);
+       if (strcmp(host_client->old_skin, host_client->playerskin))
+       {
+               //if (host_client->spawned)
+               //      SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
+               strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
+               /*// send notification to all clients
+               MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
+               MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
+               MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
+       }
+}
 
 void Host_Version_f (void)
 {
@@ -789,8 +1220,8 @@ void Host_Say(qboolean teamonly)
        int j, quoted;
        const char *p1;
        char *p2;
-       // LordHavoc: 256 char say messages
-       unsigned char text[256];
+       // LordHavoc: long say messages
+       char text[1024];
        qboolean fromServer = false;
 
        if (cmd_source == src_command)
@@ -813,7 +1244,6 @@ void Host_Say(qboolean teamonly)
        if (!teamplay.integer)
                teamonly = false;
 
-// turn on color set 1
        p1 = Cmd_Args();
        quoted = false;
        if (*p1 == '\"')
@@ -821,10 +1251,15 @@ void Host_Say(qboolean teamonly)
                quoted = true;
                p1++;
        }
-       if (!fromServer)
-               snprintf (text, sizeof(text), "%c%s: %s", 1, host_client->name, p1);
+       // note this uses the chat prefix \001
+       if (!fromServer && !teamonly)
+               dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
+       else if (!fromServer && teamonly)
+               dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
+       else if(*(sv_adminnick.string))
+               dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
        else
-               snprintf (text, sizeof(text), "%c<%s> %s", 1, hostname.string, p1);
+               dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
        p2 = text + strlen(text);
        while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
        {
@@ -833,15 +1268,17 @@ void Host_Say(qboolean teamonly)
                p2[-1] = 0;
                p2--;
        }
+       strlcat(text, "\n", sizeof(text));
 
        // note: save is not a valid edict if fromServer is true
        save = host_client;
        for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
-               if (host_client->spawned && (!teamonly || host_client->edict->v->team == save->edict->v->team))
+               if (host_client->active && (!teamonly || host_client->edict->fields.server->team == save->edict->fields.server->team))
                        SV_ClientPrint(text);
        host_client = save;
 
-       Sys_Print(&text[1]);
+       if (cls.state == ca_dedicated)
+               Con_Print(&text[1]);
 }
 
 
@@ -859,10 +1296,13 @@ void Host_Say_Team_f(void)
 
 void Host_Tell_f(void)
 {
+       const char *playername_start = NULL;
+       size_t playername_length = 0;
+       int playernumber = 0;
        client_t *save;
        int j;
        const char *p1, *p2;
-       char text[1024]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
+       char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
        qboolean fromServer = false;
 
        if (cmd_source == src_command)
@@ -876,21 +1316,83 @@ void Host_Tell_f(void)
                }
        }
 
-       if (Cmd_Argc () < 3)
+       if (Cmd_Argc () < 2)
                return;
 
+       // note this uses the chat prefix \001
        if (!fromServer)
-               sprintf (text, "%s: ", host_client->name);
+               dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
+       else if(*(sv_adminnick.string))
+               dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
        else
-               sprintf (text, "<%s> ", hostname.string);
+               dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
 
        p1 = Cmd_Args();
        p2 = p1 + strlen(p1);
        // remove the target name
-       while (p1 < p2 && *p1 != ' ')
+       while (p1 < p2 && *p1 == ' ')
                p1++;
+       if(*p1 == '#')
+       {
+               ++p1;
+               while (p1 < p2 && *p1 == ' ')
+                       p1++;
+               while (p1 < p2 && isdigit(*p1))
+               {
+                       playernumber = playernumber * 10 + (*p1 - '0');
+                       p1++;
+               }
+               --playernumber;
+       }
+       else if(*p1 == '"')
+       {
+               ++p1;
+               playername_start = p1;
+               while (p1 < p2 && *p1 != '"')
+                       p1++;
+               playername_length = p1 - playername_start;
+               if(p1 < p2)
+                       p1++;
+       }
+       else
+       {
+               playername_start = p1;
+               while (p1 < p2 && *p1 != ' ')
+                       p1++;
+               playername_length = p1 - playername_start;
+       }
        while (p1 < p2 && *p1 == ' ')
                p1++;
+       if(playername_start)
+       {
+               // set playernumber to the right client
+               char namebuf[128];
+               if(playername_length >= sizeof(namebuf))
+               {
+                       if (fromServer)
+                               Con_Print("Host_Tell: too long player name/ID\n");
+                       else
+                               SV_ClientPrint("Host_Tell: too long player name/ID\n");
+                       return;
+               }
+               memcpy(namebuf, playername_start, playername_length);
+               namebuf[playername_length] = 0;
+               for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
+               {
+                       if (!svs.clients[playernumber].active)
+                               continue;
+                       if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
+                               break;
+               }
+       }
+       if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
+       {
+               if (fromServer)
+                       Con_Print("Host_Tell: invalid player name/ID\n");
+               else
+                       SV_ClientPrint("Host_Tell: invalid player name/ID\n");
+               return;
+       }
        // remove trailing newlines
        while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
                p2--;
@@ -907,15 +1409,16 @@ void Host_Tell_f(void)
        }
        while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
                p2--;
-       for (j = strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
+       if(p1 == p2)
+               return; // empty say
+       for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
                text[j++] = *p1++;
        text[j++] = '\n';
        text[j++] = 0;
 
        save = host_client;
-       for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
-               if (host_client->spawned && !strcasecmp(host_client->name, Cmd_Argv(1)))
-                       SV_ClientPrint(text);
+       host_client = svs.clients + playernumber;
+       SV_ClientPrint(text);
        host_client = save;
 }
 
@@ -925,13 +1428,67 @@ void Host_Tell_f(void)
 Host_Color_f
 ==================
 */
-cvar_t cl_color = {CVAR_SAVE, "_cl_color", "0"};
+cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
+void Host_Color(int changetop, int changebottom)
+{
+       int top, bottom, playercolor;
+
+       // get top and bottom either from the provided values or the current values
+       // (allows changing only top or bottom, or both at once)
+       top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
+       bottom = changebottom >= 0 ? changebottom : cl_color.integer;
+
+       top &= 15;
+       bottom &= 15;
+       // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
+       //if (top > 13)
+       //      top = 13;
+       //if (bottom > 13)
+       //      bottom = 13;
+
+       playercolor = top*16 + bottom;
+
+       if (cmd_source == src_command)
+       {
+               Cvar_SetValueQuick(&cl_color, playercolor);
+               return;
+       }
+
+       if (cls.protocol == PROTOCOL_QUAKEWORLD)
+               return;
+
+       if (host_client->edict && prog->funcoffsets.SV_ChangeTeam)
+       {
+               Con_DPrint("Calling SV_ChangeTeam\n");
+               prog->globals.server->time = sv.time;
+               prog->globals.generic[OFS_PARM0] = playercolor;
+               prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
+               PRVM_ExecuteProgram(prog->funcoffsets.SV_ChangeTeam, "QC function SV_ChangeTeam is missing");
+       }
+       else
+       {
+               prvm_eval_t *val;
+               if (host_client->edict)
+               {
+                       if ((val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.clientcolors)))
+                               val->_float = playercolor;
+                       host_client->edict->fields.server->team = bottom + 1;
+               }
+               host_client->colors = playercolor;
+               if (host_client->old_colors != host_client->colors)
+               {
+                       host_client->old_colors = host_client->colors;
+                       // send notification to all clients
+                       MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
+                       MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
+                       MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
+               }
+       }
+}
+
 void Host_Color_f(void)
 {
        int             top, bottom;
-       int             playercolor;
-       mfunction_t *f;
-       func_t  SV_ChangeTeam;
 
        if (Cmd_Argc() == 1)
        {
@@ -947,54 +1504,34 @@ void Host_Color_f(void)
                top = atoi(Cmd_Argv(1));
                bottom = atoi(Cmd_Argv(2));
        }
+       Host_Color(top, bottom);
+}
 
-       top &= 15;
-       // LordHavoc: allow skin colormaps 14 and 15 (was 13)
-       if (top > 15)
-               top = 15;
-       bottom &= 15;
-       // LordHavoc: allow skin colormaps 14 and 15 (was 13)
-       if (bottom > 15)
-               bottom = 15;
-
-       playercolor = top*16 + bottom;
-
-       if (cmd_source == src_command)
+void Host_TopColor_f(void)
+{
+       if (Cmd_Argc() == 1)
        {
-               Cvar_SetValue ("_cl_color", playercolor);
-               if (cls.state == ca_connected)
-                       Cmd_ForwardToServer ();
+               Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
+               Con_Print("topcolor <0-15>\n");
                return;
        }
 
-       if (sv_player && (f = ED_FindFunction ("SV_ChangeTeam")) && (SV_ChangeTeam = (func_t)(f - pr_functions)))
+       Host_Color(atoi(Cmd_Argv(1)), -1);
+}
+
+void Host_BottomColor_f(void)
+{
+       if (Cmd_Argc() == 1)
        {
-               Con_DPrint("Calling SV_ChangeTeam\n");
-               pr_global_struct->time = sv.time;
-               pr_globals[OFS_PARM0] = playercolor;
-               pr_global_struct->self = EDICT_TO_PROG(sv_player);
-               PR_ExecuteProgram (SV_ChangeTeam, "QC function SV_ChangeTeam is missing");
+               Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
+               Con_Print("bottomcolor <0-15>\n");
+               return;
        }
-       else
-       {
-               eval_t *val;
-               if (sv_player)
-               {
-                       if ((val = GETEDICTFIELDVALUE(sv_player, eval_clientcolors)))
-                               val->_float = playercolor;
-                       sv_player->v->team = bottom + 1;
-               }
-               host_client->colors = playercolor;
-               host_client->old_colors = playercolor;
 
-               // send notification to all clients
-               MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
-               MSG_WriteByte (&sv.reliable_datagram, host_client->number);
-               MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
-       }
+       Host_Color(-1, atoi(Cmd_Argv(1)));
 }
 
-cvar_t cl_rate = {CVAR_SAVE, "_cl_rate", "10000"};
+cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
 void Host_Rate_f(void)
 {
        int rate;
@@ -1002,7 +1539,7 @@ void Host_Rate_f(void)
        if (Cmd_Argc() != 2)
        {
                Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
-               Con_Print("rate <500-25000>\n");
+               Con_Print("rate <bytespersecond>\n");
                return;
        }
 
@@ -1010,13 +1547,11 @@ void Host_Rate_f(void)
 
        if (cmd_source == src_command)
        {
-               Cvar_SetValue ("_cl_rate", bound(NET_MINRATE, rate, NET_MAXRATE));
-               if (cls.state == ca_connected)
-                       Cmd_ForwardToServer ();
+               Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
                return;
        }
 
-       host_client->netconnection->rate = rate;
+       host_client->rate = rate;
 }
 
 /*
@@ -1026,21 +1561,15 @@ Host_Kill_f
 */
 void Host_Kill_f (void)
 {
-       if (cmd_source == src_command)
-       {
-               Cmd_ForwardToServer ();
-               return;
-       }
-
-       if (!sv_player || sv_player->v->health <= 0)
+       if (host_client->edict->fields.server->health <= 0)
        {
                SV_ClientPrint("Can't suicide -- already dead!\n");
                return;
        }
 
-       pr_global_struct->time = sv.time;
-       pr_global_struct->self = EDICT_TO_PROG(host_client->edict);
-       PR_ExecuteProgram (pr_global_struct->ClientKill, "QC function ClientKill is missing");
+       prog->globals.server->time = sv.time;
+       prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
+       PRVM_ExecuteProgram (prog->globals.server->ClientKill, "QC function ClientKill is missing");
 }
 
 
@@ -1051,12 +1580,6 @@ Host_Pause_f
 */
 void Host_Pause_f (void)
 {
-
-       if (cmd_source == src_command)
-       {
-               Cmd_ForwardToServer ();
-               return;
-       }
        if (!pausable.integer)
                SV_ClientPrint("Pause not allowed.\n");
        else
@@ -1073,13 +1596,14 @@ void Host_Pause_f (void)
 ======================
 Host_PModel_f
 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
+LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
 ======================
 */
-cvar_t cl_pmodel = {CVAR_SAVE, "_cl_pmodel", "0"};
+cvar_t cl_pmodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"};
 static void Host_PModel_f (void)
 {
        int i;
-       eval_t *val;
+       prvm_eval_t *val;
 
        if (Cmd_Argc () == 1)
        {
@@ -1098,8 +1622,7 @@ static void Host_PModel_f (void)
                return;
        }
 
-       host_client->pmodel = i;
-       if (sv_player && (val = GETEDICTFIELDVALUE(sv_player, eval_pmodel)))
+       if (host_client->edict && (val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.pmodel)))
                val->_float = i;
 }
 
@@ -1113,22 +1636,22 @@ Host_PreSpawn_f
 */
 void Host_PreSpawn_f (void)
 {
-       if (cmd_source == src_command)
+       if (host_client->spawned)
        {
-               Con_Print("prespawn is not valid from the console\n");
+               Con_Print("prespawn not valid -- already spawned\n");
                return;
        }
 
-       if (host_client->spawned)
+       if (host_client->netconnection)
        {
-               Con_Print("prespawn not valid -- already spawned\n");
-               return;
+               SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
+               MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
+               MSG_WriteByte (&host_client->netconnection->message, 2);
+               host_client->sendsignon = 0;            // enable unlimited sends again
        }
 
-       SZ_Write (&host_client->message, sv.signon.data, sv.signon.cursize);
-       MSG_WriteByte (&host_client->message, svc_signonnum);
-       MSG_WriteByte (&host_client->message, 2);
-       host_client->sendsignon = true;
+       // reset the name change timer because the client will send name soon
+       host_client->nametime = 0;
 }
 
 /*
@@ -1140,14 +1663,7 @@ void Host_Spawn_f (void)
 {
        int i;
        client_t *client;
-       func_t RestoreGame;
-       mfunction_t *f;
-
-       if (cmd_source == src_command)
-       {
-               Con_Print("spawn is not valid from the console\n");
-               return;
-       }
+       int stats[MAX_CL_STATS];
 
        if (host_client->spawned)
        {
@@ -1155,116 +1671,122 @@ void Host_Spawn_f (void)
                return;
        }
 
-       if (!sv_player)
-       {
-               Con_Print("Host_Spawn: no edict??\n");
-               return;
-       }
-
+       // reset name change timer again because they might want to change name
+       // again in the first 5 seconds after connecting
        host_client->nametime = 0;
 
        // LordHavoc: moved this above the QC calls at FrikaC's request
-       // send all current names, colors, and frag counts
-       SZ_Clear (&host_client->message);
+       // LordHavoc: commented this out
+       //if (host_client->netconnection)
+       //      SZ_Clear (&host_client->netconnection->message);
 
        // run the entrance script
        if (sv.loadgame)
        {
                // loaded games are fully initialized already
-               // if this is the last client to be connected, unpause
-               sv.paused = false;
-
-               if ((f = ED_FindFunction ("RestoreGame")))
-               if ((RestoreGame = (func_t)(f - pr_functions)))
+               if (prog->funcoffsets.RestoreGame)
                {
                        Con_DPrint("Calling RestoreGame\n");
-                       pr_global_struct->time = sv.time;
-                       pr_global_struct->self = EDICT_TO_PROG(sv_player);
-                       PR_ExecuteProgram (RestoreGame, "QC function RestoreGame is missing");
+                       prog->globals.server->time = sv.time;
+                       prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
+                       PRVM_ExecuteProgram(prog->funcoffsets.RestoreGame, "QC function RestoreGame is missing");
                }
        }
        else
        {
-               // set up the edict
-               ED_ClearEdict(sv_player);
-
-               //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, sv_player->netname = %s, host_client->name = %s\n", PR_GetString(host_client->edict->v->netname), PR_GetString(sv_player->v->netname), host_client->name);
+               //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(host_client->edict->fields.server->netname), PRVM_GetString(host_client->edict->fields.server->netname), host_client->name);
 
                // copy spawn parms out of the client_t
                for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
-                       (&pr_global_struct->parm1)[i] = host_client->spawn_parms[i];
+                       (&prog->globals.server->parm1)[i] = host_client->spawn_parms[i];
 
                // call the spawn function
-               pr_global_struct->time = sv.time;
-               pr_global_struct->self = EDICT_TO_PROG(sv_player);
-               PR_ExecuteProgram (pr_global_struct->ClientConnect, "QC function ClientConnect is missing");
+               host_client->clientconnectcalled = true;
+               prog->globals.server->time = sv.time;
+               prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
+               PRVM_ExecuteProgram (prog->globals.server->ClientConnect, "QC function ClientConnect is missing");
 
-               if ((Sys_DoubleTime() - host_client->netconnection->connecttime) <= sv.time)
-                       Sys_Printf("%s entered the game\n", host_client->name);
+               if (cls.state == ca_dedicated)
+                       Con_Printf("%s connected\n", host_client->name);
 
-               PR_ExecuteProgram (pr_global_struct->PutClientInServer, "QC function PutClientInServer is missing");
+               PRVM_ExecuteProgram (prog->globals.server->PutClientInServer, "QC function PutClientInServer is missing");
        }
 
+       if (!host_client->netconnection)
+               return;
 
        // send time of update
-       MSG_WriteByte (&host_client->message, svc_time);
-       MSG_WriteFloat (&host_client->message, sv.time);
+       MSG_WriteByte (&host_client->netconnection->message, svc_time);
+       MSG_WriteFloat (&host_client->netconnection->message, sv.time);
 
+       // send all current names, colors, and frag counts
        for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
        {
                if (!client->active)
                        continue;
-               MSG_WriteByte (&host_client->message, svc_updatename);
-               MSG_WriteByte (&host_client->message, i);
-               MSG_WriteString (&host_client->message, client->old_name);
-               MSG_WriteByte (&host_client->message, svc_updatefrags);
-               MSG_WriteByte (&host_client->message, i);
-               MSG_WriteShort (&host_client->message, client->old_frags);
-               MSG_WriteByte (&host_client->message, svc_updatecolors);
-               MSG_WriteByte (&host_client->message, i);
-               MSG_WriteByte (&host_client->message, client->old_colors);
+               MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
+               MSG_WriteByte (&host_client->netconnection->message, i);
+               MSG_WriteString (&host_client->netconnection->message, client->name);
+               MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
+               MSG_WriteByte (&host_client->netconnection->message, i);
+               MSG_WriteShort (&host_client->netconnection->message, client->frags);
+               MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
+               MSG_WriteByte (&host_client->netconnection->message, i);
+               MSG_WriteByte (&host_client->netconnection->message, client->colors);
        }
 
        // send all current light styles
        for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
        {
-               MSG_WriteByte (&host_client->message, svc_lightstyle);
-               MSG_WriteByte (&host_client->message, (char)i);
-               MSG_WriteString (&host_client->message, sv.lightstyles[i]);
+               if (sv.lightstyles[i][0])
+               {
+                       MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
+                       MSG_WriteByte (&host_client->netconnection->message, (char)i);
+                       MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
+               }
        }
 
        // send some stats
-       MSG_WriteByte (&host_client->message, svc_updatestat);
-       MSG_WriteByte (&host_client->message, STAT_TOTALSECRETS);
-       MSG_WriteLong (&host_client->message, pr_global_struct->total_secrets);
+       MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
+       MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
+       MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->total_secrets);
 
-       MSG_WriteByte (&host_client->message, svc_updatestat);
-       MSG_WriteByte (&host_client->message, STAT_TOTALMONSTERS);
-       MSG_WriteLong (&host_client->message, pr_global_struct->total_monsters);
+       MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
+       MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
+       MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->total_monsters);
 
-       MSG_WriteByte (&host_client->message, svc_updatestat);
-       MSG_WriteByte (&host_client->message, STAT_SECRETS);
-       MSG_WriteLong (&host_client->message, pr_global_struct->found_secrets);
+       MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
+       MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
+       MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->found_secrets);
 
-       MSG_WriteByte (&host_client->message, svc_updatestat);
-       MSG_WriteByte (&host_client->message, STAT_MONSTERS);
-       MSG_WriteLong (&host_client->message, pr_global_struct->killed_monsters);
+       MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
+       MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
+       MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->killed_monsters);
 
        // send a fixangle
        // Never send a roll angle, because savegames can catch the server
        // in a state where it is expecting the client to correct the angle
        // and it won't happen if the game was just loaded, so you wind up
        // with a permanent head tilt
-       MSG_WriteByte (&host_client->message, svc_setangle);
-       MSG_WriteAngle (&host_client->message, sv_player->v->angles[0], sv.protocol);
-       MSG_WriteAngle (&host_client->message, sv_player->v->angles[1], sv.protocol);
-       MSG_WriteAngle (&host_client->message, 0, sv.protocol);
+       if (sv.loadgame)
+       {
+               MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
+               MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[0], sv.protocol);
+               MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[1], sv.protocol);
+               MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
+       }
+       else
+       {
+               MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
+               MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->angles[0], sv.protocol);
+               MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->angles[1], sv.protocol);
+               MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
+       }
 
-       SV_WriteClientdataToMessage (sv_player, &host_client->message);
+       SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
 
-       MSG_WriteByte (&host_client->message, svc_signonnum);
-       MSG_WriteByte (&host_client->message, 3);
-       host_client->sendsignon = true;
+       MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
+       MSG_WriteByte (&host_client->netconnection->message, 3);
 }
 
 /*
@@ -1274,13 +1796,21 @@ Host_Begin_f
 */
 void Host_Begin_f (void)
 {
-       if (cmd_source == src_command)
+       host_client->spawned = true;
+
+       // LordHavoc: note: this code also exists in SV_DropClient
+       if (sv.loadgame)
        {
-               Con_Print("begin is not valid from the console\n");
-               return;
+               int i;
+               for (i = 0;i < svs.maxclients;i++)
+                       if (svs.clients[i].active && !svs.clients[i].spawned)
+                               break;
+               if (i == svs.maxclients)
+               {
+                       Con_Printf("Loaded game, everyone rejoined - unpausing\n");
+                       sv.paused = sv.loadgame = false; // we're basically done with loading now
+               }
        }
-
-       host_client->spawned = true;
 }
 
 //===========================================================================
@@ -1301,14 +1831,15 @@ void Host_Kick_f (void)
        int i;
        qboolean byNumber = false;
 
-       if (cmd_source != src_command || !sv.active)
+       if (!sv.active)
                return;
 
+       SV_VM_Begin();
        save = host_client;
 
        if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
        {
-               i = atof(Cmd_Argv(2)) - 1;
+               i = (int)(atof(Cmd_Argv(2)) - 1);
                if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
                        return;
                byNumber = true;
@@ -1343,7 +1874,7 @@ void Host_Kick_f (void)
                if (Cmd_Argc() > 2)
                {
                        message = Cmd_Args();
-                       COM_ParseToken(&message, false);
+                       COM_ParseToken_Simple(&message, false, false);
                        if (byNumber)
                        {
                                message++;                                                      // skip the #
@@ -1362,6 +1893,7 @@ void Host_Kick_f (void)
        }
 
        host_client = save;
+       SV_VM_End();
 }
 
 /*
@@ -1381,16 +1913,7 @@ void Host_Give_f (void)
 {
        const char *t;
        int v;
-       eval_t *val;
-
-       if (cmd_source == src_command)
-       {
-               Cmd_ForwardToServer ();
-               return;
-       }
-
-       if (!sv_player)
-               return;
+       prvm_eval_t *val;
 
        if (!allowcheats)
        {
@@ -1419,128 +1942,128 @@ void Host_Give_f (void)
                        if (t[0] == '6')
                        {
                                if (t[1] == 'a')
-                                       sv_player->v->items = (int)sv_player->v->items | HIT_PROXIMITY_GUN;
+                                       host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_PROXIMITY_GUN;
                                else
-                                       sv_player->v->items = (int)sv_player->v->items | IT_GRENADE_LAUNCHER;
+                                       host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | IT_GRENADE_LAUNCHER;
                        }
                        else if (t[0] == '9')
-                               sv_player->v->items = (int)sv_player->v->items | HIT_LASER_CANNON;
+                               host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_LASER_CANNON;
                        else if (t[0] == '0')
-                               sv_player->v->items = (int)sv_player->v->items | HIT_MJOLNIR;
+                               host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_MJOLNIR;
                        else if (t[0] >= '2')
-                               sv_player->v->items = (int)sv_player->v->items | (IT_SHOTGUN << (t[0] - '2'));
+                               host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | (IT_SHOTGUN << (t[0] - '2'));
                }
                else
                {
                        if (t[0] >= '2')
-                               sv_player->v->items = (int)sv_player->v->items | (IT_SHOTGUN << (t[0] - '2'));
+                               host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | (IT_SHOTGUN << (t[0] - '2'));
                }
                break;
 
        case 's':
-               if (gamemode == GAME_ROGUE && (val = GETEDICTFIELDVALUE(sv_player, eval_ammo_shells1)))
+               if (gamemode == GAME_ROGUE && (val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_shells1)))
                        val->_float = v;
 
-               sv_player->v->ammo_shells = v;
+               host_client->edict->fields.server->ammo_shells = v;
                break;
        case 'n':
                if (gamemode == GAME_ROGUE)
                {
-                       if ((val = GETEDICTFIELDVALUE(sv_player, eval_ammo_nails1)))
+                       if ((val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_nails1)))
                        {
                                val->_float = v;
-                               if (sv_player->v->weapon <= IT_LIGHTNING)
-                                       sv_player->v->ammo_nails = v;
+                               if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
+                                       host_client->edict->fields.server->ammo_nails = v;
                        }
                }
                else
                {
-                       sv_player->v->ammo_nails = v;
+                       host_client->edict->fields.server->ammo_nails = v;
                }
                break;
        case 'l':
                if (gamemode == GAME_ROGUE)
                {
-                       val = GETEDICTFIELDVALUE(sv_player, eval_ammo_lava_nails);
+                       val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_lava_nails);
                        if (val)
                        {
                                val->_float = v;
-                               if (sv_player->v->weapon > IT_LIGHTNING)
-                                       sv_player->v->ammo_nails = v;
+                               if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
+                                       host_client->edict->fields.server->ammo_nails = v;
                        }
                }
                break;
        case 'r':
                if (gamemode == GAME_ROGUE)
                {
-                       val = GETEDICTFIELDVALUE(sv_player, eval_ammo_rockets1);
+                       val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_rockets1);
                        if (val)
                        {
                                val->_float = v;
-                               if (sv_player->v->weapon <= IT_LIGHTNING)
-                                       sv_player->v->ammo_rockets = v;
+                               if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
+                                       host_client->edict->fields.server->ammo_rockets = v;
                        }
                }
                else
                {
-                       sv_player->v->ammo_rockets = v;
+                       host_client->edict->fields.server->ammo_rockets = v;
                }
                break;
        case 'm':
                if (gamemode == GAME_ROGUE)
                {
-                       val = GETEDICTFIELDVALUE(sv_player, eval_ammo_multi_rockets);
+                       val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_multi_rockets);
                        if (val)
                        {
                                val->_float = v;
-                               if (sv_player->v->weapon > IT_LIGHTNING)
-                                       sv_player->v->ammo_rockets = v;
+                               if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
+                                       host_client->edict->fields.server->ammo_rockets = v;
                        }
                }
                break;
        case 'h':
-               sv_player->v->health = v;
+               host_client->edict->fields.server->health = v;
                break;
        case 'c':
                if (gamemode == GAME_ROGUE)
                {
-                       val = GETEDICTFIELDVALUE(sv_player, eval_ammo_cells1);
+                       val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_cells1);
                        if (val)
                        {
                                val->_float = v;
-                               if (sv_player->v->weapon <= IT_LIGHTNING)
-                                       sv_player->v->ammo_cells = v;
+                               if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
+                                       host_client->edict->fields.server->ammo_cells = v;
                        }
                }
                else
                {
-                       sv_player->v->ammo_cells = v;
+                       host_client->edict->fields.server->ammo_cells = v;
                }
                break;
        case 'p':
                if (gamemode == GAME_ROGUE)
                {
-                       val = GETEDICTFIELDVALUE(sv_player, eval_ammo_plasma);
+                       val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_plasma);
                        if (val)
                        {
                                val->_float = v;
-                               if (sv_player->v->weapon > IT_LIGHTNING)
-                                       sv_player->v->ammo_cells = v;
+                               if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
+                                       host_client->edict->fields.server->ammo_cells = v;
                        }
                }
                break;
        }
 }
 
-edict_t        *FindViewthing (void)
+prvm_edict_t   *FindViewthing (void)
 {
        int             i;
-       edict_t *e;
+       prvm_edict_t    *e;
 
-       for (i=0 ; i<sv.num_edicts ; i++)
+       for (i=0 ; i<prog->num_edicts ; i++)
        {
-               e = EDICT_NUM(i);
-               if (!strcmp (PR_GetString(e->v->classname), "viewthing"))
+               e = PRVM_EDICT_NUM(i);
+               if (!strcmp (PRVM_GetString(e->fields.server->classname), "viewthing"))
                        return e;
        }
        Con_Print("No viewthing on map\n");
@@ -1554,22 +2077,27 @@ Host_Viewmodel_f
 */
 void Host_Viewmodel_f (void)
 {
-       edict_t *e;
-       model_t *m;
+       prvm_edict_t    *e;
+       dp_model_t      *m;
+
+       if (!sv.active)
+               return;
 
+       SV_VM_Begin();
        e = FindViewthing ();
+       SV_VM_End();
        if (!e)
                return;
 
-       m = Mod_ForName (Cmd_Argv(1), false, true, false);
-       if (!m)
+       m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
+       if (!m || !m->loaded || !m->Draw)
        {
-               Con_Printf("Can't load %s\n", Cmd_Argv(1));
+               Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
                return;
        }
 
-       e->v->frame = 0;
-       cl.model_precache[(int)e->v->modelindex] = m;
+       e->fields.server->frame = 0;
+       cl.model_precache[(int)e->fields.server->modelindex] = m;
 }
 
 /*
@@ -1579,24 +2107,29 @@ Host_Viewframe_f
 */
 void Host_Viewframe_f (void)
 {
-       edict_t *e;
+       prvm_edict_t    *e;
        int             f;
-       model_t *m;
+       dp_model_t      *m;
+
+       if (!sv.active)
+               return;
 
+       SV_VM_Begin();
        e = FindViewthing ();
+       SV_VM_End();
        if (!e)
                return;
-       m = cl.model_precache[(int)e->v->modelindex];
+       m = cl.model_precache[(int)e->fields.server->modelindex];
 
        f = atoi(Cmd_Argv(1));
        if (f >= m->numframes)
                f = m->numframes-1;
 
-       e->v->frame = f;
+       e->fields.server->frame = f;
 }
 
 
-void PrintFrameName (model_t *m, int frame)
+void PrintFrameName (dp_model_t *m, int frame)
 {
        if (m->animscenes)
                Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
@@ -1611,19 +2144,24 @@ Host_Viewnext_f
 */
 void Host_Viewnext_f (void)
 {
-       edict_t *e;
-       model_t *m;
+       prvm_edict_t    *e;
+       dp_model_t      *m;
+
+       if (!sv.active)
+               return;
 
+       SV_VM_Begin();
        e = FindViewthing ();
+       SV_VM_End();
        if (!e)
                return;
-       m = cl.model_precache[(int)e->v->modelindex];
+       m = cl.model_precache[(int)e->fields.server->modelindex];
 
-       e->v->frame = e->v->frame + 1;
-       if (e->v->frame >= m->numframes)
-               e->v->frame = m->numframes - 1;
+       e->fields.server->frame = e->fields.server->frame + 1;
+       if (e->fields.server->frame >= m->numframes)
+               e->fields.server->frame = m->numframes - 1;
 
-       PrintFrameName (m, e->v->frame);
+       PrintFrameName (m, (int)e->fields.server->frame);
 }
 
 /*
@@ -1633,20 +2171,25 @@ Host_Viewprev_f
 */
 void Host_Viewprev_f (void)
 {
-       edict_t *e;
-       model_t *m;
+       prvm_edict_t    *e;
+       dp_model_t      *m;
 
+       if (!sv.active)
+               return;
+
+       SV_VM_Begin();
        e = FindViewthing ();
+       SV_VM_End();
        if (!e)
                return;
 
-       m = cl.model_precache[(int)e->v->modelindex];
+       m = cl.model_precache[(int)e->fields.server->modelindex];
 
-       e->v->frame = e->v->frame - 1;
-       if (e->v->frame < 0)
-               e->v->frame = 0;
+       e->fields.server->frame = e->fields.server->frame - 1;
+       if (e->fields.server->frame < 0)
+               e->fields.server->frame = 0;
 
-       PrintFrameName (m, e->v->frame);
+       PrintFrameName (m, (int)e->fields.server->frame);
 }
 
 /*
@@ -1667,17 +2210,8 @@ void Host_Startdemos_f (void)
 {
        int             i, c;
 
-       if (cls.state == ca_dedicated || COM_CheckParm("-listen"))
-       {
-               if (!sv.active)
-               {
-                       if (gamemode == GAME_TRANSFUSION)
-                               Cbuf_AddText ("map bb1\n");
-                       else
-                               Cbuf_AddText ("map start\n");
-               }
+       if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
                return;
-       }
 
        c = Cmd_Argc() - 1;
        if (c > MAX_DEMOS)
@@ -1733,7 +2267,45 @@ void Host_Stopdemo_f (void)
        if (!cls.demoplayback)
                return;
        CL_Disconnect ();
-       Host_ShutdownServer (false);
+       Host_ShutdownServer ();
+}
+
+void Host_SendCvar_f (void)
+{
+       int             i;
+       cvar_t  *c;
+       const char *cvarname;
+       client_t *old;
+
+       if(Cmd_Argc() != 2)
+               return;
+       cvarname = Cmd_Argv(1);
+       if (cls.state == ca_connected)
+       {
+               c = Cvar_FindVar(cvarname);
+               // LordHavoc: if there is no such cvar or if it is private, send a
+               // reply indicating that it has no value
+               if(!c || (c->flags & CVAR_PRIVATE))
+                       Cmd_ForwardStringToServer(va("sentcvar %s", cvarname));
+               else
+                       Cmd_ForwardStringToServer(va("sentcvar %s \"%s\"", c->name, c->string));
+               return;
+       }
+       if(!sv.active)// || !prog->funcoffsets.SV_ParseClientCommand)
+               return;
+
+       old = host_client;
+       if (cls.state != ca_dedicated)
+               i = 1;
+       else
+               i = 0;
+       for(;i<svs.maxclients;i++)
+               if(svs.clients[i].active && svs.clients[i].netconnection)
+               {
+                       host_client = &svs.clients[i];
+                       Host_ClientCommands("sendcvar %s\n", cvarname);
+               }
+       host_client = old;
 }
 
 static void MaxPlayers_f(void)
@@ -1742,30 +2314,467 @@ static void MaxPlayers_f(void)
 
        if (Cmd_Argc() != 2)
        {
-               Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients);
+               Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
                return;
        }
 
        if (sv.active)
        {
                Con_Print("maxplayers can not be changed while a server is running.\n");
-               return;
+               Con_Print("It will be changed on next server startup (\"map\" command).\n");
        }
 
        n = atoi(Cmd_Argv(1));
        n = bound(1, n, MAX_SCOREBOARD);
        Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
 
-       if (svs.clients)
-               Mem_Free(svs.clients);
-       svs.maxclients = n;
-       svs.clients = Mem_Alloc(sv_clients_mempool, sizeof(client_t) * svs.maxclients);
+       svs.maxclients_next = n;
        if (n == 1)
                Cvar_Set ("deathmatch", "0");
        else
                Cvar_Set ("deathmatch", "1");
 }
 
+/*
+=====================
+Host_PQRcon_f
+
+ProQuake rcon support
+=====================
+*/
+void Host_PQRcon_f (void)
+{
+       int i;
+       lhnetaddress_t to;
+       lhnetsocket_t *mysocket;
+       char peer_address[64];
+
+       if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer)
+       {
+               Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
+               return;
+       }
+
+       for (i = 0;rcon_password.string[i];i++)
+       {
+               if (ISWHITESPACE(rcon_password.string[i]))
+               {
+                       Con_Printf("rcon_password is not allowed to have any whitespace.\n");
+                       return;
+               }
+       }
+
+       if (cls.netcon)
+       {
+               InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
+       }
+       else
+       {
+               if (!rcon_address.string[0])
+               {
+                       Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
+                       return;
+               }
+               strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
+       }
+       LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
+       mysocket = NetConn_ChooseClientSocketForAddress(&to);
+       if (mysocket)
+       {
+               SZ_Clear(&net_message);
+               MSG_WriteLong (&net_message, 0);
+               MSG_WriteByte (&net_message, CCREQ_RCON);
+               MSG_WriteString (&net_message, rcon_password.string);
+               MSG_WriteString (&net_message, Cmd_Args());
+               *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
+               NetConn_Write(mysocket, net_message.data, net_message.cursize, &to);
+               SZ_Clear (&net_message);
+       }
+}
+
+//=============================================================================
+
+// QuakeWorld commands
+
+/*
+=====================
+Host_Rcon_f
+
+  Send the rest of the command line over as
+  an unconnected command.
+=====================
+*/
+void Host_Rcon_f (void) // credit: taken from QuakeWorld
+{
+       int i;
+       lhnetaddress_t to;
+       lhnetsocket_t *mysocket;
+
+       if (!rcon_password.string || !rcon_password.string[0])
+       {
+               Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
+               return;
+       }
+
+       for (i = 0;rcon_password.string[i];i++)
+       {
+               if (ISWHITESPACE(rcon_password.string[i]))
+               {
+                       Con_Printf("rcon_password is not allowed to have any whitespace.\n");
+                       return;
+               }
+       }
+
+       if (cls.netcon)
+               to = cls.netcon->peeraddress;
+       else
+       {
+               if (!rcon_address.string[0])
+               {
+                       Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
+                       return;
+               }
+               LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
+       }
+       mysocket = NetConn_ChooseClientSocketForAddress(&to);
+       if (mysocket && Cmd_Args()[0])
+       {
+               // simply put together the rcon packet and send it
+               if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
+               {
+                       if(cls.rcon_commands[cls.rcon_ringpos][0])
+                       {
+                               char s[128];
+                               LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
+                               Con_Printf("rcon to %s (for command %s) failed: too many buffered commands (possibly increase MAX_RCONS)\n", s, cls.rcon_commands[cls.rcon_ringpos]);
+                               cls.rcon_commands[cls.rcon_ringpos][0] = 0;
+                               --cls.rcon_trying;
+                       }
+                       for (i = 0;i < MAX_RCONS;i++)
+                               if(cls.rcon_commands[i][0])
+                                       if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
+                                               break;
+                       ++cls.rcon_trying;
+                       if(i >= MAX_RCONS)
+                               NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
+                       strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
+                       cls.rcon_addresses[cls.rcon_ringpos] = to;
+                       cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
+                       cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
+               }
+               else if(rcon_secure.integer)
+               {
+                       char buf[1500];
+                       char argbuf[1500];
+                       dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
+                       memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
+                       if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, strlen(rcon_password.string)))
+                       {
+                               buf[40] = ' ';
+                               strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
+                               NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
+                       }
+               }
+               else
+               {
+                       NetConn_WriteString(mysocket, va("\377\377\377\377rcon %s %s", rcon_password.string, Cmd_Args()), &to);
+               }
+       }
+}
+
+/*
+====================
+Host_User_f
+
+user <name or userid>
+
+Dump userdata / masterdata for a user
+====================
+*/
+void Host_User_f (void) // credit: taken from QuakeWorld
+{
+       int             uid;
+       int             i;
+
+       if (Cmd_Argc() != 2)
+       {
+               Con_Printf ("Usage: user <username / userid>\n");
+               return;
+       }
+
+       uid = atoi(Cmd_Argv(1));
+
+       for (i = 0;i < cl.maxclients;i++)
+       {
+               if (!cl.scores[i].name[0])
+                       continue;
+               if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
+               {
+                       InfoString_Print(cl.scores[i].qw_userinfo);
+                       return;
+               }
+       }
+       Con_Printf ("User not in server.\n");
+}
+
+/*
+====================
+Host_Users_f
+
+Dump userids for all current players
+====================
+*/
+void Host_Users_f (void) // credit: taken from QuakeWorld
+{
+       int             i;
+       int             c;
+
+       c = 0;
+       Con_Printf ("userid frags name\n");
+       Con_Printf ("------ ----- ----\n");
+       for (i = 0;i < cl.maxclients;i++)
+       {
+               if (cl.scores[i].name[0])
+               {
+                       Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
+                       c++;
+               }
+       }
+
+       Con_Printf ("%i total users\n", c);
+}
+
+/*
+==================
+Host_FullServerinfo_f
+
+Sent by server when serverinfo changes
+==================
+*/
+// TODO: shouldn't this be a cvar instead?
+void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
+{
+       char temp[512];
+       if (Cmd_Argc() != 2)
+       {
+               Con_Printf ("usage: fullserverinfo <complete info string>\n");
+               return;
+       }
+
+       strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
+       InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
+       cl.qw_teamplay = atoi(temp);
+}
+
+/*
+==================
+Host_FullInfo_f
+
+Allow clients to change userinfo
+==================
+Casey was here :)
+*/
+void Host_FullInfo_f (void) // credit: taken from QuakeWorld
+{
+       char key[512];
+       char value[512];
+       char *o;
+       const char *s;
+
+       if (Cmd_Argc() != 2)
+       {
+               Con_Printf ("fullinfo <complete info string>\n");
+               return;
+       }
+
+       s = Cmd_Argv(1);
+       if (*s == '\\')
+               s++;
+       while (*s)
+       {
+               o = key;
+               while (*s && *s != '\\')
+                       *o++ = *s++;
+               *o = 0;
+
+               if (!*s)
+               {
+                       Con_Printf ("MISSING VALUE\n");
+                       return;
+               }
+
+               o = value;
+               s++;
+               while (*s && *s != '\\')
+                       *o++ = *s++;
+               *o = 0;
+
+               if (*s)
+                       s++;
+
+               CL_SetInfo(key, value, false, false, false, false);
+       }
+}
+
+/*
+==================
+CL_SetInfo_f
+
+Allow clients to change userinfo
+==================
+*/
+void Host_SetInfo_f (void) // credit: taken from QuakeWorld
+{
+       if (Cmd_Argc() == 1)
+       {
+               InfoString_Print(cls.userinfo);
+               return;
+       }
+       if (Cmd_Argc() != 3)
+       {
+               Con_Printf ("usage: setinfo [ <key> <value> ]\n");
+               return;
+       }
+       CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
+}
+
+/*
+====================
+Host_Packet_f
+
+packet <destination> <contents>
+
+Contents allows \n escape character
+====================
+*/
+void Host_Packet_f (void) // credit: taken from QuakeWorld
+{
+       char send[2048];
+       int i, l;
+       const char *in;
+       char *out;
+       lhnetaddress_t address;
+       lhnetsocket_t *mysocket;
+
+       if (Cmd_Argc() != 3)
+       {
+               Con_Printf ("packet <destination> <contents>\n");
+               return;
+       }
+
+       if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
+       {
+               Con_Printf ("Bad address\n");
+               return;
+       }
+
+       in = Cmd_Argv(2);
+       out = send+4;
+       send[0] = send[1] = send[2] = send[3] = -1;
+
+       l = (int)strlen (in);
+       for (i=0 ; i<l ; i++)
+       {
+               if (out >= send + sizeof(send) - 1)
+                       break;
+               if (in[i] == '\\' && in[i+1] == 'n')
+               {
+                       *out++ = '\n';
+                       i++;
+               }
+               else if (in[i] == '\\' && in[i+1] == '0')
+               {
+                       *out++ = '\0';
+                       i++;
+               }
+               else if (in[i] == '\\' && in[i+1] == 't')
+               {
+                       *out++ = '\t';
+                       i++;
+               }
+               else if (in[i] == '\\' && in[i+1] == 'r')
+               {
+                       *out++ = '\r';
+                       i++;
+               }
+               else if (in[i] == '\\' && in[i+1] == '"')
+               {
+                       *out++ = '\"';
+                       i++;
+               }
+               else
+                       *out++ = in[i];
+       }
+
+       mysocket = NetConn_ChooseClientSocketForAddress(&address);
+       if (!mysocket)
+               mysocket = NetConn_ChooseServerSocketForAddress(&address);
+       if (mysocket)
+               NetConn_Write(mysocket, send, out - send, &address);
+}
+
+/*
+====================
+Host_Pings_f
+
+Send back ping and packet loss update for all current players to this player
+====================
+*/
+void Host_Pings_f (void)
+{
+       int             i, j, ping, packetloss;
+       char temp[128];
+
+       if (!host_client->netconnection)
+               return;
+
+       if (sv.protocol != PROTOCOL_QUAKEWORLD)
+       {
+               MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
+               MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
+       }
+       for (i = 0;i < svs.maxclients;i++)
+       {
+               packetloss = 0;
+               if (svs.clients[i].netconnection)
+                       for (j = 0;j < NETGRAPH_PACKETS;j++)
+                               if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
+                                       packetloss++;
+               packetloss = packetloss * 100 / NETGRAPH_PACKETS;
+               ping = (int)floor(svs.clients[i].ping*1000+0.5);
+               ping = bound(0, ping, 9999);
+               if (sv.protocol == PROTOCOL_QUAKEWORLD)
+               {
+                       // send qw_svc_updateping and qw_svc_updatepl messages
+                       MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
+                       MSG_WriteShort(&host_client->netconnection->message, ping);
+                       MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
+                       MSG_WriteByte(&host_client->netconnection->message, packetloss);
+               }
+               else
+               {
+                       // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
+                       dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
+                       MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
+               }
+       }
+       if (sv.protocol != PROTOCOL_QUAKEWORLD)
+               MSG_WriteString(&host_client->netconnection->message, "\n");
+}
+
+void Host_PingPLReport_f(void)
+{
+       int i;
+       int l = Cmd_Argc();
+       if (l > cl.maxclients)
+               l = cl.maxclients;
+       for (i = 0;i < l;i++)
+       {
+               cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
+               cl.scores[i].qw_packetloss = atoi(Cmd_Argv(1+i*2+1));
+       }
+}
+
 //=============================================================================
 
 /*
@@ -1775,65 +2784,108 @@ Host_InitCommands
 */
 void Host_InitCommands (void)
 {
-       Cmd_AddCommand ("status", Host_Status_f);
-       Cmd_AddCommand ("quit", Host_Quit_f);
+       dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
+
+       Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
+       Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
        if (gamemode == GAME_NEHAHRA)
        {
-               Cmd_AddCommand ("max", Host_God_f);
-               Cmd_AddCommand ("monster", Host_Notarget_f);
-               Cmd_AddCommand ("scrag", Host_Fly_f);
-               Cmd_AddCommand ("wraith", Host_Noclip_f);
-               Cmd_AddCommand ("gimme", Host_Give_f);
+               Cmd_AddCommand_WithClientCommand ("max", NULL, Host_God_f, "god mode (invulnerability)");
+               Cmd_AddCommand_WithClientCommand ("monster", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
+               Cmd_AddCommand_WithClientCommand ("scrag", NULL, Host_Fly_f, "fly mode (flight)");
+               Cmd_AddCommand_WithClientCommand ("wraith", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
+               Cmd_AddCommand_WithClientCommand ("gimme", NULL, Host_Give_f, "alter inventory");
        }
        else
        {
-               Cmd_AddCommand ("god", Host_God_f);
-               Cmd_AddCommand ("notarget", Host_Notarget_f);
-               Cmd_AddCommand ("fly", Host_Fly_f);
-               Cmd_AddCommand ("noclip", Host_Noclip_f);
-               Cmd_AddCommand ("give", Host_Give_f);
-       }
-       Cmd_AddCommand ("map", Host_Map_f);
-       Cmd_AddCommand ("restart", Host_Restart_f);
-       Cmd_AddCommand ("changelevel", Host_Changelevel_f);
-       Cmd_AddCommand ("connect", Host_Connect_f);
-       Cmd_AddCommand ("reconnect", Host_Reconnect_f);
-       Cmd_AddCommand ("version", Host_Version_f);
-       Cmd_AddCommand ("say", Host_Say_f);
-       Cmd_AddCommand ("say_team", Host_Say_Team_f);
-       Cmd_AddCommand ("tell", Host_Tell_f);
-       Cmd_AddCommand ("kill", Host_Kill_f);
-       Cmd_AddCommand ("pause", Host_Pause_f);
-       Cmd_AddCommand ("kick", Host_Kick_f);
-       Cmd_AddCommand ("ping", Host_Ping_f);
-       Cmd_AddCommand ("load", Host_Loadgame_f);
-       Cmd_AddCommand ("save", Host_Savegame_f);
-
-       Cmd_AddCommand ("startdemos", Host_Startdemos_f);
-       Cmd_AddCommand ("demos", Host_Demos_f);
-       Cmd_AddCommand ("stopdemo", Host_Stopdemo_f);
-
-       Cmd_AddCommand ("viewmodel", Host_Viewmodel_f);
-       Cmd_AddCommand ("viewframe", Host_Viewframe_f);
-       Cmd_AddCommand ("viewnext", Host_Viewnext_f);
-       Cmd_AddCommand ("viewprev", Host_Viewprev_f);
+               Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
+               Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
+               Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
+               Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
+               Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
+       }
+       Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
+       Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
+       Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
+       Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
+       Cmd_AddCommand ("reconnect", Host_Reconnect_f, "reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server)");
+       Cmd_AddCommand ("version", Host_Version_f, "print engine version");
+       Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
+       Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
+       Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
+       Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
+       Cmd_AddCommand_WithClientCommand ("pause", NULL, Host_Pause_f, "pause the game (if the server allows pausing)");
+       Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
+       Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
+       Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
+       Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
+
+       Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
+       Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
+       Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
+
+       Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
+       Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
+       Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
+       Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
 
        Cvar_RegisterVariable (&cl_name);
-       Cmd_AddCommand ("name", Host_Name_f);
+       Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
        Cvar_RegisterVariable (&cl_color);
-       Cmd_AddCommand ("color", Host_Color_f);
+       Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
        Cvar_RegisterVariable (&cl_rate);
-       Cmd_AddCommand ("rate", Host_Rate_f);
+       Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
        if (gamemode == GAME_NEHAHRA)
        {
                Cvar_RegisterVariable (&cl_pmodel);
-               Cmd_AddCommand ("pmodel", Host_PModel_f);
-       }
-       Cmd_AddCommand ("prespawn", Host_PreSpawn_f);
-       Cmd_AddCommand ("spawn", Host_Spawn_f);
-       Cmd_AddCommand ("begin", Host_Begin_f);
-       Cmd_AddCommand ("maxplayers", MaxPlayers_f);
+               Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "change your player model choice (Nehahra specific)");
+       }
+
+       // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
+       Cvar_RegisterVariable (&cl_playermodel);
+       Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
+       Cvar_RegisterVariable (&cl_playerskin);
+       Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
+
+       Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
+       Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
+       Cmd_AddCommand_WithClientCommand ("begin", NULL, Host_Begin_f, "signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)");
+       Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
+
+       Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
+
+       Cvar_RegisterVariable (&rcon_password);
+       Cvar_RegisterVariable (&rcon_address);
+       Cvar_RegisterVariable (&rcon_secure);
+       Cvar_RegisterVariable (&rcon_secure_challengetimeout);
+       Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP");
+       Cmd_AddCommand ("srcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP");
+       Cmd_AddCommand ("pqrcon", Host_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)");
+       Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
+       Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
+       Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
+       Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
+       Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
+       Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
+       Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
+       Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
+
+       Cmd_AddCommand_WithClientCommand ("pings", NULL, Host_Pings_f, "command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)");
+       Cmd_AddCommand ("pingplreport", Host_PingPLReport_f, "command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers)");
+
+       Cmd_AddCommand ("fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)");
+       Cvar_RegisterVariable (&r_fixtrans_auto);
+
+       Cvar_RegisterVariable (&team);
+       Cvar_RegisterVariable (&skin);
+       Cvar_RegisterVariable (&noaim);
 
        Cvar_RegisterVariable(&sv_cheats);
+       Cvar_RegisterVariable(&sv_adminnick);
+       Cvar_RegisterVariable(&sv_status_privacy);
+       Cvar_RegisterVariable(&sv_status_show_qcstatus);
 }
 
+void Host_NoOperation_f(void)
+{
+}