]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
Only the client needs this cvar
[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) && (prog != ARG_DC) \
242                                                 case selected: { arg_slot[sel_num] = result; ++sel_num; break; } \
243                                         #endif \
244                                 #else \
245                                         #if (prog != ARG_CS) && (prog != ARG_DC) \
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) || (prog == ARG_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
321         #ifdef NOTIFICATIONS_DEBUG
322         if not(notif.nent_enabled) { dprint(sprintf("Local_Notification(%s, %s): Entity was disabled...\n", Get_Notif_TypeName(net_type), notif.nent_name)); return; }
323         #endif
324         
325         if((notif.nent_stringcount + notif.nent_floatcount) > count)
326         {
327                 backtrace(sprintf(
328                         strcat(
329                                 "Not enough arguments for Local_Notification(%s, %s, ...)! ",
330                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
331                                 "Check the definition and function call for accuracy...?\n"
332                         ),
333                         Get_Notif_TypeName(net_type), notif.nent_name, notif.nent_stringcount, notif.nent_floatcount, count));
334                 return;
335         }
336         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
337         {
338                 backtrace(sprintf(
339                         strcat(
340                                 "Too many arguments for Local_Notification(%s, %s, ...)! ",
341                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
342                                 "Check the definition and function call for accuracy...?\n"
343                         ),
344                         Get_Notif_TypeName(net_type), notif.nent_name, notif.nent_stringcount, notif.nent_floatcount, count));
345                 return;
346         }
347
348         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
349         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
350         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
351         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
352         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
353         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
354         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
355         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
356
357         #ifdef NOTIFICATIONS_DEBUG
358         dprint(
359                 sprintf("Local_Notification(%s, %s, %s, %s);\n",
360                         Get_Notif_TypeName(net_type),
361                         notif.nent_name,
362                         sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4),
363                         sprintf("%d, %d, %d, %d", f1, f2, f3, f4)
364                 )
365         );
366         #endif
367         
368         switch(net_type)
369         {
370                 case MSG_INFO:
371                 {
372                         print(
373                                 Local_Notification_sprintf(
374                                         notif.nent_string,
375                                         notif.nent_args, 
376                                         s1, s2, s3, s4,
377                                         f1, f2, f3, f4)
378                         );
379                         #ifdef CSQC 
380                         if(notif.nent_icon != "")
381                         {
382                                 Local_Notification_HUD_Notify_Push(
383                                         notif.nent_icon,
384                                         notif.nent_hudargs,
385                                         s1, s2, s3, s4);
386                         } 
387                         #endif 
388                         break;
389                 }
390                 
391                 #ifdef CSQC
392                 case MSG_CENTER:
393                 {
394                         Local_Notification_centerprint_generic(
395                                 Local_Notification_sprintf(
396                                         notif.nent_string,
397                                         notif.nent_args, 
398                                         s1, s2, s3, s4,
399                                         f1, f2, f3, f4),
400                                 notif.nent_durcnt,
401                                 notif.nent_cpid,
402                                 f1, f2);
403                         break;
404                 }
405                 #endif
406                 
407                 case MSG_WEAPON:
408                 case MSG_DEATH:
409                 {
410                         if(notif.nent_msginfo)
411                         if(notif.nent_msginfo.nent_enabled)
412                         {
413                                 Local_Notification_WOVA(
414                                         MSG_INFO,
415                                         notif.nent_msginfo.nent_id, 
416                                         notif.nent_msginfo.nent_stringcount, 
417                                         notif.nent_msginfo.nent_floatcount, 
418                                         s1, s2, s3, s4,
419                                         f1, f2, f3, f4);
420                         }
421                         #ifdef CSQC
422                         if(notif.nent_msgcenter)
423                         if(notif.nent_msgcenter.nent_enabled)
424                         {
425                                 Local_Notification_WOVA(
426                                         MSG_CENTER,
427                                         notif.nent_msgcenter.nent_id, 
428                                         notif.nent_msgcenter.nent_stringcount, 
429                                         notif.nent_msgcenter.nent_floatcount, 
430                                         s1, s2, s3, s4,
431                                         f1, f2, f3, f4); 
432                         }
433                         #endif
434                         break;
435                 }
436         }
437 }
438
439 // WOVA = Without Variable Arguments 
440 void Local_Notification_WOVA(float net_type, float net_name,
441         float stringcount, float floatcount,
442         string s1, string s2, string s3, string s4,
443         float f1, float f2, float f3, float f4)
444 {
445         #define VARITEM(stringc,floatc,args) \
446                 if((stringcount == stringc) && (floatcount == floatc)) \
447                         { Local_Notification(net_type, net_name, args); return; }
448         EIGHT_VARS_TO_VARARGS_VARLIST
449         #undef VARITEM
450         Local_Notification(net_type, net_name); // some notifications don't have any arguments at all
451 }
452
453
454 // =========================
455 //  Notification Networking
456 // =========================
457
458 #ifdef CSQC
459 void Read_Notification(float is_new)
460 {
461         float net_type = ReadByte();
462         float net_name = ReadShort();
463
464         if(net_type == MSG_CENTER_KILL)
465         {
466                 if(is_new)
467                 {
468                         if(net_name == 0) { reset_centerprint_messages(); }
469                         else
470                         {
471                                 entity notif = Get_Notif_Ent(MSG_CENTER, net_name);
472                                 if not(notif) { backtrace("Read_Notification: Could not find notification entity!\n"); return; }
473                                 centerprint_generic(notif.nent_cpid, "", 0, 0);
474                         }
475                 }
476         }
477         else
478         {
479                 entity notif = Get_Notif_Ent(net_type, net_name);
480                 if not(notif) { backtrace("Read_Notification: Could not find notification entity!\n"); return; }
481
482                 #ifdef NOTIFICATIONS_DEBUG
483                 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));
484                 #endif
485
486                 string s1 = ((0 < notif.nent_stringcount) ? ReadString() : "");
487                 string s2 = ((1 < notif.nent_stringcount) ? ReadString() : "");
488                 string s3 = ((2 < notif.nent_stringcount) ? ReadString() : "");
489                 string s4 = ((3 < notif.nent_stringcount) ? ReadString() : "");
490                 float f1 = ((0 < notif.nent_floatcount) ? ReadLong() : 0);
491                 float f2 = ((1 < notif.nent_floatcount) ? ReadLong() : 0);
492                 float f3 = ((2 < notif.nent_floatcount) ? ReadLong() : 0);
493                 float f4 = ((3 < notif.nent_floatcount) ? ReadLong() : 0);
494         
495                 if(is_new)
496                 {
497                         Local_Notification_WOVA(
498                                 net_type, net_name,
499                                 notif.nent_stringcount,
500                                 notif.nent_floatcount,
501                                 s1, s2, s3, s4,
502                                 f1, f2, f3, f4);
503                 }
504         }
505 }
506 #endif
507
508 #ifdef SVQC
509 void Net_Notification_Remove()
510 {
511         float i;
512         for(i = 0; i < 4; ++i) { if(self.nent_strings[i]) { strunzone(self.nent_strings[i]); } }
513         remove(self);
514 }
515
516 float Net_Write_Notification(entity client, float sf)
517 {
518         float i, send = FALSE;
519         
520         switch(self.nent_broadcast)
521         {
522                 case NOTIF_ONE: // send to one client and their spectator
523                 {
524                         if(
525                                 (client == self.nent_client)
526                                 ||
527                                 (
528                                         (client.classname == STR_SPECTATOR)
529                                         &&
530                                         (client.enemy == self.nent_client)
531                                 )
532                         ) { send = TRUE; }
533                         break;
534                 }
535                 case NOTIF_ONE_ONLY: // send ONLY to one client
536                 {
537                         if(client == self.nent_client) { send = TRUE; }
538                         break;
539                 }
540                 case NOTIF_TEAM: // send only to X team and their spectators
541                 {
542                         if(
543                                 (client.team == self.nent_client.team)
544                                 ||
545                                 (
546                                         (client.classname == STR_SPECTATOR)
547                                         &&
548                                         (client.enemy.team == self.nent_client.team)
549                                 )
550                         ) { send = TRUE; }
551                         break;
552                 }
553                 case NOTIF_TEAM_EXCEPT: // send only to X team and their spectators, except for Y person and their spectators
554                 {
555                         if(
556                                 (client != self.nent_client)
557                                 &&
558                                 (
559                                         (client.team == self.nent_client.team)
560                                         ||
561                                         (
562                                                 (client.classname == STR_SPECTATOR)
563                                                 &&
564                                                 (
565                                                         (client.enemy != self.nent_client)
566                                                         &&
567                                                         (client.enemy.team == self.nent_client.team)
568                                                 )
569                                         )
570                                 )
571                         ) { send = TRUE; }
572                         break;
573                 }
574                 case NOTIF_ANY: // send to everyone
575                 {
576                         send = TRUE;
577                         break;
578                 }
579                 case NOTIF_ANY_EXCEPT: // send to everyone except X person and their spectators
580                 {
581                         if(
582                                 (client != self.nent_client)
583                                 &&
584                                 !(
585                                         (client.classname == STR_SPECTATOR)
586                                         &&
587                                         (client.enemy == self.nent_client)
588                                 )
589                         ) { send = TRUE; }
590                         break;
591                 }
592                 default: { send = FALSE; break; }
593         }
594
595         if(send)
596         {               
597                 WriteByte(MSG_ENTITY, ENT_CLIENT_NOTIFICATION);
598                 WriteByte(MSG_ENTITY, self.nent_net_type);
599                 WriteShort(MSG_ENTITY, self.nent_net_name);
600                 for(i = 0; i < self.nent_stringcount; ++i) { WriteString(MSG_ENTITY, self.nent_strings[i]); } 
601                 for(i = 0; i < self.nent_floatcount; ++i) { WriteLong(MSG_ENTITY, self.nent_floats[i]); }
602         }
603
604         return send; 
605 }
606
607 void Kill_Notification(float broadcast, entity client, float net_type, float net_name)
608 {
609         string checkargs = Notification_CheckArgs(broadcast, client, net_type, 1);
610         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Kill_Notification: %s\n", checkargs)); return; }
611
612         entity notif;
613
614         // if this is a centerprint, we must tell the client
615         // to kill the cpid in the centerprint queue
616         if(net_type == MSG_CENTER)
617         {
618                 notif = spawn();
619                 notif.classname = "net_kill_notification";
620                 notif.nent_broadcast = broadcast;
621                 notif.nent_client = client;
622                 notif.nent_net_type = MSG_CENTER_KILL;
623                 notif.nent_net_name = net_name;
624                 Net_LinkEntity(notif, FALSE, autocvar_notification_lifetime_runtime, Net_Write_Notification);
625         }
626
627         for(notif = world; (notif = find(notif, classname, "net_notification"));)
628         {
629                 // now kill the old send notification entity
630                 if(notif.nent_net_type == net_type)
631                 {
632                         if(net_name)
633                                 { if(notif.nent_net_name == net_name) { notif.think(); } }
634                         else
635                                 { notif.think(); }
636
637                         #ifdef NOTIFICATIONS_DEBUG
638                         print(sprintf("killed '%s'\n", notif.classname));
639                         #endif
640                 }
641         }
642 }
643
644 void Send_Notification(float broadcast, entity client,
645         float net_type, float net_name, ...count)
646 {
647         // check supplied broadcast, target, type, and name for errors
648         string checkargs = Notification_CheckArgs(broadcast, client, net_type, net_name);
649         if(checkargs != "") { backtrace(sprintf("Incorrect usage of Send_Notification: %s\n", checkargs)); return; }
650
651         // retreive counts for the arguments of this notification
652         entity notif = Get_Notif_Ent(net_type, net_name);
653         if not(notif) { backtrace("Send_Notification: Could not find notification entity!\n"); return; }
654
655         if((notif.nent_stringcount + notif.nent_floatcount) > count)
656         {
657                 backtrace(sprintf(
658                         strcat(
659                                 "Not enough arguments for Send_Notification(%d, %s, %s, ...)! ",
660                                 "stringcount(%d) + floatcount(%d) > count(%d)\n", 
661                                 "Check the definition and function call for accuracy...?\n"
662                         ),
663                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name, notif.nent_stringcount, notif.nent_floatcount, count));
664                 return;
665         }
666         else if((notif.nent_stringcount + notif.nent_floatcount) < count)
667         {
668                 backtrace(sprintf(
669                         strcat(
670                                 "Too many arguments for Send_Notification(%d, %s, %s, ...)! ",
671                                 "stringcount(%d) + floatcount(%d) < count(%d)\n",
672                                 "Check the definition and function call for accuracy...?\n"
673                         ),
674                         broadcast, Get_Notif_TypeName(net_type), notif.nent_name, notif.nent_stringcount, notif.nent_floatcount, count));
675                 return;
676         }
677
678         #ifdef NOTIFICATIONS_DEBUG
679         string s1 = ((0 < notif.nent_stringcount) ? ...(0, string) : "");
680         string s2 = ((1 < notif.nent_stringcount) ? ...(1, string) : "");
681         string s3 = ((2 < notif.nent_stringcount) ? ...(2, string) : "");
682         string s4 = ((3 < notif.nent_stringcount) ? ...(3, string) : "");
683         float f1 = ((0 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 0), float) : 0);
684         float f2 = ((1 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 1), float) : 0);
685         float f3 = ((2 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 2), float) : 0);
686         float f4 = ((3 < notif.nent_floatcount) ? ...((notif.nent_stringcount + 3), float) : 0);
687         dprint(
688                 sprintf("Send_Notification(%d, %s, %s, %s, %s - %d %d);\n",
689                         broadcast,
690                         Get_Notif_TypeName(net_type),
691                         notif.nent_name,
692                         sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4),
693                         sprintf("%d, %d, %d, %d", f1, f2, f3, f4),
694                         notif.nent_stringcount, notif.nent_floatcount
695                 )
696         );
697         #endif
698
699         entity net_notif = spawn();
700         net_notif.classname = "net_notification";
701         net_notif.nent_broadcast = broadcast;
702         net_notif.nent_client = client;
703         net_notif.nent_net_type = net_type;
704         net_notif.nent_net_name = net_name;
705         net_notif.nent_stringcount = notif.nent_stringcount;
706         net_notif.nent_floatcount = notif.nent_floatcount;
707         
708         float i;
709         for(i = 0; i < net_notif.nent_stringcount; ++i) { net_notif.nent_strings[i] = strzone(...(i, string)); }
710         for(i = 0; i < net_notif.nent_floatcount; ++i) { net_notif.nent_floats[i] = ...((net_notif.nent_stringcount + i), float); }
711
712         net_notif.think = Net_Notification_Remove;
713         net_notif.nextthink =
714                 ((time > autocvar_notification_lifetime_mapload)
715                 ?
716                         (time + autocvar_notification_lifetime_runtime)
717                         :
718                         autocvar_notification_lifetime_mapload
719                 ); 
720
721         Net_LinkEntity(net_notif, FALSE, 0, Net_Write_Notification);
722
723         if(server_is_dedicated && (broadcast == NOTIF_ANY || broadcast == NOTIF_ANY_EXCEPT) && (net_type != MSG_CENTER))
724         {
725                 Local_Notification_WOVA(
726                         net_type, net_name,
727                         notif.nent_stringcount,
728                         notif.nent_floatcount,
729                         IFSTR(0), IFSTR(1), IFSTR(2), IFSTR(3),
730                         IFFL(0), IFFL(1), IFFL(2), IFFL(3));
731         }
732 }
733
734 // WOVA = Without Variable Arguments 
735 void Send_Notification_WOVA(float broadcast, entity client,
736         float net_type, float net_name,
737         string s1, string s2, string s3, string s4,
738         float f1, float f2, float f3, float f4)
739 {
740         entity notif = Get_Notif_Ent(net_type, net_name);
741         
742         #ifdef NOTIFICATIONS_DEBUG
743         dprint(
744                 sprintf("Send_Notification_WOVA(%d, %s, %s, %s, %s - %d %d);\n",
745                         broadcast,
746                         Get_Notif_TypeName(net_type),
747                         notif.nent_name,
748                         sprintf("'%s^7', '%s^7', '%s^7', '%s^7'", s1, s2, s3, s4),
749                         sprintf("%d, %d, %d, %d", f1, f2, f3, f4),
750                         notif.nent_stringcount, notif.nent_floatcount
751                 )
752         );
753         #endif
754         
755         #define VARITEM(stringc,floatc,args) \
756                 if((notif.nent_stringcount == stringc) && (notif.nent_floatcount == floatc)) \
757                         { Send_Notification(broadcast, client, net_type, net_name, args); return; }
758         EIGHT_VARS_TO_VARARGS_VARLIST
759         #undef VARITEM
760         Send_Notification(broadcast, client, net_type, net_name); // some notifications don't have any arguments at all
761 }
762
763
764 // =============================
765 //  LEGACY NOTIFICATION SYSTEMS
766 // =============================
767
768 void Send_CSQC_Centerprint_Generic(entity e, float id, string s, float duration, float countdown_num)
769 {
770         if ((clienttype(e) == CLIENTTYPE_REAL) && (e.flags & FL_CLIENT))
771         {
772                 msg_entity = e;
773                 WRITESPECTATABLE_MSG_ONE({
774                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
775                         WriteByte(MSG_ONE, TE_CSQC_CENTERPRINT_GENERIC);
776                         WriteByte(MSG_ONE, id);
777                         WriteString(MSG_ONE, s);
778                         if (id != 0 && s != "")
779                         {
780                                 WriteByte(MSG_ONE, duration);
781                                 WriteByte(MSG_ONE, countdown_num);
782                         }
783                 });
784         }
785 }
786 #endif // ifdef SVQC