]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
I'm a goddamn genius, IT WORKS!
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / notifications.qc
1 // ================================================
2 //  Unified notification system, written by Samual
3 //  Last updated: June, 2013
4 // ================================================
5
6 string Get_Notif_TypeName(float net_type)
7 {
8         switch(net_type)
9         {
10                 case MSG_ANNCE: return "MSG_ANNCE";
11                 case MSG_INFO: return "MSG_INFO";
12                 case MSG_CENTER: return "MSG_CENTER";
13                 case MSG_CENTER_CPID: return "MSG_CENTER_CPID";
14                 case MSG_MULTI: return "MSG_MULTI";
15                 case MSG_CHOICE: return "MSG_CHOICE";
16         }
17         backtrace(sprintf("Get_Notif_TypeName(%d): Improper net type!\n", net_type));
18         return "";
19 }
20
21 entity Get_Notif_Ent(float net_type, float net_name)
22 {
23         switch(net_type)
24         {
25                 case MSG_ANNCE: return msg_annce_notifs[net_name - 1];
26                 case MSG_INFO: return msg_info_notifs[net_name - 1];
27                 case MSG_CENTER: return msg_center_notifs[net_name - 1];
28                 case MSG_MULTI: return msg_multi_notifs[net_name - 1];
29                 case MSG_CHOICE: return msg_choice_notifs[net_name - 1];
30         }
31         backtrace(sprintf("Get_Notif_Ent(%d, %d): Improper net type!\n", net_type, net_name));
32         return world;
33 }
34
35 string Notification_CheckArgs_TypeName(float net_type, float net_name)
36 {
37         // check supplied type and name for errors
38         string checkargs = "";
39         #define CHECKARG_TYPENAME(type) case MSG_##type##: \
40                 { if(!net_name || (net_name > NOTIF_##type##_COUNT)) \
41                 { checkargs = sprintf("Improper name: %d!", net_name); } break; }
42         switch(net_type)
43         {
44                 CHECKARG_TYPENAME(ANNCE)
45                 CHECKARG_TYPENAME(INFO)
46                 CHECKARG_TYPENAME(CENTER)
47                 CHECKARG_TYPENAME(MULTI)
48                 CHECKARG_TYPENAME(CHOICE)
49                 default: { checkargs = sprintf("Improper type: %d!", checkargs, net_type); break; }
50         }
51         #undef CHECKARG_TYPENAME
52         return checkargs;
53 }
54
55 #ifdef SVQC
56 string Notification_CheckArgs(
57         float broadcast, entity client,
58         float net_type, float net_name)
59 {
60         // check supplied broadcast, target, type, and name for errors
61         string checkargs = Notification_CheckArgs_TypeName(net_type, net_name);
62         if(checkargs != "") { checkargs = strcat(checkargs, " "); }
63         switch(broadcast)
64         {
65                 case NOTIF_ONE:
66                 case NOTIF_ONE_ONLY:
67                 {
68                         if(IS_NOT_A_CLIENT(client))
69                                 { checkargs = sprintf("%sNo client provided!", checkargs); }
70                         break;
71                 }
72                 
73                 case NOTIF_ALL_EXCEPT:
74                 {
75                         if(IS_NOT_A_CLIENT(client))
76                                 { checkargs = sprintf("%sException can't be a non-client!", checkargs); }
77                         break;
78                 }
79                 
80                 case NOTIF_ALL:
81                 {
82                         if(client)
83                                 { checkargs = sprintf("%sEntity provided when world was required!", checkargs); }
84                         break;
85                 }
86                 
87                 case NOTIF_TEAM:
88                 case NOTIF_TEAM_EXCEPT:
89                 {
90                         if not(teamplay) { checkargs = sprintf("%sTeamplay not active!", checkargs); }
91                         else if(IS_NOT_A_CLIENT(client))
92                         {
93                                 if(broadcast == NOTIF_TEAM) { checkargs = sprintf("%sNo client provided!", checkargs); }
94                                 else { checkargs = sprintf("%sException can't be a non-client!", checkargs); }
95                         }
96                         break;
97                 }
98                 
99                 default: { checkargs = sprintf("%sImproper broadcast: %d!", checkargs, broadcast); break; }
100         }
101         return checkargs;
102 }
103
104 float Notification_ShouldSend(float broadcast, entity to_client, entity other_client)
105 {
106         switch(broadcast)
107         {
108                 case NOTIF_ONE: // send to one client and their spectator
109                 {
110                         if(
111                                 (to_client == other_client)
112                                 ||
113                                 (
114                                         IS_SPEC(to_client)
115                                         &&
116                                         (to_client.enemy == other_client)
117                                 )
118                         ) { return TRUE; }
119                         break;
120                 }
121                 case NOTIF_ONE_ONLY: // send ONLY to one client
122                 {
123                         if(to_client == other_client) { return TRUE; }
124                         break;
125                 }
126                 case NOTIF_TEAM: // send only to X team and their spectators
127                 {
128                         if(
129                                 (to_client.team == other_client.team)
130                                 ||
131                                 (
132                                         IS_SPEC(to_client)
133                                         &&
134                                         (to_client.enemy.team == other_client.team)
135                                 )
136                         ) { return TRUE; }
137                         break;
138                 }
139                 case NOTIF_TEAM_EXCEPT: // send only to X team and their spectators, except for Y person and their spectators
140                 {
141                         if(
142                                 (to_client != other_client)
143                                 &&
144                                 (
145                                         (to_client.team == other_client.team)
146                                         ||
147                                         (
148                                                 IS_SPEC(to_client)
149                                                 &&
150                                                 (
151                                                         (to_client.enemy != other_client)
152                                                         &&
153                                                         (to_client.enemy.team == other_client.team)
154                                                 )
155                                         )
156                                 )
157                         ) { return TRUE; }
158                         break;
159                 }
160                 case NOTIF_ALL: // send to everyone
161                 {
162                         return TRUE;
163                 }
164                 case NOTIF_ALL_EXCEPT: // send to everyone except X person and their spectators
165                 {
166                         if(
167                                 (to_client != other_client)
168                                 &&
169                                 !(
170                                         IS_SPEC(to_client)
171                                         &&
172                                         (to_client.enemy == other_client)
173                                 )
174                         ) { return TRUE; }
175                         break;
176                 }
177         }
178         return FALSE;
179 }
180
181 #endif
182
183 // ===============================
184 //  Initialization Core Functions
185 // ===============================
186
187 // used by restartnotifs command to initialize notifications
188 void Destroy_Notification_Entity(entity notif)
189 {
190         if(notif.nent_name != "") { strunzone(notif.nent_name); }
191         if(notif.nent_snd != "") { strunzone(notif.nent_snd); }
192         if(notif.nent_args != "") { strunzone(notif.nent_args); }
193         if(notif.nent_hudargs != "") { strunzone(notif.nent_hudargs); }
194         if(notif.nent_icon != "") { strunzone(notif.nent_icon); }
195         if(notif.nent_durcnt != "") { strunzone(notif.nent_durcnt); }
196         if(notif.nent_string != "") { strunzone(notif.nent_string); }
197         remove(notif);
198 }
199
200 void Destroy_All_Notifications(void)
201 {
202         entity notif;
203         float i;
204         
205         #define DESTROY_LOOP(type,count) \
206                 for(i = 1; i <= count; ++i) \
207                 { \
208                         notif = Get_Notif_Ent(type, i); \
209                         if not(notif) { backtrace("Destroy_All_Notifications(): Missing notification entity!\n"); return; } \
210                         Destroy_Notification_Entity(notif); \
211                 }
212
213         // kill all networked notifications and centerprints
214         #ifdef SVQC
215         Kill_Notification(NOTIF_ALL, world, 0, 0);
216         #else
217         reset_centerprint_messages();
218         #endif
219
220         // kill all real notification entities
221         DESTROY_LOOP(MSG_ANNCE, NOTIF_ANNCE_COUNT)
222         DESTROY_LOOP(MSG_INFO, NOTIF_INFO_COUNT)
223         DESTROY_LOOP(MSG_CENTER, NOTIF_CENTER_COUNT)
224         DESTROY_LOOP(MSG_MULTI, NOTIF_MULTI_COUNT)
225         DESTROY_LOOP(MSG_CHOICE, NOTIF_CHOICE_COUNT)
226         #undef DESTROY_LOOP
227 }
228
229 string Process_Notif_Line(
230         float typeid,
231         float chat,
232         string input,
233         string notiftype,
234         string notifname,
235         string stringtype)
236 {
237         #ifdef CSQC
238         if(typeid == MSG_INFO)
239         {
240                 if((chat && autocvar_notification_allow_chatboxprint)
241                         || (autocvar_notification_allow_chatboxprint == 2))
242                 {
243                         // pass 1: add ETX char at beginning of line
244                         input = strcat("\{3}", input);
245
246                         // pass 2: add ETX char at end of each new line (so that
247                         // messages with multiple lines are put through chatbox too)
248                         input = strreplace("\n", "\n\{3}", input);
249
250                         // pass 3: strip trailing ETX char
251                         if(substring(input, (strlen(input) - 1), 1) == "\{3}")
252                                 { input = substring(input, 0, (strlen(input) - 1)); }
253                 }
254         }
255         #endif
256
257         // done to both MSG_INFO and MSG_CENTER
258         if(substring(input, (strlen(input) - 1), 1) == "\n")
259         {
260                 print(sprintf(
261                         strcat(
262                                 "^1TRAILING NEW LINE AT END OF NOTIFICATION: ",
263                                 "^7net_type = %s, net_name = %s, string = %s.\n"
264                         ),
265                         notiftype,
266                         notifname,
267                         stringtype
268                 ));
269                 notif_error = TRUE;
270                 input = substring(input, 1, (strlen(input) - 1));
271         }
272
273         return input;
274 }
275
276 string Process_Notif_Args(
277         float arg_type,
278         string args,
279         string notiftype,
280         string notifname)
281 {
282         string selected, remaining = args;
283         float sel_num = 0;
284
285         for(;(remaining != "");)
286         {
287                 selected = car(remaining); remaining = cdr(remaining);
288
289                 switch(arg_type)
290                 {
291                         case 1: // normal args
292                         {
293                                 if(sel_num == NOTIF_MAX_ARGS)
294                                 {
295                                         print(sprintf(
296                                                 strcat(
297                                                         "^1NOTIFICATION HAS TOO MANY ARGUMENTS: ",
298                                                         "^7net_type = %s, net_name = %s, max args = %d.\n"
299                                                 ),
300                                                 notiftype,
301                                                 notifname,
302                                                 NOTIF_MAX_ARGS
303                                         ));
304                                         notif_error = TRUE;
305                                         break;
306                                 }
307
308                                 switch(strtolower(selected))
309                                 {
310                                         #define ARG_CASE(prog,selected,result) \
311                                                 #if (prog != ARG_DC) \
312                                                         case selected: { ++sel_num; break; } \
313                                                 #endif
314                                         NOTIF_ARGUMENT_LIST
315                                         #undef ARG_CASE
316                                         default:
317                                         {
318                                                 print(sprintf(
319                                                         strcat(
320                                                                 "^1NOTIFICATION WITH UNKNOWN TOKEN IN ARGUMENT STRING: ",
321                                                                 "^7net_type = %s, net_name = %s, args arg = '%s'.\n"
322                                                         ),
323                                                         notiftype,
324                                                         notifname,
325                                                         selected
326                                                 ));
327                                                 notif_error = TRUE;
328                                                 break;
329                                         }
330                                 }
331                                 break;
332                         }
333                         case 2: // hudargs
334                         {
335                                 if(sel_num == NOTIF_MAX_HUDARGS)
336                                 {
337                                         print(sprintf(
338                                                 strcat(
339                                                         "^1NOTIFICATION HAS TOO MANY ARGUMENTS: ",
340                                                         "^7net_type = %s, net_name = %s, max hudargs = %d.\n"
341                                                 ),
342                                                 notiftype,
343                                                 notifname,
344                                                 NOTIF_MAX_HUDARGS
345                                         ));
346                                         notif_error = TRUE;
347                                         break;
348                                 }
349
350                                 switch(strtolower(selected))
351                                 {
352                                         #define ARG_CASE(prog,selected,result) \
353                                                 #if (prog == ARG_CS_SV_HA) \
354                                                         case selected: { ++sel_num; break; } \
355                                                 #endif
356                                         NOTIF_ARGUMENT_LIST
357                                         #undef ARG_CASE
358                                         default:
359                                         {
360                                                 print(sprintf(
361                                                         strcat(
362                                                                 "^1NOTIFICATION WITH UNKNOWN TOKEN IN ARGUMENT STRING: ",
363                                                                 "^7net_type = %s, net_name = %s, hudargs arg = '%s'.\n"
364                                                         ),
365                                                         notiftype,
366                                                         notifname,
367                                                         selected
368                                                 ));
369                                                 notif_error = TRUE;
370                                                 break;
371                                         }
372                                 }
373                                 break;
374                         }
375                         case 3: // durcnt 
376                         {
377                                 if(sel_num == NOTIF_MAX_DURCNT)
378                                 {
379                                         print(sprintf(
380                                                 strcat(
381                                                         "^1NOTIFICATION HAS TOO MANY ARGUMENTS: ",
382                                                         "^7net_type = %s, net_name = %s, max durcnt = %d.\n"
383                                                 ),
384                                                 notiftype,
385                                                 notifname,
386                                                 NOTIF_MAX_DURCNT
387                                         ));
388                                         notif_error = TRUE;
389                                         break;
390                                 }
391
392                                 switch(strtolower(selected))
393                                 {
394                                         #define ARG_CASE(prog,selected,result) \
395                                                 #if (prog == ARG_CS_SV_DC) || (prog == ARG_DC) \
396                                                         case selected: { ++sel_num; break; } \
397                                                 #endif
398                                         NOTIF_ARGUMENT_LIST
399                                         #undef ARG_CASE
400                                         default:
401                                         {
402                                                 if(ftos(stof(selected)) != "") { ++sel_num; }
403                                                 else
404                                                 {
405                                                         print(sprintf(
406                                                                 strcat(
407                                                                         "^1NOTIFICATION WITH UNKNOWN TOKEN IN ARGUMENT STRING: ",
408                                                                         "^7net_type = %s, net_name = %s, durcnt arg = '%s'.\n"
409                                                                 ),
410                                                                 notiftype,
411                                                                 notifname,
412                                                                 selected
413                                                         ));
414                                                         notif_error = TRUE;
415                                                 }
416                                                 break;
417                                         }
418                                 }
419                                 break;
420                         }
421                 }
422         }
423         return args;
424 }
425
426 void Create_Notification_Entity(
427         float var_default,
428         float var_cvar,
429         float typeid,
430         float nameid,
431         string namestring,
432         float anncename,
433         float infoname,
434         float centername,
435         float channel,
436         string snd,
437         float vol,
438         float position,
439         float strnum,
440         float flnum,
441         string args,
442         string hudargs,
443         string icon,
444         float cpid,
445         string durcnt,
446         string normal,
447         string gentle,
448         float challow,
449         float chtype,
450         float optiona,
451         float optionb)
452 {
453         // =====================
454         //  Global Entity Setup
455         // =====================
456         entity notif = spawn();
457         string typestring = "";
458         switch(typeid)
459         {
460                 case MSG_ANNCE:
461                 {
462                         msg_annce_notifs[nameid - 1] = notif;
463                         notif.classname = "msg_annce_notification";
464                         break;
465                 }
466                 case MSG_INFO:
467                 {
468                         msg_info_notifs[nameid - 1] = notif;
469                         notif.classname = "msg_info_notification";
470                         break;
471                 }
472                 case MSG_CENTER:
473                 {
474                         msg_center_notifs[nameid - 1] = notif;
475                         notif.classname = "msg_center_notification";
476                         break;
477                 }
478                 case MSG_MULTI:
479                 {
480                         msg_multi_notifs[nameid - 1] = notif;
481                         notif.classname = "msg_multi_notification";
482                         break;
483                 }
484                 case MSG_CHOICE:
485                 {
486                         msg_choice_notifs[nameid - 1] = notif;
487                         notif.classname = "msg_choice_notification";
488                         break;
489                 }
490
491                 default:
492                 {
493                         error(sprintf(
494                                 strcat(
495                                         "^1NOTIFICATION WITH IMPROPER TYPE: ",
496                                         "^7net_type = %d, net_name = %s.\n"
497                                 ),
498                                 typeid,
499                                 namestring
500                         ));
501                         return; // It's not possible to recover from this one
502                 }
503         }
504         notif.nent_default = var_default;
505         notif.nent_name = strzone(namestring);
506         notif.nent_type = typeid;
507         notif.nent_id = nameid;
508         notif.nent_enabled = (1 <= var_cvar);
509
510         typestring = Get_Notif_TypeName(typeid);
511
512         // Other pre-notif-setup requisites
513         notif_error = FALSE;
514
515         // ====================
516         //  Notification Setup
517         // ====================
518         switch(typeid)
519         {
520                 case MSG_ANNCE:
521                 {
522                         // Set MSG_ANNCE information and handle precaching
523                         #ifdef CSQC
524                         if not(GENTLE && (var_cvar == 1))
525                         {
526                                 if(snd != "")
527                                 {
528                                         if(notif.nent_enabled)
529                                         {
530                                                 precache_sound(sprintf("announcer/%s/%s.wav", autocvar_cl_announcer, snd));
531                                                 notif.nent_channel = channel;
532                                                 notif.nent_snd = strzone(snd);
533                                                 notif.nent_vol = vol;
534                                                 notif.nent_position = position;
535                                         }
536                                 }
537                                 else
538                                 {
539                                         print(sprintf(
540                                                 strcat(
541                                                         "^1NOTIFICATION WITH NO SOUND: ",
542                                                         "^7net_type = %s, net_name = %s.\n"
543                                                 ),
544                                                 typestring,
545                                                 namestring
546                                         ));
547                                         notif_error = TRUE;
548                                 }
549                         }
550                         else { notif.nent_enabled = FALSE; }
551                         #else
552                         notif.nent_enabled = FALSE;
553                         #endif
554
555                         break;
556                 }
557                 
558                 case MSG_INFO:
559                 case MSG_CENTER:
560                 {
561                         // Set MSG_INFO and MSG_CENTER string/float counts
562                         notif.nent_stringcount = strnum;
563                         notif.nent_floatcount = flnum;
564
565                         // Only initialize arguments if we're either a client or on a dedicated server
566                         #ifdef SVQC
567                         float should_process_args = server_is_dedicated;
568                         #else
569                         float should_process_args = TRUE;
570                         #endif
571
572                         if(should_process_args)
573                         {
574                                 // ========================
575                                 //  Process Main Arguments
576                                 // ========================
577                                 if(strnum + flnum)
578                                 {
579                                         if(args != "")
580                                         {
581                                                 notif.nent_args = strzone(
582                                                         Process_Notif_Args(1, args, typestring, namestring));
583                                         }
584                                         else if((hudargs == "") && (durcnt ==""))
585                                         {
586                                                 print(sprintf(
587                                                         strcat(
588                                                                 "^1NOTIFICATION HAS ARG COUNTS BUT NO ARGS OR HUDARGS OR DURCNT: ",
589                                                                 "^7net_type = %s, net_name = %s, strnum = %d, flnum = %d\n"
590                                                         ),
591                                                         typestring,
592                                                         namestring,
593                                                         strnum,
594                                                         flnum
595                                                 ));
596                                                 notif_error = TRUE;
597                                         }
598                                 }
599                                 else if(args != "")
600                                 {
601                                         notif.nent_args = strzone(
602                                                 Process_Notif_Args(1, args, typestring, namestring));
603                                 }
604
605
606                                 // =======================================
607                                 //  Process HUD and Centerprint Arguments
608                                 //    Only processed on CSQC, as these
609                                 //    args are only for HUD features.
610                                 // =======================================
611                                 #ifdef CSQC
612                                 if(hudargs != "")
613                                 {
614                                         notif.nent_hudargs = strzone(
615                                                 Process_Notif_Args(2, hudargs, typestring, namestring));
616                                                 
617                                         if(icon != "") { notif.nent_icon = strzone(icon); }
618                                         else
619                                         {
620                                                 print(sprintf(
621                                                         strcat(
622                                                                 "^1NOTIFICATION HAS HUDARGS BUT NO ICON: ",
623                                                                 "^7net_type = %s, net_name = %s.\n"
624                                                         ),
625                                                         typestring,
626                                                         namestring
627                                                 ));
628                                                 notif_error = TRUE;
629                                         }
630                                 }
631                                 else if(icon != "")
632                                 {
633                                         print(sprintf(
634                                                 strcat(
635                                                         "^1NOTIFICATION HAS ICON BUT NO HUDARGS: ",
636                                                         "^7net_type = %s, net_name = %s.\n"
637                                                 ),
638                                                 typestring,
639                                                 namestring
640                                         ));
641                                         notif_error = TRUE;
642                                 }
643
644                                 if(durcnt != "")
645                                 {
646                                         notif.nent_durcnt = strzone(
647                                                 Process_Notif_Args(3, durcnt, typestring, namestring));
648                                                 
649                                         if(cpid != NO_MSG) { notif.nent_cpid = cpid; }
650                                         else
651                                         {
652                                                 print(sprintf(
653                                                         strcat(
654                                                                 "^1NOTIFICATION HAS DURCNT BUT NO CPID: ",
655                                                                 "^7net_type = %s, net_name = %s.\n"
656                                                         ),
657                                                         typestring,
658                                                         namestring
659                                                 ));
660                                                 notif_error = TRUE;
661                                         }
662                                 } 
663                                 else if(cpid != NO_MSG) { notif.nent_cpid = cpid; }
664                                 #endif
665
666
667                                 // ======================
668                                 //  Process Notif String
669                                 // ======================
670                                 #define SET_NOTIF_STRING(string,stringname) \
671                                         notif.nent_string = strzone(CCR( \
672                                                 Process_Notif_Line( \
673                                                         typeid, \
674                                                         (var_cvar > 1), \
675                                                         string, \
676                                                         typestring, \
677                                                         namestring, \
678                                                         stringname \
679                                                 )) \
680                                         );
681
682                                 if(GENTLE)
683                                 {
684                                         if(gentle != "") { SET_NOTIF_STRING(gentle, "GENTLE") }
685                                         else if(normal != "") { SET_NOTIF_STRING(normal, "NORMAL") }
686                                 }
687                                 else if(normal != "") { SET_NOTIF_STRING(normal, "NORMAL") }
688                                 
689                                 #undef SET_NOTIF_STRING
690
691                                 // Check to make sure a string was chosen
692                                 if(notif.nent_string == "")
693                                 {
694                                         print(sprintf(
695                                                 strcat(
696                                                         "^1EMPTY NOTIFICATION: ",
697                                                         "^7net_type = %s, net_name = %s.\n"
698                                                 ),
699                                                 typestring,
700                                                 namestring
701                                         ));
702                                         notif_error = TRUE;
703                                 }
704                         }
705
706                         break;
707                 }
708
709                 case MSG_MULTI:
710                 {
711                         // Set MSG_MULTI string/float counts
712                         if((anncename == NO_MSG) && (infoname == NO_MSG) && (centername == NO_MSG))
713                         {
714                                 print(sprintf(
715                                         strcat(
716                                                 "^1NOTIFICATION WITH NO SUBCALLS: ",
717                                                 "^7net_type = %s, net_name = %s.\n"
718                                         ),
719                                         typestring,
720                                         namestring
721                                 ));
722                                 notif_error = TRUE;
723                         }
724                         else
725                         {
726                                 // announcements don't actually need any arguments, so lets not even count them.
727                                 if(anncename != NO_MSG) { notif.nent_msgannce = msg_annce_notifs[anncename - 1]; }
728                                 
729                                 float infoname_stringcount = 0, infoname_floatcount = 0;
730                                 float centername_stringcount = 0, centername_floatcount = 0;
731                                 
732                                 if(infoname != NO_MSG)
733                                 {
734                                         notif.nent_msginfo = msg_info_notifs[infoname - 1];
735                                         infoname_stringcount = notif.nent_msginfo.nent_stringcount;
736                                         infoname_floatcount = notif.nent_msginfo.nent_floatcount;
737                                 }
738                                 
739                                 if(centername != NO_MSG)
740                                 {
741                                         notif.nent_msgcenter = msg_center_notifs[centername - 1];
742                                         centername_stringcount = notif.nent_msgcenter.nent_stringcount;
743                                         centername_floatcount = notif.nent_msgcenter.nent_floatcount;
744                                 }
745                                 
746                                 // set the requirements of THIS notification to the totals of its subcalls
747                                 notif.nent_stringcount = max(infoname_stringcount, centername_stringcount);
748                                 notif.nent_floatcount = max(infoname_floatcount, centername_floatcount);
749                         }
750                         
751                         break;
752                 }
753
754                 case MSG_CHOICE:
755                 {
756                         if((chtype == NO_MSG) || (optiona == NO_MSG) || (optionb == NO_MSG))
757                         {
758                                 print(sprintf(
759                                         strcat(
760                                                 "^1NOTIFICATION IS MISSING CHOICE PARAMS: ",
761                                                 "^7net_type = %s, net_name = %s.\n"
762                                         ),
763                                         typestring,
764                                         namestring
765                                 ));
766                                 notif_error = TRUE;
767                         }
768                         else
769                         {
770                                 switch(chtype)
771                                 {
772                                         case MSG_ANNCE:
773                                         {
774                                                 notif.nent_optiona = msg_annce_notifs[optiona - 1];
775                                                 notif.nent_optionb = msg_annce_notifs[optionb - 1];
776                                                 break;
777                                         }
778                                         case MSG_INFO:
779                                         {
780                                                 notif.nent_optiona = msg_info_notifs[optiona - 1];
781                                                 notif.nent_optionb = msg_info_notifs[optionb - 1];
782                                                 break;
783                                         }
784                                         case MSG_CENTER:
785                                         {
786                                                 notif.nent_optiona = msg_center_notifs[optiona - 1];
787                                                 notif.nent_optionb = msg_center_notifs[optionb - 1];
788                                                 break;
789                                         }
790                                         case MSG_MULTI:
791                                         {
792                                                 notif.nent_optiona = msg_multi_notifs[optiona - 1];
793                                                 notif.nent_optionb = msg_multi_notifs[optionb - 1];
794                                                 break;
795                                         }
796                                         case MSG_CHOICE: // should we REALLY allow nested options?... 
797                                         {
798                                                 notif.nent_optiona = msg_choice_notifs[optiona - 1];
799                                                 notif.nent_optionb = msg_choice_notifs[optionb - 1];
800                                                 break;
801                                         }
802
803                                         default:
804                                         {
805                                                 error(sprintf(
806                                                         strcat(
807                                                                 "^1NOTIFICATION WITH IMPROPER TYPE: ",
808                                                                 "^7net_type = %d, net_name = %s.\n"
809                                                         ),
810                                                         typeid,
811                                                         namestring
812                                                 ));
813                                                 notif_error = TRUE;
814                                                 break;
815                                         }
816                                 }
817                                 notif.nent_challow = challow; // 0: never allowed, 1: allowed in warmup, 2: always allowed
818                                 notif.nent_stringcount = max(notif.nent_optiona.nent_stringcount, notif.nent_optionb.nent_stringcount);
819                                 notif.nent_floatcount = max(notif.nent_optiona.nent_floatcount, notif.nent_optionb.nent_floatcount);
820                                 print(sprintf("INIT NOTIF DEBUG: %d %d\n", notif.nent_stringcount, notif.nent_floatcount));
821                                 print(sprintf("^ optiona: %s %s : %d %d\n",
822                                         Get_Notif_TypeName(notif.nent_optiona.nent_type),
823                                         notif.nent_optiona.nent_name,
824                                         notif.nent_optiona.nent_stringcount,
825                                         notif.nent_optiona.nent_floatcount));
826                         }
827                         break;
828                 }
829                 
830                 default: print("DAFUQ?\n"); notif_error = TRUE; break;
831         }
832
833         // now check to see if any errors happened 
834         if(notif_error)
835         {
836                 notif.nent_enabled = FALSE; // disable the notification so it can't cause trouble
837                 notif_global_error = TRUE; // throw the red flag that an error happened on init
838         }
839 }
840
841
842 // ===============
843 //  Cvar Handling
844 // ===============
845
846 // used by MSG_CHOICE to build list of choices
847 #ifdef SVQC
848 void Notification_GetCvars(void)
849 {
850         float i;
851         for(i = 0; i <= NOTIF_CHOICE_COUNT; ++i)
852         {
853                 GetCvars_handleFloat(
854                         get_cvars_s,
855                         get_cvars_f,
856                         msg_choice_choices[i],
857                         sprintf("notification_%s", msg_choice_notifs[i].nent_name)
858                 );
859         }
860
861         GetCvars_handleFloat(get_cvars_s, get_cvars_f, FRAG_VERBOSE, "notification_frag_verbose");
862 }
863 #endif
864
865 // used to output notifications.cfg file
866 void Dump_Notifications(float fh, float alsoprint)
867 {
868         #define NOTIF_WRITE(a) { \
869                 fputs(fh, a); \
870                 if(alsoprint) { print(a); } }
871         #define NOTIF_WRITE_ENTITY(description) { \
872                 notif_msg = \
873                         sprintf( \
874                                 "seta notification_%s \"%d\" \"%s\"\n", \
875                                 e.nent_name, e.nent_default, description \
876                         ); \
877                 NOTIF_WRITE(notif_msg) }
878         #define NOTIF_WRITE_HARDCODED(cvar,default,description) { \
879                 notif_msg = \
880                         sprintf( \
881                                 "seta notification_%s \"%s\" \"%s\"\n", \
882                                 cvar, default, description \
883                         ); \
884                 NOTIF_WRITE(notif_msg) }
885
886         string notif_msg;
887         float i;
888         entity e;
889
890         // Note: This warning only applies to the notifications.cfg file that is output...
891
892         // You ARE supposed to manually edit this function to add i.e. hard coded
893         // notification variables for mutators or game modes or such and then
894         // regenerate the notifications.cfg file from the new code.
895
896         NOTIF_WRITE("// ********************************************** //\n");
897         NOTIF_WRITE("// ** WARNING - DO NOT MANUALLY EDIT THIS FILE ** //\n");
898         NOTIF_WRITE("// **                                          ** //\n");
899         NOTIF_WRITE("// **  This file is automatically generated    ** //\n");
900         NOTIF_WRITE("// **  by code with the command 'dumpnotifs'.  ** //\n");
901         NOTIF_WRITE("// **                                          ** //\n");
902         NOTIF_WRITE("// **  If you add a new notification, please   ** //\n");
903         NOTIF_WRITE("// **  regenerate this file with that command  ** //\n");
904         NOTIF_WRITE("// **  making sure that the output matches     ** //\n");
905         NOTIF_WRITE("// **  with the lists and defaults in code.    ** //\n");
906         NOTIF_WRITE("// **                                          ** //\n");
907         NOTIF_WRITE("// ********************************************** //\n");
908
909         // These notifications will also append their string as a comment...
910         // This is not necessary, and does not matter if they vary between config versions,
911         // it is just a semi-helpful tool for those who want to manually change their user settings.
912
913         NOTIF_WRITE(sprintf("\n// MSG_ANNCE notifications (count = %d):\n", NOTIF_ANNCE_COUNT));
914         for(i = 1; i <= NOTIF_ANNCE_COUNT; ++i)
915         {
916                 e = Get_Notif_Ent(MSG_ANNCE, i);
917                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
918                 
919                 NOTIF_WRITE_ENTITY(
920                         "Notification control cvar: 0 = disabled, 1 = enabled if gentle mode is off, 2 = always enabled)"
921                 );
922         }
923
924         NOTIF_WRITE(sprintf("\n// MSG_INFO notifications (count = %d):\n", NOTIF_INFO_COUNT));
925         for(i = 1; i <= NOTIF_INFO_COUNT; ++i)
926         {
927                 e = Get_Notif_Ent(MSG_INFO, i);
928                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
929                 
930                 NOTIF_WRITE_ENTITY(
931                         "Notification control cvar: 0 = off, 1 = print to console, "
932                         "2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
933                 );
934         }
935
936         NOTIF_WRITE(sprintf("\n// MSG_CENTER notifications (count = %d):\n", NOTIF_CENTER_COUNT));
937         for(i = 1; i <= NOTIF_CENTER_COUNT; ++i)
938         {
939                 e = Get_Notif_Ent(MSG_CENTER, i);
940                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
941                 
942                 NOTIF_WRITE_ENTITY(
943                         "Notification control cvar: 0 = off, 1 = centerprint"
944                 );
945         }
946
947         NOTIF_WRITE(sprintf("\n// MSG_MULTI notifications (count = %d):\n", NOTIF_MULTI_COUNT));
948         for(i = 1; i <= NOTIF_MULTI_COUNT; ++i)
949         {
950                 e = Get_Notif_Ent(MSG_MULTI, i);
951                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
952                 
953                 NOTIF_WRITE_ENTITY(
954                         "Notification control cvar: 0 = off, 1 = trigger subcalls"
955                 );
956         }
957
958         NOTIF_WRITE(sprintf("\n// MSG_CHOICE notifications (count = %d):\n", NOTIF_CHOICE_COUNT));
959         for(i = 1; i <= NOTIF_CHOICE_COUNT; ++i)
960         {
961                 e = Get_Notif_Ent(MSG_CHOICE, i);
962                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
963                 
964                 NOTIF_WRITE_ENTITY(
965                         "Notification control cvar: 0 = off, 1 = trigger option A subcall, 2 = trigger option B subcall"
966                 );
967         }
968
969         // edit these to match whichever cvars are used for specific notification options
970         NOTIF_WRITE("\n// HARD CODED notification variables:\n");
971         
972         NOTIF_WRITE_HARDCODED(
973                 "allow_chatboxprint", "1",
974                 "Allow notifications to be printed to chat box by setting notification cvar to 2 "
975                 "(You can also set this cvar to 2 to force ALL notifications to be printed to the chatbox)"
976         );
977         
978         NOTIF_WRITE_HARDCODED(
979                 "ctf_capture_verbose", "0",
980                 "Show extra information when someone captures a flag"
981         );
982         
983         NOTIF_WRITE_HARDCODED(
984                 "ctf_pickup_enemy_verbose", "0",
985                 "Show extra information if an enemy picks up a flag"
986         );
987         
988         NOTIF_WRITE_HARDCODED(
989                 "ctf_pickup_team_verbose", "0",
990                 "Show extra information if a team mate picks up a flag"
991         );
992         
993         NOTIF_WRITE_HARDCODED(
994                 "debug", "0",
995                 "Print extra debug information on all notification function calls "
996                 "(Requires -DNOTIFICATIONS_DEBUG flag to be enabled on QCSRC compilation)... "
997                 "0 = disabled, 1 = dprint, 2 = print"
998         );
999         
1000         NOTIF_WRITE_HARDCODED(
1001                 "errors_are_fatal", "1",
1002                 "If a notification fails upon initialization, cause a Host_Error to stop the program"
1003         );
1004         
1005         NOTIF_WRITE_HARDCODED(
1006                 "frag_verbose", "1",
1007                 "Show extra information when you frag someone (or when you are fragged"
1008         );
1009         
1010         NOTIF_WRITE_HARDCODED(
1011                 "item_centerprinttime", "1.5",
1012                 "How long to show item information centerprint messages (like 'You got the Electro' or such)"
1013         );
1014         
1015         NOTIF_WRITE_HARDCODED(
1016                 "lifetime_mapload", "10",
1017                 "Amount of time that notification entities last immediately at mapload (in seconds) "
1018                 "to help prevent notifications from being lost on early init (like gamestart countdown)"
1019         );
1020         
1021         NOTIF_WRITE_HARDCODED(
1022                 "lifetime_runtime", "0.5",
1023                 "Amount of time that notification entities last on the server during runtime (In seconds)"
1024         );
1025         
1026         NOTIF_WRITE_HARDCODED(
1027                 "server_allows_frag_verbose", "1",
1028                 "Server side cvar for showing extra information in frag messages... 0 = no extra frag information, "
1029                 "1 = frag information only in warmup, 2 = frag information allowed all the time"
1030         );
1031         
1032         NOTIF_WRITE_HARDCODED(
1033                 "server_allows_location", "1",
1034                 "Server side cvar for allowing death messages to show location information too"
1035         );
1036         
1037         NOTIF_WRITE_HARDCODED(
1038                 "show_location", "0",
1039                 "Append location information to MSG_INFO death/kill messages"
1040         );
1041         
1042         NOTIF_WRITE_HARDCODED(
1043                 "show_location_string", "",
1044                 "Replacement string piped into sprintf, "
1045                 "so you can do different messages like this: ' at the %s' or ' (near %s)'"
1046         );
1047         
1048         NOTIF_WRITE_HARDCODED(
1049                 "show_sprees", "1",
1050                 "Print information about sprees in death/kill messages"
1051         );
1052         
1053         NOTIF_WRITE_HARDCODED(
1054                 "show_sprees_center", "1",
1055                 "Show spree information in MSG_CENTER messages... "
1056                 "0 = off, 1 = target (but only for first victim) and attacker"
1057         );
1058         
1059         NOTIF_WRITE_HARDCODED(
1060                 "show_sprees_center_specialonly", "1",
1061                 "Don't show spree information in MSG_CENTER messages if it isn't an achievement"
1062         );
1063         
1064         NOTIF_WRITE_HARDCODED(
1065                 "show_sprees_info", "3",
1066                 "Show spree information in MSG_INFO messages... "
1067                 "0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker"
1068         );
1069         
1070         NOTIF_WRITE_HARDCODED(
1071                 "show_sprees_info_newline", "1",
1072                 "Show attacker spree information for MSG_INFO messages on a separate line than the death notification itself"
1073         );
1074         
1075         NOTIF_WRITE_HARDCODED(
1076                 "show_sprees_info_specialonly", "1",
1077                 "Don't show attacker spree information in MSG_INFO messages if it isn't an achievement"
1078         );
1079
1080         NOTIF_WRITE(sprintf(
1081                 strcat(
1082                         "\n// Notification counts (total = %d): ",
1083                         "MSG_ANNCE = %d, MSG_INFO = %d, MSG_CENTER = %d, MSG_MULTI = %d, MSG_CHOICE = %d\n"
1084                 ),
1085                 (
1086                         NOTIF_ANNCE_COUNT +
1087                         NOTIF_INFO_COUNT +
1088                         NOTIF_CENTER_COUNT +
1089                         NOTIF_MULTI_COUNT +
1090                         NOTIF_CHOICE_COUNT
1091                 ),
1092                 NOTIF_ANNCE_COUNT,
1093                 NOTIF_INFO_COUNT,
1094                 NOTIF_CENTER_COUNT,
1095                 NOTIF_MULTI_COUNT,
1096                 NOTIF_CHOICE_COUNT
1097         ));
1098         
1099         return;
1100         #undef NOTIF_WRITE_HARDCODED
1101         #undef NOTIF_WRITE_ENTITY
1102         #undef NOTIF_WRITE
1103 }
1104
1105
1106 // ===============================
1107 //  Frontend Notification Pushing
1108 // ===============================
1109
1110 #ifdef NOTIFICATIONS_DEBUG
1111 void Debug_Notification(string input)
1112 {
1113         switch(autocvar_notification_debug)
1114         {
1115                 case 1: { dprint(input); break; }
1116                 case 2: { print(input); break; }
1117         }
1118 }
1119 #endif
1120
1121 string Local_Notification_sprintf(
1122         string input, string args, 
1123         string s1, string s2, string s3, string s4,
1124         float f1, float f2, float f3, float f4)
1125 {
1126         #ifdef NOTIFICATIONS_DEBUG
1127         Debug_Notification(sprintf(
1128                 "Local_Notification_sprintf('%s^7', '%s', %s, %s);\n",
1129                 MakeConsoleSafe(input),
1130                 args,
1131                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1132                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1133         ));
1134         #endif
1135         
1136         string selected;
1137         float sel_num;
1138         for(sel_num = 0; sel_num < NOTIF_MAX_ARGS; ++sel_num) { arg_slot[sel_num] = ""; }
1139
1140         string tmp_s;
1141
1142         for(sel_num = 0;(args != "");)
1143         {
1144                 selected = car(args); args = cdr(args);
1145                 NOTIF_HIT_MAX(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
1146                 switch(strtolower(selected))
1147                 {
1148                         #define ARG_CASE(prog,selected,result) \
1149                                 #ifdef CSQC \
1150                                         #if (prog != ARG_SV) && (prog != ARG_DC) \
1151                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1152                                         #endif \
1153                                 #else \
1154                                         #if (prog != ARG_CS) && (prog != ARG_DC) \
1155                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1156                                         #endif \
1157                                 #endif
1158                         NOTIF_ARGUMENT_LIST
1159                         #undef ARG_CASE
1160                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
1161                 }
1162         }
1163         return sprintf(
1164                 strcat(input, "\n"),
1165                 arg_slot[0],
1166                 arg_slot[1],
1167                 arg_slot[2],
1168                 arg_slot[3],
1169                 arg_slot[4],
1170                 arg_slot[5],
1171                 arg_slot[6]
1172         );
1173 }
1174
1175 #ifdef CSQC
1176 void Local_Notification_sound(
1177         float soundchannel, string soundfile,
1178         float soundvolume, float soundposition)
1179 {
1180         if((soundfile != prev_soundfile) || (time >= (prev_soundtime + autocvar_cl_announcer_antispam)))
1181         {
1182                 #ifdef NOTIFICATIONS_DEBUG
1183                 Debug_Notification(sprintf(
1184                         "Local_Notification_sound(world, %f, '%s', %f, %f);\n",
1185                         soundchannel,
1186                         sprintf(
1187                                 "announcer/%s/%s.wav",
1188                                 autocvar_cl_announcer,
1189                                 soundfile
1190                         ),
1191                         soundvolume,
1192                         soundposition
1193                 ));
1194                 #endif
1195                 
1196                 sound(
1197                         world,
1198                         soundchannel,
1199                         sprintf(
1200                                 "announcer/%s/%s.wav",
1201                                 autocvar_cl_announcer,
1202                                 soundfile
1203                         ),
1204                         soundvolume,
1205                         soundposition
1206                 );
1207                 
1208                 if(prev_soundfile) { strunzone(prev_soundfile); }
1209                 prev_soundfile = strzone(soundfile);
1210                 prev_soundtime = time;
1211         }
1212         else
1213         {
1214                 #ifdef NOTIFICATIONS_DEBUG
1215                 Debug_Notification(sprintf(
1216                         strcat(
1217                                 "Local_Notification_sound(world, %f, '%s', %f, %f) ",
1218                                 "^1BLOCKED BY ANTISPAM:^7 prevsnd: '%s', timediff: %f, limit: %f\n"
1219                          ),
1220                         soundchannel,
1221                         sprintf(
1222                                 "announcer/%s/%s.wav",
1223                                 autocvar_cl_announcer,
1224                                 soundfile
1225                         ),
1226                         soundvolume,
1227                         soundposition,
1228                         prev_soundfile,
1229                         (time - prev_soundtime),
1230                         autocvar_cl_announcer_antispam
1231                 ));
1232                 #endif
1233         }
1234 }
1235
1236 void Local_Notification_HUD_Notify_Push(
1237         string icon, string hudargs,
1238         string s1, string s2, string s3, string s4)
1239 {
1240         string selected;
1241         float sel_num;
1242         arg_slot[0] = ""; arg_slot[1] = "";
1243
1244         for(sel_num = 0;(hudargs != "");)
1245         {
1246                 selected = car(hudargs); hudargs = cdr(hudargs);
1247                 NOTIF_HIT_MAX(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
1248                 switch(strtolower(selected))
1249                 {
1250                         #define ARG_CASE(prog,selected,result) \
1251                                 #if (prog == ARG_CS_SV_HA) \
1252                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1253                                 #endif
1254                         NOTIF_ARGUMENT_LIST
1255                         #undef ARG_CASE
1256                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
1257                 }
1258         }
1259         #ifdef NOTIFICATIONS_DEBUG
1260         Debug_Notification(sprintf(
1261                 "Local_Notification_HUD_Notify_Push('%s^7', '%s', %s, %s);\n",
1262                 icon,
1263                 hudargs,
1264                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1265                 MakeConsoleSafe(sprintf("'%s^7', '%s^7'", stof(arg_slot[0]), stof(arg_slot[1])))
1266         ));
1267         #endif
1268         HUD_Notify_Push(icon, arg_slot[0], arg_slot[1]);
1269 }
1270
1271 void Local_Notification_centerprint_generic(
1272         string input, string durcnt,
1273         float cpid, float f1, float f2)
1274 {
1275         string selected;
1276         float sel_num;
1277         arg_slot[0] = ""; arg_slot[1] = "";
1278
1279         for(sel_num = 0;(durcnt != "");)
1280         {
1281                 selected = car(durcnt); durcnt = cdr(durcnt);
1282                 NOTIF_HIT_MAX(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic")
1283                 switch(strtolower(selected))
1284                 {
1285                         #define ARG_CASE(prog,selected,result) \
1286                                 #if (prog == ARG_CS_SV_DC) || (prog == ARG_DC) \
1287                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1288                                 #endif
1289                         NOTIF_ARGUMENT_LIST
1290                         #undef ARG_CASE
1291                         default:
1292                         {
1293                                 if(ftos(stof(selected)) != "") { arg_slot[sel_num] = selected; ++sel_num; }
1294                                 else { NOTIF_HIT_UNKNOWN(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic") }
1295                                 break;
1296                         }
1297                 }
1298         }
1299         #ifdef NOTIFICATIONS_DEBUG
1300         Debug_Notification(sprintf(
1301                 "Local_Notification_centerprint_generic('%s^7', '%s', %d, %d, %d, %d);\n",
1302                 MakeConsoleSafe(input),
1303                 durcnt,
1304                 f1, f2,
1305                 stof(arg_slot[0]), stof(arg_slot[1])
1306         ));
1307         #endif
1308         centerprint_generic(cpid, input, stof(arg_slot[0]), stof(arg_slot[1]));
1309 }
1310 #endif
1311
1312 void Local_Notification(float net_type, float net_name, ...count)
1313 {
1314         // check supplied type and name for errors
1315         string checkargs = Notification_CheckArgs_TypeName(net_type, net_name);
1316         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Local_Notification: %s\n", checkargs)); return; }
1317
1318         entity notif = Get_Notif_Ent(net_type, net_name);
1319         if not(notif) { backtrace("Local_Notification: Could not find notification entity!\n"); return; }
1320         if not(notif.nent_enabled)
1321         {
1322                 #ifdef NOTIFICATIONS_DEBUG
1323                 Debug_Notification(sprintf(
1324                         "Local_Notification(%s, %s): Entity was disabled...\n",
1325                         Get_Notif_TypeName(net_type),
1326                         notif.nent_name
1327                 ));
1328                 #endif
1329                 return;
1330         }
1331         
1332         if((notif.nent_stringcount + notif.nent_floatcount) > count)
1333         {
1334                 backtrace(sprintf(
1335                         strcat(
1336                                 "Not enough arguments for Local_Notification(%s, %s, ...)! ",
1337                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
1338                                 "Check the definition and function call for accuracy...?\n"
1339                         ),
1340                         Get_Notif_TypeName(net_type), notif.nent_name,
1341                         notif.nent_stringcount, notif.nent_floatcount, count
1342                 ));
1343                 return;
1344         }
1345         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
1346         {
1347                 backtrace(sprintf(
1348                         strcat(
1349                                 "Too many arguments for Local_Notification(%s, %s, ...)! ",
1350                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
1351                                 "Check the definition and function call for accuracy...?\n"
1352                         ),
1353                         Get_Notif_TypeName(net_type), notif.nent_name,
1354                         notif.nent_stringcount, notif.nent_floatcount, count
1355                 ));
1356                 return;
1357         }
1358
1359         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
1360         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
1361         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
1362         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
1363         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
1364         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
1365         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
1366         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
1367
1368         #ifdef NOTIFICATIONS_DEBUG
1369         Debug_Notification(sprintf(
1370                 "Local_Notification(%s, %s, %s, %s);\n",
1371                 Get_Notif_TypeName(net_type),
1372                 notif.nent_name,
1373                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1374                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1375         ));
1376         #endif
1377         
1378         switch(net_type)
1379         {
1380                 case MSG_ANNCE:
1381                 {
1382                         #ifdef CSQC
1383                         Local_Notification_sound(
1384                                 notif.nent_channel,
1385                                 notif.nent_snd,
1386                                 notif.nent_vol,
1387                                 notif.nent_position
1388                         );
1389                         #else
1390                         backtrace("MSG_ANNCE on server?... Please notify Samual immediately!\n");
1391                         #endif
1392                         break;
1393                 }
1394                 
1395                 case MSG_INFO:
1396                 {
1397                         print(
1398                                 Local_Notification_sprintf(
1399                                         notif.nent_string,
1400                                         notif.nent_args, 
1401                                         s1, s2, s3, s4,
1402                                         f1, f2, f3, f4)
1403                         );
1404                         #ifdef CSQC 
1405                         if(notif.nent_icon != "")
1406                         {
1407                                 Local_Notification_HUD_Notify_Push(
1408                                         notif.nent_icon,
1409                                         notif.nent_hudargs,
1410                                         s1, s2, s3, s4);
1411                         } 
1412                         #endif 
1413                         break;
1414                 }
1415                 
1416                 #ifdef CSQC
1417                 case MSG_CENTER:
1418                 {
1419                         Local_Notification_centerprint_generic(
1420                                 Local_Notification_sprintf(
1421                                         notif.nent_string,
1422                                         notif.nent_args, 
1423                                         s1, s2, s3, s4,
1424                                         f1, f2, f3, f4),
1425                                 notif.nent_durcnt,
1426                                 notif.nent_cpid,
1427                                 f1, f2);
1428                         break;
1429                 }
1430                 #endif
1431                 
1432                 case MSG_MULTI:
1433                 {
1434                         if(notif.nent_msginfo)
1435                         if(notif.nent_msginfo.nent_enabled)
1436                         {
1437                                 Local_Notification_WOVA(
1438                                         MSG_INFO,
1439                                         notif.nent_msginfo.nent_id, 
1440                                         notif.nent_msginfo.nent_stringcount, 
1441                                         notif.nent_msginfo.nent_floatcount, 
1442                                         s1, s2, s3, s4,
1443                                         f1, f2, f3, f4);
1444                         }
1445                         #ifdef CSQC
1446                         if(notif.nent_msgannce)
1447                         if(notif.nent_msgannce.nent_enabled)
1448                         {
1449                                 Local_Notification_WOVA(
1450                                         MSG_ANNCE,
1451                                         notif.nent_msgannce.nent_id, 
1452                                         0, 0, 
1453                                         "", "", "", "",
1454                                         0, 0, 0, 0);
1455                         }
1456                         if(notif.nent_msgcenter)
1457                         if(notif.nent_msgcenter.nent_enabled)
1458                         {
1459                                 Local_Notification_WOVA(
1460                                         MSG_CENTER,
1461                                         notif.nent_msgcenter.nent_id, 
1462                                         notif.nent_msgcenter.nent_stringcount, 
1463                                         notif.nent_msgcenter.nent_floatcount, 
1464                                         s1, s2, s3, s4,
1465                                         f1, f2, f3, f4); 
1466                         }
1467                         #endif
1468                         break;
1469                 }
1470
1471                 case MSG_CHOICE:
1472                 {
1473                         entity found_choice;
1474                         
1475                         if(notif.nent_challow && (warmup_stage || (notif.nent_challow == 2)))
1476                         {
1477                                 switch(cvar_string(sprintf("notification_%s", notif.nent_name)))
1478                                 {
1479                                         case 1: found_choice = notif.nent_optiona; break;
1480                                         case 2: found_choice = notif.nent_optionb; break;
1481                                         default: return; // not enabled anyway
1482                                 }
1483                         }
1484                         else { found_choice = notif.nent_optiona; }
1485                         
1486                         Local_Notification_WOVA(
1487                                 found_choice.nent_type,
1488                                 found_choice.nent_id, 
1489                                 found_choice.nent_stringcount, 
1490                                 found_choice.nent_floatcount, 
1491                                 s1, s2, s3, s4,
1492                                 f1, f2, f3, f4); 
1493                 }
1494         }
1495 }
1496
1497 // WOVA = Without Variable Arguments 
1498 void Local_Notification_WOVA(
1499         float net_type, float net_name,
1500         float stringcount, float floatcount,
1501         string s1, string s2, string s3, string s4,
1502         float f1, float f2, float f3, float f4)
1503 {
1504         #define VARITEM(stringc,floatc,args) \
1505                 if((stringcount == stringc) && (floatcount == floatc)) \
1506                         { Local_Notification(net_type, net_name, args); return; }
1507         EIGHT_VARS_TO_VARARGS_VARLIST
1508         #undef VARITEM
1509         Local_Notification(net_type, net_name); // some notifications don't have any arguments at all
1510 }
1511
1512
1513 // =========================
1514 //  Notification Networking
1515 // =========================
1516
1517 #ifdef CSQC
1518 void Read_Notification(float is_new)
1519 {
1520         float net_type = ReadByte();
1521         float net_name = ReadShort();
1522
1523         entity notif;
1524
1525         if(net_type == MSG_CENTER_CPID)
1526         {
1527                 #ifdef NOTIFICATIONS_DEBUG
1528                 Debug_Notification(sprintf(
1529                         "Read_Notification(%d) at %f: net_type = %s, net_name = %d\n",
1530                         is_new,
1531                         time,
1532                         Get_Notif_TypeName(net_type),
1533                         net_name
1534                 ));
1535                 #endif
1536                 
1537                 if(is_new)
1538                 {
1539                         if(net_name == 0) { reset_centerprint_messages(); }
1540                         else if(net_name != NO_CPID)
1541                         {
1542                                 // in this case, net_name IS the cpid we want to kill
1543                                 centerprint_generic(net_name, "", 0, 0);
1544                         }
1545                         else
1546                         {
1547                                 backtrace(sprintf(
1548                                         "Read_Notification(%d) at %f: ^1TRIED TO KILL NO_CPID CENTERPRINT!\n",
1549                                         is_new,
1550                                         time
1551                                 ));
1552                         } 
1553                 }
1554         }
1555         else
1556         {
1557                 notif = Get_Notif_Ent(net_type, net_name);
1558                 if not(notif) { backtrace("Read_Notification: Could not find notification entity!\n"); return; }
1559
1560                 #ifdef NOTIFICATIONS_DEBUG
1561                 Debug_Notification(sprintf(
1562                         "Read_Notification(%d) at %f: net_type = %s, net_name = %s\n",
1563                         is_new,
1564                         time,
1565                         Get_Notif_TypeName(net_type),
1566                         notif.nent_name
1567                 ));
1568                 #endif
1569
1570                 string s1 = ((0 < notif.nent_stringcount) ? ReadString() : "");
1571                 string s2 = ((1 < notif.nent_stringcount) ? ReadString() : "");
1572                 string s3 = ((2 < notif.nent_stringcount) ? ReadString() : "");
1573                 string s4 = ((3 < notif.nent_stringcount) ? ReadString() : "");
1574                 float f1 = ((0 < notif.nent_floatcount) ? ReadLong() : 0);
1575                 float f2 = ((1 < notif.nent_floatcount) ? ReadLong() : 0);
1576                 float f3 = ((2 < notif.nent_floatcount) ? ReadLong() : 0);
1577                 float f4 = ((3 < notif.nent_floatcount) ? ReadLong() : 0);
1578         
1579                 if(is_new)
1580                 {
1581                         Local_Notification_WOVA(
1582                                 net_type, net_name,
1583                                 notif.nent_stringcount,
1584                                 notif.nent_floatcount,
1585                                 s1, s2, s3, s4,
1586                                 f1, f2, f3, f4);
1587                 }
1588         }
1589 }
1590 #endif
1591
1592 #ifdef SVQC
1593 void Net_Notification_Remove()
1594 {
1595         if not(self) { backtrace(sprintf("Net_Notification_Remove() at %f: Missing self!?\n", time)); return; }
1596         
1597         #ifdef NOTIFICATIONS_DEBUG
1598         Debug_Notification(sprintf(
1599                 "Net_Notification_Remove() at %f: %s '%s - %s' notification\n",
1600                 time,
1601                 ((self.nent_net_name == -1) ? "Killed" : "Removed"),
1602                 Get_Notif_TypeName(self.nent_net_type),
1603                 self.owner.nent_name
1604         ));
1605         #endif
1606         
1607         float i;
1608         for(i = 0; i < 4; ++i) { if(self.nent_strings[i]) { strunzone(self.nent_strings[i]); } }
1609         remove(self);
1610 }
1611
1612 float Net_Write_Notification(entity client, float sf)
1613 {
1614         if(Notification_ShouldSend(self.nent_broadcast, client, self.nent_client))
1615         {
1616                 float i;
1617                 WriteByte(MSG_ENTITY, ENT_CLIENT_NOTIFICATION);
1618                 WriteByte(MSG_ENTITY, self.nent_net_type);
1619                 WriteShort(MSG_ENTITY, self.nent_net_name);
1620                 for(i = 0; i < self.nent_stringcount; ++i) { WriteString(MSG_ENTITY, self.nent_strings[i]); } 
1621                 for(i = 0; i < self.nent_floatcount; ++i) { WriteLong(MSG_ENTITY, self.nent_floats[i]); }
1622                 return TRUE;
1623         }
1624         else { return FALSE; }
1625 }
1626
1627 void Kill_Notification(
1628         float broadcast, entity client,
1629         float net_type, float net_name)
1630 {
1631         string checkargs = Notification_CheckArgs(broadcast, client, 1, 1);
1632         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Kill_Notification: %s\n", checkargs)); return; }
1633
1634         #ifdef NOTIFICATIONS_DEBUG
1635         Debug_Notification(sprintf(
1636                 "Kill_Notification(%d, '%s', %s, %d);\n",
1637                 broadcast,
1638                 client.netname,
1639                 (net_type ? Get_Notif_TypeName(net_type) : "0"),
1640                 net_name
1641         ));
1642         #endif
1643
1644         entity notif, net_notif;
1645         float killed_cpid = NO_CPID;
1646         
1647         switch(net_type)
1648         {
1649                 case 0:
1650                 {
1651                         killed_cpid = 0; // kill ALL centerprints
1652                         break;
1653                 }
1654                 
1655                 case MSG_CENTER:
1656                 {
1657                         if(net_name)
1658                         {
1659                                 entity notif = Get_Notif_Ent(net_type, net_name);
1660                                 if not(notif) { backtrace("Kill_Notification: Could not find notification entity!\n"); return; }
1661                                 
1662                                 if(notif.nent_cpid)
1663                                         killed_cpid = notif.nent_cpid;
1664                                 else
1665                                         killed_cpid = NO_CPID;
1666                         }
1667                         else
1668                         {
1669                                 killed_cpid = 0; // kill ALL centerprints
1670                         }
1671                         break;
1672                 }
1673
1674                 case MSG_CENTER_CPID:
1675                 {
1676                         killed_cpid = net_name;
1677                         break;
1678                 }
1679         }
1680
1681         if(killed_cpid != NO_CPID)
1682         {
1683                 net_notif = spawn();
1684                 net_notif.classname = "net_kill_notification";
1685                 net_notif.nent_broadcast = broadcast;
1686                 net_notif.nent_client = client;
1687                 net_notif.nent_net_type = MSG_CENTER_CPID;
1688                 net_notif.nent_net_name = killed_cpid;
1689                 Net_LinkEntity(net_notif, FALSE, autocvar_notification_lifetime_runtime, Net_Write_Notification);
1690         }
1691
1692         for(notif = world; (notif = find(notif, classname, "net_notification"));)
1693         {
1694                 if(net_type)
1695                 {
1696                         if((killed_cpid != NO_CPID) && (notif.nent_net_type == MSG_CENTER))
1697                         {
1698                                 if(notif.owner.nent_cpid == killed_cpid)
1699                                 {
1700                                         notif.nent_net_name = -1;
1701                                         notif.nextthink = time;
1702                                 }
1703                                 else { continue; } // we ARE looking for a specific CPID, don't kill everything else too
1704                         }
1705                         else if(notif.nent_net_type == net_type)
1706                         {
1707                                 if(net_name)
1708                                 {
1709                                         if(notif.nent_net_name == net_name) { notif.nent_net_name = -1; notif.nextthink = time; }
1710                                         else { continue; } // we ARE looking for a certain net_name, don't kill everything else too
1711                                 }
1712                                 else { notif.nent_net_name = -1; notif.nextthink = time; }
1713                         }
1714                         else { continue; } // we ARE looking for a certain net_type, don't kill everything else too
1715                 }
1716                 else { notif.nent_net_name = -1; notif.nextthink = time; }
1717         }
1718 }
1719
1720 void Send_Notification(
1721         float broadcast, entity client,
1722         float net_type, float net_name,
1723         ...count)
1724 {
1725         // check supplied broadcast, target, type, and name for errors
1726         string checkargs = Notification_CheckArgs(broadcast, client, net_type, net_name);
1727         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Send_Notification: %s\n", checkargs)); return; }
1728
1729         // retreive counts for the arguments of this notification
1730         entity notif = Get_Notif_Ent(net_type, net_name);
1731         if not(notif) { backtrace("Send_Notification: Could not find notification entity!\n"); return; }
1732
1733         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
1734         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
1735         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
1736         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
1737         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
1738         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
1739         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
1740         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
1741
1742         #ifdef NOTIFICATIONS_DEBUG
1743         Debug_Notification(sprintf(
1744                 "Send_Notification(%d, %s, %s, %s, %s);\n",
1745                 broadcast,
1746                 Get_Notif_TypeName(net_type),
1747                 notif.nent_name,
1748                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1749                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1750         ));
1751         #endif
1752
1753         if((notif.nent_stringcount + notif.nent_floatcount) > count)
1754         {
1755                 backtrace(sprintf(
1756                         strcat(
1757                                 "Not enough arguments for Send_Notification(%d, %s, %s, ...)! ",
1758                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
1759                                 "Check the definition and function call for accuracy...?\n"
1760                         ),
1761                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name,
1762                         notif.nent_stringcount, notif.nent_floatcount, count
1763                 ));
1764                 return;
1765         }
1766         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
1767         {
1768                 backtrace(sprintf(
1769                         strcat(
1770                                 "Too many arguments for Send_Notification(%d, %s, %s, ...)! ",
1771                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
1772                                 "Check the definition and function call for accuracy...?\n"
1773                         ),
1774                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name,
1775                         notif.nent_stringcount, notif.nent_floatcount, count
1776                 ));
1777                 return;
1778         }
1779
1780         if(
1781                 server_is_dedicated
1782                 &&
1783                 (
1784                         broadcast == NOTIF_ALL
1785                         ||
1786                         broadcast == NOTIF_ALL_EXCEPT
1787                 )
1788                 &&
1789                 !(
1790                         net_type == MSG_ANNCE
1791                         ||
1792                         net_type == MSG_CENTER
1793                 )
1794         )
1795         {
1796                 Local_Notification_WOVA(
1797                         net_type, net_name,
1798                         notif.nent_stringcount,
1799                         notif.nent_floatcount,
1800                         s1, s2, s3, s4,
1801                         f1, f2, f3, f4);
1802         }
1803
1804         if(net_type == MSG_CHOICE)
1805         {
1806                 // float verbose_allowed = (autocvar_notification_server_allows_frag_verbose && ((autocvar_notification_server_allows_frag_verbose == 2) || warmup_stage));
1807                 // THIS GETS TRICKY... now we have to cycle through each possible player (checking broadcast)nent_challow
1808                 // and then do an individual NOTIF_ONE_ONLY recursive call for each one depending on their option...
1809                 // It's slow, but it's better than the alternatives:
1810                 //   1. Constantly networking all info and letting client decide
1811                 //   2. Manually handling each separate call on per-usage basis (See old CTF usage of verbose)
1812                 entity found_choice; 
1813
1814                 #define RECURSE_FROM_CHOICE(ent,action) \
1815                         if(notif.nent_challow && (warmup_stage || (notif.nent_challow == 2))) \
1816                         { \
1817                                 switch(ent.msg_choice_choices[net_name]) \
1818                                 { \
1819                                         case 1: found_choice = notif.nent_optiona; break; \
1820                                         case 2: found_choice = notif.nent_optionb; break; \
1821                                         default: action; \
1822                                 } \
1823                         } \
1824                         else { found_choice = notif.nent_optiona; } \
1825                         Send_Notification_WOVA( \
1826                                 NOTIF_ONE_ONLY, \
1827                                 ent, \
1828                                 found_choice.nent_type, \
1829                                 found_choice.nent_id, \
1830                                 found_choice.nent_stringcount, \
1831                                 found_choice.nent_floatcount, \
1832                                 s1, s2, s3, s4, \
1833                                 f1, f2, f3, f4);
1834
1835                 switch(broadcast)
1836                 {
1837                         case NOTIF_ONE_ONLY: // we can potentially save processing power with this broadcast method
1838                         {
1839                                 if(IS_REAL_CLIENT(client))
1840                                         { RECURSE_FROM_CHOICE(client, return) }
1841                                 break;
1842                         }
1843                         default:
1844                         {
1845                                 entity to;
1846                                 FOR_EACH_REALCLIENT(to)
1847                                         { if(Notification_ShouldSend(broadcast, to, client))
1848                                                 { RECURSE_FROM_CHOICE(to, continue) } }
1849                                 break;
1850                         }
1851                 }
1852         }
1853         else
1854         {
1855                 entity net_notif = spawn();
1856                 net_notif.owner = notif;
1857                 net_notif.classname = "net_notification";
1858                 net_notif.nent_broadcast = broadcast;
1859                 net_notif.nent_client = client;
1860                 net_notif.nent_net_type = net_type;
1861                 net_notif.nent_net_name = net_name;
1862                 net_notif.nent_stringcount = notif.nent_stringcount;
1863                 net_notif.nent_floatcount = notif.nent_floatcount;
1864                 
1865                 float i;
1866                 for(i = 0; i < net_notif.nent_stringcount; ++i)
1867                         { net_notif.nent_strings[i] = strzone(...(i, string)); }
1868                 for(i = 0; i < net_notif.nent_floatcount; ++i)
1869                         { net_notif.nent_floats[i] = ...((net_notif.nent_stringcount + i), float); }
1870
1871                 net_notif.think = Net_Notification_Remove;
1872                 net_notif.nextthink =
1873                         ((time > autocvar_notification_lifetime_mapload)
1874                         ?
1875                                 (time + autocvar_notification_lifetime_runtime)
1876                                 :
1877                                 autocvar_notification_lifetime_mapload
1878                         ); 
1879
1880                 Net_LinkEntity(net_notif, FALSE, 0, Net_Write_Notification);
1881         }
1882 }
1883
1884 // WOVA = Without Variable Arguments 
1885 void Send_Notification_WOVA(
1886         float broadcast, entity client,
1887         float net_type, float net_name,
1888         float stringcount, float floatcount,
1889         string s1, string s2, string s3, string s4,
1890         float f1, float f2, float f3, float f4)
1891 {
1892         #ifdef NOTIFICATIONS_DEBUG
1893         entity notif = Get_Notif_Ent(net_type, net_name);
1894         Debug_Notification(sprintf(
1895                 "Send_Notification_WOVA(%d, %s, %d, %d, %d, %s, %s);\n",
1896                 broadcast,
1897                 Get_Notif_TypeName(net_type),
1898                 notif.nent_name,
1899                 stringcount,
1900                 floatcount,
1901                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1902                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1903         ));
1904         #endif
1905         
1906         #define VARITEM(stringc,floatc,args) \
1907                 if((stringcount == stringc) && (floatcount == floatc)) \
1908                         { Send_Notification(broadcast, client, net_type, net_name, args); return; }
1909         EIGHT_VARS_TO_VARARGS_VARLIST
1910         #undef VARITEM
1911         Send_Notification(broadcast, client, net_type, net_name); // some notifications don't have any arguments at all
1912 }
1913
1914 // WOCOVA = Without Counts Or Variable Arguments 
1915 void Send_Notification_WOCOVA(
1916         float broadcast, entity client,
1917         float net_type, float net_name,
1918         string s1, string s2, string s3, string s4,
1919         float f1, float f2, float f3, float f4)
1920 {
1921         entity notif = Get_Notif_Ent(net_type, net_name);
1922         
1923         #ifdef NOTIFICATIONS_DEBUG
1924         Debug_Notification(sprintf(
1925                 "Send_Notification_WOCOVA(%d, %s, %s, %s, %s);\n",
1926                 broadcast,
1927                 Get_Notif_TypeName(net_type),
1928                 notif.nent_name,
1929                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1930                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1931         ));
1932         #endif
1933         
1934         #define VARITEM(stringc,floatc,args) \
1935                 if((notif.nent_stringcount == stringc) && (notif.nent_floatcount == floatc)) \
1936                         { Send_Notification(broadcast, client, net_type, net_name, args); return; }
1937         EIGHT_VARS_TO_VARARGS_VARLIST
1938         #undef VARITEM
1939         Send_Notification(broadcast, client, net_type, net_name); // some notifications don't have any arguments at all
1940 }
1941 #endif // ifdef SVQC