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