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{
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
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
329
330MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
331{
332 // announce remaining frags if not in qualifying mode
334 return true;
335}
336
337MUTATOR_HOOKFUNCTION(rc, GetRecords)
338{
339 int record_page = M_ARGV(0, int);
340 string ret_string = M_ARGV(1, string);
341
342 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
343 {
344 if(MapInfo_Get_ByID(i))
345 {
347
348 if(!r)
349 continue;
350
351 string h = race_readName(MapInfo_Map_bspname, 1);
352 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r, false)), " ", h, "\n");
353 }
354 }
355
356 M_ARGV(1, string) = ret_string;
357}
358
360{
361 entity player = M_ARGV(0, entity);
362
363 stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
364}
365
367{
368 float checkrules_timelimit = M_ARGV(1, float);
369 float checkrules_fraglimit = M_ARGV(2, float);
370
371 if(checkrules_timelimit >= 0)
372 {
374 {
375 M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
376 return true;
377 }
378 else if(g_race_qualifying == 2)
379 {
380 M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
381 return true;
382 }
383 }
384}
385
386MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
387{
388 if(g_race_qualifying == 2)
389 warmup_stage = 0;
390}
391
393{
395 if(g_race_qualifying == 2)
396 warmup_stage = 0;
397 radar_showenemies = true;
398}
399
401{
402 int fraglimit_override, leadlimit_override;
403 float timelimit_override, qualifying_override;
404
406 {
407 GameRules_teams(true);
409 }
410 else
411 race_teams = 0;
412
414 fraglimit_override = autocvar_g_race_laps_limit;
415 leadlimit_override = 0; // currently not supported by race
416 timelimit_override = autocvar_timelimit_override;
417
418 float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
419
421
423 {
426 }
427 else if(want_qualifying)
428 {
431 race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
432 race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
433 race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
434 qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
435 fraglimit_override = 0;
436 leadlimit_override = 0;
437 timelimit_override = qualifying_override;
438 }
439 else
441 GameRules_limit_score(fraglimit_override);
442 GameRules_limit_lead(leadlimit_override);
443 GameRules_limit_time(timelimit_override);
444 GameRules_limit_time_qualifying(qualifying_override);
445}
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
const int CBC_ORDER_EXCLUSIVE
Definition base.qh:12
#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:50
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:238
#define ClientKill
Definition _all.inc:250
#define PutClientInServer
Definition _all.inc:246
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
Definition mathlib.qh:115
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:92
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:8
float race_timelimit
Definition race.qh:23
float race_place
Definition race.qh:24
float race_fraglimit
Definition race.qh:21
float race_leadlimit
Definition race.qh:22
float race_teams
Definition race.qh:5
float race_completed
Definition race.qh:26
int g_race_qualifying
Definition race.qh:13
ClientState CS(Client this)
Definition state.qh:47
entity frag_target
Definition sv_ctf.qc:2314
void race_Initialize()
Definition sv_race.qc:392
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:400
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:23
void GameRules_teams(bool value)
Definition sv_rules.qc:3
void GameRules_limit_time_qualifying(int limit)
Definition sv_rules.qc:53
void GameRules_limit_lead(int limit)
Definition sv_rules.qc:33
void GameRules_limit_time(int limit)
Definition sv_rules.qc:43
#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
entity TeamBalance_CheckAllowedTeams(entity for_whom)
Checks whether the player can join teams according to global configuration and mutator settings.
Definition teamplay.qc:459
#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:1726
float WinningCondition_Scores(float limit, float leadlimit)
Definition world.qc:1561
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