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