3 #include <common/gamemodes/_mod.qh>
4 #include <common/mapobjects/target/location.qh>
5 #include <common/mapobjects/triggers.qh>
6 #include <common/teams.qh>
7 #include <common/util.qh>
8 #include <common/weapons/weapon.qh>
9 #include <common/wepent.qh>
10 #include <server/command/common.qh>
11 #include <server/gamelog.qh>
12 #include <server/main.qh>
13 #include <server/mapvoting.qh>
14 #include <server/miscfunctions.qh>
17 * message "": do not say, just test flood control
23 int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
25 if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ")
26 msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
29 msgin = formatmessage(source, msgin);
32 if (!(IS_PLAYER(source) || source.caplayer))
33 colorstr = "^0"; // black for spectators
35 colorstr = Team_ColorCode(source.team);
48 msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
51 * using bprint solves this... me stupid
52 // how can we prevent the message from appearing in a listen server?
53 // for now, just give "say" back and only handle say_team
56 clientcommand(source, strcat("say ", msgin));
63 namestr = playername(source.netname, source.team, (autocvar_g_chat_teamcolors && IS_PLAYER(source)));
65 string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
67 string msgstr = "", cmsgstr = "";
68 string privatemsgprefix = string_null;
69 int privatemsgprefixlen = 0;
72 bool found_me = false;
73 if(strstrofs(msgin, "/me", 0) >= 0)
76 string newnamestr = ((teamsay) ? strcat(colorstr, "(", colorprefix, namestr, colorstr, ")", "^7") : strcat(colorprefix, namestr, "^7"));
77 FOREACH_WORD(msgin, true,
79 if(strdecolorize(it) == "/me")
82 newmsgin = cons(newmsgin, newnamestr);
85 newmsgin = cons(newmsgin, it);
92 msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
93 privatemsgprefixlen = strlen(msgstr);
94 msgstr = strcat(msgstr, msgin);
95 cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
96 privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay.netname, privatesay.team, (autocvar_g_chat_teamcolors && IS_PLAYER(privatesay))), ": ^7");
102 //msgin = strreplace("/me", "", msgin);
103 //msgin = substring(msgin, 3, strlen(msgin));
104 //msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
105 msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
108 msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
109 cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
115 //msgin = strreplace("/me", "", msgin);
116 //msgin = substring(msgin, 3, strlen(msgin));
117 //msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
118 msgstr = strcat("\{1}^4* ^7", msgin);
122 msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
123 msgstr = strcat(msgstr, msgin);
127 msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
130 string fullmsgstr = msgstr;
131 string fullcmsgstr = cmsgstr;
135 var .float flood_field = floodcontrol_chat;
136 if(floodcontrol && source)
138 float flood_spl, flood_burst, flood_lmax;
141 flood_spl = autocvar_g_chat_flood_spl_tell;
142 flood_burst = autocvar_g_chat_flood_burst_tell;
143 flood_lmax = autocvar_g_chat_flood_lmax_tell;
144 flood_field = floodcontrol_chattell;
148 flood_spl = autocvar_g_chat_flood_spl_team;
149 flood_burst = autocvar_g_chat_flood_burst_team;
150 flood_lmax = autocvar_g_chat_flood_lmax_team;
151 flood_field = floodcontrol_chatteam;
155 flood_spl = autocvar_g_chat_flood_spl;
156 flood_burst = autocvar_g_chat_flood_burst;
157 flood_lmax = autocvar_g_chat_flood_lmax;
158 flood_field = floodcontrol_chat;
160 flood_burst = max(0, flood_burst - 1);
161 // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
163 // do flood control for the default line size
166 getWrappedLine_remaining = msgstr;
169 while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
171 msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
174 msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
176 if(getWrappedLine_remaining != "")
178 msgstr = strcat(msgstr, "\n");
182 if (time >= source.(flood_field))
184 source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
194 if (time >= source.(flood_field))
195 source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
200 if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
201 source.(flood_field) = flood = 0;
204 string sourcemsgstr, sourcecmsgstr;
205 if(flood == 2) // cannot happen for empty msgstr
207 if(autocvar_g_chat_flood_notify_flooder)
209 sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
214 sourcemsgstr = fullmsgstr;
215 sourcecmsgstr = fullcmsgstr;
221 sourcemsgstr = msgstr;
222 sourcecmsgstr = cmsgstr;
225 if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped
226 && (teamsay || CHAT_NOSPECTATORS()))
228 teamsay = -1; // spectators
232 LOG_INFO("NOTE: ", playername(source.netname, source.team, IS_PLAYER(source)), "^7 is flooding.");
234 // build sourcemsgstr by cutting off a prefix and replacing it by the other one
236 sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
239 if(source && CS(source).muted)
241 // always fake the message
246 if (autocvar_g_chat_flood_notify_flooder)
248 sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
259 if (privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped
260 && (IS_PLAYER(privatesay) || privatesay.caplayer) && CHAT_NOSPECTATORS())
262 ret = -1; // just hide the message completely
265 MUTATOR_CALLHOOK(ChatMessage, source, ret);
266 ret = M_ARGV(1, int);
268 string event_log_msg = "";
270 if(sourcemsgstr != "" && ret != 0)
272 if(ret < 0) // faked message, because the player is muted
274 sprint(source, sourcemsgstr);
275 if(sourcecmsgstr != "" && !privatesay)
276 centerprint(source, sourcecmsgstr);
278 else if(privatesay) // private message, between 2 people only
280 sprint(source, sourcemsgstr);
281 if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
282 if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source))
284 sprint(privatesay, msgstr);
286 centerprint(privatesay, cmsgstr);
289 else if ( teamsay && CS(source).active_minigame )
291 sprint(source, sourcemsgstr);
292 dedicated_print(msgstr); // send to server console too
293 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
296 event_log_msg = sprintf(":chat_minigame:%d:%s:%s", source.playerid, CS(source).active_minigame.netname, msgin);
299 else if(teamsay > 0) // team message, only sent to team mates
301 sprint(source, sourcemsgstr);
302 dedicated_print(msgstr); // send to server console too
303 if(sourcecmsgstr != "")
304 centerprint(source, sourcecmsgstr);
305 FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
308 centerprint(it, cmsgstr);
310 event_log_msg = sprintf(":chat_team:%d:%d:%s", source.playerid, source.team, strreplace("\n", " ", msgin));
312 else if(teamsay < 0) // spectator message, only sent to spectators
314 sprint(source, sourcemsgstr);
315 dedicated_print(msgstr); // send to server console too
316 FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
319 event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin));
324 sprint(source, sourcemsgstr);
325 dedicated_print(msgstr); // send to server console too
326 MX_Say(strcat(playername(source.netname, source.team, IS_PLAYER(source)), "^7: ", msgin));
328 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
331 event_log_msg = sprintf(":chat:%d:%s", source.playerid, strreplace("\n", " ", msgin));
335 if (autocvar_sv_eventlog && (event_log_msg != "")) {
336 GameLogEcho(event_log_msg);
342 entity findnearest(vector point, bool checkitems, vector axismod)
347 IL_EACH(((checkitems) ? g_items : g_locations), ((checkitems) ? (it.target == "###item###") : (it.classname == "target_location")),
349 if ((it.items == IT_KEY1 || it.items == IT_KEY2) && it.target == "###item###")
354 dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
355 float len = vlen2(dist);
358 for (l = 0; l < num_nearest; ++l)
360 if (len < nearest_length[l])
364 // now i tells us where to insert at
365 // INSERTION SORT! YOU'VE SEEN IT! RUN!
366 if (l < NUM_NEAREST_ENTITIES)
368 for (int j = NUM_NEAREST_ENTITIES - 1; j >= l; --j)
370 nearest_length[j + 1] = nearest_length[j];
371 nearest_entity[j + 1] = nearest_entity[j];
373 nearest_length[l] = len;
374 nearest_entity[l] = it;
375 if (num_nearest < NUM_NEAREST_ENTITIES)
376 num_nearest = num_nearest + 1;
380 // now use the first one from our list that we can see
381 for (int j = 0; j < num_nearest; ++j)
383 traceline(point, nearest_entity[j].origin, true, NULL);
384 if (trace_fraction == 1)
387 LOG_TRACEF("Nearest point (%s) is not visible, using a visible one.", nearest_entity[0].netname);
388 return nearest_entity[j];
392 if (num_nearest == 0)
395 LOG_TRACE("Not seeing any location point, using nearest as fallback.");
397 dprint("Candidates were: ");
398 for(j = 0; j < num_nearest; ++j)
402 dprint(nearest_entity[j].netname);
407 return nearest_entity[0];
410 string NearestLocation(vector p)
412 string ret = "somewhere";
413 entity loc = findnearest(p, false, '1 1 1');
418 loc = findnearest(p, true, '1 1 4');
425 string PlayerHealth(entity this)
427 float myhealth = floor(GetResource(this, RES_HEALTH));
430 else if(myhealth == -2342 || (myhealth == 2342 && mapvote_initialized))
432 else if(myhealth <= 0 || IS_DEAD(this))
434 return ftos(myhealth);
437 string WeaponNameFromWeaponentity(entity this, .entity weaponentity)
439 entity wepent = this.(weaponentity);
442 else if(wepent.m_weapon != WEP_Null)
443 return wepent.m_weapon.m_name;
444 else if(wepent.m_switchweapon != WEP_Null)
445 return wepent.m_switchweapon.m_name;
446 return "none"; //REGISTRY_GET(Weapons, wepent.cnt).m_name;
449 string formatmessage(entity this, string msg)
453 vector cursor = '0 0 0';
454 entity cursor_ent = NULL;
461 MUTATOR_CALLHOOK(PreFormatMessage, this, msg);
462 msg = M_ARGV(1, string);
466 break; // too many replacements
469 p1 = strstrofs(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
470 p2 = strstrofs(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
485 WarpZone_crosshair_trace_plusvisibletriggers(this);
486 cursor = trace_endpos;
487 cursor_ent = trace_ent;
491 replacement = substring(msg, p, 2);
492 escape = substring(msg, p + 1, 1);
494 .entity weaponentity = weaponentities[0]; // TODO: unhardcode
498 case "%": replacement = "%"; break;
499 case "\\":replacement = "\\"; break;
500 case "n": replacement = "\n"; break;
501 case "a": replacement = ftos(floor(GetResource(this, RES_ARMOR))); break;
502 case "h": replacement = PlayerHealth(this); break;
503 case "l": replacement = NearestLocation(this.origin); break;
504 case "y": replacement = NearestLocation(cursor); break;
505 case "d": replacement = NearestLocation(this.death_origin); break;
506 case "w": replacement = WeaponNameFromWeaponentity(this, weaponentity); break;
507 case "W": replacement = GetAmmoName(this.(weaponentity).m_weapon.ammo_type); break;
508 case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break;
509 case "s": replacement = ftos(vlen(this.velocity - this.velocity_z * '0 0 1')); break;
510 case "S": replacement = ftos(vlen(this.velocity)); break;
511 case "t": replacement = seconds_tostring(ceil(max(0, autocvar_timelimit * 60 + game_starttime - time))); break;
512 case "T": replacement = seconds_tostring(floor(time - game_starttime)); break;
515 MUTATOR_CALLHOOK(FormatMessage, this, escape, replacement, msg);
516 replacement = M_ARGV(2, string);
521 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
522 p = p + strlen(replacement);
528 void PrintToChat(entity client, string text)
530 text = strcat("\{1}^7", text, "\n");
531 sprint(client, text);
535 void DebugPrintToChat(entity client, string text)
537 if (autocvar_developer > 0)
539 PrintToChat(client, text);
544 void PrintToChatAll(string text)
546 text = strcat("\{1}^7", text, "\n");
551 void DebugPrintToChatAll(string text)
553 if (autocvar_developer > 0)
555 PrintToChatAll(text);
560 void PrintToChatTeam(int team_num, string text)
562 text = strcat("\{1}^7", text, "\n");
563 FOREACH_CLIENT(IS_REAL_CLIENT(it),
565 if (it.team == team_num)
573 void DebugPrintToChatTeam(int team_num, string text)
575 if (autocvar_developer > 0)
577 PrintToChatTeam(team_num, text);