]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications/all.qh
Merge branch 'master' into z411/bai-server
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / notifications / all.qh
1 #pragma once
2
3 #include <common/command/_mod.qh>
4
5 #include <common/constants.qh>
6 #include <common/teams.qh>
7 #include <common/util.qh>
8 #include <common/sounds/sound.qh>
9 #include <common/weapons/all.qh>
10
11 // Operator for bold notifications
12 #define BOLD_OPERATOR "^BOLD"
13
14 /** main types/groups of notifications */
15 ENUMCLASS(MSG)
16         /** "Global" AND "personal" announcer messages */
17         CASE(MSG, ANNCE)
18         /** "Global" information messages */
19         CASE(MSG, INFO)
20         /** "Personal" centerprint messages */
21         CASE(MSG, CENTER)
22         /** Subcall MSG_INFO and/or MSG_CENTER notifications */
23         CASE(MSG, MULTI)
24         /** Choose which subcall wrapper to activate */
25         CASE(MSG, CHOICE)
26         /** Kill centerprint message @deprecated */
27         CASE(MSG, CENTER_KILL)
28         /** Medal notification */
29         CASE(MSG, MEDAL)
30 ENUMCLASS_END(MSG)
31
32 string Get_Notif_TypeName(MSG net_type)
33 {
34         switch (net_type)
35         {
36                 case MSG_ANNCE: return "MSG_ANNCE";
37                 case MSG_INFO: return "MSG_INFO";
38                 case MSG_CENTER: return "MSG_CENTER";
39                 case MSG_MULTI: return "MSG_MULTI";
40                 case MSG_CHOICE: return "MSG_CHOICE";
41                 case MSG_MEDAL: return "MSG_MEDAL";
42                 case MSG_CENTER_KILL: return "MSG_CENTER_KILL";
43         }
44         LOG_WARNF("Get_Notif_TypeName(%d): Improper net type!", ORDINAL(net_type));
45         return "";
46 }
47 //LegendGuard adds CASE(CPID, MMM) after TIMEIN for MMM 20-02-2021
48 ENUMCLASS(CPID)
49         CASE(CPID, ASSAULT_ROLE)
50         CASE(CPID, ROUND)
51         CASE(CPID, CAMPCHECK)
52         CASE(CPID, CTF_CAPSHIELD)
53         CASE(CPID, CTF_LOWPRIO)
54         CASE(CPID, CTF_PASS)
55         CASE(CPID, STALEMATE)
56         CASE(CPID, NADES)
57         CASE(CPID, IDLING)
58         CASE(CPID, ITEM)
59         CASE(CPID, PREVENT_JOIN)
60         CASE(CPID, KEEPAWAY)
61         CASE(CPID, KEEPAWAY_WARN)
62         CASE(CPID, KEYHUNT)
63         CASE(CPID, KEYHUNT_OTHER)
64         CASE(CPID, LMS)
65         CASE(CPID, MISSING_TEAMS)
66         CASE(CPID, MISSING_PLAYERS)
67         CASE(CPID, MISSING_READY)
68         CASE(CPID, INSTAGIB_FINDAMMO)
69         CASE(CPID, MOTD)
70         CASE(CPID, NIX)
71         CASE(CPID, ONSLAUGHT)
72         CASE(CPID, ONS_CAPSHIELD)
73         CASE(CPID, OVERTIME)
74         CASE(CPID, POWERUP)
75         CASE(CPID, RACE_FINISHLAP)
76         CASE(CPID, TEAMCHANGE)
77         CASE(CPID, TIMEOUT)
78         CASE(CPID, TIMEIN)
79         CASE(CPID, MMM)
80         CASE(CPID, VEHICLES)
81         CASE(CPID, VEHICLES_OTHER)
82         /** always last */
83         CASE(CPID, LAST)
84 ENUMCLASS_END(CPID)
85
86 USING(Notification, entity);
87
88 // used for notification system multi-team identifiers
89 #define APP_TEAM_NUM(num, prefix) ((num == NUM_TEAM_1) ? prefix##_RED : ((num == NUM_TEAM_2) ? prefix##_BLUE : ((num == NUM_TEAM_3) ? prefix##_YELLOW : prefix##_PINK)))
90 #define APP_NUM(num, prefix) ((num) ? APP_TEAM_NUM(num, prefix) : prefix##_NEUTRAL)
91
92 #define EIGHT_VARS_TO_VARARGS_VARLIST \
93         VARITEM(1, 0, s1) \
94         VARITEM(2, 0, XPD(s1, s2)) \
95         VARITEM(3, 0, XPD(s1, s2, s3)) \
96         VARITEM(4, 0, XPD(s1, s2, s3, s4)) \
97         VARITEM(0, 1, f1) \
98         VARITEM(1, 1, XPD(s1, f1)) \
99         VARITEM(2, 1, XPD(s1, s2, f1)) \
100         VARITEM(3, 1, XPD(s1, s2, s3, f1)) \
101         VARITEM(4, 1, XPD(s1, s2, s3, s4, f1)) \
102         VARITEM(0, 2, XPD(f1, f2)) \
103         VARITEM(1, 2, XPD(s1, f1, f2)) \
104         VARITEM(2, 2, XPD(s1, s2, f1, f2)) \
105         VARITEM(3, 2, XPD(s1, s2, s3, f1, f2)) \
106         VARITEM(4, 2, XPD(s1, s2, s3, s4, f1, f2)) \
107         VARITEM(0, 3, XPD(f1, f2, f3)) \
108         VARITEM(1, 3, XPD(s1, f1, f2, f3)) \
109         VARITEM(2, 3, XPD(s1, s2, f1, f2, f3)) \
110         VARITEM(3, 3, XPD(s1, s2, s3, f1, f2, f3)) \
111         VARITEM(4, 3, XPD(s1, s2, s3, s4, f1, f2, f3)) \
112         VARITEM(0, 4, XPD(f1, f2, f3, f4)) \
113         VARITEM(1, 4, XPD(s1, f1, f2, f3, f4)) \
114         VARITEM(2, 4, XPD(s1, s2, f1, f2, f3, f4)) \
115         VARITEM(3, 4, XPD(s1, s2, s3, f1, f2, f3, f4)) \
116         VARITEM(4, 4, XPD(s1, s2, s3, s4, f1, f2, f3, f4))
117
118 void Destroy_All_Notifications();
119 void Create_Notification_Entity(entity notif,
120         float var_default,
121         float var_cvar,
122         MSG typeId,
123         string namestring,
124         int teamnum);
125 void Create_Notification_Entity_Annce(entity notif,
126                                                                                 float var_cvar,
127                                                                                 string namestring,
128                                                                                 /* MSG_ANNCE */
129                                                                                 float channel,
130                                                                                 string snd,
131                                                                                 float vol,
132                                                                                 float position,
133                                                                                 float queuetime);
134
135 void Create_Notification_Entity_InfoCenter(entity notif,
136                                                                                         float var_cvar,
137                                                                                         string namestring,
138                                                                                         int strnum,
139                                                                                         int flnum,
140                                                                                         /* MSG_INFO & MSG_CENTER */
141                                                                                         string args,
142                                                                                         string hudargs,
143                                                                                         string icon,
144                                                                                         CPID cpid,
145                                                                                         string durcnt,
146                                                                                         string normal,
147                                                                                         string gentle);
148
149 void Create_Notification_Entity_Multi(entity notif,
150                                                                                 float var_cvar,
151                                                                                 string namestring,
152                                                                                 /* MSG_MULTI */
153                                                                                 Notification anncename,
154                                                                                 Notification infoname,
155                                                                                 Notification centername);
156
157 void Create_Notification_Entity_Choice(entity notif,
158                                                                                 float var_cvar,
159                                                                                 string namestring,
160                                                                                 /* MSG_CHOICE */
161                                                                                 float challow_def,
162                                                                                 float challow_var,
163                                                                                 MSG chtype,
164                                                                                 Notification optiona,
165                                                                                 Notification optionb);
166
167 void Create_Notification_Entity_Medal(entity notif,
168                                                                                 float var_cvar,
169                                                                                 string namestring,
170                                                                                 /* MSG_MEDAL */
171                                                                                 string icon,
172                                                                                 Notification anncename);
173
174 void Dump_Notifications(int fh, bool alsoprint);
175
176 #define DEFAULT_FILENAME "notifications_dump.cfg"
177 // NOTE: dumpeffectinfo, dumpnotifs, dumpturrets and dumpweapons use similar code
178 GENERIC_COMMAND(dumpnotifs, "Dump all notifications into " DEFAULT_FILENAME, false)
179 {
180         switch (request)
181         {
182                 case CMD_REQUEST_COMMAND:
183                 {
184                         #ifdef GAMEQC
185                         string filename = argv(1);
186                         bool alsoprint = false;
187                         if (filename == "")
188                         {
189                                 filename = DEFAULT_FILENAME;
190                                 alsoprint = false;
191                         }
192                         else if (filename == "-")
193                         {
194                                 filename = DEFAULT_FILENAME;
195                                 alsoprint = true;
196                         }
197                         int fh = fopen(filename, FILE_WRITE);
198                         if (fh >= 0)
199                         {
200                                 Dump_Notifications(fh, alsoprint);
201                                 LOG_INFOF("Dumping notifications... File located in ^2data/data/%s^7.", filename);
202                                 fclose(fh);
203                         }
204                         else
205                         {
206                                 LOG_INFOF("^1Error: ^7Could not open file '%s'!", filename);
207                         }
208                         #else
209                         LOG_INFO("Notification dump command only works with cl_cmd and sv_cmd.");
210                         #endif
211                         return;
212                 }
213                 default:
214                 case CMD_REQUEST_USAGE:
215                 {
216                         LOG_HELP("Usage:^3 ", GetProgramCommandPrefix(), " dumpnotifs [<filename>]");
217                         LOG_HELPF("  Where <filename> is the file to write (default is %s),", DEFAULT_FILENAME);
218                         LOG_HELP("  if supplied with '-' output to console as well as default,");
219                         LOG_HELP("  if left blank, it will only write to default.");
220                         return;
221                 }
222         }
223 }
224 #undef DEFAULT_FILENAME
225
226 #ifdef NOTIFICATIONS_DEBUG
227 bool autocvar_notification_debug = false;
228 void Debug_Notification(string input)
229 {
230         switch (autocvar_notification_debug)
231         {
232                 case 1: { LOG_TRACE(input); break; }
233                 case 2: { LOG_INFO(input); break; }
234         }
235 }
236 #endif
237
238 void Local_Notification(MSG net_type, Notification net_name, ...count);
239 /** glue for networking, forwards to `Local_Notification` */
240 void Local_Notification_WOVA(
241         MSG net_type, Notification net_name,
242         float stringcount, float floatcount,
243         string s1, string s2, string s3, string s4,
244         float f1, float f2, float f3, float f4);
245
246 #ifdef CSQC
247 string prev_soundfile;
248 float prev_soundtime;
249 #endif
250
251 #ifdef SVQC
252 IntrusiveList g_notifications;
253 STATIC_INIT(g_notifications) { g_notifications = IL_NEW(); }
254 #endif
255
256 #ifdef SVQC
257 ENUMCLASS(NOTIF)
258         /** send to one client and their spectators */
259         CASE(NOTIF, ONE)
260         /** send ONLY to one client */
261         CASE(NOTIF, ONE_ONLY)
262         /** send only to X team and their spectators */
263         CASE(NOTIF, TEAM)
264         /** send only to X team and their spectators, except for Y person and their spectators */
265         CASE(NOTIF, TEAM_EXCEPT)
266         /** send only to X team; don't include spectators */
267         CASE(NOTIF, TEAM_ONLY)
268         /** send to team X team except for Y person; don't include spectators */
269         CASE(NOTIF, TEAM_ONLY_EXCEPT)
270         /** send to everyone */
271         CASE(NOTIF, ALL)
272         /** send to everyone except X person and their spectators */
273         CASE(NOTIF, ALL_EXCEPT)
274         /** send to all spectators **/
275         CASE(NOTIF, ALL_SPEC)
276 ENUMCLASS_END(NOTIF)
277
278 string Get_Notif_BroadcastName(NOTIF broadcast)
279 {
280         switch (broadcast)
281         {
282                 case NOTIF_ONE: return "NOTIF_ONE";
283                 case NOTIF_ONE_ONLY: return "NOTIF_ONE_ONLY";
284                 case NOTIF_ALL_SPEC: return "NOTIF_ALL_SPEC";
285                 case NOTIF_ALL_EXCEPT: return "NOTIF_ALL_EXCEPT";
286                 case NOTIF_ALL: return "NOTIF_ALL";
287                 case NOTIF_TEAM: return "NOTIF_TEAM";
288                 case NOTIF_TEAM_EXCEPT: return "NOTIF_TEAM_EXCEPT";
289                 case NOTIF_TEAM_ONLY: return "NOTIF_TEAM_ONLY";
290                 case NOTIF_TEAM_ONLY_EXCEPT: return "NOTIF_TEAM_ONLY_EXCEPT";
291         }
292         LOG_WARNF("Get_Notif_BroadcastName(%d): Improper broadcast!", broadcast);
293         return "";
294 }
295
296 void Kill_Notification(
297         NOTIF broadcast, entity client,
298         MSG net_type, CPID net_name);
299 void Send_Notification(
300         NOTIF broadcast, entity client,
301         MSG net_type, Notification net_name,
302         ...count);
303 void Send_Notification_WOVA(
304         NOTIF broadcast, entity client,
305         MSG net_type, Notification net_name,
306         float stringcount, float floatcount,
307         string s1, string s2, string s3, string s4,
308         float f1, float f2, float f3, float f4);
309 void Send_Notification_WOCOVA(
310         NOTIF broadcast, entity client,
311         MSG net_type, Notification net_name,
312         string s1, string s2, string s3, string s4,
313         float f1, float f2, float f3, float f4);
314 #endif
315
316 // ===========================
317 //  Special CVAR Declarations
318 // ===========================
319
320 // MAKE SURE THIS IS ALWAYS SYNCHRONIZED WITH THE DUMP
321 // NOTIFICATIONS FUNCTION IN THE .QC FILE!
322
323 #define NOTIF_ADD_AUTOCVAR(name,defaultvalue) float autocvar_notification_##name = defaultvalue;
324
325 float autocvar_notification_show_location = false;
326 string autocvar_notification_show_location_string = ""; //_(" at the %s");
327 float autocvar_notification_show_sprees = true;
328 float autocvar_notification_show_sprees_info = 3; // 0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker
329 float autocvar_notification_show_sprees_info_newline = true;
330 float autocvar_notification_show_sprees_info_specialonly = true;
331 float autocvar_notification_errors_are_fatal = true;
332 #ifdef SVQC
333 float autocvar_notification_lifetime_runtime = 0.5;
334 float autocvar_notification_lifetime_mapload = 10;
335 #endif
336
337 #ifdef SVQC
338 void Notification_GetCvars(entity this, entity store);
339 float autocvar_notification_server_allows_location = 1; // 0 = no, 1 = yes
340 #else
341 float autocvar_notification_item_centerprinttime = 1.5;
342
343 // 0 = no, 1 = yes, 2 = forced on for all MSG_INFO notifs
344 // DISABLED IN CODE, BUT ENABLED IN CONFIG FOR COMPATIBILITY WITH OLD CLIENTS
345 float autocvar_notification_allow_chatboxprint = 0;
346
347 float autocvar_notification_show_sprees_center = true;
348 float autocvar_notification_show_sprees_center_specialonly = true;
349 #endif
350
351
352 // ============================
353 //  Notification Argument List
354 // ============================
355 /*
356  These arguments get replaced with the Local_Notification_sprintf
357  and other such functions found in all.qc to supply data
358  from networked notifications to their usage in sprintf... It
359  allows for more dynamic data to be inferred by the local
360  notification parser, so that the server does not have to network
361  anything too crazy on a per-client/per-situation basis.
362
363  Pay attention to the CSQC/SVQC relations, some of these are redefined
364  in slightly different ways for different programs, this is because the
365  server does a more conservative approach to the notifs than the client.
366
367  All arguments are swapped into strings, so be sure that your
368  sprintf usage matches with proper %s placement.
369
370  Argument descriptions:
371         s1-s4: string arguments to be literally swapped into sprintf
372         s2loc: s2 string of locations of deaths or other events
373         s3loc: s3 string of locations of deaths or other events
374         f1-f4: float arguments expanded into strings to be swapped into sprintf
375         f1p2dec: f1 float to string with 2 decimal places
376         f2p2dec: f2 float to string with 2 decimal places
377         f2primsec: f2 float primary or secondary selection for weapons
378         f3primsec: f3 float primary or secondary selection for weapons
379         f1secs: count_seconds of f1
380         f1points: point or points depending on f1
381         f1ord: count_ordinal of f1
382         f1time: process_time of f1
383         f1race_time: mmssth of f1
384         f2race_time: mmssth of f2
385         race_col: color of race time/position (i.e. good or bad)
386         race_diff: show time difference between f2 and f3
387         missing_teams: show which teams still need players
388         pass_key: find the keybind for "passing" or "dropping" in CTF game mode
389         nade_key: find the keybind for nade throwing
390         frag_ping: show the ping of a player
391         frag_stats: show health/armor/ping of a player
392         frag_pos: show score status and position in the match of a player
393         spree_cen: centerprint notif for kill spree/how many kills they have
394         spree_inf: info notif for kill spree/how many kills they have
395         spree_end: placed at the end of murder messages to show ending of sprees
396         spree_lost: placed at the end of suicide messages to show losing of sprees
397         item_wepname: return full name of a weapon from weaponid
398         item_wepammo: ammo display for weapon from f1 and f2
399         item_centime: amount of time to display weapon message in centerprint
400         item_buffname: return full name of a buff from buffid
401         death_team: show the full name of the team a player is switching from
402         minigame1_name: return human readable name of a minigame from its id(s1)
403         minigame1_d: return descriptor name of a minigame from its id(s1)
404 */
405
406 const float NOTIF_MAX_ARGS = 7;
407 const float NOTIF_MAX_HUDARGS = 2;
408 const float NOTIF_MAX_DURCNT = 2;
409
410 #ifdef CSQC
411 const int NOTIF_QUEUE_MAX = 10;
412 entity notif_queue_entity[NOTIF_QUEUE_MAX];
413 MSG notif_queue_type[NOTIF_QUEUE_MAX];
414 float notif_queue_time[NOTIF_QUEUE_MAX];
415 float notif_queue_f1[NOTIF_QUEUE_MAX];
416
417 float notif_queue_next_time;
418 int notif_queue_length;
419
420 void Local_Notification_Queue_Process();
421 #endif
422
423 string arg_slot[NOTIF_MAX_ARGS];
424
425 const float ARG_CS_SV_HA = 1; // enabled on CSQC, SVQC, and Hudargs
426 const float ARG_CS_SV_DC = 2; // enabled on CSQC, SVQC, and durcnt centerprint
427 const float ARG_CS_SV = 3; // enabled on CSQC and SVQC
428 const float ARG_CS = 4; // unique result to CSQC
429 const float ARG_SV = 5; // unique result to SVQC
430 const float ARG_DC = 6; // unique result to durcnt/centerprint
431
432 // todo possible idea.... declare how many floats/strings each arg needs, and then dynamically increment the input
433 // this way, we don't need to have duplicates like i.e. s2loc and s3loc?
434
435 string BUFF_NAME(int i);
436
437 #define NOTIF_ARGUMENT_LIST \
438         ARG_CASE(ARG_CS_SV_HA,  "s1",            s1) \
439         ARG_CASE(ARG_CS_SV_HA,  "s2",            s2) \
440         ARG_CASE(ARG_CS_SV_HA,  "s3",            s3) \
441         ARG_CASE(ARG_CS_SV_HA,  "s4",            s4) \
442         ARG_CASE(ARG_CS_SV,     "s2loc",         ((autocvar_notification_show_location && (s2 != "")) ? sprintf(( ((tmp_s = autocvar_notification_show_location_string) != "") ? tmp_s : _(" (near %s)") ), s2) : "")) \
443         ARG_CASE(ARG_CS_SV,     "s3loc",         ((autocvar_notification_show_location && (s3 != "")) ? sprintf(( ((tmp_s = autocvar_notification_show_location_string) != "") ? tmp_s : _(" (near %s)") ), s3) : "")) \
444         ARG_CASE(ARG_CS_SV_DC,  "f1",            ftos(f1)) \
445         ARG_CASE(ARG_CS_SV_DC,  "f2",            ftos(f2)) \
446         ARG_CASE(ARG_CS_SV,     "f3",            ftos(f3)) \
447         ARG_CASE(ARG_CS_SV,     "f4",            ftos(f4)) \
448         ARG_CASE(ARG_CS_SV,     "f1dtime",       ftos_decimals(TIME_DECODE(f1), 2)) \
449         ARG_CASE(ARG_CS_SV,     "f2dtime",       ftos_decimals(TIME_DECODE(f2), 2)) \
450         ARG_CASE(ARG_CS,        "f2primsec",     (f2 ? _("secondary") : _("primary"))) \
451         ARG_CASE(ARG_CS,        "f3primsec",     (f3 ? _("secondary") : _("primary"))) \
452         ARG_CASE(ARG_CS,        "f1secs",        count_seconds(f1)) \
453         ARG_CASE(ARG_CS,        "f1points",      (f1 == 1 ? _("point") : _("points"))) \
454         ARG_CASE(ARG_CS_SV,     "f1ord",         count_ordinal(f1)) \
455         ARG_CASE(ARG_CS_SV,     "f1time",        process_time(2, f1)) \
456         ARG_CASE(ARG_CS_SV_HA,  "f1race_time",   mmssth(f1)) \
457         ARG_CASE(ARG_CS_SV_HA,  "f2race_time",   mmssth(f2)) \
458         ARG_CASE(ARG_CS_SV_HA,  "f3race_time",   mmssth(f3)) \
459         ARG_CASE(ARG_CS_SV,     "race_col",      CCR(((f1 == 1) ? "^F1" : "^F2"))) \
460         ARG_CASE(ARG_CS_SV,     "race_diff",     ((f2 > f3) ? sprintf(CCR("^1[+%s]"), mmssth(f2 - f3)) : sprintf(CCR("^2[-%s]"), mmssth(f3 - f2)))) \
461         ARG_CASE(ARG_CS,        "missing_teams", notif_arg_missing_teams(f1)) \
462         ARG_CASE(ARG_CS,        "pass_key",      getcommandkey(_("drop flag"), "+use")) \
463         ARG_CASE(ARG_CS,        "nade_key",      getcommandkey(_("throw nade"), "dropweapon")) \
464         ARG_CASE(ARG_CS,        "join_key",      getcommandkey(_("jump"), "+jump")) \
465         ARG_CASE(ARG_CS,        "frag_ping",     notif_arg_frag_ping(true, f2)) \
466         ARG_CASE(ARG_CS,        "frag_stats",    notif_arg_frag_stats(f2, f3, f4)) \
467         ARG_CASE(ARG_CS,        "frag_pos",      notif_arg_frag_pos(f2)) \
468         ARG_CASE(ARG_CS,        "spree_cen",     (autocvar_notification_show_sprees ? notif_arg_spree_cen(f1) : "")) \
469         ARG_CASE(ARG_CS_SV,     "spree_inf",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(1, input, s2, f2) : "")) \
470         ARG_CASE(ARG_CS_SV,     "spree_end",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(-1, "", "", f1) : "")) \
471         ARG_CASE(ARG_CS_SV,     "spree_lost",    (autocvar_notification_show_sprees ? notif_arg_spree_inf(-2, "", "", f1) : "")) \
472         ARG_CASE(ARG_CS_SV,     "item_wepname",  REGISTRY_GET(Weapons, f1).m_name) \
473         ARG_CASE(ARG_CS_SV,     "item_buffname", BUFF_NAME(f1)) \
474         ARG_CASE(ARG_CS_SV,     "f3buffname",    BUFF_NAME(f3)) \
475         ARG_CASE(ARG_CS_SV,     "item_wepammo",  (f2 > 0 ? notif_arg_item_wepammo(f1, f2) : "")) \
476         ARG_CASE(ARG_DC,        "item_centime",  ftos(autocvar_notification_item_centerprinttime)) \
477         ARG_CASE(ARG_SV,        "death_team",    Team_ColoredFullName(f1)) \
478         ARG_CASE(ARG_CS,        "death_team",    Team_ColoredFullName(f1 - 1)) \
479         ARG_CASE(ARG_CS_SV_HA,  "minigame1_name",find(NULL,netname,s1).descriptor.message) \
480         ARG_CASE(ARG_CS_SV_HA,  "minigame1_d",   find(NULL,netname,s1).descriptor.netname)
481
482 #define NOTIF_HIT_MAX(count,funcname) MACRO_BEGIN \
483         if(sel_num == count) { backtrace(sprintf("%s: Hit maximum arguments!\n", funcname)); break; } \
484 MACRO_END
485
486 #define NOTIF_HIT_UNKNOWN(token,funcname) { backtrace(sprintf("%s: Hit unknown token in selected string! '%s'\n", funcname, selected)); break; }
487
488 #define KILL_SPREE_LIST \
489         SPREE_ITEM(3, 03, _("TRIPLE FRAG! "), _("%s^K1 made a TRIPLE FRAG! %s^BG"), _("%s^K1 made a TRIPLE SCORE! %s^BG")) \
490         SPREE_ITEM(5, 05, _("RAGE! "), _("%s^K1 unlocked RAGE! %s^BG"), _("%s^K1 made FIVE SCORES IN A ROW! %s^BG")) \
491         SPREE_ITEM(10, 10, _("MASSACRE! "), _("%s^K1 started a MASSACRE! %s^BG"), _("%s^K1 made TEN SCORES IN A ROW! %s^BG")) \
492         SPREE_ITEM(15, 15, _("MAYHEM! "), _("%s^K1 executed MAYHEM! %s^BG"), _("%s^K1 made FIFTEEN SCORES IN A ROW! %s^BG")) \
493
494 #ifdef CSQC
495 // z411 TODO : This actually doesn't work very well.
496 // This gets run before the client gets score updates so it works
497 // fine when you're playing (because frags get updated first)
498 // but it breaks a lot when you're spectating because
499 // we sometimes get the new frag info at different times
500 // (before or after we run this). A suggested fix would
501 // be to do this sorting and comparison in the server.
502 string notif_arg_frag_pos(int score)
503 {
504         entity pl;
505         int place = 1;
506         string str, color, tail;
507         bool tied = false;
508         
509         for(pl = players.sort_next; pl; pl = pl.sort_next) {
510                 if(pl.team == NUM_SPECTATOR) continue;
511                 if(pl.(scores(SP_SCORE)) == score) break;
512                 ++place;
513         }
514         
515         entity prev = pl.sort_prev;
516         entity next = pl.sort_next;
517         if(prev && prev.(scores(SP_SCORE)) == score) {
518                 tied = true;
519                 --place; // We're tied always for the best place
520         }
521         if(next && next.(scores(SP_SCORE)) == score) {
522                 tied = true;
523         }
524         
525         switch(place) {
526                 case 1:
527                         color = "^4";
528                         break;
529                 case 2:
530                         color = "^1";
531                         break;
532                 case 3:
533                         color = "^3";
534                         break;
535                 default:
536                         color = "";
537         }
538         
539         switch(place % 10) {
540                 case 1:
541                         tail = "st";
542                         break;
543                 case 2:
544                         tail = "nd";
545                         break;
546                 case 3:
547                         tail = "rd";
548                         break;
549                 default:
550                         tail = "th";
551         }
552         
553         str = strcat(color, ftos(place), tail);
554         if(tied)
555                 return strcat("Tied for ", str);
556         else
557                 return str;
558 }
559
560 string notif_arg_frag_ping(bool newline, float fping)
561 {
562         string s = newline ? "\n" : " ";
563         if (fping < 0)
564                 return sprintf(CCR(_("%s(^F1Bot^BG)")), s);
565         else
566                 return sprintf(CCR(_("%s(Ping ^F1%d^BG)")), s, fping);
567 }
568
569 string notif_arg_frag_stats(float fhealth, float farmor, float fping)
570 {
571         string s = notif_arg_frag_ping(false, fping);
572         if (fhealth > 1)
573                 return sprintf(CCR(_("\n(Health ^1%d^BG / Armor ^2%d^BG)%s")), fhealth, farmor, s);
574         else
575                 return sprintf(CCR(_("\n(^F4Dead^BG)%s")), s);
576 }
577
578 string notif_arg_missing_teams(float f1)
579 {
580         return strcat(
581                 ((f1 & BIT(0)) ? strcat(Team_ColoredFullName(NUM_TEAM_1), (f1 >> 1) ? ", " : "") : ""),
582                 ((f1 & BIT(1)) ? strcat(Team_ColoredFullName(NUM_TEAM_2), (f1 >> 2) ? ", " : "") : ""),
583                 ((f1 & BIT(2)) ? strcat(Team_ColoredFullName(NUM_TEAM_3), (f1 >> 3) ? ", " : "") : ""),
584                 ((f1 & BIT(3)) ?        Team_ColoredFullName(NUM_TEAM_4)                         : "")
585         );
586 }
587
588 string notif_arg_spree_cen(float spree)
589 {
590         // 0 = off, 1 = target (but only for first victim) and attacker
591         if(autocvar_notification_show_sprees_center)
592         {
593                 if(spree > 1)
594                 {
595                         #define SPREE_ITEM(counta,countb,center,normal,gentle) \
596                                 case counta: { return normal_or_gentle(center, sprintf(_("%d score spree! "), spree)); }
597
598                         switch(spree)
599                         {
600                                 KILL_SPREE_LIST
601                                 default:
602                                 {
603                                         if (!autocvar_notification_show_sprees_center_specialonly)
604                                         {
605                                                 return
606                                                         sprintf(
607                                                                 normal_or_gentle(
608                                                                         _("%d frag spree! "),
609                                                                         _("%d score spree! ")
610                                                                 ),
611                                                                 spree);
612                                         }
613                                         else { return ""; } // don't show spree information if it isn't an achievement
614                                 }
615                         }
616
617                         #undef SPREE_ITEM
618                 }
619                 else if(spree == -1) // first blood
620                 {
621                         return normal_or_gentle(_("First blood! "), _("First score! "));
622                 }
623                 else if(spree == -2) // first victim
624                 {
625                         return normal_or_gentle(_("First victim! "), _("First casualty! "));
626                 }
627         }
628         return "";
629 }
630 #endif
631
632 string notif_arg_spree_inf(float type, string input, string player, float spree)
633 {
634         switch(type)
635         {
636                 case 1: // attacker kill spree
637                 {
638                         // 0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker
639                         // this conditional (& 2) is true for 2 and 3
640                         if(autocvar_notification_show_sprees_info & 2)
641                         {
642                                 #ifdef CSQC
643                                 string spree_newline =
644                                         ( autocvar_notification_show_sprees_info_newline ?
645                                                 ((substring(input, 0, 1) == "\{3}") ? "\n\{3}" : "\n") : "" );
646                                 #else
647                                 string spree_newline =
648                                         (autocvar_notification_show_sprees_info_newline ? "\n" : "");
649                                 #endif
650
651                                 if(spree > 1)
652                                 {
653                                         #define SPREE_ITEM(counta,countb,center,normal,gentle) \
654                                                 case counta: { return sprintf(CCR(normal_or_gentle(normal, gentle)), player, spree_newline); }
655
656                                         switch(spree)
657                                         {
658                                                 KILL_SPREE_LIST
659                                                 default:
660                                                 {
661                                                         if (!autocvar_notification_show_sprees_info_specialonly)
662                                                         {
663                                                                 return
664                                                                         sprintf(
665                                                                                 CCR(normal_or_gentle(
666                                                                                         _("%s^K1 has %d frags in a row! %s^BG"),
667                                                                                         _("%s^K1 made %d scores in a row! %s^BG")
668                                                                                 )),
669                                                                                 player,
670                                                                                 spree,
671                                                                                 spree_newline
672                                                                         );
673                                                         }
674                                                         else { return ""; } // don't show spree information if it isn't an achievement
675                                                 }
676                                         }
677
678                                         #undef SPREE_ITEM
679                                 }
680                                 else if(spree == -1) // firstblood
681                                 {
682                                         return
683                                                 sprintf(
684                                                         CCR(normal_or_gentle(
685                                                                 _("%s^K1 drew first blood! %s^BG"),
686                                                                 _("%s^K1 got the first score! %s^BG")
687                                                         )),
688                                                         player,
689                                                         spree_newline
690                                                 );
691                                 }
692                         }
693                         break;
694                 }
695
696                 case -1: // kill spree ended
697                 {
698                         if((spree > 1) && (autocvar_notification_show_sprees_info & 1))
699                         {
700                                 return
701                                         sprintf(normal_or_gentle(
702                                                 _(", ending their %d frag spree"),
703                                                 _(", ending their %d score spree")
704                                                 ),
705                                                 spree
706                                         );
707                         }
708                         break;
709                 }
710
711                 case -2: // kill spree lost
712                 {
713                         if((spree > 1) && (autocvar_notification_show_sprees_info & 1))
714                         {
715                                 return
716                                         sprintf(normal_or_gentle(
717                                                 _(", losing their %d frag spree"),
718                                                 _(", losing their %d score spree")
719                                                 ),
720                                                 spree
721                                         );
722                         }
723                         break;
724                 }
725         }
726         return "";
727 }
728
729 string notif_arg_item_wepammo(float f1, float f2)
730 {
731         string ammoitems = "";
732         Weapon wep = REGISTRY_GET(Weapons, f1);
733         // TODO: registry handles
734         switch (wep.ammo_type)
735         {
736                 case RES_SHELLS:  ammoitems = ITEM_Shells.m_name;      break;
737                 case RES_BULLETS: ammoitems = ITEM_Bullets.m_name;     break;
738                 case RES_ROCKETS: ammoitems = ITEM_Rockets.m_name;     break;
739                 case RES_CELLS:   ammoitems = ITEM_Cells.m_name;       break;
740                 case RES_PLASMA:  ammoitems = ITEM_Plasma.m_name;      break;
741                 case RES_FUEL:    ammoitems = ITEM_JetpackFuel.m_name; break;
742                 default: return ""; // doesn't use ammo
743         }
744         return sprintf(_(" with %d %s"), f2, ammoitems);
745 }
746
747
748 // ====================================
749 //  Initialization/Create Declarations
750 // ====================================
751
752 // common notification entity values
753 .int nent_default;
754 .bool nent_enabled;
755 .MSG nent_type;
756 .string nent_name;
757 .int nent_stringcount;
758 .int nent_floatcount;
759 .int nent_teamnum;
760
761 // MSG_ANNCE entity values
762 .int nent_channel;
763 .string nent_snd;
764 .float nent_vol;
765 .float nent_position;
766 .float nent_queuetime;
767
768 // MSG_INFO and MSG_CENTER entity values
769 .string nent_args; // used by both
770 .string nent_hudargs; // used by info
771 .string nent_icon; // used by info
772 .CPID nent_cpid; // used by center
773 .string nent_durcnt; // used by center
774 .string nent_string; // used by both
775
776 // MSG_MULTI entity values
777 .entity nent_msgannce;
778 .entity nent_msginfo;
779 .entity nent_msgcenter;
780
781 // MSG_CHOICE entity values
782 .float nent_challow_def;
783 .float nent_challow_var;
784 .entity nent_optiona;
785 .entity nent_optionb;
786
787 // networked notification entity values
788 #ifdef SVQC
789 .NOTIF nent_broadcast;
790 #endif
791 .entity nent_client;
792 .MSG nent_net_type;
793 .float nent_net_name;
794 .string nent_strings[4];
795 .float nent_floats[4];
796
797 #define ACVNN(name) autocvar_notification_##name
798
799 REGISTRY(Notifications, BITS(11))
800 REGISTER_REGISTRY(Notifications)
801 REGISTRY_SORT(Notifications);
802
803 REGISTRY_DEFINE_GET(Notifications, NULL)
804 STATIC_INIT(Notifications) { FOREACH(Notifications, true, it.m_id = i); }
805 REGISTRY_CHECK(Notifications)
806
807 const int NOTIF_CHOICE_MAX = 20;
808 // NOTE: a team choice is actually made of 4 choices (one per team) with the same nent_choice_idx
809 // thus they are counted as 1 in nent_choice_count
810 int nent_choice_count = 0;
811 .int nent_choice_idx;
812 .int msg_choice_choices[NOTIF_CHOICE_MAX]; // set on each player containing MSG_CHOICE choices
813 // initialization error detection
814 bool notif_error;
815 bool notif_global_error;
816
817 STATIC_INIT_LATE(Notif_Choices)
818 {
819         if (nent_choice_count > NOTIF_CHOICE_MAX)
820                 LOG_FATALF("Too many MSG_CHOICE notifications (%d), hit NOTIF_CHOICE_MAX (%d) limit",
821                         nent_choice_count, NOTIF_CHOICE_MAX);
822 }
823
824 string Get_Notif_CvarName(Notification notif)
825 {
826         if(!notif.nent_teamnum)
827                 return notif.nent_name;
828         return substring(notif.nent_name, 0, -strlen(Static_Team_ColorName(notif.nent_teamnum)) - 2);
829 }
830
831 Notification Get_Notif_Ent(MSG net_type, int net_name)
832 {
833         Notification it = REGISTRY_GET(Notifications, net_name);
834         if (it.nent_type != net_type) {
835                 LOG_WARNF("Get_Notif_Ent(%s (%d), %s (%d)): Improper net type '%s'!",
836                         Get_Notif_TypeName(net_type), net_type,
837                         it.registered_id, net_name,
838                         Get_Notif_TypeName(it.nent_type)
839                 );
840                 return NULL;
841         }
842         return it;
843 }
844
845 #define MSG_ANNCE_NOTIF_TEAM(teamnum, name, cvarname, defaultvalue, sound, channel, volume, position, queuetime) \
846         MSG_ANNCE_NOTIF_(teamnum, ANNCE_##name, ANNCE_##cvarname, defaultvalue, sound, channel, volume, position, queuetime)
847
848 #define MSG_ANNCE_NOTIF(name, defaultvalue, sound, channel, volume, position, queuetime) \
849         NOTIF_ADD_AUTOCVAR(ANNCE_##name, defaultvalue) \
850         MSG_ANNCE_NOTIF_(0, ANNCE_##name, ANNCE_##name, defaultvalue, sound, channel, volume, position, queuetime)
851
852 #define MSG_ANNCE_NOTIF_(teamnum, name, cvarname, defaultvalue, sound, channel, volume, position, queuetime) \
853         REGISTER(Notifications, name, m_id, new_pure(msg_annce_notification)) { \
854                 Create_Notification_Entity      (this, defaultvalue, ACVNN(cvarname), MSG_ANNCE, strtoupper(#name), teamnum); \
855                 Create_Notification_Entity_Annce(this, ACVNN(cvarname), strtoupper(#name), \
856                         channel,    /* channel   */ \
857                         sound,      /* snd       */ \
858                         volume,     /* vol       */ \
859                         position,   /* position  */ \
860                         queuetime); /* queuetime */ \
861         }
862
863 #define MSG_INFO_NOTIF_TEAM(teamnum, name, cvarname, defaultvalue, strnum, flnum, args, hudargs, icon, normal, gentle) \
864         MSG_INFO_NOTIF_(teamnum, INFO_##name, INFO_##cvarname, defaultvalue, strnum, flnum, args, hudargs, icon, normal, gentle)
865
866 #define MSG_INFO_NOTIF(name, defaultvalue, strnum, flnum, args, hudargs, icon, normal, gentle) \
867         NOTIF_ADD_AUTOCVAR(INFO_##name, defaultvalue) \
868         MSG_INFO_NOTIF_(0, INFO_##name, INFO_##name, defaultvalue, strnum, flnum, args, hudargs, icon, normal, gentle)
869
870 #define MSG_INFO_NOTIF_(teamnum, name, cvarname, defaultvalue, strnum, flnum, args, hudargs, icon, normal, gentle) \
871         REGISTER(Notifications, name, m_id, new_pure(msg_info_notification)) { \
872                 Create_Notification_Entity           (this, defaultvalue, ACVNN(cvarname), MSG_INFO, strtoupper(#name), teamnum); \
873                 Create_Notification_Entity_InfoCenter(this, ACVNN(cvarname), strtoupper(#name), strnum, flnum, \
874                         args,     /* args    */ \
875                         hudargs,  /* hudargs */ \
876                         icon,     /* icon    */ \
877                         CPID_Null,/* cpid    */ \
878                         "",       /* durcnt  */ \
879                         normal,   /* normal  */ \
880                         gentle);  /* gentle  */ \
881         }
882
883 .string nent_iconargs;
884 #define MULTIICON_INFO(name, defaultvalue, strnum, flnum, args, hudargs, iconargs, icon, normal, gentle) \
885         MULTIICON_INFO_(INFO_##name, defaultvalue, strnum, flnum, args, hudargs, iconargs, icon, normal, gentle)
886 #define MULTIICON_INFO_(name, defaultvalue, strnum, flnum, args, hudargs, iconargs, icon, normal, gentle) \
887         NOTIF_ADD_AUTOCVAR(name, defaultvalue) \
888         REGISTER(Notifications, name, m_id, new_pure(msg_info_notification)) { \
889                 Create_Notification_Entity           (this, defaultvalue, ACVNN(name), MSG_INFO, strtoupper(#name), 0); \
890                 Create_Notification_Entity_InfoCenter(this, ACVNN(name), strtoupper(#name), strnum, flnum, \
891                         args,     /* args    */ \
892                         hudargs,  /* hudargs */ \
893                         icon,     /* icon    */ \
894                         CPID_Null,/* cpid    */ \
895                         "",       /* durcnt  */ \
896                         normal,   /* normal  */ \
897                         gentle);  /* gentle  */ \
898                 this.nent_iconargs = iconargs; \
899         }
900
901 #define MSG_CENTER_NOTIF_TEAM(teamnum, name, cvarname, defaultvalue, strnum, flnum, args, cpid, durcnt, normal, gentle) \
902         MSG_CENTER_NOTIF_(teamnum, CENTER_##name, CENTER_##cvarname, defaultvalue, strnum, flnum, args, cpid, durcnt, normal, gentle)
903
904 #define MSG_CENTER_NOTIF(name, defaultvalue, strnum, flnum, args, cpid, durcnt, normal, gentle) \
905         NOTIF_ADD_AUTOCVAR(CENTER_##name, defaultvalue) \
906         MSG_CENTER_NOTIF_(0, CENTER_##name, CENTER_##name, defaultvalue, strnum, flnum, args, cpid, durcnt, normal, gentle)
907
908 #define MSG_CENTER_NOTIF_(teamnum, name, cvarname, defaultvalue, strnum, flnum, args, cpid, durcnt, normal, gentle) \
909         REGISTER(Notifications, name, m_id, new_pure(msg_center_notification)) { \
910                 Create_Notification_Entity           (this, defaultvalue, ACVNN(cvarname), MSG_CENTER, strtoupper(#name), teamnum); \
911                 Create_Notification_Entity_InfoCenter(this, ACVNN(cvarname), strtoupper(#name), strnum, flnum, \
912                         args,    /* args    */ \
913                         "",      /* hudargs */ \
914                         "",      /* icon    */ \
915                         cpid,    /* cpid    */ \
916                         durcnt,  /* durcnt  */ \
917                         normal,  /* normal  */ \
918                         gentle); /* gentle  */ \
919         }
920
921 #define MSG_MULTI_NOTIF(name, defaultvalue, anncename, infoname, centername) \
922         NOTIF_ADD_AUTOCVAR(name, defaultvalue) \
923         REGISTER(Notifications, name, m_id, new_pure(msg_multi_notification)) { \
924                 Create_Notification_Entity      (this, defaultvalue, ACVNN(name), MSG_MULTI, strtoupper(#name), 0); \
925                 Create_Notification_Entity_Multi(this, ACVNN(name), strtoupper(#name), \
926                         anncename,   /* anncename  */ \
927                         infoname,    /* infoname   */ \
928                         centername); /* centername */ \
929         }
930
931 #define MSG_CHOICE_NOTIF_TEAM(teamnum, name, cvarname, defaultvalue, challow, chtype, optiona, optionb) \
932         MSG_CHOICE_NOTIF_(teamnum, CHOICE_##name, CHOICE_##cvarname, defaultvalue, challow, chtype, optiona, optionb)
933
934 #define MSG_CHOICE_NOTIF(name, defaultvalue, challow, chtype, optiona, optionb) \
935         NOTIF_ADD_AUTOCVAR(CHOICE_##name, defaultvalue) \
936         NOTIF_ADD_AUTOCVAR(CHOICE_##name##_ALLOWED, challow) \
937         MSG_CHOICE_NOTIF_(0, CHOICE_##name, CHOICE_##name, defaultvalue, challow, chtype, optiona, optionb)
938
939 #define MSG_CHOICE_NOTIF_(teamnum, name, cvarname, defaultvalue, challow, chtype, optiona, optionb) \
940         REGISTER(Notifications, name, m_id, new_pure(msg_choice_notification)) { \
941                 this.nent_choice_idx = nent_choice_count; \
942                 if (!teamnum || teamnum == NUM_TEAM_4) \
943                         nent_choice_count++; \
944                 Create_Notification_Entity       (this, defaultvalue, ACVNN(cvarname), MSG_CHOICE, strtoupper(#name), teamnum); \
945                 Create_Notification_Entity_Choice(this, ACVNN(cvarname), strtoupper(#name), \
946                         challow,                                 /* challow_def */ \
947                         autocvar_notification_##cvarname##_ALLOWED,  /* challow_var */ \
948                         chtype,                                  /* chtype      */ \
949                         optiona,                                 /* optiona     */ \
950                         optionb);                                /* optionb     */ \
951         }
952         
953 #define MSG_MEDAL_NOTIF(name, defaultvalue, icon, anncename) \
954         NOTIF_ADD_AUTOCVAR(MEDAL_##name, defaultvalue) \
955         MSG_MEDAL_NOTIF_(0, MEDAL_##name, MEDAL_##name, defaultvalue, icon, anncename)
956
957 #define MSG_MEDAL_NOTIF_(teamnum, name, cvarname, defaultvalue, icon, anncename) \
958         REGISTER(Notifications, name, m_id, new_pure(msg_medal_notification)) { \
959                 Create_Notification_Entity      (this, defaultvalue, ACVNN(cvarname), MSG_MEDAL, strtoupper(#name), teamnum); \
960                 Create_Notification_Entity_Medal(this, ACVNN(cvarname), strtoupper(#name), \
961                         icon, \
962                         anncename); \
963         }
964
965 REGISTRY_BEGIN(Notifications)
966 {
967         notif_global_error = false;
968 }
969
970 REGISTRY_END(Notifications)
971 {
972         if (!notif_global_error) return;
973         // shit happened... stop the loading of the program now if this is unacceptable
974         if (autocvar_notification_errors_are_fatal)
975                 LOG_FATAL("Notification initialization failed! Read above and fix the errors!");
976         else
977                 LOG_SEVERE("Notification initialization failed! Read above and fix the errors!");
978 }
979
980 #ifdef CSQC
981 .int cvar_value;
982 void ReplicateVars(bool would_destroy)
983 {
984         if (!would_destroy)
985                 FOREACH(Notifications, it.nent_type == MSG_CHOICE && (!it.nent_teamnum || it.nent_teamnum == NUM_TEAM_1), {
986                         string cvarname = strcat("notification_", Get_Notif_CvarName(it));
987                         // NOTE: REPLICATE_SIMPLE can return;
988                         REPLICATE_SIMPLE(it.cvar_value, cvarname);
989                 });
990 }
991 #endif
992
993 #include "all.inc"