From: Mario Date: Wed, 28 Sep 2022 04:40:46 +0000 (+1000) Subject: Merge branch 'master' into TimePath/menu_chat X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=5ad5b35f4c84d795c24bda18e3874e3e14e0775c;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into TimePath/menu_chat --- 5ad5b35f4c84d795c24bda18e3874e3e14e0775c diff --cc qcsrc/client/main.qc index 2d8483c83,22f0438f4..da36877e8 --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@@ -913,33 -1030,10 +1032,33 @@@ void CSQC_Parse_StuffCmd(string strMess void CSQC_Parse_Print(string strMessage) { if (autocvar_developer_csqcentities) LOG_INFOF("CSQC_Parse_Print(\"%s\")", strMessage); - print(ColorTranslateRGB(strMessage)); + if (autocvar__cl_hook_print != "") { + int flags = NOTICE_SILENT; + for (int i = 0, n = tokenizebyseparator(strMessage, "\n"); i < n; ++i) { + string s = substring(argv(i), 0, -1); + int c = str2chr(s, 0); + if (c == 1 || c == 2 || c == 3) { + s = substring(s, 1, -1); + if (c == 1) { + flags &= ~NOTICE_SILENT; + if (str2chr(s, 0) == '\r') { + s = substring(s, 1, -1); + flags |= NOTICE_PRIVATE; + } + } + if (c != 2) { + flags |= NOTICE_CHAT; + } + s = strcat("^3", s); + } + localcmd("\n", autocvar__cl_hook_print, " ", console_encode(s), " ", itos(flags), "\n"); + } + } else { + print(ColorTranslateRGB(strMessage)); + } } - // CSQC_Parse_CenterPrint : Provides the centerprint_hud string in the first parameter that the server provided. + // CSQC_Parse_CenterPrint : Provides the centerprint_AddStandard string in the first parameter that the server provided. void CSQC_Parse_CenterPrint(string strMessage) { if (autocvar_developer_csqcentities) LOG_INFOF("CSQC_Parse_CenterPrint(\"%s\")", strMessage); diff --cc qcsrc/lib/cvar.qh index cdd1b23a8,d5b47ce43..02c82603a --- a/qcsrc/lib/cvar.qh +++ b/qcsrc/lib/cvar.qh @@@ -31,28 -30,74 +31,86 @@@ string MakeConsoleSafe(string input return input; } +ERASEABLE +string console_encode(string input) +{ + return json_stringify_string(strreplace("$", "$$", input)); +} + +ERASEABLE +string console_decode(string input) +{ + return json_parse_string(input); +} + + /** + * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ... + * +: all must match. this is the default + * -: one must NOT match + * + * var>x + * var=x + * var<=x + * var==x + * var!=x + * var===x + * var!==x + */ ERASEABLE - void cvar_describe(string name, string desc) + bool expr_evaluate(string s) { - localcmd(sprintf("\nset %1$s \"$%1$s\" \"%2$s\"\n", name, MakeConsoleSafe(desc))); - } + bool ret = false; + if (str2chr(s, 0) == '+') { + s = substring(s, 1, -1); + } else if (str2chr(s, 0) == '-') { + ret = true; + s = substring(s, 1, -1); + } + bool expr_fail = false; + for (int i = 0, n = tokenize_console(s); i < n; ++i) { + int o; + string k, v; + s = argv(i); + #define X(expr) \ + if (expr) \ + continue; \ + expr_fail = true; \ + break; - ERASEABLE - void cvar_archive(string name) - { - localcmd(sprintf("\nseta %1$s \"$%1$s\"\n", name)); + #define BINOP(op, len, expr) \ + if ((o = strstrofs(s, op, 0)) >= 0) { \ + k = substring(s, 0, o); \ + v = substring(s, o + len, -1); \ + X(expr); \ + } + BINOP(">=", 2, cvar(k) >= stof(v)); + BINOP("<=", 2, cvar(k) <= stof(v)); + BINOP(">", 1, cvar(k) > stof(v)); + BINOP("<", 1, cvar(k) < stof(v)); + BINOP("==", 2, cvar(k) == stof(v)); + BINOP("!=", 2, cvar(k) != stof(v)); + BINOP("===", 3, cvar_string(k) == v); + BINOP("!==", 3, cvar_string(k) != v); + { + k = s; + bool b = true; + if (str2chr(k, 0) == '!') { + k = substring(s, 1, -1); + b = false; + } + float f = stof(k); + bool isnum = ftos(f) == k; + X(boolean(isnum ? f : cvar(k)) == b); + } + #undef BINOP + #undef X + } + if (!expr_fail) { + ret = !ret; + } + // now ret is true if we want to keep the item, and false if we want to get rid of it + return ret; } ERASEABLE diff --cc qcsrc/lib/vector.qh index 6e622b416,23bfdf055..fa1bb5d01 --- a/qcsrc/lib/vector.qh +++ b/qcsrc/lib/vector.qh @@@ -104,15 -86,21 +86,22 @@@ float boxinsidebox(vector smins, vecto // vector vec2(vector v); // returns a vector with just the x and y components of the given vector // vector vec2(float x, float y); // returns a vector with the given x and y components -noref vector _vec2; -#define vec2(...) EVAL(OVERLOAD(vec2, __VA_ARGS__)) -#define vec2_1(v) (_vec2 = (v), _vec2.z = 0, _vec2) -#define vec2_2(x, y) (_vec2_x = (x), _vec2_y = (y), _vec2) +noref vector _vec_zero; +noref vector _vec2_tmp; +noref vector _vec3_tmp; -noref vector _vec3; -#define vec3(_x, _y, _z) (_vec3.x = (_x), _vec3.y = (_y), _vec3.z = (_z), _vec3) +#define vec2(...) EVAL(OVERLOAD(vec2, __VA_ARGS__)) +#define vec2_1(v) (_vec2_tmp = (v), _vec2_tmp.z = 0, _vec2_tmp + _vec_zero) +#define vec2_2(_x, _y) (_vec2_tmp.x = (_x), _vec2_tmp.y = (_y), _vec2_tmp + _vec_zero) +#define vec3(_x, _y, _z) (_vec3_tmp.x = (_x), _vec3_tmp.y = (_y), _vec3_tmp.z = (_z), _vec3_tmp + _vec_zero) + #define VEC_NAN vec3(FLOAT_NAN, FLOAT_NAN, FLOAT_NAN); + + ERASEABLE + bool is_all_nans(vector v) { + return isnan(v.x) && isnan(v.y) && isnan(v.z); + } + ERASEABLE vector Rotate(vector v, float a) { diff --cc qcsrc/menu/modules/chat/commands.qc index 03e779e15,000000000..8e16915d3 mode 100644,000000..100644 --- a/qcsrc/menu/modules/chat/commands.qc +++ b/qcsrc/menu/modules/chat/commands.qc @@@ -1,119 -1,0 +1,119 @@@ +#include "commands.qh" + +#include + +#include "state.qh" + +noref string autocvar__cl_hook_print = "menu_cmd notice"; + - GENERIC_COMMAND(notice, "Append a notice to chat") ++GENERIC_COMMAND(notice, "Append a notice to chat", true) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + string s = console_decode(substring(command, argv_start_index(1), argv_end_index(1) - argv_start_index(1))); + int flags = stoi(argv(2)); + s = ColorTranslateRGB(s); + string prefix = (flags & NOTICE_CHAT) ? "\{3}" : ""; + if (!(flags & NOTICE_SILENT)) { + entity snd = (flags & NOTICE_PRIVATE) ? SND_TALK2 : SND_TALK; + localsound(Sound_fixpath(snd)); + } + chat_lines_push(s); + print(prefix, "^7", s, "\n"); + return; + } + + default: + case CMD_REQUEST_USAGE: + { + LOG_INFO("Usage:^3 ", GetProgramCommandPrefix(), " notice message"); + LOG_INFO(" Where 'message' is the message to append."); + return; + } + } +} + +void m_goto(string name); + - GENERIC_COMMAND(commandmode, "input a console command") ++GENERIC_COMMAND(commandmode, "input a console command", true) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + if (!autoextension_chat) { + return; + } + string prefix = arguments > 1 ? strcat(substring(command, argv_start_index(1), argv_end_index(-1)), " ") : ""; + chat_command = ""; + strcpy(chat_text, prefix); + if (!isdemo()) { + m_goto("Chat"); + } + return; + } + + default: + case CMD_REQUEST_USAGE: + { + LOG_INFO("Usage:^3 ", GetProgramCommandPrefix(), " commandmode [prefix]"); + return; + } + } +} + - GENERIC_COMMAND(messagemode, "input a chat message to say to everyone") ++GENERIC_COMMAND(messagemode, "input a chat message to say to everyone", true) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + if (!autoextension_chat) { + return; + } + string prefix = arguments > 1 ? strcat(substring(command, argv_start_index(1), argv_end_index(-1)), " ") : ""; + chat_command = "say "; + strcpy(chat_text, prefix); + if (!isdemo()) { + m_goto("Chat"); + } + return; + } + + default: + case CMD_REQUEST_USAGE: + { + LOG_INFO("Usage:^3 ", GetProgramCommandPrefix(), " messagemode [prefix]"); + return; + } + } +} + - GENERIC_COMMAND(messagemode2, "input a chat message to say to only your team") ++GENERIC_COMMAND(messagemode2, "input a chat message to say to only your team", true) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + if (!autoextension_chat) { + return; + } + string prefix = arguments > 1 ? strcat(substring(command, argv_start_index(1), argv_end_index(-1)), " ") : ""; + chat_command = "say_team "; + strcpy(chat_text, prefix); + if (!isdemo()) { + m_goto("Chat"); + } + return; + } + + default: + case CMD_REQUEST_USAGE: + { + LOG_INFO("Usage:^3 ", GetProgramCommandPrefix(), " messagemode2 [prefix]"); + return; + } + } +} diff --cc qcsrc/menu/modules/extensions/commands.qc index 77eab7fa4,000000000..e28913510 mode 100644,000000..100644 --- a/qcsrc/menu/modules/extensions/commands.qc +++ b/qcsrc/menu/modules/extensions/commands.qc @@@ -1,54 -1,0 +1,54 @@@ +#include "commands.qh" + +#include "state.qh" + +// space separated list of extensions +void extensions_set(string s) +{ + for (int i = 0, n = extensions_count(); i < n; ++i) { + string id = extensions_get_name(i); + void(bool) set = extensions_get_setter(i); + bool enable = strhasword(s, id); + set(enable); + } +} + - GENERIC_COMMAND(_cl_hook_gamestart, "_cl_hook_gamestart") ++GENERIC_COMMAND(_cl_hook_gamestart, "_cl_hook_gamestart", true) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + string s = substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)); + localcmd("\nset _cl_hook_gametype ", s, "; _cl_hook_gamestart_stage2\n"); + extensions_set(cvar_string("_cl_extensions")); + cvar_set("_cl_extensions", ""); + return; + } + + default: + case CMD_REQUEST_USAGE: + { + return; + } + } +} + - GENERIC_COMMAND(_cl_extensions, "_cl_extensions") ++GENERIC_COMMAND(_cl_extensions, "_cl_extensions", true) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + string s = substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)); + extensions_set(s); + return; + } + + default: + case CMD_REQUEST_USAGE: + { + return; + } + } +} diff --cc qcsrc/menu/xonotic/mainwindow.qc index 5c22e249c,e215e5080..ac0eb8fd1 --- a/qcsrc/menu/xonotic/mainwindow.qc +++ b/qcsrc/menu/xonotic/mainwindow.qc @@@ -1,7 -1,6 +1,7 @@@ #include "mainwindow.qh" - #include "../mutators/events.qh" +#include + #include #include "nexposee.qh" #include "inputbox.qh" diff --cc qcsrc/server/chat.qc index 000000000,a73de2a1f..06ae8f7ac mode 000000,100644..100644 --- a/qcsrc/server/chat.qc +++ b/qcsrc/server/chat.qc @@@ -1,0 -1,581 +1,581 @@@ + #include "chat.qh" + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + /** + * message "": do not say, just test flood control + * return value: + * 1 = accept + * 0 = reject + * -1 = fake accept + */ + int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol) + { + if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ") + msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!) + + if (source) + msgin = formatmessage(source, msgin); + + string colorstr; + if (!(IS_PLAYER(source) || INGAME(source))) + colorstr = "^0"; // black for spectators + else if(teamplay) + colorstr = Team_ColorCode(source.team); + else + { + colorstr = ""; + teamsay = false; + } + + if (!source) { + colorstr = ""; + teamsay = false; + } + + if(msgin != "") + msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin); + + /* + * using bprint solves this... me stupid + // how can we prevent the message from appearing in a listen server? + // for now, just give "say" back and only handle say_team + if(!teamsay) + { + clientcommand(source, strcat("say ", msgin)); + return; + } + */ + + string namestr = ""; + if (source) + namestr = playername(source.netname, source.team, (autocvar_g_chat_teamcolors && IS_PLAYER(source))); + + string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7"; + + string msgstr = "", cmsgstr = ""; + string privatemsgprefix = string_null; + int privatemsgprefixlen = 0; + if (msgin != "") + { + bool found_me = false; + if(strstrofs(msgin, "/me", 0) >= 0) + { + string newmsgin = ""; + string newnamestr = ((teamsay) ? strcat(colorstr, "(", colorprefix, namestr, colorstr, ")", "^7") : strcat(colorprefix, namestr, "^7")); + FOREACH_WORD(msgin, true, + { + if(strdecolorize(it) == "/me") + { + found_me = true; + newmsgin = cons(newmsgin, newnamestr); + } + else + newmsgin = cons(newmsgin, it); + }); + msgin = newmsgin; + } + + if(privatesay) + { - msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7"); ++ msgstr = strcat("\{1}\r* ", colorprefix, namestr, "^3 tells you: ^7"); + privatemsgprefixlen = strlen(msgstr); + msgstr = strcat(msgstr, msgin); + cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin); - privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay.netname, privatesay.team, (autocvar_g_chat_teamcolors && IS_PLAYER(privatesay))), ": ^7"); ++ privatemsgprefix = strcat("\{1}\r* ^3You tell ", playername(privatesay.netname, privatesay.team, (autocvar_g_chat_teamcolors && IS_PLAYER(privatesay))), ": ^7"); + } + else if(teamsay) + { + if(found_me) + { + //msgin = strreplace("/me", "", msgin); + //msgin = substring(msgin, 3, strlen(msgin)); + //msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin); - msgstr = strcat("\{1}\{13}^4* ", "^7", msgin); ++ msgstr = strcat("\{1}\r^4* ", "^7", msgin); + } + else - msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin); ++ msgstr = strcat("\{1}\r", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin); + cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin); + } + else + { + if(found_me) + { + //msgin = strreplace("/me", "", msgin); + //msgin = substring(msgin, 3, strlen(msgin)); + //msgin = strreplace("/me", strcat(colorprefix, namestr), msgin); + msgstr = strcat("\{1}^4* ^7", msgin); + } + else { + msgstr = "\{1}"; + msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7"); + msgstr = strcat(msgstr, msgin); + } + cmsgstr = ""; + } + msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint + } + + string fullmsgstr = msgstr; + string fullcmsgstr = cmsgstr; + + // FLOOD CONTROL + int flood = 0; + var .float flood_field = floodcontrol_chat; + if(floodcontrol && source) + { + float flood_spl, flood_burst, flood_lmax; + if(privatesay) + { + flood_spl = autocvar_g_chat_flood_spl_tell; + flood_burst = autocvar_g_chat_flood_burst_tell; + flood_lmax = autocvar_g_chat_flood_lmax_tell; + flood_field = floodcontrol_chattell; + } + else if(teamsay) + { + flood_spl = autocvar_g_chat_flood_spl_team; + flood_burst = autocvar_g_chat_flood_burst_team; + flood_lmax = autocvar_g_chat_flood_lmax_team; + flood_field = floodcontrol_chatteam; + } + else + { + flood_spl = autocvar_g_chat_flood_spl; + flood_burst = autocvar_g_chat_flood_burst; + flood_lmax = autocvar_g_chat_flood_lmax; + flood_field = floodcontrol_chat; + } + flood_burst = max(0, flood_burst - 1); + // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four! + + // do flood control for the default line size + if(msgstr != "") + { + getWrappedLine_remaining = msgstr; + msgstr = ""; + int lines = 0; + while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax)) + { + msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width + ++lines; + } + msgstr = substring(msgstr, 1, strlen(msgstr) - 1); + + if(getWrappedLine_remaining != "") + { + msgstr = strcat(msgstr, "\n"); + flood = 2; + } + + if (time >= source.(flood_field)) + { + source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl; + } + else + { + flood = 1; + msgstr = fullmsgstr; + } + } + else + { + if (time >= source.(flood_field)) + source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl; + else + flood = 1; + } + + if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection + source.(flood_field) = flood = 0; + } + + string sourcemsgstr, sourcecmsgstr; + if(flood == 2) // cannot happen for empty msgstr + { + if(autocvar_g_chat_flood_notify_flooder) + { + sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n"); + sourcecmsgstr = ""; + } + else + { + sourcemsgstr = fullmsgstr; + sourcecmsgstr = fullcmsgstr; + } + cmsgstr = ""; + } + else + { + sourcemsgstr = msgstr; + sourcecmsgstr = cmsgstr; + } + + if (!privatesay && source && !(IS_PLAYER(source) || INGAME(source)) && !game_stopped + && (teamsay || CHAT_NOSPECTATORS())) + { + teamsay = -1; // spectators + } + + if(flood) + LOG_INFO("NOTE: ", playername(source.netname, source.team, IS_PLAYER(source)), "^7 is flooding."); + + // build sourcemsgstr by cutting off a prefix and replacing it by the other one + if(privatesay) + sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1)); + + int ret; + if(source && CS(source).muted) + { + // always fake the message + ret = -1; + } + else if(flood == 1) + { + if (autocvar_g_chat_flood_notify_flooder) + { + sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n")); + ret = 0; + } + else + ret = -1; + } + else + { + ret = 1; + } + + if (privatesay && source && !(IS_PLAYER(source) || INGAME(source)) && !game_stopped + && (IS_PLAYER(privatesay) || INGAME(privatesay)) && CHAT_NOSPECTATORS()) + { + ret = -1; // just hide the message completely + } + + MUTATOR_CALLHOOK(ChatMessage, source, ret); + ret = M_ARGV(1, int); + + string event_log_msg = ""; + + if(sourcemsgstr != "" && ret != 0) + { + if(ret < 0) // faked message, because the player is muted + { + sprint(source, sourcemsgstr); + if(sourcecmsgstr != "" && !privatesay) + centerprint(source, sourcecmsgstr); + } + else if(privatesay) // private message, between 2 people only + { + sprint(source, sourcemsgstr); + if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled + if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source)) + { + sprint(privatesay, msgstr); + if(cmsgstr != "") + centerprint(privatesay, cmsgstr); + } + } + else if ( teamsay && CS(source).active_minigame ) + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { + sprint(it, msgstr); + }); + event_log_msg = sprintf(":chat_minigame:%d:%s:%s", source.playerid, CS(source).active_minigame.netname, msgin); + + } + else if(teamsay > 0) // team message, only sent to team mates + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + if(sourcecmsgstr != "") + centerprint(source, sourcecmsgstr); + FOREACH_CLIENT((IS_PLAYER(it) || INGAME(it)) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { + sprint(it, msgstr); + if(cmsgstr != "") + centerprint(it, cmsgstr); + }); + event_log_msg = sprintf(":chat_team:%d:%d:%s", source.playerid, source.team, strreplace("\n", " ", msgin)); + } + else if(teamsay < 0) // spectator message, only sent to spectators + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + FOREACH_CLIENT(!(IS_PLAYER(it) || INGAME(it)) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { + sprint(it, msgstr); + }); + event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin)); + } + else + { + if (source) { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + MX_Say(strcat(playername(source.netname, source.team, IS_PLAYER(source)), "^7: ", msgin)); + } + FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { + sprint(it, msgstr); + }); + event_log_msg = sprintf(":chat:%d:%s", source.playerid, strreplace("\n", " ", msgin)); + } + } + + if (autocvar_sv_eventlog && (event_log_msg != "")) { + GameLogEcho(event_log_msg); + } + + return ret; + } + + entity findnearest(vector point, bool checkitems, vector axismod) + { + vector dist; + int num_nearest = 0; + + IL_EACH(((checkitems) ? g_items : g_locations), ((checkitems) ? (it.target == "###item###") : (it.classname == "target_location")), + { + if ((it.items == IT_KEY1 || it.items == IT_KEY2) && it.target == "###item###") + dist = it.oldorigin; + else + dist = it.origin; + dist = dist - point; + dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1'; + float len = vlen2(dist); + + int l; + for (l = 0; l < num_nearest; ++l) + { + if (len < nearest_length[l]) + break; + } + + // now i tells us where to insert at + // INSERTION SORT! YOU'VE SEEN IT! RUN! + if (l < NUM_NEAREST_ENTITIES) + { + for (int j = NUM_NEAREST_ENTITIES - 1; j >= l; --j) + { + nearest_length[j + 1] = nearest_length[j]; + nearest_entity[j + 1] = nearest_entity[j]; + } + nearest_length[l] = len; + nearest_entity[l] = it; + if (num_nearest < NUM_NEAREST_ENTITIES) + num_nearest = num_nearest + 1; + } + }); + + // now use the first one from our list that we can see + for (int j = 0; j < num_nearest; ++j) + { + traceline(point, nearest_entity[j].origin, true, NULL); + if (trace_fraction == 1) + { + if (j != 0) + LOG_TRACEF("Nearest point (%s) is not visible, using a visible one.", nearest_entity[0].netname); + return nearest_entity[j]; + } + } + + if (num_nearest == 0) + return NULL; + + LOG_TRACE("Not seeing any location point, using nearest as fallback."); + /* DEBUGGING CODE: + dprint("Candidates were: "); + for(j = 0; j < num_nearest; ++j) + { + if(j != 0) + dprint(", "); + dprint(nearest_entity[j].netname); + } + dprint("\n"); + */ + + return nearest_entity[0]; + } + + string NearestLocation(vector p) + { + string ret = "somewhere"; + entity loc = findnearest(p, false, '1 1 1'); + if (loc) + ret = loc.message; + else + { + loc = findnearest(p, true, '1 1 4'); + if (loc) + ret = loc.netname; + } + return ret; + } + + string PlayerHealth(entity this) + { + float myhealth = floor(GetResource(this, RES_HEALTH)); + if(myhealth == -666) + return "spectating"; + else if(myhealth == -2342 || (myhealth == 2342 && mapvote_initialized)) + return "observing"; + else if(myhealth <= 0 || IS_DEAD(this)) + return "dead"; + return ftos(myhealth); + } + + string WeaponNameFromWeaponentity(entity this, .entity weaponentity) + { + entity wepent = this.(weaponentity); + if(!wepent) + return "none"; + else if(wepent.m_weapon != WEP_Null) + return wepent.m_weapon.m_name; + else if(wepent.m_switchweapon != WEP_Null) + return wepent.m_switchweapon.m_name; + return "none"; //REGISTRY_GET(Weapons, wepent.cnt).m_name; + } + + string formatmessage(entity this, string msg) + { + float p, p1, p2; + float n; + vector cursor = '0 0 0'; + entity cursor_ent = NULL; + string escape; + string replacement; + p = 0; + n = 7; + bool traced = false; + + MUTATOR_CALLHOOK(PreFormatMessage, this, msg); + msg = M_ARGV(1, string); + + while (1) { + if (n < 1) + break; // too many replacements + + n = n - 1; + p1 = strstrofs(msg, "%", p); // NOTE: this destroys msg as it's a tempstring! + p2 = strstrofs(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring! + + if (p1 < 0) + p1 = p2; + + if (p2 < 0) + p2 = p1; + + p = min(p1, p2); + + if (p < 0) + break; + + if(!traced) + { + WarpZone_crosshair_trace_plusvisibletriggers(this); + cursor = trace_endpos; + cursor_ent = trace_ent; + traced = true; + } + + replacement = substring(msg, p, 2); + escape = substring(msg, p + 1, 1); + + .entity weaponentity = weaponentities[0]; // TODO: unhardcode + + switch(escape) + { + case "%": replacement = "%"; break; + case "\\":replacement = "\\"; break; + case "n": replacement = "\n"; break; + case "a": replacement = ftos(floor(GetResource(this, RES_ARMOR))); break; + case "h": replacement = PlayerHealth(this); break; + case "l": replacement = NearestLocation(this.origin); break; + case "y": replacement = NearestLocation(cursor); break; + case "d": replacement = NearestLocation(this.death_origin); break; + case "w": replacement = WeaponNameFromWeaponentity(this, weaponentity); break; + case "W": replacement = GetAmmoName(this.(weaponentity).m_weapon.ammo_type); break; + case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break; + case "s": replacement = ftos(vlen(this.velocity - this.velocity_z * '0 0 1')); break; + case "S": replacement = ftos(vlen(this.velocity)); break; + case "t": replacement = seconds_tostring(ceil(max(0, autocvar_timelimit * 60 + game_starttime - time))); break; + case "T": replacement = seconds_tostring(floor(time - game_starttime)); break; + default: + { + MUTATOR_CALLHOOK(FormatMessage, this, escape, replacement, msg); + replacement = M_ARGV(2, string); + break; + } + } + + msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2))); + p = p + strlen(replacement); + } + return msg; + } + + ERASEABLE + void PrintToChat(entity client, string text) + { + text = strcat("\{1}^7", text, "\n"); + sprint(client, text); + } + + ERASEABLE + void DebugPrintToChat(entity client, string text) + { + if (autocvar_developer > 0) + { + PrintToChat(client, text); + } + } + + ERASEABLE + void PrintToChatAll(string text) + { + text = strcat("\{1}^7", text, "\n"); + bprint(text); + } + + ERASEABLE + void DebugPrintToChatAll(string text) + { + if (autocvar_developer > 0) + { + PrintToChatAll(text); + } + } + + ERASEABLE + void PrintToChatTeam(int team_num, string text) + { + text = strcat("\{1}^7", text, "\n"); + FOREACH_CLIENT(IS_REAL_CLIENT(it), + { + if (it.team == team_num) + { + sprint(it, text); + } + }); + } + + ERASEABLE + void DebugPrintToChatTeam(int team_num, string text) + { + if (autocvar_developer > 0) + { + PrintToChatTeam(team_num, text); + } + } diff --cc qcsrc/server/command/sv_cmd.qc index 1e9a119cf,b35f4fb53..92430a9d6 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@@ -137,11 -126,11 +126,11 @@@ void GameCommand_adminmsg(int request, else { centerprint(client, strcat("^3", GetCallerName(NULL), ":\n^7", admin_message)); - sprint(client, strcat("\{1}\{13}^3", GetCallerName(NULL), "^7: ", admin_message, "\n")); + sprint(client, strcat("\{1}\r^3", GetCallerName(NULL), "^7: ", admin_message, "\n")); } - successful = strcat(successful, (successful ? ", " : ""), playername(client, false)); - LOG_TRACE("Message sent to ", playername(client, false)); + successful = strcat(successful, (successful ? ", " : ""), playername(client.netname, client.team, false)); + LOG_TRACE("Message sent to ", playername(client.netname, client.team, false)); continue; }