Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
main.qc
Go to the documentation of this file.
1#include "main.qh"
2
4#include <common/constants.qh>
6#include <common/debug.qh>
7#include <common/mapinfo.qh>
10#include <common/util.qh>
16#include <server/anticheat.qh>
17#include <server/bot/api.qh>
20#include <server/damage.qh>
21#include <server/gamelog.qh>
22#include <server/hook.qh>
23#include <server/ipban.qh>
25#include <server/spawnpoints.qh>
28#include <server/world.qh>
29
31{
32 if (this.owner)
33 dropclient(this.owner);
34 delete(this);
35}
36
45{
46 bool scheduled = false;
47 FOREACH_ENTITY_CLASS("dropclient_handler", true,
48 {
49 if(it.owner == this)
50 {
51 scheduled = true;
52 break; // can't use return here, compiler shows a warning
53 }
54 });
55 if (scheduled)
56 return false;
57
58 entity e = new_pure(dropclient_handler);
60 e.owner = this;
61 e.nextthink = time + 0.1;
62
63 // ignore this player for team balancing and queuing
64 this.team = -1;
65 this.wants_join = 0;
67 return true;
68}
69
71{
72 if (this.contents_damagetime >= time)
73 {
74 return;
75 }
76
78
79 if (this.flags & FL_PROJECTILE)
80 {
81 if (this.watertype == CONTENT_LAVA)
83 else if (this.watertype == CONTENT_SLIME)
84 Damage (this, NULL, NULL, autocvar_g_balance_contents_projectiledamage * autocvar_g_balance_contents_damagerate * this.waterlevel, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0');
85 }
86 else
87 {
88 // TODO: do we even need this hack? frozen players still die in lava!
89 if (STAT(FROZEN, this))
90 {
91 if (this.watertype == CONTENT_LAVA)
92 Damage(this, NULL, NULL, 10000, DEATH_LAVA.m_id, DMG_NOWEP, this.origin, '0 0 0');
93 else if (this.watertype == CONTENT_SLIME)
94 Damage(this, NULL, NULL, 10000, DEATH_SLIME.m_id, DMG_NOWEP, this.origin, '0 0 0');
95 }
96 else if (this.watertype == CONTENT_LAVA)
97 {
98 if (this.watersound_finished < time)
99 {
100 this.watersound_finished = time + 0.5;
101 sound (this, CH_PLAYER_SINGLE, SND_LAVA, VOL_BASE, ATTEN_NORM);
102 }
106 }
107 else if (this.watertype == CONTENT_SLIME)
108 {
109 if (this.watersound_finished < time)
110 {
111 this.watersound_finished = time + 0.5;
112 sound (this, CH_PLAYER_SINGLE, SND_SLIME, VOL_BASE, ATTEN_NORM);
113 }
115 }
116 }
117}
118
120{
121 if (this.watertype <= CONTENT_WATER && this.waterlevel > 0) // workaround a retarded bug made by id software :P (yes, it's that old of a bug)
122 {
123 if (!(this.flags & FL_INWATER))
124 {
125 this.flags |= FL_INWATER;
126 this.contents_damagetime = 0;
127 }
128
130 }
131 else
132 {
133 if (this.flags & FL_INWATER)
134 {
135 // play leave water sound
136 this.flags &= ~FL_INWATER;
137 this.contents_damagetime = 0;
138 }
139 }
140}
141
143{
144 if(IS_VEHICLE(this) || (this.flags & FL_PROJECTILE))
145 return; // vehicles and projectiles don't receive fall damage
146 if(!(this.velocity || this.oldvelocity))
147 return; // if the entity hasn't moved and isn't moving, then don't do anything
148
149 // check for falling damage
150 bool have_hook = false;
151 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
152 {
153 .entity weaponentity = weaponentities[slot];
154 if(this.(weaponentity).hook && this.(weaponentity).hook.state)
155 {
156 have_hook = true;
157 break;
158 }
159 }
160 if(!have_hook)
161 {
162 float dm; // dm is the velocity DECREASE. Velocity INCREASE should never cause a sound or any damage.
164 dm = fabs(this.oldvelocity.z) - vlen(this.velocity);
165 else
166 dm = vlen(this.oldvelocity) - vlen(this.velocity);
167 if (IS_DEAD(this))
169 else
171 if (dm > 0)
172 {
173 tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
175 Damage (this, NULL, NULL, dm, DEATH_FALL.m_id, DMG_NOWEP, this.origin, '0 0 0');
176 }
177 }
178
180 Damage (this, NULL, NULL, 100000, DEATH_SHOOTING_STAR.m_id, DMG_NOWEP, this.origin, '0 0 0');
181}
182
184{
186 return;
187
188 IL_EACH(g_damagedbycontents, it.damagedbycontents,
189 {
190 if (it.move_movetype == MOVETYPE_NOCLIP) continue;
191 CreatureFrame_Liquids(it);
192 CreatureFrame_FallDamage(it);
193 it.oldvelocity = it.velocity;
194 });
195}
196
197// called shortly after map change in dedicated
199{
201 setpause(1);
202}
203
204// called every normal frame in singleplayer/listen
206{
207 int n = 0, p = 0;
209 if (PHYS_INPUT_BUTTON_CHAT(it)) ++p;
210 ++n;
211 });
212 if (!n) return;
213 if (n == p)
214 setpause(1);
215 else
216 setpause(0);
217}
218
219// called every paused frame by DP
220void SV_PausedTic(float elapsedtime)
221{
222 if (autocvar__endmatch) // `endmatch` while paused
223 setpause(0); // proceed to intermission
224 else if (!autocvar_sv_dedicated)
225 {
228 else
229 setpause(0);
230 }
231}
232
233void dedicated_print(string input)
234{
235 if (autocvar_sv_dedicated) print(input);
236}
237
239{
240 if (e.initialize_entity)
241 {
242 entity ent, prev = NULL;
243 for (ent = initialize_entity_first; ent; )
244 {
245 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
246 {
247 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
248 // skip it in linked list
249 if (prev)
250 {
251 prev.initialize_entity_next = ent.initialize_entity_next;
252 ent = prev.initialize_entity_next;
253 }
254 else
255 {
256 initialize_entity_first = ent.initialize_entity_next;
258 }
259 }
260 else
261 {
262 prev = ent;
263 ent = ent.initialize_entity_next;
264 }
265 }
266 }
267}
268
270{
271 if(e.remove_except_protected_forbidden)
272 error("not allowed to remove this at this point");
273 builtin_remove(e);
274}
275
277{
278 if(e.classname == "spike")
279 error("Removing spikes is forbidden (crylink bug), please report");
280 builtin_remove(e);
281}
282
284{
286 builtin_remove(e);
287}
288
289/*
290=============
291StartFrame
292
293Called before each frame by the server
294=============
295*/
296
298
299void systems_update();
300void sys_phys_update(entity this, float dt);
302{
304 {
305 // DP calls these for real clients only
306 sys_phys_update(it, frametime); // called by SV_PlayerPhysics for players
307 PlayerPreThink(it);
308 });
309
311
312 delete_fn = remove_unsafely; // not during spawning!
316
317#ifdef PROFILING
318 if(time > client_cefc_accumulatortime + 1)
319 {
320 float t = client_cefc_accumulator / (time - client_cefc_accumulatortime);
321 int c_seeing = 0;
322 int c_seen = 0;
323 FOREACH_CLIENT(true, {
324 if(IS_REAL_CLIENT(it))
325 ++c_seeing;
326 if(IS_PLAYER(it))
327 ++c_seen;
328 });
329 LOG_INFO(
330 "CEFC time: ", ftos(t * 1000), "ms; ",
331 "CEFC calls per second: ", ftos(c_seeing * (c_seen - 1) / t), "; ",
332 "CEFC 100% load at: ", ftos(solve_quadratic(t, -t, -1) * '0 1 0')
333 );
334 client_cefc_accumulatortime = time;
335 client_cefc_accumulator = 0;
336 }
337#endif
338
339 IL_EACH(g_projectiles, it.csqcprojectile_clientanimate, CSQCProjectile_Check(it));
340
341 if (RedirectionThink()) return;
342
345
347
349 if (sys_frametime <= 0) sys_frametime = 1.0 / 60.0; // somewhat safe fallback
350
351 if (timeout_status == TIMEOUT_LEADTIME) // just before the timeout (when timeout_status will be TIMEOUT_ACTIVE)
352 orig_slowmo = autocvar_slowmo; // slowmo will be restored after the timeout
353
354 // detect when the pre-game countdown (if any) has ended and the game has started
355 bool game_delay = (time < game_starttime);
356 if (autocvar_sv_eventlog && game_delay_last && !game_delay)
357 GameLogEcho(":startdelay_ended");
358 game_delay_last = game_delay;
359
362
363 // after CheckRules_World() as it may set intermission_running, and after RedirectionThink() in case listen server is closing
366
368 ReadyRestart(true);
369 return;
370 }
371
374 MUTATOR_CALLHOOK(SV_StartFrame);
375
376 GlobalStats_updateglobal();
377 FOREACH_CLIENT(true,
378 {
379 GlobalStats_update(it);
380 if (IS_FAKE_CLIENT(it))
381 PlayerPostThink(it); // DP calls this for real clients only
382 PlayerFrame(it);
383 });
384}
385
391
393{
394 if (this)
395 if (this.gametypefilter != "")
397 {
398 delete(this);
399 return;
400 }
401 if (this.cvarfilter != "" && !expr_evaluate(this.cvarfilter)) {
402 delete(this);
403 return;
404 }
405
406 if (q3compat && DoesQ3ARemoveThisEntity(this)) {
407 delete(this);
408 return;
409 }
410
411 set_movetype(this, this.movetype);
412
413 if (this.monster_attack) {
415 }
416
417 // support special -1 and -2 angle from radiant
418 if (this.angles == '0 -1 0') {
419 this.angles = '-90 0 0';
420 } else if (this.angles == '0 -2 0') {
421 this.angles = '+90 0 0';
422 }
423
424 #define X(out, in) MACRO_BEGIN \
425 if (in != 0) { out = out + (random() * 2 - 1) * in; } \
426 MACRO_END
427 X(this.origin.x, this.originjitter.x); X(this.origin.y, this.originjitter.y); X(this.origin.z, this.originjitter.z);
428 X(this.angles.x, this.anglesjitter.x); X(this.angles.y, this.anglesjitter.y); X(this.angles.z, this.anglesjitter.z);
429 X(this.angles.y, this.anglejitter);
430 #undef X
431
432 if (MUTATOR_CALLHOOK(OnEntityPreSpawn, this)) {
433 delete(this);
434 return;
435 }
436}
437
451string GetField_fullspawndata(entity e, string fieldname, bool vfspath)
452{
453 string v = string_null;
454
455 if (!e.fullspawndata) // Engine lacks support, warning spam in CheckEngineExtensions()
456 return v;
457
458 if (strstrofs(e.fullspawndata, "//", 0) >= 0)
459 {
460 // tokenize and tokenize_console return early if "//" is reached,
461 // which can leave an odd number of tokens and break key:value pairing.
462 LOG_WARNF("^1EDICT %s fullspawndata contains unsupported //comment^7%s", ftos(num_for_edict(e)), e.fullspawndata);
463 return v;
464 }
465
466 //print(sprintf("%s(EDICT %s, FIELD %s)\n", __FUNC__, ftos(num_for_edict(e)), fieldname));
467 //print(strcat("FULLSPAWNDATA:", e.fullspawndata, "\n"));
468
469 // tokenize treats \ as an escape, but tokenize_console returns the required literal
470 for (int t = tokenize_console(e.fullspawndata) - 3; t > 0; t -= 2)
471 {
472 //print(sprintf("\tTOKEN %s:%s\t%s:%s\n", ftos(t), ftos(t + 1), argv(t), argv(t + 1)));
473 if (argv(t) == fieldname)
474 {
475 v = argv(t + 1);
476 break;
477 }
478 }
479
480 //print(strcat("RESULT: ", v, "\n\n"));
481
482 if (v && vfspath)
483 {
484 v = strreplace("\\", "/", v);
485 if (whichpack(v) == "")
486 return string_null;
487 }
488
489 return v;
490}
491
492/*
493=============
494FindFileInMapPack
495
496Returns the first matching VFS file path that exists in the current map's pack.
497Returns string_null if no files match or the map isn't packaged.
498=============
499*/
500string FindFileInMapPack(string pattern)
501{
502 if (!checkextension("DP_QC_FS_SEARCH_PACKFILE"))
503 return string_null;
504
505 string base_pack = whichpack(strcat("maps/", mapname, ".bsp"));
506 if (base_pack == "" || !base_pack) // this map isn't packaged or there was an error
507 return string_null;
508
509 int glob = search_packfile_begin(pattern, true, true, base_pack);
510 if (glob < 0)
511 return string_null;
512
513 string file = search_getfilename(glob, 0);
514 search_end(glob);
515 return file;
516}
517
519{
520 // create waypoint links for warpzones
521 entity tracetest_ent = spawn();
522 setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST);
523 tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
524 //for(entity e = warpzone_first; e; e = e.warpzone_next)
525 for(entity e = NULL; (e = find(e, classname, "trigger_warpzone")); )
526 waypoint_spawnforteleporter_wz(e, tracetest_ent);
527 delete(tracetest_ent);
528}
529
531void URI_Get_Callback(float id, float status, string data)
532{
533 if(url_URI_Get_Callback(id, status, data))
534 {
535 // handled
536 }
537 else if (id == URI_GET_DISCARD)
538 {
539 // discard
540 }
541 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
542 {
543 // sv_cmd curl
544 Curl_URI_Get_Callback(id, status, data);
545 }
546 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
547 {
548 // online ban list
549 OnlineBanList_URI_Get_Callback(id, status, data);
550 }
551 else if (MUTATOR_CALLHOOK(URI_GetCallback, id, status, data))
552 {
553 // handled by a mutator
554 }
555 else
556 {
557 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".");
558 }
559}
560
561/*
562==================
563main
564
565unused but required by the engine
566==================
567*/
568void main ()
569{
570
571}
void anticheat_startframe()
Definition anticheat.qc:240
void waypoint_spawnforteleporter_wz(entity e, entity tracetest_ent)
void bot_serverframe()
Definition bot.qc:689
int player_count
Definition api.qh:103
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
void URI_Get_Callback(int id, int status, string data)
engine callback
Definition main.qc:1552
entity owner
Definition main.qh:87
bool warmup_stage
Definition main.qh:120
int team
Definition main.qh:188
float serverprevtime
Definition main.qh:207
entity hook
Definition player.qh:239
#define IS_DEAD(s)
Definition player.qh:245
float autocvar_g_maxspeed
Definition player.qh:43
#define PHYS_INPUT_BUTTON_CHAT(s)
Definition player.qh:159
float watertype
Definition player.qh:225
float waterlevel
Definition player.qh:226
#define IS_PLAYER(s)
Definition player.qh:243
float warmup_limit
Definition stats.qh:375
int timeout_status
Definition stats.qh:87
float game_starttime
Definition stats.qh:82
float game_stopped
Definition stats.qh:81
float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
Definition util.qc:1087
void execute_next_frame()
Definition util.qc:1748
const vector PL_MIN_CONST
Definition constants.qh:56
const int FL_PROJECTILE
Definition constants.qh:85
const int FL_INWATER
Definition constants.qh:73
const vector PL_MAX_CONST
Definition constants.qh:55
string classname
float movetype
float flags
const float MOVE_NOMONSTERS
float DPCONTENTS_BOTCLIP
float DPCONTENTS_SOLID
float frametime
string mapname
vector mins
vector velocity
float DPCONTENTS_BODY
float DPCONTENTS_PLAYERCLIP
float time
vector maxs
float trace_dphitq3surfaceflags
vector origin
const float CONTENT_LAVA
float Q3SURFACEFLAG_NODAMAGE
const float CONTENT_SLIME
#define spawn
void CSQCProjectile_Check(entity e)
ERASEABLE bool expr_evaluate(string s)
Evaluate an expression of the form: [+ | -]?
Definition cvar.qh:48
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition damage.qc:503
float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
Definition damage.qc:1002
IntrusiveList g_damagedbycontents
Definition damage.qh:135
#define DMG_NOWEP
Definition damage.qh:104
#define strstrofs
#define tokenize_console
void systems_update()
Definition main.qc:7
ent angles
Definition ent_cs.qc:121
int wants_join
Definition ent_cs.qh:73
void GameLogEcho(string s)
Definition gamelog.qc:15
bool autocvar_sv_eventlog
Definition gamelog.qh:3
void Curl_URI_Get_Callback(int id, float status, string data)
Definition generic.qc:31
prev
Definition all.qh:71
bool intermission_running
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define IL_EACH(this, cond, body)
void OnlineBanList_URI_Get_Callback(float id, float status, string data)
Definition ipban.qc:79
#define FOREACH_ENTITY_CLASS(class, cond, body)
Definition iter.qh:189
#define PlayerPostThink
Definition _all.inc:258
#define SV_OnEntityPreSpawnFunction
Definition _all.inc:266
#define PlayerPreThink
Definition _all.inc:254
float servertime
Definition net.qh:348
void UncustomizeEntitiesRun()
Definition net.qh:168
#define STAT(...)
Definition stats.qh:82
#define LOG_WARNF(...)
Definition log.qh:62
#define LOG_INFO(...)
Definition log.qh:65
Gametype MapInfo_LoadedGametype
Definition mapinfo.qh:220
ERASEABLE vector solve_quadratic(float a, float b, float c)
ax^2 + bx + c = 0
Definition math.qh:304
entity find(entity start,.string field, string match)
string search_getfilename(float handle, float num)
float vlen(vector v)
float min(float f,...)
float checkextension(string ext)
string ftos(float f)
float fabs(float f)
void print(string text,...)
string argv(float n)
void search_end(float handle)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
string string_null
Definition nil.qh:9
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define new_pure(class)
purely logical entities (not linked to the area grid)
Definition oo.qh:67
#define NULL
Definition post.qh:14
#define error
Definition pre.qh:6
bool DoesQ3ARemoveThisEntity(entity this)
Definition quake3.qc:360
q3compat
Definition quake3.qc:59
#define setthink(e, f)
void PlayerFrame(entity this)
Definition client.qc:2822
float orig_slowmo
Definition common.qh:58
float sys_frametime
Definition common.qh:57
const float TIMEOUT_LEADTIME
Definition common.qh:48
void ReadyRestart(bool forceWarmupEnd)
Definition vote.qc:526
void remove_safely(entity e)
Definition main.qc:283
void dedicated_print(string input)
print(), but only print if the server is not local
Definition main.qc:233
float anglejitter
Definition main.qc:388
#define X(out, in)
string gametypefilter
Definition main.qc:389
void Pause_TryPause_Dedicated(entity this)
Definition main.qc:198
bool game_delay_last
Definition main.qc:297
void sys_phys_update(entity this, float dt)
Definition physics.qc:10
void WarpZone_PostInitialize_Callback()
Definition main.qc:518
void remove_unsafely(entity e)
Definition main.qc:276
void make_safe_for_remove(entity e)
Definition main.qc:238
void StartFrame()
Definition main.qc:301
vector originjitter
Definition main.qc:386
bool dropclient_schedule(entity this)
Schedules dropclient for a player and returns true; if dropclient is already scheduled (for that play...
Definition main.qc:44
void CreatureFrame_Liquids(entity this)
Definition main.qc:119
string GetField_fullspawndata(entity e, string fieldname, bool vfspath)
Retrieves the value of a map entity field from fullspawndata.
Definition main.qc:451
void remove_except_protected(entity e)
Definition main.qc:269
void SV_PausedTic(float elapsedtime)
Definition main.qc:220
void CreatureFrame_hotliquids(entity this)
Definition main.qc:70
void dropclient_do(entity this)
Definition main.qc:30
void CreatureFrame_FallDamage(entity this)
Definition main.qc:142
string FindFileInMapPack(string pattern)
Definition main.qc:500
void main()
Definition main.qc:568
void CreatureFrame_All()
Definition main.qc:183
void Pause_TryPause()
Definition main.qc:205
vector anglesjitter
Definition main.qc:387
string cvarfilter
Definition main.qc:390
int autocvar_g_balance_contents_playerdamage_lava_burn
Definition main.qh:7
float autocvar_sys_ticrate
Definition main.qh:17
float autocvar_g_balance_contents_playerdamage_lava_burn_time
Definition main.qh:8
bool autocvar_sv_autopause
Definition main.qh:19
vector oldvelocity
Definition main.qh:42
float watersound_finished
Definition main.qh:44
int autocvar_g_balance_falldamage_maxdamage
Definition main.qh:13
float autocvar_g_balance_falldamage_minspeed
Definition main.qh:14
float autocvar_g_balance_contents_damagerate
Definition main.qh:3
#define autocvar_slowmo
Definition main.qh:16
float serverframetime
Definition main.qh:39
float autocvar_g_balance_falldamage_factor
Definition main.qh:12
int autocvar_g_balance_contents_playerdamage_slime
Definition main.qh:9
float autocvar_g_balance_falldamage_deadminspeed
Definition main.qh:11
int autocvar_g_balance_contents_playerdamage_lava
Definition main.qh:6
int autocvar_g_balance_contents_projectiledamage
Definition main.qh:10
float contents_damagetime
Definition main.qh:49
bool autocvar_g_balance_falldamage_onlyvertical
Definition main.qh:15
int have_team_spawns
IntrusiveList g_projectiles
Definition common.qh:58
void WarpZone_StartFrame()
Definition server.qc:772
const int CH_PLAYER_SINGLE
Definition sound.qh:21
const float VOL_BASE
Definition sound.qh:36
const float ATTEN_NORM
Definition sound.qh:30
#define sound(e, c, s, v, a)
Definition sound.qh:52
bool monster_attack
IntrusiveList g_monster_targets
var void delete_fn(entity e)
bool teamplay
Definition teams.qh:59
ERASEABLE float url_URI_Get_Callback(int id, float status, string data)
Definition urllib.qc:28
const int URI_GET_IPBAN
Definition urllib.qh:5
const int URI_GET_IPBAN_END
Definition urllib.qh:6
const int URI_GET_CURL
Definition urllib.qh:7
const int URI_GET_DISCARD
Definition urllib.qh:4
const int URI_GET_CURL_END
Definition urllib.qh:8
const string STR_OBSERVER
Definition utils.qh:7
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:50
#define IS_VEHICLE(v)
Definition utils.qh:22
#define IS_FAKE_CLIENT(v)
Definition utils.qh:16
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
const int MAX_WEAPONSLOTS
Definition weapon.qh:16
entity weaponentities[MAX_WEAPONSLOTS]
Definition weapon.qh:17
void CheckRules_World()
Definition world.qc:1705
void InitializeEntitiesRun()
Definition world.qc:2242
float RedirectionThink()
Definition world.qc:2540
entity initialize_entity_first
Definition world.qh:121
bool autocvar_sv_dedicated
Definition world.qh:41
bool autocvar__endmatch
Definition world.qh:6