X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=cl_cmd.c;h=4f844a821f25981c9f86d9053885871e20713f6f;hp=8378e46d32a433912ef0df18c5369520f6bac15d;hb=3b9f725654833ed39d7329fe1446842424768bdc;hpb=037b6f399743cea2d02a1046cfcec9ca4294c265 diff --git a/cl_cmd.c b/cl_cmd.c index 8378e46d..4f844a82 100644 --- a/cl_cmd.c +++ b/cl_cmd.c @@ -23,15 +23,24 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // for secure rcon authentication #include "hmac.h" #include "mdfour.h" +#include "image.h" #include -cvar_t rcon_password = {CVAR_CLIENT | CVAR_SERVER | CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"}; -cvar_t rcon_secure = {CVAR_CLIENT | CVAR_SERVER, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"}; -cvar_t rcon_secure_challengetimeout = {CVAR_CLIENT, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"}; -cvar_t rcon_address = {CVAR_CLIENT, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"}; -cvar_t cl_name = {CVAR_CLIENT | CVAR_SAVE | CVAR_USERINFO, "name", "player", "change your player name"}; -cvar_t cl_topcolor = {CVAR_CLIENT | CVAR_SAVE | CVAR_USERINFO, "topcolor", "0", "change the color of your shirt"}; -cvar_t cl_bottomcolor = {CVAR_CLIENT | CVAR_SAVE | CVAR_USERINFO, "bottomcolor", "0", "change the color of your pants"}; +#include "cl_collision.h" + +cvar_t cl_name = {CF_CLIENT | CF_ARCHIVE | CF_USERINFO, "name", "player", "change your player name"}; +cvar_t cl_rate = {CF_CLIENT | CF_ARCHIVE | CF_USERINFO, "rate", "20000", "change your connection speed"}; +cvar_t cl_rate_burstsize = {CF_CLIENT | CF_ARCHIVE | CF_USERINFO, "rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"}; +cvar_t cl_topcolor = {CF_CLIENT | CF_ARCHIVE | CF_USERINFO, "topcolor", "0", "change the color of your shirt"}; +cvar_t cl_bottomcolor = {CF_CLIENT | CF_ARCHIVE | CF_USERINFO, "bottomcolor", "0", "change the color of your pants"}; +cvar_t cl_team = {CF_CLIENT | CF_USERINFO | CF_ARCHIVE, "team", "none", "QW team (4 character limit, example: blue)"}; +cvar_t cl_skin = {CF_CLIENT | CF_USERINFO | CF_ARCHIVE, "skin", "", "QW player skin name (example: base)"}; +cvar_t cl_noaim = {CF_CLIENT | CF_USERINFO | CF_ARCHIVE, "noaim", "1", "QW option to disable vertical autoaim"}; +cvar_t cl_pmodel = {CF_CLIENT | CF_USERINFO | CF_ARCHIVE, "pmodel", "0", "current player model number in nehahra"}; +cvar_t r_fixtrans_auto = {CF_CLIENT, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"}; + +extern cvar_t rcon_secure; +extern cvar_t rcon_secure_challengetimeout; /* =================== @@ -163,7 +172,8 @@ void CL_ForwardToServer (const char *s) void CL_ForwardToServer_f (cmd_state_t *cmd) { const char *s; - char vabuf[1024]; + char vabuf[MAX_INPUTLINE]; + size_t i; if (!strcasecmp(Cmd_Argv(cmd, 0), "cmd")) { // we want to strip off "cmd", so just send the args @@ -172,7 +182,11 @@ void CL_ForwardToServer_f (cmd_state_t *cmd) else { // we need to keep the command name, so send Cmd_Argv(cmd, 0), a space and then Cmd_Args(cmd) - s = va(vabuf, sizeof(vabuf), "%s %s", Cmd_Argv(cmd, 0), Cmd_Argc(cmd) > 1 ? Cmd_Args(cmd) : ""); + i = dpsnprintf(vabuf, sizeof(vabuf), "%s", Cmd_Argv(cmd, 0)); + if(Cmd_Argc(cmd) > 1) + // (i + 1) accounts for the added space + dpsnprintf(&vabuf[i], sizeof(vabuf) - (i + 1), " %s", Cmd_Args(cmd)); + s = vabuf; } // don't send an empty forward message if the user tries "cmd" by itself if (!s || !*s) @@ -180,34 +194,72 @@ void CL_ForwardToServer_f (cmd_state_t *cmd) CL_ForwardToServer(s); } +static void CL_SendCvar_f(cmd_state_t *cmd) +{ + cvar_t *c; + const char *cvarname; + char vabuf[1024]; + + if(Cmd_Argc(cmd) != 2) + return; + cvarname = Cmd_Argv(cmd, 1); + if (cls.state == ca_connected) + { + c = Cvar_FindVar(&cvars_all, cvarname, CF_CLIENT | CF_SERVER); + // LadyHavoc: if there is no such cvar or if it is private, send a + // reply indicating that it has no value + if(!c || (c->flags & CF_PRIVATE)) + CL_ForwardToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname)); + else + CL_ForwardToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string)); + return; + } +} + /* ================== CL_Color_f ================== */ -cvar_t cl_color = {CVAR_READONLY | CVAR_CLIENT | CVAR_SAVE, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"}; +cvar_t cl_color = {CF_CLIENT | CF_ARCHIVE, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"}; -// Ignore the callbacks so this two-to-three way synchronization doesn't cause an infinite loop. +// HACK: Ignore the callbacks so this two-to-three way synchronization doesn't cause an infinite loop. static void CL_Color_c(cvar_t *var) { char vabuf[1024]; - - Cvar_Set_NoCallback(&cl_topcolor, va(vabuf, sizeof(vabuf), "%i", ((var->integer >> 4) & 15))); - Cvar_Set_NoCallback(&cl_bottomcolor, va(vabuf, sizeof(vabuf), "%i", (var->integer & 15))); + void (*callback_save)(cvar_t *); + + callback_save = cl_topcolor.callback; + cl_topcolor.callback = NULL; + Cvar_SetQuick(&cl_topcolor, va(vabuf, sizeof(vabuf), "%i", ((var->integer >> 4) & 15))); + cl_topcolor.callback = callback_save; + + callback_save = cl_bottomcolor.callback; + cl_bottomcolor.callback = NULL; + Cvar_SetQuick(&cl_bottomcolor, va(vabuf, sizeof(vabuf), "%i", (var->integer & 15))); + cl_bottomcolor.callback = callback_save; } static void CL_Topcolor_c(cvar_t *var) { char vabuf[1024]; - - Cvar_Set_NoCallback(&cl_color, va(vabuf, sizeof(vabuf), "%i", var->integer*16 + cl_bottomcolor.integer)); + void (*callback_save)(cvar_t *); + + callback_save = cl_color.callback; + cl_color.callback = NULL; + Cvar_SetQuick(&cl_color, va(vabuf, sizeof(vabuf), "%i", var->integer*16 + cl_bottomcolor.integer)); + cl_color.callback = callback_save; } static void CL_Bottomcolor_c(cvar_t *var) { char vabuf[1024]; + void (*callback_save)(cvar_t *); - Cvar_Set_NoCallback(&cl_color, va(vabuf, sizeof(vabuf), "%i", cl_topcolor.integer*16 + var->integer)); + callback_save = cl_color.callback; + cl_color.callback = NULL; + Cvar_SetQuick(&cl_color, va(vabuf, sizeof(vabuf), "%i", cl_topcolor.integer*16 + var->integer)); + cl_color.callback = callback_save; } static void CL_Color_f(cmd_state_t *cmd) @@ -216,7 +268,7 @@ static void CL_Color_f(cmd_state_t *cmd) if (Cmd_Argc(cmd) == 1) { - if (cmd->source == src_command) + if (cmd->source == src_local) { Con_Printf("\"color\" is \"%i %i\"\n", cl_topcolor.integer, cl_bottomcolor.integer); Con_Print("color <0-15> [0-15]\n"); @@ -251,7 +303,7 @@ static void CL_Color_f(cmd_state_t *cmd) //if (bottom > 13) // bottom = 13; - if (cmd->source == src_command) + if (cmd->source == src_local) { Cvar_SetValueQuick(&cl_topcolor, top); Cvar_SetValueQuick(&cl_bottomcolor, bottom); @@ -259,6 +311,68 @@ static void CL_Color_f(cmd_state_t *cmd) } } +/* +==================== +CL_User_f + +user + +Dump userdata / masterdata for a user +==================== +*/ +static void CL_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld +{ + int uid; + int i; + + if (Cmd_Argc(cmd) != 2) + { + Con_Printf ("Usage: user \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"); +} + +/* +==================== +CL_Users_f + +Dump userids for all current players +==================== +*/ +static void CL_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); +} + /* ==================== CL_Packet_f @@ -480,33 +594,160 @@ static void CL_Rcon_f(cmd_state_t *cmd) // credit: taken from QuakeWorld } } -static void CL_RCon_ClearPassword_c(cvar_t *var) +/* +================== +CL_FullServerinfo_f + +Sent by server when serverinfo changes +================== +*/ +// TODO: shouldn't this be a cvar instead? +static void CL_FullServerinfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld +{ + char temp[512]; + if (Cmd_Argc(cmd) != 2) + { + Con_Printf ("usage: fullserverinfo \n"); + return; + } + + strlcpy (cl.qw_serverinfo, Cmd_Argv(cmd, 1), sizeof(cl.qw_serverinfo)); + InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp)); + cl.qw_teamplay = atoi(temp); +} + +/* +================== +CL_FullInfo_f + +Allow clients to change userinfo +================== +Casey was here :) +*/ +static void CL_FullInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld +{ + char key[512]; + char value[512]; + const char *s; + + if (Cmd_Argc(cmd) != 2) + { + Con_Printf ("fullinfo \n"); + return; + } + + s = Cmd_Argv(cmd, 1); + if (*s == '\\') + s++; + while (*s) + { + size_t len = strcspn(s, "\\"); + if (len >= sizeof(key)) { + len = sizeof(key) - 1; + } + strlcpy(key, s, len + 1); + s += len; + if (!*s) + { + Con_Printf ("MISSING VALUE\n"); + return; + } + ++s; // Skip over backslash. + + len = strcspn(s, "\\"); + if (len >= sizeof(value)) { + len = sizeof(value) - 1; + } + strlcpy(value, s, len + 1); + + CL_SetInfo(key, value, false, false, false, false); + + s += len; + if (!*s) + { + break; + } + ++s; // Skip over backslash. + } +} + +/* +================== +CL_SetInfo_f + +Allow clients to change userinfo +================== +*/ +static void CL_SetInfo_f(cmd_state_t *cmd) // credit: taken from QuakeWorld { - // whenever rcon_secure is changed to 0, clear rcon_password for - // security reasons (prevents a send-rcon-password-as-plaintext - // attack based on NQ protocol session takeover and svc_stufftext) - if(var->integer <= 0) - Cvar_SetQuick(&rcon_password, ""); + if (Cmd_Argc(cmd) == 1) + { + InfoString_Print(cls.userinfo); + return; + } + if (Cmd_Argc(cmd) != 3) + { + Con_Printf ("usage: setinfo [ ]\n"); + return; + } + CL_SetInfo(Cmd_Argv(cmd, 1), Cmd_Argv(cmd, 2), true, false, false, false); +} + +static void CL_PingPLReport_f(cmd_state_t *cmd) +{ + char *errbyte; + int i; + int l = Cmd_Argc(cmd); + if (l > cl.maxclients) + l = cl.maxclients; + for (i = 0;i < l;i++) + { + cl.scores[i].qw_ping = atoi(Cmd_Argv(cmd, 1+i*2)); + cl.scores[i].qw_packetloss = strtol(Cmd_Argv(cmd, 1+i*2+1), &errbyte, 0); + if(errbyte && *errbyte == ',') + cl.scores[i].qw_movementloss = atoi(errbyte + 1); + else + cl.scores[i].qw_movementloss = 0; + } } void CL_InitCommands(void) { + dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp"); + + Cvar_RegisterVariable(&cl_name); + Cvar_RegisterAlias(&cl_name, "_cl_name"); + Cvar_RegisterVariable(&cl_rate); + Cvar_RegisterAlias(&cl_rate, "_cl_rate"); + Cvar_RegisterVariable(&cl_rate_burstsize); + Cvar_RegisterAlias(&cl_rate_burstsize, "_cl_rate_burstsize"); + Cvar_RegisterVariable(&cl_pmodel); + Cvar_RegisterAlias(&cl_pmodel, "_cl_pmodel"); Cvar_RegisterVariable(&cl_color); Cvar_RegisterCallback(&cl_color, CL_Color_c); Cvar_RegisterVariable(&cl_topcolor); Cvar_RegisterCallback(&cl_topcolor, CL_Topcolor_c); Cvar_RegisterVariable(&cl_bottomcolor); Cvar_RegisterCallback(&cl_bottomcolor, CL_Bottomcolor_c); - Cvar_RegisterVariable(&rcon_address); - Cvar_RegisterVariable(&rcon_secure); - Cvar_RegisterCallback(&rcon_secure, CL_RCon_ClearPassword_c); - Cvar_RegisterVariable(&rcon_secure_challengetimeout); - - Cmd_AddCommand(CMD_CLIENT | CMD_CLIENT_FROM_SERVER, "cmd", CL_ForwardToServer_f, "send a console commandline to the server (used by some mods)"); - Cmd_AddCommand(CMD_CLIENT, "color", CL_Color_f, "change your player shirt and pants colors"); - Cmd_AddCommand(CMD_CLIENT, "rcon", CL_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP"); - Cmd_AddCommand(CMD_CLIENT, "srcon", CL_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP"); - Cmd_AddCommand(CMD_CLIENT, "pqrcon", CL_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)"); - Cmd_AddCommand(CMD_CLIENT, "packet", CL_Packet_f, "send a packet to the specified address:port containing a text string"); - + Cvar_RegisterVariable(&r_fixtrans_auto); + Cvar_RegisterVariable(&cl_team); + Cvar_RegisterVariable(&cl_skin); + Cvar_RegisterVariable(&cl_noaim); + + Cmd_AddCommand(CF_CLIENT | CF_CLIENT_FROM_SERVER, "cmd", CL_ForwardToServer_f, "send a console commandline to the server (used by some mods)"); + Cmd_AddCommand(CF_CLIENT, "color", CL_Color_f, "change your player shirt and pants colors"); + Cmd_AddCommand(CF_CLIENT, "rcon", CL_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP"); + Cmd_AddCommand(CF_CLIENT, "srcon", CL_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP"); + Cmd_AddCommand(CF_CLIENT, "pqrcon", CL_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)"); + Cmd_AddCommand(CF_SHARED, "user", CL_User_f, "prints additional information about a player number or name on the scoreboard"); + Cmd_AddCommand(CF_SHARED, "users", CL_Users_f, "prints additional information about all players on the scoreboard"); + Cmd_AddCommand(CF_CLIENT, "packet", CL_Packet_f, "send a packet to the specified address:port containing a text string"); + Cmd_AddCommand(CF_CLIENT, "fullinfo", CL_FullInfo_f, "allows client to modify their userinfo"); + Cmd_AddCommand(CF_CLIENT, "setinfo", CL_SetInfo_f, "modifies your userinfo"); + Cmd_AddCommand(CF_CLIENT, "fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)"); + host.hook.CL_SendCvar = CL_SendCvar_f; + + // commands that are only sent by server to client for execution + Cmd_AddCommand(CF_CLIENT_FROM_SERVER, "pingplreport", CL_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(CF_CLIENT_FROM_SERVER, "fullserverinfo", CL_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string"); }