]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_lms.qc
efab45e3fa817be6fbc02eb316167b47b7f8c34f
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_lms.qc
1 #ifndef GAMEMODE_LMS_H
2 #define GAMEMODE_LMS_H
3
4 #define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
5 void lms_Initialize();
6
7 REGISTER_MUTATOR(lms, false)
8 {
9         SetLimits(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override), 0, -1, -1);
10
11         MUTATOR_ONADD
12         {
13                 if (time > 1) // game loads at time 1
14                         error("This is a game type and it cannot be added at runtime.");
15                 lms_Initialize();
16         }
17
18         MUTATOR_ONROLLBACK_OR_REMOVE
19         {
20                 // we actually cannot roll back lms_Initialize here
21                 // BUT: we don't need to! If this gets called, adding always
22                 // succeeds.
23         }
24
25         MUTATOR_ONREMOVE
26         {
27                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
28                 return -1;
29         }
30
31         return 0;
32 }
33
34 // scoreboard stuff
35 const float SP_LMS_LIVES = 4;
36 const float SP_LMS_RANK = 5;
37
38 // lives related defs
39 float lms_lowest_lives;
40 float lms_next_place;
41 float LMS_NewPlayerLives();
42
43 #endif
44
45 #ifdef IMPLEMENTATION
46
47 #include "../../campaign.qh"
48 #include "../../command/cmd.qh"
49
50 int autocvar_g_lms_extra_lives;
51 bool autocvar_g_lms_join_anytime;
52 int autocvar_g_lms_last_join;
53 bool autocvar_g_lms_regenerate;
54
55 // main functions
56 float LMS_NewPlayerLives()
57 {
58         float fl;
59         fl = autocvar_fraglimit;
60         if(fl == 0)
61                 fl = 999;
62
63         // first player has left the game for dying too much? Nobody else can get in.
64         if(lms_lowest_lives < 1)
65                 return 0;
66
67         if(!autocvar_g_lms_join_anytime)
68                 if(lms_lowest_lives < fl - autocvar_g_lms_last_join)
69                         return 0;
70
71         return bound(1, lms_lowest_lives, fl);
72 }
73
74 // mutator hooks
75 MUTATOR_HOOKFUNCTION(lms, reset_map_global)
76 {
77         lms_lowest_lives = 999;
78         lms_next_place = player_count;
79
80         return false;
81 }
82
83 MUTATOR_HOOKFUNCTION(lms, reset_map_players)
84 {SELFPARAM();
85         entity e;
86         if(restart_mapalreadyrestarted || (time < game_starttime))
87         FOR_EACH_CLIENT(e)
88         if(IS_PLAYER(e))
89         {
90                 WITH(entity, self, e, PlayerScore_Add(e, SP_LMS_LIVES, LMS_NewPlayerLives()));
91         }
92
93         return false;
94 }
95
96 MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
97 {SELFPARAM();
98         // player is dead and becomes observer
99         // FIXME fix LMS scoring for new system
100         if(PlayerScore_Add(self, SP_LMS_RANK, 0) > 0)
101         {
102                 self.classname = "observer";
103                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_LMS_NOLIVES);
104         }
105
106         return false;
107 }
108
109 MUTATOR_HOOKFUNCTION(lms, PlayerDies)
110 {SELFPARAM();
111         self.respawn_flags |= RESPAWN_FORCE;
112
113         return false;
114 }
115
116 void lms_RemovePlayer(entity player)
117 {
118         // Only if the player cannot play at all
119         if(PlayerScore_Add(player, SP_LMS_RANK, 0) == 666)
120                 player.frags = FRAGS_SPECTATOR;
121         else
122                 player.frags = FRAGS_LMS_LOSER;
123
124         if(player.killcount != -666)
125                 if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
126                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
127                 else
128                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
129 }
130
131 MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
132 {SELFPARAM();
133         lms_RemovePlayer(self);
134         return false;
135 }
136
137 MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
138 {SELFPARAM();
139         lms_RemovePlayer(self);
140         return false;
141 }
142
143 MUTATOR_HOOKFUNCTION(lms, ClientConnect)
144 {SELFPARAM();
145         self.classname = "player";
146         campaign_bots_may_start = 1;
147
148         if(PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
149         {
150                 PlayerScore_Add(self, SP_LMS_RANK, 666);
151                 self.frags = FRAGS_SPECTATOR;
152         }
153
154         return false;
155 }
156
157 MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
158 {SELFPARAM();
159         if(self.deadflag == DEAD_DYING)
160                 self.deadflag = DEAD_RESPAWNING;
161
162         return false;
163 }
164
165 MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
166 {
167         if(autocvar_g_lms_regenerate)
168                 return false;
169         return true;
170 }
171
172 MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
173 {
174         // forbode!
175         return true;
176 }
177
178 MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
179 {
180         // remove a life
181         float tl;
182         tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1);
183         if(tl < lms_lowest_lives)
184                 lms_lowest_lives = tl;
185         if(tl <= 0)
186         {
187                 if(!lms_next_place)
188                         lms_next_place = player_count;
189                 else
190                         lms_next_place = min(lms_next_place, player_count);
191                 PlayerScore_Add(frag_target, SP_LMS_RANK, lms_next_place); // won't ever spawn again
192                 --lms_next_place;
193         }
194         frag_score = 0;
195
196         return true;
197 }
198
199 MUTATOR_HOOKFUNCTION(lms, SetStartItems)
200 {
201         start_items &= ~IT_UNLIMITED_AMMO;
202         start_health       = warmup_start_health       = cvar("g_lms_start_health");
203         start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
204         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
205         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
206         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
207         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
208         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
209         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
210
211         return false;
212 }
213
214 MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
215 {
216         // don't clear player score
217         return true;
218 }
219
220 MUTATOR_HOOKFUNCTION(lms, FilterItem)
221 {SELFPARAM();
222         if(autocvar_g_lms_extra_lives)
223         if(self.itemdef == ITEM_HealthMega)
224         {
225                 self.max_health = 1;
226                 return false;
227         }
228
229         return true;
230 }
231
232 MUTATOR_HOOKFUNCTION(lms, ItemTouch)
233 {SELFPARAM();
234         // give extra lives for mega health
235         if (self.items & ITEM_HealthMega.m_itemid)
236         {
237                 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_EXTRALIVES);
238                 PlayerScore_Add(other, SP_LMS_LIVES, autocvar_g_lms_extra_lives);
239         }
240
241         return MUT_ITEMTOUCH_CONTINUE;
242 }
243
244 MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
245 {
246         entity head;
247         FOR_EACH_REALCLIENT(head)
248         {
249                 ++bot_activerealplayers;
250                 ++bot_realplayers;
251         }
252
253         return true;
254 }
255
256 MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
257 {
258         if(self.lms_spectate_warning)
259         {
260                 // for the forfeit message...
261                 self.lms_spectate_warning = 2;
262                 // mark player as spectator
263                 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
264         }
265         else
266         {
267                 self.lms_spectate_warning = 1;
268                 sprint(self, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
269                 return MUT_SPECCMD_RETURN;
270         }
271         return MUT_SPECCMD_CONTINUE;
272 }
273
274 MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
275 {
276         ret_float = WinningCondition_LMS();
277         return true;
278 }
279
280 MUTATOR_HOOKFUNCTION(lms, WantWeapon)
281 {
282         want_allguns = true;
283         return false;
284 }
285
286 MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
287 {
288         return true;
289 }
290
291 MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
292 {
293         if(gameover)
294         if(score_field == SP_LMS_RANK)
295                 return true; // allow writing to this field in intermission as it is needed for newly joining players
296         return false;
297 }
298
299 // scoreboard stuff
300 void lms_ScoreRules()
301 {
302         ScoreRules_basics(0, 0, 0, false);
303         ScoreInfo_SetLabel_PlayerScore(SP_LMS_LIVES,    "lives",     SFL_SORT_PRIO_SECONDARY);
304         ScoreInfo_SetLabel_PlayerScore(SP_LMS_RANK,     "rank",      SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
305         ScoreRules_basics_end();
306 }
307
308 void lms_Initialize()
309 {
310         lms_lowest_lives = 9999;
311         lms_next_place = 0;
312
313         lms_ScoreRules();
314 }
315
316
317 #endif