X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=host_cmd.c;h=5a8db28a0e16f1af1a5a80205d542d6e04380a72;hp=865ecb026b2f02b65337af548e902f93ba1a4e1b;hb=7a96ec58916ceea50684acf4c00eaef551199fef;hpb=b84125ed0d5fc6922a9ea18b7762b489e377175d diff --git a/host_cmd.c b/host_cmd.c index 865ecb02..5a8db28a 100644 --- a/host_cmd.c +++ b/host_cmd.c @@ -19,13 +19,22 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" +#include "sv_demo.h" +#include "image.h" int current_skill; cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"}; -cvar_t rcon_password = {0, "rcon_password", "", "password to authenticate rcon commands"}; +cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"}; +cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands"}; 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; +extern qboolean host_shuttingdown; + /* ================== Host_Quit_f @@ -34,10 +43,12 @@ Host_Quit_f void Host_Quit_f (void) { - Sys_Quit (); + if(host_shuttingdown) + Con_Printf("shutting down already!\n"); + else + Sys_Quit (0); } - /* ================== Host_Status_f @@ -51,7 +62,8 @@ void Host_Status_f (void) 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; @@ -61,6 +73,9 @@ void Host_Status_f (void) else print = SV_ClientPrintf; + if (!sv.active) + return; + for (players = 0, j = 0;j < svs.maxclients;j++) if (svs.clients[j].active) players++; @@ -68,6 +83,7 @@ void Host_Status_f (void) 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); for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++) { @@ -84,7 +100,7 @@ void Host_Status_f (void) } else hours = 0; - print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", j+1, client->name, (int)client->edict->fields.server->frags, hours, minutes, seconds); + print ("#%-3u %-16.16s %3i %2i:%02i:%02i\n", j+1, client->name, client->frags, hours, minutes, seconds); print (" %s\n", client->netconnection ? client->netconnection->address : "botclient"); } } @@ -99,12 +115,6 @@ Sets client to godmode */ void Host_God_f (void) { - if (cmd_source == src_command) - { - Cmd_ForwardToServer (); - return; - } - if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); @@ -120,12 +130,6 @@ void Host_God_f (void) void Host_Notarget_f (void) { - if (cmd_source == src_command) - { - Cmd_ForwardToServer (); - return; - } - if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); @@ -143,12 +147,6 @@ qboolean noclip_anglehack; void Host_Noclip_f (void) { - if (cmd_source == src_command) - { - Cmd_ForwardToServer (); - return; - } - if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); @@ -178,12 +176,6 @@ Sets client to flymode */ void Host_Fly_f (void) { - if (cmd_source == src_command) - { - Cmd_ForwardToServer (); - return; - } - if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); @@ -209,6 +201,7 @@ Host_Ping_f ================== */ +void Host_Pings_f (void); // called by Host_Ping_f void Host_Ping_f (void) { int i; @@ -217,7 +210,8 @@ void Host_Ping_f (void) if (cmd_source == src_command) { - if (!sv.active) + // 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; @@ -227,13 +221,20 @@ void Host_Ping_f (void) else print = SV_ClientPrintf; + if (!sv.active) + return; + print("Client ping times:\n"); for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) { if (!client->active) continue; - print("%4i %s\n", (int)floor(client->ping*1000+0.5), 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(); } /* @@ -263,21 +264,21 @@ 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", ""); cls.demonum = -1; // stop demo loop in case this fails CL_Disconnect (); Host_ShutdownServer(); - // 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"); @@ -299,28 +300,20 @@ void Host_Changelevel_f (void) Con_Print("changelevel : continue game on a new level\n"); return; } - if (cls.demoplayback) - { - Con_Print("Only the server may changelevel\n"); - return; - } // 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 (); 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"); @@ -347,15 +340,12 @@ void Host_Restart_f (void) 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; 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"); @@ -371,22 +361,50 @@ This is sent just before a server changes levels */ void Host_Reconnect_f (void) { - if (cmd_source == src_command) - { - Con_Print("reconnect is not valid from the console\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 (Cmd_Argc() != 1) + // if connected, do something based on protocol + if (cls.protocol == PROTOCOL_QUAKEWORLD) { - Con_Print("reconnect : wait for signon messages again\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"); + } } - if (!cls.signon) + else { - //Con_Print("reconnect: no signon, ignoring reconnect\n"); - return; + // 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 } - cls.signon = 0; // need new connection messages } /* @@ -417,33 +435,6 @@ LOAD / SAVE GAME #define SAVEGAME_VERSION 5 -/* -=============== -Host_SavegameComment - -Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current -=============== -*/ -void Host_SavegameComment (char *text) -{ - int i; - char kills[20]; - - for (i=0 ; ifields.server->deadflag) { - if (i > 0) - { - Con_Print("Can't save multiplayer games.\n"); - return; - } - if (svs.clients[i].edict->fields.server->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) { @@ -511,8 +494,19 @@ void Host_Savegame_f (void) return; } + SV_VM_Begin(); + FS_Printf(f, "%i\n", SAVEGAME_VERSION); - Host_SavegameComment (comment); + + memset(comment, 0, sizeof(comment)); + sprintf(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); + // convert space to _ to make stdio happy + // LordHavoc: convert control characters to _ as well + for (i=0 ; inum_edicts ; i++) PRVM_ED_Write (f, PRVM_EDICT_NUM(i)); @@ -562,20 +554,24 @@ void Host_Loadgame_f (void) int version; float spawn_parms[NUM_SPAWN_PARMS]; - if (cmd_source != src_command) - return; - if (Cmd_Argc() != 2) { Con_Print("load : 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 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL); @@ -586,7 +582,7 @@ void Host_Loadgame_f (void) } // version - COM_ParseToken(&t, false); + COM_ParseToken_Simple(&t, false, false); version = atoi(com_token); if (version != SAVEGAME_VERSION) { @@ -596,27 +592,25 @@ void Host_Loadgame_f (void) } // description - // this is a little hard to parse, as : is a separator in COM_ParseToken, - // so use the console parser instead - COM_ParseTokenConsole(&t); + COM_ParseToken_Simple(&t, false, false); for (i = 0;i < NUM_SPAWN_PARMS;i++) { - COM_ParseToken(&t, false); + COM_ParseToken_Simple(&t, false, false); spawn_parms[i] = atof(com_token); } // skill - COM_ParseToken(&t, false); + COM_ParseToken_Simple(&t, false, false); // this silliness is so we can load 1.06 save files, which have float skill values current_skill = (int)(atof(com_token) + 0.5); Cvar_SetValue ("skill", (float)current_skill); // mapname - COM_ParseToken(&t, false); - strcpy (mapname, com_token); + COM_ParseToken_Simple(&t, false, false); + strlcpy (mapname, com_token, sizeof(mapname)); // time - COM_ParseToken(&t, false); + COM_ParseToken_Simple(&t, false, false); time = atof(com_token); allowcheats = sv_cheats.integer != 0; @@ -637,7 +631,7 @@ void Host_Loadgame_f (void) { // light style oldt = t; - COM_ParseToken(&t, false); + 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 saves 256 lightstyle savegames if (com_token[0] == '{') @@ -655,7 +649,7 @@ void Host_Loadgame_f (void) for(;;) { oldt = t; - COM_ParseToken(&t, false); + COM_ParseToken_Simple(&t, false, false); if (com_token[0] == '{') { t = oldt; @@ -670,10 +664,10 @@ void Host_Loadgame_f (void) for (;;) { start = t; - while (COM_ParseToken(&t, false)) + while (COM_ParseToken_Simple(&t, false, false)) if (!strcmp(com_token, "}")) break; - if (!COM_ParseToken(&start, false)) + if (!COM_ParseToken_Simple(&start, false, false)) { // end of file break; @@ -698,7 +692,6 @@ void Host_Loadgame_f (void) Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS); } while (entnum >= prog->max_edicts) - //SV_IncreaseEdicts(); PRVM_MEM_IncreaseEdicts(); ent = PRVM_EDICT_NUM(entnum); memset (ent->fields.server, 0, prog->progs->entityfields * 4); @@ -712,6 +705,7 @@ void Host_Loadgame_f (void) entnum++; } + Mem_Free(text); prog->num_edicts = entnum; sv.time = time; @@ -722,7 +716,7 @@ void Host_Loadgame_f (void) SV_VM_End(); // 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)) + if (sv.active && cls.state == ca_disconnected) CL_EstablishConnection("local:1"); } @@ -733,10 +727,11 @@ void Host_Loadgame_f (void) Host_Name_f ====================== */ -cvar_t cl_name = {CVAR_SAVE, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"}; +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; char newName[sizeof(host_client->name)]; if (Cmd_Argc () == 1) @@ -750,40 +745,84 @@ void Host_Name_f (void) else strlcpy (newName, Cmd_Args(), sizeof (newName)); - for (i = 0, j = 0;newName[i];i++) - if (newName[i] != '\r' && newName[i] != '\n') - newName[j++] = newName[i]; - newName[j] = 0; - if (cmd_source == src_command) { Cvar_Set ("_cl_name", newName); - InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "name", newName); - if (cls.state == ca_connected) - Cmd_ForwardToServer (); 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; + 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; + + 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_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 changed name to %s\n", host_client->old_name, host_client->name); - strcpy(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); } } @@ -792,7 +831,7 @@ void Host_Name_f (void) Host_Playermodel_f ====================== */ -cvar_t cl_playermodel = {CVAR_SAVE, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz (changed by playermodel command)"}; +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) { @@ -818,29 +857,26 @@ void Host_Playermodel_f (void) if (cmd_source == src_command) { Cvar_Set ("_cl_playermodel", newPath); - InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "playermodel", newPath); - if (cls.state == ca_connected) - Cmd_ForwardToServer (); return; } /* - if (sv.time < host_client->nametime) + if (realtime < host_client->nametime) { SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n"); return; } - host_client->nametime = sv.time + 5; + host_client->nametime = realtime + 5; */ // point the string back at updateclient->name to keep it safe strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel)); - if( eval_playermodel ) - PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_playermodel)->string = PRVM_SetEngineString(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)) { - strcpy(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); @@ -853,7 +889,7 @@ void Host_Playermodel_f (void) Host_Playerskin_f ====================== */ -cvar_t cl_playerskin = {CVAR_SAVE, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz (changed by playerskin command)"}; +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; @@ -878,31 +914,28 @@ void Host_Playerskin_f (void) if (cmd_source == src_command) { Cvar_Set ("_cl_playerskin", newPath); - InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "playerskin", newPath); - if (cls.state == ca_connected) - Cmd_ForwardToServer (); return; } /* - if (sv.time < host_client->nametime) + if (realtime < host_client->nametime) { SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n"); return; } - host_client->nametime = sv.time + 5; + 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( eval_playerskin ) - PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_playerskin)->string = PRVM_SetEngineString(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); - strcpy(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); @@ -945,7 +978,6 @@ void Host_Say(qboolean teamonly) if (!teamplay.integer) teamonly = false; -// turn on color set 1 p1 = Cmd_Args(); quoted = false; if (*p1 == '\"') @@ -953,10 +985,15 @@ void Host_Say(qboolean teamonly) quoted = true; p1++; } - if (!fromServer) - dpsnprintf (text, sizeof(text), "%c%s" STRING_COLOR_DEFAULT_STR ": %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 - dpsnprintf (text, sizeof(text), "%c<%s" STRING_COLOR_DEFAULT_STR "> %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))) { @@ -970,7 +1007,7 @@ void Host_Say(qboolean teamonly) // 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->fields.server->team == save->edict->fields.server->team)) + if (host_client->active && (!teamonly || host_client->edict->fields.server->team == save->edict->fields.server->team)) SV_ClientPrint(text); host_client = save; @@ -993,6 +1030,9 @@ 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; @@ -1010,21 +1050,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); + sprintf (text, "\001%s tells you: ", host_client->name); + else if(*(sv_adminnick.string)) + sprintf (text, "\001<%s tells you> ", sv_adminnick.string); else - sprintf (text, "<%s> ", hostname.string); + sprintf (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--; @@ -1041,15 +1143,16 @@ void Host_Tell_f(void) } while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r')) 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; } @@ -1059,67 +1162,49 @@ void Host_Tell_f(void) Host_Color_f ================== */ -cvar_t cl_color = {CVAR_SAVE, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"}; -void Host_Color_f(void) +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; - int playercolor; - mfunction_t *f; - func_t SV_ChangeTeam; + int top, bottom, playercolor; - if (Cmd_Argc() == 1) - { - Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15); - Con_Print("color <0-15> [0-15]\n"); - return; - } - - if (Cmd_Argc() == 2) - top = bottom = atoi(Cmd_Argv(1)); - else - { - top = atoi(Cmd_Argv(1)); - bottom = atoi(Cmd_Argv(2)); - } + // 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; - // 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; + // 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_SetValue ("_cl_color", playercolor); - InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "topcolor", va("%i", top)); - InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "bottomcolor", va("%i", bottom)); - if (cls.state == ca_connected && cls.protocol != PROTOCOL_QUAKEWORLD) - Cmd_ForwardToServer (); + Cvar_SetValueQuick(&cl_color, playercolor); return; } if (cls.protocol == PROTOCOL_QUAKEWORLD) return; - if (host_client->edict && (f = PRVM_ED_FindFunction ("SV_ChangeTeam")) && (SV_ChangeTeam = (func_t)(f - prog->functions))) + 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 (SV_ChangeTeam, "QC function SV_ChangeTeam is missing"); + PRVM_ExecuteProgram(prog->funcoffsets.SV_ChangeTeam, "QC function SV_ChangeTeam is missing"); } else { prvm_eval_t *val; if (host_client->edict) { - if ((val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_clientcolors))) + if ((val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.clientcolors))) val->_float = playercolor; host_client->edict->fields.server->team = bottom + 1; } @@ -1135,7 +1220,52 @@ void Host_Color_f(void) } } -cvar_t cl_rate = {CVAR_SAVE, "_cl_rate", "10000", "internal storage cvar for current rate (changed by rate command)"}; +void Host_Color_f(void) +{ + int top, bottom; + + if (Cmd_Argc() == 1) + { + Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15); + Con_Print("color <0-15> [0-15]\n"); + return; + } + + if (Cmd_Argc() == 2) + top = bottom = atoi(Cmd_Argv(1)); + else + { + top = atoi(Cmd_Argv(1)); + bottom = atoi(Cmd_Argv(2)); + } + Host_Color(top, bottom); +} + +void Host_TopColor_f(void) +{ + if (Cmd_Argc() == 1) + { + Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15); + Con_Print("topcolor <0-15>\n"); + return; + } + + Host_Color(atoi(Cmd_Argv(1)), -1); +} + +void Host_BottomColor_f(void) +{ + if (Cmd_Argc() == 1) + { + Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15); + Con_Print("bottomcolor <0-15>\n"); + return; + } + + Host_Color(-1, atoi(Cmd_Argv(1))); +} + +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; @@ -1143,7 +1273,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 \n"); return; } @@ -1151,10 +1281,7 @@ void Host_Rate_f(void) if (cmd_source == src_command) { - Cvar_SetValue ("_cl_rate", bound(NET_MINRATE, rate, NET_MAXRATE)); - InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "rate", va("%i", rate)); - if (cls.state == ca_connected) - Cmd_ForwardToServer (); + Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate)); return; } @@ -1168,12 +1295,6 @@ Host_Kill_f */ void Host_Kill_f (void) { - if (cmd_source == src_command) - { - Cmd_ForwardToServer (); - return; - } - if (host_client->edict->fields.server->health <= 0) { SV_ClientPrint("Can't suicide -- already dead!\n"); @@ -1193,12 +1314,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 @@ -1218,7 +1333,7 @@ LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mind 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", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"}; +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; @@ -1241,7 +1356,7 @@ static void Host_PModel_f (void) return; } - if (host_client->edict && (val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_pmodel))) + if (host_client->edict && (val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.pmodel))) val->_float = i; } @@ -1255,12 +1370,6 @@ Host_PreSpawn_f */ void Host_PreSpawn_f (void) { - if (cmd_source == src_command) - { - Con_Print("prespawn is not valid from the console\n"); - return; - } - if (host_client->spawned) { Con_Print("prespawn not valid -- already spawned\n"); @@ -1272,6 +1381,7 @@ void Host_PreSpawn_f (void) 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 } // reset the name change timer because the client will send name soon @@ -1287,16 +1397,8 @@ void Host_Spawn_f (void) { int i; client_t *client; - func_t RestoreGame; - mfunction_t *f; int stats[MAX_CL_STATS]; - if (cmd_source == src_command) - { - Con_Print("spawn is not valid from the console\n"); - return; - } - if (host_client->spawned) { Con_Print("Spawn not valid -- already spawned\n"); @@ -1316,16 +1418,12 @@ void Host_Spawn_f (void) if (sv.loadgame) { // loaded games are fully initialized already - // if this is the last client to be connected, unpause - sv.paused = false; - - if ((f = PRVM_ED_FindFunction ("RestoreGame"))) - if ((RestoreGame = (func_t)(f - prog->functions))) + if (prog->funcoffsets.RestoreGame) { Con_DPrint("Calling RestoreGame\n"); prog->globals.server->time = sv.time; prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict); - PRVM_ExecuteProgram (RestoreGame, "QC function RestoreGame is missing"); + PRVM_ExecuteProgram(prog->funcoffsets.RestoreGame, "QC function RestoreGame is missing"); } } else @@ -1342,8 +1440,8 @@ void Host_Spawn_f (void) 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->connecttime) <= sv.time) - Con_Printf("%s entered the game\n", host_client->name); + if (cls.state == ca_dedicated) + Con_Printf("%s connected\n", host_client->name); PRVM_ExecuteProgram (prog->globals.server->PutClientInServer, "QC function PutClientInServer is missing"); } @@ -1385,29 +1483,39 @@ void Host_Spawn_f (void) // send some stats MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS); - MSG_WriteLong (&host_client->netconnection->message, prog->globals.server->total_secrets); + MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->total_secrets); MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS); - MSG_WriteLong (&host_client->netconnection->message, prog->globals.server->total_monsters); + MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->total_monsters); MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS); - MSG_WriteLong (&host_client->netconnection->message, prog->globals.server->found_secrets); + MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->found_secrets); MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS); - MSG_WriteLong (&host_client->netconnection->message, prog->globals.server->killed_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->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); + 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 (host_client, host_client->edict, &host_client->netconnection->message, stats); @@ -1422,13 +1530,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; } //=========================================================================== @@ -1449,7 +1565,7 @@ void Host_Kick_f (void) int i; qboolean byNumber = false; - if (cmd_source != src_command || !sv.active) + if (!sv.active) return; SV_VM_Begin(); @@ -1457,7 +1573,7 @@ void Host_Kick_f (void) 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; @@ -1492,7 +1608,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 # @@ -1533,12 +1649,6 @@ void Host_Give_f (void) int v; prvm_eval_t *val; - if (cmd_source == src_command) - { - Cmd_ForwardToServer (); - return; - } - if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); @@ -1585,7 +1695,7 @@ void Host_Give_f (void) break; case 's': - if (gamemode == GAME_ROGUE && (val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_ammo_shells1))) + if (gamemode == GAME_ROGUE && (val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_shells1))) val->_float = v; host_client->edict->fields.server->ammo_shells = v; @@ -1593,7 +1703,7 @@ void Host_Give_f (void) case 'n': if (gamemode == GAME_ROGUE) { - if ((val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_ammo_nails1))) + if ((val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_nails1))) { val->_float = v; if (host_client->edict->fields.server->weapon <= IT_LIGHTNING) @@ -1608,7 +1718,7 @@ void Host_Give_f (void) case 'l': if (gamemode == GAME_ROGUE) { - val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_ammo_lava_nails); + val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_lava_nails); if (val) { val->_float = v; @@ -1620,7 +1730,7 @@ void Host_Give_f (void) case 'r': if (gamemode == GAME_ROGUE) { - val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_ammo_rockets1); + val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_rockets1); if (val) { val->_float = v; @@ -1636,7 +1746,7 @@ void Host_Give_f (void) case 'm': if (gamemode == GAME_ROGUE) { - val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_ammo_multi_rockets); + val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_multi_rockets); if (val) { val->_float = v; @@ -1651,7 +1761,7 @@ void Host_Give_f (void) case 'c': if (gamemode == GAME_ROGUE) { - val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_ammo_cells1); + val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_cells1); if (val) { val->_float = v; @@ -1667,7 +1777,7 @@ void Host_Give_f (void) case 'p': if (gamemode == GAME_ROGUE) { - val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_ammo_plasma); + val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_plasma); if (val) { val->_float = v; @@ -1702,7 +1812,7 @@ Host_Viewmodel_f void Host_Viewmodel_f (void) { prvm_edict_t *e; - model_t *m; + dp_model_t *m; if (!sv.active) return; @@ -1733,7 +1843,7 @@ void Host_Viewframe_f (void) { prvm_edict_t *e; int f; - model_t *m; + dp_model_t *m; if (!sv.active) return; @@ -1753,7 +1863,7 @@ void Host_Viewframe_f (void) } -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); @@ -1769,7 +1879,7 @@ Host_Viewnext_f void Host_Viewnext_f (void) { prvm_edict_t *e; - model_t *m; + dp_model_t *m; if (!sv.active) return; @@ -1785,7 +1895,7 @@ void Host_Viewnext_f (void) if (e->fields.server->frame >= m->numframes) e->fields.server->frame = m->numframes - 1; - PrintFrameName (m, e->fields.server->frame); + PrintFrameName (m, (int)e->fields.server->frame); } /* @@ -1796,7 +1906,7 @@ Host_Viewprev_f void Host_Viewprev_f (void) { prvm_edict_t *e; - model_t *m; + dp_model_t *m; if (!sv.active) return; @@ -1813,7 +1923,7 @@ void Host_Viewprev_f (void) if (e->fields.server->frame < 0) e->fields.server->frame = 0; - PrintFrameName (m, e->fields.server->frame); + PrintFrameName (m, (int)e->fields.server->frame); } /* @@ -1834,7 +1944,7 @@ void Host_Startdemos_f (void) { int i, c; - if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-demolooponly")) + if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo")) return; c = Cmd_Argc() - 1; @@ -1843,7 +1953,7 @@ void Host_Startdemos_f (void) Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS); c = MAX_DEMOS; } - Con_Printf("%i demo(s) in loop\n", c); + Con_DPrintf("%i demo(s) in loop\n", c); for (i=1 ; iflags & CVAR_PRIVATE)) + Cmd_ForwardStringToServer(va("sentcvar %s", cvarname)); + else + Cmd_ForwardStringToServer(va("sentcvar %s \"%s\"", c->name, c->string)); return; - if (cls.state != ca_dedicated) - Cmd_ForwardStringToServer(va("sentcvar %s %s\n", c->name, c->string)); - if(!sv.active)// || !SV_ParseClientCommandQC) + } + if(!sv.active)// || !prog->funcoffsets.SV_ParseClientCommand) return; old = host_client; @@ -1918,7 +2037,7 @@ void Host_SendCvar_f (void) if(svs.clients[i].active && svs.clients[i].netconnection) { host_client = &svs.clients[i]; - Host_ClientCommands(va("sendcvar %s\n", c->name)); + Host_ClientCommands(va("sendcvar %s\n", cvarname)); } host_client = old; } @@ -1957,20 +2076,6 @@ static void MaxPlayers_f(void) // QuakeWorld commands -char emodel_name[] = - { 'e' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 }; -char pmodel_name[] = - { 'p' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 }; -char prespawn_name[] = - { 'p'^0xff, 'r'^0xff, 'e'^0xff, 's'^0xff, 'p'^0xff, 'a'^0xff, 'w'^0xff, 'n'^0xff, - ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '0'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 }; -char modellist_name[] = - { 'm'^0xff, 'o'^0xff, 'd'^0xff, 'e'^0xff, 'l'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff, - ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 }; -char soundlist_name[] = - { 's'^0xff, 'o'^0xff, 'u'^0xff, 'n'^0xff, 'd'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff, - ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 }; - /* ===================== Host_Rcon_f @@ -2004,7 +2109,7 @@ void Host_Rcon_f (void) // credit: taken from QuakeWorld to = cls.netcon->peeraddress; else { - if (!rcon_address.integer || !rcon_address.string[0]) + if (!rcon_address.string[0]) { Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); return; @@ -2045,9 +2150,9 @@ void Host_User_f (void) // credit: taken from QuakeWorld { if (!cl.scores[i].name[0]) continue; - if (cl.scores[i].userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1))) + if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1))) { - InfoString_Print(cl.scores[i].userinfo); + InfoString_Print(cl.scores[i].qw_userinfo); return; } } @@ -2073,7 +2178,7 @@ void Host_Users_f (void) // credit: taken from QuakeWorld { if (cl.scores[i].name[0]) { - Con_Printf ("%6i %4i %s\n", cl.scores[i].userid, cl.scores[i].frags, cl.scores[i].name); + Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name); c++; } } @@ -2091,13 +2196,16 @@ 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 \n"); return; } - strlcpy (cl.serverinfo, Cmd_Argv(1), sizeof(cl.serverinfo)); + 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); } /* @@ -2146,16 +2254,7 @@ void Host_FullInfo_f (void) // credit: taken from QuakeWorld if (*s) s++; - if (!strcasecmp(key, pmodel_name) || !strcasecmp(key, emodel_name)) - continue; - - if (key[0] == '*') - { - Con_Printf("Can't set star-key \"%s\" to \"%s\"\n", key, value); - continue; - } - - InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), key, value); + CL_SetInfo(key, value, false, false, false, false); } } @@ -2178,16 +2277,7 @@ void Host_SetInfo_f (void) // credit: taken from QuakeWorld Con_Printf ("usage: setinfo [ ]\n"); return; } - if (!strcasecmp(Cmd_Argv(1), pmodel_name) || !strcasecmp(Cmd_Argv(1), emodel_name)) - return; - if (Cmd_Argv(1)[0] == '*') - { - Con_Printf("Can't set star-key \"%s\" to \"%s\"\n", Cmd_Argv(1), Cmd_Argv(2)); - return; - } - InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), Cmd_Argv(1), Cmd_Argv(2)); - if (cls.state == ca_connected) - Cmd_ForwardToServer (); + CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false); } /* @@ -2224,7 +2314,7 @@ void Host_Packet_f (void) // credit: taken from QuakeWorld out = send+4; send[0] = send[1] = send[2] = send[3] = 0xff; - l = strlen (in); + l = (int)strlen (in); for (i=0 ; i= send + sizeof(send) - 1) @@ -2234,14 +2324,97 @@ void Host_Packet_f (void) // credit: taken from QuakeWorld *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]; } - *out = 0; mysocket = NetConn_ChooseClientSocketForAddress(&address); + if (!mysocket) + mysocket = NetConn_ChooseServerSocketForAddress(&address); if (mysocket) - NetConn_WriteString(mysocket, send, &address); + 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_unreliablesize[j] == 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)); + } } //============================================================================= @@ -2253,39 +2426,39 @@ Host_InitCommands */ void Host_InitCommands (void) { - strcpy(cls.userinfo, "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\*ver\\dp"); + dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp"); - Cmd_AddCommand ("status", Host_Status_f, "print server status information"); + 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, "god mode (invulnerability)"); - Cmd_AddCommand ("monster", Host_Notarget_f, "notarget mode (monsters do not see you)"); - Cmd_AddCommand ("scrag", Host_Fly_f, "fly mode (flight)"); - Cmd_AddCommand ("wraith", Host_Noclip_f, "noclip mode (flight without collisions, move through walls)"); - Cmd_AddCommand ("gimme", Host_Give_f, "alter inventory"); + 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, "god mode (invulnerability)"); - Cmd_AddCommand ("notarget", Host_Notarget_f, "notarget mode (monsters do not see you)"); - Cmd_AddCommand ("fly", Host_Fly_f, "fly mode (flight)"); - Cmd_AddCommand ("noclip", Host_Noclip_f, "noclip mode (flight without collisions, move through walls)"); - Cmd_AddCommand ("give", Host_Give_f, "alter inventory"); + 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, "reset signon level in preparation for a new level (do not use)"); + 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 ("say", Host_Say_f, "send a chat message to everyone on the server"); - Cmd_AddCommand ("say_team", Host_Say_Team_f, "send a chat message to your team on the server"); - Cmd_AddCommand ("tell", Host_Tell_f, "send a chat message to only one person on the server"); - Cmd_AddCommand ("kill", Host_Kill_f, "die instantly"); - Cmd_AddCommand ("pause", Host_Pause_f, "pause the game (if the server allows pausing)"); + 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 ("ping", Host_Ping_f, "print ping times of all players on the server"); + 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"); @@ -2299,29 +2472,29 @@ void Host_InitCommands (void) 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, "change your player name"); + Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name"); Cvar_RegisterVariable (&cl_color); - Cmd_AddCommand ("color", Host_Color_f, "change your player shirt and pants colors"); + 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, "change your network connection speed"); + 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, "change your player model choice (Nehahra specific)"); + 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 ("playermodel", Host_Playermodel_f, "change your player model"); + Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model"); Cvar_RegisterVariable (&cl_playerskin); - Cmd_AddCommand ("playerskin", Host_Playerskin_f, "change your player skin number"); + Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number"); - Cmd_AddCommand ("prespawn", Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)"); - Cmd_AddCommand ("spawn", Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)"); - Cmd_AddCommand ("begin", 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_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"); // By [515] + 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); @@ -2332,7 +2505,23 @@ void Host_InitCommands (void) 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); } +void Host_NoOperation_f(void) +{ +}