]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
That deserves to be its own category
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / notifications.qc
1 // ================================================
2 //  Unified notification system, written by Samual
3 //  Last updated: February, 2013
4 // ================================================
5
6 string Get_Notif_TypeName(float net_type)
7 {
8         switch(net_type)
9         {
10                 case MSG_INFO: return "MSG_INFO";
11                 case MSG_CENTER: return "MSG_CENTER";
12                 case MSG_WEAPON: return "MSG_WEAPON";
13                 case MSG_DEATH: return "MSG_DEATH";
14         }
15         backtrace(sprintf("Get_Notif_TypeName(%d): Improper net type!\n", net_type));
16         return "your ass";
17 }
18
19 entity Get_Notif_Ent(float net_type, float net_name)
20 {
21         switch(net_type)
22         {
23                 case MSG_INFO: return msg_info_notifs[net_name - 1];
24                 case MSG_CENTER: return msg_center_notifs[net_name - 1];
25                 case MSG_WEAPON: return msg_weapon_notifs[net_name - 1];
26                 case MSG_DEATH: return msg_death_notifs[net_name - 1];
27         }
28         backtrace(sprintf("Get_Notif_Ent(%d, %d): Improper net type!\n", net_type, net_name));
29         return world;
30 }
31
32 string Notification_CheckArgs_TypeName(float net_type, float net_name)
33 {
34         // check supplied type and name for errors
35         string checkargs = "";
36         #define CHECKARG_TYPENAME(type) case MSG_##type##: \
37                 { if(!net_name || (net_name > NOTIF_##type##_COUNT)) \
38                 { checkargs = sprintf("Improper name: %d!", net_name); } break; }
39         switch(net_type)
40         {
41                 CHECKARG_TYPENAME(INFO)
42                 CHECKARG_TYPENAME(CENTER)
43                 CHECKARG_TYPENAME(WEAPON)
44                 CHECKARG_TYPENAME(DEATH)
45                 default: { checkargs = sprintf("Improper type: %d!", checkargs, net_type); break; }
46         }
47         #undef CHECKARG_TYPENAME
48         return checkargs;
49 }
50
51 #ifdef SVQC
52 string Notification_CheckArgs(float broadcast, entity client, float net_type, float net_name)
53 {
54         // check supplied broadcast, target, type, and name for errors
55         string checkargs = Notification_CheckArgs_TypeName(net_type, net_name);
56         if(checkargs != "") { checkargs = strcat(checkargs, " "); }
57         switch(broadcast)
58         {
59                 case NOTIF_ONE:
60                 case NOTIF_ONE_ONLY:
61                 {
62                         if(IS_NOT_A_CLIENT(client))
63                                 { checkargs = sprintf("%sNo client provided!", checkargs); }
64                         break;
65                 }
66                 
67                 case NOTIF_ANY_EXCEPT:
68                 {
69                         if(IS_NOT_A_CLIENT(client))
70                                 { checkargs = sprintf("%sException can't be a non-client!", checkargs); }
71                         break;
72                 }
73                 
74                 case NOTIF_ANY:
75                 {
76                         if(client)
77                                 { checkargs = sprintf("%sEntity provided when world was required!", checkargs); }
78                         break;
79                 }
80                 
81                 case NOTIF_TEAM:
82                 case NOTIF_TEAM_EXCEPT:
83                 {
84                         if not(teamplay) { checkargs = sprintf("%sTeamplay not active!", checkargs); }
85                         else if(IS_NOT_A_CLIENT(client))
86                         {
87                                 if(broadcast == NOTIF_TEAM) { checkargs = sprintf("%sNo client provided!", checkargs); }
88                                 else { checkargs = sprintf("%sException can't be a non-client!", checkargs); }
89                         }
90                         break;
91                 }
92                 
93                 default: { checkargs = sprintf("%sImproper broadcast: %d!", checkargs, broadcast); break; }
94         }
95         return checkargs;
96 }
97 #endif
98
99 // ===============================
100 //  Initialization Core Functions
101 // ===============================
102
103 string Process_Notif_Line(
104         float msg_is_info,
105         float chat,
106         string input,
107         string notiftype,
108         string notifname,
109         string stringtype)
110 {
111         if(msg_is_info)
112         {
113                 #ifdef CSQC
114                 if((chat && autocvar_notification_allow_chatboxprint)
115                         || (autocvar_notification_allow_chatboxprint == 2))
116                 {
117                         // pass 1: add ETX char at beginning of line
118                         input = strcat("\{3}", input);
119
120                         // pass 2: add ETX char at end of each new line (so that
121                         // messages with multiple lines are put through chatbox too)
122                         input = strreplace("\n", "\n\{3}", input);
123
124                         // pass 3: strip trailing ETX char
125                         if(substring(input, (strlen(input) - 1), 1) == "\{3}")
126                                 { input = substring(input, 0, (strlen(input) - 1)); }
127                 }
128                 #endif
129                 if(substring(input, (strlen(input) - 1), 1) != "\n")
130                 {
131                         print(sprintf(
132                                 strcat(
133                                         "^1MISSING/BROKEN NEW LINE AT END OF NOTIFICATION: ",
134                                         "^7net_type = MSG_%s, net_name = %s, string = %s.\n"
135                                 ),
136                                 notiftype,
137                                 notifname,
138                                 stringtype
139                         ));
140                         notif_error = TRUE;
141                         return strcat(input, "\n");
142                 }
143         }
144         return input;
145 }
146
147 string Process_Notif_Args(
148         float arg_type,
149         string args,
150         string notiftype,
151         string notifname)
152 {
153         string selected, remaining = args;
154         float sel_num = 0;
155
156         for(;(remaining != "");)
157         {
158                 selected = car(remaining); remaining = cdr(remaining);
159
160                 switch(arg_type)
161                 {
162                         case 1: // normal args
163                         {
164                                 if(sel_num == NOTIF_MAX_ARGS)
165                                 {
166                                         print(sprintf(
167                                                 strcat(
168                                                         "^1NOTIFICATION HAS TOO MANY ARGUMENTS: ",
169                                                         "^7net_type = MSG_%s, net_name = %s, max args = %d.\n"
170                                                 ),
171                                                 notiftype,
172                                                 notifname,
173                                                 NOTIF_MAX_ARGS
174                                         ));
175                                         notif_error = TRUE;
176                                         break;
177                                 }
178
179                                 switch(strtolower(selected))
180                                 {
181                                         #define ARG_CASE(prog,selected,result) case selected: { ++sel_num; break; }
182                                         NOTIF_ARGUMENT_LIST
183                                         #undef ARG_CASE
184                                         default:
185                                         {
186                                                 print(sprintf(
187                                                         strcat(
188                                                                 "^1NOTIFICATION WITH UNKNOWN TOKEN IN ARGUMENT STRING: ",
189                                                                 "^7net_type = MSG_%s, net_name = %s, args arg = '%s'.\n"
190                                                         ),
191                                                         notiftype,
192                                                         notifname,
193                                                         selected
194                                                 ));
195                                                 notif_error = TRUE;
196                                                 break;
197                                         }
198                                 }
199                                 break;
200                         }
201                         case 2: // hudargs
202                         {
203                                 if(sel_num == NOTIF_MAX_HUDARGS)
204                                 {
205                                         print(sprintf(
206                                                 strcat(
207                                                         "^1NOTIFICATION HAS TOO MANY ARGUMENTS: ",
208                                                         "^7net_type = MSG_%s, net_name = %s, max hudargs = %d.\n"
209                                                 ),
210                                                 notiftype,
211                                                 notifname,
212                                                 NOTIF_MAX_HUDARGS
213                                         ));
214                                         notif_error = TRUE;
215                                         break;
216                                 }
217
218                                 switch(strtolower(selected))
219                                 {
220                                         #define ARG_CASE(prog,selected,result) \
221                                                 #if (prog == ARG_CS_SV_HA) \
222                                                         case selected: { ++sel_num; break; } \
223                                                 #endif
224                                         NOTIF_ARGUMENT_LIST
225                                         #undef ARG_CASE
226                                         default:
227                                         {
228                                                 print(sprintf(
229                                                         strcat(
230                                                                 "^1NOTIFICATION WITH UNKNOWN TOKEN IN ARGUMENT STRING: ",
231                                                                 "^7net_type = MSG_%s, net_name = %s, hudargs arg = '%s'.\n"
232                                                         ),
233                                                         notiftype,
234                                                         notifname,
235                                                         selected
236                                                 ));
237                                                 notif_error = TRUE;
238                                                 break;
239                                         }
240                                 }
241                                 break;
242                         }
243                         case 3: // durcnt 
244                         {
245                                 if(sel_num == NOTIF_MAX_DURCNT)
246                                 {
247                                         print(sprintf(
248                                                 strcat(
249                                                         "^1NOTIFICATION HAS TOO MANY ARGUMENTS: ",
250                                                         "^7net_type = MSG_%s, net_name = %s, max durcnt = %d.\n"
251                                                 ),
252                                                 notiftype,
253                                                 notifname,
254                                                 NOTIF_MAX_DURCNT
255                                         ));
256                                         notif_error = TRUE;
257                                         break;
258                                 }
259
260                                 switch(strtolower(selected))
261                                 {
262                                         #define ARG_CASE(prog,selected,result) \
263                                                 #if (prog == ARG_CS_SV_DC) || (prog == ARG_DC) \
264                                                         case selected: { ++sel_num; break; } \
265                                                 #endif
266                                         NOTIF_ARGUMENT_LIST
267                                         #undef ARG_CASE
268                                         default:
269                                         {
270                                                 if(ftos(stof(selected)) != "") { ++sel_num; }
271                                                 else
272                                                 {
273                                                         print(sprintf(
274                                                                 strcat(
275                                                                         "^1NOTIFICATION WITH UNKNOWN TOKEN IN ARGUMENT STRING: ",
276                                                                         "^7net_type = MSG_%s, net_name = %s, durcnt arg = '%s'.\n"
277                                                                 ),
278                                                                 notiftype,
279                                                                 notifname,
280                                                                 selected
281                                                         ));
282                                                         notif_error = TRUE;
283                                                 }
284                                                 break;
285                                         }
286                                 }
287                                 break;
288                         }
289                 }
290         }
291         return args;
292 }
293
294 void Create_Notification_Entity(
295         float var_default,
296         float var_cvar,
297         float typeid,
298         float nameid,
299         string namestring,
300         float infoname,
301         float centername,
302         float strnum,
303         float flnum,
304         string args,
305         string hudargs,
306         string icon,
307         float cpid,
308         string durcnt,
309         string normal,
310         string gentle,
311         float msg_is_info,
312         float msg_is_multi)
313 {
314         // =====================
315         //  Global Entity Setup
316         // =====================
317         entity notif = spawn();
318         string typestring = "";
319         switch(typeid)
320         {
321                 case MSG_INFO:
322                 {
323                         typestring = "MSG_INFO";
324                         msg_info_notifs[nameid - 1] = notif;
325                         notif.classname = "msg_info_notification";
326                         break;
327                 }
328                 case MSG_CENTER:
329                 {
330                         typestring = "MSG_CENTER";
331                         msg_center_notifs[nameid - 1] = notif;
332                         notif.classname = "msg_center_notification";
333                         break;
334                 }
335                 case MSG_WEAPON:
336                 {
337                         typestring = "MSG_WEAPON";
338                         msg_weapon_notifs[nameid - 1] = notif;
339                         notif.classname = "msg_weapon_notification";
340                         break;
341                 }
342                 case MSG_DEATH:
343                 {
344                         typestring = "MSG_DEATH";
345                         msg_death_notifs[nameid - 1] = notif;
346                         notif.classname = "msg_death_notification";
347                         break;
348                 }
349                 default:
350                 {
351                         error(sprintf(
352                                 strcat(
353                                         "^1NOTIFICATION WITH IMPROPER TYPE: ",
354                                         "^7net_type = %d, net_name = %s.\n"
355                                 ),
356                                 typeid,
357                                 namestring
358                         ));
359                         return; // It's not possible to recover from this one
360                 }
361         }
362         notif.nent_default = var_default;
363         notif.nent_name = strzone(namestring);
364         notif.nent_id = nameid;
365         notif.nent_enabled = (1 <= var_cvar);
366
367         // Other pre-notif-setup requisites
368         notif_error = FALSE;
369
370         // ====================
371         //  Notification Setup
372         // ====================
373         if(msg_is_multi)
374         {
375                 // Set MSG_WEAPON and MSG_DEATH string/float counts
376                 if((infoname == NO_MSG) && (centername == NO_MSG))
377                 {
378                         print(sprintf(
379                                 strcat(
380                                         "^1NOTIFICATION WITH NO SUBCALLS: ",
381                                         "^7net_type = %s, net_name = %s.\n"
382                                 ),
383                                 typestring,
384                                 namestring
385                         ));
386                         notif_error = TRUE;
387                 }
388                 else
389                 {
390                         float infoname_stringcount = 0, infoname_floatcount = 0;
391                         float centername_stringcount = 0, centername_floatcount = 0;
392                         
393                         if(infoname != NO_MSG)
394                         {
395                                 notif.nent_msginfo = msg_info_notifs[infoname - 1];
396                                 infoname_stringcount = notif.nent_msginfo.nent_stringcount;
397                                 infoname_floatcount = notif.nent_msginfo.nent_floatcount;
398                         }
399                         
400                         if(centername != NO_MSG)
401                         {
402                                 notif.nent_msgcenter = msg_center_notifs[centername - 1];
403                                 centername_stringcount = notif.nent_msgcenter.nent_stringcount;
404                                 centername_floatcount = notif.nent_msgcenter.nent_floatcount;
405                         }
406                         
407                         // set the requirements of THIS notification to the totals of its subcalls
408                         notif.nent_stringcount = max(infoname_stringcount, centername_stringcount);
409                         notif.nent_floatcount = max(infoname_floatcount, centername_floatcount);
410                 }
411         }
412         else
413         {
414                 // Set MSG_INFO and MSG_CENTER string/float counts
415                 notif.nent_stringcount = strnum;
416                 notif.nent_floatcount = flnum;
417
418                 // Only initialize arguments if we're either a client or on a dedicated server
419                 #ifdef SVQC
420                 float should_process_args = server_is_dedicated;
421                 #else
422                 float should_process_args = TRUE;
423                 #endif
424
425                 if(should_process_args)
426                 {
427                         // ========================
428                         //  Process Main Arguments
429                         // ========================
430                         if(strnum + flnum)
431                         {
432                                 if(args != "")
433                                 {
434                                         notif.nent_args = strzone(
435                                                 Process_Notif_Args(1, args, typestring, namestring));
436                                 }
437                                 else if((hudargs == "") && (durcnt ==""))
438                                 {
439                                         print(sprintf(
440                                                 strcat(
441                                                         "^1NOTIFICATION HAS ARG COUNTS BUT NO ARGS OR HUDARGS OR DURCNT: ",
442                                                         "^7net_type = %s, net_name = %s, strnum = %d, flnum = %d\n"
443                                                 ),
444                                                 typestring,
445                                                 namestring,
446                                                 strnum,
447                                                 flnum
448                                         ));
449                                         notif_error = TRUE;
450                                 }
451                         }
452                         else if(args != "")
453                         {
454                                 notif.nent_args = strzone(
455                                         Process_Notif_Args(1, args, typestring, namestring));
456                         }
457
458
459                         // =======================================
460                         //  Process HUD and Centerprint Arguments
461                         //    Only processed on CSQC, as these
462                         //    args are only for HUD features.
463                         // =======================================
464                         #ifdef CSQC
465                         if(hudargs != "")
466                         {
467                                 notif.nent_hudargs = strzone(
468                                         Process_Notif_Args(2, hudargs, typestring, namestring));
469                                         
470                                 if(icon != "") { notif.nent_icon = strzone(icon); }
471                                 else
472                                 {
473                                         print(sprintf(
474                                                 strcat(
475                                                         "^1NOTIFICATION HAS HUDARGS BUT NO ICON: ",
476                                                         "^7net_type = MSG_%s, net_name = %s.\n"
477                                                 ),
478                                                 typestring,
479                                                 namestring
480                                         ));
481                                         notif_error = TRUE;
482                                 }
483                         }
484                         else if(icon != "")
485                         {
486                                 print(sprintf(
487                                         strcat(
488                                                 "^1NOTIFICATION HAS ICON BUT NO HUDARGS: ",
489                                                 "^7net_type = MSG_%s, net_name = %s.\n"
490                                         ),
491                                         typestring,
492                                         namestring
493                                 ));
494                                 notif_error = TRUE;
495                         }
496
497                         if(durcnt != "")
498                         {
499                                 notif.nent_durcnt = strzone(
500                                         Process_Notif_Args(3, durcnt, typestring, namestring));
501                                         
502                                 if(cpid != NO_MSG) { notif.nent_cpid = cpid; }
503                                 else
504                                 {
505                                         print(sprintf(
506                                                 strcat(
507                                                         "^1NOTIFICATION HAS DURCNT BUT NO CPID: ",
508                                                         "^7net_type = MSG_%s, net_name = %s.\n"
509                                                 ),
510                                                 typestring,
511                                                 namestring
512                                         ));
513                                         notif_error = TRUE;
514                                 }
515                         } 
516                         else if(cpid != NO_MSG) { notif.nent_cpid = cpid; }
517                         #endif
518
519
520                         // ======================
521                         //  Process Notif String
522                         // ======================
523                         #define SET_NOTIF_STRING(string,stringname) \
524                                 notif.nent_string = strzone(CCR( \
525                                         Process_Notif_Line( \
526                                                 msg_is_info, \
527                                                 (var_cvar > 1), \
528                                                 string, \
529                                                 typestring, \
530                                                 namestring, \
531                                                 stringname \
532                                         )) \
533                                 );
534
535                         if(GENTLE)
536                         {
537                                 if(gentle != "") { SET_NOTIF_STRING(gentle, "GENTLE") }
538                                 else if(normal != "") { SET_NOTIF_STRING(normal, "NORMAL") }
539                         }
540                         else if(normal != "") { SET_NOTIF_STRING(normal, "NORMAL") }
541                         
542                         #undef SET_NOTIF_STRING
543
544                         // Check to make sure a string was chosen
545                         if(notif.nent_string == "")
546                         {
547                                 print(sprintf(
548                                         strcat(
549                                                 "^1EMPTY NOTIFICATION: ",
550                                                 "^7net_type = MSG_%s, net_name = %s.\n"
551                                         ),
552                                         typestring,
553                                         namestring
554                                 ));
555                                 notif_error = TRUE;
556                         }
557                 }
558         }
559
560         // now check to see if any errors happened 
561         if(notif_error)
562         {
563                 notif.nent_enabled = FALSE; // disable the notification so it can't cause trouble
564                 notif_global_error = TRUE; // throw the red flag that an error happened on init
565         }
566 }
567
568
569 // =========================================
570 //  Cvar Handling With 'dumpnotifs' Command
571 // =========================================
572
573 void Dump_Notifications(float fh, float alsoprint)
574 {
575         #define NOTIF_WRITE(a) { \
576                 fputs(fh, a); \
577                 if(alsoprint) { print(a); } }
578         #define NOTIF_WRITE_ENTITY(name,default,description) { \
579                 notif_msg = \
580                         sprintf( \
581                                 "seta notification_%s \"%d\" \"%s\"\n", \
582                                 name, default, description \
583                         ); \
584                 NOTIF_WRITE(notif_msg) }
585         #define NOTIF_WRITE_HARDCODED(cvar,default,description) { \
586                 notif_msg = \
587                         sprintf( \
588                                 "seta notification_%s \"%s\" \"%s\"\n", \
589                                 cvar, default, description \
590                         ); \
591                 NOTIF_WRITE(notif_msg) }
592
593         string notif_msg;
594         float i;
595         entity e;
596
597         // Note: This warning only applies to the notifications.cfg file that is output...
598
599         // You ARE supposed to manually edit this function to add i.e. hard coded
600         // notification variables for mutators or game modes or such and then
601         // regenerate the notifications.cfg file from the new code.
602
603         NOTIF_WRITE("// ********************************************** //\n");
604         NOTIF_WRITE("// ** WARNING - DO NOT MANUALLY EDIT THIS FILE ** //\n");
605         NOTIF_WRITE("// **                                          ** //\n");
606         NOTIF_WRITE("// **  This file is automatically generated    ** //\n");
607         NOTIF_WRITE("// **  by code with the command 'dumpnotifs'.  ** //\n");
608         NOTIF_WRITE("// **                                          ** //\n");
609         NOTIF_WRITE("// **  If you add a new notification, please   ** //\n");
610         NOTIF_WRITE("// **  regenerate this file with that command  ** //\n");
611         NOTIF_WRITE("// **  making sure that the output matches     ** //\n");
612         NOTIF_WRITE("// **  with the lists and defaults in code.    ** //\n");
613         NOTIF_WRITE("// **                                          ** //\n");
614         NOTIF_WRITE("// ********************************************** //\n");
615
616         // These notifications will also append their string as a comment...
617         // This is not necessary, and does not matter if they vary between config versions,
618         // it is just a semi-helpful tool for those who want to manually change their user settings.
619
620         NOTIF_WRITE(sprintf("\n// MSG_INFO notifications (count = %d):\n", NOTIF_INFO_COUNT));
621         for(i = 1; i <= NOTIF_INFO_COUNT; ++i)
622         {
623                 e = Get_Notif_Ent(MSG_INFO, i);
624                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
625                 NOTIF_WRITE_ENTITY(e.nent_name, e.nent_default, "Notification control cvar: 0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)");
626         }
627
628         NOTIF_WRITE(sprintf("\n// MSG_CENTER notifications (count = %d):\n", NOTIF_CENTER_COUNT));
629         for(i = 1; i <= NOTIF_CENTER_COUNT; ++i)
630         {
631                 e = Get_Notif_Ent(MSG_CENTER, i);
632                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
633                 NOTIF_WRITE_ENTITY(e.nent_name, e.nent_default, "Notification control cvar: 0 = off, 1 = centerprint");
634         }
635
636         NOTIF_WRITE(sprintf("\n// MSG_WEAPON notifications (count = %d):\n", NOTIF_WEAPON_COUNT));
637         for(i = 1; i <= NOTIF_WEAPON_COUNT; ++i)
638         {
639                 e = Get_Notif_Ent(MSG_WEAPON, i);
640                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
641                 NOTIF_WRITE_ENTITY(e.nent_name, e.nent_default, "Notification control cvar: 0 = off, 1 = trigger subcalls");
642         }
643
644         NOTIF_WRITE(sprintf("\n// MSG_DEATH notifications (count = %d):\n", NOTIF_DEATH_COUNT));
645         for(i = 1; i <= NOTIF_DEATH_COUNT; ++i)
646         {
647                 e = Get_Notif_Ent(MSG_DEATH, i);
648                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
649                 NOTIF_WRITE_ENTITY(e.nent_name, e.nent_default, "Notification control cvar: 0 = off, 1 = trigger subcalls");
650         }
651
652         // edit these to match whichever cvars are used for specific notification options
653         NOTIF_WRITE("\n// HARD CODED notification variables:\n");
654         NOTIF_WRITE_HARDCODED("allow_chatboxprint",                             "1",    "Allow notifications to be printed to chat box by setting notification cvar to 2 (You can also set this cvar to 2 to force ALL notifications to be printed to the chatbox)");
655         NOTIF_WRITE_HARDCODED("show_location",                                          "0",    "Append location information to MSG_INFO death/kill messages");
656         NOTIF_WRITE_HARDCODED("show_location_string",                           "",     "Replacement string piped into sprintf, so you can do different messages like this: ' at the %s' or ' (near %s)'");
657         NOTIF_WRITE_HARDCODED("show_sprees",                                            "1",    "Print information about sprees in death/kill messages");
658         NOTIF_WRITE_HARDCODED("show_sprees_center",                             "1",    "Show spree information in MSG_CENTER messages... 0 = off, 1 = target (but only for first victim) and attacker");
659         NOTIF_WRITE_HARDCODED("show_sprees_center_specialonly",         "1",    "Don't show spree information in MSG_CENTER messages if it isn't an achievement");
660         NOTIF_WRITE_HARDCODED("show_sprees_info",                                       "3",    "Show spree information in MSG_INFO messages... 0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker");
661         NOTIF_WRITE_HARDCODED("show_sprees_info_newline",                       "0",    "Show attacker spree information for MSG_INFO messages on a separate line than the death notification itself");
662         NOTIF_WRITE_HARDCODED("show_sprees_info_specialonly",           "1",    "Don't show attacker spree information in MSG_INFO messages if it isn't an achievement");
663         NOTIF_WRITE_HARDCODED("item_centerprinttime",                           "1.5",  "How long to show item information centerprint messages (like 'You got the Electro' or such)");
664         NOTIF_WRITE_HARDCODED("errors_are_fatal",                                       "1",    "If a notification fails upon initialization, cause a Host_Error to stop the program");
665         NOTIF_WRITE_HARDCODED("ctf_pickup_team_verbose",                        "0",    "Show extra information if a team mate picks up a flag");
666         NOTIF_WRITE_HARDCODED("ctf_pickup_enemy_verbose",                       "0",    "Show extra information if an enemy picks up a flag");
667         NOTIF_WRITE_HARDCODED("ctf_capture_verbose",                            "0",    "Show extra information when someone captures a flag");
668         NOTIF_WRITE_HARDCODED("frag_verbose",                                           "1",    "Show extra information when you frag someone (or when you are fragged");
669         NOTIF_WRITE_HARDCODED("lifetime_runtime",                                       "0.5",  "Amount of time that notification entities last on the server during runtime (In seconds)");
670         NOTIF_WRITE_HARDCODED("lifetime_mapload",                                       "10",   "Amount of time that notification entities last immediately at mapload (in seconds) to help prevent notifications from being lost on early init (like gamestart countdown)");
671
672         NOTIF_WRITE(sprintf("\n// Notification counts (total = %d): MSG_INFO = %d, MSG_CENTER = %d, MSG_WEAPON = %d, MSG_DEATH = %d\n",
673                 (NOTIF_INFO_COUNT + NOTIF_CENTER_COUNT + NOTIF_WEAPON_COUNT + NOTIF_DEATH_COUNT), 
674                 NOTIF_INFO_COUNT, NOTIF_CENTER_COUNT, NOTIF_WEAPON_COUNT, NOTIF_DEATH_COUNT));
675         
676         return;
677         #undef NOTIF_WRITE_HARDCODED
678         #undef NOTIF_WRITE_ENTITY
679         #undef NOTIF_WRITE
680 }
681
682 #ifdef SVQC
683 void Notification_GetCvars()
684 {
685         GetCvars_handleFloat(get_cvars_s, get_cvars_f, FRAG_VERBOSE, "notification_frag_verbose");
686 }
687 #endif
688
689
690 // ===============================
691 //  Frontend Notification Pushing
692 // ===============================
693
694 string Local_Notification_sprintf(string input, string args, 
695         string s1, string s2, string s3, string s4,
696         float f1, float f2, float f3, float f4)
697 {
698         #ifdef NOTIFICATIONS_DEBUG
699         dprint(sprintf(
700                 "Local_Notification_sprintf('%s^7', '%s', %s, %s);\n",
701                 strreplace("\n", "\\n", input),
702                 args,
703                 strreplace("\n", "\\n", sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
704                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
705         ));
706         #endif
707         
708         string selected;
709         float sel_num;
710         for(sel_num = 0; sel_num < NOTIF_MAX_ARGS; ++sel_num) { arg_slot[sel_num] = ""; }
711
712         string tmp_s;
713
714         for(sel_num = 0;(args != "");)
715         {
716                 selected = car(args); args = cdr(args);
717                 NOTIF_HIT_MAX(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
718                 switch(strtolower(selected))
719                 {
720                         #define ARG_CASE(prog,selected,result) \
721                                 #ifdef CSQC \
722                                         #if (prog != ARG_SV) && (prog != ARG_DC) \
723                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
724                                         #endif \
725                                 #else \
726                                         #if (prog != ARG_CS) && (prog != ARG_DC) \
727                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
728                                         #endif \
729                                 #endif
730                         NOTIF_ARGUMENT_LIST
731                         #undef ARG_CASE
732                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
733                 }
734         }
735         return sprintf(input, arg_slot[0], arg_slot[1], arg_slot[2], arg_slot[3], arg_slot[4], arg_slot[5], arg_slot[6]);
736 }
737
738 #ifdef CSQC
739 void Local_Notification_HUD_Notify_Push(string icon, string hudargs, string s1, string s2, string s3, string s4)
740 {
741         string selected;
742         float sel_num;
743         arg_slot[0] = ""; arg_slot[1] = "";
744
745         for(sel_num = 0;(hudargs != "");)
746         {
747                 selected = car(hudargs); hudargs = cdr(hudargs);
748                 NOTIF_HIT_MAX(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
749                 switch(strtolower(selected))
750                 {
751                         #define ARG_CASE(prog,selected,result) \
752                                 #if (prog == ARG_CS_SV_HA) \
753                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
754                                 #endif
755                         NOTIF_ARGUMENT_LIST
756                         #undef ARG_CASE
757                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
758                 }
759         }
760         #ifdef NOTIFICATIONS_DEBUG
761         dprint(sprintf(
762                 "Local_Notification_HUD_Notify_Push('%s^7', '%s', %s, %s);\n",
763                 icon,
764                 hudargs,
765                 strreplace("\n", "\\n", sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
766                 strreplace("\n", "\\n", sprintf("'%s^7', '%s^7'", stof(arg_slot[0]), stof(arg_slot[1])))
767         ));
768         #endif
769         HUD_Notify_Push(icon, arg_slot[0], arg_slot[1]);
770 }
771
772 void Local_Notification_centerprint_generic(string input, string durcnt, float cpid, float f1, float f2)
773 {
774         string selected;
775         float sel_num;
776         arg_slot[0] = ""; arg_slot[1] = "";
777
778         for(sel_num = 0;(durcnt != "");)
779         {
780                 selected = car(durcnt); durcnt = cdr(durcnt);
781                 NOTIF_HIT_MAX(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic")
782                 switch(strtolower(selected))
783                 {
784                         #define ARG_CASE(prog,selected,result) \
785                                 #if (prog == ARG_CS_SV_DC) || (prog == ARG_DC) \
786                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
787                                 #endif
788                         NOTIF_ARGUMENT_LIST
789                         #undef ARG_CASE
790                         default:
791                         {
792                                 if(ftos(stof(selected)) != "") { arg_slot[sel_num] = selected; ++sel_num; }
793                                 else { NOTIF_HIT_UNKNOWN(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic") }
794                                 break;
795                         }
796                 }
797         }
798         #ifdef NOTIFICATIONS_DEBUG
799         dprint(sprintf(
800                 "Local_Notification_centerprint_generic('%s^7', '%s', %d, %d, %d, %d);\n",
801                 strreplace("\n", "\\n", input),
802                 durcnt,
803                 f1, f2,
804                 stof(arg_slot[0]), stof(arg_slot[1])
805         ));
806         #endif
807         centerprint_generic(cpid, input, stof(arg_slot[0]), stof(arg_slot[1]));
808 }
809 #endif
810
811 void Local_Notification(float net_type, float net_name, ...count)
812 {
813         // check supplied type and name for errors
814         string checkargs = Notification_CheckArgs_TypeName(net_type, net_name);
815         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Local_Notification: %s\n", checkargs)); return; }
816
817         entity notif = Get_Notif_Ent(net_type, net_name);
818         if not(notif) { backtrace("Local_Notification: Could not find notification entity!\n"); return; }
819
820         #ifdef NOTIFICATIONS_DEBUG
821         if not(notif.nent_enabled)
822         {
823                 dprint(sprintf(
824                         "Local_Notification(%s, %s): Entity was disabled...\n",
825                         Get_Notif_TypeName(net_type),
826                         notif.nent_name
827                 ));
828                 return;
829         }
830         #endif
831         
832         if((notif.nent_stringcount + notif.nent_floatcount) > count)
833         {
834                 backtrace(sprintf(
835                         strcat(
836                                 "Not enough arguments for Local_Notification(%s, %s, ...)! ",
837                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
838                                 "Check the definition and function call for accuracy...?\n"
839                         ),
840                         Get_Notif_TypeName(net_type), notif.nent_name,
841                         notif.nent_stringcount, notif.nent_floatcount, count
842                 ));
843                 return;
844         }
845         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
846         {
847                 backtrace(sprintf(
848                         strcat(
849                                 "Too many arguments for Local_Notification(%s, %s, ...)! ",
850                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
851                                 "Check the definition and function call for accuracy...?\n"
852                         ),
853                         Get_Notif_TypeName(net_type), notif.nent_name,
854                         notif.nent_stringcount, notif.nent_floatcount, count
855                 ));
856                 return;
857         }
858
859         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
860         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
861         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
862         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
863         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
864         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
865         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
866         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
867
868         #ifdef NOTIFICATIONS_DEBUG
869         dprint(sprintf(
870                 "Local_Notification(%s, %s, %s, %s);\n",
871                 Get_Notif_TypeName(net_type),
872                 notif.nent_name,
873                 strreplace("\n", "\\n", sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
874                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
875         ));
876         #endif
877         
878         switch(net_type)
879         {
880                 case MSG_INFO:
881                 {
882                         print(
883                                 Local_Notification_sprintf(
884                                         notif.nent_string,
885                                         notif.nent_args, 
886                                         s1, s2, s3, s4,
887                                         f1, f2, f3, f4)
888                         );
889                         #ifdef CSQC 
890                         if(notif.nent_icon != "")
891                         {
892                                 Local_Notification_HUD_Notify_Push(
893                                         notif.nent_icon,
894                                         notif.nent_hudargs,
895                                         s1, s2, s3, s4);
896                         } 
897                         #endif 
898                         break;
899                 }
900                 
901                 #ifdef CSQC
902                 case MSG_CENTER:
903                 {
904                         Local_Notification_centerprint_generic(
905                                 Local_Notification_sprintf(
906                                         notif.nent_string,
907                                         notif.nent_args, 
908                                         s1, s2, s3, s4,
909                                         f1, f2, f3, f4),
910                                 notif.nent_durcnt,
911                                 notif.nent_cpid,
912                                 f1, f2);
913                         break;
914                 }
915                 #endif
916                 
917                 case MSG_WEAPON:
918                 case MSG_DEATH:
919                 {
920                         if(notif.nent_msginfo)
921                         if(notif.nent_msginfo.nent_enabled)
922                         {
923                                 Local_Notification_WOVA(
924                                         MSG_INFO,
925                                         notif.nent_msginfo.nent_id, 
926                                         notif.nent_msginfo.nent_stringcount, 
927                                         notif.nent_msginfo.nent_floatcount, 
928                                         s1, s2, s3, s4,
929                                         f1, f2, f3, f4);
930                         }
931                         #ifdef CSQC
932                         if(notif.nent_msgcenter)
933                         if(notif.nent_msgcenter.nent_enabled)
934                         {
935                                 Local_Notification_WOVA(
936                                         MSG_CENTER,
937                                         notif.nent_msgcenter.nent_id, 
938                                         notif.nent_msgcenter.nent_stringcount, 
939                                         notif.nent_msgcenter.nent_floatcount, 
940                                         s1, s2, s3, s4,
941                                         f1, f2, f3, f4); 
942                         }
943                         #endif
944                         break;
945                 }
946         }
947 }
948
949 // WOVA = Without Variable Arguments 
950 void Local_Notification_WOVA(float net_type, float net_name,
951         float stringcount, float floatcount,
952         string s1, string s2, string s3, string s4,
953         float f1, float f2, float f3, float f4)
954 {
955         #define VARITEM(stringc,floatc,args) \
956                 if((stringcount == stringc) && (floatcount == floatc)) \
957                         { Local_Notification(net_type, net_name, args); return; }
958         EIGHT_VARS_TO_VARARGS_VARLIST
959         #undef VARITEM
960         Local_Notification(net_type, net_name); // some notifications don't have any arguments at all
961 }
962
963
964 // =========================
965 //  Notification Networking
966 // =========================
967
968 #ifdef CSQC
969 void Read_Notification(float is_new)
970 {
971         float net_type = ReadByte();
972         float net_name = ReadShort();
973
974         entity notif;
975
976         if(net_type == MSG_CENTER_KILL)
977         {
978                 if(is_new)
979                 {
980                         if(net_name == 0) { reset_centerprint_messages(); }
981                         else
982                         {
983                                 notif = Get_Notif_Ent(MSG_CENTER, net_name);
984                                 if not(notif) { backtrace("Read_Notification: Could not find notification entity!\n"); return; }
985                                 centerprint_generic(notif.nent_cpid, "", 0, 0);
986                         }
987                 }
988         }
989         else
990         {
991                 notif = Get_Notif_Ent(net_type, net_name);
992                 if not(notif) { backtrace("Read_Notification: Could not find notification entity!\n"); return; }
993
994                 #ifdef NOTIFICATIONS_DEBUG
995                 dprint(sprintf(
996                         "Read_Notification(%d) at %f: net_type = %s, net_name = %s\n",
997                         is_new,
998                         time,
999                         Get_Notif_TypeName(net_type),
1000                         notif.nent_name
1001                 ));
1002                 #endif
1003
1004                 string s1 = ((0 < notif.nent_stringcount) ? ReadString() : "");
1005                 string s2 = ((1 < notif.nent_stringcount) ? ReadString() : "");
1006                 string s3 = ((2 < notif.nent_stringcount) ? ReadString() : "");
1007                 string s4 = ((3 < notif.nent_stringcount) ? ReadString() : "");
1008                 float f1 = ((0 < notif.nent_floatcount) ? ReadLong() : 0);
1009                 float f2 = ((1 < notif.nent_floatcount) ? ReadLong() : 0);
1010                 float f3 = ((2 < notif.nent_floatcount) ? ReadLong() : 0);
1011                 float f4 = ((3 < notif.nent_floatcount) ? ReadLong() : 0);
1012         
1013                 if(is_new)
1014                 {
1015                         Local_Notification_WOVA(
1016                                 net_type, net_name,
1017                                 notif.nent_stringcount,
1018                                 notif.nent_floatcount,
1019                                 s1, s2, s3, s4,
1020                                 f1, f2, f3, f4);
1021                 }
1022         }
1023 }
1024 #endif
1025
1026 #ifdef SVQC
1027 void Net_Notification_Remove()
1028 {
1029         #ifdef NOTIFICATIONS_DEBUG
1030         if not(self) { dprint(sprintf("Net_Notification_Remove() at %f: Missing self!?\n", time)); return; }
1031         if(self.nent_net_name == -1)
1032         {
1033                 dprint(sprintf(
1034                         "Net_Notification_Remove() at %f: Killed '%s' notification\n",
1035                         time,
1036                         Get_Notif_TypeName(self.nent_net_type)
1037                 ));
1038         }
1039         else
1040         #endif
1041         {
1042                 string checkargs = Notification_CheckArgs_TypeName(self.nent_net_type, self.nent_net_name);
1043                 if(checkargs != "") { dprint(sprintf("Incorrect usage of Net_Notification_Remove() at %f: %s\n", time, checkargs)); return; }
1044
1045                 #ifdef NOTIFICATIONS_DEBUG
1046                 entity realent = Get_Notif_Ent(self.nent_net_type, self.nent_net_name);
1047                 dprint(sprintf(
1048                         "Net_Notification_Remove() at %f: Removed '%s - %s' notification\n",
1049                         time,
1050                         Get_Notif_TypeName(self.nent_net_type), 
1051                         realent.nent_name
1052                 ));
1053                 #endif
1054         }
1055         
1056         float i;
1057         for(i = 0; i < 4; ++i) { if(self.nent_strings[i]) { strunzone(self.nent_strings[i]); } }
1058         remove(self);
1059 }
1060
1061 float Net_Write_Notification(entity client, float sf)
1062 {
1063         float i, send = FALSE;
1064         
1065         switch(self.nent_broadcast)
1066         {
1067                 case NOTIF_ONE: // send to one client and their spectator
1068                 {
1069                         if(
1070                                 (client == self.nent_client)
1071                                 ||
1072                                 (
1073                                         (client.classname == STR_SPECTATOR)
1074                                         &&
1075                                         (client.enemy == self.nent_client)
1076                                 )
1077                         ) { send = TRUE; }
1078                         break;
1079                 }
1080                 case NOTIF_ONE_ONLY: // send ONLY to one client
1081                 {
1082                         if(client == self.nent_client) { send = TRUE; }
1083                         break;
1084                 }
1085                 case NOTIF_TEAM: // send only to X team and their spectators
1086                 {
1087                         if(
1088                                 (client.team == self.nent_client.team)
1089                                 ||
1090                                 (
1091                                         (client.classname == STR_SPECTATOR)
1092                                         &&
1093                                         (client.enemy.team == self.nent_client.team)
1094                                 )
1095                         ) { send = TRUE; }
1096                         break;
1097                 }
1098                 case NOTIF_TEAM_EXCEPT: // send only to X team and their spectators, except for Y person and their spectators
1099                 {
1100                         if(
1101                                 (client != self.nent_client)
1102                                 &&
1103                                 (
1104                                         (client.team == self.nent_client.team)
1105                                         ||
1106                                         (
1107                                                 (client.classname == STR_SPECTATOR)
1108                                                 &&
1109                                                 (
1110                                                         (client.enemy != self.nent_client)
1111                                                         &&
1112                                                         (client.enemy.team == self.nent_client.team)
1113                                                 )
1114                                         )
1115                                 )
1116                         ) { send = TRUE; }
1117                         break;
1118                 }
1119                 case NOTIF_ANY: // send to everyone
1120                 {
1121                         send = TRUE;
1122                         break;
1123                 }
1124                 case NOTIF_ANY_EXCEPT: // send to everyone except X person and their spectators
1125                 {
1126                         if(
1127                                 (client != self.nent_client)
1128                                 &&
1129                                 !(
1130                                         (client.classname == STR_SPECTATOR)
1131                                         &&
1132                                         (client.enemy == self.nent_client)
1133                                 )
1134                         ) { send = TRUE; }
1135                         break;
1136                 }
1137                 default: { send = FALSE; break; }
1138         }
1139
1140         if(send)
1141         {               
1142                 WriteByte(MSG_ENTITY, ENT_CLIENT_NOTIFICATION);
1143                 WriteByte(MSG_ENTITY, self.nent_net_type);
1144                 WriteShort(MSG_ENTITY, self.nent_net_name);
1145                 for(i = 0; i < self.nent_stringcount; ++i) { WriteString(MSG_ENTITY, self.nent_strings[i]); } 
1146                 for(i = 0; i < self.nent_floatcount; ++i) { WriteLong(MSG_ENTITY, self.nent_floats[i]); }
1147         }
1148
1149         return send; 
1150 }
1151
1152 void Kill_Notification(float broadcast, entity client, float net_type, float net_name)
1153 {
1154         string checkargs = Notification_CheckArgs(broadcast, client, 1, 1);
1155         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Kill_Notification: %s\n", checkargs)); return; }
1156
1157         #ifdef NOTIFICATIONS_DEBUG
1158         dprint(sprintf(
1159                 "Kill_Notification(%d, '%s', %d, %d);\n",
1160                 broadcast,
1161                 client.netname,
1162                 net_type,
1163                 net_name
1164         ));
1165         #endif
1166
1167         entity notif, net_notif;
1168
1169         // if no name is provided, just kill ALL the centerprint notifications
1170         if(net_type == MSG_CENTER)
1171         {
1172                 net_notif = spawn();
1173                 net_notif.classname = "net_kill_notification";
1174                 net_notif.nent_broadcast = broadcast;
1175                 net_notif.nent_client = client;
1176                 net_notif.nent_net_type = MSG_CENTER_KILL;
1177                 net_notif.nent_net_name = net_name;
1178                 Net_LinkEntity(net_notif, FALSE, autocvar_notification_lifetime_runtime, Net_Write_Notification);
1179         }
1180
1181         for(notif = world; (notif = find(notif, classname, "net_notification"));)
1182         {
1183                 // now kill the old send notification entity
1184                 if(net_type)
1185                 {
1186                         if(notif.nent_net_type == net_type)
1187                         {
1188                                 if(net_name)
1189                                 {
1190                                         if(notif.nent_net_name == net_name) { notif.nent_net_name = -1; notif.think(); }
1191                                         else { continue; } // we ARE looking for a certain net_name, don't kill everything else too
1192                                 }
1193                                 else { notif.nent_net_name = -1; notif.think(); }
1194                         }
1195                         else { continue; } // we ARE looking for a certain net_type, don't kill everything else too
1196                 }
1197                 else { notif.nent_net_name = -1; notif.think(); }
1198         }
1199 }
1200
1201 void Send_Notification(float broadcast, entity client,
1202         float net_type, float net_name, ...count)
1203 {
1204         // check supplied broadcast, target, type, and name for errors
1205         string checkargs = Notification_CheckArgs(broadcast, client, net_type, net_name);
1206         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Send_Notification: %s\n", checkargs)); return; }
1207
1208         // retreive counts for the arguments of this notification
1209         entity notif = Get_Notif_Ent(net_type, net_name);
1210         if not(notif) { backtrace("Send_Notification: Could not find notification entity!\n"); return; }
1211
1212         if((notif.nent_stringcount + notif.nent_floatcount) > count)
1213         {
1214                 backtrace(sprintf(
1215                         strcat(
1216                                 "Not enough arguments for Send_Notification(%d, %s, %s, ...)! ",
1217                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
1218                                 "Check the definition and function call for accuracy...?\n"
1219                         ),
1220                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name,
1221                         notif.nent_stringcount, notif.nent_floatcount, count
1222                 ));
1223                 return;
1224         }
1225         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
1226         {
1227                 backtrace(sprintf(
1228                         strcat(
1229                                 "Too many arguments for Send_Notification(%d, %s, %s, ...)! ",
1230                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
1231                                 "Check the definition and function call for accuracy...?\n"
1232                         ),
1233                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name,
1234                         notif.nent_stringcount, notif.nent_floatcount, count
1235                 ));
1236                 return;
1237         }
1238
1239         #ifdef NOTIFICATIONS_DEBUG
1240         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
1241         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
1242         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
1243         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
1244         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
1245         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
1246         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
1247         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
1248         dprint(sprintf(
1249                 "Send_Notification(%d, %s, %s, %s, %s);\n",
1250                 broadcast,
1251                 Get_Notif_TypeName(net_type),
1252                 notif.nent_name,
1253                 strreplace("\n", "\\n", sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1254                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1255         ));
1256         #endif
1257
1258         entity net_notif = spawn();
1259         net_notif.classname = "net_notification";
1260         net_notif.nent_broadcast = broadcast;
1261         net_notif.nent_client = client;
1262         net_notif.nent_net_type = net_type;
1263         net_notif.nent_net_name = net_name;
1264         net_notif.nent_stringcount = notif.nent_stringcount;
1265         net_notif.nent_floatcount = notif.nent_floatcount;
1266         
1267         float i;
1268         for(i = 0; i < net_notif.nent_stringcount; ++i) { net_notif.nent_strings[i] = strzone(...(i, string)); }
1269         for(i = 0; i < net_notif.nent_floatcount; ++i) { net_notif.nent_floats[i] = ...((net_notif.nent_stringcount + i), float); }
1270
1271         net_notif.think = Net_Notification_Remove;
1272         net_notif.nextthink =
1273                 ((time > autocvar_notification_lifetime_mapload)
1274                 ?
1275                         (time + autocvar_notification_lifetime_runtime)
1276                         :
1277                         autocvar_notification_lifetime_mapload
1278                 ); 
1279
1280         Net_LinkEntity(net_notif, FALSE, 0, Net_Write_Notification);
1281
1282         if(server_is_dedicated && (broadcast == NOTIF_ANY || broadcast == NOTIF_ANY_EXCEPT) && (net_type != MSG_CENTER))
1283         {
1284                 Local_Notification_WOVA(
1285                         net_type, net_name,
1286                         notif.nent_stringcount,
1287                         notif.nent_floatcount,
1288                         IFSTR(0), IFSTR(1), IFSTR(2), IFSTR(3),
1289                         IFFL(0), IFFL(1), IFFL(2), IFFL(3));
1290         }
1291 }
1292
1293 // WOVA = Without Variable Arguments 
1294 void Send_Notification_WOVA(float broadcast, entity client,
1295         float net_type, float net_name,
1296         string s1, string s2, string s3, string s4,
1297         float f1, float f2, float f3, float f4)
1298 {
1299         entity notif = Get_Notif_Ent(net_type, net_name);
1300         
1301         #ifdef NOTIFICATIONS_DEBUG
1302         dprint(sprintf(
1303                 "Send_Notification_WOVA(%d, %s, %s, %s, %s - %d %d);\n",
1304                 broadcast,
1305                 Get_Notif_TypeName(net_type),
1306                 notif.nent_name,
1307                 sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4),
1308                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4),
1309                 notif.nent_stringcount, notif.nent_floatcount
1310         ));
1311         #endif
1312         
1313         #define VARITEM(stringc,floatc,args) \
1314                 if((notif.nent_stringcount == stringc) && (notif.nent_floatcount == floatc)) \
1315                         { Send_Notification(broadcast, client, net_type, net_name, args); return; }
1316         EIGHT_VARS_TO_VARARGS_VARLIST
1317         #undef VARITEM
1318         Send_Notification(broadcast, client, net_type, net_name); // some notifications don't have any arguments at all
1319 }
1320
1321
1322 // =============================
1323 //  LEGACY NOTIFICATION SYSTEMS
1324 // =============================
1325
1326 void Send_CSQC_Centerprint_Generic(entity e, float id, string s, float duration, float countdown_num)
1327 {
1328         if ((clienttype(e) == CLIENTTYPE_REAL) && (e.flags & FL_CLIENT))
1329         {
1330                 msg_entity = e;
1331                 WRITESPECTATABLE_MSG_ONE({
1332                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
1333                         WriteByte(MSG_ONE, TE_CSQC_CENTERPRINT_GENERIC);
1334                         WriteByte(MSG_ONE, id);
1335                         WriteString(MSG_ONE, s);
1336                         if (id != 0 && s != "")
1337                         {
1338                                 WriteByte(MSG_ONE, duration);
1339                                 WriteByte(MSG_ONE, countdown_num);
1340                         }
1341                 });
1342         }
1343 }
1344 #endif // ifdef SVQC