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