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