X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fmain.qc;h=b5832d8939ae91b28b44f671007e9a619d5ec44f;hb=94d74449f36b5750f1d1450b02c3817f179211b1;hp=fb7df4ade9589d6bbbe80f77007c18947e8bcea8;hpb=f6efc137bef946957459ddfcd447114bc2c9b7a5;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/main.qc b/qcsrc/server/main.qc index fb7df4ade..b5832d893 100644 --- a/qcsrc/server/main.qc +++ b/qcsrc/server/main.qc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,41 @@ #include #include +void dropclient_do(entity this) +{ + if (this.owner) + dropclient(this.owner); + delete(this); +} + +/** + * Schedules dropclient for a player and returns true; + * if dropclient is already scheduled (for that player) it does nothing and returns false. + * + * NOTE: this function exists only to allow sending a message to the kicked player with + * Send_Notification, which doesn't work if called together with dropclient + */ +bool dropclient_schedule(entity this) +{ + bool scheduled = false; + FOREACH_ENTITY_CLASS("dropclient_handler", true, + { + if(it.owner == this) + { + scheduled = true; + break; // can't use return here, compiler shows a warning + } + }); + if (scheduled) + return false; + + entity e = new_pure(dropclient_handler); + setthink(e, dropclient_do); + e.owner = this; + e.nextthink = time + 0.1; + return true; +} + void CreatureFrame_hotliquids(entity this) { if (this.contents_damagetime >= time) @@ -108,12 +144,12 @@ void CreatureFrame_FallDamage(entity this) bool have_hook = false; for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) { - .entity weaponentity = weaponentities[slot]; - if(this.(weaponentity).hook && this.(weaponentity).hook.state) - { - have_hook = true; - break; - } + .entity weaponentity = weaponentities[slot]; + if(this.(weaponentity).hook && this.(weaponentity).hook.state) + { + have_hook = true; + break; + } } if(!have_hook) { @@ -152,20 +188,40 @@ void CreatureFrame_All() }); } -void Pause_TryPause(bool ispaused) +// called shortly after map change in dedicated +void Pause_TryPause_Dedicated(entity this) +{ + if (player_count == 0 && !intermission_running && !autocvar__endmatch) + setpause(1); +} + +// called every normal frame in singleplayer/listen +void Pause_TryPause() { - int n = 0; - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { - if (PHYS_INPUT_BUTTON_CHAT(it) != ispaused) return; + int n = 0, p = 0; + FOREACH_CLIENT(IS_REAL_CLIENT(it), { + if (PHYS_INPUT_BUTTON_CHAT(it)) ++p; ++n; }); if (!n) return; - setpause(ispaused); + if (n == p) + setpause(1); + else + setpause(0); } +// called every paused frame by DP void SV_PausedTic(float elapsedtime) { - if (!server_is_dedicated) Pause_TryPause(false); + if (autocvar__endmatch) // `endmatch` while paused + setpause(0); // proceed to intermission + else if (!server_is_dedicated) + { + if (autocvar_sv_autopause) + Pause_TryPause(); + else + setpause(0); + } } void dedicated_print(string input) @@ -175,33 +231,33 @@ void dedicated_print(string input) void make_safe_for_remove(entity e) { - if (e.initialize_entity) - { - entity ent, prev = NULL; - for (ent = initialize_entity_first; ent; ) - { - if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e))) - { - //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n"); - // skip it in linked list - if (prev) - { - prev.initialize_entity_next = ent.initialize_entity_next; - ent = prev.initialize_entity_next; - } - else - { - initialize_entity_first = ent.initialize_entity_next; - ent = initialize_entity_first; - } - } - else - { - prev = ent; - ent = ent.initialize_entity_next; - } - } - } + if (e.initialize_entity) + { + entity ent, prev = NULL; + for (ent = initialize_entity_first; ent; ) + { + if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e))) + { + //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n"); + // skip it in linked list + if (prev) + { + prev.initialize_entity_next = ent.initialize_entity_next; + ent = prev.initialize_entity_next; + } + else + { + initialize_entity_first = ent.initialize_entity_next; + ent = initialize_entity_first; + } + } + else + { + prev = ent; + ent = ent.initialize_entity_next; + } + } + } } void remove_except_protected(entity e) @@ -213,15 +269,15 @@ void remove_except_protected(entity e) void remove_unsafely(entity e) { - if(e.classname == "spike") - error("Removing spikes is forbidden (crylink bug), please report"); - builtin_remove(e); + if(e.classname == "spike") + error("Removing spikes is forbidden (crylink bug), please report"); + builtin_remove(e); } void remove_safely(entity e) { - make_safe_for_remove(e); - builtin_remove(e); + make_safe_for_remove(e); + builtin_remove(e); } /* @@ -234,17 +290,15 @@ Called before each frame by the server bool game_delay_last; -bool autocvar_sv_autopause = false; void systems_update(); void sys_phys_update(entity this, float dt); void StartFrame() { - // TODO: if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction) - IL_EACH(g_players, IS_FAKE_CLIENT(it), sys_phys_update(it, frametime)); - IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPreThink(it)); + // TODO: if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction) + IL_EACH(g_players, IS_FAKE_CLIENT(it), sys_phys_update(it, frametime)); + IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPreThink(it)); execute_next_frame(); - if (autocvar_sv_autopause && !server_is_dedicated) Pause_TryPause(true); delete_fn = remove_unsafely; // not during spawning! serverprevtime = servertime; @@ -264,10 +318,10 @@ void StartFrame() ++c_seen; }); LOG_INFO( - "CEFC time: ", ftos(t * 1000), "ms; ", - "CEFC calls per second: ", ftos(c_seeing * (c_seen - 1) / t), "; ", - "CEFC 100% load at: ", ftos(solve_quadratic(t, -t, -1) * '0 1 0') - ); + "CEFC time: ", ftos(t * 1000), "ms; ", + "CEFC calls per second: ", ftos(c_seeing * (c_seen - 1) / t), "; ", + "CEFC 100% load at: ", ftos(solve_quadratic(t, -t, -1) * '0 1 0') + ); client_cefc_accumulatortime = time; client_cefc_accumulator = 0; } @@ -297,8 +351,12 @@ void StartFrame() CreatureFrame_All(); CheckRules_World(); - if (warmup_stage && !game_stopped && warmup_limit > 0 && time >= warmup_limit) { - ReadyRestart(); + // after CheckRules_World() as it may set intermission_running, and after RedirectionThink() in case listen server is closing + if (autocvar_sv_autopause && !server_is_dedicated && !intermission_running) + Pause_TryPause(); + + if (warmup_stage && !game_stopped && warmup_limit > 0 && time - game_starttime >= warmup_limit) { + ReadyRestart(true); return; } @@ -307,8 +365,8 @@ void StartFrame() MUTATOR_CALLHOOK(SV_StartFrame); GlobalStats_updateglobal(); - FOREACH_CLIENT(true, GlobalStats_update(it)); - IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPostThink(it)); + FOREACH_CLIENT(true, GlobalStats_update(it)); + IL_EACH(g_players, IS_FAKE_CLIENT(it), PlayerPostThink(it)); } .vector originjitter; @@ -340,22 +398,22 @@ void SV_OnEntityPreSpawnFunction(entity this) if (this.monster_attack) { IL_PUSH(g_monster_targets, this); - } + } // support special -1 and -2 angle from radiant if (this.angles == '0 -1 0') { this.angles = '-90 0 0'; } else if (this.angles == '0 -2 0') { this.angles = '+90 0 0'; - } + } - #define X(out, in) MACRO_BEGIN \ - if (in != 0) { out = out + (random() * 2 - 1) * in; } \ - MACRO_END - X(this.origin.x, this.originjitter.x); X(this.origin.y, this.originjitter.y); X(this.origin.z, this.originjitter.z); - X(this.angles.x, this.anglesjitter.x); X(this.angles.y, this.anglesjitter.y); X(this.angles.z, this.anglesjitter.z); - X(this.angles.y, this.anglejitter); - #undef X + #define X(out, in) MACRO_BEGIN \ + if (in != 0) { out = out + (random() * 2 - 1) * in; } \ + MACRO_END + X(this.origin.x, this.originjitter.x); X(this.origin.y, this.originjitter.y); X(this.origin.z, this.originjitter.z); + X(this.angles.x, this.anglesjitter.x); X(this.angles.y, this.anglesjitter.y); X(this.angles.z, this.anglesjitter.z); + X(this.angles.y, this.anglejitter); + #undef X if (MUTATOR_CALLHOOK(OnEntityPreSpawn, this)) { delete(this); @@ -381,7 +439,7 @@ string GetField_fullspawndata(entity e, string f, ...) if (!e.fullspawndata) { - LOG_WARNF("^1EDICT %s (classname %s) has no fullspawndata, engine lacks support?", ftos(num_for_edict(e)), e.classname); + //LOG_WARNF("^1EDICT %s (classname %s) has no fullspawndata, engine lacks support?", ftos(num_for_edict(e)), e.classname); return v; } @@ -419,6 +477,32 @@ string GetField_fullspawndata(entity e, string f, ...) return v; } +/* +============= +FindFileInMapPack + +Returns the first matching VFS file path that exists in the current map's pack. +Returns string_null if no files match or the map isn't packaged. +============= +*/ +string FindFileInMapPack(string pattern) +{ + if (!checkextension("DP_QC_FS_SEARCH_PACKFILE")) + return string_null; + + string base_pack = whichpack(strcat("maps/", mapname, ".bsp")); + if (base_pack == "" || !base_pack) // this map isn't packaged or there was an error + return string_null; + + int glob = search_packfile_begin(pattern, true, true, base_pack); + if (glob < 0) + return string_null; + + string file = search_getfilename(glob, 0); + search_end(glob); + return file; +} + void WarpZone_PostInitialize_Callback() { // create waypoint links for warpzones