]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_lms.qc
Merge branch 'DefaultUser/gametype_votescreen' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_lms.qc
1 #include "gamemode_lms.qh"
2
3 #include <common/mutators/mutator/instagib/items.qc>
4 #include <server/campaign.qh>
5 #include <server/command/_mod.qh>
6
7 int autocvar_g_lms_extra_lives;
8 bool autocvar_g_lms_join_anytime;
9 int autocvar_g_lms_last_join;
10 bool autocvar_g_lms_regenerate;
11
12 // main functions
13 float LMS_NewPlayerLives()
14 {
15         float fl;
16         fl = autocvar_fraglimit;
17         if(fl == 0)
18                 fl = 999;
19
20         // first player has left the game for dying too much? Nobody else can get in.
21         if(lms_lowest_lives < 1)
22                 return 0;
23
24         if(!autocvar_g_lms_join_anytime)
25                 if(lms_lowest_lives < fl - autocvar_g_lms_last_join)
26                         return 0;
27
28         return bound(1, lms_lowest_lives, fl);
29 }
30
31 void ClearWinners();
32
33 // LMS winning condition: game terminates if and only if there's at most one
34 // one player who's living lives. Top two scores being equal cancels the time
35 // limit.
36 int WinningCondition_LMS()
37 {
38         entity head, head2;
39         bool have_player = false;
40         bool have_players = false;
41
42         int l = LMS_NewPlayerLives();
43
44         head = find(NULL, classname, STR_PLAYER);
45         if(head)
46                 have_player = true;
47         head2 = find(head, classname, STR_PLAYER);
48         if(head2)
49                 have_players = true;
50
51         if(have_player)
52         {
53                 // we have at least one player
54                 if(have_players)
55                 {
56                         // two or more active players - continue with the game
57                 }
58                 else
59                 {
60                         // exactly one player?
61
62                         ClearWinners();
63                         SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
64
65                         if(l)
66                         {
67                                 // game still running (that is, nobody got removed from the game by a frag yet)? then continue
68                                 return WINNING_NO;
69                         }
70                         else
71                         {
72                                 // a winner!
73                                 // and assign him his first place
74                                 PlayerScore_Add(head, SP_LMS_RANK, 1);
75                                 return WINNING_YES;
76                         }
77                 }
78         }
79         else
80         {
81                 // nobody is playing at all...
82                 if(l)
83                 {
84                         // wait for players...
85                 }
86                 else
87                 {
88                         // SNAFU (maybe a draw game?)
89                         ClearWinners();
90                         LOG_TRACE("No players, ending game.");
91                         return WINNING_YES;
92                 }
93         }
94
95         // When we get here, we have at least two players who are actually LIVING,
96         // now check if the top two players have equal score.
97         WinningConditionHelper(NULL);
98
99         ClearWinners();
100         if(WinningConditionHelper_winner)
101                 WinningConditionHelper_winner.winning = true;
102         if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
103                 return WINNING_NEVER;
104
105         // Top two have different scores? Way to go for our beloved TIMELIMIT!
106         return WINNING_NO;
107 }
108
109 // mutator hooks
110 MUTATOR_HOOKFUNCTION(lms, reset_map_global)
111 {
112         lms_lowest_lives = 999;
113         lms_next_place = player_count;
114 }
115
116 MUTATOR_HOOKFUNCTION(lms, reset_map_players)
117 {
118         if(restart_mapalreadyrestarted || (time < game_starttime))
119         FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(PlayerScore_Add(it, SP_LMS_LIVES, LMS_NewPlayerLives())));
120 }
121
122 MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
123 {
124         entity player = M_ARGV(0, entity);
125
126         // player is dead and becomes observer
127         // FIXME fix LMS scoring for new system
128         if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0)
129         {
130                 TRANSMUTE(Observer, player);
131                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
132         }
133 }
134
135 MUTATOR_HOOKFUNCTION(lms, PlayerDies)
136 {
137         entity frag_target = M_ARGV(2, entity);
138
139         frag_target.respawn_flags |= RESPAWN_FORCE;
140 }
141
142 void lms_RemovePlayer(entity player)
143 {
144         // Only if the player cannot play at all
145         if(PlayerScore_Add(player, SP_LMS_RANK, 0) == 666)
146                 player.frags = FRAGS_SPECTATOR;
147         else
148                 player.frags = FRAGS_LMS_LOSER;
149
150         if(player.killcount != FRAGS_SPECTATOR)
151                 if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
152                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
153                 else
154                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
155 }
156
157 MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
158 {
159         entity player = M_ARGV(0, entity);
160
161         lms_RemovePlayer(player);
162 }
163
164 MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
165 {
166     entity player = M_ARGV(0, entity);
167
168         lms_RemovePlayer(player);
169         return true;  // prevent team reset
170 }
171
172 MUTATOR_HOOKFUNCTION(lms, ClientConnect)
173 {
174         entity player = M_ARGV(0, entity);
175
176         TRANSMUTE(Player, player);
177         campaign_bots_may_start = true;
178
179         if(PlayerScore_Add(player, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
180         {
181                 PlayerScore_Add(player, SP_LMS_RANK, 666);
182                 player.frags = FRAGS_SPECTATOR;
183         }
184 }
185
186 MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
187 {
188         entity player = M_ARGV(0, entity);
189
190         if(player.deadflag == DEAD_DYING)
191                 player.deadflag = DEAD_RESPAWNING;
192 }
193
194 MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
195 {
196         if(autocvar_g_lms_regenerate)
197                 return false;
198         return true;
199 }
200
201 MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
202 {
203         // forbode!
204         return true;
205 }
206
207 MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
208 {
209         entity frag_target = M_ARGV(1, entity);
210
211         // remove a life
212         float tl;
213         tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1);
214         if(tl < lms_lowest_lives)
215                 lms_lowest_lives = tl;
216         if(tl <= 0)
217         {
218                 if(!lms_next_place)
219                         lms_next_place = player_count;
220                 else
221                         lms_next_place = min(lms_next_place, player_count);
222                 PlayerScore_Add(frag_target, SP_LMS_RANK, lms_next_place); // won't ever spawn again
223                 --lms_next_place;
224         }
225         M_ARGV(2, float) = 0;
226
227         return true;
228 }
229
230 MUTATOR_HOOKFUNCTION(lms, SetStartItems)
231 {
232         start_items &= ~IT_UNLIMITED_AMMO;
233         start_health       = warmup_start_health       = cvar("g_lms_start_health");
234         start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
235         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
236         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
237         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
238         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
239         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
240         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
241 }
242
243 MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
244 {
245         // don't clear player score
246         return true;
247 }
248
249 MUTATOR_HOOKFUNCTION(lms, FilterItem)
250 {
251         entity item = M_ARGV(0, entity);
252
253         if(autocvar_g_lms_extra_lives)
254         if(item.itemdef == ITEM_ExtraLife)
255                 return false;
256
257         return true;
258 }
259
260 void lms_extralife(entity this)
261 {
262         StartItem(this, ITEM_ExtraLife);
263 }
264
265 MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
266 {
267         if (!autocvar_g_powerups) return false;
268         if (!autocvar_g_lms_extra_lives) return false;
269
270         entity ent = M_ARGV(0, entity);
271
272         // Can't use .itemdef here
273         if (ent.classname != "item_health_mega") return false;
274
275         entity e = spawn();
276         setthink(e, lms_extralife);
277
278         e.nextthink = time + 0.1;
279         e.spawnflags = ent.spawnflags;
280         e.noalign = ent.noalign;
281         setorigin(e, ent.origin);
282
283         return true;
284 }
285
286 MUTATOR_HOOKFUNCTION(lms, ItemTouch)
287 {
288         entity item = M_ARGV(0, entity);
289         entity toucher = M_ARGV(1, entity);
290
291         if(item.itemdef == ITEM_ExtraLife)
292         {
293                 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
294                 PlayerScore_Add(toucher, SP_LMS_LIVES, autocvar_g_lms_extra_lives);
295                 return MUT_ITEMTOUCH_PICKUP;
296         }
297
298         return MUT_ITEMTOUCH_CONTINUE;
299 }
300
301 MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
302 {
303         FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(
304                 ++M_ARGV(0, int);
305                 ++M_ARGV(1, int);
306         ));
307
308         return true;
309 }
310
311 MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
312 {
313     entity player = M_ARGV(0, entity);
314
315         if(player.lms_spectate_warning)
316         {
317                 // for the forfeit message...
318                 player.lms_spectate_warning = 2;
319                 // mark player as spectator
320                 PlayerScore_Add(player, SP_LMS_RANK, 666 - PlayerScore_Add(player, SP_LMS_RANK, 0));
321         }
322         else
323         {
324                 player.lms_spectate_warning = 1;
325                 sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
326                 return MUT_SPECCMD_RETURN;
327         }
328         return MUT_SPECCMD_CONTINUE;
329 }
330
331 MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
332 {
333         M_ARGV(0, float) = WinningCondition_LMS();
334         return true;
335 }
336
337 MUTATOR_HOOKFUNCTION(lms, WantWeapon)
338 {
339         M_ARGV(2, bool) = true; // all weapons
340 }
341
342 MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
343 {
344         return true;
345 }
346
347 MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
348 {
349         if(gameover)
350         if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
351                 return true; // allow writing to this field in intermission as it is needed for newly joining players
352 }
353
354 // scoreboard stuff
355 void lms_ScoreRules()
356 {
357         ScoreRules_basics(0, 0, 0, false);
358         ScoreInfo_SetLabel_PlayerScore(SP_LMS_LIVES,    "lives",     SFL_SORT_PRIO_SECONDARY);
359         ScoreInfo_SetLabel_PlayerScore(SP_LMS_RANK,     "rank",      SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
360         ScoreRules_basics_end();
361 }
362
363 void lms_Initialize()
364 {
365         lms_lowest_lives = 9999;
366         lms_next_place = 0;
367
368         lms_ScoreRules();
369 }