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