From: z411 Date: Tue, 20 Oct 2020 18:17:45 +0000 (-0300) Subject: Merge branch 'master' into z411/bai-server X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=f4b6d6fd77c0b87a59dbee7cac146fd124c9cbf8;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into z411/bai-server # Conflicts: # qcsrc/client/announcer.qc # qcsrc/client/draw.qh # qcsrc/client/hud/panel/chat.qc # qcsrc/client/main.qc # qcsrc/client/view.qc # qcsrc/common/items/inventory.qh # qcsrc/server/client.qc # qcsrc/server/client.qh # qcsrc/server/command/cmd.qc # qcsrc/server/miscfunctions.qc # qcsrc/server/world.qc --- f4b6d6fd77c0b87a59dbee7cac146fd124c9cbf8 diff --cc qcsrc/client/announcer.qc index 01afb0902,a5b3eecb1..d6f2f5065 --- a/qcsrc/client/announcer.qc +++ b/qcsrc/client/announcer.qc @@@ -1,15 -1,9 +1,14 @@@ #include "announcer.qh" + #include #include + #include #include +#include - #include "hud/panel/centerprint.qh" + +#include +#include - #include "miscfunctions.qh" bool announcer_1min; bool announcer_5min; diff --cc qcsrc/client/hud/hud.qh index 1021d7067,b8740560f..47e39f6ba --- a/qcsrc/client/hud/hud.qh +++ b/qcsrc/client/hud/hud.qh @@@ -110,6 -122,6 +122,7 @@@ int ts_primary, ts_secondary float weapontime; float weaponprevtime; ++float timer; float teamnagger; int hudShiftState; diff --cc qcsrc/client/hud/panel/chat.qc index 6807c2183,13ef78136..cb7729d49 --- a/qcsrc/client/hud/panel/chat.qc +++ b/qcsrc/client/hud/panel/chat.qc @@@ -1,8 -1,7 +1,8 @@@ #include "chat.qh" #include - #include + #include +#include // Chat (#12) diff --cc qcsrc/client/hud/panel/timer.qc index cfac04ecb,446c6229b..68c28ac5a --- a/qcsrc/client/hud/panel/timer.qc +++ b/qcsrc/client/hud/panel/timer.qc @@@ -1,7 -1,7 +1,7 @@@ #include "timer.qh" #include - #include -#include ++#include #include // Timer (#5) diff --cc qcsrc/common/items/inventory.qh index b3778f468,c47be6699..c0a94707b --- a/qcsrc/common/items/inventory.qh +++ b/qcsrc/common/items/inventory.qh @@@ -38,14 -37,7 +37,13 @@@ STATIC_INIT(Inventory #endif #ifdef CSQC - #include -Inventory g_inventory; +#include + +//Inventory g_inventory; +Inventory inventoryslots[255]; +float last_pickup_timer; +entity last_pickup_item; +int last_pickup_times; NET_HANDLE(ENT_CLIENT_INVENTORY, bool isnew) { make_pure(this); @@@ -149,11 -117,7 +147,11 @@@ bool Inventory_Send(Inventory this, Cli TC(Inventory, this); WriteHeader(MSG_ENTITY, ENT_CLIENT_INVENTORY); TC(PlayerState, this.owner); + - // z411 send entity number - WriteByte(MSG_ENTITY, etof(this.owner.m_client)); ++ // z411 send entity number ++ WriteByte(MSG_ENTITY, etof(this.owner.m_client)); + - Inventory_Write(this); + Inventory_Write(this, to.inventory_store); return true; } @@@ -165,25 -129,13 +163,23 @@@ bool Inventory_customize(entity this, e void Inventory_new(PlayerState this) { - Inventory inv = NEW(Inventory), bak = NEW(Inventory); - inv.inventory = bak; - if(!g_duel) - setcefc(inv, Inventory_customize); + Inventory inv = NEW(Inventory); - setcefc(inv, Inventory_customize); ++ if(!g_duel) setcefc(inv, Inventory_customize); Net_LinkEntity((inv.owner = this).inventory = inv, false, 0, Inventory_Send); } void Inventory_delete(entity e) { delete(e.inventory.inventory); delete(e.inventory); } void Inventory_update(entity e) { e.inventory.SendFlags = 0xFFFFFF; } + void InventoryStorage_attach(entity e) { e.inventory_store = NEW(Inventory); e.inventory_store.drawonlytoclient = e; } + void InventoryStorage_detach(entity e) { delete(e.inventory_store); } ++ +void Inventory_ClearAll() { - LOG_INFO("Clearing inventory"); - + FOREACH_CLIENT(IS_PLAYER(it), { + entity store = PS(it); + FOREACH(Items, true, { + store.inventory.inv_items[it.m_id] = 0; + }); + Inventory_update(store); + }); +} - #endif diff --cc qcsrc/common/mutators/mutator/attackertext/cl_attackertext.qc index 2e5dee78f,000000000..0916d4d5b mode 100644,000000..100644 --- a/qcsrc/common/mutators/mutator/attackertext/cl_attackertext.qc +++ b/qcsrc/common/mutators/mutator/attackertext/cl_attackertext.qc @@@ -1,61 -1,0 +1,61 @@@ +#include "cl_attackertext.qh" + +AUTOCVAR_SAVE(cl_attackertext, bool, true, "Draw damage dealt where you hit the enemy"); +AUTOCVAR_SAVE(cl_attackertext_friendlyfire, bool, false, "Show for friendlyfire"); +AUTOCVAR_SAVE(cl_attackertext_time, float, 3, "Time to show"); +AUTOCVAR_SAVE(cl_attackertext_fadetime, float, 2, "Time to fade"); +AUTOCVAR_SAVE(cl_attackertext_decolorize, int, 1, "1 = decolorize names when teamplay, 2 = decolorize always"); + +REGISTER_MUTATOR(attackertext, true); + +MUTATOR_HOOKFUNCTION(attackertext, DrawInfoMessages) +{ + if (autocvar_cl_attackertext == 0) return false; + + float fade_start = max(0, autocvar_cl_attackertext_time); + float fade_time = max(0, autocvar_cl_attackertext_fadetime); + + if (last_attack_time && last_attack_time > time - fade_start - fade_time) { + vector pos = M_ARGV(0, vector); + vector mySize = M_ARGV(1, vector); + vector fontsize = '0.2 0.2 0' * mySize.y; + int img_curr_group = M_ARGV(2, int); + + float alpha_ = 0; + + if (last_attack_time + fade_start > time) + alpha_ = panel_fg_alpha; + else if (fade_time != 0) + alpha_ = panel_fg_alpha - bound(0, (time - last_attack_time - fade_start) * (1 / fade_time), 1); + else + return true; + + pos = InfoMessages_drawstring(last_attack_name, pos, mySize, alpha_, fontsize); + img_curr_group = -1; + + return true; + } + + return false; +} + + +NET_HANDLE(attackertext, bool isNew) +{ + int server_entity_index = ReadByte(); + int flags = ReadByte(); + bool friendlyfire = flags & ATFLAG_SAMETEAM; + + return = true; + + if (autocvar_cl_attackertext == 0) return; + if (friendlyfire && autocvar_cl_attackertext_friendlyfire == 0) return; + + string s = entcs_GetName(server_entity_index - 1); + if ((autocvar_cl_attackertext_decolorize == 1 && teamplay) || autocvar_cl_attackertext_decolorize == 2) - s = playername(s, entcs_GetTeam(server_entity_index - 1)); ++ s = playername(s, entcs_GetTeam(server_entity_index - 1), true); + + last_attack_time = time; + strfree(last_attack_name); + strcpy(last_attack_name, s); +} diff --cc qcsrc/common/net_linked.qh index c431c601d,6651c6cb9..4e888e7c3 --- a/qcsrc/common/net_linked.qh +++ b/qcsrc/common/net_linked.qh @@@ -7,9 -7,6 +7,10 @@@ REGISTER_NET_TEMP(TE_CSQC_PINGPLREPORT REGISTER_NET_TEMP(TE_CSQC_WEAPONCOMPLAIN) REGISTER_NET_TEMP(TE_CSQC_VEHICLESETUP) +REGISTER_NET_TEMP(TE_CSQC_TEAMNAMES) +REGISTER_NET_TEMP(TE_CSQC_CHATSOUND) ++REGISTER_NET_TEMP(TE_CSQC_WEAPONPICKUP) + const int RACE_NET_CHECKPOINT_HIT_QUALIFYING = 0; // byte checkpoint, short time, short recordtime, string recordholder const int RACE_NET_CHECKPOINT_CLEAR = 1; const int RACE_NET_CHECKPOINT_NEXT_QUALIFYING = 2; // byte nextcheckpoint, short recordtime, string recordholder diff --cc qcsrc/server/chat.qc index 000000000,204ed1341..beac6e263 mode 000000,100644..100644 --- a/qcsrc/server/chat.qc +++ b/qcsrc/server/chat.qc @@@ -1,0 -1,581 +1,618 @@@ + #include "chat.qh" + + #include + #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) || source.caplayer)) + 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) ++ if (source) { + namestr = playername(source.netname, source.team, (autocvar_g_chat_teamcolors && IS_PLAYER(source))); ++ ++ if (IS_DEAD(source) || source.frags == FRAGS_PLAYER_OUT_OF_GAME) ++ namestr = strcat("(DEAD) ", namestr); ++ else if (IS_OBSERVER(source) || IS_SPEC(source)) ++ namestr = strcat("(s) ", namestr); ++ } + + 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"); + 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"); + } + 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); + } + else + msgstr = strcat("\{1}\{13}", 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) || source.caplayer) && !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) || source.caplayer) && !game_stopped + && (IS_PLAYER(privatesay) || privatesay.caplayer) && 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) || it.caplayer) && 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) || it.caplayer) && 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)); ++ ++ if(!play_chatsound(source, msgin)) ++ 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(!play_chatsound(source, msgin)) ++ 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; + } + ++bool play_chatsound(entity source, string msgin) ++{ ++ if(autocvar_sv_chat_sounds && CS(source).cvar_cl_chat_sounds) { ++ var .float flood_sound = floodcontrol_chatsound; ++ ++ if (source.(flood_sound) < time - autocvar_sv_chat_sounds_flood) { ++ string rawmsg; ++ bool found = false; ++ rawmsg = strreplace("\n", " ", msgin); ++ ++ FOREACH_WORD(autocvar_sv_chat_sounds_list, it == rawmsg, { found = true; }); ++ if (found) { ++ FOREACH_CLIENT(IS_REAL_CLIENT(it) && CS(it).cvar_cl_chat_sounds, { ++ msg_entity = it; ++ WriteHeader(MSG_ONE, TE_CSQC_CHATSOUND); ++ WriteString(MSG_ONE, rawmsg); ++ }); ++ source.(flood_sound) = time; ++ return true; ++ } ++ } ++ } ++ ++ return false; ++} ++ + 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/chat.qh index 000000000,9c30e89a4..1c45d3db7 mode 000000,100644..100644 --- a/qcsrc/server/chat.qh +++ b/qcsrc/server/chat.qh @@@ -1,0 -1,50 +1,52 @@@ + #pragma once + + const float NUM_NEAREST_ENTITIES = 4; + entity nearest_entity[NUM_NEAREST_ENTITIES]; + float nearest_length[NUM_NEAREST_ENTITIES]; + + .float floodcontrol_chat; + .float floodcontrol_chatteam; + .float floodcontrol_chattell; + .float floodcontrol_voice; + .float floodcontrol_voiceteam; ++.float floodcontrol_chatsound; + + #define CHAT_NOSPECTATORS() ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)) + + int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol); ++bool play_chatsound(entity source, string msgin); + + string NearestLocation(vector p); + + string formatmessage(entity this, string msg); + + /// \brief Print the string to the client's chat. + /// \param[in] client Client to print to. + /// \param[in] text Text to print. + void PrintToChat(entity client, string text); + + /// \brief Print the string to the client's chat if the server cvar "developer" + /// is not 0. + /// \param[in] client Client to print to. + /// \param[in] text Text to print. + void DebugPrintToChat(entity client, string text); + + /// \brief Prints the string to all clients' chat. + /// \param[in] text Text to print. + void PrintToChatAll(string text); + + /// \brief Prints the string to all clients' chat if the server cvar "developer" + /// is not 0. + /// \param[in] text Text to print. + void DebugPrintToChatAll(string text); + + /// \brief Print the string to chat of all clients of the specified team. + /// \param[in] team_num Team to print to. See NUM_TEAM constants. + /// \param[in] text Text to print. + void PrintToChatTeam(int team_num, string text); + + /// \brief Print the string to chat of all clients of the specified team if the + /// server cvar "developer" is not 0. + /// \param[in] team_num Team to print to. See NUM_TEAM constants. + /// \param[in] text Text to print. + void DebugPrintToChatTeam(int team_num, string text); diff --cc qcsrc/server/client.qh index a36758b67,abda1204a..2a447b635 --- a/qcsrc/server/client.qh +++ b/qcsrc/server/client.qh @@@ -399,11 -350,4 +354,9 @@@ void Join(entity this) #define SPECTATE_COPY() ACCUMULATE void SpectateCopy(entity this, entity spectatee) #define SPECTATE_COPYFIELD(fld) SPECTATE_COPY() { this.(fld) = spectatee.(fld); } - int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol); - const int MAX_SPECTATORS = 7; + +float _medal_times; +#define Give_Medal(entity,medalname) \ + _medal_times = GameRules_scoring_add(entity, MEDAL_##medalname, 1); \ + Send_Notification(NOTIF_ONE, entity, MSG_MEDAL, MEDAL_##medalname, _medal_times); diff --cc qcsrc/server/command/cmd.qc index 2772e4f2c,106da7720..0daef91ef --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@@ -382,11 -371,11 +371,11 @@@ void ClientCommand_ready(entity caller { case CMD_REQUEST_COMMAND: { - if (IS_CLIENT(caller)) + if (IS_CLIENT(caller) && caller.last_ready < time - 3) // anti-spam { - if (warmup_stage || sv_ready_restart || g_race_qualifying == 2) + if (warmup_stage || autocvar_sv_ready_restart || g_race_qualifying == 2) { - if (!readyrestart_happened || sv_ready_restart_repeatable) + if (!readyrestart_happened || autocvar_sv_ready_restart_repeatable) { if (time < game_starttime) // game is already restarting return; @@@ -394,16 -383,14 +383,16 @@@ { caller.ready = false; if(IS_PLAYER(caller) || caller.caplayer == 1) - bprint("\{1}", playername(caller, false), "^2 is ^1NOT^2 ready\n"); - bprint(playername(caller.netname, caller.team, false), "^2 is ^1NOT^2 ready\n"); ++ bprint("\{1}", playername(caller.netname, caller.team, false), "^2 is ^1NOT^2 ready\n"); } else { caller.ready = true; if(IS_PLAYER(caller) || caller.caplayer == 1) - bprint("\{1}", playername(caller, false), "^2 is ready\n"); - bprint(playername(caller.netname, caller.team, false), "^2 is ready\n"); ++ bprint("\{1}", playername(caller.netname, caller.team, false), "^2 is ready\n"); } + + caller.last_ready = time; // cannot reset the game while a timeout is active! if (!timeout_status) ReadyCount(); diff --cc qcsrc/server/command/common.qc index 981e79206,e4299039e..44ab98342 --- a/qcsrc/server/command/common.qc +++ b/qcsrc/server/command/common.qc @@@ -774,10 -752,10 +774,10 @@@ void CommonCommand_timeout(int request timeout_time = autocvar_sv_timeout_length; timeout_leadtime = autocvar_sv_timeout_leadtime; - timeout_handler = spawn(); + timeout_handler = new(timeout_handler); setthink(timeout_handler, timeout_handler_think); timeout_handler.nextthink = time; // always let the entity think asap - + Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_TIMEOUT); } } diff --cc qcsrc/server/damage.qc index 6aa915507,fd2e9f2a6..9ed80e0e1 --- a/qcsrc/server/damage.qc +++ b/qcsrc/server/damage.qc @@@ -330,7 -315,7 +331,9 @@@ void Obituary(entity attacker, entity i Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname); Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), playername(targ, true), playername(attacker, true), deathlocation, CS(targ).killcount); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount); ++ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), ++ playername(targ.netname, targ.team, true), playername(attacker.netname, attacker.team, true), ++ deathlocation, CS(targ).killcount); // In this case, the death message will ALWAYS be "foo was betrayed by bar" // No need for specific death/weapon messages... @@@ -436,8 -414,8 +439,8 @@@ if(deathtype == DEATH_BUFF.m_id) f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id; - if (!Obituary_WeaponDeath(targ, attacker, true, deathtype, playername(targ, true), playername(attacker, true), deathlocation, CS(targ).killcount, kill_count_to_attacker)) - Obituary_SpecialDeath(targ, true, deathtype, playername(targ, true), playername(attacker, true), deathlocation, CS(targ).killcount, kill_count_to_attacker, f3); - if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker)) - Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3); ++ if (!Obituary_WeaponDeath(targ, attacker, true, deathtype, playername(targ.netname, targ.team, true), playername(attacker.netname, targ.team, true), deathlocation, CS(targ).killcount, kill_count_to_attacker)) ++ Obituary_SpecialDeath(targ, true, deathtype, playername(targ.netname, targ.team, true), playername(attacker.netname, attacker.team, true), deathlocation, CS(targ).killcount, kill_count_to_attacker, f3); } } @@@ -454,7 -432,7 +457,7 @@@ case DEATH_HURTTRIGGER: { Obituary_SpecialDeath(targ, false, deathtype, - playername(targ, true), - targ.netname, ++ playername(targ.netname, targ.team, true), inflictor.message, deathlocation, CS(targ).killcount, @@@ -466,7 -444,7 +469,7 @@@ case DEATH_CUSTOM: { Obituary_SpecialDeath(targ, false, deathtype, - playername(targ, true), - targ.netname, ++ playername(targ.netname, targ.team, true), ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage), deathlocation, CS(targ).killcount, @@@ -477,7 -455,7 +480,7 @@@ default: { - Obituary_SpecialDeath(targ, false, deathtype, playername(targ, true), deathlocation, "", CS(targ).killcount, 0, 0); - Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); ++ Obituary_SpecialDeath(targ, false, deathtype, playername(targ.netname, targ.team, true), deathlocation, "", CS(targ).killcount, 0, 0); break; } } diff --cc qcsrc/server/scores.qc index 99b55083b,3113e8a96..8906547c4 --- a/qcsrc/server/scores.qc +++ b/qcsrc/server/scores.qc @@@ -511,17 -511,9 +511,17 @@@ void WinningConditionHelper(entity this } }); - WinningConditionHelper_equality = (PlayerScore_Compare(winnerscorekeeper, secondscorekeeper, 0) == 0); + WinningConditionHelper_equality = (PlayerScore_Compare(winnerscorekeeper, secondscorekeeper, false) == 0); if(WinningConditionHelper_equality) + { + WinningConditionHelper_equality_one = WinningConditionHelper_winner; + WinningConditionHelper_equality_two = WinningConditionHelper_second; WinningConditionHelper_winner = WinningConditionHelper_second = NULL; + } + else + { + WinningConditionHelper_equality_one = WinningConditionHelper_equality_two = NULL; + } WinningConditionHelper_topscore = winnerscorekeeper.scores_primary; WinningConditionHelper_secondscore = secondscorekeeper.scores_primary; diff --cc qcsrc/server/world.qc index 4b930263c,0aed2e507..4ab109901 --- a/qcsrc/server/world.qc +++ b/qcsrc/server/world.qc @@@ -1017,12 -1009,9 +1017,12 @@@ spawnfunc(worldspawn modname = strzone(modname); WinningConditionHelper(this); // set worldstatus - + world_initialized = 1; __spawnfunc_spawn_all(); + + if(!warmup_stage) + round_handler_Activate(true); } spawnfunc(light) @@@ -1760,11 -1397,7 +1444,6 @@@ void ClearWinners( FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; }); } - void ShuffleMaplist() - { - cvar_set("g_maplist", shufflewords(autocvar_g_maplist)); - } - -int fragsleft_last; float WinningCondition_Scores(float limit, float leadlimit) { // TODO make everything use THIS winning condition (except LMS)