]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
Now add some cvar handling and move frag messages to MSG_CHOICE system
[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
834 // ===============
835
836 // used by MSG_CHOICE to build list of choices
837 #ifdef SVQC
838 void Notification_GetCvars(void)
839 {
840         float i;
841         for(i = 0; i <= NOTIF_CHOICE_COUNT; ++i)
842         {
843                 GetCvars_handleFloat(
844                         get_cvars_s,
845                         get_cvars_f,
846                         msg_choice_choices[i],
847                         sprintf("notification_%s", msg_choice_notifs[i].nent_name)
848                 );
849         }
850
851         GetCvars_handleFloat(get_cvars_s, get_cvars_f, FRAG_VERBOSE, "notification_frag_verbose");
852 }
853 #endif
854
855 // used to output notifications.cfg file
856 void Dump_Notifications(float fh, float alsoprint)
857 {
858         #define NOTIF_WRITE(a) { \
859                 fputs(fh, a); \
860                 if(alsoprint) { print(a); } }
861         #define NOTIF_WRITE_ENTITY(description) { \
862                 notif_msg = \
863                         sprintf( \
864                                 "seta notification_%s \"%d\" \"%s\"\n", \
865                                 e.nent_name, e.nent_default, description \
866                         ); \
867                 NOTIF_WRITE(notif_msg) }
868         #define NOTIF_WRITE_HARDCODED(cvar,default,description) { \
869                 notif_msg = \
870                         sprintf( \
871                                 "seta notification_%s \"%s\" \"%s\"\n", \
872                                 cvar, default, description \
873                         ); \
874                 NOTIF_WRITE(notif_msg) }
875
876         string notif_msg;
877         float i;
878         entity e;
879
880         // Note: This warning only applies to the notifications.cfg file that is output...
881
882         // You ARE supposed to manually edit this function to add i.e. hard coded
883         // notification variables for mutators or game modes or such and then
884         // regenerate the notifications.cfg file from the new code.
885
886         NOTIF_WRITE("// ********************************************** //\n");
887         NOTIF_WRITE("// ** WARNING - DO NOT MANUALLY EDIT THIS FILE ** //\n");
888         NOTIF_WRITE("// **                                          ** //\n");
889         NOTIF_WRITE("// **  This file is automatically generated    ** //\n");
890         NOTIF_WRITE("// **  by code with the command 'dumpnotifs'.  ** //\n");
891         NOTIF_WRITE("// **                                          ** //\n");
892         NOTIF_WRITE("// **  If you add a new notification, please   ** //\n");
893         NOTIF_WRITE("// **  regenerate this file with that command  ** //\n");
894         NOTIF_WRITE("// **  making sure that the output matches     ** //\n");
895         NOTIF_WRITE("// **  with the lists and defaults in code.    ** //\n");
896         NOTIF_WRITE("// **                                          ** //\n");
897         NOTIF_WRITE("// ********************************************** //\n");
898
899         // These notifications will also append their string as a comment...
900         // This is not necessary, and does not matter if they vary between config versions,
901         // it is just a semi-helpful tool for those who want to manually change their user settings.
902
903         NOTIF_WRITE(sprintf("\n// MSG_ANNCE notifications (count = %d):\n", NOTIF_ANNCE_COUNT));
904         for(i = 1; i <= NOTIF_ANNCE_COUNT; ++i)
905         {
906                 e = Get_Notif_Ent(MSG_ANNCE, i);
907                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
908                 
909                 NOTIF_WRITE_ENTITY(
910                         "Notification control cvar: 0 = disabled, 1 = enabled if gentle mode is off, 2 = always enabled)"
911                 );
912         }
913
914         NOTIF_WRITE(sprintf("\n// MSG_INFO notifications (count = %d):\n", NOTIF_INFO_COUNT));
915         for(i = 1; i <= NOTIF_INFO_COUNT; ++i)
916         {
917                 e = Get_Notif_Ent(MSG_INFO, i);
918                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
919                 
920                 NOTIF_WRITE_ENTITY(
921                         "Notification control cvar: 0 = off, 1 = print to console, "
922                         "2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
923                 );
924         }
925
926         NOTIF_WRITE(sprintf("\n// MSG_CENTER notifications (count = %d):\n", NOTIF_CENTER_COUNT));
927         for(i = 1; i <= NOTIF_CENTER_COUNT; ++i)
928         {
929                 e = Get_Notif_Ent(MSG_CENTER, i);
930                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
931                 
932                 NOTIF_WRITE_ENTITY(
933                         "Notification control cvar: 0 = off, 1 = centerprint"
934                 );
935         }
936
937         NOTIF_WRITE(sprintf("\n// MSG_MULTI notifications (count = %d):\n", NOTIF_MULTI_COUNT));
938         for(i = 1; i <= NOTIF_MULTI_COUNT; ++i)
939         {
940                 e = Get_Notif_Ent(MSG_MULTI, i);
941                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
942                 
943                 NOTIF_WRITE_ENTITY(
944                         "Notification control cvar: 0 = off, 1 = trigger subcalls"
945                 );
946         }
947
948         NOTIF_WRITE(sprintf("\n// MSG_CHOICE notifications (count = %d):\n", NOTIF_CHOICE_COUNT));
949         for(i = 1; i <= NOTIF_CHOICE_COUNT; ++i)
950         {
951                 e = Get_Notif_Ent(MSG_CHOICE, i);
952                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
953                 
954                 NOTIF_WRITE_ENTITY(
955                         "Notification control cvar: 0 = off, 1 = trigger option A subcall, 2 = trigger option B subcall"
956                 );
957         }
958
959         // edit these to match whichever cvars are used for specific notification options
960         NOTIF_WRITE("\n// HARD CODED notification variables:\n");
961         
962         NOTIF_WRITE_HARDCODED(
963                 "allow_chatboxprint", "1",
964                 "Allow notifications to be printed to chat box by setting notification cvar to 2 "
965                 "(You can also set this cvar to 2 to force ALL notifications to be printed to the chatbox)"
966         );
967         
968         NOTIF_WRITE_HARDCODED(
969                 "ctf_capture_verbose", "0",
970                 "Show extra information when someone captures a flag"
971         );
972         
973         NOTIF_WRITE_HARDCODED(
974                 "ctf_pickup_enemy_verbose", "0",
975                 "Show extra information if an enemy picks up a flag"
976         );
977         
978         NOTIF_WRITE_HARDCODED(
979                 "ctf_pickup_team_verbose", "0",
980                 "Show extra information if a team mate picks up a flag"
981         );
982         
983         NOTIF_WRITE_HARDCODED(
984                 "debug", "0",
985                 "Print extra debug information on all notification function calls "
986                 "(Requires -DNOTIFICATIONS_DEBUG flag to be enabled on QCSRC compilation)... "
987                 "0 = disabled, 1 = dprint, 2 = print"
988         );
989         
990         NOTIF_WRITE_HARDCODED(
991                 "errors_are_fatal", "1",
992                 "If a notification fails upon initialization, cause a Host_Error to stop the program"
993         );
994         
995         NOTIF_WRITE_HARDCODED(
996                 "frag_verbose", "1",
997                 "Show extra information when you frag someone (or when you are fragged"
998         );
999         
1000         NOTIF_WRITE_HARDCODED(
1001                 "item_centerprinttime", "1.5",
1002                 "How long to show item information centerprint messages (like 'You got the Electro' or such)"
1003         );
1004         
1005         NOTIF_WRITE_HARDCODED(
1006                 "lifetime_mapload", "10",
1007                 "Amount of time that notification entities last immediately at mapload (in seconds) "
1008                 "to help prevent notifications from being lost on early init (like gamestart countdown)"
1009         );
1010         
1011         NOTIF_WRITE_HARDCODED(
1012                 "lifetime_runtime", "0.5",
1013                 "Amount of time that notification entities last on the server during runtime (In seconds)"
1014         );
1015         
1016         NOTIF_WRITE_HARDCODED(
1017                 "server_allows_frag_verbose", "1",
1018                 "Server side cvar for showing extra information in frag messages... 0 = no extra frag information, "
1019                 "1 = frag information only in warmup, 2 = frag information allowed all the time"
1020         );
1021         
1022         NOTIF_WRITE_HARDCODED(
1023                 "server_allows_location", "1",
1024                 "Server side cvar for allowing death messages to show location information too"
1025         );
1026         
1027         NOTIF_WRITE_HARDCODED(
1028                 "show_location", "0",
1029                 "Append location information to MSG_INFO death/kill messages"
1030         );
1031         
1032         NOTIF_WRITE_HARDCODED(
1033                 "show_location_string", "",
1034                 "Replacement string piped into sprintf, "
1035                 "so you can do different messages like this: ' at the %s' or ' (near %s)'"
1036         );
1037         
1038         NOTIF_WRITE_HARDCODED(
1039                 "show_sprees", "1",
1040                 "Print information about sprees in death/kill messages"
1041         );
1042         
1043         NOTIF_WRITE_HARDCODED(
1044                 "show_sprees_center", "1",
1045                 "Show spree information in MSG_CENTER messages... "
1046                 "0 = off, 1 = target (but only for first victim) and attacker"
1047         );
1048         
1049         NOTIF_WRITE_HARDCODED(
1050                 "show_sprees_center_specialonly", "1",
1051                 "Don't show spree information in MSG_CENTER messages if it isn't an achievement"
1052         );
1053         
1054         NOTIF_WRITE_HARDCODED(
1055                 "show_sprees_info", "3",
1056                 "Show spree information in MSG_INFO messages... "
1057                 "0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker"
1058         );
1059         
1060         NOTIF_WRITE_HARDCODED(
1061                 "show_sprees_info_newline", "1",
1062                 "Show attacker spree information for MSG_INFO messages on a separate line than the death notification itself"
1063         );
1064         
1065         NOTIF_WRITE_HARDCODED(
1066                 "show_sprees_info_specialonly", "1",
1067                 "Don't show attacker spree information in MSG_INFO messages if it isn't an achievement"
1068         );
1069
1070         NOTIF_WRITE(sprintf(
1071                 strcat(
1072                         "\n// Notification counts (total = %d): ",
1073                         "MSG_ANNCE = %d, MSG_INFO = %d, MSG_CENTER = %d, MSG_MULTI = %d, MSG_CHOICE = %d\n"
1074                 ),
1075                 (
1076                         NOTIF_ANNCE_COUNT +
1077                         NOTIF_INFO_COUNT +
1078                         NOTIF_CENTER_COUNT +
1079                         NOTIF_MULTI_COUNT +
1080                         NOTIF_CHOICE_COUNT
1081                 ),
1082                 NOTIF_ANNCE_COUNT,
1083                 NOTIF_INFO_COUNT,
1084                 NOTIF_CENTER_COUNT,
1085                 NOTIF_MULTI_COUNT,
1086                 NOTIF_CHOICE_COUNT
1087         ));
1088         
1089         return;
1090         #undef NOTIF_WRITE_HARDCODED
1091         #undef NOTIF_WRITE_ENTITY
1092         #undef NOTIF_WRITE
1093 }
1094
1095
1096 // ===============================
1097 //  Frontend Notification Pushing
1098 // ===============================
1099
1100 #ifdef NOTIFICATIONS_DEBUG
1101 void Debug_Notification(string input)
1102 {
1103         switch(autocvar_notification_debug)
1104         {
1105                 case 1: { dprint(input); break; }
1106                 case 2: { print(input); break; }
1107         }
1108 }
1109 #endif
1110
1111 string Local_Notification_sprintf(
1112         string input, string args, 
1113         string s1, string s2, string s3, string s4,
1114         float f1, float f2, float f3, float f4)
1115 {
1116         #ifdef NOTIFICATIONS_DEBUG
1117         Debug_Notification(sprintf(
1118                 "Local_Notification_sprintf('%s^7', '%s', %s, %s);\n",
1119                 MakeConsoleSafe(input),
1120                 args,
1121                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1122                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1123         ));
1124         #endif
1125         
1126         string selected;
1127         float sel_num;
1128         for(sel_num = 0; sel_num < NOTIF_MAX_ARGS; ++sel_num) { arg_slot[sel_num] = ""; }
1129
1130         string tmp_s;
1131
1132         for(sel_num = 0;(args != "");)
1133         {
1134                 selected = car(args); args = cdr(args);
1135                 NOTIF_HIT_MAX(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
1136                 switch(strtolower(selected))
1137                 {
1138                         #define ARG_CASE(prog,selected,result) \
1139                                 #ifdef CSQC \
1140                                         #if (prog != ARG_SV) && (prog != ARG_DC) \
1141                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1142                                         #endif \
1143                                 #else \
1144                                         #if (prog != ARG_CS) && (prog != ARG_DC) \
1145                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1146                                         #endif \
1147                                 #endif
1148                         NOTIF_ARGUMENT_LIST
1149                         #undef ARG_CASE
1150                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
1151                 }
1152         }
1153         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]);
1154 }
1155
1156 #ifdef CSQC
1157 void Local_Notification_sound(
1158         float soundchannel, string soundfile,
1159         float soundvolume, float soundposition)
1160 {
1161         if((soundfile != prev_soundfile) || (time >= (prev_soundtime + autocvar_cl_announcer_antispam)))
1162         {
1163                 #ifdef NOTIFICATIONS_DEBUG
1164                 Debug_Notification(sprintf(
1165                         "Local_Notification_sound(world, %f, '%s', %f, %f);\n",
1166                         soundchannel,
1167                         sprintf(
1168                                 "announcer/%s/%s.wav",
1169                                 autocvar_cl_announcer,
1170                                 soundfile
1171                         ),
1172                         soundvolume,
1173                         soundposition
1174                 ));
1175                 #endif
1176                 
1177                 sound(
1178                         world,
1179                         soundchannel,
1180                         sprintf(
1181                                 "announcer/%s/%s.wav",
1182                                 autocvar_cl_announcer,
1183                                 soundfile
1184                         ),
1185                         soundvolume,
1186                         soundposition
1187                 );
1188                 
1189                 if(prev_soundfile) { strunzone(prev_soundfile); }
1190                 prev_soundfile = strzone(soundfile);
1191                 prev_soundtime = time;
1192         }
1193         else
1194         {
1195                 #ifdef NOTIFICATIONS_DEBUG
1196                 Debug_Notification(sprintf(
1197                         strcat(
1198                                 "Local_Notification_sound(world, %f, '%s', %f, %f) ",
1199                                 "^1BLOCKED BY ANTISPAM:^7 prevsnd: '%s', timediff: %f, limit: %f\n"
1200                          ),
1201                         soundchannel,
1202                         sprintf(
1203                                 "announcer/%s/%s.wav",
1204                                 autocvar_cl_announcer,
1205                                 soundfile
1206                         ),
1207                         soundvolume,
1208                         soundposition,
1209                         prev_soundfile,
1210                         (time - prev_soundtime),
1211                         autocvar_cl_announcer_antispam
1212                 ));
1213                 #endif
1214         }
1215 }
1216
1217 void Local_Notification_HUD_Notify_Push(
1218         string icon, string hudargs,
1219         string s1, string s2, string s3, string s4)
1220 {
1221         string selected;
1222         float sel_num;
1223         arg_slot[0] = ""; arg_slot[1] = "";
1224
1225         for(sel_num = 0;(hudargs != "");)
1226         {
1227                 selected = car(hudargs); hudargs = cdr(hudargs);
1228                 NOTIF_HIT_MAX(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
1229                 switch(strtolower(selected))
1230                 {
1231                         #define ARG_CASE(prog,selected,result) \
1232                                 #if (prog == ARG_CS_SV_HA) \
1233                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1234                                 #endif
1235                         NOTIF_ARGUMENT_LIST
1236                         #undef ARG_CASE
1237                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
1238                 }
1239         }
1240         #ifdef NOTIFICATIONS_DEBUG
1241         Debug_Notification(sprintf(
1242                 "Local_Notification_HUD_Notify_Push('%s^7', '%s', %s, %s);\n",
1243                 icon,
1244                 hudargs,
1245                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1246                 MakeConsoleSafe(sprintf("'%s^7', '%s^7'", stof(arg_slot[0]), stof(arg_slot[1])))
1247         ));
1248         #endif
1249         HUD_Notify_Push(icon, arg_slot[0], arg_slot[1]);
1250 }
1251
1252 void Local_Notification_centerprint_generic(
1253         string input, string durcnt,
1254         float cpid, float f1, float f2)
1255 {
1256         string selected;
1257         float sel_num;
1258         arg_slot[0] = ""; arg_slot[1] = "";
1259
1260         for(sel_num = 0;(durcnt != "");)
1261         {
1262                 selected = car(durcnt); durcnt = cdr(durcnt);
1263                 NOTIF_HIT_MAX(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic")
1264                 switch(strtolower(selected))
1265                 {
1266                         #define ARG_CASE(prog,selected,result) \
1267                                 #if (prog == ARG_CS_SV_DC) || (prog == ARG_DC) \
1268                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
1269                                 #endif
1270                         NOTIF_ARGUMENT_LIST
1271                         #undef ARG_CASE
1272                         default:
1273                         {
1274                                 if(ftos(stof(selected)) != "") { arg_slot[sel_num] = selected; ++sel_num; }
1275                                 else { NOTIF_HIT_UNKNOWN(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic") }
1276                                 break;
1277                         }
1278                 }
1279         }
1280         #ifdef NOTIFICATIONS_DEBUG
1281         Debug_Notification(sprintf(
1282                 "Local_Notification_centerprint_generic('%s^7', '%s', %d, %d, %d, %d);\n",
1283                 MakeConsoleSafe(input),
1284                 durcnt,
1285                 f1, f2,
1286                 stof(arg_slot[0]), stof(arg_slot[1])
1287         ));
1288         #endif
1289         centerprint_generic(cpid, input, stof(arg_slot[0]), stof(arg_slot[1]));
1290 }
1291 #endif
1292
1293 void Local_Notification(float net_type, float net_name, ...count)
1294 {
1295         // check supplied type and name for errors
1296         string checkargs = Notification_CheckArgs_TypeName(net_type, net_name);
1297         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Local_Notification: %s\n", checkargs)); return; }
1298
1299         entity notif = Get_Notif_Ent(net_type, net_name);
1300         if not(notif) { backtrace("Local_Notification: Could not find notification entity!\n"); return; }
1301         if not(notif.nent_enabled)
1302         {
1303                 #ifdef NOTIFICATIONS_DEBUG
1304                 Debug_Notification(sprintf(
1305                         "Local_Notification(%s, %s): Entity was disabled...\n",
1306                         Get_Notif_TypeName(net_type),
1307                         notif.nent_name
1308                 ));
1309                 #endif
1310                 return;
1311         }
1312         
1313         if((notif.nent_stringcount + notif.nent_floatcount) > count)
1314         {
1315                 backtrace(sprintf(
1316                         strcat(
1317                                 "Not enough 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         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
1327         {
1328                 backtrace(sprintf(
1329                         strcat(
1330                                 "Too many arguments for Local_Notification(%s, %s, ...)! ",
1331                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
1332                                 "Check the definition and function call for accuracy...?\n"
1333                         ),
1334                         Get_Notif_TypeName(net_type), notif.nent_name,
1335                         notif.nent_stringcount, notif.nent_floatcount, count
1336                 ));
1337                 return;
1338         }
1339
1340         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
1341         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
1342         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
1343         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
1344         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
1345         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
1346         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
1347         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
1348
1349         #ifdef NOTIFICATIONS_DEBUG
1350         Debug_Notification(sprintf(
1351                 "Local_Notification(%s, %s, %s, %s);\n",
1352                 Get_Notif_TypeName(net_type),
1353                 notif.nent_name,
1354                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1355                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1356         ));
1357         #endif
1358         
1359         switch(net_type)
1360         {
1361                 case MSG_ANNCE:
1362                 {
1363                         #ifdef CSQC
1364                         Local_Notification_sound(
1365                                 notif.nent_channel,
1366                                 notif.nent_snd,
1367                                 notif.nent_vol,
1368                                 notif.nent_position
1369                         );
1370                         #else
1371                         backtrace("MSG_ANNCE on server?... Please notify Samual immediately!\n");
1372                         #endif
1373                         break;
1374                 }
1375                 
1376                 case MSG_INFO:
1377                 {
1378                         print(
1379                                 Local_Notification_sprintf(
1380                                         notif.nent_string,
1381                                         notif.nent_args, 
1382                                         s1, s2, s3, s4,
1383                                         f1, f2, f3, f4)
1384                         );
1385                         #ifdef CSQC 
1386                         if(notif.nent_icon != "")
1387                         {
1388                                 Local_Notification_HUD_Notify_Push(
1389                                         notif.nent_icon,
1390                                         notif.nent_hudargs,
1391                                         s1, s2, s3, s4);
1392                         } 
1393                         #endif 
1394                         break;
1395                 }
1396                 
1397                 #ifdef CSQC
1398                 case MSG_CENTER:
1399                 {
1400                         Local_Notification_centerprint_generic(
1401                                 Local_Notification_sprintf(
1402                                         notif.nent_string,
1403                                         notif.nent_args, 
1404                                         s1, s2, s3, s4,
1405                                         f1, f2, f3, f4),
1406                                 notif.nent_durcnt,
1407                                 notif.nent_cpid,
1408                                 f1, f2);
1409                         break;
1410                 }
1411                 #endif
1412                 
1413                 case MSG_MULTI:
1414                 {
1415                         if(notif.nent_msginfo)
1416                         if(notif.nent_msginfo.nent_enabled)
1417                         {
1418                                 Local_Notification_WOVA(
1419                                         MSG_INFO,
1420                                         notif.nent_msginfo.nent_id, 
1421                                         notif.nent_msginfo.nent_stringcount, 
1422                                         notif.nent_msginfo.nent_floatcount, 
1423                                         s1, s2, s3, s4,
1424                                         f1, f2, f3, f4);
1425                         }
1426                         #ifdef CSQC
1427                         if(notif.nent_msgannce)
1428                         if(notif.nent_msgannce.nent_enabled)
1429                         {
1430                                 Local_Notification_WOVA(
1431                                         MSG_ANNCE,
1432                                         notif.nent_msgannce.nent_id, 
1433                                         0, 0, 
1434                                         "", "", "", "",
1435                                         0, 0, 0, 0);
1436                         }
1437                         if(notif.nent_msgcenter)
1438                         if(notif.nent_msgcenter.nent_enabled)
1439                         {
1440                                 Local_Notification_WOVA(
1441                                         MSG_CENTER,
1442                                         notif.nent_msgcenter.nent_id, 
1443                                         notif.nent_msgcenter.nent_stringcount, 
1444                                         notif.nent_msgcenter.nent_floatcount, 
1445                                         s1, s2, s3, s4,
1446                                         f1, f2, f3, f4); 
1447                         }
1448                         #endif
1449                         break;
1450                 }
1451
1452                 case MSG_CHOICE:
1453                 {
1454                         entity found_choice; 
1455                         switch(cvar_string(sprintf("notification_%s", notif.nent_name)))
1456                         {
1457                                 case 1: found_choice = notif.nent_optiona; break;
1458                                 case 2: found_choice = notif.nent_optionb; break;
1459                                 default: return; // not enabled anyway
1460                         }
1461                         Local_Notification_WOVA(
1462                                 found_choice.nent_type,
1463                                 found_choice.nent_id, 
1464                                 found_choice.nent_stringcount, 
1465                                 found_choice.nent_floatcount, 
1466                                 s1, s2, s3, s4,
1467                                 f1, f2, f3, f4); 
1468                 }
1469         }
1470 }
1471
1472 // WOVA = Without Variable Arguments 
1473 void Local_Notification_WOVA(
1474         float net_type, float net_name,
1475         float stringcount, float floatcount,
1476         string s1, string s2, string s3, string s4,
1477         float f1, float f2, float f3, float f4)
1478 {
1479         #define VARITEM(stringc,floatc,args) \
1480                 if((stringcount == stringc) && (floatcount == floatc)) \
1481                         { Local_Notification(net_type, net_name, args); return; }
1482         EIGHT_VARS_TO_VARARGS_VARLIST
1483         #undef VARITEM
1484         Local_Notification(net_type, net_name); // some notifications don't have any arguments at all
1485 }
1486
1487
1488 // =========================
1489 //  Notification Networking
1490 // =========================
1491
1492 #ifdef CSQC
1493 void Read_Notification(float is_new)
1494 {
1495         float net_type = ReadByte();
1496         float net_name = ReadShort();
1497
1498         entity notif;
1499
1500         if(net_type == MSG_CENTER_CPID)
1501         {
1502                 #ifdef NOTIFICATIONS_DEBUG
1503                 Debug_Notification(sprintf(
1504                         "Read_Notification(%d) at %f: net_type = %s, net_name = %d\n",
1505                         is_new,
1506                         time,
1507                         Get_Notif_TypeName(net_type),
1508                         net_name
1509                 ));
1510                 #endif
1511                 
1512                 if(is_new)
1513                 {
1514                         if(net_name == 0) { reset_centerprint_messages(); }
1515                         else if(net_name != NO_CPID)
1516                         {
1517                                 // in this case, net_name IS the cpid we want to kill
1518                                 centerprint_generic(net_name, "", 0, 0);
1519                         }
1520                         else
1521                         {
1522                                 backtrace(sprintf(
1523                                         "Read_Notification(%d) at %f: ^1TRIED TO KILL NO_CPID CENTERPRINT!\n",
1524                                         is_new,
1525                                         time
1526                                 ));
1527                         } 
1528                 }
1529         }
1530         else
1531         {
1532                 notif = Get_Notif_Ent(net_type, net_name);
1533                 if not(notif) { backtrace("Read_Notification: Could not find notification entity!\n"); return; }
1534
1535                 #ifdef NOTIFICATIONS_DEBUG
1536                 Debug_Notification(sprintf(
1537                         "Read_Notification(%d) at %f: net_type = %s, net_name = %s\n",
1538                         is_new,
1539                         time,
1540                         Get_Notif_TypeName(net_type),
1541                         notif.nent_name
1542                 ));
1543                 #endif
1544
1545                 string s1 = ((0 < notif.nent_stringcount) ? ReadString() : "");
1546                 string s2 = ((1 < notif.nent_stringcount) ? ReadString() : "");
1547                 string s3 = ((2 < notif.nent_stringcount) ? ReadString() : "");
1548                 string s4 = ((3 < notif.nent_stringcount) ? ReadString() : "");
1549                 float f1 = ((0 < notif.nent_floatcount) ? ReadLong() : 0);
1550                 float f2 = ((1 < notif.nent_floatcount) ? ReadLong() : 0);
1551                 float f3 = ((2 < notif.nent_floatcount) ? ReadLong() : 0);
1552                 float f4 = ((3 < notif.nent_floatcount) ? ReadLong() : 0);
1553         
1554                 if(is_new)
1555                 {
1556                         Local_Notification_WOVA(
1557                                 net_type, net_name,
1558                                 notif.nent_stringcount,
1559                                 notif.nent_floatcount,
1560                                 s1, s2, s3, s4,
1561                                 f1, f2, f3, f4);
1562                 }
1563         }
1564 }
1565 #endif
1566
1567 #ifdef SVQC
1568 void Net_Notification_Remove()
1569 {
1570         if not(self) { backtrace(sprintf("Net_Notification_Remove() at %f: Missing self!?\n", time)); return; }
1571         
1572         #ifdef NOTIFICATIONS_DEBUG
1573         Debug_Notification(sprintf(
1574                 "Net_Notification_Remove() at %f: %s '%s - %s' notification\n",
1575                 time,
1576                 ((self.nent_net_name == -1) ? "Killed" : "Removed"),
1577                 Get_Notif_TypeName(self.nent_net_type),
1578                 self.owner.nent_name
1579         ));
1580         #endif
1581         
1582         float i;
1583         for(i = 0; i < 4; ++i) { if(self.nent_strings[i]) { strunzone(self.nent_strings[i]); } }
1584         remove(self);
1585 }
1586
1587 float Net_Write_Notification(entity client, float sf)
1588 {
1589         if(Notification_ShouldSend(self.nent_broadcast, client, self.nent_client))
1590         {
1591                 float i;
1592                 WriteByte(MSG_ENTITY, ENT_CLIENT_NOTIFICATION);
1593                 WriteByte(MSG_ENTITY, self.nent_net_type);
1594                 WriteShort(MSG_ENTITY, self.nent_net_name);
1595                 for(i = 0; i < self.nent_stringcount; ++i) { WriteString(MSG_ENTITY, self.nent_strings[i]); } 
1596                 for(i = 0; i < self.nent_floatcount; ++i) { WriteLong(MSG_ENTITY, self.nent_floats[i]); }
1597                 return TRUE;
1598         }
1599         else { return FALSE; }
1600 }
1601
1602 void Kill_Notification(
1603         float broadcast, entity client,
1604         float net_type, float net_name)
1605 {
1606         string checkargs = Notification_CheckArgs(broadcast, client, 1, 1);
1607         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Kill_Notification: %s\n", checkargs)); return; }
1608
1609         #ifdef NOTIFICATIONS_DEBUG
1610         Debug_Notification(sprintf(
1611                 "Kill_Notification(%d, '%s', %s, %d);\n",
1612                 broadcast,
1613                 client.netname,
1614                 (net_type ? Get_Notif_TypeName(net_type) : "0"),
1615                 net_name
1616         ));
1617         #endif
1618
1619         entity notif, net_notif;
1620         float killed_cpid = NO_CPID;
1621         
1622         switch(net_type)
1623         {
1624                 case 0:
1625                 {
1626                         killed_cpid = 0; // kill ALL centerprints
1627                         break;
1628                 }
1629                 
1630                 case MSG_CENTER:
1631                 {
1632                         if(net_name)
1633                         {
1634                                 entity notif = Get_Notif_Ent(net_type, net_name);
1635                                 if not(notif) { backtrace("Kill_Notification: Could not find notification entity!\n"); return; }
1636                                 
1637                                 if(notif.nent_cpid)
1638                                         killed_cpid = notif.nent_cpid;
1639                                 else
1640                                         killed_cpid = NO_CPID;
1641                         }
1642                         else
1643                         {
1644                                 killed_cpid = 0; // kill ALL centerprints
1645                         }
1646                         break;
1647                 }
1648
1649                 case MSG_CENTER_CPID:
1650                 {
1651                         killed_cpid = net_name;
1652                         break;
1653                 }
1654         }
1655
1656         if(killed_cpid != NO_CPID)
1657         {
1658                 net_notif = spawn();
1659                 net_notif.classname = "net_kill_notification";
1660                 net_notif.nent_broadcast = broadcast;
1661                 net_notif.nent_client = client;
1662                 net_notif.nent_net_type = MSG_CENTER_CPID;
1663                 net_notif.nent_net_name = killed_cpid;
1664                 Net_LinkEntity(net_notif, FALSE, autocvar_notification_lifetime_runtime, Net_Write_Notification);
1665         }
1666
1667         for(notif = world; (notif = find(notif, classname, "net_notification"));)
1668         {
1669                 if(net_type)
1670                 {
1671                         if((killed_cpid != NO_CPID) && (notif.nent_net_type == MSG_CENTER))
1672                         {
1673                                 if(notif.owner.nent_cpid == killed_cpid)
1674                                 {
1675                                         notif.nent_net_name = -1;
1676                                         notif.nextthink = time;
1677                                 }
1678                                 else { continue; } // we ARE looking for a specific CPID, don't kill everything else too
1679                         }
1680                         else if(notif.nent_net_type == net_type)
1681                         {
1682                                 if(net_name)
1683                                 {
1684                                         if(notif.nent_net_name == net_name) { notif.nent_net_name = -1; notif.nextthink = time; }
1685                                         else { continue; } // we ARE looking for a certain net_name, don't kill everything else too
1686                                 }
1687                                 else { notif.nent_net_name = -1; notif.nextthink = time; }
1688                         }
1689                         else { continue; } // we ARE looking for a certain net_type, don't kill everything else too
1690                 }
1691                 else { notif.nent_net_name = -1; notif.nextthink = time; }
1692         }
1693 }
1694
1695 void Send_Notification(
1696         float broadcast, entity client,
1697         float net_type, float net_name,
1698         ...count)
1699 {
1700         // check supplied broadcast, target, type, and name for errors
1701         string checkargs = Notification_CheckArgs(broadcast, client, net_type, net_name);
1702         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Send_Notification: %s\n", checkargs)); return; }
1703
1704         // retreive counts for the arguments of this notification
1705         entity notif = Get_Notif_Ent(net_type, net_name);
1706         if not(notif) { backtrace("Send_Notification: Could not find notification entity!\n"); return; }
1707
1708         if((notif.nent_stringcount + notif.nent_floatcount) > count)
1709         {
1710                 backtrace(sprintf(
1711                         strcat(
1712                                 "Not enough 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         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
1722         {
1723                 backtrace(sprintf(
1724                         strcat(
1725                                 "Too many arguments for Send_Notification(%d, %s, %s, ...)! ",
1726                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
1727                                 "Check the definition and function call for accuracy...?\n"
1728                         ),
1729                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name,
1730                         notif.nent_stringcount, notif.nent_floatcount, count
1731                 ));
1732                 return;
1733         }
1734
1735         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
1736         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
1737         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
1738         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
1739         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
1740         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
1741         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
1742         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
1743
1744         #ifdef NOTIFICATIONS_DEBUG
1745         Debug_Notification(sprintf(
1746                 "Send_Notification(%d, %s, %s, %s, %s);\n",
1747                 broadcast,
1748                 Get_Notif_TypeName(net_type),
1749                 notif.nent_name,
1750                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1751                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1752         ));
1753         #endif
1754
1755         if(
1756                 server_is_dedicated
1757                 &&
1758                 (
1759                         broadcast == NOTIF_ALL
1760                         ||
1761                         broadcast == NOTIF_ALL_EXCEPT
1762                 )
1763                 &&
1764                 !(
1765                         net_type == MSG_ANNCE
1766                         ||
1767                         net_type == MSG_CENTER
1768                 )
1769         )
1770         {
1771                 Local_Notification_WOVA(
1772                         net_type, net_name,
1773                         notif.nent_stringcount,
1774                         notif.nent_floatcount,
1775                         s1, s2, s3, s4,
1776                         f1, f2, f3, f4);
1777         }
1778
1779         if(net_type == MSG_CHOICE)
1780         {
1781                 // float verbose_allowed = (autocvar_notification_server_allows_frag_verbose && ((autocvar_notification_server_allows_frag_verbose == 2) || inWarmupStage));
1782                 // THIS GETS TRICKY... now we have to cycle through each possible player (checking broadcast)
1783                 // and then do an individual NOTIF_ONE_ONLY recursive call for each one depending on their option...
1784                 // It's slow, but it's better than the alternatives:
1785                 //   1. Constantly networking all info and letting client decide
1786                 //   2. Manually handling each separate call on per-usage basis (See old CTF usage of verbose)
1787                 entity found_choice; 
1788
1789                 #define RECURSE_FROM_CHOICE(ent,action) \
1790                         switch(ent.msg_choice_choices[net_name]) \
1791                         { \
1792                                 case 1: found_choice = notif.nent_optiona; break; \
1793                                 case 2: found_choice = notif.nent_optionb; break; \
1794                                 default: action; \
1795                         } \
1796                         Send_Notification_WOVA( \
1797                                 NOTIF_ONE_ONLY, \
1798                                 ent, \
1799                                 found_choice.nent_type, \
1800                                 found_choice.nent_id, \
1801                                 s1, s2, s3, s4, \
1802                                 f1, f2, f3, f4);
1803
1804                 switch(broadcast)
1805                 {
1806                         case NOTIF_ONE_ONLY: // we can potentially save processing power with this broadcast method
1807                         {
1808                                 if(IS_REAL_CLIENT(client))
1809                                         { RECURSE_FROM_CHOICE(client, return) }
1810                                 break;
1811                         }
1812                         default:
1813                         {
1814                                 entity to;
1815                                 FOR_EACH_REALCLIENT(to)
1816                                         { if(Notification_ShouldSend(broadcast, to, client))
1817                                                 { RECURSE_FROM_CHOICE(to, continue) } }
1818                                 break;
1819                         }
1820                 }
1821         }
1822         else
1823         {
1824                 entity net_notif = spawn();
1825                 net_notif.owner = notif;
1826                 net_notif.classname = "net_notification";
1827                 net_notif.nent_broadcast = broadcast;
1828                 net_notif.nent_client = client;
1829                 net_notif.nent_net_type = net_type;
1830                 net_notif.nent_net_name = net_name;
1831                 net_notif.nent_stringcount = notif.nent_stringcount;
1832                 net_notif.nent_floatcount = notif.nent_floatcount;
1833                 
1834                 float i;
1835                 for(i = 0; i < net_notif.nent_stringcount; ++i)
1836                         { net_notif.nent_strings[i] = strzone(...(i, string)); }
1837                 for(i = 0; i < net_notif.nent_floatcount; ++i)
1838                         { net_notif.nent_floats[i] = ...((net_notif.nent_stringcount + i), float); }
1839
1840                 net_notif.think = Net_Notification_Remove;
1841                 net_notif.nextthink =
1842                         ((time > autocvar_notification_lifetime_mapload)
1843                         ?
1844                                 (time + autocvar_notification_lifetime_runtime)
1845                                 :
1846                                 autocvar_notification_lifetime_mapload
1847                         ); 
1848
1849                 Net_LinkEntity(net_notif, FALSE, 0, Net_Write_Notification);
1850         }
1851 }
1852
1853 // WOVA = Without Variable Arguments 
1854 void Send_Notification_WOVA(
1855         float broadcast, entity client,
1856         float net_type, float net_name,
1857         string s1, string s2, string s3, string s4,
1858         float f1, float f2, float f3, float f4)
1859 {
1860         entity notif = Get_Notif_Ent(net_type, net_name);
1861         
1862         #ifdef NOTIFICATIONS_DEBUG
1863         Debug_Notification(sprintf(
1864                 "Send_Notification_WOVA(%d, %s, %s, %s, %s);\n",
1865                 broadcast,
1866                 Get_Notif_TypeName(net_type),
1867                 notif.nent_name,
1868                 MakeConsoleSafe(sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4)),
1869                 sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
1870         ));
1871         #endif
1872         
1873         #define VARITEM(stringc,floatc,args) \
1874                 if((notif.nent_stringcount == stringc) && (notif.nent_floatcount == floatc)) \
1875                         { Send_Notification(broadcast, client, net_type, net_name, args); return; }
1876         EIGHT_VARS_TO_VARARGS_VARLIST
1877         #undef VARITEM
1878         Send_Notification(broadcast, client, net_type, net_name); // some notifications don't have any arguments at all
1879 }
1880 #endif // ifdef SVQC