]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
Refactor obituary code, and move the frag messages over to new system :D
[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 BOT_PING -1
26
27 #define NOTIF_MATCH(a,b) if(min(NOTIF_MAX, a) == b)
28 #ifdef CSQC
29 string got_commandkey;
30 #define ADD_CSQC_AUTOCVAR(name) var float autocvar_notification_##name = TRUE;
31 var float autocvar_notification_ctf_capture_verbose = TRUE;
32 var float autocvar_notification_ctf_pickup_team_verbose = TRUE;
33 var float autocvar_notification_ctf_pickup_enemy_verbose = TRUE;
34 #define CHECK_AUTOCVAR(name) if(autocvar_notification_##name)
35 #define HANDLE_CPID(cpid) ((min(NOTIF_MAX, cpid) == NO_CPID) ? FALSE : cpid)
36 #define PASS_KEY ((((got_commandkey = getcommandkey("pass", "+use")) != "pass") && !(strstrofs(got_commandkey, "not bound", 0) >= 0)) ? sprintf(CCR(_(" ^F1(Press %s)")), got_commandkey) : "")
37 #define FRAG_SPREE (((f1 == 3) || (f1 == 5) || (f1 == 10) || (f1 == 15) || (f1 == 20) || (f1 == 25) || (f1 == 30)) ? sprintf(normal_or_gentle(_("%d kill spree! "), _("%d score spree! ")), f1) : "")
38 #define FRAG_PING ((f2 != BOT_PING) ? sprintf(CCR(_("\n(Ping ^2%d^BG)")), f2) : "")
39 #define FRAG_STATS sprintf(CCR(_("\n(Health ^1%d^BG / Armor ^2%d^BG)%s")), f1, f2, ((f3 != BOT_PING) ? sprintf(CCR(_(" (Ping ^2%d^BG)")), f3) : ""))
40 //#define FRAG_POS ((Should_Print_Score_Pos(f1)) ? sprintf("\n^BG%s", Read_Score_Pos(f1)) : "")
41 #else
42 #define ADD_CSQC_AUTOCVAR(name)
43 #endif
44
45
46 /*
47         If BELOW negative maxplayers, you dropped a place lower
48         If below 0, you are tied for that place
49         If above 0, you are holding that place alone
50         If above positive maxplayers, you moved up a place
51
52 float Should_Print_Score_Pos
53
54 string Read_Score_Pos(float num)
55 {
56         
57 }
58
59 float Form_Score_Pos(entity player)
60 {
61         return 
62 }*/
63
64 // ====================================
65 //  Notifications List and Information
66 // ====================================
67 /*
68  List of all notifications (including identifiers and display information)
69  Format: name, strnum, flnum, args, *icon/CPID, *durcnt, normal, gentle
70  Asterisked fields are not present in all notification types.
71  Specifications:
72     Name of notification
73     Number of STRING arguments (so that networking knows how many to send/receive)
74     Number of FLOAT arguments (so that networking knows how many to send/receive)
75     Arguments for sprintf(string, args), if no args needed then use ""
76     *Icon/CPID:
77       MSG_INFO: STRING: icon string name for the hud notify panel, "" if no icon is used
78       MSG_CENTER: FLOAT: centerprint ID number (CPID_*), NO_CPID if no CPID is needed
79     *Duration/Countdown:
80       MSG_CENTER: XPND2(FLOAT, FLOAT): extra arguments for centerprint messages
81     Normal message (string for sprintf when gentle messages are NOT enabled)
82     Gentle message (string for sprintf when gentle messages ARE enabled)
83
84  Messages have ^F1, ^F2, and ^BG in them-- these are replaced
85  with colors according to the cvars the user has chosen.
86     ^F1 = highest priority, "primary"
87     ^F2 = next highest priority, "secondary"
88     ^BG = normal/less important priority, "tertiary"
89
90  Guidlines (please try and follow these):
91     -ALWAYS start the string with a color, preferably background.
92     -ALWAYS reset a color after a name (this way they don't set it for the whole string).
93     -NEVER re-declare an event twice.
94     -NEVER add or remove fields from the format, it SHOULD already work.
95     -MSG_INFO messages must ALWAYS end with a new line: \n
96     -Be clean and simple with your notification naming,
97      nothing too long for the name field... Abbreviations are your friend. :D
98     -Keep the spacing as clean as possible... if the arguments are abnormally long,
99       it's okay to go out of line a bit... but try and keep it clean still.
100     -Keep the notifications in alphabetical order.
101     ARIRE unir frk jvgu lbhe bja zbgure. (gvc sbe zvxrrhfn) -- Don't pay attention to this ^_^
102 */
103
104 // weaponorder[f1].netname
105
106 #define MULTITEAM_INFO(prefix,teams,strnum,flnum,args,hudargs,icon,normal,gentle) \
107         MSG_INFO_NOTIF(prefix##RED, strnum, flnum, args, hudargs, sprintf(icon, strtolower(STR_TEAM_1)), TCR(normal, COL_TEAM_1, strtoupper(STR_TEAM_1)), TCR(gentle, COL_TEAM_1, strtoupper(STR_TEAM_1))) \
108         MSG_INFO_NOTIF(prefix##BLUE, strnum, flnum, args, hudargs, sprintf(icon, strtolower(STR_TEAM_2)), TCR(normal, COL_TEAM_2, strtoupper(STR_TEAM_2)), TCR(gentle, COL_TEAM_2, strtoupper(STR_TEAM_2))) \
109         #if teams >= 3 \
110                 MSG_INFO_NOTIF(prefix##YELLOW, strnum, flnum, args, hudargs, sprintf(icon, strtolower(STR_TEAM_3)), TCR(normal, COL_TEAM_3, strtoupper(STR_TEAM_3)), TCR(gentle, COL_TEAM_3, strtoupper(STR_TEAM_3))) \
111         #endif \
112         #if teams >= 4 \
113                 MSG_INFO_NOTIF(prefix##PINK, strnum, flnum, args, hudargs, sprintf(icon, strtolower(STR_TEAM_4)), TCR(normal, COL_TEAM_4, strtoupper(STR_TEAM_4)), TCR(gentle, COL_TEAM_4, strtoupper(STR_TEAM_4))) \
114         #endif
115 #define MSG_INFO_NOTIFICATIONS \
116         MSG_INFO_NOTIF(INFO_EMPTY,                                                      0, 0, NO_STR_ARG, XPND2("", ""),                                        "", "", "") \
117         MULTITEAM_INFO(INFO_SCORES_, 4,                                         0, 0, NO_STR_ARG, XPND2("", ""),                                        "", _("^TC^TT ^BGteam scores!\n"), "") \
118         MULTITEAM_INFO(INFO_CTF_FLAGRETURN_DROPPED_, 2,         0, 0, NO_STR_ARG, XPND2("", ""),                                        "", _("^BGThe ^TC^TT^BG flag was dropped in the base and returned itself\n"), "") \
119         MULTITEAM_INFO(INFO_CTF_FLAGRETURN_DAMAGED_, 2,         0, 0, NO_STR_ARG, XPND2("", ""),                                        "", _("^BGThe ^TC^TT^BG flag was destroyed and returned to base\n"), "") \
120         MULTITEAM_INFO(INFO_CTF_FLAGRETURN_SPEEDRUN_, 2,        0, 1, f1/100, XPND2("", ""),                                            "", _("^BGThe ^TC^TT^BG flag became impatient after ^F1%.2f^BG seconds and returned itself\n"), "") \
121         MULTITEAM_INFO(INFO_CTF_FLAGRETURN_NEEDKILL_, 2,        0, 0, NO_STR_ARG, XPND2("", ""),                                        "", _("^BGThe ^TC^TT^BG flag fell somewhere it couldn't be reached and returned to base\n"), "") \
122         MULTITEAM_INFO(INFO_CTF_FLAGRETURN_ABORTRUN_, 2,        0, 0, NO_STR_ARG, XPND2("", ""),                                        "", _("^BGThe ^TC^TT^BG flag was returned to base by its owner\n"), "") \
123         MULTITEAM_INFO(INFO_CTF_FLAGRETURN_TIMEOUT_, 2,         0, 0, NO_STR_ARG, XPND2("", ""),                                        "", _("^BGThe ^TC^TT^BG flag has returned to the base\n"), "") \
124         MULTITEAM_INFO(INFO_CTF_PICKUP_, 2,                                     1, 0, s1, XPND2(s1, ""),                                                        "notify_%s_taken", _("^BG%s^BG got the ^TC^TT^BG flag\n"), "") \
125         MULTITEAM_INFO(INFO_CTF_RETURN_, 2,                                     1, 0, s1, XPND2(s1, ""),                                                        "notify_%s_returned", _("^BG%s^BG returned the ^TC^TT^BG flag\n"), "") \
126         MULTITEAM_INFO(INFO_CTF_LOST_, 2,                                       1, 0, s1, XPND2(s1, ""),                                                        "notify_%s_lost", _("^BG%s^BG lost the ^TC^TT^BG flag\n"), "") \
127         MULTITEAM_INFO(INFO_CTF_CAPTURE_, 2,                            1, 0, s1, XPND2(s1, ""),                                                        "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag\n"), "") \
128         MULTITEAM_INFO(INFO_CTF_CAPTURE_TIME_, 2,                       1, 1, XPND2(s1, f1/100), XPND2(s1, ""),                         "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F1%.2f^BG seconds\n"), "") \
129         MULTITEAM_INFO(INFO_CTF_CAPTURE_BROKEN_, 2,                     2, 2, XPND4(s1, f1/100, s2, f2/100), XPND2(s1, ""),     "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F1%.2f^BG seconds, breaking ^BG%s^BG's previous record of ^F2%.2f^BG seconds\n"), "") \
130         MULTITEAM_INFO(INFO_CTF_CAPTURE_UNBROKEN_, 2,           2, 2, XPND4(s1, f1/100, s2, f2/100), XPND2(s1, ""),     "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag in ^F2%.2f^BG seconds, failing to break ^BG%s^BG's previous record of ^F1%.2f^BG seconds\n"), "") \
131         #undef MSG_INFO_NOTIF
132
133 #define MULTITEAM_CENTER(prefix,teams,strnum,flnum,args,cpid,durcnt,normal,gentle) \
134         MSG_CENTER_NOTIF(prefix##RED, strnum, flnum, args, cpid, durcnt, TCR(normal, COL_TEAM_1, strtoupper(STR_TEAM_1)), TCR(gentle, COL_TEAM_1, strtoupper(STR_TEAM_1))) \
135         MSG_CENTER_NOTIF(prefix##BLUE, strnum, flnum, args, cpid, durcnt, TCR(normal, COL_TEAM_2, strtoupper(STR_TEAM_2)), TCR(gentle, COL_TEAM_2, strtoupper(STR_TEAM_2))) \
136         #if teams >= 3 \
137                 MSG_CENTER_NOTIF(prefix##YELLOW, strnum, flnum, args, cpid, durcnt, TCR(normal, COL_TEAM_3, strtoupper(STR_TEAM_3)), TCR(gentle, COL_TEAM_3, strtoupper(STR_TEAM_3))) \
138         #endif \
139         #if teams >= 4 \
140                 MSG_CENTER_NOTIF(prefix##PINK, strnum, flnum, args, cpid, durcnt, TCR(normal, COL_TEAM_4, strtoupper(STR_TEAM_4)), TCR(gentle, COL_TEAM_4, strtoupper(STR_TEAM_4))) \
141         #endif
142 #define MSG_CENTER_NOTIFICATIONS \
143         MSG_CENTER_NOTIF(CENTER_EMPTY,                                                  0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), "", "") \
144         MSG_CENTER_NOTIF(CENTER_CTF_CAPTURESHIELD_SHIELDED,             0, 0, NO_STR_ARG,                               CPID_CTF_CAPSHIELD,             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."), "") \
145         MSG_CENTER_NOTIF(CENTER_CTF_CAPTURESHIELD_FREE,                 0, 0, NO_STR_ARG,                               CPID_CTF_CAPSHIELD,             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."), "") \
146         MULTITEAM_CENTER(CENTER_CTF_PASS_OTHER_, 2,                             2, 0, XPND2(s1, s2),                    CPID_CTF_PASS,                  XPND2(0, 0), _("^BG%s^BG passed the ^TC^TT^BG flag to %s"), "") \
147         MULTITEAM_CENTER(CENTER_CTF_PASS_SENT_, 2,                              1, 0, s1,                                               CPID_CTF_PASS,                  XPND2(0, 0), _("^BGYou passed the ^TC^TT^BG flag to %s"), "") \
148         MULTITEAM_CENTER(CENTER_CTF_PASS_RECEIVED_, 2,                  1, 0, s1,                                               CPID_CTF_PASS,                  XPND2(0, 0), _("^BGYou received the ^TC^TT^BG flag from %s"), "") \
149         MSG_CENTER_NOTIF(CENTER_CTF_PASS_REQUESTING,                    1, 0, s1,                                               CPID_CTF_PASS,                  XPND2(0, 0), _("^BGRequesting %s^BG to pass you the flag"), "") \
150         MSG_CENTER_NOTIF(CENTER_CTF_PASS_REQUESTED,                     1, 0, XPND2(s1, PASS_KEY),              CPID_CTF_PASS,                  XPND2(0, 0), _("^BG%s^BG requests you to pass the flag%s"), "") \
151         MULTITEAM_CENTER(CENTER_CTF_RETURN_, 2,                                 0, 0, NO_STR_ARG,                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYou returned the ^TC^TT^BG flag!"), "") \
152         MULTITEAM_CENTER(CENTER_CTF_CAPTURE_, 2,                                0, 0, NO_STR_ARG,                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYou captured the ^TC^TT^BG flag!"), "") \
153         MULTITEAM_CENTER(CENTER_CTF_PICKUP_, 2,                                 0, 0, NO_STR_ARG,                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYou got the ^TC^TT^BG flag!"), "") \
154         MSG_CENTER_NOTIF(CENTER_CTF_PICKUP_TEAM,                                1, 0, s1,                                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYour %steam mate^BG got the flag! Protect them!"), "") \
155         MSG_CENTER_NOTIF(CENTER_CTF_PICKUP_TEAM_VERBOSE,                2, 0, XPND3(s1, s2, s1),                CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYour %steam mate (^BG%s%s)^BG got the flag! Protect them!"), "") \
156         MSG_CENTER_NOTIF(CENTER_CTF_PICKUP_ENEMY,                               1, 0, s1,                                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGThe %senemy^BG got your flag! Retrieve it!"), "") \
157         MSG_CENTER_NOTIF(CENTER_CTF_PICKUP_ENEMY_VERBOSE,               2, 0, XPND3(s1, s2, s1),                CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGThe %senemy (^BG%s%s)^BG got your flag! Retrieve it!"), "") \
158         MSG_CENTER_NOTIF(CENTER_CTF_STALEMATE_CARRIER,                  0, 0, NO_STR_ARG,                               CPID_STALEMATE,                 XPND2(0, 0), _("^BGStalemate! Enemies can now see you on radar!"), "") \
159         MSG_CENTER_NOTIF(CENTER_CTF_STALEMATE_OTHER,                    0, 0, NO_STR_ARG,                               CPID_STALEMATE,                 XPND2(0, 0), _("^BGStalemate! Flag carriers can now be seen by enemies on radar!"), "") \
160         MSG_CENTER_NOTIF(CENTER_CTF_FLAG_THROW_PUNISH,                  0, 1, f1,                                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGToo many flag throws! Throwing disabled for %d seconds."), "") \
161         MSG_CENTER_NOTIF(CENTER_DEATH_FRAG,                                             1, 1, XPND2(FRAG_SPREE, s1),                                                    NO_CPID, XPND2(0, 0), _("^K3%sYou fragged ^BG%s"), _("^K3%sYou scored against ^BG%s")) \
162         MSG_CENTER_NOTIF(CENTER_DEATH_FRAGGED,                                  1, 0, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K1You were fragged by ^BG%s"), _("^K1You were scored against by ^BG%s")) \
163         MSG_CENTER_NOTIF(CENTER_DEATH_TYPEFRAG,                                 1, 1, XPND2(FRAG_SPREE, s1),                                                    NO_CPID, XPND2(0, 0), _("^K1%sYou typefragged ^BG%s"), _("^K1%sYou scored against ^BG%s^K1 while they were typing")) \
164         MSG_CENTER_NOTIF(CENTER_DEATH_TYPEFRAGGED,                              1, 0, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K1You were typefragged by ^BG%s"), _("^K1You were scored against by ^BG%s^K1 while typing!")) \
165         MSG_CENTER_NOTIF(CENTER_DEATH_FRAG_FIRST,                               1, 0, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K3First blood! You fragged ^BG%s"), _("^K3First score! You scored against ^BG%s")) \
166         MSG_CENTER_NOTIF(CENTER_DEATH_FRAGGED_FIRST,                    1, 0, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K1First victim! You were fragged by ^BG%s"), _("^K1First casualty! You were scored against by ^BG%s")) \
167         MSG_CENTER_NOTIF(CENTER_DEATH_TYPEFRAG_FIRST,                   1, 0, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K1First blood! You typefragged ^BG%s"), _("^K1First score! You scored against ^BG%s^K1 while they were typing")) \
168         MSG_CENTER_NOTIF(CENTER_DEATH_TYPEFRAGGED_FIRST,                1, 0, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K1First victim! You were typefragged by ^BG%s"), _("^K1First casualty! You were scored against by ^BG%s^K1 while typing!")) \
169         MSG_CENTER_NOTIF(CENTER_DEATH_FRAG_VERBOSE,                             1, 2, XPND3(FRAG_SPREE, s1, FRAG_PING),                                 NO_CPID, XPND2(0, 0), _("^K3You fragged ^BG%s^BG%s"), _("^K3You scored against ^BG%s^BG%s")) \
170         MSG_CENTER_NOTIF(CENTER_DEATH_FRAGGED_VERBOSE,                  1, 3, XPND2(s1, FRAG_STATS),                                                    NO_CPID, XPND2(0, 0), _("^K1You were fragged by ^BG%s^BG%s"), _("^K1You were scored against by ^BG%s^BG%s")) \
171         MSG_CENTER_NOTIF(CENTER_DEATH_TYPEFRAG_VERBOSE,                 1, 2, XPND3(FRAG_SPREE, s1, FRAG_PING),                                 NO_CPID, XPND2(0, 0), _("^K1You typefragged ^BG%s^BG%s"), _("^K1You scored against ^BG%s^K1 while they were typing^BG%s")) \
172         MSG_CENTER_NOTIF(CENTER_DEATH_TYPEFRAGGED_VERBOSE,              1, 3, XPND2(s1, FRAG_STATS),                                                    NO_CPID, XPND2(0, 0), _("^K1You were typefragged by ^BG%s^BG%s"), _("^K1You were scored against by ^BG%s^K1 while typing^BG%s")) \
173         MSG_CENTER_NOTIF(CENTER_DEATH_FRAG_FIRST_VERBOSE,               1, 1, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K3First blood! You fragged ^BG%s"), _("^K3First score! You scored against ^BG%s")) \
174         MSG_CENTER_NOTIF(CENTER_DEATH_FRAGGED_FIRST_VERBOSE,    1, 3, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K1First victim! You were fragged by ^BG%s"), _("^K1First casualty! You were scored against by ^BG%s")) \
175         MSG_CENTER_NOTIF(CENTER_DEATH_TYPEFRAG_FIRST_VERBOSE,   1, 1, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K1First blood! You typefragged ^BG%s"), _("^K1First score! You scored against ^BG%s^K1 while they were typing")) \
176         MSG_CENTER_NOTIF(CENTER_DEATH_TYPEFRAGGED_FIRST_VERBOSE,1, 3, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K1First victim! You were typefragged by ^BG%s"), _("^K1First casualty! You were scored against by ^BG%s^K1 while typing!")) \
177         #undef MSG_CENTER_NOTIF
178
179 #define MSG_WEAPON_NOTIFICATIONS \
180         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"), "") \
181         #undef MSG_WEAPON_NOTIF
182
183
184 // ====================================
185 //  Initialization/Create Declarations
186 // ====================================
187
188 #define NOTIF_FIRST 1
189 #define NOTIF_MAX 1024 // limit of recursive functions with ACCUMULATE_FUNCTION
190 float NOTIF_INFO_COUNT;
191 float NOTIF_CENTER_COUNT;
192 float NOTIF_WEAPON_COUNT;
193 float NOTIF_CPID_COUNT;
194
195 #define MSG_INFO_NOTIF(name,strnum,flnum,args,icon,normal,gentle) \
196         ADD_CSQC_AUTOCVAR(name) \
197         float name; \
198         void RegisterNotification_##name() \
199         { \
200                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_INFO_COUNT) \
201                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_INFO_COUNT, "notifications") \
202         } \
203         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
204
205 #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
206         ADD_CSQC_AUTOCVAR(name) \
207         float name; \
208         float cpid; \
209         void RegisterNotification_##name() \
210         { \
211                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_CENTER_COUNT) \
212                 SET_FIELD_COUNT(cpid, NOTIF_FIRST, NOTIF_CPID_COUNT) \
213                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_CENTER_COUNT, "notifications") \
214         } \
215         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
216
217 #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) \
218         ADD_CSQC_AUTOCVAR(name) \
219         float name; \
220         void RegisterNotification_##name() \
221         { \
222                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_WEAPON_COUNT) \
223                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_WEAPON_COUNT, "notifications") \
224         } \
225         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
226
227 // NOW we actually activate the declarations
228 MSG_INFO_NOTIFICATIONS
229 MSG_CENTER_NOTIFICATIONS
230 MSG_WEAPON_NOTIFICATIONS
231
232
233 // ======================
234 //  Supporting Functions
235 // ======================
236
237 // select between the normal or the gentle message string based on client (or server) settings
238 string normal_or_gentle(string normal, string gentle)
239 {
240         #ifndef MENUQC
241                 #ifdef CSQC
242                 if(autocvar_cl_gentle || autocvar_cl_gentle_messages)
243                 #else
244                 if(autocvar_sv_gentle)
245                 #endif
246                         return ((gentle != "") ? gentle : normal);
247                 else
248                         return normal;
249         #else
250                 return normal;
251         #endif
252 }
253
254 float notif_stringcount(string s1, string s2)
255 {
256         float stringcount;
257         if(s1 != NO_STR_ARG) ++stringcount;
258         if(s2 != NO_STR_ARG) ++stringcount;
259         return stringcount;
260 }
261
262 float notif_floatcount(float f1, float f2, float f3)
263 {
264         float floatcount;
265         if(f1 != NO_FL_ARG) ++floatcount;
266         if(f2 != NO_FL_ARG) ++floatcount;
267         if(f3 != NO_FL_ARG) ++floatcount;
268         return floatcount;
269 }
270
271 // get the actual name of a notification and return it as a string
272 string Get_Field_Value(float field, float net_type, float net_name)
273 {
274         string output;
275         
276         #define GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) \
277                 if(field == F_NAME) { output = VAR_TO_TEXT(name); } \
278                 else if(field == F_STRNUM) { output = ftos(strnum); } \
279                 else if(field == F_FLNUM) { output = ftos(flnum); }
280         
281         switch(net_type)
282         {
283                 case MSG_INFO:
284                 {
285                         #define MSG_INFO_NOTIF(name,strnum,flnum,args,hudargs,icon,normal,gentle) \
286                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
287                         MSG_INFO_NOTIFICATIONS
288                         break;
289                 }
290                 case MSG_CENTER:
291                 {
292                         #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
293                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
294                         MSG_CENTER_NOTIFICATIONS
295                         break;
296                 }
297                 case MSG_WEAPON:
298                 {
299                         #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) \
300                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
301                         MSG_WEAPON_NOTIFICATIONS
302                         break;
303                 }
304         }
305
306         #undef GET_FIELD_VALUE_OUTPUT
307         return output;
308 }
309
310 // team code replace
311 string TCR(string input, string teamcolor, string teamtext)
312 {
313         input = strreplace("^TC", teamcolor, input);
314         input = strreplace("^TT", teamtext, input);
315         return input;
316 }
317
318 // color code replace, place inside of sprintf and parse the string
319 string CCR(string input)
320 {
321         // foreground/normal colors
322         input = strreplace("^F1", "^2", input); // primary priority (important names, etc)
323         input = strreplace("^F2", "^3", input); // secondary priority (items, locations, numbers, etc)
324
325         // "kill" colors
326         input = strreplace("^K1", "^1", input); // "bad" or "dangerous" text (death messages against you, kill notifications, etc)
327         input = strreplace("^K2", "^3", input); // similar to above, but less important... OR, a highlight out of above message type
328         input = strreplace("^K3", "^4", input); // "good" or "beneficial" text (you fragging someone, etc)
329
330         // background colors
331         input = strreplace("^BG", "^7", input); // neutral/unimportant text
332         input = strreplace("^N", "^7", input); // "none"-- reset to white...
333         return input;
334 }
335
336
337 // =============================
338 //  Debug/Maintenance Functions
339 // =============================
340
341 #define NOTIF_Write(type,name,text) fputs(fh, (sprintf("seta %s 1 // %s - %s\n", name, type, strreplace("\n", "\\n", text))))
342 void Dump_Notifications(float fh)
343 {
344         #define MSG_INFO_NOTIF(name,strnum,flnum,args,hudargs,icon,normal,gentle) { NOTIF_Write("MSG_INFO", VAR_TO_TEXT(name), normal); }
345         #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) { NOTIF_Write("MSG_CENTER", VAR_TO_TEXT(name), normal); }
346         #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) { NOTIF_Write("MSG_WEAPON", VAR_TO_TEXT(name), normal); }
347         MSG_INFO_NOTIFICATIONS
348         MSG_CENTER_NOTIFICATIONS
349         MSG_WEAPON_NOTIFICATIONS
350         return;
351 }
352
353
354 // ===============================
355 //  Frontend Notification Pushing
356 // ===============================
357
358 #ifdef CSQC
359 #define KN_MAX_ENTRIES 10
360 float kn_index;
361 float killnotify_times[KN_MAX_ENTRIES];
362 string killnotify_icon[KN_MAX_ENTRIES];
363 string killnotify_attackers[KN_MAX_ENTRIES];
364 string killnotify_victims[KN_MAX_ENTRIES];
365 // 0 = "Y [used by] X", 1 = "X [did action to] Y"
366 void HUD_Notify_Push(string icon, string attacker, string victim)
367 {
368         if(icon != "")
369         {
370                 --kn_index;
371                 if (kn_index == -1) { kn_index = KN_MAX_ENTRIES-1; }
372                 killnotify_times[kn_index] = time;
373
374                 // icon
375                 if(killnotify_icon[kn_index]) { strunzone(killnotify_icon[kn_index]); }
376                 killnotify_icon[kn_index] = strzone(icon);
377
378                 // attacker
379                 if(killnotify_attackers[kn_index]) { strunzone(killnotify_attackers[kn_index]); }
380                 killnotify_attackers[kn_index] = strzone(attacker);
381
382                 // victim
383                 if(killnotify_victims[kn_index]) { strunzone(killnotify_victims[kn_index]); }
384                 killnotify_victims[kn_index] = strzone(victim);
385         }
386 }
387
388 void Local_Notification(float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
389 {
390         switch(net_type)
391         {
392                 case MSG_INFO:
393                 {
394                         #define MSG_INFO_NOTIF(name,strnum,flnum,args,hudargs,icon,normal,gentle) \
395                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) \
396                                 { \
397                                         print(sprintf(CCR(normal_or_gentle(normal, gentle)), args)); \
398                                         if(strtolower(icon) != "") { HUD_Notify_Push(icon, hudargs); } \
399                                 } }
400                         MSG_INFO_NOTIFICATIONS
401                         break;
402                 }
403                 case MSG_CENTER:
404                 {
405                         #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
406                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) \
407                                 { \
408                                         centerprint_generic(HANDLE_CPID(cpid), sprintf(CCR(normal_or_gentle(normal, gentle)), args), durcnt); \
409                                 } }
410                         MSG_CENTER_NOTIFICATIONS
411                         break;
412                 }
413                 case MSG_WEAPON:
414                 {
415                         #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) \
416                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) \
417                                 { \
418                                         print("unhandled\n"); \
419                                 } }
420                         MSG_WEAPON_NOTIFICATIONS
421                         break;
422                 }
423         }
424 }
425 #endif
426
427
428 // =========================
429 //  Notification Networking
430 // =========================
431
432 #ifdef CSQC
433 void Read_Notification(void)
434 {
435         float net_type = ReadByte();
436         float net_name = ReadShort();
437
438         float stringcount = stof(Get_Field_Value(F_STRNUM, net_type, net_name));
439         float floatcount = stof(Get_Field_Value(F_FLNUM, net_type, net_name));
440         
441         Local_Notification(net_type, net_name,
442                 ((stringcount >= 1) ? ReadString() : ""),
443                 ((stringcount == 2) ? ReadString() : ""),
444                 ((floatcount >= 1) ? ReadLong() : 0),
445                 ((floatcount >= 2) ? ReadLong() : 0),
446                 ((floatcount == 3) ? ReadLong() : 0));
447 }
448 #endif
449
450 #ifdef SVQC
451 void Send_Notification(entity client, float broadcast, float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
452 {
453         if(net_type && net_name)
454         {
455                 //print("notification: ", Get_Field_Value(F_NAME, net_type, net_name), ": ", ftos(net_name), ".\n");
456
457                 float stringcount = stof(Get_Field_Value(F_STRNUM, net_type, net_name));
458                 float floatcount = stof(Get_Field_Value(F_FLNUM, net_type, net_name));
459                 
460                 if(notif_stringcount(s1, s2) > stringcount) { backtrace("Too many string arguments for notification!\n"); return; }
461                 if(notif_floatcount(f1, f2, f3) > floatcount) { backtrace("Too many float arguments for notification!\n"); return; }
462
463                 if(broadcast == MSG_ONE)
464                 {
465                         if(client && (clienttype(client) == CLIENTTYPE_REAL) && (client.flags & FL_CLIENT))
466                         {
467                                 // personal/direct notification sent to ONE person and their spectators
468                                 msg_entity = client;
469                                 WRITESPECTATABLE_MSG_ONE({
470                                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
471                                         WriteByte(MSG_ONE, TE_CSQC_NOTIFICATION);
472                                         WriteByte(MSG_ONE, net_type);
473                                         WriteShort(MSG_ONE, net_name);
474                                         if(stringcount >= 1) { WriteString(MSG_ONE, s1); }
475                                         if(stringcount == 2) { WriteString(MSG_ONE, s2); }
476                                         if(floatcount >= 1) { WriteLong(MSG_ONE, f1); }
477                                         if(floatcount >= 2) { WriteLong(MSG_ONE, f2); }
478                                         if(floatcount == 3) { WriteLong(MSG_ONE, f3); }
479                                 });
480                         }
481                 }
482                 else if(broadcast == MSG_ALL)
483                 {
484                         // global notification sent to EVERYONE
485                         WriteByte(MSG_ALL, SVC_TEMPENTITY);
486                         WriteByte(MSG_ALL, TE_CSQC_NOTIFICATION);
487                         WriteByte(MSG_ALL, net_type);
488                         WriteShort(MSG_ALL, net_name);
489                         if(stringcount >= 1) { WriteString(MSG_ALL, s1); }
490                         if(stringcount == 2) { WriteString(MSG_ALL, s2); }
491                         if(floatcount >= 1) { WriteLong(MSG_ALL, f1); }
492                         if(floatcount >= 2) { WriteLong(MSG_ALL, f2); }
493                         if(floatcount == 3) { WriteLong(MSG_ALL, f3); }
494                 }
495                 else { backtrace("Unknown MSG_ type to write with!\n"); }
496
497                 if(!server_is_local && (net_type == MSG_INFO))
498                 {
499                         #define MSG_INFO_NOTIF(name,strnum,flnum,args,hudargs,icon,normal,gentle) \
500                                 { NOTIF_MATCH(name, net_name) { print(sprintf(CCR(normal_or_gentle(normal, gentle)), args)); } }
501                         MSG_INFO_NOTIFICATIONS
502                 }
503         }
504         else { backtrace("Incorrect usage of Send_Notification!\n"); }
505 }
506
507 void Send_Notification_ToTeam(float targetteam, entity except, float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
508 {
509         entity tmp_entity;
510         FOR_EACH_REALCLIENT(tmp_entity)
511         {
512                 if(tmp_entity.classname == STR_PLAYER)
513                 if(tmp_entity.team == targetteam)
514                 if(tmp_entity != except)
515                 {
516                         Send_Notification(tmp_entity, MSG_ONE, net_type, net_name, s1, s2, f1, f2, f3);
517                 }
518         }
519 }
520
521 // WARNING: use this ONLY if you need exceptions or want to exclude spectators, otherwise use Send_Notification(..., world, ...)
522 void Send_Notification_ToAll(entity except, float spectators, float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
523 {
524         entity tmp_entity;
525         FOR_EACH_REALCLIENT(tmp_entity)
526         {
527                 if((tmp_entity.classname == STR_PLAYER) || spectators)
528                 if(tmp_entity != except)
529                 {
530                         Send_Notification(tmp_entity, MSG_ONE, net_type, net_name, s1, s2, f1, f2, f3);
531                 }
532         }
533 }
534
535
536 // =============================
537 //  LEGACY NOTIFICATION SYSTEMS
538 // =============================
539
540 void Send_KillNotification(string s1, string s2, string s3, float msg, float type)
541 {
542         WriteByte(MSG_ALL, SVC_TEMPENTITY);
543         WriteByte(MSG_ALL, TE_CSQC_KILLNOTIFY);
544         WriteString(MSG_ALL, s1);
545         WriteString(MSG_ALL, s2);
546         WriteString(MSG_ALL, s3);
547         WriteShort(MSG_ALL, msg);
548         WriteByte(MSG_ALL, type);
549 }
550
551 // Function is used to send a generic centerprint whose content CSQC gets to decide (gentle version or not in the below cases)
552 void Send_CSQC_KillCenterprint(entity e, string s1, string s2, float msg, float type)
553 {
554         if (clienttype(e) == CLIENTTYPE_REAL)
555         {
556                 msg_entity = e;
557                 WRITESPECTATABLE_MSG_ONE({
558                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
559                         WriteByte(MSG_ONE, TE_CSQC_KILLCENTERPRINT);
560                         WriteString(MSG_ONE, s1);
561                         WriteString(MSG_ONE, s2);
562                         WriteShort(MSG_ONE, msg);
563                         WriteByte(MSG_ONE, type);
564                 });
565         }
566 }
567
568 void Send_CSQC_Centerprint_Generic(entity e, float id, string s, float duration, float countdown_num)
569 {
570         if ((clienttype(e) == CLIENTTYPE_REAL) && (e.flags & FL_CLIENT))
571         {
572                 msg_entity = e;
573                 WRITESPECTATABLE_MSG_ONE({
574                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
575                         WriteByte(MSG_ONE, TE_CSQC_CENTERPRINT_GENERIC);
576                         WriteByte(MSG_ONE, id);
577                         WriteString(MSG_ONE, s);
578                         if (id != 0 && s != "")
579                         {
580                                 WriteByte(MSG_ONE, duration);
581                                 WriteByte(MSG_ONE, countdown_num);
582                         }
583                 });
584         }
585 }
586 void Send_CSQC_Centerprint_Generic_Expire(entity e, float id)
587 {
588         Send_CSQC_Centerprint_Generic(e, id, "", 1, 0);
589 }
590 #endif