]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/notifications.qc
e0aab87a0469f745e82e6bc1b62182a19c763c85
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / notifications.qc
1 // ================================================
2 //  Unified notification system, written by Samual
3 //  Last updated: November, 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 #define MSG_DEATH 4 // "Personal" AND "Global" death messages 
11
12 #define NO_STR_ARG ""
13 #define NO_FL_ARG -12345
14
15 #define F_NAME 1
16 #define F_STRNUM 2
17 #define F_FLNUM 3
18
19 #define BOT_PING -1
20
21 // Since this is code uses macro processors to list notifications,
22 // the normal compiler sees these checks as "constant" and throws
23 // a warning. We have to get around this by using another function.
24 #define NOTIF_MATCH(a,b) if(min(NOTIF_MAX, a) == b)
25
26 #ifdef CSQC
27 /*
28  Acquire special information to generate for display in the
29  notification from variables networked to the client.
30  Macro descriptions:
31     PASS_KEY: find the keybind for "passing" or "dropping" in CTF game mode
32     FRAG_SPREE: find out if the player is on a kill spree/how many kills they have
33     FRAG_PING: show the ping of a player
34     FRAG_STATS: show health/armor/ping of a player
35     FRAG_POS: show score status and position in the match of a player
36     DEATH_TEAM: show the full name of the team a player is switching from
37 */
38 string got_commandkey;
39 #define PASS_KEY ((((got_commandkey = getcommandkey("pass", "+use")) != "pass") && !(strstrofs(got_commandkey, "not bound", 0) >= 0)) ? sprintf(CCR(_(" ^F1(Press %s)")), got_commandkey) : "")
40 #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) : "")
41 #define FRAG_PING ((f2 != BOT_PING) ? sprintf(CCR(_("\n(Ping ^2%d^BG)")), f2) : "")
42 #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) : ""))
43 //#define FRAG_POS ((Should_Print_Score_Pos(f1)) ? sprintf("\n^BG%s", Read_Score_Pos(f1)) : "")
44 #define DEATH_TEAM Team_ColoredFullName(f1)
45
46 // NO_CPID normally has a variable value, so we need to check and see
47 // whether a notification uses it. If so, cancel out the centerprint ID.
48 #define HANDLE_CPID(cpid) ((min(NOTIF_MAX, cpid) == NO_CPID) ? FALSE : cpid)
49
50 // client-side handling of cvars
51 #define ADD_CSQC_AUTOCVAR(name) var float autocvar_notification_##name = TRUE;
52 #define CHECK_AUTOCVAR(name) if(autocvar_notification_##name)
53 #else
54
55 // allow sending of notifications to also pass through to spectators (specifically for centerprints)
56 #ifdef SVQC
57 #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
58 #define WRITESPECTATABLE_MSG_ONE(statement) WRITESPECTATABLE_MSG_ONE_VARNAME(oldmsg_entity, statement)
59 #define WRITESPECTATABLE(msg,statement) if(msg == MSG_ONE) { WRITESPECTATABLE_MSG_ONE(statement); } else statement float WRITESPECTATABLE_workaround = 0
60 #endif
61
62 // do nothing for the other programs, they don't need cvars (those are just for the clients)
63 #define ADD_CSQC_AUTOCVAR(name)
64 #endif
65
66
67 /*
68         If BELOW negative maxplayers, you dropped a place lower
69         If below 0, you are tied for that place
70         If above 0, you are holding that place alone
71         If above positive maxplayers, you moved up a place
72
73 float Should_Print_Score_Pos
74
75 string Read_Score_Pos(float num)
76 {
77         
78 }
79
80 float Form_Score_Pos(entity player)
81 {
82         return 
83 }*/
84
85 // ====================================
86 //  Notifications List and Information
87 // ====================================
88 /*
89  List of all notifications (including identifiers and display information)
90  Format: name, strnum, flnum, args, *icon/CPID, *durcnt, normal, gentle
91  Asterisked fields are not present in all notification types.
92  Specifications:
93     Name of notification
94     Number of STRING arguments (so that networking knows how many to send/receive)
95     Number of FLOAT arguments (so that networking knows how many to send/receive)
96     Arguments for sprintf(string, args), if no args needed then use ""
97     *Icon/CPID:
98       MSG_INFO: STRING: icon string name for the hud notify panel, "" if no icon is used
99       MSG_CENTER: FLOAT: centerprint ID number (CPID_*), NO_CPID if no CPID is needed
100     *Duration/Countdown:
101       MSG_CENTER: XPND2(FLOAT, FLOAT): extra arguments for centerprint messages
102     Normal message (string for sprintf when gentle messages are NOT enabled)
103     Gentle message (string for sprintf when gentle messages ARE enabled)
104
105  Messages with ^F1, ^BG, ^TC, etc etc in them will replace those strings
106  with colors according to the cvars the user has chosen. This allows for
107  users to create unique color profiles for their HUD, giving more customization
108  options to HUD designers and end users who want such a feature.
109
110  Check out the function calls for string CCR(...) and
111  string TCR(...) to better understand how these codes work.
112
113  Guidlines (please try and follow these):
114     -ALWAYS start the string with a color, preferably background.
115     -ALWAYS reset a color after a name (this way they don't set it for the whole string).
116     -NEVER re-declare an event twice.
117     -NEVER add or remove fields from the format, it SHOULD already work.
118     -MSG_INFO messages must ALWAYS end with a new line: \n
119     -Be clean and simple with your notification naming,
120      nothing too long for the name field... Abbreviations are your friend. :D
121     -Keep the spacing as clean as possible... if the arguments are abnormally long,
122       it's okay to go out of line a bit... but try and keep it clean still.
123     -Sort the notifications in the most appropriate order for their tasks.
124       TODO: ? centerprint IDs are given priority based on their order (first being highest priority going downwards)
125     -ARIRE unir frk jvgu lbhe bja zbgure. (gvc sbe zvxrrhfn) -- Don't pay attention to this ^_^
126 */
127
128 // weaponorder[f1].netname
129
130 #define MULTITEAM_INFO(prefix,teams,strnum,flnum,args,hudargs,icon,normal,gentle) \
131         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))) \
132         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))) \
133         #if teams >= 3 \
134                 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))) \
135         #endif \
136         #if teams >= 4 \
137                 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))) \
138         #endif
139 #define MSG_INFO_NOTIFICATIONS \
140         MSG_INFO_NOTIF(INFO_EMPTY,                                                      0, 0, NO_STR_ARG, XPND2("", ""),                                        "", "", "") \
141         MULTITEAM_INFO(INFO_SCORES_, 4,                                         0, 0, NO_STR_ARG, XPND2("", ""),                                        "", _("^TC^TT ^BGteam scores!\n"), "") \
142         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"), "") \
143         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"), "") \
144         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"), "") \
145         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"), "") \
146         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"), "") \
147         MULTITEAM_INFO(INFO_CTF_FLAGRETURN_TIMEOUT_, 2,         0, 0, NO_STR_ARG, XPND2("", ""),                                        "", _("^BGThe ^TC^TT^BG flag has returned to the base\n"), "") \
148         MULTITEAM_INFO(INFO_CTF_PICKUP_, 2,                                     1, 0, s1, XPND2(s1, ""),                                                        "notify_%s_taken", _("^BG%s^BG got the ^TC^TT^BG flag\n"), "") \
149         MULTITEAM_INFO(INFO_CTF_RETURN_, 2,                                     1, 0, s1, XPND2(s1, ""),                                                        "notify_%s_returned", _("^BG%s^BG returned the ^TC^TT^BG flag\n"), "") \
150         MULTITEAM_INFO(INFO_CTF_LOST_, 2,                                       1, 0, s1, XPND2(s1, ""),                                                        "notify_%s_lost", _("^BG%s^BG lost the ^TC^TT^BG flag\n"), "") \
151         MULTITEAM_INFO(INFO_CTF_CAPTURE_, 2,                            1, 0, s1, XPND2(s1, ""),                                                        "notify_%s_captured", _("^BG%s^BG captured the ^TC^TT^BG flag\n"), "") \
152         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"), "") \
153         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"), "") \
154         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"), "") \
155         #undef MSG_INFO_NOTIF
156
157 #define MULTITEAM_CENTER(prefix,teams,strnum,flnum,args,cpid,durcnt,normal,gentle) \
158         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))) \
159         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))) \
160         #if teams >= 3 \
161                 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))) \
162         #endif \
163         #if teams >= 4 \
164                 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))) \
165         #endif
166 #define MSG_CENTER_NOTIFICATIONS \
167         MSG_CENTER_NOTIF(CENTER_EMPTY,                                                  0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), "", "") \
168         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."), "") \
169         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."), "") \
170         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"), "") \
171         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"), "") \
172         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"), "") \
173         MSG_CENTER_NOTIF(CENTER_CTF_PASS_REQUESTING,                    1, 0, s1,                                               CPID_CTF_PASS,                  XPND2(0, 0), _("^BGRequesting %s^BG to pass you the flag"), "") \
174         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"), "") \
175         MULTITEAM_CENTER(CENTER_CTF_RETURN_, 2,                                 0, 0, NO_STR_ARG,                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYou returned the ^TC^TT^BG flag!"), "") \
176         MULTITEAM_CENTER(CENTER_CTF_CAPTURE_, 2,                                0, 0, NO_STR_ARG,                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYou captured the ^TC^TT^BG flag!"), "") \
177         MULTITEAM_CENTER(CENTER_CTF_PICKUP_, 2,                                 0, 0, NO_STR_ARG,                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGYou got the ^TC^TT^BG flag!"), "") \
178         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!"), "") \
179         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!"), "") \
180         MSG_CENTER_NOTIF(CENTER_CTF_PICKUP_ENEMY,                               1, 0, s1,                                               CPID_CTF_LOWPRIO,               XPND2(0, 0), _("^BGThe %senemy^BG got your flag! Retrieve it!"), "") \
181         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!"), "") \
182         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!"), "") \
183         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!"), "") \
184         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."), "") \
185         #undef MSG_CENTER_NOTIF
186
187 #define MSG_WEAPON_NOTIFICATIONS \
188         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"), "") \
189         #undef MSG_WEAPON_NOTIF
190
191 #define MSG_DEATH_NOTIFICATIONS \
192         MSG_DEATH_NOTIF(DEATH_SELF_CUSTOM,                                              2, 0, XPND2(s1, s2),                    NO_CPID,                                XPND2(0, 0), _("^K1You were %s, %s"), "") \
193         MSG_DEATH_NOTIF(DEATH_SELF_GENERIC,                                             0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1Watch your step!"), "") \
194         MSG_DEATH_NOTIF(DEATH_SELF_SELFKILL,                                    0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You killed your own dumb self!"), _("^K1You need to be more careful!")) \
195         MSG_DEATH_NOTIF(DEATH_SELF_SUICIDE,                                             0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You committed suicide!"), _("^K1You ended it all!")) \
196         MSG_DEATH_NOTIF(DEATH_SELF_NOAMMO,                                              0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You were killed for running out of ammo..."), _("^K1You are reinserted into the game for running out of ammo...")) \
197         MSG_DEATH_NOTIF(DEATH_SELF_ROT,                                                 0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You grew too old without taking your medicine"), _("^K1You need to preserve your health")) \
198         MSG_DEATH_NOTIF(DEATH_SELF_CAMP,                                                0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1Die camper!"), _("^K1Reconsider your tactics, camper!")) \
199         MSG_DEATH_NOTIF(DEATH_SELF_BETRAYAL,                                    0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1Don't shoot your team mates!"), _("^K1Don't go against your team mates!")) \
200         MSG_DEATH_NOTIF(DEATH_SELF_TEAMCHANGE,                                  0, 1, DEATH_TEAM,                               NO_CPID,                                XPND2(0, 0), _("^BGYou are now on: %s"), "") \
201         MSG_DEATH_NOTIF(DEATH_SELF_AUTOTEAMCHANGE,                              0, 1, DEATH_TEAM,                               NO_CPID,                                XPND2(0, 0), _("^BGYou have been moved into a different team to improve team balance\nYou are now on: %s"), "") \
202         MSG_DEATH_NOTIF(DEATH_SELF_FALL,                                                0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You hit the ground with a bit too much force"), "") \
203         MSG_DEATH_NOTIF(DEATH_SELF_DROWN,                                               0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You couldn't catch your breath in time!"), "") \
204         MSG_DEATH_NOTIF(DEATH_SELF_LAVA,                                                0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You couldn't stand the heat!"), "") \
205         MSG_DEATH_NOTIF(DEATH_SELF_SLIME,                                               0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You melted away in slime!"), "") \
206         MSG_DEATH_NOTIF(DEATH_SELF_SHOOTING_STAR,                               0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You went faster than the speed of light!"), "") \
207         MSG_DEATH_NOTIF(DEATH_SELF_SWAMP,                                               0, 0, NO_STR_ARG,                               NO_CPID,                                XPND2(0, 0), _("^K1You got stuck in a swamp!"), "") \
208         MSG_DEATH_NOTIF(DEATH_MURDER_FRAG,                                              1, 1, XPND2(FRAG_SPREE, s1),                                                    NO_CPID, XPND2(0, 0), _("^K3%sYou fragged ^BG%s"), _("^K3%sYou scored against ^BG%s")) \
209         MSG_DEATH_NOTIF(DEATH_MURDER_FRAGGED,                                   1, 0, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K1You were fragged by ^BG%s"), _("^K1You were scored against by ^BG%s")) \
210         MSG_DEATH_NOTIF(DEATH_MURDER_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")) \
211         MSG_DEATH_NOTIF(DEATH_MURDER_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!")) \
212         MSG_DEATH_NOTIF(DEATH_MURDER_FRAG_FIRST,                                1, 0, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K3First blood! You fragged ^BG%s"), _("^K3First score! You scored against ^BG%s")) \
213         MSG_DEATH_NOTIF(DEATH_MURDER_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")) \
214         MSG_DEATH_NOTIF(DEATH_MURDER_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")) \
215         MSG_DEATH_NOTIF(DEATH_MURDER_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!")) \
216         MSG_DEATH_NOTIF(DEATH_MURDER_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")) \
217         MSG_DEATH_NOTIF(DEATH_MURDER_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")) \
218         MSG_DEATH_NOTIF(DEATH_MURDER_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")) \
219         MSG_DEATH_NOTIF(DEATH_MURDER_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")) \
220         MSG_DEATH_NOTIF(DEATH_MURDER_FRAG_FIRST_VERBOSE,                1, 1, s1,                                                                                               NO_CPID, XPND2(0, 0), _("^K3First blood! You fragged ^BG%s"), _("^K3First score! You scored against ^BG%s")) \
221         MSG_DEATH_NOTIF(DEATH_MURDER_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")) \
222         MSG_DEATH_NOTIF(DEATH_MURDER_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")) \
223         MSG_DEATH_NOTIF(DEATH_MURDER_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!")) \
224         #undef MSG_DEATH_NOTIF
225
226
227 // ====================================
228 //  Initialization/Create Declarations
229 // ====================================
230
231 #define NOTIF_FIRST 1
232 #define NOTIF_MAX 1024 // limit of recursive functions with ACCUMULATE_FUNCTION
233 float NOTIF_INFO_COUNT;
234 float NOTIF_CENTER_COUNT;
235 float NOTIF_WEAPON_COUNT;
236 float NOTIF_DEATH_COUNT;
237 float NOTIF_CPID_COUNT;
238
239 #define MSG_INFO_NOTIF(name,strnum,flnum,args,icon,normal,gentle) \
240         ADD_CSQC_AUTOCVAR(name) \
241         float name; \
242         void RegisterNotification_##name() \
243         { \
244                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_INFO_COUNT) \
245                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_INFO_COUNT, "notifications") \
246         } \
247         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
248
249 #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
250         ADD_CSQC_AUTOCVAR(name) \
251         float name; \
252         float cpid; \
253         void RegisterNotification_##name() \
254         { \
255                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_CENTER_COUNT) \
256                 SET_FIELD_COUNT(cpid, NOTIF_FIRST, NOTIF_CPID_COUNT) \
257                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_CENTER_COUNT, "notifications") \
258         } \
259         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
260
261 #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) \
262         ADD_CSQC_AUTOCVAR(name) \
263         float name; \
264         void RegisterNotification_##name() \
265         { \
266                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_WEAPON_COUNT) \
267                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_WEAPON_COUNT, "notifications") \
268         } \
269         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
270
271 #define MSG_DEATH_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
272         ADD_CSQC_AUTOCVAR(name) \
273         float name; \
274         void RegisterNotification_##name() \
275         { \
276                 SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_DEATH_COUNT) \
277                 CHECK_MAX_COUNT(name, NOTIF_MAX, NOTIF_DEATH_COUNT, "notifications") \
278         } \
279         ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name)
280
281 // NOW we actually activate the declarations
282 MSG_INFO_NOTIFICATIONS
283 MSG_CENTER_NOTIFICATIONS
284 MSG_WEAPON_NOTIFICATIONS
285 MSG_DEATH_NOTIFICATIONS
286
287
288 // ======================
289 //  Supporting Functions
290 // ======================
291
292 // select between the normal or the gentle message string based on client (or server) settings
293 string normal_or_gentle(string normal, string gentle)
294 {
295         #ifndef MENUQC
296                 #ifdef CSQC
297                 if(autocvar_cl_gentle || autocvar_cl_gentle_messages)
298                 #else
299                 if(autocvar_sv_gentle)
300                 #endif
301                         return ((gentle != "") ? gentle : normal);
302                 else
303                         return normal;
304         #else
305                 return normal;
306         #endif
307 }
308
309 float notif_stringcount(string s1, string s2)
310 {
311         float stringcount;
312         if(s1 != NO_STR_ARG) ++stringcount;
313         if(s2 != NO_STR_ARG) ++stringcount;
314         return stringcount;
315 }
316
317 float notif_floatcount(float f1, float f2, float f3)
318 {
319         float floatcount;
320         if(f1 != NO_FL_ARG) ++floatcount;
321         if(f2 != NO_FL_ARG) ++floatcount;
322         if(f3 != NO_FL_ARG) ++floatcount;
323         return floatcount;
324 }
325
326 // get the actual name of a notification and return it as a string
327 string Get_Field_Value(float field, float net_type, float net_name)
328 {
329         string output;
330         
331         #define GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) \
332                 if(field == F_NAME) { output = VAR_TO_TEXT(name); } \
333                 else if(field == F_STRNUM) { output = ftos(strnum); } \
334                 else if(field == F_FLNUM) { output = ftos(flnum); }
335         
336         switch(net_type)
337         {
338                 case MSG_INFO:
339                 {
340                         #define MSG_INFO_NOTIF(name,strnum,flnum,args,hudargs,icon,normal,gentle) \
341                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
342                         MSG_INFO_NOTIFICATIONS
343                         break;
344                 }
345                 case MSG_CENTER:
346                 {
347                         #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
348                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
349                         MSG_CENTER_NOTIFICATIONS
350                         break;
351                 }
352                 case MSG_WEAPON:
353                 {
354                         #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) \
355                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
356                         MSG_WEAPON_NOTIFICATIONS
357                         break;
358                 }
359                 case MSG_DEATH:
360                 {
361                         #define MSG_DEATH_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
362                                 { NOTIF_MATCH(name, net_name) { GET_FIELD_VALUE_OUTPUT(field,name,strnum,flnum) } }
363                         MSG_DEATH_NOTIFICATIONS
364                         break;
365                 }
366         }
367
368         #undef GET_FIELD_VALUE_OUTPUT
369         return output;
370 }
371
372 // team code replace
373 string TCR(string input, string teamcolor, string teamtext)
374 {
375         input = strreplace("^TC", teamcolor, input);
376         input = strreplace("^TT", teamtext, input);
377         return input;
378 }
379
380 // color code replace, place inside of sprintf and parse the string
381 string CCR(string input)
382 {
383         // foreground/normal colors
384         input = strreplace("^F1", "^2", input); // primary priority (important names, etc)
385         input = strreplace("^F2", "^3", input); // secondary priority (items, locations, numbers, etc)
386
387         // "kill" colors
388         input = strreplace("^K1", "^1", input); // "bad" or "dangerous" text (death messages against you, kill notifications, etc)
389         input = strreplace("^K2", "^3", input); // similar to above, but less important... OR, a highlight out of above message type
390         input = strreplace("^K3", "^4", input); // "good" or "beneficial" text (you fragging someone, etc)
391
392         // background colors
393         input = strreplace("^BG", "^7", input); // neutral/unimportant text
394         input = strreplace("^N", "^7", input); // "none"-- reset to white...
395         return input;
396 }
397
398
399 // =============================
400 //  Debug/Maintenance Functions
401 // =============================
402
403 #define NOTIF_Write(type,name,text) fputs(fh, (sprintf("seta %s 1 // %s - %s\n", name, type, strreplace("\n", "\\n", text))))
404 void Dump_Notifications(float fh)
405 {
406         #define MSG_INFO_NOTIF(name,strnum,flnum,args,hudargs,icon,normal,gentle) { NOTIF_Write("MSG_INFO", VAR_TO_TEXT(name), normal); }
407         #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) { NOTIF_Write("MSG_CENTER", VAR_TO_TEXT(name), normal); }
408         #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) { NOTIF_Write("MSG_WEAPON", VAR_TO_TEXT(name), normal); }
409         #define MSG_DEATH_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) { NOTIF_Write("MSG_DEATH", VAR_TO_TEXT(name), normal); }
410         MSG_INFO_NOTIFICATIONS
411         MSG_CENTER_NOTIFICATIONS
412         MSG_WEAPON_NOTIFICATIONS
413         MSG_DEATH_NOTIFICATIONS
414         return;
415 }
416
417
418 // ===============================
419 //  Frontend Notification Pushing
420 // ===============================
421
422 #ifdef CSQC
423 #define KN_MAX_ENTRIES 10
424 float kn_index;
425 float killnotify_times[KN_MAX_ENTRIES];
426 string killnotify_icon[KN_MAX_ENTRIES];
427 string killnotify_attackers[KN_MAX_ENTRIES];
428 string killnotify_victims[KN_MAX_ENTRIES];
429 // 0 = "Y [used by] X", 1 = "X [did action to] Y"
430 void HUD_Notify_Push(string icon, string attacker, string victim)
431 {
432         if(icon != "")
433         {
434                 --kn_index;
435                 if (kn_index == -1) { kn_index = KN_MAX_ENTRIES-1; }
436                 killnotify_times[kn_index] = time;
437
438                 // icon
439                 if(killnotify_icon[kn_index]) { strunzone(killnotify_icon[kn_index]); }
440                 killnotify_icon[kn_index] = strzone(icon);
441
442                 // attacker
443                 if(killnotify_attackers[kn_index]) { strunzone(killnotify_attackers[kn_index]); }
444                 killnotify_attackers[kn_index] = strzone(attacker);
445
446                 // victim
447                 if(killnotify_victims[kn_index]) { strunzone(killnotify_victims[kn_index]); }
448                 killnotify_victims[kn_index] = strzone(victim);
449         }
450 }
451
452 void Local_Notification(float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
453 {
454         switch(net_type)
455         {
456                 case MSG_INFO:
457                 {
458                         #define MSG_INFO_NOTIF(name,strnum,flnum,args,hudargs,icon,normal,gentle) \
459                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) \
460                                 { \
461                                         print(sprintf(CCR(normal_or_gentle(normal, gentle)), args)); \
462                                         if(strtolower(icon) != "") { HUD_Notify_Push(icon, hudargs); } \
463                                 } }
464                         MSG_INFO_NOTIFICATIONS
465                         break;
466                 }
467                 case MSG_CENTER:
468                 {
469                         #define MSG_CENTER_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
470                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) \
471                                 { \
472                                         centerprint_generic(HANDLE_CPID(cpid), sprintf(CCR(normal_or_gentle(normal, gentle)), args), durcnt); \
473                                 } }
474                         MSG_CENTER_NOTIFICATIONS
475                         break;
476                 }
477                 case MSG_WEAPON:
478                 {
479                         #define MSG_WEAPON_NOTIF(name,strnum,flnum,args,normal,gentle) \
480                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) \
481                                 { \
482                                         print("unhandled\n"); \
483                                 } }
484                         MSG_WEAPON_NOTIFICATIONS
485                         break;
486                 }
487                 case MSG_DEATH:
488                 {
489                         #define MSG_DEATH_NOTIF(name,strnum,flnum,args,cpid,durcnt,normal,gentle) \
490                                 { NOTIF_MATCH(name, net_name) CHECK_AUTOCVAR(name) \
491                                 { \
492                                         centerprint_generic(HANDLE_CPID(cpid), sprintf(CCR(normal_or_gentle(normal, gentle)), args), durcnt); \
493                                 } }
494                         MSG_DEATH_NOTIFICATIONS
495                         break;
496                 }
497         }
498 }
499 #endif
500
501
502 // =========================
503 //  Notification Networking
504 // =========================
505
506 #ifdef CSQC
507 void Read_Notification(void)
508 {
509         float net_type = ReadByte();
510         float net_name = ReadShort();
511
512         float stringcount = stof(Get_Field_Value(F_STRNUM, net_type, net_name));
513         float floatcount = stof(Get_Field_Value(F_FLNUM, net_type, net_name));
514         
515         Local_Notification(net_type, net_name,
516                 ((stringcount >= 1) ? ReadString() : ""),
517                 ((stringcount == 2) ? ReadString() : ""),
518                 ((floatcount >= 1) ? ReadLong() : 0),
519                 ((floatcount >= 2) ? ReadLong() : 0),
520                 ((floatcount == 3) ? ReadLong() : 0));
521 }
522 #endif
523
524 #ifdef SVQC
525 void Send_Notification(entity client, float broadcast, float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
526 {
527         if(net_type && net_name)
528         {
529                 //print("notification: ", Get_Field_Value(F_NAME, net_type, net_name), ": ", ftos(net_name), ".\n");
530
531                 float stringcount = stof(Get_Field_Value(F_STRNUM, net_type, net_name));
532                 float floatcount = stof(Get_Field_Value(F_FLNUM, net_type, net_name));
533                 
534                 if(notif_stringcount(s1, s2) > stringcount) { backtrace("Too many string arguments for notification!\n"); return; }
535                 if(notif_floatcount(f1, f2, f3) > floatcount) { backtrace("Too many float arguments for notification!\n"); return; }
536
537                 if(broadcast == MSG_ONE)
538                 {
539                         if(client && (clienttype(client) == CLIENTTYPE_REAL) && (client.flags & FL_CLIENT))
540                         {
541                                 // personal/direct notification sent to ONE person and their spectators
542                                 msg_entity = client;
543                                 WRITESPECTATABLE_MSG_ONE({
544                                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
545                                         WriteByte(MSG_ONE, TE_CSQC_NOTIFICATION);
546                                         WriteByte(MSG_ONE, net_type);
547                                         WriteShort(MSG_ONE, net_name);
548                                         if(stringcount >= 1) { WriteString(MSG_ONE, s1); }
549                                         if(stringcount == 2) { WriteString(MSG_ONE, s2); }
550                                         if(floatcount >= 1) { WriteLong(MSG_ONE, f1); }
551                                         if(floatcount >= 2) { WriteLong(MSG_ONE, f2); }
552                                         if(floatcount == 3) { WriteLong(MSG_ONE, f3); }
553                                 });
554                         }
555                 }
556                 else if(broadcast == MSG_ALL)
557                 {
558                         // global notification sent to EVERYONE
559                         WriteByte(MSG_ALL, SVC_TEMPENTITY);
560                         WriteByte(MSG_ALL, TE_CSQC_NOTIFICATION);
561                         WriteByte(MSG_ALL, net_type);
562                         WriteShort(MSG_ALL, net_name);
563                         if(stringcount >= 1) { WriteString(MSG_ALL, s1); }
564                         if(stringcount == 2) { WriteString(MSG_ALL, s2); }
565                         if(floatcount >= 1) { WriteLong(MSG_ALL, f1); }
566                         if(floatcount >= 2) { WriteLong(MSG_ALL, f2); }
567                         if(floatcount == 3) { WriteLong(MSG_ALL, f3); }
568                 }
569                 else { backtrace("Unknown MSG_ type to write with!\n"); }
570
571                 if(!server_is_local && (net_type == MSG_INFO))
572                 {
573                         #define MSG_INFO_NOTIF(name,strnum,flnum,args,hudargs,icon,normal,gentle) \
574                                 { NOTIF_MATCH(name, net_name) { print(sprintf(CCR(normal_or_gentle(normal, gentle)), args)); } }
575                         MSG_INFO_NOTIFICATIONS
576                 }
577         }
578         else { backtrace("Incorrect usage of Send_Notification!\n"); }
579 }
580
581 void Send_Notification_ToTeam(float targetteam, entity except, float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
582 {
583         entity tmp_entity;
584         FOR_EACH_REALCLIENT(tmp_entity)
585         {
586                 if(tmp_entity.classname == STR_PLAYER)
587                 if(tmp_entity.team == targetteam)
588                 if(tmp_entity != except)
589                 {
590                         Send_Notification(tmp_entity, MSG_ONE, net_type, net_name, s1, s2, f1, f2, f3);
591                 }
592         }
593 }
594
595 // WARNING: use this ONLY if you need exceptions or want to exclude spectators, otherwise use Send_Notification(world, MSG_ALL, ...)
596 void Send_Notification_ToAll(entity except, float spectators, float net_type, float net_name, string s1, string s2, float f1, float f2, float f3)
597 {
598         entity tmp_entity;
599         FOR_EACH_REALCLIENT(tmp_entity)
600         {
601                 if((tmp_entity.classname == STR_PLAYER) || spectators)
602                 if(tmp_entity != except)
603                 {
604                         Send_Notification(tmp_entity, MSG_ONE, net_type, net_name, s1, s2, f1, f2, f3);
605                 }
606         }
607 }
608
609
610 // =============================
611 //  LEGACY NOTIFICATION SYSTEMS
612 // =============================
613
614 void Send_KillNotification(string s1, string s2, string s3, float msg, float type)
615 {
616         WriteByte(MSG_ALL, SVC_TEMPENTITY);
617         WriteByte(MSG_ALL, TE_CSQC_KILLNOTIFY);
618         WriteString(MSG_ALL, s1);
619         WriteString(MSG_ALL, s2);
620         WriteString(MSG_ALL, s3);
621         WriteShort(MSG_ALL, msg);
622         WriteByte(MSG_ALL, type);
623 }
624
625 // Function is used to send a generic centerprint whose content CSQC gets to decide (gentle version or not in the below cases)
626 void Send_CSQC_KillCenterprint(entity e, string s1, string s2, float msg, float type)
627 {
628         if (clienttype(e) == CLIENTTYPE_REAL)
629         {
630                 msg_entity = e;
631                 WRITESPECTATABLE_MSG_ONE({
632                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
633                         WriteByte(MSG_ONE, TE_CSQC_KILLCENTERPRINT);
634                         WriteString(MSG_ONE, s1);
635                         WriteString(MSG_ONE, s2);
636                         WriteShort(MSG_ONE, msg);
637                         WriteByte(MSG_ONE, type);
638                 });
639         }
640 }
641
642 void Send_CSQC_Centerprint_Generic(entity e, float id, string s, float duration, float countdown_num)
643 {
644         if ((clienttype(e) == CLIENTTYPE_REAL) && (e.flags & FL_CLIENT))
645         {
646                 msg_entity = e;
647                 WRITESPECTATABLE_MSG_ONE({
648                         WriteByte(MSG_ONE, SVC_TEMPENTITY);
649                         WriteByte(MSG_ONE, TE_CSQC_CENTERPRINT_GENERIC);
650                         WriteByte(MSG_ONE, id);
651                         WriteString(MSG_ONE, s);
652                         if (id != 0 && s != "")
653                         {
654                                 WriteByte(MSG_ONE, duration);
655                                 WriteByte(MSG_ONE, countdown_num);
656                         }
657                 });
658         }
659 }
660 void Send_CSQC_Centerprint_Generic_Expire(entity e, float id)
661 {
662         Send_CSQC_Centerprint_Generic(e, id, "", 1, 0);
663 }
664 #endif