Cleanup
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / notifications.qh
1 #ifndef NOTIFICATIONS_H
2 #define NOTIFICATIONS_H
3
4 #include "command/all.qh"
5
6 #include "constants.qh"
7 #include "teams.qh"
8 #include "util.qh"
9
10 // ================================================
11 //  Unified notification system, written by Samual
12 //  Last updated: March, 2013
13 // ================================================
14
15 // main types/groups of notifications
16 const int MSG_ANNCE = 1; // "Global" AND "personal" announcer messages
17 const int MSG_INFO = 2; // "Global" information messages
18 const int MSG_CENTER = 3; // "Personal" centerprint messages
19 const int MSG_CENTER_CPID = 4; // Kill centerprint message
20 const int MSG_MULTI = 5; // Subcall MSG_INFO and/or MSG_CENTER notifications
21 const int MSG_CHOICE = 6; // Choose which subcall wrapper to activate
22
23 // negative confirmations
24 const int NO_MSG = -12345;  // allows various things to know when no information is added
25 const int NOTIF_ABORT = -1234;   // allows Send_Notification to safely abort sending
26
27 #define EIGHT_VARS_TO_VARARGS_VARLIST \
28     VARITEM(1, 0, s1) \
29     VARITEM(2, 0, XPD(s1, s2)) \
30     VARITEM(3, 0, XPD(s1, s2, s3)) \
31     VARITEM(4, 0, XPD(s1, s2, s3, s4)) \
32     VARITEM(0, 1, f1) \
33     VARITEM(1, 1, XPD(s1, f1)) \
34     VARITEM(2, 1, XPD(s1, s2, f1)) \
35     VARITEM(3, 1, XPD(s1, s2, s3, f1)) \
36     VARITEM(4, 1, XPD(s1, s2, s3, s4, f1)) \
37     VARITEM(0, 2, XPD(f1, f2)) \
38     VARITEM(1, 2, XPD(s1, f1, f2)) \
39     VARITEM(2, 2, XPD(s1, s2, f1, f2)) \
40     VARITEM(3, 2, XPD(s1, s2, s3, f1, f2)) \
41     VARITEM(4, 2, XPD(s1, s2, s3, s4, f1, f2)) \
42     VARITEM(0, 3, XPD(f1, f2, f3)) \
43     VARITEM(1, 3, XPD(s1, f1, f2, f3)) \
44     VARITEM(2, 3, XPD(s1, s2, f1, f2, f3)) \
45     VARITEM(3, 3, XPD(s1, s2, s3, f1, f2, f3)) \
46     VARITEM(4, 3, XPD(s1, s2, s3, s4, f1, f2, f3)) \
47     VARITEM(0, 4, XPD(f1, f2, f3, f4)) \
48     VARITEM(1, 4, XPD(s1, f1, f2, f3, f4)) \
49     VARITEM(2, 4, XPD(s1, s2, f1, f2, f3, f4)) \
50     VARITEM(3, 4, XPD(s1, s2, s3, f1, f2, f3, f4)) \
51     VARITEM(4, 4, XPD(s1, s2, s3, s4, f1, f2, f3, f4))
52
53 void Destroy_All_Notifications();
54 void Create_Notification_Entity(
55     float var_default,
56     float var_cvar,
57     float typeId,
58     float nameid,
59     string namestring,
60     int strnum,
61     int flnum,
62     /* MSG_ANNCE */
63     float channel,
64     string snd,
65     float vol,
66     float position,
67     /* MSG_INFO & MSG_CENTER */
68     string args,
69     string hudargs,
70     string icon,
71     float cpid,
72     string durcnt,
73     string normal,
74     string gentle,
75     /* MSG_MULTI */
76     float anncename,
77     float infoname,
78     float centername,
79     /* MSG_CHOICE */
80     float challow_def,
81     float challow_var,
82     float chtype,
83     float optiona,
84     float optionb);
85
86 void Dump_Notifications(float fh, float alsoprint);
87
88
89 GENERIC_COMMAND(dumpnotifs, "Dump all notifications into notifications_dump.txt")
90 {
91         switch(request)
92         {
93                 case CMD_REQUEST_COMMAND:
94                 {
95                         #ifndef MENUQC
96                         float fh, alsoprint = false;
97                         string filename = argv(1);
98
99                         if(filename == "")
100                         {
101                                 filename = "notifications_dump.cfg";
102                                 alsoprint = false;
103                         }
104                         else if(filename == "-")
105                         {
106                                 filename = "notifications_dump.cfg";
107                                 alsoprint = true;
108                         }
109                         fh = fopen(filename, FILE_WRITE);
110
111                         if(fh >= 0)
112                         {
113                                 Dump_Notifications(fh, alsoprint);
114                                 LOG_INFOF("Dumping notifications... File located in ^2data/data/%s^7.\n", filename);
115                                 fclose(fh);
116                         }
117                         else
118                         {
119                                 LOG_INFOF("^1Error: ^7Could not open file '%s'!\n", filename);
120                         }
121                         #else
122                         LOG_INFO(_("Notification dump command only works with cl_cmd and sv_cmd.\n"));
123                         #endif
124                         return;
125                 }
126
127                 default:
128                 case CMD_REQUEST_USAGE:
129                 {
130                         LOG_INFO(strcat("\nUsage:^3 ", GetProgramCommandPrefix(), " dumpnotifs [filename]"));
131                         LOG_INFO("  Where 'filename' is the file to write (default is notifications_dump.cfg),\n");
132                         LOG_INFO("  if supplied with '-' output to console as well as default,\n");
133                         LOG_INFO("  if left blank, it will only write to default.\n");
134                         return;
135                 }
136         }
137 }
138
139 #ifdef NOTIFICATIONS_DEBUG
140 void Debug_Notification(string input);
141 #endif
142
143 void Local_Notification(int net_type, int net_name, ...count);
144 void Local_Notification_WOVA(
145     float net_type, float net_name,
146     float stringcount, float floatcount,
147     string s1, string s2, string s3, string s4,
148     float f1, float f2, float f3, float f4);
149
150 #ifdef CSQC // CLIENT ONLY
151 string prev_soundfile;
152 float prev_soundtime;
153 #endif
154
155 #ifdef SVQC // SERVER ONLY
156 const float NOTIF_ONE = 1;
157 const float NOTIF_ONE_ONLY = 2;
158 const float NOTIF_TEAM = 3;
159 const float NOTIF_TEAM_EXCEPT = 4;
160 const float NOTIF_ALL = 5;
161 const float NOTIF_ALL_EXCEPT = 6;
162
163 void Kill_Notification(
164     float broadcast, entity client,
165     float net_type, float net_name);
166 void Send_Notification(
167     float broadcast, entity client,
168     float net_type, float net_name,
169     ...count);
170 void Send_Notification_WOVA(
171     float broadcast, entity client,
172     float net_type, float net_name,
173     float stringcount, float floatcount,
174     string s1, string s2, string s3, string s4,
175     float f1, float f2, float f3, float f4);
176 void Send_Notification_WOCOVA(
177     float broadcast, entity client,
178     float net_type, float net_name,
179     string s1, string s2, string s3, string s4,
180     float f1, float f2, float f3, float f4);
181 #endif
182
183 // ===========================
184 //  Special CVAR Declarations
185 // ===========================
186
187 // MAKE SURE THIS IS ALWAYS SYNCHRONIZED WITH THE DUMP
188 // NOTIFICATIONS FUNCTION IN THE .QC FILE!
189
190 #define NOTIF_ADD_AUTOCVAR(name,default) float autocvar_notification_##name = default;
191
192 float autocvar_notification_show_location = false;
193 string autocvar_notification_show_location_string = ""; //_(" at the %s");
194 float autocvar_notification_show_sprees = true;
195 float autocvar_notification_show_sprees_info = 3; // 0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker
196 float autocvar_notification_show_sprees_info_newline = true;
197 float autocvar_notification_show_sprees_info_specialonly = true;
198 float autocvar_notification_errors_are_fatal = true;
199 float autocvar_notification_lifetime_runtime = 0.5;
200 float autocvar_notification_lifetime_mapload = 10;
201 float autocvar_notification_debug = false;
202
203 #ifdef SVQC
204 .float FRAG_VERBOSE;
205 void Notification_GetCvars();
206 float autocvar_notification_server_allows_location = 1; // 0 = no, 1 = yes
207 #else
208 float autocvar_notification_item_centerprinttime = 1.5;
209
210 // 0 = no, 1 = yes, 2 = forced on for all MSG_INFO notifs
211 // DISABLED IN CODE, BUT ENABLED IN CONFIG FOR COMPATIBILITY WITH OLD CLIENTS
212 float autocvar_notification_allow_chatboxprint = 0;
213
214 float autocvar_notification_show_sprees_center = true;
215 float autocvar_notification_show_sprees_center_specialonly = true;
216 #endif
217
218
219 // ============================
220 //  Notification Argument List
221 // ============================
222 /*
223  These arguments get replaced with the Local_Notification_sprintf
224  and other such functions found in notifications.qc to supply data
225  from networked notifications to their usage in sprintf... It
226  allows for more dynamic data to be inferred by the local
227  notification parser, so that the server does not have to network
228  anything too crazy on a per-client/per-situation basis.
229
230  Pay attention to the CSQC/SVQC relations, some of these are redefined
231  in slightly different ways for different programs, this is because the
232  server does a more conservative approach to the notifs than the client.
233
234  All arguments are swapped into strings, so be sure that your
235  sprintf usage matches with proper %s placement.
236
237  Argument descriptions:
238     s1-s4: string arguments to be literally swapped into sprintf
239     s2loc: s2 string of locations of deaths or other events
240     s3loc: s3 string of locations of deaths or other events
241     f1-f4: float arguments expanded into strings to be swapped into sprintf
242     f1p2dec: f1 float to string with 2 decimal places
243     f2p2dec: f2 float to string with 2 decimal places
244     f2primsec: f2 float primary or secondary selection for weapons
245     f3primsec: f3 float primary or secondary selection for weapons
246     f1secs: count_seconds of f1
247     f1ord: count_ordinal of f1
248     f1time: process_time of f1
249     f1race_time: mmssss of f1
250     f2race_time: mmssss of f2
251     race_col: color of race time/position (i.e. good or bad)
252     race_diff: show time difference between f2 and f3
253     missing_teams: show which teams still need players
254     pass_key: find the keybind for "passing" or "dropping" in CTF game mode
255     frag_ping: show the ping of a player
256     frag_stats: show health/armor/ping of a player
257     frag_pos: show score status and position in the match of a player
258     spree_cen: centerprint notif for kill spree/how many kills they have
259     spree_inf: info notif for kill spree/how many kills they have
260     spree_end: placed at the end of murder messages to show ending of sprees
261     spree_lost: placed at the end of suicide messages to show losing of sprees
262     item_wepname: return full name of a weapon from weaponid
263     item_wepammo: ammo display for weapon from string
264     item_centime: amount of time to display weapon message in centerprint
265     item_buffname: return full name of a buff from buffid
266     death_team: show the full name of the team a player is switching from
267     minigame1_name: return human readable name of a minigame from its id(s1)
268     minigame1_d: return descriptor name of a minigame from its id(s1)
269 */
270
271 const float NOTIF_MAX_ARGS = 7;
272 const float NOTIF_MAX_HUDARGS = 2;
273 const float NOTIF_MAX_DURCNT = 2;
274
275 string arg_slot[NOTIF_MAX_ARGS];
276
277 const float ARG_CS_SV_HA = 1; // enabled on CSQC, SVQC, and Hudargs
278 const float ARG_CS_SV_DC = 2; // enabled on CSQC, SVQC, and durcnt centerprint
279 const float ARG_CS_SV = 3; // enabled on CSQC and SVQC
280 const float ARG_CS = 4; // unique result to CSQC
281 const float ARG_SV = 5; // unique result to SVQC
282 const float ARG_DC = 6; // unique result to durcnt/centerprint
283
284 // todo possible idea.... declare how many floats/strings each arg needs, and then dynamically increment the input
285 // this way, we don't need to have duplicates like i.e. s2loc and s3loc?
286
287 #define NOTIF_ARGUMENT_LIST \
288     ARG_CASE(ARG_CS_SV_HA,  "s1",            s1) \
289     ARG_CASE(ARG_CS_SV_HA,  "s2",            s2) \
290     ARG_CASE(ARG_CS_SV_HA,  "s3",            s3) \
291     ARG_CASE(ARG_CS_SV_HA,  "s4",            s4) \
292     ARG_CASE(ARG_CS_SV,     "s2loc",         ((autocvar_notification_show_location && (s2 != "")) ? sprintf(( ((tmp_s = autocvar_notification_show_location_string) != "") ? tmp_s : _(" (near %s)") ), s2) : "")) \
293     ARG_CASE(ARG_CS_SV,     "s3loc",         ((autocvar_notification_show_location && (s3 != "")) ? sprintf(( ((tmp_s = autocvar_notification_show_location_string) != "") ? tmp_s : _(" (near %s)") ), s3) : "")) \
294     ARG_CASE(ARG_CS_SV_DC,  "f1",            ftos(f1)) \
295     ARG_CASE(ARG_CS_SV_DC,  "f2",            ftos(f2)) \
296     ARG_CASE(ARG_CS_SV,     "f3",            ftos(f3)) \
297     ARG_CASE(ARG_CS_SV,     "f4",            ftos(f4)) \
298     ARG_CASE(ARG_CS_SV,     "f1p2dec",       ftos_decimals(f1/100, 2)) \
299     ARG_CASE(ARG_CS_SV,     "f2p2dec",       ftos_decimals(f2/100, 2)) \
300     ARG_CASE(ARG_CS,        "f2primsec",     (f2 ? _("secondary") : _("primary"))) \
301     ARG_CASE(ARG_CS,        "f3primsec",     (f3 ? _("secondary") : _("primary"))) \
302     ARG_CASE(ARG_CS,        "f1secs",        count_seconds(f1)) \
303     ARG_CASE(ARG_CS_SV,     "f1ord",         count_ordinal(f1)) \
304     ARG_CASE(ARG_CS,        "f1time",        process_time(2, f1)) \
305     ARG_CASE(ARG_CS_SV_HA,  "f1race_time",   mmssss(f1)) \
306     ARG_CASE(ARG_CS_SV_HA,  "f2race_time",   mmssss(f2)) \
307     ARG_CASE(ARG_CS_SV_HA,  "f3race_time",   mmssss(f3)) \
308     ARG_CASE(ARG_CS_SV,     "race_col",      CCR(((f1 == 1) ? "^F1" : "^F2"))) \
309     ARG_CASE(ARG_CS_SV,     "race_diff",     ((f2 > f3) ? sprintf(CCR("^1[+%s]"), mmssss(f2 - f3)) : sprintf(CCR("^2[-%s]"), mmssss(f3 - f2)))) \
310     ARG_CASE(ARG_CS,        "missing_teams", notif_arg_missing_teams(f1)) \
311     ARG_CASE(ARG_CS,        "pass_key",      ((((tmp_s = getcommandkey("pass", "+use")) != "pass") && !(strstrofs(tmp_s, "not bound", 0) >= 0)) ? sprintf(CCR(_(" ^F1(Press %s)")), tmp_s) : "")) \
312     ARG_CASE(ARG_CS,        "frag_ping",     notif_arg_frag_ping(true, f2)) \
313     ARG_CASE(ARG_CS,        "frag_stats",    notif_arg_frag_stats(f2, f3, f4)) \
314     /*ARG_CASE(ARG_CS,      "frag_pos",      ((Should_Print_Score_Pos(f1)) ? sprintf("\n^BG%s", Read_Score_Pos(f1)) : ""))*/ \
315     ARG_CASE(ARG_CS,        "spree_cen",     (autocvar_notification_show_sprees ? notif_arg_spree_cen(f1) : "")) \
316     ARG_CASE(ARG_CS_SV,     "spree_inf",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(1, input, s2, f2) : "")) \
317     ARG_CASE(ARG_CS_SV,     "spree_end",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(-1, "", "", f1) : "")) \
318     ARG_CASE(ARG_CS_SV,     "spree_lost",    (autocvar_notification_show_sprees ? notif_arg_spree_inf(-2, "", "", f1) : "")) \
319     ARG_CASE(ARG_CS_SV,     "item_wepname",  WEP_NAME(f1)) \
320     ARG_CASE(ARG_CS_SV,     "item_buffname", sprintf("%s%s", rgb_to_hexcolor(Buffs_from(f1).m_color), Buffs_from(f1).m_prettyName)) \
321     ARG_CASE(ARG_CS_SV,     "f3buffname",    sprintf("%s%s", rgb_to_hexcolor(Buffs_from(f3).m_color), Buffs_from(f3).m_prettyName)) \
322     ARG_CASE(ARG_CS_SV,     "item_wepammo",  (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \
323     ARG_CASE(ARG_DC,        "item_centime",  ftos(autocvar_notification_item_centerprinttime)) \
324     ARG_CASE(ARG_SV,        "death_team",    Team_ColoredFullName(f1)) \
325     ARG_CASE(ARG_CS,        "death_team",    Team_ColoredFullName(f1 - 1)) \
326     ARG_CASE(ARG_CS_SV_HA,  "minigame1_name",find(world,netname,s1).descriptor.message) \
327     ARG_CASE(ARG_CS_SV_HA,  "minigame1_d",   find(world,netname,s1).descriptor.netname)
328
329 #define NOTIF_HIT_MAX(count,funcname) do { \
330     if(sel_num == count) { backtrace(sprintf("%s: Hit maximum arguments!\n", funcname)); break; } \
331 } while(0)
332 #define NOTIF_HIT_UNKNOWN(token,funcname) { backtrace(sprintf("%s: Hit unknown token in selected string! '%s'\n", funcname, selected)); break; }
333
334 #define KILL_SPREE_LIST \
335     SPREE_ITEM(3, 03, _("TRIPLE FRAG! "), _("%s^K1 made a TRIPLE FRAG! %s^BG"), _("%s^K1 made a TRIPLE SCORE! %s^BG")) \
336     SPREE_ITEM(5, 05, _("RAGE! "), _("%s^K1 unlocked RAGE! %s^BG"), _("%s^K1 made FIVE SCORES IN A ROW! %s^BG")) \
337     SPREE_ITEM(10, 10, _("MASSACRE! "), _("%s^K1 started a MASSACRE! %s^BG"), _("%s^K1 made TEN SCORES IN A ROW! %s^BG")) \
338     SPREE_ITEM(15, 15, _("MAYHEM! "), _("%s^K1 executed MAYHEM! %s^BG"), _("%s^K1 made FIFTEEN SCORES IN A ROW! %s^BG")) \
339     SPREE_ITEM(20, 20, _("BERSERKER! "), _("%s^K1 is a BERSERKER! %s^BG"), _("%s^K1 made TWENTY SCORES IN A ROW! %s^BG")) \
340     SPREE_ITEM(25, 25, _("CARNAGE! "), _("%s^K1 inflicts CARNAGE! %s^BG"), _("%s^K1 made TWENTY FIVE SCORES IN A ROW! %s^BG")) \
341     SPREE_ITEM(30, 30, _("ARMAGEDDON! "), _("%s^K1 unleashes ARMAGEDDON! %s^BG"), _("%s^K1 made THIRTY SCORES IN A ROW! %s^BG"))
342
343 #ifdef CSQC
344 string notif_arg_frag_ping(float newline, float fping)
345 {
346     if(fping == NO_MSG)
347         return sprintf(CCR(_("%s(^F1Bot^BG)")), (newline ? "\n" : " "));
348     else
349         return sprintf(CCR(_("%s(Ping ^F1%d^BG)")), (newline ? "\n" : " "), fping);
350 }
351
352 string notif_arg_frag_stats(float fhealth, float farmor, float fping)
353 {
354     if (!(fhealth < 1))
355         return sprintf(CCR(_("\n(Health ^1%d^BG / Armor ^2%d^BG)%s")), fhealth, farmor, notif_arg_frag_ping(false, fping));
356     else
357         return sprintf(CCR(_("\n(^F4Dead^BG)%s")), notif_arg_frag_ping(false, fping));
358 }
359
360 string notif_arg_missing_teams(float f1)
361 {
362     return sprintf("%s%s%s%s",
363         ((f1 & 1) ?
364             sprintf("%s%s", Team_ColoredFullName(NUM_TEAM_1), ((f1 & (2 + 4 + 8)) ? ", " : ""))
365             :
366             ""
367         ),
368         ((f1 & 2) ?
369             sprintf("%s%s", Team_ColoredFullName(NUM_TEAM_2), ((f1 & (4 + 8)) ? ", " : ""))
370             :
371             ""
372         ),
373         ((f1 & 4) ?
374             sprintf("%s%s", Team_ColoredFullName(NUM_TEAM_3), ((f1 & 8) ? ", " : ""))
375             :
376             ""
377         ),
378         ((f1 & 8) ?
379             Team_ColoredFullName(NUM_TEAM_4)
380             :
381             ""
382         )
383     );
384 }
385
386 string notif_arg_spree_cen(float spree)
387 {
388     // 0 = off, 1 = target (but only for first victim) and attacker
389     if(autocvar_notification_show_sprees_center)
390     {
391         if(spree > 1)
392         {
393             #define SPREE_ITEM(counta,countb,center,normal,gentle) \
394                 case counta: { return normal_or_gentle(center, sprintf(_("%d score spree! "), spree)); }
395
396             switch(spree)
397             {
398                 KILL_SPREE_LIST
399                 default:
400                 {
401                     if (!autocvar_notification_show_sprees_center_specialonly)
402                     {
403                         return
404                             sprintf(
405                                 normal_or_gentle(
406                                     _("%d frag spree! "),
407                                     _("%d score spree! ")
408                                 ),
409                                 spree);
410                     }
411                     else { return ""; } // don't show spree information if it isn't an achievement
412                 }
413             }
414
415             #undef SPREE_ITEM
416         }
417         else if(spree == -1) // first blood
418         {
419             return normal_or_gentle(_("First blood! "), _("First score! "));
420         }
421         else if(spree == -2) // first victim
422         {
423             return normal_or_gentle(_("First victim! "), _("First casualty! "));
424         }
425     }
426     return "";
427 }
428 #endif
429
430 string notif_arg_spree_inf(float type, string input, string player, float spree)
431 {
432     switch(type)
433     {
434         case 1: // attacker kill spree
435         {
436             // 0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker
437             // this conditional (& 2) is true for 2 and 3
438             if(autocvar_notification_show_sprees_info & 2)
439             {
440                 #ifdef CSQC
441                 string spree_newline =
442                     ( autocvar_notification_show_sprees_info_newline ?
443                         ((substring(input, 0, 1) == "\{3}") ? "\n\{3}" : "\n") : "" );
444                 #else
445                 string spree_newline =
446                     (autocvar_notification_show_sprees_info_newline ? "\n" : "");
447                 #endif
448
449                 if(spree > 1)
450                 {
451                     #define SPREE_ITEM(counta,countb,center,normal,gentle) \
452                         case counta: { return sprintf(CCR(normal_or_gentle(normal, gentle)), player, spree_newline); }
453
454                     switch(spree)
455                     {
456                         KILL_SPREE_LIST
457                         default:
458                         {
459                             if (!autocvar_notification_show_sprees_info_specialonly)
460                             {
461                                 return
462                                     sprintf(
463                                         CCR(normal_or_gentle(
464                                             _("%s^K1 has %d frags in a row! %s^BG"),
465                                             _("%s^K1 made %d scores in a row! %s^BG")
466                                         )),
467                                         player,
468                                         spree,
469                                         spree_newline
470                                     );
471                             }
472                             else { return ""; } // don't show spree information if it isn't an achievement
473                         }
474                     }
475
476                     #undef SPREE_ITEM
477                 }
478                 else if(spree == -1) // firstblood
479                 {
480                     return
481                         sprintf(
482                             CCR(normal_or_gentle(
483                                 _("%s^K1 drew first blood! %s^BG"),
484                                 _("%s^K1 got the first score! %s^BG")
485                             )),
486                             player,
487                             spree_newline
488                         );
489                 }
490             }
491             break;
492         }
493
494         case -1: // kill spree ended
495         {
496             if((spree > 1) && (autocvar_notification_show_sprees_info & 1))
497             {
498                 return
499                     sprintf(normal_or_gentle(
500                         _(", ending their %d frag spree"),
501                         _(", ending their %d score spree")
502                         ),
503                         spree
504                     );
505             }
506             break;
507         }
508
509         case -2: // kill spree lost
510         {
511             if((spree > 1) && (autocvar_notification_show_sprees_info & 1))
512             {
513                 return
514                     sprintf(normal_or_gentle(
515                         _(", losing their %d frag spree"),
516                         _(", losing their %d score spree")
517                         ),
518                         spree
519                     );
520             }
521             break;
522         }
523     }
524     return "";
525 }
526
527
528 // ====================================
529 //  Initialization/Create Declarations
530 // ====================================
531
532 enum {
533     NO_CPID
534 ,   CPID_ASSAULT_ROLE
535 ,   CPID_ROUND
536 ,   CPID_CAMPCHECK
537 ,   CPID_CTF_CAPSHIELD
538 ,   CPID_CTF_LOWPRIO
539 ,   CPID_CTF_PASS
540 ,   CPID_STALEMATE
541 ,   CPID_NADES
542 ,   CPID_IDLING
543 ,   CPID_ITEM
544 ,   CPID_PREVENT_JOIN
545 ,   CPID_KEEPAWAY
546 ,   CPID_KEEPAWAY_WARN
547 ,   CPID_KEYHUNT
548 ,   CPID_KEYHUNT_OTHER
549 ,   CPID_LMS
550 ,   CPID_MISSING_TEAMS
551 ,   CPID_MISSING_PLAYERS
552 ,   CPID_INSTAGIB_FINDAMMO
553 ,   CPID_MOTD
554 ,   CPID_NIX
555 ,   CPID_ONSLAUGHT
556 ,   CPID_ONS_CAPSHIELD
557 ,   CPID_OVERTIME
558 ,   CPID_POWERUP
559 ,   CPID_RACE_FINISHLAP
560 ,   CPID_TEAMCHANGE
561 ,   CPID_TIMEOUT
562 ,   CPID_VEHICLES
563 ,   CPID_VEHICLES_OTHER
564 // always last
565 ,   NOTIF_CPID_COUNT
566 };
567 // notification counts
568 const float NOTIF_FIRST = 1;
569 float NOTIF_ANNCE_COUNT;
570 float NOTIF_INFO_COUNT;
571 float NOTIF_CENTER_COUNT;
572 float NOTIF_MULTI_COUNT;
573 float NOTIF_CHOICE_COUNT;
574
575 // notification limits -- INCREASE AS NECESSARY
576 const float NOTIF_ANNCE_MAX   = 400;
577 const float NOTIF_INFO_MAX    = 450;
578 const float NOTIF_CENTER_MAX  = 350;
579 const float NOTIF_MULTI_MAX   = 300;
580 const float NOTIF_CHOICE_MAX  = 50;
581
582 // notification entities
583 entity msg_annce_notifs[NOTIF_ANNCE_MAX];
584 entity msg_info_notifs[NOTIF_INFO_MAX];
585 entity msg_center_notifs[NOTIF_CENTER_MAX];
586 entity msg_multi_notifs[NOTIF_MULTI_MAX];
587 entity msg_choice_notifs[NOTIF_CHOICE_MAX];
588
589 // common notification entity values
590 .float nent_default;
591 .float nent_enabled;
592 .float nent_type;
593 .float nent_id;
594 .string nent_name;
595 .int nent_stringcount;
596 .int nent_floatcount;
597
598 // MSG_ANNCE entity values
599 .float nent_channel;
600 .string nent_snd;
601 .float nent_vol;
602 .float nent_position;
603
604 // MSG_INFO and MSG_CENTER entity values
605 .string nent_args; // used by both
606 .string nent_hudargs; // used by info
607 .string nent_icon; // used by info
608 .float nent_cpid; // used by center
609 .string nent_durcnt; // used by center
610 .string nent_string; // used by both
611
612 // MSG_MULTI entity values
613 .entity nent_msgannce;
614 .entity nent_msginfo;
615 .entity nent_msgcenter;
616
617 // MSG_CHOICE entity values
618 .float nent_challow_def;
619 .float nent_challow_var;
620 .entity nent_optiona;
621 .entity nent_optionb;
622
623 // networked notification entity values
624 .float nent_broadcast;
625 .entity nent_client;
626 .float nent_net_type;
627 .float nent_net_name;
628 .string nent_strings[4];
629 .float nent_floats[4];
630
631 // other notification properties
632 .float msg_choice_choices[NOTIF_CHOICE_MAX]; // set on each player containing MSG_CHOICE choices
633
634 // initialization error detection
635 float notif_error;
636 float notif_global_error;
637
638 #define MSG_ANNCE_NOTIF(default,name,channel,sound,volume,position) \
639     NOTIF_ADD_AUTOCVAR(name, default) \
640     float name; \
641     void RegisterNotification_##name() \
642     { \
643         SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_ANNCE_COUNT) \
644         CHECK_MAX_COUNT(name, NOTIF_ANNCE_MAX, NOTIF_ANNCE_COUNT, "MSG_ANNCE") \
645         Create_Notification_Entity( \
646             /* COMMON ======================== */ \
647             default,            /* var_default */ \
648             ACVNN(name),        /* var_cvar    */ \
649             MSG_ANNCE,          /* typeId      */ \
650             name,               /* nameid      */ \
651             strtoupper(#name),  /* namestring  */ \
652             NO_MSG,             /* strnum      */ \
653             NO_MSG,             /* flnum       */ \
654             /* ANNCE ============= */ \
655             channel,   /* channel  */ \
656             sound,     /* snd      */ \
657             volume,    /* vol      */ \
658             position,  /* position */ \
659             /* INFO & CENTER == */ \
660             "",      /* args    */ \
661             "",      /* hudargs */ \
662             "",      /* icon    */ \
663             NO_MSG,  /* cpid    */ \
664             "",      /* durcnt  */ \
665             "",      /* normal  */ \
666             "",      /* gentle  */ \
667             /* MULTI ============= */ \
668             NO_MSG,  /* anncename  */ \
669             NO_MSG,  /* infoname   */ \
670             NO_MSG,  /* centername */ \
671             /* MSG_CHOICE ========== */ \
672             NO_MSG,   /* challow_def */ \
673             NO_MSG,   /* challow_var */ \
674             NO_MSG,   /* chtype      */ \
675             NO_MSG,   /* optiona     */ \
676             NO_MSG);  /* optionb     */ \
677     } \
678     ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
679
680 #define MSG_INFO_NOTIF(default,name,strnum,flnum,args,hudargs,icon,normal,gentle) \
681     NOTIF_ADD_AUTOCVAR(name, default) \
682     int name; \
683     void RegisterNotification_##name() \
684     { \
685         SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_INFO_COUNT) \
686         CHECK_MAX_COUNT(name, NOTIF_INFO_MAX, NOTIF_INFO_COUNT, "MSG_INFO") \
687         Create_Notification_Entity( \
688             /* COMMON ======================== */ \
689             default,            /* var_default */ \
690             ACVNN(name),        /* var_cvar    */ \
691             MSG_INFO,           /* typeId      */ \
692             name,               /* nameid      */ \
693             strtoupper(#name),  /* namestring  */ \
694             strnum,             /* strnum      */ \
695             flnum,              /* flnum       */ \
696             /* ANNCE =========== */ \
697             NO_MSG,  /* channel  */ \
698             "",      /* snd      */ \
699             NO_MSG,  /* vol      */ \
700             NO_MSG,  /* position */ \
701             /* INFO & CENTER === */ \
702             args,     /* args    */ \
703             hudargs,  /* hudargs */ \
704             icon,     /* icon    */ \
705             NO_MSG,   /* cpid    */ \
706             "",       /* durcnt  */ \
707             normal,   /* normal  */ \
708             gentle,   /* gentle  */ \
709             /* MULTI ============= */ \
710             NO_MSG,  /* anncename  */ \
711             NO_MSG,  /* infoname   */ \
712             NO_MSG,  /* centername */ \
713             /* CHOICE ============== */ \
714             NO_MSG,   /* challow_def */ \
715             NO_MSG,   /* challow_var */ \
716             NO_MSG,   /* chtype      */ \
717             NO_MSG,   /* optiona     */ \
718             NO_MSG);  /* optionb     */ \
719     } \
720     ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
721
722 .string nent_iconargs;
723 #define MULTIICON_INFO(default,name,strnum,flnum,args,hudargs,iconargs,icon,normal,gentle) \
724     NOTIF_ADD_AUTOCVAR(name, default) \
725     int name; \
726     void RegisterNotification_##name() \
727     { \
728         SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_INFO_COUNT) \
729         CHECK_MAX_COUNT(name, NOTIF_INFO_MAX, NOTIF_INFO_COUNT, "MSG_INFO") \
730         Create_Notification_Entity( \
731             /* COMMON ======================== */ \
732             default,            /* var_default */ \
733             ACVNN(name),        /* var_cvar    */ \
734             MSG_INFO,           /* typeid      */ \
735             name,               /* nameid      */ \
736             strtoupper(#name),  /* namestring  */ \
737             strnum,             /* strnum      */ \
738             flnum,              /* flnum       */ \
739             /* ANNCE =========== */ \
740             NO_MSG,  /* channel  */ \
741             "",      /* snd      */ \
742             NO_MSG,  /* vol      */ \
743             NO_MSG,  /* position */ \
744             /* INFO & CENTER === */ \
745             args,     /* args    */ \
746             hudargs,  /* hudargs */ \
747             icon,     /* icon    */ \
748             NO_MSG,   /* cpid    */ \
749             "",       /* durcnt  */ \
750             normal,   /* normal  */ \
751             gentle,   /* gentle  */ \
752             /* MULTI ============= */ \
753             NO_MSG,  /* anncename  */ \
754             NO_MSG,  /* infoname   */ \
755             NO_MSG,  /* centername */ \
756             /* CHOICE ============== */ \
757             NO_MSG,   /* challow_def */ \
758             NO_MSG,   /* challow_var */ \
759             NO_MSG,   /* chtype      */ \
760             NO_MSG,   /* optiona     */ \
761             NO_MSG);  /* optionb     */ \
762         msg_info_notifs[name - 1].nent_iconargs = iconargs; \
763     } \
764     ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
765
766 #define MSG_CENTER_NOTIF(default,name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
767     NOTIF_ADD_AUTOCVAR(name, default) \
768     float name; \
769     void RegisterNotification_##name() \
770     { \
771         SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_CENTER_COUNT) \
772         CHECK_MAX_COUNT(name, NOTIF_CENTER_MAX, NOTIF_CENTER_COUNT, "MSG_CENTER") \
773         Create_Notification_Entity( \
774             /* COMMON ======================== */ \
775             default,            /* var_default */ \
776             ACVNN(name),        /* var_cvar    */ \
777             MSG_CENTER,         /* typeId      */ \
778             name,               /* nameid      */ \
779             strtoupper(#name),  /* namestring  */ \
780             strnum,             /* strnum      */ \
781             flnum,              /* flnum       */ \
782             /* ANNCE =========== */ \
783             NO_MSG,  /* channel  */ \
784             "",      /* snd      */ \
785             NO_MSG,  /* vol      */ \
786             NO_MSG,  /* position */ \
787             /* INFO & CENTER == */ \
788             args,    /* args    */ \
789             "",      /* hudargs */ \
790             "",      /* icon    */ \
791             cpid,    /* cpid    */ \
792             durcnt,  /* durcnt  */ \
793             normal,  /* normal  */ \
794             gentle,  /* gentle  */ \
795             /* MULTI ============= */ \
796             NO_MSG,  /* anncename  */ \
797             NO_MSG,  /* infoname   */ \
798             NO_MSG,  /* centername */ \
799             /* CHOICE ============== */ \
800             NO_MSG,   /* challow_def */ \
801             NO_MSG,   /* challow_var */ \
802             NO_MSG,   /* chtype      */ \
803             NO_MSG,   /* optiona     */ \
804             NO_MSG);  /* optionb     */ \
805     } \
806     ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
807
808 #define MSG_MULTI_NOTIF(default,name,anncename,infoname,centername) \
809     NOTIF_ADD_AUTOCVAR(name, default) \
810     int name; \
811     void RegisterNotification_##name() \
812     { \
813         SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_MULTI_COUNT) \
814         CHECK_MAX_COUNT(name, NOTIF_MULTI_MAX, NOTIF_MULTI_COUNT, "MSG_MULTI") \
815         Create_Notification_Entity( \
816             /* COMMON ======================== */ \
817             default,            /* var_default */ \
818             ACVNN(name),        /* var_cvar    */ \
819             MSG_MULTI,          /* typeId      */ \
820             name,               /* nameid      */ \
821             strtoupper(#name),  /* namestring  */ \
822             NO_MSG,             /* strnum      */ \
823             NO_MSG,             /* flnum       */ \
824             /* ANNCE =========== */ \
825             NO_MSG,  /* channel  */ \
826             "",      /* snd      */ \
827             NO_MSG,  /* vol      */ \
828             NO_MSG,  /* position */ \
829             /* INFO & CENTER == */ \
830             "",      /* args    */ \
831             "",      /* hudargs */ \
832             "",      /* icon    */ \
833             NO_MSG,  /* cpid    */ \
834             "",      /* durcnt  */ \
835             "",      /* normal  */ \
836             "",      /* gentle  */ \
837             /* MULTI ================= */ \
838             anncename,   /* anncename  */ \
839             infoname,    /* infoname   */ \
840             centername,  /* centername */ \
841             /* CHOICE ============== */ \
842             NO_MSG,   /* challow_def */ \
843             NO_MSG,   /* challow_var */ \
844             NO_MSG,   /* chtype      */ \
845             NO_MSG,   /* optiona     */ \
846             NO_MSG);  /* optionb     */ \
847     } \
848     ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
849
850 #define ACVNN(name) autocvar_notification_##name
851
852 #define MSG_CHOICE_NOTIF(default,challow,name,chtype,optiona,optionb) \
853     NOTIF_ADD_AUTOCVAR(name, default) \
854     NOTIF_ADD_AUTOCVAR(name##_ALLOWED, challow) \
855     float name; \
856     void RegisterNotification_##name() \
857     { \
858         SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_CHOICE_COUNT) \
859         CHECK_MAX_COUNT(name, NOTIF_CHOICE_MAX, NOTIF_CHOICE_COUNT, "MSG_CHOICE") \
860         Create_Notification_Entity( \
861             /* COMMON ======================== */ \
862             default,            /* var_default */ \
863             ACVNN(name),        /* var_cvar    */ \
864             MSG_CHOICE,         /* typeId      */ \
865             name,               /* nameid      */ \
866             strtoupper(#name),  /* namestring  */ \
867             NO_MSG,             /* strnum      */ \
868             NO_MSG,             /* flnum       */ \
869             /* ANNCE =========== */ \
870             NO_MSG,  /* channel  */ \
871             "",      /* snd      */ \
872             NO_MSG,  /* vol      */ \
873             NO_MSG,  /* position */ \
874             /* INFO & CENTER == */ \
875             "",      /* args    */ \
876             "",      /* hudargs */ \
877             "",      /* icon    */ \
878             NO_MSG,  /* cpid    */ \
879             "",      /* durcnt  */ \
880             "",      /* normal  */ \
881             "",      /* gentle  */ \
882             /* MULTI ============= */ \
883             NO_MSG,  /* anncename  */ \
884             NO_MSG,  /* infoname   */ \
885             NO_MSG,  /* centername */ \
886             /* CHOICE ============================================= */ \
887             challow,                                 /* challow_def */ \
888             autocvar_notification_##name##_ALLOWED,  /* challow_var */ \
889             chtype,                                  /* chtype      */ \
890             optiona,                                 /* optiona     */ \
891             optionb);                                /* optionb     */ \
892     } \
893     ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
894
895 void RegisterNotifications_First()
896 {
897     notif_global_error = false;
898
899     #ifdef SVQC
900     #define dedi (server_is_dedicated ? "a dedicated " : "")
901     #else
902     #define dedi ""
903     #endif
904
905     LOG_INFOF("Beginning notification initialization on %s%s program...\n", dedi, PROGNAME);
906     #undef dedi
907
908     // maybe do another implementation of this with checksums? for now, we don't need versioning
909     /*if(autocvar_notification_version != NOTIF_VERSION)
910     {
911         #ifdef CSQC
912         if(autocvar_notification_version_mismatch_client_error)
913         #else
914         if(autocvar_notification_version_mismatch_server_error)
915         #endif
916             notif_global_error = true;
917
918         printf("^1NOTIFICATION VERSION MISMATCH: ^7program = %s, config = %d, code = %d.\n",
919             PROGNAME, autocvar_notification_version, NOTIF_VERSION);
920     }*/
921 }
922
923 void RegisterNotifications_Done()
924 {
925     if(notif_global_error)
926     {
927         // shit happened... stop the loading of the program now if this is unacceptable
928         if(autocvar_notification_errors_are_fatal)
929             error("Notification initialization failed! Read above and fix the errors!\n");
930         else
931             LOG_INFO("Notification initialization failed! Read above and fix the errors!\n");
932     }
933     else { LOG_INFO("Notification initialization successful!\n"); }
934 }
935
936 // NOW we actually activate the declarations
937 ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotifications_First)
938 #include "notifications.inc"
939 ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotifications_Done)
940
941 STATIC_INIT(RegisterNotifications) { CALL_ACCUMULATED_FUNCTION(RegisterNotifications); }
942
943 #endif