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