Xonotic
sv_race.qc
Go to the documentation of this file.
1 #include "sv_race.qh"
2 
3 #include <server/client.qh>
4 #include <server/world.qh>
5 #include <server/gamelog.qh>
6 #include <server/intermission.qh>
7 #include <server/race.qh>
8 #include <common/ent_cs.qh>
10 
11 #define autocvar_g_race_laps_limit cvar("g_race_laps_limit")
15 
16 // legacy bot roles
19 {
20  if(IS_DEAD(this))
21  return;
22 
24  {
26 
27  bool raw_touch_check = true;
28  int cp = this.race_checkpoint;
29 
30  LABEL(search_racecheckpoints)
32  {
33  if(it.cnt == cp || cp == -1)
34  {
35  // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
36  // e.g. checkpoint in front of Stormkeep's warpzone
37  // the same workaround is applied in CTS game mode
38  if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
39  {
40  cp = race_NextCheckpoint(cp);
41  raw_touch_check = false;
42  goto search_racecheckpoints;
43  }
44  navigation_routerating(this, it, 1000000, 5000);
45  }
46  });
47 
49 
51  }
52 }
53 
55 {
58  if (race_teams)
59  field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
60  else if (g_race_qualifying)
61  field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
63  {
64  field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
65  field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
66  field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
67  }
68  });
69 }
70 
71 void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
72 {
74  GameLogEcho(strcat(":race:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
75 }
76 
77 float WinningCondition_Race(float fraglimit)
78 {
79  float wc;
80  float n, c;
81 
82  n = 0;
83  c = 0;
85  ++n;
86  if(CS(it).race_completed)
87  ++c;
88  });
89  if(n && (n == c))
90  return WINNING_YES;
91  wc = WinningCondition_Scores(fraglimit, 0);
92 
93  // ALWAYS initiate overtime, unless EVERYONE has finished the race!
95  // do NOT support equality when the laps are all raced!
97  else
98  return WINNING_NEVER;
99 }
100 
102 {
103  float wc;
104  wc = WinningCondition_Scores(limit, 0);
105 
106  // NEVER initiate overtime
108  {
109  return WINNING_YES;
110  }
111 
112  return wc;
113 }
114 
116 {
118  M_ARGV(1, float) = 0; // killtime
119 }
120 
121 MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
122 {
123  entity player = M_ARGV(0, entity);
124 
126  race_PreparePlayer(player); // nice try
127 }
128 
129 MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
130 {
131  entity player = M_ARGV(0, entity);
132  float dt = M_ARGV(1, float);
133 
134  player.race_movetime_frac += dt;
135  float f = floor(player.race_movetime_frac);
136  player.race_movetime_frac -= f;
137  player.race_movetime_count += f;
138  player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
139 
140  if(IS_PLAYER(player))
141  {
142  if (player.race_penalty)
143  if (time > player.race_penalty)
144  player.race_penalty = 0;
145  if(player.race_penalty)
146  {
147  player.velocity = '0 0 0';
148  set_movetype(player, MOVETYPE_NONE);
149  player.disableclientprediction = 2;
150  }
151  }
152 
153  // force kbd movement for fairness
154  float wishspeed;
155  vector wishvel;
156 
157  // if record times matter
158  // ensure nothing EVIL is being done (i.e. div0_evade)
159  // this hinders joystick users though
160  // but it still gives SOME analog control
161  wishvel.x = fabs(CS(player).movement.x);
162  wishvel.y = fabs(CS(player).movement.y);
163  if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
164  {
165  wishvel.z = 0;
166  wishspeed = vlen(wishvel);
167  if(wishvel.x >= 2 * wishvel.y)
168  {
169  // pure X motion
170  if(CS(player).movement.x > 0)
171  CS(player).movement_x = wishspeed;
172  else
173  CS(player).movement_x = -wishspeed;
174  CS(player).movement_y = 0;
175  }
176  else if(wishvel.y >= 2 * wishvel.x)
177  {
178  // pure Y motion
179  CS(player).movement_x = 0;
180  if(CS(player).movement.y > 0)
181  CS(player).movement_y = wishspeed;
182  else
183  CS(player).movement_y = -wishspeed;
184  }
185  else
186  {
187  // diagonal
188  if(CS(player).movement.x > 0)
189  CS(player).movement_x = M_SQRT1_2 * wishspeed;
190  else
191  CS(player).movement_x = -M_SQRT1_2 * wishspeed;
192  if(CS(player).movement.y > 0)
193  CS(player).movement_y = M_SQRT1_2 * wishspeed;
194  else
195  CS(player).movement_y = -M_SQRT1_2 * wishspeed;
196  }
197  }
198 }
199 
200 MUTATOR_HOOKFUNCTION(rc, reset_map_global)
201 {
202  float s;
203 
205 
207  PlayerScore_Sort(race_place, 0, true, false);
208 
209  FOREACH_CLIENT(true, {
210  if(it.race_place)
211  {
212  s = GameRules_scoring_add(it, RACE_FASTEST, 0);
213  if(!s)
214  it.race_place = 0;
215  }
216  race_EventLog(ftos(it.race_place), it);
217  });
218 
219  if(g_race_qualifying == 2)
220  {
221  g_race_qualifying = 0;
223  cvar_set("fraglimit", ftos(race_fraglimit));
224  cvar_set("leadlimit", ftos(race_leadlimit));
225  cvar_set("timelimit", ftos(race_timelimit));
226  race_ScoreRules();
227  }
228 }
229 
231 {
232  entity player = M_ARGV(0, entity);
233 
234  race_PreparePlayer(player);
235  player.race_checkpoint = -1;
236 
237  race_SendAll(player, false);
238 }
239 
240 MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
241 {
242  entity player = M_ARGV(0, entity);
243 
245  {
246  if(GameRules_scoring_add(player, RACE_FASTEST, 0))
247  player.frags = FRAGS_PLAYER_OUT_OF_GAME;
248  else
249  player.frags = FRAGS_SPECTATOR;
250  }
251 
252  race_PreparePlayer(player);
253  player.race_checkpoint = -1;
254 }
255 
256 MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
257 {
258  entity player = M_ARGV(0, entity);
259  entity spawn_spot = M_ARGV(1, entity);
260 
261  if(spawn_spot.target == "")
262  // Emergency: this wasn't a real spawnpoint. Can this ever happen?
263  race_PreparePlayer(player);
264 
265  // if we need to respawn, do it right
266  player.race_respawn_checkpoint = player.race_checkpoint;
267  player.race_respawn_spotref = spawn_spot;
268 
269  player.race_place = 0;
270 }
271 
273 {
274  entity player = M_ARGV(0, entity);
275 
276  if(IS_PLAYER(player))
277  if(!game_stopped)
278  {
279  if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
280  race_PreparePlayer(player);
281  else // respawn
282  race_RetractPlayer(player);
283 
284  race_AbandonRaceCheck(player);
285  }
286 }
287 
288 MUTATOR_HOOKFUNCTION(rc, PlayerDamaged)
289 {
290  int frag_deathtype = M_ARGV(5, int);
291  if (frag_deathtype == DEATH_KILL.m_id)
292  return true; // forbid logging damage
293 }
294 
295 MUTATOR_HOOKFUNCTION(rc, PlayerDies)
296 {
297  entity frag_target = M_ARGV(2, entity);
298 
299  frag_target.respawn_flags |= RESPAWN_FORCE;
300  race_AbandonRaceCheck(frag_target);
301 }
302 
303 MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
304 {
305  entity bot = M_ARGV(0, entity);
306 
307  bot.havocbot_role = havocbot_role_race;
308  return true;
309 }
310 
312 {
313  entity player = M_ARGV(0, entity);
314 
315  race_checkAndWriteName(player);
316  race_SpeedAwardFrame(player);
317 }
318 
319 MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
320 {
322  return true; // in qualifying, you don't lose score by observing
323 }
324 
326 {
327  M_ARGV(0, float) = race_teams;
328  return true;
329 }
330 
331 MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
332 {
333  // announce remaining frags if not in qualifying mode
334  if(!g_race_qualifying)
335  return true;
336 }
337 
338 MUTATOR_HOOKFUNCTION(rc, GetRecords)
339 {
340  int record_page = M_ARGV(0, int);
341  string ret_string = M_ARGV(1, string);
342 
343  for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
344  {
345  if(MapInfo_Get_ByID(i))
346  {
347  float r = race_readTime(MapInfo_Map_bspname, 1);
348 
349  if(!r)
350  continue;
351 
352  string h = race_readName(MapInfo_Map_bspname, 1);
353  ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
354  }
355  }
356 
357  M_ARGV(1, string) = ret_string;
358 }
359 
360 MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
361 {
362  return true; // doesn't work so well
363 }
364 
366 {
367  entity player = M_ARGV(0, entity);
368 
369  stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
370 }
371 
373 {
374  float checkrules_timelimit = M_ARGV(1, float);
375  float checkrules_fraglimit = M_ARGV(2, float);
376 
377  if(checkrules_timelimit >= 0)
378  {
379  if(!g_race_qualifying)
380  {
381  M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
382  return true;
383  }
384  else if(g_race_qualifying == 2)
385  {
386  M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
387  return true;
388  }
389  }
390 }
391 
392 MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
393 {
394  if(g_race_qualifying == 2)
395  warmup_stage = 0;
396 }
397 
399 {
400  race_ScoreRules();
401  if(g_race_qualifying == 2)
402  warmup_stage = 0;
403  radar_showenemies = true;
404 }
405 
407 {
408  int fraglimit_override, leadlimit_override;
409  float timelimit_override, qualifying_override;
410 
412  {
413  GameRules_teams(true);
415  }
416  else
417  race_teams = 0;
418 
419  qualifying_override = autocvar_g_race_qualifying_timelimit_override;
420  fraglimit_override = autocvar_g_race_laps_limit;
421  leadlimit_override = 0; // currently not supported by race
422  timelimit_override = autocvar_timelimit_override;
423 
424  float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
425 
427 
429  {
430  g_race_qualifying = 1;
432  }
433  else if(want_qualifying)
434  {
435  g_race_qualifying = 2;
437  race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
438  race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
439  race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
440  qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
441  fraglimit_override = 0;
442  leadlimit_override = 0;
443  timelimit_override = qualifying_override;
444  }
445  else
446  g_race_qualifying = 0;
447  GameRules_limit_score(fraglimit_override);
448  GameRules_limit_lead(leadlimit_override);
449  GameRules_limit_time(timelimit_override);
450  GameRules_limit_time_qualifying(qualifying_override);
451 }
void race_SendAll(entity player, bool only_rankings)
Definition: race.qc:324
const int RESPAWN_FORCE
Definition: client.qh:330
#define GameRules_scoring(teams, spprio, stprio, fields)
Definition: sv_rules.qh:53
#define IL_EACH(this, cond, body)
float MOVETYPE_NONE
Definition: progsdefs.qc:246
void navigation_goalrating_start(entity this)
Definition: navigation.qc:1830
const int CBC_ORDER_EXCLUSIVE
Definition: base.qh:9
float autocvar_g_race_qualifying_timelimit_override
Definition: sv_race.qc:13
float race_place
Definition: race.qh:23
void race_ClearRecords()
Definition: race.qc:1210
void ClientConnect(entity this)
ClientConnect
Definition: client.qc:1096
void navigation_goalrating_end(entity this)
Definition: navigation.qc:1845
void Score_NicePrint(entity to)
Prints the scores to the console of a player.
Definition: scores.qc:903
void ClientKill(entity this)
Definition: clientkill.qc:208
const int SFL_SORT_PRIO_SECONDARY
Scoring priority (NOTE: PRIMARY is used for fraglimit)
Definition: scores.qh:126
entity() spawn
ClientState CS(Client this)
Definition: state.qh:47
#define FOREACH_CLIENT(cond, body)
Definition: utils.qh:49
#define autocvar_g_race_laps_limit
Definition: sv_race.qc:11
#define GameRules_scoring_add(client, fld, value)
Definition: sv_rules.qh:78
void GameRules_limit_score(int limit)
Definition: sv_rules.qc:34
void race_checkAndWriteName(entity player)
Definition: race.qc:143
const int WINNING_STARTSUDDENDEATHOVERTIME
Definition: world.qh:138
bool warmup_stage
Definition: main.qh:103
#define RACE_RECORD
Definition: util.qh:58
const int SFL_SORT_PRIO_PRIMARY
Definition: scores.qh:127
bool MapInfo_Get_ByID(int i)
Definition: mapinfo.qc:256
const float M_SQRT1_2
Definition: mathlib.qh:115
void CheckRules_World()
Definition: world.qc:1593
void GameRules_score_enabled(bool value)
Disabling score disables the "score" column on the scoreboard.
Definition: sv_rules.qc:28
int killcount
Definition: client.qh:317
bool g_race_qualifying
Definition: race.qh:12
#define TIME_ENCODED_TOSTRING(n)
Definition: util.qh:57
void GameRules_limit_time_qualifying(int limit)
Definition: sv_rules.qc:64
const int SFL_LOWER_IS_BETTER
Lower scores are better (e.g.
Definition: scores.qh:98
void navigation_routerating(entity this, entity e, float f, float rangebias)
Definition: navigation.qc:1220
const int WINNING_YES
Definition: world.qh:136
void race_ScoreRules()
Definition: sv_race.qc:54
void GameRules_limit_time(int limit)
Definition: sv_rules.qc:54
float MapInfo_count
Definition: mapinfo.qh:144
void GameRules_teams(bool value)
Definition: sv_rules.qc:6
bool navigation_goalrating_timeout(entity this)
Definition: navigation.qc:43
string record_type
Definition: world.qh:49
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
int autocvar_g_race_teams
Definition: sv_race.qc:14
void GetPressedKeys(entity this)
Definition: client.qc:1681
IntrusiveList g_racecheckpoints
Definition: race.qc:69
bool autocvar_g_allow_checkpoints
Definition: race.qh:3
float race_readTime(string map, float pos)
Definition: race.qc:78
void race_SpeedAwardFrame(entity player)
Definition: race.qc:296
void race_PreparePlayer(entity this)
Definition: race.qc:1180
#define NULL
Definition: post.qh:17
const float ST_RACE_LAPS
Definition: race.qh:8
float WinningCondition_QualifyingThenRace(float limit)
Definition: sv_race.qc:101
const int WINNING_NEVER
Definition: world.qh:137
void GameLogEcho(string s)
Definition: gamelog.qc:12
void race_EventLog(string mode, entity actor)
Definition: sv_race.qc:71
#define M_ARGV(x, type)
Definition: events.qh:17
#define IS_DEAD(s)
Definition: utils.qh:26
void race_Initialize()
Definition: sv_race.qc:398
void navigation_goalrating_timeout_set(entity this)
Definition: navigation.qc:19
entity PlayerScore_Sort(.float field, int teams, bool strict, bool nospectators)
Sorts the players and stores their place in the given field, starting with.
Definition: scores.qc:724
vector movement
#define stuffcmd(cl,...)
Definition: progsdefs.qh:23
vector(float skel, float bonenum) _skel_get_boneabs_hidden
float race_checkpoint
Definition: sv_race.qc:17
void havocbot_role_race(entity this)
Definition: sv_race.qc:18
bool independent_players
Definition: client.qh:312
const int FRAGS_PLAYER_OUT_OF_GAME
Definition: constants.qh:5
string race_readName(string map, float pos)
Definition: race.qc:138
float race_fraglimit
Definition: race.qh:20
string MapInfo_Map_bspname
Definition: mapinfo.qh:6
float race_completed
Definition: race.qh:25
float autocvar_g_race_qualifying_timelimit
Definition: sv_race.qc:12
void GameRules_limit_lead(int limit)
Definition: sv_rules.qc:44
const int FRAGS_SPECTATOR
Definition: constants.qh:4
float WinningCondition_Scores(float limit, float leadlimit)
Definition: world.qc:1428
#define LABEL(id)
Definition: compiler.qh:36
MUTATOR_HOOKFUNCTION(rc, ClientKill)
Definition: sv_race.qc:115
void race_RetractPlayer(entity this)
Definition: race.qc:1189
PutClientInServer(this)
float WinningCondition_Race(float fraglimit)
Definition: sv_race.qc:77
bool autocvar_g_campaign
Definition: campaign.qh:6
entity TeamBalance_CheckAllowedTeams(entity for_whom)
Checks whether the player can join teams according to global configuration and mutator settings...
Definition: teamplay.qc:487
void rc_SetLimits()
Definition: sv_race.qc:406
bool autocvar_sv_eventlog
Definition: gamelog.qh:3
float time
Definition: csprogsdefs.qc:16
float race_leadlimit
Definition: race.qh:21
const int SFL_TIME
Display as mm:ss.s, value is stored as 10ths of a second (AND 0 is the worst possible value!) ...
Definition: scores.qh:118
void set_movetype(entity this, int mt)
#define IS_PLAYER(v)
Definition: utils.qh:9
#define BITS(n)
Definition: bits.qh:9
void FixClientCvars(entity e)
Definition: client.qc:947
float race_timelimit
Definition: race.qh:22
float race_teams
Definition: race.qh:5
void race_AbandonRaceCheck(entity p)
Definition: race.qc:1163