]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
Wait, don't just kill ALL notifications of the same type... durr!
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / notifications.qc
1 // ================================================
2 //  Unified notification system, written by Samual
3 //  Last updated: February, 2013
4 // ================================================
5
6 string Get_Notif_TypeName(float net_type)
7 {
8         switch(net_type)
9         {
10                 case MSG_INFO: return "MSG_INFO";
11                 case MSG_CENTER: return "MSG_CENTER";
12                 case MSG_WEAPON: return "MSG_WEAPON";
13                 case MSG_DEATH: return "MSG_DEATH";
14         }
15         backtrace(sprintf("Get_Notif_TypeName(%d): Improper net type!\n", net_type));
16         return "your ass";
17 }
18
19 entity Get_Notif_Ent(float net_type, float net_name)
20 {
21         switch(net_type)
22         {
23                 case MSG_INFO: return msg_info_notifs[net_name - 1];
24                 case MSG_CENTER: return msg_center_notifs[net_name - 1];
25                 case MSG_WEAPON: return msg_weapon_notifs[net_name - 1];
26                 case MSG_DEATH: return msg_death_notifs[net_name - 1];
27         }
28         backtrace(sprintf("Get_Notif_Ent(%d, %d): Improper net type!\n", net_type, net_name));
29         return world;
30 }
31
32 string Notification_CheckArgs_TypeName(float net_type, float net_name)
33 {
34         // check supplied type and name for errors
35         string checkargs = "";
36         #define CHECKARG_TYPENAME(type) case MSG_##type##: \
37                 { if(!net_name || (net_name > NOTIF_##type##_COUNT)) \
38                 { checkargs = sprintf("Improper name: %d!", net_name); } break; }
39         switch(net_type)
40         {
41                 CHECKARG_TYPENAME(INFO)
42                 CHECKARG_TYPENAME(CENTER)
43                 CHECKARG_TYPENAME(WEAPON)
44                 CHECKARG_TYPENAME(DEATH)
45                 default: { checkargs = sprintf("Improper type: %d!", checkargs, net_type); break; }
46         }
47         #undef CHECKARG_TYPENAME
48         return checkargs;
49 }
50
51 #ifdef SVQC
52 string Notification_CheckArgs(float broadcast, entity client, float net_type, float net_name)
53 {
54         // check supplied broadcast, target, type, and name for errors
55         string checkargs = Notification_CheckArgs_TypeName(net_type, net_name);
56         if(checkargs != "") { checkargs = strcat(checkargs, " "); }
57         switch(broadcast)
58         {
59                 case NOTIF_ONE:
60                 case NOTIF_ONE_ONLY:
61                 {
62                         if(IS_NOT_A_CLIENT(client))
63                                 { checkargs = sprintf("%sNo client provided!", checkargs); }
64                         break;
65                 }
66                 
67                 case NOTIF_ANY_EXCEPT:
68                 {
69                         if(IS_NOT_A_CLIENT(client))
70                                 { checkargs = sprintf("%sException can't be a non-client!", checkargs); }
71                         break;
72                 }
73                 
74                 case NOTIF_ANY:
75                 {
76                         if(client)
77                                 { checkargs = sprintf("%sEntity provided when world was required!", checkargs); }
78                         break;
79                 }
80                 
81                 case NOTIF_TEAM:
82                 case NOTIF_TEAM_EXCEPT:
83                 {
84                         if not(teamplay) { checkargs = sprintf("%sTeamplay not active!", checkargs); }
85                         else if(IS_NOT_A_CLIENT(client))
86                         {
87                                 if(broadcast == NOTIF_TEAM) { checkargs = sprintf("%sNo client provided!", checkargs); }
88                                 else { checkargs = sprintf("%sException can't be a non-client!", checkargs); }
89                         }
90                         break;
91                 }
92                 
93                 default: { checkargs = sprintf("%sImproper broadcast: %d!", checkargs, broadcast); break; }
94         }
95         return checkargs;
96 }
97 #endif
98
99 // ===============================
100 //  Frontend Notification Pushing
101 // ===============================
102
103 void Dump_Notifications(float fh, float alsoprint)
104 {
105         #define NOTIF_WRITE(a) { \
106                 fputs(fh, a); \
107                 if(alsoprint) { print(a); } }
108         #define NOTIF_WRITE_SETA(name,default,text) { \
109                 notif_msg = \
110                         sprintf( \
111                                 "seta notification_%s %d \"notif string: %s^7\"\n", \
112                                 name, default, strreplace("\{3}", "", strreplace("\n", "\\n", text)) \
113                         ); \
114                 NOTIF_WRITE(notif_msg) }
115
116         string notif_msg;
117         float i;
118         entity e;
119
120         // Note: This warning only applies to the notifications.cfg file that is output...
121
122         // You ARE supposed to manually edit this function to add i.e. hard coded
123         // notification variables for mutators or game modes or such and then
124         // regenerate the notifications.cfg file from the new code.
125
126         NOTIF_WRITE("// ********************************************** //\n");
127         NOTIF_WRITE("// ** WARNING - DO NOT MANUALLY EDIT THIS FILE ** //\n");
128         NOTIF_WRITE("// **                                          ** //\n");
129         NOTIF_WRITE("// **  This file is automatically generated    ** //\n");
130         NOTIF_WRITE("// **  by code with the command 'dumpnotifs'.  ** //\n");
131         NOTIF_WRITE("// **                                          ** //\n");
132         NOTIF_WRITE("// **  If you add a new notification, please   ** //\n");
133         NOTIF_WRITE("// **  regenerate this file with that command  ** //\n");
134         NOTIF_WRITE("// **  making sure that the output matches     ** //\n");
135         NOTIF_WRITE("// **  with the lists and defaults in code.    ** //\n");
136         NOTIF_WRITE("// **                                          ** //\n");
137         NOTIF_WRITE("// ********************************************** //\n");
138
139         // These notifications will also append their string as a comment...
140         // This is not necessary, and does not matter if they vary between config versions,
141         // it is just a semi-helpful tool for those who want to manually change their user settings.
142
143         NOTIF_WRITE(sprintf("\n// MSG_INFO notifications (count = %d):\n", NOTIF_INFO_COUNT));
144         for(i = 1; i <= NOTIF_INFO_COUNT; ++i)
145         {
146                 e = Get_Notif_Ent(MSG_INFO, i);
147                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
148                 NOTIF_WRITE_SETA(e.nent_name, e.nent_default, e.nent_string);
149         }
150
151         NOTIF_WRITE(sprintf("\n// MSG_CENTER notifications (count = %d):\n", NOTIF_CENTER_COUNT));
152         for(i = 1; i <= NOTIF_CENTER_COUNT; ++i)
153         {
154                 e = Get_Notif_Ent(MSG_CENTER, i);
155                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
156                 NOTIF_WRITE_SETA(e.nent_name, e.nent_default, e.nent_string);
157         }
158
159         NOTIF_WRITE(sprintf("\n// MSG_WEAPON notifications (count = %d):\n", NOTIF_WEAPON_COUNT));
160         for(i = 1; i <= NOTIF_WEAPON_COUNT; ++i)
161         {
162                 e = Get_Notif_Ent(MSG_WEAPON, i);
163                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
164                 NOTIF_WRITE_SETA(e.nent_name, e.nent_default, sprintf("infoname: %s, centername: %s",
165                         e.nent_msginfo.nent_name, e.nent_msgcenter.nent_name));
166         }
167
168         NOTIF_WRITE(sprintf("\n// MSG_DEATH notifications (count = %d):\n", NOTIF_DEATH_COUNT));
169         for(i = 1; i <= NOTIF_DEATH_COUNT; ++i)
170         {
171                 e = Get_Notif_Ent(MSG_DEATH, i);
172                 if not(e) { backtrace("Dump_Notifications(): Missing notification entity!\n"); return; }
173                 NOTIF_WRITE_SETA(e.nent_name, e.nent_default, sprintf("infoname: %s, centername: %s",
174                         e.nent_msginfo.nent_name, e.nent_msgcenter.nent_name));
175         }
176
177         // edit these to match whichever cvars are used for specific notification options
178         NOTIF_WRITE("\n// HARD CODED notification variables:\n");
179         NOTIF_WRITE("seta notification_allow_chatboxprint 1 \"Allow notifications to be printed to chat box by setting notification cvar to 2 (You can also set this cvar to 2 to force ALL notifications to be printed to the chatbox)\"\n");
180         NOTIF_WRITE("seta notification_show_location 0 \"Append location information to MSG_INFO death/kill messages\"\n");
181         NOTIF_WRITE("seta notification_show_location_string \"\" \"Replacement string piped into sprintf, so you can do different messages like this: ' at the %s' or ' (near %s)'\"\n");
182         NOTIF_WRITE("seta notification_show_sprees 1 \"Print information about sprees in death/kill messages\"\n");
183         NOTIF_WRITE("seta notification_show_sprees_center 1 \"Show spree information in MSG_CENTER messages... 0 = off, 1 = target (but only for first victim) and attacker\"\n");
184         NOTIF_WRITE("seta notification_show_sprees_center_specialonly 1 \"Don't show spree information in MSG_CENTER messages if it isn't an achievement\"\n");
185         NOTIF_WRITE("seta notification_show_sprees_info 3 \"Show spree information in MSG_INFO messages... 0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker\"\n");
186         NOTIF_WRITE("seta notification_show_sprees_info_newline 0 \"Show attacker spree information for MSG_INFO messages on a separate line than the death notification itself\"\n");
187         NOTIF_WRITE("seta notification_show_sprees_info_specialonly 1 \"Don't show attacker spree information in MSG_INFO messages if it isn't an achievement\"\n");
188         NOTIF_WRITE("seta notification_errors_are_fatal 1 \"If a notification fails upon initialization, cause a Host_Error to stop the program\"\n");
189         NOTIF_WRITE("seta notification_ctf_pickup_team_verbose 1 \"Show extra information if a team mate picks up a flag\"\n");
190         NOTIF_WRITE("seta notification_ctf_pickup_enemy_verbose 1 \"Show extra information if an enemy picks up a flag\"\n");
191         NOTIF_WRITE("seta notification_ctf_capture_verbose 1 \"Show extra information when someone captures a flag\"\n");
192         NOTIF_WRITE("seta notification_frag_verbose 1 \"Show extra information when you frag someone (or when you are fragged\"\n");
193         NOTIF_WRITE("seta notification_lifetime_runtime 0.5 \"Amount of time that notification entities last on the server during runtime (In seconds)\"\n");
194         NOTIF_WRITE("seta notification_lifetime_mapload 10 \"Amount of time that notification entities last immediately at mapload (in seconds) to help prevent notifications from being lost on early init (like gamestart countdown)\"\n");
195
196         NOTIF_WRITE(sprintf("\n// Notification counts (total = %d): MSG_INFO = %d, MSG_CENTER = %d, MSG_WEAPON = %d, MSG_DEATH = %d\n",
197                 (NOTIF_INFO_COUNT + NOTIF_CENTER_COUNT + NOTIF_WEAPON_COUNT + NOTIF_DEATH_COUNT), 
198                 NOTIF_INFO_COUNT, NOTIF_CENTER_COUNT, NOTIF_WEAPON_COUNT, NOTIF_DEATH_COUNT));
199         
200         return;
201         #undef NOTIF_WRITE_SETA
202         #undef NOTIF_WRITE
203 }
204
205 #ifdef SVQC
206 void Notification_GetCvars()
207 {
208         GetCvars_handleFloat(get_cvars_s, get_cvars_f, FRAG_VERBOSE, "notification_frag_verbose");
209 }
210 #endif
211
212 string Local_Notification_sprintf(string input, string args, 
213         string s1, string s2, string s3, string s4,
214         float f1, float f2, float f3, float f4)
215 {
216         #ifdef NOTIFICATIONS_DEBUG
217         dprint(
218                 sprintf("Local_Notification_sprintf('%s^7', '%s', %s, %s);\n",
219                         strreplace("\n", "\\n", input),
220                         args,
221                         sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4),
222                         sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
223                 )
224         );
225         #endif
226         
227         string selected;
228         float sel_num;
229         for(sel_num = 0; sel_num < NOTIF_MAX_ARGS; ++sel_num) { arg_slot[sel_num] = ""; }
230
231         string tmp_s;
232
233         for(sel_num = 0;(args != "");)
234         {
235                 selected = car(args); args = cdr(args);
236                 NOTIF_HIT_MAX(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
237                 switch(strtolower(selected))
238                 {
239                         #define ARG_CASE(prog,selected,result) \
240                                 #ifdef CSQC \
241                                         #if (prog != ARG_SV) \
242                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
243                                         #endif \
244                                 #else \
245                                         #if (prog != ARG_CS) \
246                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
247                                         #endif \
248                                 #endif
249                         NOTIF_ARGUMENT_LIST
250                         #undef ARG_CASE
251                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_ARGS, "Local_Notification_sprintf")
252                 }
253         }
254         return sprintf(input, arg_slot[0], arg_slot[1], arg_slot[2], arg_slot[3], arg_slot[4], arg_slot[5], arg_slot[6]);
255 }
256
257 #ifdef CSQC
258 void Local_Notification_HUD_Notify_Push(string icon, string hudargs, string s1, string s2, string s3, string s4)
259 {
260         string selected;
261         float sel_num;
262         arg_slot[0] = ""; arg_slot[1] = "";
263
264         for(sel_num = 0;(hudargs != "");)
265         {
266                 selected = car(hudargs); hudargs = cdr(hudargs);
267                 NOTIF_HIT_MAX(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
268                 switch(strtolower(selected))
269                 {
270                         #define ARG_CASE(prog,selected,result) \
271                                 #if (prog == ARG_CS_SV_HA) \
272                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
273                                 #endif
274                         NOTIF_ARGUMENT_LIST
275                         #undef ARG_CASE
276                         default: NOTIF_HIT_UNKNOWN(NOTIF_MAX_HUDARGS, "Local_Notification_HUD_Notify_Push")
277                 }
278         }
279         HUD_Notify_Push(icon, arg_slot[0], arg_slot[1]);
280 }
281
282 void Local_Notification_centerprint_generic(string input, string durcnt, float cpid, float f1, float f2)
283 {
284         string selected;
285         float sel_num;
286         arg_slot[0] = ""; arg_slot[1] = "";
287
288         for(sel_num = 0;(durcnt != "");)
289         {
290                 selected = car(durcnt); durcnt = cdr(durcnt);
291                 NOTIF_HIT_MAX(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic")
292                 switch(strtolower(selected))
293                 {
294                         #define ARG_CASE(prog,selected,result) \
295                                 #if (prog == ARG_CS_SV_DC) \
296                                         case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
297                                 #endif
298                         NOTIF_ARGUMENT_LIST
299                         #undef ARG_CASE
300                         default:
301                         {
302                                 if(ftos(stof(selected)) != "") { arg_slot[sel_num] = selected; ++sel_num; }
303                                 else { NOTIF_HIT_UNKNOWN(NOTIF_MAX_DURCNT, "Local_Notification_centerprint_generic") }
304                                 break;
305                         }
306                 }
307         }
308         centerprint_generic(cpid, input, stof(arg_slot[0]), stof(arg_slot[1]));
309 }
310 #endif
311
312 void Local_Notification(float net_type, float net_name, ...count)
313 {
314         // check supplied type and name for errors
315         string checkargs = Notification_CheckArgs_TypeName(net_type, net_name);
316         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Local_Notification: %s\n", checkargs)); return; }
317
318         entity notif = Get_Notif_Ent(net_type, net_name);
319         if not(notif) { backtrace("Local_Notification: Could not find notification entity!\n"); return; }
320         if not(notif.nent_enabled) { dprint(sprintf("Local_Notification(%s, %s): Entity was disabled...\n", Get_Notif_TypeName(net_type), notif.nent_name)); return; }
321
322         if((notif.nent_stringcount + notif.nent_floatcount) > count)
323         {
324                 backtrace(sprintf(
325                         strcat(
326                                 "Not enough arguments for Local_Notification(%s, %s, ...)! ",
327                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
328                                 "Check the definition and function call for accuracy...?\n"
329                         ),
330                         Get_Notif_TypeName(net_type), notif.nent_name, notif.nent_stringcount, notif.nent_floatcount, count));
331                 return;
332         }
333         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
334         {
335                 backtrace(sprintf(
336                         strcat(
337                                 "Too many arguments for Local_Notification(%s, %s, ...)! ",
338                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
339                                 "Check the definition and function call for accuracy...?\n"
340                         ),
341                         Get_Notif_TypeName(net_type), notif.nent_name, notif.nent_stringcount, notif.nent_floatcount, count));
342                 return;
343         }
344
345         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
346         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
347         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
348         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
349         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
350         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
351         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
352         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
353
354         #ifdef NOTIFICATIONS_DEBUG
355         dprint(
356                 sprintf("Local_Notification(%s, %s, %s, %s);\n",
357                         Get_Notif_TypeName(net_type),
358                         notif.nent_name,
359                         sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4),
360                         sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
361                 )
362         );
363         #endif
364         
365         switch(net_type)
366         {
367                 case MSG_INFO:
368                 {
369                         print(
370                                 Local_Notification_sprintf(
371                                         notif.nent_string,
372                                         notif.nent_args, 
373                                         s1, s2, s3, s4,
374                                         f1, f2, f3, f4)
375                         );
376                         #ifdef CSQC 
377                         if(notif.nent_icon != "")
378                         {
379                                 Local_Notification_HUD_Notify_Push(
380                                         notif.nent_icon,
381                                         notif.nent_hudargs,
382                                         s1, s2, s3, s4);
383                         } 
384                         #endif 
385                         break;
386                 }
387                 
388                 #ifdef CSQC
389                 case MSG_CENTER:
390                 {
391                         Local_Notification_centerprint_generic(
392                                 Local_Notification_sprintf(
393                                         notif.nent_string,
394                                         notif.nent_args, 
395                                         s1, s2, s3, s4,
396                                         f1, f2, f3, f4),
397                                 notif.nent_durcnt,
398                                 notif.nent_cpid,
399                                 f1, f2);
400                         break;
401                 }
402                 #endif
403                 
404                 case MSG_WEAPON:
405                 case MSG_DEATH:
406                 {
407                         if(notif.nent_msginfo)
408                         if(notif.nent_msginfo.nent_enabled)
409                         {
410                                 Local_Notification_WOVA(
411                                         MSG_INFO,
412                                         notif.nent_msginfo.nent_id, 
413                                         notif.nent_msginfo.nent_stringcount, 
414                                         notif.nent_msginfo.nent_floatcount, 
415                                         s1, s2, s3, s4,
416                                         f1, f2, f3, f4);
417                         }
418                         #ifdef CSQC
419                         if(notif.nent_msgcenter)
420                         if(notif.nent_msgcenter.nent_enabled)
421                         {
422                                 Local_Notification_WOVA(
423                                         MSG_CENTER,
424                                         notif.nent_msgcenter.nent_id, 
425                                         notif.nent_msgcenter.nent_stringcount, 
426                                         notif.nent_msgcenter.nent_floatcount, 
427                                         s1, s2, s3, s4,
428                                         f1, f2, f3, f4); 
429                         }
430                         #endif
431                         break;
432                 }
433         }
434 }
435
436 // WOVA = Without Variable Arguments 
437 void Local_Notification_WOVA(float net_type, float net_name,
438         float stringcount, float floatcount,
439         string s1, string s2, string s3, string s4,
440         float f1, float f2, float f3, float f4)
441 {
442         #define VARITEM(stringc,floatc,args) \
443                 if((stringcount == stringc) && (floatcount == floatc)) \
444                         { Local_Notification(net_type, net_name, args); return; }
445         EIGHT_VARS_TO_VARARGS_VARLIST
446         #undef VARITEM
447         Local_Notification(net_type, net_name); // some notifications don't have any arguments at all
448 }
449
450
451 // =========================
452 //  Notification Networking
453 // =========================
454
455 #ifdef CSQC
456 void Read_Notification(float is_new)
457 {
458         float net_type = ReadByte();
459         float net_name = ReadShort();
460
461         if(net_type == MSG_CENTER_KILL)
462         {
463                 if(is_new)
464                 {
465                         if(net_name == 0) { reset_centerprint_messages(); }
466                         else
467                         {
468                                 entity notif = Get_Notif_Ent(MSG_CENTER, net_name);
469                                 if not(notif) { print("Read_Notification: Could not find notification entity!\n"); return; }
470                                 centerprint_generic(notif.nent_cpid, "", 0, 0);
471                         }
472                 }
473         }
474         else
475         {
476                 entity notif = Get_Notif_Ent(net_type, net_name);
477                 if not(notif) { print("Read_Notification: Could not find notification entity!\n"); return; }
478
479                 #ifdef NOTIFICATIONS_DEBUG
480                 dprint(sprintf("Read_Notification(%d) at %f: net_type = %s, net_name = %s\n", is_new, time, Get_Notif_TypeName(net_type), notif.nent_name));
481                 #endif
482
483                 string s1 = ((0 < notif.nent_stringcount) ? ReadString() : "");
484                 string s2 = ((1 < notif.nent_stringcount) ? ReadString() : "");
485                 string s3 = ((2 < notif.nent_stringcount) ? ReadString() : "");
486                 string s4 = ((3 < notif.nent_stringcount) ? ReadString() : "");
487                 float f1 = ((0 < notif.nent_floatcount) ? ReadLong() : 0);
488                 float f2 = ((1 < notif.nent_floatcount) ? ReadLong() : 0);
489                 float f3 = ((2 < notif.nent_floatcount) ? ReadLong() : 0);
490                 float f4 = ((3 < notif.nent_floatcount) ? ReadLong() : 0);
491         
492                 if(is_new)
493                 {
494                         Local_Notification_WOVA(
495                                 net_type, net_name,
496                                 notif.nent_stringcount,
497                                 notif.nent_floatcount,
498                                 s1, s2, s3, s4,
499                                 f1, f2, f3, f4);
500                 }
501         }
502 }
503 #endif
504
505 #ifdef SVQC
506 void Net_Notification_Remove()
507 {
508         float i;
509         for(i = 0; i < 4; ++i) { if(self.nent_strings[i]) { strunzone(self.nent_strings[i]); } }
510         remove(self);
511 }
512
513 float Net_Write_Notification(entity client, float sf)
514 {
515         float i, send = FALSE;
516         
517         switch(self.nent_broadcast)
518         {
519                 case NOTIF_ONE: // send to one client and their spectator
520                 {
521                         if(
522                                 (client == self.nent_client)
523                                 ||
524                                 (
525                                         (client.classname == STR_SPECTATOR)
526                                         &&
527                                         (client.enemy == self.nent_client)
528                                 )
529                         ) { send = TRUE; }
530                         break;
531                 }
532                 case NOTIF_ONE_ONLY: // send ONLY to one client
533                 {
534                         if(client == self.nent_client) { send = TRUE; }
535                         break;
536                 }
537                 case NOTIF_TEAM: // send only to X team and their spectators
538                 {
539                         if(
540                                 (client.team == self.nent_client.team)
541                                 ||
542                                 (
543                                         (client.classname == STR_SPECTATOR)
544                                         &&
545                                         (client.enemy.team == self.nent_client.team)
546                                 )
547                         ) { send = TRUE; }
548                         break;
549                 }
550                 case NOTIF_TEAM_EXCEPT: // send only to X team and their spectators, except for Y person and their spectators
551                 {
552                         if(
553                                 (client != self.nent_client)
554                                 &&
555                                 (
556                                         (client.team == self.nent_client.team)
557                                         ||
558                                         (
559                                                 (client.classname == STR_SPECTATOR)
560                                                 &&
561                                                 (
562                                                         (client.enemy != self.nent_client)
563                                                         &&
564                                                         (client.enemy.team == self.nent_client.team)
565                                                 )
566                                         )
567                                 )
568                         ) { send = TRUE; }
569                         break;
570                 }
571                 case NOTIF_ANY: // send to everyone
572                 {
573                         send = TRUE;
574                         break;
575                 }
576                 case NOTIF_ANY_EXCEPT: // send to everyone except X person and their spectators
577                 {
578                         if(
579                                 (client != self.nent_client)
580                                 &&
581                                 !(
582                                         (client.classname == STR_SPECTATOR)
583                                         &&
584                                         (client.enemy == self.nent_client)
585                                 )
586                         ) { send = TRUE; }
587                         break;
588                 }
589                 default: { send = FALSE; break; }
590         }
591
592         if(send)
593         {               
594                 WriteByte(MSG_ENTITY, ENT_CLIENT_NOTIFICATION);
595                 WriteByte(MSG_ENTITY, self.nent_net_type);
596                 WriteShort(MSG_ENTITY, self.nent_net_name);
597                 for(i = 0; i < self.nent_stringcount; ++i) { WriteString(MSG_ENTITY, self.nent_strings[i]); } 
598                 for(i = 0; i < self.nent_floatcount; ++i) { WriteLong(MSG_ENTITY, self.nent_floats[i]); }
599         }
600
601         return send; 
602 }
603
604 void Kill_Notification(float broadcast, entity client, float net_type, float net_name)
605 {
606         string checkargs = Notification_CheckArgs(broadcast, client, net_type, 1);
607         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Kill_Notification: %s\n", checkargs)); return; }
608
609         entity notif;
610
611         // if this is a centerprint, we must tell the client
612         // to kill the cpid in the centerprint queue
613         if(net_type == MSG_CENTER)
614         {
615                 notif = spawn();
616                 notif.classname = "net_kill_notification";
617                 notif.nent_broadcast = broadcast;
618                 notif.nent_client = client;
619                 notif.nent_net_type = MSG_CENTER_KILL;
620                 notif.nent_net_name = net_name;
621                 Net_LinkEntity(notif, FALSE, autocvar_notification_lifetime_runtime, Net_Write_Notification);
622         }
623
624         for(notif = world; (notif = find(notif, classname, "net_notification"));)
625         {
626                 // now kill the old send notification entity
627                 if(notif.nent_net_type == net_type)
628                 {
629                         if(net_name)
630                                 { if(notif.nent_net_name == net_name) { notif.think(); } }
631                         else
632                                 { notif.think(); }
633                                 
634                         print(sprintf("killed '%s'\n", notif.classname));
635                 }
636         }
637 }
638
639 void Send_Notification(float broadcast, entity client,
640         float net_type, float net_name, ...count)
641 {
642         // check supplied broadcast, target, type, and name for errors
643         string checkargs = Notification_CheckArgs(broadcast, client, net_type, net_name);
644         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Send_Notification: %s\n", checkargs)); return; }
645
646         // retreive counts for the arguments of this notification
647         entity notif = Get_Notif_Ent(net_type, net_name);
648         if not(notif) { backtrace("Send_Notification: Could not find notification entity!\n"); return; }
649
650         if((notif.nent_stringcount + notif.nent_floatcount) > count)
651         {
652                 backtrace(sprintf(
653                         strcat(
654                                 "Not enough arguments for Send_Notification(%d, %s, %s, ...)! ",
655                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
656                                 "Check the definition and function call for accuracy...?\n"
657                         ),
658                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name, notif.nent_stringcount, notif.nent_floatcount, count));
659                 return;
660         }
661         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
662         {
663                 backtrace(sprintf(
664                         strcat(
665                                 "Too many arguments for Send_Notification(%d, %s, %s, ...)! ",
666                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
667                                 "Check the definition and function call for accuracy...?\n"
668                         ),
669                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name, notif.nent_stringcount, notif.nent_floatcount, count));
670                 return;
671         }
672
673         #ifdef NOTIFICATIONS_DEBUG
674         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
675         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
676         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
677         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
678         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
679         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
680         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
681         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
682         dprint(
683                 sprintf("Send_Notification(%d, %s, %s, %s, %s - %d %d);\n",
684                         broadcast,
685                         Get_Notif_TypeName(net_type),
686                         notif.nent_name,
687                         sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4),
688                         sprintf("%d, %d, %d, %d", f1, f2, f3, f4),
689                         notif.nent_stringcount, notif.nent_floatcount
690                 )
691         );
692         #endif
693
694         entity net_notif = spawn();
695         net_notif.classname = "net_notification";
696         net_notif.nent_broadcast = broadcast;
697         net_notif.nent_client = client;
698         net_notif.nent_net_type = net_type;
699         net_notif.nent_net_name = net_name;
700         net_notif.nent_stringcount = notif.nent_stringcount;
701         net_notif.nent_floatcount = notif.nent_floatcount;
702         
703         float i;
704         for(i = 0; i < net_notif.nent_stringcount; ++i) { net_notif.nent_strings[i] = strzone(...(i, string)); }
705         for(i = 0; i < net_notif.nent_floatcount; ++i) { net_notif.nent_floats[i] = ...((net_notif.nent_stringcount + i), float); }
706
707         net_notif.think = Net_Notification_Remove;
708         net_notif.nextthink = ((time > autocvar_notification_lifetime_mapload) ?
709                         (time + autocvar_notification_lifetime_runtime) :
710                         autocvar_notification_lifetime_mapload); 
711
712         Net_LinkEntity(net_notif, FALSE, 0, Net_Write_Notification);
713
714         if(server_is_dedicated && (broadcast == NOTIF_ANY || broadcast == NOTIF_ANY_EXCEPT) && (net_type != MSG_CENTER))
715         {
716                 Local_Notification_WOVA(
717                         net_type, net_name,
718                         notif.nent_stringcount,
719                         notif.nent_floatcount,
720                         IFSTR(0), IFSTR(1), IFSTR(2), IFSTR(3),
721                         IFFL(0), IFFL(1), IFFL(2), IFFL(3));
722         }
723 }
724
725 // WOVA = Without Variable Arguments 
726 void Send_Notification_WOVA(float broadcast, entity client,
727         float net_type, float net_name,
728         string s1, string s2, string s3, string s4,
729         float f1, float f2, float f3, float f4)
730 {
731         entity notif = Get_Notif_Ent(net_type, net_name);
732         
733         #ifdef NOTIFICATIONS_DEBUG
734         dprint(
735                 sprintf("Send_Notification_WOVA(%d, %s, %s, %s, %s - %d %d);\n",
736                         broadcast,
737                         Get_Notif_TypeName(net_type),
738                         notif.nent_name,
739                         sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4),
740                         sprintf("%d, %d, %d, %d", f1, f2, f3, f4),
741                         notif.nent_stringcount, notif.nent_floatcount
742                 )
743         );
744         #endif
745         
746         #define VARITEM(stringc,floatc,args) \
747                 if((notif.nent_stringcount == stringc) && (notif.nent_floatcount == floatc)) \
748                         { Send_Notification(broadcast, client, net_type, net_name, args); return; }
749         EIGHT_VARS_TO_VARARGS_VARLIST
750         #undef VARITEM
751         Send_Notification(broadcast, client, net_type, net_name); // some notifications don't have any arguments at all
752 }
753
754
755 // =============================
756 //  LEGACY NOTIFICATION SYSTEMS
757 // =============================
758
759 void Send_CSQC_Centerprint_Generic(entity e, float id, string s, float duration, float countdown_num)
760 {
761         if ((clienttype(e) == CLIENTTYPE_REAL) && (e.flags & FL_CLIENT))
762         {
763                 msg_entity = e;
764                 WRITESPECTATABLE_MSG_ONE({
765                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
766                         WriteByte(MSG_ONE, TE_CSQC_CENTERPRINT_GENERIC);
767                         WriteByte(MSG_ONE, id);
768                         WriteString(MSG_ONE, s);
769                         if (id != 0 && s != "")
770                         {
771                                 WriteByte(MSG_ONE, duration);
772                                 WriteByte(MSG_ONE, countdown_num);
773                         }
774                 });
775         }
776 }
777 #endif // ifdef SVQC