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;
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
121MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
122{
123 entity player = M_ARGV(0, entity);
124
126 race_PreparePlayer(player); // nice try
127}
128
129MUTATOR_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';
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
200MUTATOR_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 {
223 cvar_set("fraglimit", ftos(race_fraglimit));
224 cvar_set("leadlimit", ftos(race_leadlimit));
225 cvar_set("timelimit", ftos(race_timelimit));
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
240MUTATOR_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
256MUTATOR_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
288MUTATOR_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
295MUTATOR_HOOKFUNCTION(rc, PlayerDies)
296{
298
299 frag_target.respawn_flags |= RESPAWN_FORCE;
301}
302
303MUTATOR_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
316 race_SpeedAwardFrame(player);
317}
318
319MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
320{
322 return true; // in qualifying, you don't lose score by observing
323}
324
330
331MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
332{
333 // announce remaining frags if not in qualifying mode
335 return true;
336}
337
338MUTATOR_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 {
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, false)), " ", h, "\n");
354 }
355 }
356
357 M_ARGV(1, string) = ret_string;
358}
359
361{
362 entity player = M_ARGV(0, entity);
363
364 stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
365}
366
368{
369 float checkrules_timelimit = M_ARGV(1, float);
370 float checkrules_fraglimit = M_ARGV(2, float);
371
372 if(checkrules_timelimit >= 0)
373 {
375 {
376 M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
377 return true;
378 }
379 else if(g_race_qualifying == 2)
380 {
381 M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
382 return true;
383 }
384 }
385}
386
387MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
388{
389 if(g_race_qualifying == 2)
390 warmup_stage = 0;
391}
392
394{
396 if(g_race_qualifying == 2)
397 warmup_stage = 0;
398 radar_showenemies = true;
399}
400
402{
403 int fraglimit_override, leadlimit_override;
404 float timelimit_override, qualifying_override;
405
407 {
408 GameRules_teams(true);
410 }
411 else
412 race_teams = 0;
413
415 fraglimit_override = autocvar_g_race_laps_limit;
416 leadlimit_override = 0; // currently not supported by race
417 timelimit_override = autocvar_timelimit_override;
418
419 float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
420
422
424 {
427 }
428 else if(want_qualifying)
429 {
432 race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
433 race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
434 race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
435 qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
436 fraglimit_override = 0;
437 leadlimit_override = 0;
438 timelimit_override = qualifying_override;
439 }
440 else
442 GameRules_limit_score(fraglimit_override);
443 GameRules_limit_lead(leadlimit_override);
444 GameRules_limit_time(timelimit_override);
445 GameRules_limit_time_qualifying(qualifying_override);
446}
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:229
#define IS_DEAD(s)
Definition player.qh:245
#define IS_PLAYER(s)
Definition player.qh:243
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:275
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:747
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:917
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:748
vector
Definition self.qh:92
void GetPressedKeys(entity this)
Definition client.qc:1761
void FixClientCvars(entity e)
Definition client.qc:997
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:333
void race_PreparePlayer(entity this)
Definition race.qc:1246
void race_ClearRecords()
Definition race.qc:1285
void race_SpeedAwardFrame(entity player)
Definition race.qc:305
string race_readName(string map, float pos)
Definition race.qc:129
float race_NextCheckpoint(float f)
Definition race.qc:175
void race_checkAndWriteName(entity player)
Definition race.qc:134
IntrusiveList g_racecheckpoints
Definition race.qc:67
void race_RetractPlayer(entity this)
Definition race.qc:1255
void race_AbandonRaceCheck(entity p)
Definition race.qc:1229
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:2321
void race_Initialize()
Definition sv_race.qc:393
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:401
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:50
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
void CheckRules_World()
Definition world.qc:1705
float WinningCondition_Scores(float limit, float leadlimit)
Definition world.qc:1540
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