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