Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
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>
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 gametype
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{
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
71void 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
77float 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 = WinningCondition_Scores(limit, 0);
104
105 // NEVER initiate overtime
107 {
108 return WINNING_YES;
109 }
110
111 return wc;
112}
113
115{
117 M_ARGV(1, float) = 0; // killtime
118}
119
120MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
121{
122 entity player = M_ARGV(0, entity);
123
125 race_PreparePlayer(player); // nice try
126}
127
128MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
129{
130 entity player = M_ARGV(0, entity);
131 float dt = M_ARGV(1, float);
132
133 player.race_movetime_frac += dt;
134 float f = floor(player.race_movetime_frac);
135 player.race_movetime_frac -= f;
136 player.race_movetime_count += f;
137 player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
138
139 if(IS_PLAYER(player))
140 {
141 if (player.race_penalty)
142 if (time > player.race_penalty)
143 player.race_penalty = 0;
144 if(player.race_penalty)
145 {
146 player.velocity = '0 0 0';
148 player.disableclientprediction = 2;
149 }
150 }
151
152 // force kbd movement for fairness
153 float wishspeed;
154 vector wishvel;
155
156 // if record times matter
157 // ensure nothing EVIL is being done (i.e. div0_evade)
158 // this hinders joystick users though
159 // but it still gives SOME analog control
160 wishvel.x = fabs(CS(player).movement.x);
161 wishvel.y = fabs(CS(player).movement.y);
162 if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
163 {
164 wishvel.z = 0;
165 wishspeed = vlen(wishvel);
166 if(wishvel.x >= 2 * wishvel.y)
167 {
168 // pure X motion
169 if(CS(player).movement.x > 0)
170 CS(player).movement_x = wishspeed;
171 else
172 CS(player).movement_x = -wishspeed;
173 CS(player).movement_y = 0;
174 }
175 else if(wishvel.y >= 2 * wishvel.x)
176 {
177 // pure Y motion
178 CS(player).movement_x = 0;
179 if(CS(player).movement.y > 0)
180 CS(player).movement_y = wishspeed;
181 else
182 CS(player).movement_y = -wishspeed;
183 }
184 else
185 {
186 // diagonal
187 if(CS(player).movement.x > 0)
188 CS(player).movement_x = M_SQRT1_2 * wishspeed;
189 else
190 CS(player).movement_x = -M_SQRT1_2 * wishspeed;
191 if(CS(player).movement.y > 0)
192 CS(player).movement_y = M_SQRT1_2 * wishspeed;
193 else
194 CS(player).movement_y = -M_SQRT1_2 * wishspeed;
195 }
196 }
197}
198
199MUTATOR_HOOKFUNCTION(rc, reset_map_global)
200{
201 float s;
202
204
206 PlayerScore_Sort(race_place, 0, true, false);
207
208 FOREACH_CLIENT(true, {
209 if(it.race_place)
210 {
211 s = GameRules_scoring_add(it, RACE_FASTEST, 0);
212 if(!s)
213 it.race_place = 0;
214 }
215 race_EventLog(ftos(it.race_place), it);
216 });
217
218 if(g_race_qualifying == 2)
219 {
222 cvar_set("fraglimit", ftos(race_fraglimit));
223 cvar_set("leadlimit", ftos(race_leadlimit));
224 cvar_set("timelimit", ftos(race_timelimit));
226 }
227}
228
230{
231 entity player = M_ARGV(0, entity);
232
233 race_PreparePlayer(player);
234 player.race_checkpoint = -1;
235
236 race_SendAll(player, false);
237}
238
239MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
240{
241 entity player = M_ARGV(0, entity);
242
244 {
245 if(GameRules_scoring_add(player, RACE_FASTEST, 0))
246 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
247 else
248 player.frags = FRAGS_SPECTATOR;
249 }
250
251 race_PreparePlayer(player);
252 player.race_checkpoint = -1;
253}
254
255MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
256{
257 entity player = M_ARGV(0, entity);
258 entity spawn_spot = M_ARGV(1, entity);
259
260 if(spawn_spot.target == "")
261 // Emergency: this wasn't a real spawnpoint. Can this ever happen?
262 race_PreparePlayer(player);
263
264 // if we need to respawn, do it right
265 player.race_respawn_checkpoint = player.race_checkpoint;
266 player.race_respawn_spotref = spawn_spot;
267
268 player.race_place = 0;
269}
270
272{
273 entity player = M_ARGV(0, entity);
274
275 if(IS_PLAYER(player))
276 if(!game_stopped)
277 {
278 if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
279 race_PreparePlayer(player);
280 else // respawn
281 race_RetractPlayer(player);
282
283 race_AbandonRaceCheck(player);
284 }
285}
286
287MUTATOR_HOOKFUNCTION(rc, PlayerDamaged)
288{
289 int frag_deathtype = M_ARGV(5, int);
290 if (frag_deathtype == DEATH_KILL.m_id)
291 return true; // forbid logging damage
292}
293
294MUTATOR_HOOKFUNCTION(rc, PlayerDies)
295{
297
298 frag_target.respawn_flags |= RESPAWN_FORCE;
300}
301
302MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
303{
304 entity bot = M_ARGV(0, entity);
305
306 bot.havocbot_role = havocbot_role_race;
307 return true;
308}
309
311{
312 entity player = M_ARGV(0, entity);
313
315 race_SpeedAwardFrame(player);
316}
317
318MUTATOR_HOOKFUNCTION(rc, PreferPlayerScore_Clear)
319{
321 return true; // in qualifying, you don't lose score by observing
322}
323
324MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
325{
326 // announce remaining frags if not in qualifying mode
328 return true;
329}
330
331MUTATOR_HOOKFUNCTION(rc, GetRecords)
332{
333 int record_page = M_ARGV(0, int);
334 string ret_string = M_ARGV(1, string);
335
336 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
337 {
338 if(MapInfo_Get_ByID(i))
339 {
341
342 if(!r)
343 continue;
344
345 string h = race_readName(MapInfo_Map_bspname, 1);
346 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r, false)), " ", h, "\n");
347 }
348 }
349
350 M_ARGV(1, string) = ret_string;
351}
352
354{
355 entity player = M_ARGV(0, entity);
356
357 stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
358}
359
361{
362 float checkrules_timelimit = M_ARGV(1, float);
363 float checkrules_fraglimit = M_ARGV(2, float);
364
365 if(checkrules_timelimit >= 0)
366 {
368 {
369 M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
370 return true;
371 }
372 else if(g_race_qualifying == 2)
373 {
374 M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
375 return true;
376 }
377 }
378}
379
380MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
381{
382 if(g_race_qualifying == 2)
383 warmup_stage = 0;
384}
385
387{
389 if(g_race_qualifying == 2)
390 warmup_stage = 0;
391 radar_showenemies = true;
392}
393
395{
396 int fraglimit_override, leadlimit_override;
397 float timelimit_override, qualifying_override;
398
400 {
401 GameRules_teams(true);
403 }
404 else
406
408 fraglimit_override = autocvar_g_race_laps_limit;
409 leadlimit_override = 0; // currently not supported by race
410 timelimit_override = autocvar_timelimit_override;
411
412 float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
413
415
417 {
420 }
421 else if(want_qualifying)
422 {
425 race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
426 race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
427 race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
428 qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
429 fraglimit_override = 0;
430 leadlimit_override = 0;
431 timelimit_override = qualifying_override;
432 }
433 else
435 GameRules_limit_score(fraglimit_override);
436 GameRules_limit_lead(leadlimit_override);
437 GameRules_limit_time(timelimit_override);
438 GameRules_limit_time_qualifying(qualifying_override);
439}
void navigation_goalrating_start(entity this)
void navigation_goalrating_timeout_set(entity this)
Definition navigation.qc:20
bool navigation_goalrating_timeout(entity this)
Definition navigation.qc:44
void navigation_routerating(entity this, entity e, float f, float rangebias)
void navigation_goalrating_end(entity this)
#define MUTATOR_HOOKFUNCTION(...)
Definition base.qh:335
#define BITS(n)
Definition bits.qh:9
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
bool warmup_stage
Definition main.qh:120
#define M_ARGV(x, type)
Definition events.qh:17
vector movement
Definition player.qh:228
#define IS_DEAD(s)
Definition player.qh:244
#define IS_PLAYER(s)
Definition player.qh:242
const int SFL_LOWER_IS_BETTER
Lower scores are better (e.g.
Definition scores.qh:102
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:122
const int SFL_SORT_PRIO_SECONDARY
Scoring priority (NOTE: PRIMARY is used for fraglimit) NOTE: SFL_SORT_PRIO_SECONDARY value must be lo...
Definition scores.qh:133
const int SFL_SORT_PRIO_PRIMARY
Definition scores.qh:134
#define autocvar_timelimit_override
Definition stats.qh:93
#define autocvar_timelimit
Definition stats.qh:92
#define autocvar_fraglimit
Definition stats.qh:90
float game_stopped
Definition stats.qh:81
int autocvar_leadlimit
Definition stats.qh:84
#define TIME_ENCODED_TOSTRING(n, compact)
Definition util.qh:96
#define RACE_RECORD
Definition util.qh:97
#define LABEL(id)
Definition compiler.qh:34
const int FRAGS_PLAYER_OUT_OF_GAME
Definition constants.qh:5
const int FRAGS_SPECTATOR
Definition constants.qh:4
float time
vector origin
bool radar_showenemies
Definition ent_cs.qh:46
void GameLogEcho(string s)
Definition gamelog.qc:15
bool autocvar_sv_eventlog
Definition gamelog.qh:3
#define IL_EACH(this, cond, body)
#define ClientConnect
Definition _all.inc:242
#define ClientKill
Definition _all.inc:254
#define PutClientInServer
Definition _all.inc:250
bool MapInfo_Get_ByID(int i)
Definition mapinfo.qc:274
float MapInfo_count
Definition mapinfo.qh:166
string MapInfo_Map_bspname
Definition mapinfo.qh:6
const float M_SQRT1_2
1/sqrt(2)
Definition mathlib.qh:119
bool autocvar_g_campaign
Definition menu.qc:752
void cvar_set(string name, string value)
float bound(float min, float value, float max)
float vlen(vector v)
string ftos(float f)
float fabs(float f)
float floor(float f)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_NONE
Definition movetypes.qh:129
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define NULL
Definition post.qh:14
#define stuffcmd(cl,...)
Definition progsdefs.qh:23
float race_checkpoint
Definition racetimer.qh:8
void Score_NicePrint(entity to)
Prints the scores to the console of a player.
Definition scores.qc:914
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:745
vector
Definition self.qh:96
void GetPressedKeys(entity this)
Definition client.qc:1762
void FixClientCvars(entity e)
Definition client.qc:999
int killcount
Definition client.qh:315
bool independent_players
Definition client.qh:310
const int RESPAWN_FORCE
Definition client.qh:326
float race_readTime(string map, float pos)
Definition race.qc:69
void race_SendAll(entity player, bool only_rankings)
Definition race.qc:332
void race_PreparePlayer(entity this)
Definition race.qc:1220
void race_ClearRecords()
Definition race.qc:1259
void race_SpeedAwardFrame(entity player)
Definition race.qc:304
string race_readName(string map, float pos)
Definition race.qc:128
float race_NextCheckpoint(float f)
Definition race.qc:174
void race_checkAndWriteName(entity player)
Definition race.qc:133
IntrusiveList g_racecheckpoints
Definition race.qc:67
void race_RetractPlayer(entity this)
Definition race.qc:1229
void race_AbandonRaceCheck(entity p)
Definition race.qc:1203
bool autocvar_g_allow_checkpoints
Definition race.qh:3
const float ST_RACE_LAPS
Definition race.qh:6
float race_timelimit
Definition race.qh:21
float race_place
Definition race.qh:22
float race_fraglimit
Definition race.qh:19
float race_leadlimit
Definition race.qh:20
float race_completed
Definition race.qh:24
int g_race_qualifying
Definition race.qh:11
ClientState CS(Client this)
Definition state.qh:47
entity frag_target
Definition sv_ctf.qc:2314
void race_Initialize()
Definition sv_race.qc:386
float autocvar_g_race_qualifying_timelimit
Definition sv_race.qc:12
float WinningCondition_QualifyingThenRace(float limit)
Definition sv_race.qc:101
#define autocvar_g_race_laps_limit
Definition sv_race.qc:11
void havocbot_role_race(entity this)
Definition sv_race.qc:18
void race_EventLog(string mode, entity actor)
Definition sv_race.qc:71
void rc_SetLimits()
Definition sv_race.qc:394
float autocvar_g_race_qualifying_timelimit_override
Definition sv_race.qc:13
int autocvar_g_race_teams
Definition sv_race.qc:14
float WinningCondition_Race(float fraglimit)
Definition sv_race.qc:77
void race_ScoreRules()
Definition sv_race.qc:54
void GameRules_limit_score(int limit)
Definition sv_rules.qc:22
void GameRules_teams(bool value)
Definition sv_rules.qc:3
void GameRules_limit_time_qualifying(int limit)
Definition sv_rules.qc:52
void GameRules_limit_lead(int limit)
Definition sv_rules.qc:32
void GameRules_limit_time(int limit)
Definition sv_rules.qc:42
#define GameRules_score_enabled(value)
Definition sv_rules.qh:41
#define GameRules_scoring_add(client, fld, value)
Definition sv_rules.qh:85
#define GameRules_scoring(teams, spprio, stprio, fields)
Definition sv_rules.qh:58
int teamplay_bitmask
The set of currently available teams (AVAILABLE_TEAMS is the number of them).
Definition teamplay.qh:18
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:52
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt().
Definition vector.qh:8
void CheckRules_World()
Definition world.qc:1725
float WinningCondition_Scores(float limit, float leadlimit)
Definition world.qc:1560
const int WINNING_NEVER
Definition world.qh:134
const int WINNING_STARTSUDDENDEATHOVERTIME
Definition world.qh:135
string record_type
Definition world.qh:55
const int WINNING_YES
Definition world.qh:133