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