]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications/all.qh
Merge branch 'LegendaryGuard/ttt' 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, CAMPAIGN_MESSAGE)
70         CASE(CPID, MOTD)
71         CASE(CPID, NIX)
72         CASE(CPID, ONSLAUGHT)
73         CASE(CPID, ONS_CAPSHIELD)
74         CASE(CPID, OVERTIME)
75         CASE(CPID, POWERUP)
76         CASE(CPID, RACE_FINISHLAP)
77         CASE(CPID, TEAMCHANGE)
78         CASE(CPID, TIMEOUT)
79         CASE(CPID, TIMEIN)
80         CASE(CPID, MMM)
81         CASE(CPID, VEHICLES)
82         CASE(CPID, VEHICLES_OTHER)
83         /** always last */
84         CASE(CPID, LAST)
85 ENUMCLASS_END(CPID)
86
87 USING(Notification, entity);
88
89 // used for notification system multi-team identifiers
90 #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)))
91 #define APP_NUM(num, prefix) ((num) ? APP_TEAM_NUM(num, prefix) : prefix##_NEUTRAL)
92
93 #define EIGHT_VARS_TO_VARARGS_VARLIST \
94         VARITEM(1, 0, s1) \
95         VARITEM(2, 0, XPD(s1, s2)) \
96         VARITEM(3, 0, XPD(s1, s2, s3)) \
97         VARITEM(4, 0, XPD(s1, s2, s3, s4)) \
98         VARITEM(0, 1, f1) \
99         VARITEM(1, 1, XPD(s1, f1)) \
100         VARITEM(2, 1, XPD(s1, s2, f1)) \
101         VARITEM(3, 1, XPD(s1, s2, s3, f1)) \
102         VARITEM(4, 1, XPD(s1, s2, s3, s4, f1)) \
103         VARITEM(0, 2, XPD(f1, f2)) \
104         VARITEM(1, 2, XPD(s1, f1, f2)) \
105         VARITEM(2, 2, XPD(s1, s2, f1, f2)) \
106         VARITEM(3, 2, XPD(s1, s2, s3, f1, f2)) \
107         VARITEM(4, 2, XPD(s1, s2, s3, s4, f1, f2)) \
108         VARITEM(0, 3, XPD(f1, f2, f3)) \
109         VARITEM(1, 3, XPD(s1, f1, f2, f3)) \
110         VARITEM(2, 3, XPD(s1, s2, f1, f2, f3)) \
111         VARITEM(3, 3, XPD(s1, s2, s3, f1, f2, f3)) \
112         VARITEM(4, 3, XPD(s1, s2, s3, s4, f1, f2, f3)) \
113         VARITEM(0, 4, XPD(f1, f2, f3, f4)) \
114         VARITEM(1, 4, XPD(s1, f1, f2, f3, f4)) \
115         VARITEM(2, 4, XPD(s1, s2, f1, f2, f3, f4)) \
116         VARITEM(3, 4, XPD(s1, s2, s3, f1, f2, f3, f4)) \
117         VARITEM(4, 4, XPD(s1, s2, s3, s4, f1, f2, f3, f4))
118
119 void Destroy_All_Notifications();
120 void Create_Notification_Entity(entity notif,
121         float var_default,
122         float var_cvar,
123         MSG typeId,
124         string namestring,
125         int teamnum);
126 void Create_Notification_Entity_Annce(entity notif,
127                                                                                 float var_cvar,
128                                                                                 string namestring,
129                                                                                 /* MSG_ANNCE */
130                                                                                 float channel,
131                                                                                 string snd,
132                                                                                 float vol,
133                                                                                 float position,
134                                                                                 float queuetime);
135
136 void Create_Notification_Entity_InfoCenter(entity notif,
137                                                                                         float var_cvar,
138                                                                                         string namestring,
139                                                                                         int strnum,
140                                                                                         int flnum,
141                                                                                         /* MSG_INFO & MSG_CENTER */
142                                                                                         string args,
143                                                                                         string hudargs,
144                                                                                         string icon,
145                                                                                         CPID cpid,
146                                                                                         string durcnt,
147                                                                                         string normal,
148                                                                                         string gentle);
149
150 void Create_Notification_Entity_Multi(entity notif,
151                                                                                 float var_cvar,
152                                                                                 string namestring,
153                                                                                 /* MSG_MULTI */
154                                                                                 Notification anncename,
155                                                                                 Notification infoname,
156                                                                                 Notification centername);
157
158 void Create_Notification_Entity_Choice(entity notif,
159                                                                                 float var_cvar,
160                                                                                 string namestring,
161                                                                                 /* MSG_CHOICE */
162                                                                                 float challow_def,
163                                                                                 float challow_var,
164                                                                                 MSG chtype,
165                                                                                 Notification optiona,
166                                                                                 Notification optionb);
167
168 void Create_Notification_Entity_Medal(entity notif,
169                                                                                 float var_cvar,
170                                                                                 string namestring,
171                                                                                 /* MSG_MEDAL */
172                                                                                 string icon,
173                                                                                 Notification anncename);
174
175 void Dump_Notifications(int fh, bool alsoprint);
176
177 #define DEFAULT_FILENAME "notifications_dump.cfg"
178 // NOTE: dumpeffectinfo, dumpnotifs, dumpturrets and dumpweapons use similar code
179 GENERIC_COMMAND(dumpnotifs, "Dump all notifications into " DEFAULT_FILENAME, false)
180 {
181         switch (request)
182         {
183                 case CMD_REQUEST_COMMAND:
184                 {
185                         #ifdef GAMEQC
186                         string filename = argv(1);
187                         bool alsoprint = false;
188                         if (filename == "")
189                         {
190                                 filename = DEFAULT_FILENAME;
191                                 alsoprint = false;
192                         }
193                         else if (filename == "-")
194                         {
195                                 filename = DEFAULT_FILENAME;
196                                 alsoprint = true;
197                         }
198                         int fh = fopen(filename, FILE_WRITE);
199                         if (fh >= 0)
200                         {
201                                 Dump_Notifications(fh, alsoprint);
202                                 LOG_INFOF("Dumping notifications... File located in ^2data/data/%s^7.", filename);
203                                 fclose(fh);
204                         }
205                         else
206                         {
207                                 LOG_INFOF("^1Error: ^7Could not open file '%s'!", filename);
208                         }
209                         #else
210                         LOG_INFO("Notification dump command only works with cl_cmd and sv_cmd.");
211                         #endif
212                         return;
213                 }
214                 default:
215                 case CMD_REQUEST_USAGE:
216                 {
217                         LOG_HELP("Usage:^3 ", GetProgramCommandPrefix(), " dumpnotifs [<filename>]");
218                         LOG_HELPF("  Where <filename> is the file to write (default is %s),", DEFAULT_FILENAME);
219                         LOG_HELP("  if supplied with '-' output to console as well as default,");
220                         LOG_HELP("  if left blank, it will only write to default.");
221                         return;
222                 }
223         }
224 }
225 #undef DEFAULT_FILENAME
226
227 #ifdef NOTIFICATIONS_DEBUG
228 bool autocvar_notification_debug = false;
229 void Debug_Notification(string input)
230 {
231         switch (autocvar_notification_debug)
232         {
233                 case 1: { LOG_TRACE(input); break; }
234                 case 2: { LOG_INFO(input); break; }
235         }
236 }
237 #endif
238
239 void Local_Notification(MSG net_type, Notification net_name, ...count);
240 /** glue for networking, forwards to `Local_Notification` */
241 void Local_Notification_WOVA(
242         MSG net_type, Notification net_name,
243         float stringcount, float floatcount,
244         string s1, string s2, string s3, string s4,
245         float f1, float f2, float f3, float f4);
246
247 #ifdef CSQC
248 string prev_soundfile;
249 float prev_soundtime;
250 #endif
251
252 #ifdef SVQC
253 IntrusiveList g_notifications;
254 STATIC_INIT(g_notifications) { g_notifications = IL_NEW(); }
255 #endif
256
257 #ifdef SVQC
258 ENUMCLASS(NOTIF)
259         /** send to one client and their spectators */
260         CASE(NOTIF, ONE)
261         /** send ONLY to one client */
262         CASE(NOTIF, ONE_ONLY)
263         /** send only to X team and their spectators */
264         CASE(NOTIF, TEAM)
265         /** send only to X team and their spectators, except for Y person and their spectators */
266         CASE(NOTIF, TEAM_EXCEPT)
267         /** send only to X team; don't include spectators */
268         CASE(NOTIF, TEAM_ONLY)
269         /** send to team X team except for Y person; don't include spectators */
270         CASE(NOTIF, TEAM_ONLY_EXCEPT)
271         /** send to everyone */
272         CASE(NOTIF, ALL)
273         /** send to everyone except X person and their spectators */
274         CASE(NOTIF, ALL_EXCEPT)
275         /** send to all spectators **/
276         CASE(NOTIF, ALL_SPEC)
277 ENUMCLASS_END(NOTIF)
278
279 string Get_Notif_BroadcastName(NOTIF broadcast)
280 {
281         switch (broadcast)
282         {
283                 case NOTIF_ONE: return "NOTIF_ONE";
284                 case NOTIF_ONE_ONLY: return "NOTIF_ONE_ONLY";
285                 case NOTIF_ALL_SPEC: return "NOTIF_ALL_SPEC";
286                 case NOTIF_ALL_EXCEPT: return "NOTIF_ALL_EXCEPT";
287                 case NOTIF_ALL: return "NOTIF_ALL";
288                 case NOTIF_TEAM: return "NOTIF_TEAM";
289                 case NOTIF_TEAM_EXCEPT: return "NOTIF_TEAM_EXCEPT";
290                 case NOTIF_TEAM_ONLY: return "NOTIF_TEAM_ONLY";
291                 case NOTIF_TEAM_ONLY_EXCEPT: return "NOTIF_TEAM_ONLY_EXCEPT";
292         }
293         LOG_WARNF("Get_Notif_BroadcastName(%d): Improper broadcast!", broadcast);
294         return "";
295 }
296
297 void Kill_Notification(
298         NOTIF broadcast, entity client,
299         MSG net_type, CPID net_name);
300 void Send_Notification(
301         NOTIF broadcast, entity client,
302         MSG net_type, Notification net_name,
303         ...count);
304 void Send_Notification_WOVA(
305         NOTIF broadcast, entity client,
306         MSG net_type, Notification net_name,
307         float stringcount, float floatcount,
308         string s1, string s2, string s3, string s4,
309         float f1, float f2, float f3, float f4);
310 void Send_Notification_WOCOVA(
311         NOTIF broadcast, entity client,
312         MSG net_type, Notification net_name,
313         string s1, string s2, string s3, string s4,
314         float f1, float f2, float f3, float f4);
315 #endif
316
317 // ===========================
318 //  Special CVAR Declarations
319 // ===========================
320
321 // MAKE SURE THIS IS ALWAYS SYNCHRONIZED WITH THE DUMP
322 // NOTIFICATIONS FUNCTION IN THE .QC FILE!
323
324 #define NOTIF_ADD_AUTOCVAR(name,defaultvalue) float autocvar_notification_##name = defaultvalue;
325
326 float autocvar_notification_show_location = false;
327 string autocvar_notification_show_location_string = ""; //_(" at the %s");
328 float autocvar_notification_show_sprees = true;
329 float autocvar_notification_show_sprees_info = 3; // 0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker
330 float autocvar_notification_show_sprees_info_newline = true;
331 float autocvar_notification_show_sprees_info_specialonly = true;
332 float autocvar_notification_errors_are_fatal = true;
333 #ifdef SVQC
334 float autocvar_notification_lifetime_runtime = 0.5;
335 float autocvar_notification_lifetime_mapload = 10;
336 #endif
337
338 #ifdef SVQC
339 void Notification_GetCvars(entity this, entity store);
340 float autocvar_notification_server_allows_location = 1; // 0 = no, 1 = yes
341 #else
342 float autocvar_notification_item_centerprinttime = 1.5;
343
344 // 0 = no, 1 = yes, 2 = forced on for all MSG_INFO notifs
345 // DISABLED IN CODE, BUT ENABLED IN CONFIG FOR COMPATIBILITY WITH OLD CLIENTS
346 float autocvar_notification_allow_chatboxprint = 0;
347
348 float autocvar_notification_show_sprees_center = true;
349 float autocvar_notification_show_sprees_center_specialonly = true;
350 #endif
351
352
353 // ============================
354 //  Notification Argument List
355 // ============================
356 /*
357  These arguments get replaced with the Local_Notification_sprintf
358  and other such functions found in all.qc to supply data
359  from networked notifications to their usage in sprintf... It
360  allows for more dynamic data to be inferred by the local
361  notification parser, so that the server does not have to network
362  anything too crazy on a per-client/per-situation basis.
363
364  Pay attention to the CSQC/SVQC relations, some of these are redefined
365  in slightly different ways for different programs, this is because the
366  server does a more conservative approach to the notifs than the client.
367
368  All arguments are swapped into strings, so be sure that your
369  sprintf usage matches with proper %s placement.
370
371  Argument descriptions:
372         s1-s4: string arguments to be literally swapped into sprintf
373         s2loc: s2 string of locations of deaths or other events
374         s3loc: s3 string of locations of deaths or other events
375         f1-f4: float arguments expanded into strings to be swapped into sprintf
376         f1p2dec: f1 float to string with 2 decimal places
377         f2p2dec: f2 float to string with 2 decimal places
378         f2primsec: f2 float primary or secondary selection for weapons
379         f3primsec: f3 float primary or secondary selection for weapons
380         f1secs: count_seconds of f1
381         f1points: point or points depending on f1
382         f1ord: count_ordinal of f1
383         f1time: process_time of f1
384         f1race_time: mmssss of f1
385         f2race_time: mmssss of f2
386         race_col: color of race time/position (i.e. good or bad)
387         race_diff: show time difference between f2 and f3
388         missing_teams: show which teams still need players
389         pass_key: find the keybind for "passing" or "dropping" in CTF game mode
390         nade_key: find the keybind for nade throwing
391         frag_ping: show the ping of a player
392         frag_stats: show health/armor/ping of a player
393         frag_pos: show score status and position in the match of a player
394         spree_cen: centerprint notif for kill spree/how many kills they have
395         spree_inf: info notif for kill spree/how many kills they have
396         spree_end: placed at the end of murder messages to show ending of sprees
397         spree_lost: placed at the end of suicide messages to show losing of sprees
398         item_wepname: return full name of a weapon from weaponid
399         item_wepammo: ammo display for weapon from f1 and f2
400         item_centime: amount of time to display weapon message in centerprint
401         item_buffname: return full name of a buff from buffid
402         death_team: show the full name of the team a player is switching from
403         minigame1_name: return human readable name of a minigame from its id(s1)
404         minigame1_d: return descriptor name of a minigame from its id(s1)
405 */
406
407 const float NOTIF_MAX_ARGS = 7;
408 const float NOTIF_MAX_HUDARGS = 2;
409 const float NOTIF_MAX_DURCNT = 2;
410
411 #ifdef CSQC
412 const int NOTIF_QUEUE_MAX = 10;
413 entity notif_queue_entity[NOTIF_QUEUE_MAX];
414 MSG notif_queue_type[NOTIF_QUEUE_MAX];
415 float notif_queue_time[NOTIF_QUEUE_MAX];
416 float notif_queue_f1[NOTIF_QUEUE_MAX];
417
418 float notif_queue_next_time;
419 int notif_queue_length;
420
421 void Local_Notification_Queue_Process();
422 #endif
423
424 string arg_slot[NOTIF_MAX_ARGS];
425
426 const float ARG_CS_SV_HA = 1; // enabled on CSQC, SVQC, and Hudargs
427 const float ARG_CS_SV_DC = 2; // enabled on CSQC, SVQC, and durcnt centerprint
428 const float ARG_CS_SV = 3; // enabled on CSQC and SVQC
429 const float ARG_CS = 4; // unique result to CSQC
430 const float ARG_SV = 5; // unique result to SVQC
431 const float ARG_DC = 6; // unique result to durcnt/centerprint
432
433 // todo possible idea.... declare how many floats/strings each arg needs, and then dynamically increment the input
434 // this way, we don't need to have duplicates like i.e. s2loc and s3loc?
435
436 string BUFF_NAME(int i);
437
438 #define NOTIF_ARGUMENT_LIST \
439         ARG_CASE(ARG_CS_SV_HA,  "s1",            s1) \
440         ARG_CASE(ARG_CS_SV_HA,  "s2",            s2) \
441         ARG_CASE(ARG_CS_SV_HA,  "s3",            s3) \
442         ARG_CASE(ARG_CS_SV_HA,  "s4",            s4) \
443         ARG_CASE(ARG_CS_SV,     "s2loc",         ((autocvar_notification_show_location && (s2 != "")) ? sprintf(( ((tmp_s = autocvar_notification_show_location_string) != "") ? tmp_s : _(" (near %s)") ), s2) : "")) \
444         ARG_CASE(ARG_CS_SV,     "s3loc",         ((autocvar_notification_show_location && (s3 != "")) ? sprintf(( ((tmp_s = autocvar_notification_show_location_string) != "") ? tmp_s : _(" (near %s)") ), s3) : "")) \
445         ARG_CASE(ARG_CS_SV_DC,  "f1",            ftos(f1)) \
446         ARG_CASE(ARG_CS_SV_DC,  "f2",            ftos(f2)) \
447         ARG_CASE(ARG_CS_SV,     "f3",            ftos(f3)) \
448         ARG_CASE(ARG_CS_SV,     "f4",            ftos(f4)) \
449         ARG_CASE(ARG_CS_SV,     "f1dtime",       ftos_decimals(TIME_DECODE(f1), 2)) \
450         ARG_CASE(ARG_CS_SV,     "f2dtime",       ftos_decimals(TIME_DECODE(f2), 2)) \
451         ARG_CASE(ARG_CS,        "f2primsec",     (f2 ? _("secondary") : _("primary"))) \
452         ARG_CASE(ARG_CS,        "f3primsec",     (f3 ? _("secondary") : _("primary"))) \
453         ARG_CASE(ARG_CS,        "f1secs",        count_seconds(f1)) \
454         ARG_CASE(ARG_CS,        "f1points",      (f1 == 1 ? _("point") : _("points"))) \
455         ARG_CASE(ARG_CS_SV,     "f1ord",         count_ordinal(f1)) \
456         ARG_CASE(ARG_CS_SV,     "f1time",        process_time(2, f1)) \
457         ARG_CASE(ARG_CS_SV_HA,  "f1race_time",   mmssss(f1)) \
458         ARG_CASE(ARG_CS_SV_HA,  "f2race_time",   mmssss(f2)) \
459         ARG_CASE(ARG_CS_SV_HA,  "f3race_time",   mmssss(f3)) \
460         ARG_CASE(ARG_CS_SV,     "race_col",      CCR(((f1 == 1) ? "^F1" : "^F2"))) \
461         ARG_CASE(ARG_CS_SV,     "race_diff",     ((f2 > f3) ? sprintf(CCR("^1[+%s]"), mmssss(f2 - f3)) : sprintf(CCR("^2[-%s]"), mmssss(f3 - f2)))) \
462         ARG_CASE(ARG_CS,        "missing_teams", notif_arg_missing_teams(f1)) \
463         ARG_CASE(ARG_CS,        "pass_key",      getcommandkey(_("drop flag"), "+use")) \
464         ARG_CASE(ARG_CS,        "nade_key",      getcommandkey(_("throw nade"), "dropweapon")) \
465         ARG_CASE(ARG_CS,        "join_key",      getcommandkey(_("jump"), "+jump")) \
466         ARG_CASE(ARG_CS,        "frag_ping",     notif_arg_frag_ping(true, f2)) \
467         ARG_CASE(ARG_CS,        "frag_stats",    notif_arg_frag_stats(f2, f3, f4)) \
468         ARG_CASE(ARG_CS,        "frag_pos",      notif_arg_frag_pos(f2)) \
469         ARG_CASE(ARG_CS,        "spree_cen",     (autocvar_notification_show_sprees ? notif_arg_spree_cen(f1) : "")) \
470         ARG_CASE(ARG_CS_SV,     "spree_inf",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(1, input, s2, f2) : "")) \
471         ARG_CASE(ARG_CS_SV,     "spree_end",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(-1, "", "", f1) : "")) \
472         ARG_CASE(ARG_CS_SV,     "spree_lost",    (autocvar_notification_show_sprees ? notif_arg_spree_inf(-2, "", "", f1) : "")) \
473         ARG_CASE(ARG_CS_SV,     "item_wepname",  REGISTRY_GET(Weapons, f1).m_name) \
474         ARG_CASE(ARG_CS_SV,     "item_buffname", BUFF_NAME(f1)) \
475         ARG_CASE(ARG_CS_SV,     "f3buffname",    BUFF_NAME(f3)) \
476         ARG_CASE(ARG_CS_SV,     "item_wepammo",  (f2 > 0 ? notif_arg_item_wepammo(f1, f2) : "")) \
477         ARG_CASE(ARG_DC,        "item_centime",  ftos(autocvar_notification_item_centerprinttime)) \
478         ARG_CASE(ARG_SV,        "death_team",    Team_ColoredFullName(f1)) \
479         ARG_CASE(ARG_CS,        "death_team",    Team_ColoredFullName(f1 - 1)) \
480         ARG_CASE(ARG_CS_SV_HA,  "minigame1_name",find(NULL,netname,s1).descriptor.message) \
481         ARG_CASE(ARG_CS_SV_HA,  "minigame1_d",   find(NULL,netname,s1).descriptor.netname)
482
483 #define NOTIF_HIT_MAX(count,funcname) MACRO_BEGIN \
484         if(sel_num == count) { backtrace(sprintf("%s: Hit maximum arguments!\n", funcname)); break; } \
485 MACRO_END
486
487 #define NOTIF_HIT_UNKNOWN(token,funcname) { backtrace(sprintf("%s: Hit unknown token in selected string! '%s'\n", funcname, selected)); break; }
488
489 #define KILL_SPREE_LIST \
490         SPREE_ITEM(3, 03, _("TRIPLE FRAG! "), _("%s^K1 made a TRIPLE FRAG! %s^BG"), _("%s^K1 made a TRIPLE SCORE! %s^BG")) \
491         SPREE_ITEM(5, 05, _("RAGE! "), _("%s^K1 unlocked RAGE! %s^BG"), _("%s^K1 made FIVE SCORES IN A ROW! %s^BG")) \
492         SPREE_ITEM(10, 10, _("MASSACRE! "), _("%s^K1 started a MASSACRE! %s^BG"), _("%s^K1 made TEN SCORES IN A ROW! %s^BG")) \
493         SPREE_ITEM(15, 15, _("MAYHEM! "), _("%s^K1 executed MAYHEM! %s^BG"), _("%s^K1 made FIFTEEN SCORES IN A ROW! %s^BG")) \
494
495 #ifdef CSQC
496 // z411 TODO : This actually doesn't work very well.
497 // This gets run before the client gets score updates so it works
498 // fine when you're playing (because frags get updated first)
499 // but it breaks a lot when you're spectating because
500 // we sometimes get the new frag info at different times
501 // (before or after we run this). A suggested fix would
502 // be to do this sorting and comparison in the server.
503 string notif_arg_frag_pos(int score)
504 {
505         entity pl;
506         int place = 1;
507         string str, color, tail;
508         bool tied = false;
509         
510         for(pl = players.sort_next; pl; pl = pl.sort_next) {
511                 if(pl.team == NUM_SPECTATOR) continue;
512                 if(pl.(scores(SP_SCORE)) == score) break;
513                 ++place;
514         }
515         
516         entity prev = pl.sort_prev;
517         entity next = pl.sort_next;
518         if(prev && prev.(scores(SP_SCORE)) == score) {
519                 tied = true;
520                 --place; // We're tied always for the best place
521         }
522         if(next && next.(scores(SP_SCORE)) == score) {
523                 tied = true;
524         }
525         
526         switch(place) {
527                 case 1:
528                         color = "^4";
529                         break;
530                 case 2:
531                         color = "^1";
532                         break;
533                 case 3:
534                         color = "^3";
535                         break;
536                 default:
537                         color = "";
538         }
539         
540         switch(place % 10) {
541                 case 1:
542                         tail = "st";
543                         break;
544                 case 2:
545                         tail = "nd";
546                         break;
547                 case 3:
548                         tail = "rd";
549                         break;
550                 default:
551                         tail = "th";
552         }
553         
554         str = strcat(color, ftos(place), tail);
555         if(tied)
556                 return strcat("Tied for ", str);
557         else
558                 return str;
559 }
560
561 string notif_arg_frag_ping(bool newline, float fping)
562 {
563         string s = newline ? "\n" : " ";
564         if (fping < 0)
565                 return sprintf(CCR(_("%s(^F1Bot^BG)")), s);
566         else
567                 return sprintf(CCR(_("%s(Ping ^F1%d^BG)")), s, fping);
568 }
569
570 string notif_arg_frag_stats(float fhealth, float farmor, float fping)
571 {
572         string s = notif_arg_frag_ping(false, fping);
573         if (fhealth > 1)
574                 return sprintf(CCR(_("\n(Health ^1%d^BG / Armor ^2%d^BG)%s")), fhealth, farmor, s);
575         else
576                 return sprintf(CCR(_("\n(^F4Dead^BG)%s")), s);
577 }
578
579 string notif_arg_missing_teams(float f1)
580 {
581         return strcat(
582                 ((f1 & BIT(0)) ? strcat(Team_ColoredFullName(NUM_TEAM_1), (f1 >> 1) ? ", " : "") : ""),
583                 ((f1 & BIT(1)) ? strcat(Team_ColoredFullName(NUM_TEAM_2), (f1 >> 2) ? ", " : "") : ""),
584                 ((f1 & BIT(2)) ? strcat(Team_ColoredFullName(NUM_TEAM_3), (f1 >> 3) ? ", " : "") : ""),
585                 ((f1 & BIT(3)) ?        Team_ColoredFullName(NUM_TEAM_4)                         : "")
586         );
587 }
588
589 string notif_arg_spree_cen(float spree)
590 {
591         // 0 = off, 1 = target (but only for first victim) and attacker
592         if(autocvar_notification_show_sprees_center)
593         {
594                 if(spree > 1)
595                 {
596                         #define SPREE_ITEM(counta,countb,center,normal,gentle) \
597                                 case counta: { return normal_or_gentle(center, sprintf(_("%d score spree! "), spree)); }
598
599                         switch(spree)
600                         {
601                                 KILL_SPREE_LIST
602                                 default:
603                                 {
604                                         if (!autocvar_notification_show_sprees_center_specialonly)
605                                         {
606                                                 return
607                                                         sprintf(
608                                                                 normal_or_gentle(
609                                                                         _("%d frag spree! "),
610                                                                         _("%d score spree! ")
611                                                                 ),
612                                                                 spree);
613                                         }
614                                         else { return ""; } // don't show spree information if it isn't an achievement
615                                 }
616                         }
617
618                         #undef SPREE_ITEM
619                 }
620                 else if(spree == -1) // first blood
621                 {
622                         return normal_or_gentle(_("First blood! "), _("First score! "));
623                 }
624                 else if(spree == -2) // first victim
625                 {
626                         return normal_or_gentle(_("First victim! "), _("First casualty! "));
627                 }
628         }
629         return "";
630 }
631 #endif
632
633 string notif_arg_spree_inf(float type, string input, string player, float spree)
634 {
635         switch(type)
636         {
637                 case 1: // attacker kill spree
638                 {
639                         // 0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker
640                         // this conditional (& 2) is true for 2 and 3
641                         if(autocvar_notification_show_sprees_info & 2)
642                         {
643                                 #ifdef CSQC
644                                 string spree_newline =
645                                         ( autocvar_notification_show_sprees_info_newline ?
646                                                 ((substring(input, 0, 1) == "\{3}") ? "\n\{3}" : "\n") : "" );
647                                 #else
648                                 string spree_newline =
649                                         (autocvar_notification_show_sprees_info_newline ? "\n" : "");
650                                 #endif
651
652                                 if(spree > 1)
653                                 {
654                                         #define SPREE_ITEM(counta,countb,center,normal,gentle) \
655                                                 case counta: { return sprintf(CCR(normal_or_gentle(normal, gentle)), player, spree_newline); }
656
657                                         switch(spree)
658                                         {
659                                                 KILL_SPREE_LIST
660                                                 default:
661                                                 {
662                                                         if (!autocvar_notification_show_sprees_info_specialonly)
663                                                         {
664                                                                 return
665                                                                         sprintf(
666                                                                                 CCR(normal_or_gentle(
667                                                                                         _("%s^K1 has %d frags in a row! %s^BG"),
668                                                                                         _("%s^K1 made %d scores in a row! %s^BG")
669                                                                                 )),
670                                                                                 player,
671                                                                                 spree,
672                                                                                 spree_newline
673                                                                         );
674                                                         }
675                                                         else { return ""; } // don't show spree information if it isn't an achievement
676                                                 }
677                                         }
678
679                                         #undef SPREE_ITEM
680                                 }
681                                 else if(spree == -1) // firstblood
682                                 {
683                                         return
684                                                 sprintf(
685                                                         CCR(normal_or_gentle(
686                                                                 _("%s^K1 drew first blood! %s^BG"),
687                                                                 _("%s^K1 got the first score! %s^BG")
688                                                         )),
689                                                         player,
690                                                         spree_newline
691                                                 );
692                                 }
693                         }
694                         break;
695                 }
696
697                 case -1: // kill spree ended
698                 {
699                         if((spree > 1) && (autocvar_notification_show_sprees_info & 1))
700                         {
701                                 return
702                                         sprintf(normal_or_gentle(
703                                                 _(", ending their %d frag spree"),
704                                                 _(", ending their %d score spree")
705                                                 ),
706                                                 spree
707                                         );
708                         }
709                         break;
710                 }
711
712                 case -2: // kill spree lost
713                 {
714                         if((spree > 1) && (autocvar_notification_show_sprees_info & 1))
715                         {
716                                 return
717                                         sprintf(normal_or_gentle(
718                                                 _(", losing their %d frag spree"),
719                                                 _(", losing their %d score spree")
720                                                 ),
721                                                 spree
722                                         );
723                         }
724                         break;
725                 }
726         }
727         return "";
728 }
729
730 string notif_arg_item_wepammo(float f1, float f2)
731 {
732         string ammoitems = "";
733         Weapon wep = REGISTRY_GET(Weapons, f1);
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"