]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
Alright, now initialization should work for MSG_CHOICE
[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 chtype,
449         float optiona,
450         float optionb)
451 {
452         // =====================
453         //  Global Entity Setup
454         // =====================
455         entity notif = spawn();
456         string typestring = "";
457         switch(typeid)
458         {
459                 case MSG_ANNCE:
460                 {
461                         msg_annce_notifs[nameid - 1] = notif;
462                         notif.classname = "msg_annce_notification";
463                         break;
464                 }
465                 case MSG_INFO:
466                 {
467                         msg_info_notifs[nameid - 1] = notif;
468                         notif.classname = "msg_info_notification";
469                         break;
470                 }
471                 case MSG_CENTER:
472                 {
473                         msg_center_notifs[nameid - 1] = notif;
474                         notif.classname = "msg_center_notification";
475                         break;
476                 }
477                 case MSG_MULTI:
478                 {
479                         msg_multi_notifs[nameid - 1] = notif;
480                         notif.classname = "msg_multi_notification";
481                         break;
482                 }
483                 case MSG_CHOICE:
484                 {
485                         msg_choice_notifs[nameid - 1] = notif;
486                         notif.classname = "msg_choice_notification";
487                         break;
488                 }
489
490                 default:
491                 {
492                         error(sprintf(
493                                 strcat(
494                                         "^1NOTIFICATION WITH IMPROPER TYPE: ",
495                                         "^7net_type = %d, net_name = %s.\n"
496                                 ),
497                                 typeid,
498                                 namestring
499                         ));
500                         return; // It's not possible to recover from this one
501                 }
502         }
503         notif.nent_default = var_default;
504         notif.nent_name = strzone(namestring);
505         notif.nent_type = typeid;
506         notif.nent_id = nameid;
507         notif.nent_enabled = (1 <= var_cvar);
508
509         typestring = Get_Notif_TypeName(typeid);
510
511         // Other pre-notif-setup requisites
512         notif_error = FALSE;
513
514         // ====================
515         //  Notification Setup
516         // ====================
517         switch(typeid)
518         {
519                 case MSG_ANNCE:
520                 {
521                         // Set MSG_ANNCE information and handle precaching
522                         #ifdef CSQC
523                         if not(GENTLE && (var_cvar == 1))
524                         {
525                                 if(snd != "")
526                                 {
527                                         if(notif.nent_enabled)
528                                         {
529                                                 precache_sound(sprintf("announcer/%s/%s.wav", autocvar_cl_announcer, snd));
530                                                 notif.nent_channel = channel;
531                                                 notif.nent_snd = strzone(snd);
532                                                 notif.nent_vol = vol;
533                                                 notif.nent_position = position;
534                                         }
535                                 }
536                                 else
537                                 {
538                                         print(sprintf(
539                                                 strcat(
540                                                         "^1NOTIFICATION WITH NO SOUND: ",
541                                                         "^7net_type = %s, net_name = %s.\n"
542                                                 ),
543                                                 typestring,
544                                                 namestring
545                                         ));
546                                         notif_error = TRUE;
547                                 }
548                         }
549                         else { notif.nent_enabled = FALSE; }
550                         #else
551                         notif.nent_enabled = FALSE;
552                         #endif
553
554                         break;
555                 }
556                 
557                 case MSG_INFO:
558                 case MSG_CENTER:
559                 {
560                         // Set MSG_INFO and MSG_CENTER string/float counts
561                         notif.nent_stringcount = strnum;
562                         notif.nent_floatcount = flnum;
563
564                         // Only initialize arguments if we're either a client or on a dedicated server
565                         #ifdef SVQC
566                         float should_process_args = server_is_dedicated;
567                         #else
568                         float should_process_args = TRUE;
569                         #endif
570
571                         if(should_process_args)
572                         {
573                                 // ========================
574                                 //  Process Main Arguments
575                                 // ========================
576                                 if(strnum + flnum)
577                                 {
578                                         if(args != "")
579                                         {
580                                                 notif.nent_args = strzone(
581                                                         Process_Notif_Args(1, args, typestring, namestring));
582                                         }
583                                         else if((hudargs == "") && (durcnt ==""))
584                                         {
585                                                 print(sprintf(
586                                                         strcat(
587                                                                 "^1NOTIFICATION HAS ARG COUNTS BUT NO ARGS OR HUDARGS OR DURCNT: ",
588                                                                 "^7net_type = %s, net_name = %s, strnum = %d, flnum = %d\n"
589                                                         ),
590                                                         typestring,
591                                                         namestring,
592                                                         strnum,
593                                                         flnum
594                                                 ));
595                                                 notif_error = TRUE;
596                                         }
597                                 }
598                                 else if(args != "")
599                                 {
600                                         notif.nent_args = strzone(
601                                                 Process_Notif_Args(1, args, typestring, namestring));
602                                 }
603
604
605                                 // =======================================
606                                 //  Process HUD and Centerprint Arguments
607                                 //    Only processed on CSQC, as these
608                                 //    args are only for HUD features.
609                                 // =======================================
610                                 #ifdef CSQC
611                                 if(hudargs != "")
612                                 {
613                                         notif.nent_hudargs = strzone(
614                                                 Process_Notif_Args(2, hudargs, typestring, namestring));
615                                                 
616                                         if(icon != "") { notif.nent_icon = strzone(icon); }
617                                         else
618                                         {
619                                                 print(sprintf(
620                                                         strcat(
621                                                                 "^1NOTIFICATION HAS HUDARGS BUT NO ICON: ",
622                                                                 "^7net_type = %s, net_name = %s.\n"
623                                                         ),
624                                                         typestring,
625                                                         namestring
626                                                 ));
627                                                 notif_error = TRUE;
628                                         }
629                                 }
630                                 else if(icon != "")
631                                 {
632                                         print(sprintf(
633                                                 strcat(
634                                                         "^1NOTIFICATION HAS ICON BUT NO HUDARGS: ",
635                                                         "^7net_type = %s, net_name = %s.\n"
636                                                 ),
637                                                 typestring,
638                                                 namestring
639                                         ));
640                                         notif_error = TRUE;
641                                 }
642
643                                 if(durcnt != "")
644                                 {
645                                         notif.nent_durcnt = strzone(
646                                                 Process_Notif_Args(3, durcnt, typestring, namestring));
647                                                 
648                                         if(cpid != NO_MSG) { notif.nent_cpid = cpid; }
649                                         else
650                                         {
651                                                 print(sprintf(
652                                                         strcat(
653                                                                 "^1NOTIFICATION HAS DURCNT BUT NO CPID: ",
654                                                                 "^7net_type = %s, net_name = %s.\n"
655                                                         ),
656                                                         typestring,
657                                                         namestring
658                                                 ));
659                                                 notif_error = TRUE;
660                                         }
661                                 } 
662                                 else if(cpid != NO_MSG) { notif.nent_cpid = cpid; }
663                                 #endif
664
665
666                                 // ======================
667                                 //  Process Notif String
668                                 // ======================
669                                 #define SET_NOTIF_STRING(string,stringname) \
670                                         notif.nent_string = strzone(CCR( \
671                                                 Process_Notif_Line( \
672                                                         typeid, \
673                                                         (var_cvar > 1), \
674                                                         string, \
675                                                         typestring, \
676                                                         namestring, \
677                                                         stringname \
678                                                 )) \
679                                         );
680
681                                 if(GENTLE)
682                                 {
683                                         if(gentle != "") { SET_NOTIF_STRING(gentle, "GENTLE") }
684                                         else if(normal != "") { SET_NOTIF_STRING(normal, "NORMAL") }
685                                 }
686                                 else if(normal != "") { SET_NOTIF_STRING(normal, "NORMAL") }
687                                 
688                                 #undef SET_NOTIF_STRING
689
690                                 // Check to make sure a string was chosen
691                                 if(notif.nent_string == "")
692                                 {
693                                         print(sprintf(
694                                                 strcat(
695                                                         "^1EMPTY NOTIFICATION: ",
696                                                         "^7net_type = %s, net_name = %s.\n"
697                                                 ),
698                                                 typestring,
699                                                 namestring
700                                         ));
701                                         notif_error = TRUE;
702                                 }
703                         }
704
705                         break;
706                 }
707
708                 case MSG_MULTI:
709                 {
710                         // Set MSG_MULTI string/float counts
711                         if((anncename == NO_MSG) && (infoname == NO_MSG) && (centername == NO_MSG))
712                         {
713                                 print(sprintf(
714                                         strcat(
715                                                 "^1NOTIFICATION WITH NO SUBCALLS: ",
716                                                 "^7net_type = %s, net_name = %s.\n"
717                                         ),
718                                         typestring,
719                                         namestring
720                                 ));
721                                 notif_error = TRUE;
722                         }
723                         else
724                         {
725                                 // announcements don't actually need any arguments, so lets not even count them.
726                                 if(anncename != NO_MSG) { notif.nent_msgannce = msg_annce_notifs[anncename - 1]; }
727                                 
728                                 float infoname_stringcount = 0, infoname_floatcount = 0;
729                                 float centername_stringcount = 0, centername_floatcount = 0;
730                                 
731                                 if(infoname != NO_MSG)
732                                 {
733                                         notif.nent_msginfo = msg_info_notifs[infoname - 1];
734                                         infoname_stringcount = notif.nent_msginfo.nent_stringcount;
735                                         infoname_floatcount = notif.nent_msginfo.nent_floatcount;
736                                 }
737                                 
738                                 if(centername != NO_MSG)
739                                 {
740                                         notif.nent_msgcenter = msg_center_notifs[centername - 1];
741                                         centername_stringcount = notif.nent_msgcenter.nent_stringcount;
742                                         centername_floatcount = notif.nent_msgcenter.nent_floatcount;
743                                 }
744                                 
745                                 // set the requirements of THIS notification to the totals of its subcalls
746                                 notif.nent_stringcount = max(infoname_stringcount, centername_stringcount);
747                                 notif.nent_floatcount = max(infoname_floatcount, centername_floatcount);
748                         }
749                         
750                         break;
751                 }
752
753                 case MSG_CHOICE:
754                 {
755                         if((chtype == NO_MSG) || (optiona == NO_MSG) || (optionb == NO_MSG))
756                         {
757                                 print(sprintf(
758                                         strcat(
759                                                 "^1NOTIFICATION IS MISSING CHOICE PARAMS: ",
760                                                 "^7net_type = %s, net_name = %s.\n"
761                                         ),
762                                         typestring,
763                                         namestring
764                                 ));
765                                 notif_error = TRUE;
766                         }
767                         else
768                         {
769                                 switch(chtype)
770                                 {
771                                         case MSG_ANNCE:
772                                         {
773                                                 notif.nent_optiona = msg_annce_notifs[optiona - 1];
774                                                 notif.nent_optionb = msg_annce_notifs[optionb - 1];
775                                                 break;
776                                         }
777                                         case MSG_INFO:
778                                         {
779                                                 notif.nent_optiona = msg_info_notifs[optiona - 1];
780                                                 notif.nent_optionb = msg_info_notifs[optionb - 1];
781                                                 break;
782                                         }
783                                         case MSG_CENTER:
784                                         {
785                                                 notif.nent_optiona = msg_center_notifs[optiona - 1];
786                                                 notif.nent_optionb = msg_center_notifs[optionb - 1];
787                                                 break;
788                                         }
789                                         case MSG_MULTI:
790                                         {
791                                                 notif.nent_optiona = msg_multi_notifs[optiona - 1];
792                                                 notif.nent_optionb = msg_multi_notifs[optionb - 1];
793                                                 break;
794                                         }
795                                         case MSG_CHOICE: // should we REALLY allow nested options?... 
796                                         {
797                                                 notif.nent_optiona = msg_choice_notifs[optiona - 1];
798                                                 notif.nent_optionb = msg_choice_notifs[optionb - 1];
799                                                 break;
800                                         }
801
802                                         default:
803                                         {
804                                                 error(sprintf(
805                                                         strcat(
806                                                                 "^1NOTIFICATION WITH IMPROPER TYPE: ",
807                                                                 "^7net_type = %d, net_name = %s.\n"
808                                                         ),
809                                                         typeid,
810                                                         namestring
811                                                 ));
812                                                 notif_error = TRUE;
813                                                 break;
814                                         }
815                                 }
816                         }
817                         break;
818                 }
819                 
820                 default: print("DAFUQ?\n"); notif_error = TRUE; break;
821         }
822
823         // now check to see if any errors happened 
824         if(notif_error)
825         {
826                 notif.nent_enabled = FALSE; // disable the notification so it can't cause trouble
827                 notif_global_error = TRUE; // throw the red flag that an error happened on init
828         }
829 }
830
831
832 // =========================================
833 //  Cvar Handling With 'dumpnotifs' Command
834 // =========================================
835
836 void Dump_Notifications(float fh, float alsoprint)
837 {
838         #define NOTIF_WRITE(a) { \
839                 fputs(fh, a); \
840                 if(alsoprint) { print(a); } }
841         #define NOTIF_WRITE_ENTITY(description) { \
842                 notif_msg = \
843                         sprintf( \
844                                 "seta notification_%s \"%d\" \"%s\"\n", \
845                                 e.nent_name, e.nent_default, description \
846                         ); \
847                 NOTIF_WRITE(notif_msg) }
848         #define NOTIF_WRITE_HARDCODED(cvar,default,description) { \
849                 notif_msg = \
850                         sprintf( \
851                                 "seta notification_%s \"%s\" \"%s\"\n", \
852                                 cvar, default, description \
853                         ); \
854                 NOTIF_WRITE(notif_msg) }
855
856         string notif_msg;
857         float i;
858         entity e;
859
860         // Note: This warning only applies to the notifications.cfg file that is output...
861
862         // You ARE supposed to manually edit this function to add i.e. hard coded
863         // notification variables for mutators or game modes or such and then
864         // regenerate the notifications.cfg file from the new code.
865
866         NOTIF_WRITE("// ********************************************** //\n");
867         NOTIF_WRITE("// ** WARNING - DO NOT MANUALLY EDIT THIS FILE ** //\n");
868         NOTIF_WRITE("// **                                          ** //\n");
869         NOTIF_WRITE("// **  This file is automatically generated    ** //\n");
870         NOTIF_WRITE("// **  by code with the command 'dumpnotifs'.  ** //\n");
871         NOTIF_WRITE("// **                                          ** //\n");
872         NOTIF_WRITE("// **  If you add a new notification, please   ** //\n");
873         NOTIF_WRITE("// **  regenerate this file with that command  ** //\n");
874         NOTIF_WRITE("// **  making sure that the output matches     ** //\n");
875         NOTIF_WRITE("// **  with the lists and defaults in code.    ** //\n");
876         NOTIF_WRITE("// **                                          ** //\n");
877         NOTIF_WRITE("// ********************************************** //\n");
878
879         // These notifications will also append their string as a comment...
880         // This is not necessary, and does not matter if they vary between config versions,
881         // it is just a semi-helpful tool for those who want to manually change their user settings.
882
883         NOTIF_WRITE(sprintf("\n// MSG_ANNCE notifications (count = %d):\n", NOTIF_ANNCE_COUNT));
884         for(i = 1; i <= NOTIF_ANNCE_COUNT; ++i)
885         {
886                 e = Get_Notif_Ent(MSG_ANNCE, i);
887                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
888                 
889                 NOTIF_WRITE_ENTITY(
890                         "Notification control cvar: 0 = disabled, 1 = enabled if gentle mode is off, 2 = always enabled)"
891                 );
892         }
893
894         NOTIF_WRITE(sprintf("\n// MSG_INFO notifications (count = %d):\n", NOTIF_INFO_COUNT));
895         for(i = 1; i <= NOTIF_INFO_COUNT; ++i)
896         {
897                 e = Get_Notif_Ent(MSG_INFO, i);
898                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
899                 
900                 NOTIF_WRITE_ENTITY(
901                         "Notification control cvar: 0 = off, 1 = print to console, "
902                         "2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
903                 );
904         }
905
906         NOTIF_WRITE(sprintf("\n// MSG_CENTER notifications (count = %d):\n", NOTIF_CENTER_COUNT));
907         for(i = 1; i <= NOTIF_CENTER_COUNT; ++i)
908         {
909                 e = Get_Notif_Ent(MSG_CENTER, i);
910                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
911                 
912                 NOTIF_WRITE_ENTITY(
913                         "Notification control cvar: 0 = off, 1 = centerprint"
914                 );
915         }
916
917         NOTIF_WRITE(sprintf("\n// MSG_MULTI notifications (count = %d):\n", NOTIF_MULTI_COUNT));
918         for(i = 1; i <= NOTIF_MULTI_COUNT; ++i)
919         {
920                 e = Get_Notif_Ent(MSG_MULTI, i);
921                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
922                 
923                 NOTIF_WRITE_ENTITY(
924                         "Notification control cvar: 0 = off, 1 = trigger subcalls"
925                 );
926         }
927
928         NOTIF_WRITE(sprintf("\n// MSG_CHOICE notifications (count = %d):\n", NOTIF_CHOICE_COUNT));
929         for(i = 1; i <= NOTIF_CHOICE_COUNT; ++i)
930         {
931                 e = Get_Notif_Ent(MSG_CHOICE, i);
932                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
933                 
934                 NOTIF_WRITE_ENTITY(
935                         "Notification control cvar: 0 = off, 1 = trigger option A subcall, 2 = trigger option B subcall"
936                 );
937         }
938
939         // edit these to match whichever cvars are used for specific notification options
940         NOTIF_WRITE("\n// HARD CODED notification variables:\n");
941         
942         NOTIF_WRITE_HARDCODED(
943                 "allow_chatboxprint", "1",
944                 "Allow notifications to be printed to chat box by setting notification cvar to 2 "
945                 "(You can also set this cvar to 2 to force ALL notifications to be printed to the chatbox)"
946         );
947         
948         NOTIF_WRITE_HARDCODED(
949                 "ctf_capture_verbose", "0",
950                 "Show extra information when someone captures a flag"
951         );
952         
953         NOTIF_WRITE_HARDCODED(
954                 "ctf_pickup_enemy_verbose", "0",
955                 "Show extra information if an enemy picks up a flag"
956         );
957         
958         NOTIF_WRITE_HARDCODED(
959                 "ctf_pickup_team_verbose", "0",
960                 "Show extra information if a team mate picks up a flag"
961         );
962         
963         NOTIF_WRITE_HARDCODED(
964                 "debug", "0",
965                 "Print extra debug information on all notification function calls "
966                 "(Requires -DNOTIFICATIONS_DEBUG flag to be enabled on QCSRC compilation)... "
967                 "0 = disabled, 1 = dprint, 2 = print"
968         );
969         
970         NOTIF_WRITE_HARDCODED(
971                 "errors_are_fatal", "1",
972                 "If a notification fails upon initialization, cause a Host_Error to stop the program"
973         );
974         
975         NOTIF_WRITE_HARDCODED(
976                 "frag_verbose", "1",
977                 "Show extra information when you frag someone (or when you are fragged"
978         );
979         
980         NOTIF_WRITE_HARDCODED(
981                 "item_centerprinttime", "1.5",
982                 "How long to show item information centerprint messages (like 'You got the Electro' or such)"
983         );
984         
985         NOTIF_WRITE_HARDCODED(
986                 "lifetime_mapload", "10",
987                 "Amount of time that notification entities last immediately at mapload (in seconds) "
988                 "to help prevent notifications from being lost on early init (like gamestart countdown)"
989         );
990         
991         NOTIF_WRITE_HARDCODED(
992                 "lifetime_runtime", "0.5",
993                 "Amount of time that notification entities last on the server during runtime (In seconds)"
994         );
995         
996         NOTIF_WRITE_HARDCODED(
997                 "server_allows_frag_verbose", "1",
998                 "Server side cvar for showing extra information in frag messages... 0 = no extra frag information, "
999                 "1 = frag information only in warmup, 2 = frag information allowed all the time"
1000         );
1001         
1002         NOTIF_WRITE_HARDCODED(
1003                 "server_allows_location", "1",
1004                 "Server side cvar for allowing death messages to show location information too"
1005         );
1006         
1007         NOTIF_WRITE_HARDCODED(
1008                 "show_location", "0",
1009                 "Append location information to MSG_INFO death/kill messages"
1010         );
1011         
1012         NOTIF_WRITE_HARDCODED(
1013                 "show_location_string", "",
1014                 "Replacement string piped into sprintf, "
1015                 "so you can do different messages like this: ' at the %s' or ' (near %s)'"
1016         );
1017         
1018         NOTIF_WRITE_HARDCODED(
1019                 "show_sprees", "1",
1020                 "Print information about sprees in death/kill messages"
1021         );
1022         
1023         NOTIF_WRITE_HARDCODED(
1024                 "show_sprees_center", "1",
1025                 "Show spree information in MSG_CENTER messages... "
1026                 "0 = off, 1 = target (but only for first victim) and attacker"
1027         );
1028         
1029         NOTIF_WRITE_HARDCODED(
1030                 "show_sprees_center_specialonly", "1",
1031                 "Don't show spree information in MSG_CENTER messages if it isn't an achievement"
1032         );
1033         
1034         NOTIF_WRITE_HARDCODED(
1035                 "show_sprees_info", "3",
1036                 "Show spree information in MSG_INFO messages... "
1037                 "0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker"
1038         );
1039         
1040         NOTIF_WRITE_HARDCODED(
1041                 "show_sprees_info_newline", "1",
1042                 "Show attacker spree information for MSG_INFO messages on a separate line than the death notification itself"
1043         );
1044         
1045         NOTIF_WRITE_HARDCODED(
1046                 "show_sprees_info_specialonly", "1",
1047                 "Don't show attacker spree information in MSG_INFO messages if it isn't an achievement"
1048         );
1049
1050         NOTIF_WRITE(sprintf(
1051                 strcat(
1052                         "\n// Notification counts (total = %d): ",
1053                         "MSG_ANNCE = %d, MSG_INFO = %d, MSG_CENTER = %d, MSG_MULTI = %d, MSG_CHOICE = %d\n"
1054                 ),
1055                 (
1056                         NOTIF_ANNCE_COUNT +
1057                         NOTIF_INFO_COUNT +
1058                         NOTIF_CENTER_COUNT +
1059                         NOTIF_MULTI_COUNT +
1060                         NOTIF_CHOICE_COUNT
1061                 ),
1062                 NOTIF_ANNCE_COUNT,
1063                 NOTIF_INFO_COUNT,
1064                 NOTIF_CENTER_COUNT,
1065                 NOTIF_MULTI_COUNT,
1066                 NOTIF_CHOICE_COUNT
1067         ));
1068         
1069         return;
1070         #undef NOTIF_WRITE_HARDCODED
1071         #undef NOTIF_WRITE_ENTITY
1072         #undef NOTIF_WRITE
1073 }
1074
1075 #ifdef SVQC
1076 void Notification_GetCvars()
1077 {
1078         GetCvars_handleFloat(get_cvars_s, get_cvars_f, FRAG_VERBOSE, "notification_frag_verbose");
1079 }
1080 #endif
1081
1082
1083 // ===============================
1084 //  Frontend Notification Pushing
1085 // ===============================
1086
1087 #ifdef NOTIFICATIONS_DEBUG
1088 void Debug_Notification(string input)
1089 {
1090         switch(autocvar_notification_debug)
1091         {
1092                 case 1: { dprint(input); break; }
1093                 case 2: { print(input); break; }
1094         }
1095 }
1096 #endif
1097
1098 string Local_Notification_sprintf(
1099         string input, string args, 
1100         string s1, string s2, string s3, string s4,
1101         float f1, float f2, float f3, float f4)
1102 {
1103         #ifdef NOTIFICATIONS_DEBUG
1104         Debug_Notification(sprintf(
1105                 "Local_Notification_sprintf('%s^7', '%s', %s, %s);\n",
1106                 MakeConsoleSafe(input),
1107                 args,
1108                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1109                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1110         ));
1111         #endif
1112         
1113         string selected;
1114         float sel_num;
1115         for(sel_num = 0; sel_num < NOTIF_MAX_ARGS; ++sel_num) { arg_slot[sel_num] = ""; }
1116
1117         string tmp_s;
1118
1119         for(sel_num = 0;(args != "");)
1120         {
1121                 selected = car(args); args = cdr(args);
1122                 NOTIF_HIT_MAX(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
1123                 switch(strtolower(selected))
1124                 {
1125                         #define ARG_CASE(prog,selected,result) \
1126                                 #ifdef CSQC \
1127                                         #if (prog != ARG_SV) && (prog != ARG_DC) \
1128                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1129                                         #endif \
1130                                 #else \
1131                                         #if (prog != ARG_CS) && (prog != ARG_DC) \
1132                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1133                                         #endif \
1134                                 #endif
1135                         NOTIF_ARGUMENT_LIST
1136                         #undef ARG_CASE
1137                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
1138                 }
1139         }
1140         return sprintf(strcat(input, "\n"), arg_slot[0], arg_slot[1], arg_slot[2], arg_slot[3], arg_slot[4], arg_slot[5], arg_slot[6]);
1141 }
1142
1143 #ifdef CSQC
1144 void Local_Notification_sound(
1145         float soundchannel, string soundfile,
1146         float soundvolume, float soundposition)
1147 {
1148         if((soundfile != prev_soundfile) || (time >= (prev_soundtime + autocvar_cl_announcer_antispam)))
1149         {
1150                 #ifdef NOTIFICATIONS_DEBUG
1151                 Debug_Notification(sprintf(
1152                         "Local_Notification_sound(world, %f, '%s', %f, %f);\n",
1153                         soundchannel,
1154                         sprintf(
1155                                 "announcer/%s/%s.wav",
1156                                 autocvar_cl_announcer,
1157                                 soundfile
1158                         ),
1159                         soundvolume,
1160                         soundposition
1161                 ));
1162                 #endif
1163                 
1164                 sound(
1165                         world,
1166                         soundchannel,
1167                         sprintf(
1168                                 "announcer/%s/%s.wav",
1169                                 autocvar_cl_announcer,
1170                                 soundfile
1171                         ),
1172                         soundvolume,
1173                         soundposition
1174                 );
1175                 
1176                 if(prev_soundfile) { strunzone(prev_soundfile); }
1177                 prev_soundfile = strzone(soundfile);
1178                 prev_soundtime = time;
1179         }
1180         else
1181         {
1182                 #ifdef NOTIFICATIONS_DEBUG
1183                 Debug_Notification(sprintf(
1184                         strcat(
1185                                 "Local_Notification_sound(world, %f, '%s', %f, %f) ",
1186                                 "^1BLOCKED BY ANTISPAM:^7 prevsnd: '%s', timediff: %f, limit: %f\n"
1187                          ),
1188                         soundchannel,
1189                         sprintf(
1190                                 "announcer/%s/%s.wav",
1191                                 autocvar_cl_announcer,
1192                                 soundfile
1193                         ),
1194                         soundvolume,
1195                         soundposition,
1196                         prev_soundfile,
1197                         (time - prev_soundtime),
1198                         autocvar_cl_announcer_antispam
1199                 ));
1200                 #endif
1201         }
1202 }
1203
1204 void Local_Notification_HUD_Notify_Push(
1205         string icon, string hudargs,
1206         string s1, string s2, string s3, string s4)
1207 {
1208         string selected;
1209         float sel_num;
1210         arg_slot[0] = ""; arg_slot[1] = "";
1211
1212         for(sel_num = 0;(hudargs != "");)
1213         {
1214                 selected = car(hudargs); hudargs = cdr(hudargs);
1215                 NOTIF_HIT_MAX(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
1216                 switch(strtolower(selected))
1217                 {
1218                         #define ARG_CASE(prog,selected,result) \
1219                                 #if (prog == ARG_CS_SV_HA) \
1220                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1221                                 #endif
1222                         NOTIF_ARGUMENT_LIST
1223                         #undef ARG_CASE
1224                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
1225                 }
1226         }
1227         #ifdef NOTIFICATIONS_DEBUG
1228         Debug_Notification(sprintf(
1229                 "Local_Notification_HUD_Notify_Push('%s^7', '%s', %s, %s);\n",
1230                 icon,
1231                 hudargs,
1232                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1233                 MakeConsoleSafe(sprintf("'%s^7', '%s^7'", stof(arg_slot[0]), stof(arg_slot[1])))
1234         ));
1235         #endif
1236         HUD_Notify_Push(icon, arg_slot[0], arg_slot[1]);
1237 }
1238
1239 void Local_Notification_centerprint_generic(
1240         string input, string durcnt,
1241         float cpid, float f1, float f2)
1242 {
1243         string selected;
1244         float sel_num;
1245         arg_slot[0] = ""; arg_slot[1] = "";
1246
1247         for(sel_num = 0;(durcnt != "");)
1248         {
1249                 selected = car(durcnt); durcnt = cdr(durcnt);
1250                 NOTIF_HIT_MAX(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic")
1251                 switch(strtolower(selected))
1252                 {
1253                         #define ARG_CASE(prog,selected,result) \
1254                                 #if (prog == ARG_CS_SV_DC) || (prog == ARG_DC) \
1255                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1256                                 #endif
1257                         NOTIF_ARGUMENT_LIST
1258                         #undef ARG_CASE
1259                         default:
1260                         {
1261                                 if(ftos(stof(selected)) != "") { arg_slot[sel_num] = selected; ++sel_num; }
1262                                 else { NOTIF_HIT_UNKNOWN(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic") }
1263                                 break;
1264                         }
1265                 }
1266         }
1267         #ifdef NOTIFICATIONS_DEBUG
1268         Debug_Notification(sprintf(
1269                 "Local_Notification_centerprint_generic('%s^7', '%s', %d, %d, %d, %d);\n",
1270                 MakeConsoleSafe(input),
1271                 durcnt,
1272                 f1, f2,
1273                 stof(arg_slot[0]), stof(arg_slot[1])
1274         ));
1275         #endif
1276         centerprint_generic(cpid, input, stof(arg_slot[0]), stof(arg_slot[1]));
1277 }
1278 #endif
1279
1280 void Local_Notification(float net_type, float net_name, ...count)
1281 {
1282         // check supplied type and name for errors
1283         string checkargs = Notification_CheckArgs_TypeName(net_type, net_name);
1284         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Local_Notification: %s\n", checkargs)); return; }
1285
1286         entity notif = Get_Notif_Ent(net_type, net_name);
1287         if not(notif) { backtrace("Local_Notification: Could not find notification entity!\n"); return; }
1288         if not(notif.nent_enabled)
1289         {
1290                 #ifdef NOTIFICATIONS_DEBUG
1291                 Debug_Notification(sprintf(
1292                         "Local_Notification(%s, %s): Entity was disabled...\n",
1293                         Get_Notif_TypeName(net_type),
1294                         notif.nent_name
1295                 ));
1296                 #endif
1297                 return;
1298         }
1299         
1300         if((notif.nent_stringcount + notif.nent_floatcount) > count)
1301         {
1302                 backtrace(sprintf(
1303                         strcat(
1304                                 "Not enough arguments for Local_Notification(%s, %s, ...)! ",
1305                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
1306                                 "Check the definition and function call for accuracy...?\n"
1307                         ),
1308                         Get_Notif_TypeName(net_type), notif.nent_name,
1309                         notif.nent_stringcount, notif.nent_floatcount, count
1310                 ));
1311                 return;
1312         }
1313         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
1314         {
1315                 backtrace(sprintf(
1316                         strcat(
1317                                 "Too many arguments for Local_Notification(%s, %s, ...)! ",
1318                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
1319                                 "Check the definition and function call for accuracy...?\n"
1320                         ),
1321                         Get_Notif_TypeName(net_type), notif.nent_name,
1322                         notif.nent_stringcount, notif.nent_floatcount, count
1323                 ));
1324                 return;
1325         }
1326
1327         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
1328         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
1329         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
1330         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
1331         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
1332         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
1333         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
1334         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
1335
1336         #ifdef NOTIFICATIONS_DEBUG
1337         Debug_Notification(sprintf(
1338                 "Local_Notification(%s, %s, %s, %s);\n",
1339                 Get_Notif_TypeName(net_type),
1340                 notif.nent_name,
1341                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1342                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1343         ));
1344         #endif
1345         
1346         switch(net_type)
1347         {
1348                 case MSG_ANNCE:
1349                 {
1350                         #ifdef CSQC
1351                         Local_Notification_sound(
1352                                 notif.nent_channel,
1353                                 notif.nent_snd,
1354                                 notif.nent_vol,
1355                                 notif.nent_position
1356                         );
1357                         #else
1358                         backtrace("MSG_ANNCE on server?... Please notify Samual immediately!\n");
1359                         #endif
1360                         break;
1361                 }
1362                 
1363                 case MSG_INFO:
1364                 {
1365                         print(
1366                                 Local_Notification_sprintf(
1367                                         notif.nent_string,
1368                                         notif.nent_args, 
1369                                         s1, s2, s3, s4,
1370                                         f1, f2, f3, f4)
1371                         );
1372                         #ifdef CSQC 
1373                         if(notif.nent_icon != "")
1374                         {
1375                                 Local_Notification_HUD_Notify_Push(
1376                                         notif.nent_icon,
1377                                         notif.nent_hudargs,
1378                                         s1, s2, s3, s4);
1379                         } 
1380                         #endif 
1381                         break;
1382                 }
1383                 
1384                 #ifdef CSQC
1385                 case MSG_CENTER:
1386                 {
1387                         Local_Notification_centerprint_generic(
1388                                 Local_Notification_sprintf(
1389                                         notif.nent_string,
1390                                         notif.nent_args, 
1391                                         s1, s2, s3, s4,
1392                                         f1, f2, f3, f4),
1393                                 notif.nent_durcnt,
1394                                 notif.nent_cpid,
1395                                 f1, f2);
1396                         break;
1397                 }
1398                 #endif
1399                 
1400                 case MSG_MULTI:
1401                 {
1402                         if(notif.nent_msginfo)
1403                         if(notif.nent_msginfo.nent_enabled)
1404                         {
1405                                 Local_Notification_WOVA(
1406                                         MSG_INFO,
1407                                         notif.nent_msginfo.nent_id, 
1408                                         notif.nent_msginfo.nent_stringcount, 
1409                                         notif.nent_msginfo.nent_floatcount, 
1410                                         s1, s2, s3, s4,
1411                                         f1, f2, f3, f4);
1412                         }
1413                         #ifdef CSQC
1414                         if(notif.nent_msgannce)
1415                         if(notif.nent_msgannce.nent_enabled)
1416                         {
1417                                 Local_Notification_WOVA(
1418                                         MSG_ANNCE,
1419                                         notif.nent_msgannce.nent_id, 
1420                                         0, 0, 
1421                                         "", "", "", "",
1422                                         0, 0, 0, 0);
1423                         }
1424                         if(notif.nent_msgcenter)
1425                         if(notif.nent_msgcenter.nent_enabled)
1426                         {
1427                                 Local_Notification_WOVA(
1428                                         MSG_CENTER,
1429                                         notif.nent_msgcenter.nent_id, 
1430                                         notif.nent_msgcenter.nent_stringcount, 
1431                                         notif.nent_msgcenter.nent_floatcount, 
1432                                         s1, s2, s3, s4,
1433                                         f1, f2, f3, f4); 
1434                         }
1435                         #endif
1436                         break;
1437                 }
1438
1439                 case MSG_CHOICE:
1440                 {
1441                         entity found_choice; 
1442                         switch(cvar_string(sprintf("notification_%s", notif.nent_name)))
1443                         {
1444                                 case 1: found_choice = notif.nent_optiona; break;
1445                                 case 2: found_choice = notif.nent_optionb; break;
1446                                 default: return; // not enabled anyway
1447                         }
1448                         Local_Notification_WOVA(
1449                                 found_choice.nent_type,
1450                                 found_choice.nent_id, 
1451                                 found_choice.nent_stringcount, 
1452                                 found_choice.nent_floatcount, 
1453                                 s1, s2, s3, s4,
1454                                 f1, f2, f3, f4); 
1455                 }
1456         }
1457 }
1458
1459 // WOVA = Without Variable Arguments 
1460 void Local_Notification_WOVA(
1461         float net_type, float net_name,
1462         float stringcount, float floatcount,
1463         string s1, string s2, string s3, string s4,
1464         float f1, float f2, float f3, float f4)
1465 {
1466         #define VARITEM(stringc,floatc,args) \
1467                 if((stringcount == stringc) && (floatcount == floatc)) \
1468                         { Local_Notification(net_type, net_name, args); return; }
1469         EIGHT_VARS_TO_VARARGS_VARLIST
1470         #undef VARITEM
1471         Local_Notification(net_type, net_name); // some notifications don't have any arguments at all
1472 }
1473
1474
1475 // =========================
1476 //  Notification Networking
1477 // =========================
1478
1479 #ifdef CSQC
1480 void Read_Notification(float is_new)
1481 {
1482         float net_type = ReadByte();
1483         float net_name = ReadShort();
1484
1485         entity notif;
1486
1487         if(net_type == MSG_CENTER_CPID)
1488         {
1489                 #ifdef NOTIFICATIONS_DEBUG
1490                 Debug_Notification(sprintf(
1491                         "Read_Notification(%d) at %f: net_type = %s, net_name = %d\n",
1492                         is_new,
1493                         time,
1494                         Get_Notif_TypeName(net_type),
1495                         net_name
1496                 ));
1497                 #endif
1498                 
1499                 if(is_new)
1500                 {
1501                         if(net_name == 0) { reset_centerprint_messages(); }
1502                         else if(net_name != NO_CPID)
1503                         {
1504                                 // in this case, net_name IS the cpid we want to kill
1505                                 centerprint_generic(net_name, "", 0, 0);
1506                         }
1507                         else
1508                         {
1509                                 backtrace(sprintf(
1510                                         "Read_Notification(%d) at %f: ^1TRIED TO KILL NO_CPID CENTERPRINT!\n",
1511                                         is_new,
1512                                         time
1513                                 ));
1514                         } 
1515                 }
1516         }
1517         else
1518         {
1519                 notif = Get_Notif_Ent(net_type, net_name);
1520                 if not(notif) { backtrace("Read_Notification: Could not find notification entity!\n"); return; }
1521
1522                 #ifdef NOTIFICATIONS_DEBUG
1523                 Debug_Notification(sprintf(
1524                         "Read_Notification(%d) at %f: net_type = %s, net_name = %s\n",
1525                         is_new,
1526                         time,
1527                         Get_Notif_TypeName(net_type),
1528                         notif.nent_name
1529                 ));
1530                 #endif
1531
1532                 string s1 = ((0 < notif.nent_stringcount) ? ReadString() : "");
1533                 string s2 = ((1 < notif.nent_stringcount) ? ReadString() : "");
1534                 string s3 = ((2 < notif.nent_stringcount) ? ReadString() : "");
1535                 string s4 = ((3 < notif.nent_stringcount) ? ReadString() : "");
1536                 float f1 = ((0 < notif.nent_floatcount) ? ReadLong() : 0);
1537                 float f2 = ((1 < notif.nent_floatcount) ? ReadLong() : 0);
1538                 float f3 = ((2 < notif.nent_floatcount) ? ReadLong() : 0);
1539                 float f4 = ((3 < notif.nent_floatcount) ? ReadLong() : 0);
1540         
1541                 if(is_new)
1542                 {
1543                         Local_Notification_WOVA(
1544                                 net_type, net_name,
1545                                 notif.nent_stringcount,
1546                                 notif.nent_floatcount,
1547                                 s1, s2, s3, s4,
1548                                 f1, f2, f3, f4);
1549                 }
1550         }
1551 }
1552 #endif
1553
1554 #ifdef SVQC
1555 void Net_Notification_Remove()
1556 {
1557         if not(self) { backtrace(sprintf("Net_Notification_Remove() at %f: Missing self!?\n", time)); return; }
1558         
1559         #ifdef NOTIFICATIONS_DEBUG
1560         Debug_Notification(sprintf(
1561                 "Net_Notification_Remove() at %f: %s '%s - %s' notification\n",
1562                 time,
1563                 ((self.nent_net_name == -1) ? "Killed" : "Removed"),
1564                 Get_Notif_TypeName(self.nent_net_type),
1565                 self.owner.nent_name
1566         ));
1567         #endif
1568         
1569         float i;
1570         for(i = 0; i < 4; ++i) { if(self.nent_strings[i]) { strunzone(self.nent_strings[i]); } }
1571         remove(self);
1572 }
1573
1574 float Net_Write_Notification(entity client, float sf)
1575 {
1576         if(Notification_ShouldSend(self.nent_broadcast, client, self.nent_client))
1577         {
1578                 float i;
1579                 WriteByte(MSG_ENTITY, ENT_CLIENT_NOTIFICATION);
1580                 WriteByte(MSG_ENTITY, self.nent_net_type);
1581                 WriteShort(MSG_ENTITY, self.nent_net_name);
1582                 for(i = 0; i < self.nent_stringcount; ++i) { WriteString(MSG_ENTITY, self.nent_strings[i]); } 
1583                 for(i = 0; i < self.nent_floatcount; ++i) { WriteLong(MSG_ENTITY, self.nent_floats[i]); }
1584                 return TRUE;
1585         }
1586         else { return FALSE; }
1587 }
1588
1589 void Kill_Notification(
1590         float broadcast, entity client,
1591         float net_type, float net_name)
1592 {
1593         string checkargs = Notification_CheckArgs(broadcast, client, 1, 1);
1594         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Kill_Notification: %s\n", checkargs)); return; }
1595
1596         #ifdef NOTIFICATIONS_DEBUG
1597         Debug_Notification(sprintf(
1598                 "Kill_Notification(%d, '%s', %s, %d);\n",
1599                 broadcast,
1600                 client.netname,
1601                 (net_type ? Get_Notif_TypeName(net_type) : "0"),
1602                 net_name
1603         ));
1604         #endif
1605
1606         entity notif, net_notif;
1607         float killed_cpid = NO_CPID;
1608         
1609         switch(net_type)
1610         {
1611                 case 0:
1612                 {
1613                         killed_cpid = 0; // kill ALL centerprints
1614                         break;
1615                 }
1616                 
1617                 case MSG_CENTER:
1618                 {
1619                         if(net_name)
1620                         {
1621                                 entity notif = Get_Notif_Ent(net_type, net_name);
1622                                 if not(notif) { backtrace("Kill_Notification: Could not find notification entity!\n"); return; }
1623                                 
1624                                 if(notif.nent_cpid)
1625                                         killed_cpid = notif.nent_cpid;
1626                                 else
1627                                         killed_cpid = NO_CPID;
1628                         }
1629                         else
1630                         {
1631                                 killed_cpid = 0; // kill ALL centerprints
1632                         }
1633                         break;
1634                 }
1635
1636                 case MSG_CENTER_CPID:
1637                 {
1638                         killed_cpid = net_name;
1639                         break;
1640                 }
1641         }
1642
1643         if(killed_cpid != NO_CPID)
1644         {
1645                 net_notif = spawn();
1646                 net_notif.classname = "net_kill_notification";
1647                 net_notif.nent_broadcast = broadcast;
1648                 net_notif.nent_client = client;
1649                 net_notif.nent_net_type = MSG_CENTER_CPID;
1650                 net_notif.nent_net_name = killed_cpid;
1651                 Net_LinkEntity(net_notif, FALSE, autocvar_notification_lifetime_runtime, Net_Write_Notification);
1652         }
1653
1654         for(notif = world; (notif = find(notif, classname, "net_notification"));)
1655         {
1656                 if(net_type)
1657                 {
1658                         if((killed_cpid != NO_CPID) && (notif.nent_net_type == MSG_CENTER))
1659                         {
1660                                 if(notif.owner.nent_cpid == killed_cpid)
1661                                 {
1662                                         notif.nent_net_name = -1;
1663                                         notif.nextthink = time;
1664                                 }
1665                                 else { continue; } // we ARE looking for a specific CPID, don't kill everything else too
1666                         }
1667                         else if(notif.nent_net_type == net_type)
1668                         {
1669                                 if(net_name)
1670                                 {
1671                                         if(notif.nent_net_name == net_name) { notif.nent_net_name = -1; notif.nextthink = time; }
1672                                         else { continue; } // we ARE looking for a certain net_name, don't kill everything else too
1673                                 }
1674                                 else { notif.nent_net_name = -1; notif.nextthink = time; }
1675                         }
1676                         else { continue; } // we ARE looking for a certain net_type, don't kill everything else too
1677                 }
1678                 else { notif.nent_net_name = -1; notif.nextthink = time; }
1679         }
1680 }
1681
1682 void Send_Notification(
1683         float broadcast, entity client,
1684         float net_type, float net_name,
1685         ...count)
1686 {
1687         // check supplied broadcast, target, type, and name for errors
1688         string checkargs = Notification_CheckArgs(broadcast, client, net_type, net_name);
1689         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Send_Notification: %s\n", checkargs)); return; }
1690
1691         // retreive counts for the arguments of this notification
1692         entity notif = Get_Notif_Ent(net_type, net_name);
1693         if not(notif) { backtrace("Send_Notification: Could not find notification entity!\n"); return; }
1694
1695         if((notif.nent_stringcount + notif.nent_floatcount) > count)
1696         {
1697                 backtrace(sprintf(
1698                         strcat(
1699                                 "Not enough arguments for Send_Notification(%d, %s, %s, ...)! ",
1700                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
1701                                 "Check the definition and function call for accuracy...?\n"
1702                         ),
1703                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name,
1704                         notif.nent_stringcount, notif.nent_floatcount, count
1705                 ));
1706                 return;
1707         }
1708         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
1709         {
1710                 backtrace(sprintf(
1711                         strcat(
1712                                 "Too many arguments for Send_Notification(%d, %s, %s, ...)! ",
1713                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
1714                                 "Check the definition and function call for accuracy...?\n"
1715                         ),
1716                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name,
1717                         notif.nent_stringcount, notif.nent_floatcount, count
1718                 ));
1719                 return;
1720         }
1721
1722         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
1723         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
1724         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
1725         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
1726         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
1727         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
1728         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
1729         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
1730
1731         #ifdef NOTIFICATIONS_DEBUG
1732         Debug_Notification(sprintf(
1733                 "Send_Notification(%d, %s, %s, %s, %s);\n",
1734                 broadcast,
1735                 Get_Notif_TypeName(net_type),
1736                 notif.nent_name,
1737                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1738                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1739         ));
1740         #endif
1741
1742         if(
1743                 server_is_dedicated
1744                 &&
1745                 (
1746                         broadcast == NOTIF_ALL
1747                         ||
1748                         broadcast == NOTIF_ALL_EXCEPT
1749                 )
1750                 &&
1751                 !(
1752                         net_type == MSG_ANNCE
1753                         ||
1754                         net_type == MSG_CENTER
1755                 )
1756         )
1757         {
1758                 Local_Notification_WOVA(
1759                         net_type, net_name,
1760                         notif.nent_stringcount,
1761                         notif.nent_floatcount,
1762                         s1, s2, s3, s4,
1763                         f1, f2, f3, f4);
1764         }
1765
1766         if(net_type == MSG_CHOICE)
1767         {
1768                 // THIS GETS TRICKY... now we have to cycle through each possible player (checking broadcast)
1769                 // and then do an individual NOTIF_ONE_ONLY recursive call for each one depending on their option...
1770                 // It's slow, but it's better than the alternatives:
1771                 //   1. Constantly networking all info and letting client decide
1772                 //   2. Manually handling each separate call on per-usage basis (See old CTF usage of verbose)
1773                 entity found_choice; 
1774
1775                 #define RECURSE_FROM_CHOICE(ent,action) \
1776                         switch(ent.msg_choice_choices[net_name]) \
1777                         { \
1778                                 case 1: found_choice = notif.nent_optiona; break; \
1779                                 case 2: found_choice = notif.nent_optionb; break; \
1780                                 default: action; \
1781                         } \
1782                         Send_Notification_WOVA( \
1783                                 NOTIF_ONE_ONLY, \
1784                                 ent, \
1785                                 found_choice.nent_type, \
1786                                 found_choice.nent_id, \
1787                                 s1, s2, s3, s4, \
1788                                 f1, f2, f3, f4);
1789
1790                 switch(broadcast)
1791                 {
1792                         case NOTIF_ONE_ONLY: // we can potentially save processing power with this broadcast method
1793                         {
1794                                 if(IS_REAL_CLIENT(client))
1795                                         { RECURSE_FROM_CHOICE(client, return) }
1796                                 break;
1797                         }
1798                         default:
1799                         {
1800                                 entity to;
1801                                 FOR_EACH_REALCLIENT(to)
1802                                         { if(Notification_ShouldSend(broadcast, to, client))
1803                                                 { RECURSE_FROM_CHOICE(to, continue) } }
1804                                 break;
1805                         }
1806                 }
1807         }
1808         else
1809         {
1810                 entity net_notif = spawn();
1811                 net_notif.owner = notif;
1812                 net_notif.classname = "net_notification";
1813                 net_notif.nent_broadcast = broadcast;
1814                 net_notif.nent_client = client;
1815                 net_notif.nent_net_type = net_type;
1816                 net_notif.nent_net_name = net_name;
1817                 net_notif.nent_stringcount = notif.nent_stringcount;
1818                 net_notif.nent_floatcount = notif.nent_floatcount;
1819                 
1820                 float i;
1821                 for(i = 0; i < net_notif.nent_stringcount; ++i)
1822                         { net_notif.nent_strings[i] = strzone(...(i, string)); }
1823                 for(i = 0; i < net_notif.nent_floatcount; ++i)
1824                         { net_notif.nent_floats[i] = ...((net_notif.nent_stringcount + i), float); }
1825
1826                 net_notif.think = Net_Notification_Remove;
1827                 net_notif.nextthink =
1828                         ((time > autocvar_notification_lifetime_mapload)
1829                         ?
1830                                 (time + autocvar_notification_lifetime_runtime)
1831                                 :
1832                                 autocvar_notification_lifetime_mapload
1833                         ); 
1834
1835                 Net_LinkEntity(net_notif, FALSE, 0, Net_Write_Notification);
1836         }
1837 }
1838
1839 // WOVA = Without Variable Arguments 
1840 void Send_Notification_WOVA(
1841         float broadcast, entity client,
1842         float net_type, float net_name,
1843         string s1, string s2, string s3, string s4,
1844         float f1, float f2, float f3, float f4)
1845 {
1846         entity notif = Get_Notif_Ent(net_type, net_name);
1847         
1848         #ifdef NOTIFICATIONS_DEBUG
1849         Debug_Notification(sprintf(
1850                 "Send_Notification_WOVA(%d, %s, %s, %s, %s);\n",
1851                 broadcast,
1852                 Get_Notif_TypeName(net_type),
1853                 notif.nent_name,
1854                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1855                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1856         ));
1857         #endif
1858         
1859         #define VARITEM(stringc,floatc,args) \
1860                 if((notif.nent_stringcount == stringc) && (notif.nent_floatcount == floatc)) \
1861                         { Send_Notification(broadcast, client, net_type, net_name, args); return; }
1862         EIGHT_VARS_TO_VARARGS_VARLIST
1863         #undef VARITEM
1864         Send_Notification(broadcast, client, net_type, net_name); // some notifications don't have any arguments at all
1865 }
1866 #endif // ifdef SVQC