]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qc
GunGame: Small improvements.
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / gungame / sv_gungame.qc
1 /// \file
2 /// \brief Source file that contains implementation of the GunGame gamemode.
3 /// \copyright GNU GPLv2 or any later version.
4
5 #include "sv_gungame.qh"
6
7 //============================ Constants ======================================
8
9 const string GUNGAME_WEAPONS_CVAR = "g_gg_weapons";
10
11 //======================= Global variables ====================================
12
13 /// \brief Number of kills needed to advance to the next weapon.
14 int autocvar_g_gg_kills_per_weapon;
15
16 int gungame_win_level; ///< Player who reaches this level wins.
17 string gungame_weapons; ///< Holds weapons corresponding to levels.
18
19 int gungame_status; ///< Holds global status of the game.
20 entity gungame_leading_player; ///< Holds the leading player.
21 int gungame_leading_level; ///< Holds the leading level.
22 entity gungame_leading_weapon; ///< Holds the leading weapon.
23
24 //====================== Forward declarations =================================
25
26 /// \brief Resets the state to initial one.
27 void GunGame_Reset();
28
29 /// \brief Returns the weapon that corresponds to the given level.
30 /// \param[in] level Level of the weapon.
31 /// \return Weapon corresponding to the given level.
32 entity GunGame_GetWeapon(int level);
33
34 /// \brief Updates stats of all players.
35 void GunGame_UpdateStats();
36
37 //========================= Free functions ====================================
38
39 void GunGame_Initialize()
40 {
41         GunGame_Reset();
42 }
43
44 void GunGame_Reset()
45 {
46         strcpy(gungame_weapons, cvar_string(GUNGAME_WEAPONS_CVAR));
47         gungame_win_level = tokenize_console(gungame_weapons) *
48                 autocvar_g_gg_kills_per_weapon;
49         if (gungame_win_level == 0)
50         {
51                 LOG_FATAL("GunGame: Invalid weapon configuration.");
52         }
53         gungame_status = WINNING_NO;
54         GameRules_limit_score(gungame_win_level);
55         gungame_leading_player = NULL;
56         gungame_leading_level = 0;
57         gungame_leading_weapon = GunGame_GetWeapon(0);
58         GunGame_UpdateStats();
59 }
60
61 entity GunGame_GetWeapon(int level)
62 {
63         if (level >= gungame_win_level)
64         {
65                 return NULL;
66         }
67         tokenize_console(gungame_weapons);
68         string weapon_name = argv(floor(level / autocvar_g_gg_kills_per_weapon));
69         Weapon weapon = Weapons_fromstr(weapon_name);
70         if (weapon == WEP_Null)
71         {
72                 LOG_FATAL("GunGame_GetWeapon: Invalid level or weapon name");
73         }
74         return weapon;
75 }
76
77 /// \brief Returns the player level.
78 /// \param[in] player Player to check.
79 /// \return Level of the player.
80 int GunGame_GetPlayerLevel(entity player)
81 {
82         return PlayerScore_Get(player, SP_SCORE);
83 }
84
85 /// \brief Updates the information about the leading player.
86 void GunGame_UpdateLeadingPlayer()
87 {
88         entity previous_leader = gungame_leading_player;
89         FOREACH_CLIENT(true,
90         {
91                 if (gungame_leading_player == NULL)
92                 {
93                         gungame_leading_player = it;
94                         continue;
95                 }
96                 if (GunGame_GetPlayerLevel(it) > GunGame_GetPlayerLevel(
97                         gungame_leading_player))
98                 {
99                         gungame_leading_player = it;
100                 }
101         });
102         if (gungame_leading_player == NULL)
103         {
104                 return;
105         }
106         if ((gungame_leading_player == previous_leader) &&
107                 (GunGame_GetPlayerLevel(gungame_leading_player) ==
108                 gungame_leading_level))
109         {
110                 return;
111         }
112         gungame_leading_level = GunGame_GetPlayerLevel(gungame_leading_player);
113         gungame_leading_weapon = GunGame_GetWeapon(gungame_leading_level);
114         GunGame_UpdateStats();
115 }
116
117 void GunGame_UpdateStats()
118 {
119         FOREACH_CLIENT(IS_REAL_CLIENT(it),
120         {
121                 STAT(GUNGAME_LEADING_WEAPON, it) = gungame_leading_weapon.m_id;
122         });
123 }
124
125 /// \brief Gives the player a weapon that corresponds to their level.
126 /// \param[in,out] player Player to give weapon to.
127 void GunGame_GivePlayerWeapon(entity player)
128 {
129         int level = GunGame_GetPlayerLevel(player);
130         if (level >= gungame_win_level)
131         {
132                 return;
133         }
134         entity weapon = GunGame_GetWeapon(level);
135         STAT(WEAPONS, player) |= weapon.m_wepset;
136         int levels_left = gungame_win_level - level;
137         if (levels_left > 3)
138         {
139                 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
140                         CENTER_GUNGAME_NEW_LEVEL, level + 1, weapon.m_id);
141                 Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_GUNGAME_NEW_LEVEL,
142                         level + 1, weapon.m_id);
143                 return;
144         }
145         if (levels_left > 1)
146         {
147                 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
148                         CENTER_GUNGAME_LEVELS_LEFT, levels_left, weapon.m_id);
149                 Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_GUNGAME_LEVELS_LEFT,
150                         levels_left, weapon.m_id);
151                 return;
152         }
153         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_GUNGAME_LAST_LEVEL,
154                 weapon.m_id);
155         Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_GUNGAME_LAST_LEVEL,
156                 weapon.m_id);
157 }
158
159 //============================= Hooks ========================================
160
161 /// \brief Hook that is called to determine if there is a weapon arena.
162 MUTATOR_HOOKFUNCTION(gg, SetWeaponArena)
163 {
164         M_ARGV(0, string) = "off";
165 }
166
167 /// \brief Hook that is called to determine start items of all players.
168 MUTATOR_HOOKFUNCTION(gg, SetStartItems)
169 {
170         start_weapons = WEPSET(Null);
171         warmup_start_weapons = WEPSET(Null);
172 }
173
174 /// \brief Hook that is called when an item is about to spawn.
175 MUTATOR_HOOKFUNCTION(gg, FilterItemDefinition)
176 {
177         entity item = M_ARGV(0, entity);
178         if (item.instanceOfAmmo)
179         {
180                 // Block ammo from spawning.
181                 return true;
182         }
183         if (item.instanceOfWeaponPickup)
184         {
185                 // Block weapons from spawning.
186                 return true;
187         }
188 }
189
190 /// \brief Hook that is called every frame to check if the game is won and/or
191 /// over.
192 MUTATOR_HOOKFUNCTION(gg, CheckRules_World)
193 {
194         M_ARGV(0, float) = gungame_status;
195         //M_ARGV(2, float) = gungame_win_level;
196         return true;
197 }
198
199 /// \brief Hook that is called when player connects to the server.
200 MUTATOR_HOOKFUNCTION(gg, ClientConnect)
201 {
202         entity player = M_ARGV(0, entity);
203         if (!IS_REAL_CLIENT(player))
204         {
205                 return true;
206         }
207         STAT(GUNGAME_LEADING_WEAPON, player) = gungame_leading_weapon.m_id;
208         return true;
209 }
210
211 MUTATOR_HOOKFUNCTION(gg, reset_map_global)
212 {
213         GunGame_Reset();
214 }
215
216 /// \brief Hook that is called when player spawns.
217 MUTATOR_HOOKFUNCTION(gg, PlayerSpawn, CBC_ORDER_LAST)
218 {
219         entity player = M_ARGV(0, entity);
220         STAT(WEAPONS, player) = WEPSET(Null);
221         GunGame_GivePlayerWeapon(player);
222         player.items |= IT_UNLIMITED_AMMO;
223 }
224
225 /// \brief Hook which is called when the player tries to throw their weapon.
226 MUTATOR_HOOKFUNCTION(gg, ForbidThrowCurrentWeapon)
227 {
228         return true;
229 }
230
231 /// \brief Hook that is called when player dies.
232 MUTATOR_HOOKFUNCTION(gg, PlayerDies)
233 {
234         GunGame_UpdateLeadingPlayer();
235         entity attacker = M_ARGV(1, entity);
236         if (!IS_PLAYER(attacker) || IS_DEAD(attacker))
237         {
238                 return;
239         }
240         if (GunGame_GetPlayerLevel(attacker) >= gungame_win_level)
241         {
242                 gungame_status = WINNING_YES;
243                 return;
244         }
245         STAT(WEAPONS, attacker) = WEPSET(Null);
246         GunGame_GivePlayerWeapon(attacker);
247 }
248
249 /// \brief Hook that determines whether remaining frags are announced.
250 MUTATOR_HOOKFUNCTION(gg, Scores_CountFragsRemaining)
251 {
252         // Announce remaining frags.
253         return true;
254 }