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