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