]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
6dffb6656560ef11f430107ef5f85fe986c744bb
[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)
8 #define MSG_NOTIFY 2 // "Global" events to be sent to the notification panel
9 #define MSG_CENTER 3 // "Personal" centerprint messages
10 #define MSG_WEAPON 4 // "Personal" weapon messages (like "You got the Nex", sent to weapon notify panel)
11
12 // expand multiple arguments into one argument
13 #define XPND4(a,b,c,d) a, b, c, d
14 #define XPND3(a,b,c) a, b, c
15 #define XPND2(a,b) a, b
16
17 // allow sending of notifications to also pass through to spectators (specifically for centerprints)
18 #ifdef SVQC
19 #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
20 #define WRITESPECTATABLE_MSG_ONE(statement) WRITESPECTATABLE_MSG_ONE_VARNAME(oldmsg_entity, statement)
21 #define WRITESPECTATABLE(msg,statement) if(msg == MSG_ONE) { WRITESPECTATABLE_MSG_ONE(statement); } else statement float WRITESPECTATABLE_workaround = 0
22 #endif
23
24 #define HANDLE_CPID(cpid) ((min(NOTIF_MAX, cpid) == NO_CPID) ? FALSE : cpid)
25 #define NOTIF_MATCH(a,b) if(min(NOTIF_MAX, a) == b)
26 #define VAR_TO_TEXT(var) #var
27
28 #define CHECK_FIELD_AND_COUNT(field,count) if(!field) { field = (NOTIF_FIRST + count); ++count; }
29 #define CHECK_MAX_NOTIFICATIONS(name,count) if(count == NOTIF_MAX) { error(strcat("Maximum notifications hit: ", VAR_TO_TEXT(name), ": ", ftos(count), ".\n")); }
30
31
32 // ====================================
33 //  Notifications List and Information
34 // ====================================
35 /*
36  List of all notifications (including identifiers and display information)
37  Format: name, args, *icon/CPID, *durcnt, normal, gentle
38  Asterisked fields are not present in all notification types.
39  Specifications:
40     Name of notification
41     Arguments for sprintf(string, args), if no args needed then use ""
42     *Icon/CPID:
43       MSG_NOTIFY: STRING: icon string name for the hud notify panel, "" if no icon is used
44       MSG_CENTER: FLOAT: centerprint ID number (CPID_*), NO_CPID if no CPID is needed
45     *Duration/Countdown:
46       MSG_CENTER: XPND2(FLOAT, FLOAT): extra arguments for centerprint messages
47     Normal message (string for sprintf when gentle messages are NOT enabled)
48     Gentle message (string for sprintf when gentle messages ARE enabled)
49
50  Messages have ^F1, ^F2, and ^BG in them-- these are replaced
51  with colors according to the cvars the user has chosen.
52     ^F1 = highest priority, "primary"
53     ^F2 = next highest priority, "secondary"
54     ^BG = normal/less important priority, "tertiary"
55
56  Guidlines (please try and follow these):
57     ALWAYS start the string with a color, preferably background.
58     ALWAYS end messages with a new line.
59     ALWAYS properly use tab spacing to even out the notifications.
60     NEVER re-declare an event twice.
61     NEVER add or remove fields from the format, it SHOULD already work.
62     ARIRE unir frk jvgu lbhe bja zbgure. (gvc sbe zvxrrhfn) -- Don't pay attention to this ^_^
63     Be clean and simple with your notification naming, nothing too long.
64     Keep the notifications in alphabetical order.
65 */
66 #define MSG_INFO_NOTIFICATIONS \
67         MSG_INFO_NOTIF(DEATH_MARBLES_LOST, XPND3(s1, s2, s3), _("^F1%s^BG lost their marbles against ^F1%s^BG using the ^F2%s^BG\n"), "") \
68         #undef MSG_INFO_NOTIF
69
70 #define MSG_NOTIFY_NOTIFICATIONS \
71         MSG_NOTIFY_NOTIF(DEATH_MARBLES_LOST2, XPND3(s1, s2, s3), "notify_death", _("^F1%s^BG lost their marbles against ^F1%s^BG using the ^F2%s^BG\n"), "") \
72         #undef MSG_NOTIFY_NOTIF
73
74 #define MSG_CENTER_NOTIFICATIONS \
75         MSG_CENTER_NOTIF(CENTER_CTF_CAPTURESHIELD_SHIELDED,             "",                             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."), "") \
76         MSG_CENTER_NOTIF(CENTER_CTF_CAPTURESHIELD_FREE,                 "",                             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."), "") \
77         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_PASS,                                 XPND2(s1, s2, s3),      CPID_CTF_PASS,                  XPND2(0, 0), _("^BG%s passed the ^F1%s^BG to %s"), "") \
78         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_PASS_SENT,                    XPND2(s1, s2),          CPID_CTF_PASS,                  XPND2(0, 0), _("^BGYou passed the ^F1%s^BG to %s"), "") \
79         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_PASS_RECEIVED,                XPND2(s1, s2),          CPID_CTF_PASS,                  XPND2(0, 0), _("^BGYou received the ^F1%s^BG from %s"), "") \
80         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_RETURN,                               s1,                                     CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYou returned the ^F1%s"), "") \
81         MSG_CENTER_NOTIF(CENTER_CTF_EVENT_CAPTURE,                              s1,                                     NO_CPID,                                XPND2(0, 0), _("^BGYou captured the ^F1%s"), "") \
82         #undef MSG_CENTER_NOTIF
83
84 #define MSG_WEAPON_NOTIFICATIONS \
85         MSG_WEAPON_NOTIF(DEATH_MARBLES_LOST3, XPND3(s1, s2, s3), _("^F1%s^BG lost their marbles against ^F1%s^BG using the ^F2%s^BG\n"), "") \
86         #undef MSG_WEAPON_NOTIF
87
88
89 // ====================================
90 //  Initialization/Create Declarations
91 // ====================================
92
93 #define NOTIF_FIRST 1
94 #define NOTIF_MAX 1024 // limit of recursive functions with ACCUMULATE_FUNCTION
95 float NOTIF_INFO_COUNT;
96 float NOTIF_NOTIFY_COUNT;
97 float NOTIF_CENTER_COUNT;
98 float NOTIF_WEAPON_COUNT;
99 float NOTIF_CPID_COUNT;
100         
101 #define MSG_INFO_NOTIF(name,args,normal,gentle) \
102         float name; \
103         void DecNotif_##name() \
104         { \
105                 CHECK_FIELD_AND_COUNT(name, NOTIF_INFO_COUNT) \
106                 CHECK_MAX_NOTIFICATIONS(name, NOTIF_INFO_COUNT) \
107         } \
108         ACCUMULATE_FUNCTION(DecNotifs, DecNotif_##name)
109
110 #define MSG_NOTIFY_NOTIF(name,args,icon,normal,gentle) \
111         float name; \
112         void DecNotif_##name() \
113         { \
114                 CHECK_FIELD_AND_COUNT(name, NOTIF_NOTIFY_COUNT) \
115                 CHECK_MAX_NOTIFICATIONS(name, NOTIF_NOTIFY_COUNT) \
116         } \
117         ACCUMULATE_FUNCTION(DecNotifs, DecNotif_##name)
118
119 #define MSG_CENTER_NOTIF(name,args,cpid,durcnt,normal,gentle) \
120         float name; \
121         float cpid; \
122         void DecNotif_##name() \
123         { \
124                 CHECK_FIELD_AND_COUNT(name, NOTIF_CENTER_COUNT) \
125                 CHECK_FIELD_AND_COUNT(cpid, NOTIF_CPID_COUNT) \
126                 CHECK_MAX_NOTIFICATIONS(name, NOTIF_CENTER_COUNT) \
127         } \
128         ACCUMULATE_FUNCTION(DecNotifs, DecNotif_##name)
129
130 #define MSG_WEAPON_NOTIF(name,args,normal,gentle) \
131         float name; \
132         void DecNotif_##name() \
133         { \
134                 CHECK_FIELD_AND_COUNT(name, NOTIF_WEAPON_COUNT) \
135                 CHECK_MAX_NOTIFICATIONS(name, NOTIF_WEAPON_COUNT) \
136         } \
137         ACCUMULATE_FUNCTION(DecNotifs, DecNotif_##name)
138
139 // NOW we actually activate the declarations
140 MSG_INFO_NOTIFICATIONS
141 MSG_NOTIFY_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 // get the actual name of a notification and return it as a string
164 string Get_Notif_Name(float net_type, float net_name)
165 {
166         switch(net_type)
167         {
168                 case MSG_INFO:
169                 {
170                         #define MSG_INFO_NOTIF(name,args,normal,gentle) \
171                                 { NOTIF_MATCH(name,net_name) { return VAR_TO_TEXT(name); } }
172                         MSG_INFO_NOTIFICATIONS
173                         break;
174                 }
175                 case MSG_NOTIFY:
176                 {
177                         #define MSG_NOTIFY_NOTIF(name,args,icon,normal,gentle) \
178                                 { NOTIF_MATCH(name,net_name) { return VAR_TO_TEXT(name); } }
179                         MSG_NOTIFY_NOTIFICATIONS
180                         break;
181                 }
182                 case MSG_CENTER:
183                 {
184                         #define MSG_CENTER_NOTIF(name,args,cpid,durcnt,normal,gentle) \
185                                 { NOTIF_MATCH(name,net_name) { return VAR_TO_TEXT(name); } }
186                         MSG_CENTER_NOTIFICATIONS
187                         break;
188                 }
189                 case MSG_WEAPON:
190                 {
191                         #define MSG_WEAPON_NOTIF(name,args,normal,gentle) \
192                                 { NOTIF_MATCH(name,net_name) { return VAR_TO_TEXT(name); } }
193                         MSG_WEAPON_NOTIFICATIONS
194                         break;
195                 }
196         }
197         return "";
198 }
199
200 // color code replace, place inside of sprintf and parse the string
201 string CCR(string input)
202 {
203         input = strreplace("^F1", "^3", input);
204         input = strreplace("^F2", "^2", input);
205         input = strreplace("^K1", "^1", input);
206         input = strreplace("^K2", "^5", input);
207         input = strreplace("^BG", "^7", input);
208
209         input = strreplace("^N", "^7", input); // "none"-- reset to white
210
211         return input;
212 }
213
214
215 // ===============================
216 //  Frontend Notification Pushing
217 // ===============================
218
219 #ifdef CSQC
220 void Local_Notification(float net_type, float net_name, string s1, string s2, string s3)
221 {
222         switch(net_type)
223         {
224                 case MSG_INFO:
225                 {
226                         #define MSG_INFO_NOTIF(name,args,normal,gentle) \
227                                 { NOTIF_MATCH(name, net_name) { print(sprintf(CCR(normal_or_gentle(normal, gentle)), args)); } }
228
229                         MSG_INFO_NOTIFICATIONS
230                         break;
231                 }
232                 case MSG_NOTIFY:
233                 {
234                         #define MSG_NOTIFY_NOTIF(name,args,icon,normal,gentle) \
235                                 { NOTIF_MATCH(name,net_name) { print("unhandled\n"); } }
236                                 
237                         MSG_NOTIFY_NOTIFICATIONS
238                         break;
239                 }
240                 case MSG_CENTER:
241                 {
242                         #define MSG_CENTER_NOTIF(name,args,cpid,durcnt,normal,gentle) \
243                                 { NOTIF_MATCH(name, net_name) { centerprint_generic(HANDLE_CPID(cpid), sprintf(CCR(normal_or_gentle(normal, gentle)), args), durcnt); } }
244
245                         MSG_CENTER_NOTIFICATIONS
246                         break;
247                 }
248                 case MSG_WEAPON:
249                 {
250                         #define MSG_WEAPON_NOTIF(name,args,normal,gentle) \
251                                 { NOTIF_MATCH(name,net_name) { print("unhandled\n"); } }
252                                 
253                         MSG_WEAPON_NOTIFICATIONS
254                         break;
255                 }
256         }
257 }
258 #endif
259
260
261 // =========================
262 //  Notification Networking
263 // =========================
264
265 #ifdef SVQC
266 void Send_Notification(float net_type, entity client, float net_name, string s1, string s2, string s3)
267 {
268         if(net_type && net_name)
269         {
270                 print("notification: ", Get_Notif_Name(net_type, net_name), ": ", ftos(net_name), ".\n");
271                 
272                 if(client && (clienttype(client) == CLIENTTYPE_REAL) && (client.flags & FL_CLIENT))
273                 {
274                         // personal/direct notification sent to ONE person and their spectators
275                         msg_entity = client;
276                         WRITESPECTATABLE_MSG_ONE({
277                                 WriteByte(MSG_ONE, SVC_TEMPENTITY);
278                                 WriteByte(MSG_ONE, TE_CSQC_NOTIFICATION);
279                                 WriteShort(MSG_ONE, net_type);
280                                 WriteCoord(MSG_ONE, net_name);
281                                 WriteString(MSG_ONE, s1);
282                                 WriteString(MSG_ONE, s2);
283                                 WriteString(MSG_ALL, s3);
284                         });
285                 }
286                 else
287                 {
288                         // global notification sent to EVERYONE
289                         WriteByte(MSG_ALL, SVC_TEMPENTITY);
290                         WriteByte(MSG_ALL, TE_CSQC_NOTIFICATION);
291                         WriteShort(MSG_ALL, net_type);
292                         WriteCoord(MSG_ALL, net_name);
293                         WriteString(MSG_ALL, s1);
294                         WriteString(MSG_ALL, s2);
295                         WriteString(MSG_ALL, s3);
296                 }
297
298                 if(!server_is_local && ((net_type == MSG_INFO || net_type == MSG_NOTIFY) || client == world))
299                 {
300                         switch(net_type)
301                         {
302                                 case MSG_INFO:
303                                 {
304                                         #define MSG_INFO_NOTIF(name,args,normal,gentle) \
305                                                 { NOTIF_MATCH(name, net_name) { print(sprintf(CCR(normal_or_gentle(normal, gentle)), args)); } }
306
307                                         MSG_INFO_NOTIFICATIONS
308                                         break;
309                                 }
310
311                                 case MSG_NOTIFY:
312                                 {
313                                         #define MSG_NOTIFY_NOTIF(name,args,icon,normal,gentle) \
314                                                 { NOTIF_MATCH(name,net_name) { print("unhandled\n"); } }
315                                 
316                                         MSG_NOTIFY_NOTIFICATIONS
317                                         break;
318                                 }
319                         }
320                 }
321         }
322         else { backtrace("Incorrect usage of Send_Notification!\n"); }
323 }
324
325 void Send_Notification_ToTeam(float targetteam, entity except, float net_type, float net_name, string s1, string s2, string s3)
326 {
327         entity tmp_entity;
328         FOR_EACH_REALCLIENT(tmp_entity)
329         {
330                 if(tmp_entity.classname == STR_PLAYER)
331                 if(tmp_entity.team == targetteam)
332                 if(tmp_entity != except)
333                 {
334                         Send_Notification(net_type, tmp_entity, net_name, s1, s2, s3);
335                 }
336         }
337 }
338
339 // use this ONLY if you need exceptions or want to exclude spectators, otherwise use Send_Notification(..., world, ...)
340 void Send_Notification_ToAll(entity except, float spectators, float net_type, float net_name, string s1, string s2, string s3)
341 {
342         entity tmp_entity;
343         FOR_EACH_REALCLIENT(tmp_entity)
344         {
345                 if((tmp_entity.classname == STR_PLAYER) || spectators)
346                 if(tmp_entity != except)
347                 {
348                         Send_Notification(net_type, tmp_entity, net_name, s1, s2, s3);
349                 }
350         }
351 }
352
353 // LEGACY NOTIFICATION SYSTEMS
354 void Send_KillNotification(string s1, string s2, string s3, float msg, float type)
355 {
356         WriteByte(MSG_ALL, SVC_TEMPENTITY);
357         WriteByte(MSG_ALL, TE_CSQC_KILLNOTIFY);
358         WriteString(MSG_ALL, s1);
359         WriteString(MSG_ALL, s2);
360         WriteString(MSG_ALL, s3);
361         WriteShort(MSG_ALL, msg);
362         WriteByte(MSG_ALL, type);
363 }
364
365 // Function is used to send a generic centerprint whose content CSQC gets to decide (gentle version or not in the below cases)
366 void Send_CSQC_KillCenterprint(entity e, string s1, string s2, float msg, float type)
367 {
368         if (clienttype(e) == CLIENTTYPE_REAL)
369         {
370                 msg_entity = e;
371                 WRITESPECTATABLE_MSG_ONE({
372                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
373                         WriteByte(MSG_ONE, TE_CSQC_KILLCENTERPRINT);
374                         WriteString(MSG_ONE, s1);
375                         WriteString(MSG_ONE, s2);
376                         WriteShort(MSG_ONE, msg);
377                         WriteByte(MSG_ONE, type);
378                 });
379         }
380 }
381
382 void Send_CSQC_Centerprint_Generic(entity e, float id, string s, float duration, float countdown_num)
383 {
384         if ((clienttype(e) == CLIENTTYPE_REAL) && (e.flags & FL_CLIENT))
385         {
386                 msg_entity = e;
387                 WRITESPECTATABLE_MSG_ONE({
388                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
389                         WriteByte(MSG_ONE, TE_CSQC_CENTERPRINT_GENERIC);
390                         WriteByte(MSG_ONE, id);
391                         WriteString(MSG_ONE, s);
392                         if (id != 0 && s != "")
393                         {
394                                 WriteByte(MSG_ONE, duration);
395                                 WriteByte(MSG_ONE, countdown_num);
396                         }
397                 });
398         }
399 }
400 void Send_CSQC_Centerprint_Generic_Expire(entity e, float id)
401 {
402         Send_CSQC_Centerprint_Generic(e, id, "", 1, 0);
403 }
404 #endif