]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
Finish adding deathtypes to autogenerated list, improve the networking
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / notifications.qc
1 // ================================================
2 //  Unified notification system, written by Samual
3 //  Last updated: September, 2012
4 // ================================================
5
6 // main types/groups of notifications
7 #define MSG_INFO 1 // "Global" information messages (sent to console, and notify panel if it has an icon)
8 #define MSG_CENTER 2 // "Personal" centerprint messages
9 #define MSG_WEAPON 3 // "Personal" weapon messages (like "You got the Nex", sent to weapon notify panel)
10
11 #define NO_STR_ARG ""
12 #define NO_FL_ARG -12345
13
14 #define F_NAME 1
15 #define F_STRNUM 2
16 #define F_FLNUM 3
17
18 // allow sending of notifications to also pass through to spectators (specifically for centerprints)
19 #ifdef SVQC
20 #define WRITESPECTATABLE_MSG_ONE_VARNAME(varname,statement) entity varname; varname = msg_entity; FOR_EACH_REALCLIENT(msg_entity) if(msg_entity == varname || (msg_entity.classname == STR_SPECTATOR && msg_entity.enemy == varname)) statement msg_entity = varname
21 #define WRITESPECTATABLE_MSG_ONE(statement) WRITESPECTATABLE_MSG_ONE_VARNAME(oldmsg_entity, statement)
22 #define WRITESPECTATABLE(msg,statement) if(msg == MSG_ONE) { WRITESPECTATABLE_MSG_ONE(statement); } else statement float WRITESPECTATABLE_workaround = 0
23 #endif
24
25 #define HANDLE_CPID(cpid) ((min(NOTIF_MAX, cpid) == NO_CPID) ? FALSE : cpid)
26 #define NOTIF_MATCH(a,b) if(min(NOTIF_MAX, a) == b)
27
28
29 // ====================================
30 //  Notifications List and Information
31 // ====================================
32 /*
33  List of all notifications (including identifiers and display information)
34  Format: name, strnum, flnum, args, *icon/CPID, *durcnt, normal, gentle
35  Asterisked fields are not present in all notification types.
36  Specifications:
37     Name of notification
38     Number of STRING arguments (so that networking knows how many to send/receive)
39     Number of FLOAT arguments (so that networking knows how many to send/receive)
40     Arguments for sprintf(string, args), if no args needed then use ""
41     *Icon/CPID:
42       MSG_INFO: STRING: icon string name for the hud notify panel, "" if no icon is used
43       MSG_CENTER: FLOAT: centerprint ID number (CPID_*), NO_CPID if no CPID is needed
44     *Duration/Countdown:
45       MSG_CENTER: XPND2(FLOAT, FLOAT): extra arguments for centerprint messages
46     Normal message (string for sprintf when gentle messages are NOT enabled)
47     Gentle message (string for sprintf when gentle messages ARE enabled)
48
49  Messages have ^F1, ^F2, and ^BG in them-- these are replaced
50  with colors according to the cvars the user has chosen.
51     ^F1 = highest priority, "primary"
52     ^F2 = next highest priority, "secondary"
53     ^BG = normal/less important priority, "tertiary"
54
55  Guidlines (please try and follow these):
56     ALWAYS start the string with a color, preferably background.
57     ALWAYS end messages with a new line.
58     ALWAYS properly use tab spacing to even out the notifications.
59     NEVER re-declare an event twice.
60     NEVER add or remove fields from the format, it SHOULD already work.
61     ARIRE unir frk jvgu lbhe bja zbgure. (gvc sbe zvxrrhfn) -- Don't pay attention to this ^_^
62     Be clean and simple with your notification naming, nothing too long.
63     Keep the notifications in alphabetical order.
64 */
65
66 // flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
67 // weaponorder[f1].netname
68 #define MSG_INFO_NOTIFICATIONS \
69         MSG_INFO_NOTIF(INFO_CTF_GOTFLAG_RED, 2, 1, XPND3(s1, s2, "foobar"), "notify_death", _("^F1%s^BG lost their marbles against ^F1%s^BG using the ^F2%s^BG\n"), "") \
70         #undef MSG_INFO_NOTIF
71
72 #define MSG_CENTER_NOTIFICATIONS \
73         MSG_CENTER_NOTIF(CENTER_CTF_CAPTURESHIELD_SHIELDED,             0, 0, NO_STR_ARG,                       CPID_CTF_CAPTURESHIELD, XPND2(0, 0), _("^BGYou are now ^F1shielded^BG from the flag\n^BGfor ^F2too many unsuccessful attempts^BG to capture.\n^BGMake some defensive scores before trying again."), "") \
74         MSG_CENTER_NOTIF(CENTER_CTF_CAPTURESHIELD_FREE,                 0, 0, NO_STR_ARG,                       CPID_CTF_CAPTURESHIELD, XPND2(0, 0), _("^BGYou are now free.\n^BGFeel free to ^F2try to capture^BG the flag again\n^BGif you think you will succeed."), "") \
75         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_PASS_RED,                     2, 0, XPND2(s1, s2),            CPID_CTF_PASS,                  XPND2(0, 0), _("^BG%s passed the ^1RED^BG flag to %s"), "") \
76         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_PASS_SENT_RED,                1, 0, s1,                                       CPID_CTF_PASS,                  XPND2(0, 0), _("^BGYou passed the ^1RED^BG flag to %s"), "") \
77         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_PASS_RECEIVED_RED,    1, 0, s1,                                       CPID_CTF_PASS,                  XPND2(0, 0), _("^BGYou received the ^1RED^BG flag from %s"), "") \
78         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_PASS_BLUE,                    2, 0, XPND2(s1, s2),            CPID_CTF_PASS,                  XPND2(0, 0), _("^BG%s passed the ^4BLUE^BG flag to %s"), "") \
79         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_PASS_SENT_BLUE,               1, 0, s1,                                       CPID_CTF_PASS,                  XPND2(0, 0), _("^BGYou passed the ^4BLUE^BG flag to %s"), "") \
80         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_PASS_RECEIVED_BLUE,   1, 0, s1,                                       CPID_CTF_PASS,                  XPND2(0, 0), _("^BGYou received the ^4BLUE^BG flag from %s"), "") \
81         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_RETURN,                               0, 0, NO_STR_ARG,                       CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYou returned the ^F1%s"), "") \
82         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_CAPTURE,                              0, 0, NO_STR_ARG,                       NO_CPID,                                XPND2(0, 0), _("^BGYou captured the ^F1%s"), "") \
83         #undef MSG_CENTER_NOTIF
84
85 #define MSG_WEAPON_NOTIFICATIONS \
86         MSG_WEAPON_NOTIF(DEATH_MARBLES_LOST3, 2, 1, XPND3(s1, s2, f1), _("^F1%s^BG lost their marbles against ^F1%s^BG using the ^F2%s^BG\n"), "") \
87         #undef MSG_WEAPON_NOTIF
88
89
90 // ====================================
91 //  Initialization/Create Declarations
92 // ====================================
93
94 #define NOTIF_FIRST 1
95 #define NOTIF_MAX 1024 // limit of recursive functions with ACCUMULATE_FUNCTION
96 float NOTIF_INFO_COUNT;
97 float NOTIF_CENTER_COUNT;
98 float NOTIF_WEAPON_COUNT;
99 float NOTIF_CPID_COUNT;
100
101 #ifdef CSQC
102 #define ADD_CSQC_AUTOCVAR(name) var float autocvar_notification_##name = TRUE;
103 #define CHECK_AUTOCVAR(name) if(autocvar_notification_##name)
104 #else
105 #define ADD_CSQC_AUTOCVAR(name)
106 #endif
107
108 #define MSG_INFO_NOTIF(name,strnum,flnum,args,icon,normal,gentle) \
109         ADD_CSQC_AUTOCVAR(name) \
110         float name; \
111         void RegisterNotification_##name() \
112         { \
113                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_INFO_COUNT) \
114                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_INFO_COUNT, "notifications") \
115         } \
116         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
117
118 #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
119         ADD_CSQC_AUTOCVAR(name) \
120         float name; \
121         float cpid; \
122         void RegisterNotification_##name() \
123         { \
124                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_CENTER_COUNT) \
125                 SET_FIELD_COUNT(cpid, NOTIF_FIRST, NOTIF_CPID_COUNT) \
126                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_CENTER_COUNT, "notifications") \
127         } \
128         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
129
130 #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) \
131         ADD_CSQC_AUTOCVAR(name) \
132         float name; \
133         void RegisterNotification_##name() \
134         { \
135                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_WEAPON_COUNT) \
136                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_WEAPON_COUNT, "notifications") \
137         } \
138         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
139
140 // NOW we actually activate the declarations
141 MSG_INFO_NOTIFICATIONS
142 MSG_CENTER_NOTIFICATIONS
143 MSG_WEAPON_NOTIFICATIONS
144
145
146 // ======================
147 //  Supporting Functions
148 // ======================
149
150 // select between the normal or the gentle message string based on client (or server) settings
151 string normal_or_gentle(string normal, string gentle)
152 {
153         #ifdef CSQC
154         if(autocvar_cl_gentle || autocvar_cl_gentle_messages)
155         #else
156         if(autocvar_sv_gentle)
157         #endif
158                 return ((gentle != "") ? gentle : normal);
159         else
160                 return normal;
161 }
162
163 float notif_stringcount(string s1, string s2)
164 {
165         float stringcount;
166         if(s1 != NO_STR_ARG) ++stringcount;
167         if(s2 != NO_STR_ARG) ++stringcount;
168         return stringcount;
169 }
170
171 float notif_floatcount(float f1, float f2, float f3)
172 {
173         float floatcount;
174         if(f1 != NO_FL_ARG) ++floatcount;
175         if(f2 != NO_FL_ARG) ++floatcount;
176         if(f3 != NO_FL_ARG) ++floatcount;
177         return floatcount;
178 }
179
180 #define GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) \
181         if(field == F_NAME) { output = VAR_TO_TEXT(name); } \
182         else if(field == F_STRNUM) { output = ftos(strnum); } \
183         else if(field == F_FLNUM) { output = ftos(flnum); }
184
185 // get the actual name of a notification and return it as a string
186 string Get_Field_Value(float field, float net_type, float net_name)
187 {
188         string output;
189         
190         switch(net_type)
191         {
192                 case MSG_INFO:
193                 {
194                         #define MSG_INFO_NOTIF(name,strnum,flnum,args,icon,normal,gentle) \
195                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
196                         MSG_INFO_NOTIFICATIONS
197                         break;
198                 }
199                 case MSG_CENTER:
200                 {
201                         #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
202                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
203                         MSG_CENTER_NOTIFICATIONS
204                         break;
205                 }
206                 case MSG_WEAPON:
207                 {
208                         #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) \
209                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
210                         MSG_WEAPON_NOTIFICATIONS
211                         break;
212                 }
213         }
214         
215         return output;
216 }
217
218 // color code replace, place inside of sprintf and parse the string
219 string CCR(string input)
220 {
221         input = strreplace("^F1", "^3", input); // autocvar_notification_colors_F1 
222         input = strreplace("^F2", "^2", input); // autocvar_notification_colors_F2
223         input = strreplace("^K1", "^1", input); // autocvar_notification_colors_K1
224         input = strreplace("^K2", "^5", input); // autocvar_notification_colors_K2
225         input = strreplace("^BG", "^7", input); // autocvar_notification_colors_BG
226
227         input = strreplace("^N", "^7", input); // "none"-- reset to white
228
229         return input;
230 }
231
232
233 // ===============================
234 //  Frontend Notification Pushing
235 // ===============================
236
237 #ifdef CSQC
238 void Local_Notification(float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
239 {
240         switch(net_type)
241         {
242                 case MSG_INFO:
243                 {
244                         #define MSG_INFO_NOTIF(name,strnum,flnum,args,icon,normal,gentle) \
245                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) { print(sprintf(CCR(normal_or_gentle(normal, gentle)), args)); } }
246                         MSG_INFO_NOTIFICATIONS
247                         break;
248                 }
249                 case MSG_CENTER:
250                 {
251                         #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
252                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) { centerprint_generic(HANDLE_CPID(cpid), sprintf(CCR(normal_or_gentle(normal, gentle)), args), durcnt); } }
253                         MSG_CENTER_NOTIFICATIONS
254                         break;
255                 }
256                 case MSG_WEAPON:
257                 {
258                         #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) \
259                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) { print("unhandled\n"); } }
260                         MSG_WEAPON_NOTIFICATIONS
261                         break;
262                 }
263         }
264 }
265 #endif
266
267
268 // =========================
269 //  Notification Networking
270 // =========================
271
272 #ifdef CSQC
273 void Read_Notification(void)
274 {
275         float net_type = ReadByte();
276         float net_name = ReadShort();
277
278         float stringcount = stof(Get_Field_Value(F_STRNUM, net_type, net_name));
279         float floatcount = stof(Get_Field_Value(F_FLNUM, net_type, net_name));  
280         
281         Local_Notification(net_type, net_name,
282                 ((stringcount >= 1) ? ReadString() : ""),
283                 ((stringcount == 2) ? ReadString() : ""),
284                 ((floatcount >= 1) ? ReadLong() : 0),
285                 ((floatcount >= 2) ? ReadLong() : 0),
286                 ((floatcount == 3) ? ReadLong() : 0));
287 }
288 #endif
289
290 #ifdef SVQC
291 void Send_Notification(float net_type, entity client, float net_name, string s1, string s2, float f1, float f2, float f3)
292 {
293         if(net_type && net_name)
294         {
295                 print("notification: ", Get_Field_Value(F_NAME, net_type, net_name), ": ", ftos(net_name), ".\n");
296
297                 float stringcount = stof(Get_Field_Value(F_STRNUM, net_type, net_name));
298                 float floatcount = stof(Get_Field_Value(F_FLNUM, net_type, net_name));
299                 
300                 if(notif_stringcount(s1, s2) > stringcount) { backtrace("Too many string arguments for notification!\n"); return; }
301                 if(notif_floatcount(f1, f2, f3) > floatcount) { backtrace("Too many float arguments for notification!\n"); return; }
302                 
303                 if(client && (clienttype(client) == CLIENTTYPE_REAL) && (client.flags & FL_CLIENT))
304                 {
305                         // personal/direct notification sent to ONE person and their spectators
306                         msg_entity = client;
307                         WRITESPECTATABLE_MSG_ONE({
308                                 WriteByte(MSG_ONE, SVC_TEMPENTITY);
309                                 WriteByte(MSG_ONE, TE_CSQC_NOTIFICATION);
310                                 WriteByte(MSG_ONE, net_type);
311                                 WriteShort(MSG_ONE, net_name);
312                                 if(stringcount >= 1) { WriteString(MSG_ONE, s1); }
313                                 if(stringcount == 2) { WriteString(MSG_ONE, s2); }
314                                 if(floatcount >= 1) { WriteLong(MSG_ONE, f1); }
315                                 if(floatcount >= 2) { WriteLong(MSG_ONE, f2); }
316                                 if(floatcount == 3) { WriteLong(MSG_ONE, f3); }
317                         });
318                 }
319                 else
320                 {
321                         // global notification sent to EVERYONE
322                         WriteByte(MSG_ALL, SVC_TEMPENTITY);
323                         WriteByte(MSG_ALL, TE_CSQC_NOTIFICATION);
324                         WriteByte(MSG_ALL, net_type);
325                         WriteShort(MSG_ALL, net_name);
326                         if(stringcount >= 1) { WriteString(MSG_ALL, s1); }
327                         if(stringcount == 2) { WriteString(MSG_ALL, s2); }
328                         if(floatcount >= 1) { WriteLong(MSG_ALL, f1); }
329                         if(floatcount >= 2) { WriteLong(MSG_ALL, f2); }
330                         if(floatcount == 3) { WriteLong(MSG_ALL, f3); }
331                 }
332
333                 if(!server_is_local && (net_type == MSG_INFO))
334                 {
335                         #define MSG_INFO_NOTIF(name,strnum,flnum,args,icon,normal,gentle) \
336                                 { NOTIF_MATCH(name, net_name) { print(sprintf(CCR(normal_or_gentle(normal, gentle)), args)); } }
337                         MSG_INFO_NOTIFICATIONS
338                 }
339         }
340         else { backtrace("Incorrect usage of Send_Notification!\n"); }
341 }
342
343 void Send_Notification_ToTeam(float targetteam, entity except, float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
344 {
345         entity tmp_entity;
346         FOR_EACH_REALCLIENT(tmp_entity)
347         {
348                 if(tmp_entity.classname == STR_PLAYER)
349                 if(tmp_entity.team == targetteam)
350                 if(tmp_entity != except)
351                 {
352                         Send_Notification(net_type, tmp_entity, net_name, s1, s2, f1, f2, f3);
353                 }
354         }
355 }
356
357 // WARNING: use this ONLY if you need exceptions or want to exclude spectators, otherwise use Send_Notification(..., world, ...)
358 void Send_Notification_ToAll(entity except, float spectators, float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
359 {
360         entity tmp_entity;
361         FOR_EACH_REALCLIENT(tmp_entity)
362         {
363                 if((tmp_entity.classname == STR_PLAYER) || spectators)
364                 if(tmp_entity != except)
365                 {
366                         Send_Notification(net_type, tmp_entity, net_name, s1, s2, f1, f2, f3);
367                 }
368         }
369 }
370
371
372 // =============================
373 //  LEGACY NOTIFICATION SYSTEMS
374 // =============================
375
376 void Send_KillNotification(string s1, string s2, string s3, float msg, float type)
377 {
378         WriteByte(MSG_ALL, SVC_TEMPENTITY);
379         WriteByte(MSG_ALL, TE_CSQC_KILLNOTIFY);
380         WriteString(MSG_ALL, s1);
381         WriteString(MSG_ALL, s2);
382         WriteString(MSG_ALL, s3);
383         WriteShort(MSG_ALL, msg);
384         WriteByte(MSG_ALL, type);
385 }
386
387 // Function is used to send a generic centerprint whose content CSQC gets to decide (gentle version or not in the below cases)
388 void Send_CSQC_KillCenterprint(entity e, string s1, string s2, float msg, float type)
389 {
390         if (clienttype(e) == CLIENTTYPE_REAL)
391         {
392                 msg_entity = e;
393                 WRITESPECTATABLE_MSG_ONE({
394                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
395                         WriteByte(MSG_ONE, TE_CSQC_KILLCENTERPRINT);
396                         WriteString(MSG_ONE, s1);
397                         WriteString(MSG_ONE, s2);
398                         WriteShort(MSG_ONE, msg);
399                         WriteByte(MSG_ONE, type);
400                 });
401         }
402 }
403
404 void Send_CSQC_Centerprint_Generic(entity e, float id, string s, float duration, float countdown_num)
405 {
406         if ((clienttype(e) == CLIENTTYPE_REAL) && (e.flags & FL_CLIENT))
407         {
408                 msg_entity = e;
409                 WRITESPECTATABLE_MSG_ONE({
410                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
411                         WriteByte(MSG_ONE, TE_CSQC_CENTERPRINT_GENERIC);
412                         WriteByte(MSG_ONE, id);
413                         WriteString(MSG_ONE, s);
414                         if (id != 0 && s != "")
415                         {
416                                 WriteByte(MSG_ONE, duration);
417                                 WriteByte(MSG_ONE, countdown_num);
418                         }
419                 });
420         }
421 }
422 void Send_CSQC_Centerprint_Generic_Expire(entity e, float id)
423 {
424         Send_CSQC_Centerprint_Generic(e, id, "", 1, 0);
425 }
426 #endif