]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - sv_ccmds.c
protocol/dp8: Implement parting messages
[xonotic/darkplaces.git] / sv_ccmds.c
index c4aad6fc2457312d06fb4963ccdaa6b6e377fc4a..036f4c63f1588cae58155a02479a9ec77f9ae1b7 100644 (file)
@@ -24,11 +24,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "sv_demo.h"
 
 int current_skill;
-cvar_t sv_cheats = {CVAR_SERVER | CVAR_NOTIFY, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
-cvar_t sv_adminnick = {CVAR_SERVER | CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
-cvar_t sv_status_privacy = {CVAR_SERVER | CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
-cvar_t sv_status_show_qcstatus = {CVAR_SERVER | 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 sv_namechangetimer = {CVAR_SERVER | CVAR_SAVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"};
+cvar_t sv_cheats = {CF_SERVER | CF_NOTIFY, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
+cvar_t sv_adminnick = {CF_SERVER | CF_ARCHIVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
+cvar_t sv_status_privacy = {CF_SERVER | CF_ARCHIVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
+cvar_t sv_status_show_qcstatus = {CF_SERVER | CF_ARCHIVE, "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 sv_namechangetimer = {CF_SERVER | CF_ARCHIVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"};
 
 /*
 ===============================================================================
@@ -61,9 +61,9 @@ static void SV_Map_f(cmd_state_t *cmd)
        if (gamemode == GAME_DELUXEQUAKE)
                Cvar_Set(&cvars_all, "warpmark", "");
 
-       cls.demonum = -1;               // stop demo loop in case this fails
+       if(host.hook.Disconnect)
+               host.hook.Disconnect(false, NULL);
 
-       CL_Disconnect ();
        SV_Shutdown();
 
        if(svs.maxclients != svs.maxclients_next)
@@ -74,18 +74,15 @@ static void SV_Map_f(cmd_state_t *cmd)
                svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
        }
 
-#ifdef CONFIG_MENU
-       // remove menu
-       if (key_dest == key_menu || key_dest == key_menu_grabbed)
-               MR_ToggleMenu(0);
-#endif
-       key_dest = key_game;
+       if(host.hook.ToggleMenu)
+               host.hook.ToggleMenu();
 
        svs.serverflags = 0;                    // haven't completed an episode yet
        strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
        SV_SpawnServer(level);
-       if (sv.active && cls.state == ca_disconnected)
-               CL_EstablishConnection("local:1", -2);
+
+       if(sv.active && host.hook.ConnectLocal != NULL)
+               host.hook.ConnectLocal();
 }
 
 /*
@@ -111,18 +108,15 @@ static void SV_Changelevel_f(cmd_state_t *cmd)
                return;
        }
 
-#ifdef CONFIG_MENU
-       // remove menu
-       if (key_dest == key_menu || key_dest == key_menu_grabbed)
-               MR_ToggleMenu(0);
-#endif
-       key_dest = key_game;
+       if(host.hook.ToggleMenu)
+               host.hook.ToggleMenu();
 
        SV_SaveSpawnparms ();
        strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
        SV_SpawnServer(level);
-       if (sv.active && cls.state == ca_disconnected)
-               CL_EstablishConnection("local:1", -2);
+       
+       if(sv.active && host.hook.ConnectLocal != NULL)
+               host.hook.ConnectLocal();
 }
 
 /*
@@ -147,17 +141,14 @@ static void SV_Restart_f(cmd_state_t *cmd)
                return;
        }
 
-#ifdef CONFIG_MENU
-       // remove menu
-       if (key_dest == key_menu || key_dest == key_menu_grabbed)
-               MR_ToggleMenu(0);
-#endif
-       key_dest = key_game;
+       if(host.hook.ToggleMenu)
+               host.hook.ToggleMenu();
 
        strlcpy(mapname, sv.name, sizeof(mapname));
        SV_SpawnServer(mapname);
-       if (sv.active && cls.state == ca_disconnected)
-               CL_EstablishConnection("local:1", -2);
+       
+       if(sv.active && host.hook.ConnectLocal != NULL)
+               host.hook.ConnectLocal();
 }
 
 //===========================================================================
@@ -205,7 +196,7 @@ static void SV_God_f(cmd_state_t *cmd)
                SV_ClientPrint("godmode ON\n");
 }
 
-qboolean noclip_anglehack;
+qbool noclip_anglehack;
 
 static void SV_Noclip_f(cmd_state_t *cmd)
 {
@@ -408,33 +399,19 @@ SV_Pause_f
 static void SV_Pause_f(cmd_state_t *cmd)
 {
        void (*print) (const char *fmt, ...);
-       if (cmd->source == src_command)
-       {
-               // if running a client, try to send over network so the pause is handled by the server
-               if (cls.state == ca_connected)
-               {
-                       CL_ForwardToServer_f(cmd);
-                       return;
-               }
+       if (cmd->source == src_local)
                print = Con_Printf;
-       }
        else
                print = SV_ClientPrintf;
 
-       if (!pausable.integer)
+       if (!pausable.integer && cmd->source == src_client && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
        {
-               if (cmd->source == src_client)
-               {
-                       if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
-                       {
-                               print("Pause not allowed.\n");
-                               return;
-                       }
-               }
+               print("Pause not allowed.\n");
+               return;
        }
        
        sv.paused ^= 1;
-       if (cmd->source != src_command)
+       if (cmd->source != src_local)
                SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
        else if(*(sv_adminnick.string))
                SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
@@ -445,7 +422,7 @@ static void SV_Pause_f(cmd_state_t *cmd)
        MSG_WriteByte(&sv.reliable_datagram, sv.paused);
 }
 
-static void SV_Say(cmd_state_t *cmd, qboolean teamonly)
+static void SV_Say(cmd_state_t *cmd, qbool teamonly)
 {
        prvm_prog_t *prog = SVVM_prog;
        client_t *save;
@@ -454,20 +431,12 @@ static void SV_Say(cmd_state_t *cmd, qboolean teamonly)
        char *p2;
        // LadyHavoc: long say messages
        char text[1024];
-       qboolean fromServer = false;
+       qbool fromServer = false;
 
-       if (cmd->source == src_command)
+       if (cmd->source == src_local)
        {
-               if (cls.state == ca_dedicated)
-               {
-                       fromServer = true;
-                       teamonly = false;
-               }
-               else
-               {
-                       CL_ForwardToServer_f(cmd);
-                       return;
-               }
+               fromServer = true;
+               teamonly = false;
        }
 
        if (Cmd_Argc (cmd) < 2)
@@ -509,7 +478,7 @@ static void SV_Say(cmd_state_t *cmd, qboolean teamonly)
                        SV_ClientPrint(text);
        host_client = save;
 
-       if (cls.state == ca_dedicated)
+       if(!host_isclient.integer)
                Con_Print(&text[1]);
 }
 
@@ -532,18 +501,10 @@ static void SV_Tell_f(cmd_state_t *cmd)
        int j;
        const char *p1, *p2;
        char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
-       qboolean fromServer = false;
+       qbool fromServer = false;
 
-       if (cmd->source == src_command)
-       {
-               if (cls.state == ca_dedicated)
-                       fromServer = true;
-               else
-               {
-                       CL_ForwardToServer_f(cmd);
-                       return;
-               }
-       }
+       if (cmd->source == src_local)
+               fromServer = true;
 
        if (Cmd_Argc (cmd) < 2)
                return;
@@ -663,16 +624,8 @@ static void SV_Ping_f(cmd_state_t *cmd)
        client_t *client;
        void (*print) (const char *fmt, ...);
 
-       if (cmd->source == src_command)
-       {
-               // 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)
-               {
-                       CL_ForwardToServer_f(cmd);
-                       return;
-               }
+       if (cmd->source == src_local)
                print = Con_Printf;
-       }
        else
                print = SV_ClientPrintf;
 
@@ -747,68 +700,6 @@ static void SV_Pings_f(cmd_state_t *cmd)
                MSG_WriteString(&host_client->netconnection->message, "\n");
 }
 
-/*
-====================
-SV_User_f
-
-user <name or userid>
-
-Dump userdata / masterdata for a user
-====================
-*/
-static void SV_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
-{
-       int             uid;
-       int             i;
-
-       if (Cmd_Argc(cmd) != 2)
-       {
-               Con_Printf ("Usage: user <username / userid>\n");
-               return;
-       }
-
-       uid = atoi(Cmd_Argv(cmd, 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(cmd, 1)))
-               {
-                       InfoString_Print(cl.scores[i].qw_userinfo);
-                       return;
-               }
-       }
-       Con_Printf ("User not in server.\n");
-}
-
-/*
-====================
-SV_Users_f
-
-Dump userids for all current players
-====================
-*/
-static void SV_Users_f(cmd_state_t *cmd) // 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);
-}
-
 /*
 ==================
 SV_Status_f
@@ -825,7 +716,7 @@ static void SV_Status_f(cmd_state_t *cmd)
        int frags;
        char vabuf[1024];
 
-       if (cmd->source == src_command)
+       if (cmd->source == src_local)
                print = Con_Printf;
        else
                print = SV_ClientPrintf;
@@ -845,11 +736,11 @@ static void SV_Status_f(cmd_state_t *cmd)
        for (players = 0, i = 0;i < svs.maxclients;i++)
                if (svs.clients[i].active)
                        players++;
-       print ("host:     %s\n", Cvar_VariableString (&cvars_all, "hostname", CVAR_SERVER));
+       print ("host:     %s\n", Cvar_VariableString (&cvars_all, "hostname", CF_SERVER));
        print ("version:  %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
        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(vabuf, sizeof(vabuf)));
+       print ("timing:   %s\n", SV_TimingReport(vabuf, sizeof(vabuf)));
        print ("players:  %i active (%i max)\n\n", players, svs.maxclients);
 
        if (in == 1)
@@ -887,7 +778,7 @@ static void SV_Status_f(cmd_state_t *cmd)
                        ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
                }
 
-               if(sv_status_privacy.integer && cmd->source != src_command && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
+               if(sv_status_privacy.integer && cmd->source != src_local && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
                        strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
                else
                        strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
@@ -938,6 +829,23 @@ static void SV_Status_f(cmd_state_t *cmd)
        }
 }
 
+void SV_Name(int clientnum)
+{
+       prvm_prog_t *prog = SVVM_prog;
+       PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
+       if (strcmp(host_client->old_name, host_client->name))
+       {
+               if (host_client->begun)
+                       SV_BroadcastPrintf("\003%s ^7changed name to ^3%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, clientnum);
+               MSG_WriteString (&sv.reliable_datagram, host_client->name);
+               SV_WriteNetnameIntoDemo(host_client);
+       }       
+}
+
 /*
 ======================
 SV_Name_f
@@ -945,9 +853,8 @@ SV_Name_f
 */
 static void SV_Name_f(cmd_state_t *cmd)
 {
-       prvm_prog_t *prog = SVVM_prog;
        int i, j;
-       qboolean valid_colors;
+       qbool valid_colors;
        const char *newNameSource;
        char newName[sizeof(host_client->name)];
 
@@ -961,7 +868,7 @@ static void SV_Name_f(cmd_state_t *cmd)
 
        strlcpy(newName, newNameSource, sizeof(newName));
 
-       if (cmd->source == src_command)
+       if (cmd->source == src_local)
                return;
 
        if (host.realtime < host_client->nametime && strcmp(newName, host_client->name))
@@ -1040,18 +947,7 @@ static void SV_Name_f(cmd_state_t *cmd)
        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);
 
-       PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
-       if (strcmp(host_client->old_name, host_client->name))
-       {
-               if (host_client->begun)
-                       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);
-       }
+       SV_Name(host_client - svs.clients);
 }
 
 static void SV_Rate_f(cmd_state_t *cmd)
@@ -1060,7 +956,7 @@ static void SV_Rate_f(cmd_state_t *cmd)
 
        rate = atoi(Cmd_Argv(cmd, 1));
 
-       if (cmd->source == src_command)
+       if (cmd->source == src_local)
                return;
 
        host_client->rate = rate;
@@ -1130,9 +1026,10 @@ static void SV_Kick_f(cmd_state_t *cmd)
 {
        const char *who;
        const char *message = NULL;
+       char reason[512];
        client_t *save;
        int i;
-       qboolean byNumber = false;
+       qbool byNumber = false;
 
        if (!sv.active)
                return;
@@ -1159,9 +1056,9 @@ static void SV_Kick_f(cmd_state_t *cmd)
 
        if (i < svs.maxclients)
        {
-               if (cmd->source == src_command)
+               if (cmd->source == src_local)
                {
-                       if (cls.state == ca_dedicated)
+                       if(!host_isclient.integer)
                                who = "Console";
                        else
                                who = cl_name.string;
@@ -1188,10 +1085,11 @@ static void SV_Kick_f(cmd_state_t *cmd)
                                message++;
                }
                if (message)
-                       SV_ClientPrintf("Kicked by %s: %s\n", who, message);
+                       SV_DropClient (false, va(reason, sizeof(reason), "Kicked by %s: %s", who, message)); // kicked
+                       //SV_ClientPrintf("Kicked by %s: %s\n", who, message);
                else
-                       SV_ClientPrintf("Kicked by %s\n", who);
-               SV_DropClient (false); // kicked
+                       //SV_ClientPrintf("Kicked by %s\n", who);
+                       SV_DropClient (false, va(reason, sizeof(reason), "Kicked by %s", who)); // kicked
        }
 
        host_client = save;
@@ -1370,7 +1268,7 @@ static void SV_Viewmodel_f(cmd_state_t *cmd)
 {
        prvm_prog_t *prog = SVVM_prog;
        prvm_edict_t    *e;
-       dp_model_t      *m;
+       model_t *m;
 
        if (!sv.active)
                return;
@@ -1399,7 +1297,7 @@ static void SV_Viewframe_f(cmd_state_t *cmd)
        prvm_prog_t *prog = SVVM_prog;
        prvm_edict_t    *e;
        int             f;
-       dp_model_t      *m;
+       model_t *m;
 
        if (!sv.active)
                return;
@@ -1417,7 +1315,7 @@ static void SV_Viewframe_f(cmd_state_t *cmd)
        }
 }
 
-static void PrintFrameName (dp_model_t *m, int frame)
+static void PrintFrameName (model_t *m, int frame)
 {
        if (m->animscenes)
                Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
@@ -1434,7 +1332,7 @@ static void SV_Viewnext_f(cmd_state_t *cmd)
 {
        prvm_prog_t *prog = SVVM_prog;
        prvm_edict_t    *e;
-       dp_model_t      *m;
+       model_t *m;
 
        if (!sv.active)
                return;
@@ -1461,7 +1359,7 @@ static void SV_Viewprev_f(cmd_state_t *cmd)
 {
        prvm_prog_t *prog = SVVM_prog;
        prvm_edict_t    *e;
-       dp_model_t      *m;
+       model_t *m;
 
        if (!sv.active)
                return;
@@ -1494,7 +1392,7 @@ static void SV_SendCvar_f(cmd_state_t *cmd)
        cvarname = Cmd_Argv(cmd, 1);
 
        old = host_client;
-       if (cls.state != ca_dedicated)
+       if(host_isclient.integer)
                i = 1;
        else
                i = 0;
@@ -1510,42 +1408,73 @@ static void SV_SendCvar_f(cmd_state_t *cmd)
 static void SV_Ent_Create_f(cmd_state_t *cmd)
 {
        prvm_prog_t *prog = SVVM_prog;
-       ddef_t *key;
+       prvm_edict_t *ed;
+       mdef_t *key;
        int i;
-       qboolean expectval = false, haveorigin = false;
+       qbool haveorigin;
+
        void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
 
-       prvm_edict_t *ed = PRVM_ED_Alloc(SVVM_prog);
+       if(!Cmd_Argc(cmd))
+               return;
+
+       ed = PRVM_ED_Alloc(SVVM_prog);
 
        PRVM_ED_ParseEpair(prog, ed, PRVM_ED_FindField(prog, "classname"), Cmd_Argv(cmd, 1), false);
 
-       for(i = 2; i < Cmd_Argc(cmd); i++)
+       // Spawn where the player is aiming. We need a view matrix first.
+       if(cmd->source == src_client)
        {
-               if(!expectval)
-               {
-                       if(!(key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, i))))
-                       {
-                               Con_Printf("Key %s not found!\n", Cmd_Argv(cmd, i));
-                               return;
-                       }
+               vec3_t org, temp, dest;
+               matrix4x4_t view;
+               trace_t trace;
+               char buf[128];
 
-                       if(!strcmp(Cmd_Argv(cmd, i), "origin") && !haveorigin)
-                               haveorigin = true;
+               SV_GetEntityMatrix(prog, host_client->edict, &view, true);
 
-                       expectval = true;
-               }
-               else
+               Matrix4x4_OriginFromMatrix(&view, org);
+               VectorSet(temp, 65536, 0, 0);
+               Matrix4x4_Transform(&view, temp, dest);         
+
+               trace = SV_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value);
+
+               dpsnprintf(buf, sizeof(buf), "%g %g %g", trace.endpos[0], trace.endpos[1], trace.endpos[2]);
+               PRVM_ED_ParseEpair(prog, ed, PRVM_ED_FindField(prog, "origin"), buf, false);
+
+               haveorigin = true;
+       }
+       // Or spawn at a specified origin.
+       else
+       {
+               print = Con_Printf;
+               haveorigin = false;
+       }
+
+       // Allow more than one key/value pair by cycling between expecting either one.
+       for(i = 2; i < Cmd_Argc(cmd); i += 2)
+       {
+               if(!(key = PRVM_ED_FindField(prog, Cmd_Argv(cmd, i))))
                {
-                       PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, i), false);
-                       expectval = false;
+                       print("Key %s not found!\n", Cmd_Argv(cmd, i));
+                       PRVM_ED_Free(prog, ed);
+                       return;
                }
+
+               /*
+                * This is mostly for dedicated server console, but if the
+                * player gave a custom origin, we can ignore the traceline.
+                */
+               if(!strcmp(Cmd_Argv(cmd, i), "origin"))
+                       haveorigin = true;
+
+               if (i + 1 < Cmd_Argc(cmd))
+                       PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(cmd, i+1), false);
        }
-       
+
        if(!haveorigin)
        {
                print("Missing origin\n");
-               if(cmd->source == src_client)
-                       print("This should never happen if you're a player. Please report this to a developer.\n");
+               PRVM_ED_Free(prog, ed);
                return;
        }
 
@@ -1557,6 +1486,7 @@ static void SV_Ent_Create_f(cmd_state_t *cmd)
                print("Could not spawn a \"%s\". No such entity or it has no spawn function\n", Cmd_Argv(cmd, 1));
                if(cmd->source == src_client)
                        Con_Printf("%s tried to spawn a \"%s\"\n", host_client->name, Cmd_Argv(cmd, 1));
+               // CallSpawnFunction already freed the edict for us.
                return;
        }
 
@@ -1569,6 +1499,108 @@ static void SV_Ent_Create_f(cmd_state_t *cmd)
                Con_Printf("%s spawned a \"%s\"\n", host_client->name, Cmd_Argv(cmd, 1));
 }
 
+static void SV_Ent_Remove_f(cmd_state_t *cmd)
+{
+       prvm_prog_t *prog = SVVM_prog;
+       prvm_edict_t *ed;
+       int i, ednum = 0;
+       void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
+
+       if(!Cmd_Argc(cmd))
+               return;
+
+       // Allow specifying edict by number
+       if(Cmd_Argc(cmd) > 1 && Cmd_Argv(cmd, 1))
+       {
+               ednum = atoi(Cmd_Argv(cmd, 1));
+               if(!ednum)
+               {
+                       print("Cannot remove the world\n");
+                       return;
+               }
+       }
+       // Or trace a line if it's a client who didn't specify one.
+       else if(cmd->source == src_client)
+       {
+               vec3_t org, temp, dest;
+               matrix4x4_t view;
+               trace_t trace;
+
+               SV_GetEntityMatrix(prog, host_client->edict, &view, true);
+
+               Matrix4x4_OriginFromMatrix(&view, org);
+               VectorSet(temp, 65536, 0, 0);
+               Matrix4x4_Transform(&view, temp, dest);         
+
+               trace = SV_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, 0, 0, collision_extendmovelength.value);
+               
+               if(trace.ent)
+                       ednum = (int)PRVM_EDICT_TO_PROG(trace.ent);
+               if(!trace.ent || !ednum)
+                       // Don't remove the world, but don't annoy players with a print if they miss
+                       return;
+       }
+       else
+       {
+               // Only a dedicated server console should be able to reach this.
+               print("No edict given\n");
+               return;
+       }
+
+       ed = PRVM_EDICT_NUM(ednum);
+
+       if(ed)
+       {
+               // Skip players
+               for (i = 0; i < svs.maxclients; i++)
+               {
+                       if(ed == svs.clients[i].edict)
+                               return;
+               }
+
+               if(!ed->free)
+               {
+                       print("Removed a \"%s\"\n", PRVM_GetString(prog, PRVM_serveredictstring(ed, classname)));
+                       PRVM_ED_ClearEdict(prog, ed);
+                       PRVM_ED_Free(prog, ed);
+               }
+       }
+       else
+       {
+               // This should only be reachable if an invalid edict number was given
+               print("No such entity\n");
+               return;
+       }
+}
+
+static void SV_Ent_Remove_All_f(cmd_state_t *cmd)
+{
+       prvm_prog_t *prog = SVVM_prog;
+       int i, rmcount;
+       prvm_edict_t *ed;
+       void (*print)(const char *, ...) = (cmd->source == src_client ? SV_ClientPrintf : Con_Printf);
+
+       for (i = 0, rmcount = 0, ed = PRVM_EDICT_NUM(i); i < prog->num_edicts; i++, ed = PRVM_NEXT_EDICT(ed))
+       {
+               if(!ed->free && !strcmp(PRVM_GetString(prog, PRVM_serveredictstring(ed, classname)), Cmd_Argv(cmd, 1)))
+               {
+                       if(!i)
+                       {
+                               print("Cannot remove the world\n");
+                               return;
+                       }
+                       PRVM_ED_ClearEdict(prog, ed);
+                       PRVM_ED_Free(prog, ed);
+                       rmcount++;
+               }
+       }
+
+       if(!rmcount)
+               print("No \"%s\" found\n", Cmd_Argv(cmd, 1));
+       else
+               print("Removed %i of \"%s\"\n", rmcount, Cmd_Argv(cmd, 1));
+}
+
 void SV_InitOperatorCommands(void)
 {
        Cvar_RegisterVariable(&sv_cheats);
@@ -1578,47 +1610,47 @@ void SV_InitOperatorCommands(void)
        Cvar_RegisterVariable(&sv_status_show_qcstatus);
        Cvar_RegisterVariable(&sv_namechangetimer);
        
-       Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
-       Cmd_AddCommand(CMD_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
-       Cmd_AddCommand(CMD_SHARED, "restart", SV_Restart_f, "restart current level");
-       Cmd_AddCommand(CMD_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
-       Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
-       Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
-       Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
-       Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
-       Cmd_AddCommand(CMD_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
-       Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
-       Cmd_AddCommand(CMD_SHARED, "load", SV_Loadgame_f, "load a saved game file");
-       Cmd_AddCommand(CMD_SHARED, "save", SV_Savegame_f, "save the game to a file");
-       Cmd_AddCommand(CMD_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
-       Cmd_AddCommand(CMD_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
-       Cmd_AddCommand(CMD_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
-       Cmd_AddCommand(CMD_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
-       Cmd_AddCommand(CMD_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
-       Cmd_AddCommand(CMD_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
-       Cmd_AddCommand(CMD_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
-       Cmd_AddCommand(CMD_SERVER, "sendcvar", SV_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
-
-       // commands that do not have automatic forwarding from cmd_client, these are internal details of the network protocol and not of interest to users (if they know what they are doing they can still use a generic "cmd prespawn" or similar)
-       Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
-       Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "spawn", SV_Spawn_f, "internal use - signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
-       Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "begin", SV_Begin_f, "internal use - 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(CMD_SERVER_FROM_CLIENT, "pings", SV_Pings_f, "internal use - 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(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
-       Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
-       Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
-       Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
-       Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
-       Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
+       Cmd_AddCommand(CF_SERVER | CF_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
+       Cmd_AddCommand(CF_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
+       Cmd_AddCommand(CF_SHARED, "restart", SV_Restart_f, "restart current level");
+       Cmd_AddCommand(CF_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
+       Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
+       Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
+       Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
+       Cmd_AddCommand(CF_SERVER | CF_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
+       Cmd_AddCommand(CF_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
+       Cmd_AddCommand(CF_SHARED | CF_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
+       Cmd_AddCommand(CF_SHARED, "load", SV_Loadgame_f, "load a saved game file");
+       Cmd_AddCommand(CF_SHARED, "save", SV_Savegame_f, "save the game to a file");
+       Cmd_AddCommand(CF_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
+       Cmd_AddCommand(CF_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
+       Cmd_AddCommand(CF_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
+       Cmd_AddCommand(CF_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
+       Cmd_AddCommand(CF_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
+       host.hook.SV_SendCvar = SV_SendCvar_f;
+
+       // commands that do not have automatic forwarding from cmd_local, these are internal details of the network protocol and not of interest to users (if they know what they are doing they can still use a generic "cmd prespawn" or similar)
+       Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
+       Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "spawn", SV_Spawn_f, "internal use - signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
+       Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "begin", SV_Begin_f, "internal use - 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(CF_SERVER_FROM_CLIENT, "pings", SV_Pings_f, "internal use - 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(CF_CHEAT | CF_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
+       Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
+       Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
+       Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
+       Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
+       Cmd_AddCommand(CF_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
        
-       Cmd_AddCommand(CMD_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
-       Cmd_AddCommand(CMD_USERINFO, "name", SV_Name_f, "change your player name");
-       Cmd_AddCommand(CMD_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
-       Cmd_AddCommand(CMD_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");
-       Cmd_AddCommand(CMD_USERINFO, "pmodel", SV_PModel_f, "(Nehahra-only) change your player model choice");
-       Cmd_AddCommand(CMD_USERINFO, "playermodel", SV_Playermodel_f, "change your player model");
-       Cmd_AddCommand(CMD_USERINFO, "playerskin", SV_Playerskin_f, "change your player skin number");
-
-       Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "ent_create", SV_Ent_Create_f, "Creates an entity at the specified coordinate, of the specified classname. If executed from a server, origin has to be specified manually.");
+       Cmd_AddCommand(CF_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
+       Cmd_AddCommand(CF_USERINFO, "name", SV_Name_f, "change your player name");
+       Cmd_AddCommand(CF_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
+       Cmd_AddCommand(CF_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");
+       Cmd_AddCommand(CF_USERINFO, "pmodel", SV_PModel_f, "(Nehahra-only) change your player model choice");
+       Cmd_AddCommand(CF_USERINFO, "playermodel", SV_Playermodel_f, "change your player model");
+       Cmd_AddCommand(CF_USERINFO, "playerskin", SV_Playerskin_f, "change your player skin number");
+
+       Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "ent_create", SV_Ent_Create_f, "Creates an entity at the specified coordinate, of the specified classname. If executed from a server, origin has to be specified manually.");
+       Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "ent_remove_all", SV_Ent_Remove_All_f, "Removes all entities of the specified classname");
+       Cmd_AddCommand(CF_CHEAT | CF_SERVER_FROM_CLIENT, "ent_remove", SV_Ent_Remove_f, "Removes an entity by number, or the entity you're aiming at");
 }