X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fworld.qc;h=d8c44d3fb5e8515ec2c4c1b59b8163e8dbd37a6e;hb=cde1a828578994d75c4aebeb7beb4d2350688507;hp=7f651fa7748291ff5e7090192793cf39c206afa9;hpb=613663cc624d93b575bbb2e1402ab673d94d02c7;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/world.qc b/qcsrc/server/world.qc index 7f651fa77..d8c44d3fb 100644 --- a/qcsrc/server/world.qc +++ b/qcsrc/server/world.qc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -165,10 +166,14 @@ void cvar_changes_init() { k = bufstr_get(h, i); -#define BADPREFIX(p) if(substring(k, 0, strlen(p)) == p) continue -#define BADPRESUFFIX(p,s) if(substring(k, 0, strlen(p)) == p && substring(k, -strlen(s), -1) == s) continue +#define BADPREFIX_COND(p) (substring(k, 0, strlen(p)) == p) +#define BADSUFFIX_COND(s) (substring(k, -strlen(s), -1) == s) + +#define BADPREFIX(p) if(BADPREFIX_COND(p)) continue +#define BADPRESUFFIX(p, s) if(BADPREFIX_COND(p) && BADSUFFIX_COND(s)) continue #define BADCVAR(p) if(k == p) continue #define BADVALUE(p, val) if (k == p && v == val) continue +#define BADPRESUFFIXVALUE(p, s, val) if(BADPREFIX_COND(p) && BADSUFFIX_COND(s) && v == val) continue // general excludes and namespaces for server admin used cvars BADPREFIX("help_"); // PN's server has this listed as changed, let's not rat him out for THAT @@ -231,6 +236,9 @@ void cvar_changes_init() BADCVAR("timeformat"); BADCVAR("timestamps"); BADCVAR("g_require_stats"); + BADCVAR("g_chatban_list"); + BADCVAR("g_playban_list"); + BADCVAR("g_voteban_list"); BADPREFIX("developer_"); BADPREFIX("g_ban_"); BADPREFIX("g_banned_list"); @@ -254,8 +262,6 @@ void cvar_changes_init() // these can contain player IDs, so better hide BADPREFIX("g_forced_team_"); - BADCVAR("sv_muteban_list"); - BADCVAR("sv_voteban_list"); BADCVAR("sv_allow_customplayermodels_idlist"); BADCVAR("sv_allow_customplayermodels_speciallist"); @@ -284,6 +290,7 @@ void cvar_changes_init() BADCVAR("g_keyhunt"); BADCVAR("g_keyhunt_teams"); BADCVAR("g_lms"); + BADCVAR("g_mayhem"); BADCVAR("g_nexball"); BADCVAR("g_onslaught"); BADCVAR("g_race"); @@ -298,11 +305,18 @@ void cvar_changes_init() BADCVAR("g_tdm"); BADCVAR("g_tdm_on_dm_maps"); BADCVAR("g_tdm_teams"); + BADCVAR("g_tka"); + BADCVAR("g_tka_on_ka_maps"); + BADCVAR("g_tka_on_tdm_maps"); + BADCVAR("g_tka_teams"); + BADCVAR("g_tmayhem"); + BADCVAR("g_tmayhem_teams"); BADCVAR("g_vip"); BADCVAR("leadlimit"); BADCVAR("nextmap"); BADCVAR("teamplay"); BADCVAR("timelimit"); + BADCVAR("g_mapinfo_q3compat"); BADCVAR("g_mapinfo_settemp_acl"); BADCVAR("g_mapinfo_ignore_warnings"); BADCVAR("g_maplist_ignore_sizes"); @@ -374,10 +388,15 @@ void cvar_changes_init() BADCVAR("g_spawn_alloweffects"); BADCVAR("g_tdm_point_leadlimit"); BADCVAR("g_tdm_point_limit"); + BADCVAR("g_mayhem_point_limit"); + BADCVAR("g_mayhem_point_leadlimit"); + BADCVAR("g_tmayhem_point_limit"); + BADCVAR("g_tmayhem_point_leadlimit"); BADCVAR("leadlimit_and_fraglimit"); BADCVAR("leadlimit_override"); BADCVAR("pausable"); BADCVAR("sv_announcer"); + BADCVAR("sv_autopause"); BADCVAR("sv_checkforpacketsduringsleep"); BADCVAR("sv_damagetext"); BADCVAR("sv_db_saveasdump"); @@ -394,7 +413,6 @@ void cvar_changes_init() BADCVAR("w_prop_interval"); BADPREFIX("chat_"); BADPREFIX("crypto_"); - BADPREFIX("gameversion"); BADPREFIX("g_chat_"); BADPREFIX("g_ctf_captimerecord_"); BADPREFIX("g_hats_"); @@ -438,6 +456,7 @@ void cvar_changes_init() BADCVAR("g_ban_sync_uri"); BADCVAR("g_buffs"); BADCVAR("g_ca_teams_override"); + BADCVAR("g_ca_prevent_stalemate"); BADCVAR("g_ctf_fullbrightflags"); BADCVAR("g_ctf_ignore_frags"); BADCVAR("g_ctf_leaderboard"); @@ -451,6 +470,7 @@ void cvar_changes_init() BADCVAR("g_keyhunt_point_limit"); BADCVAR("g_keyhunt_teams_override"); BADCVAR("g_lms_lives_override"); + BADCVAR("g_mayhem_powerups"); BADCVAR("g_maplist"); BADCVAR("g_maxplayers"); BADCVAR("g_mirrordamage"); @@ -467,6 +487,8 @@ void cvar_changes_init() BADCVAR("g_start_delay"); BADCVAR("g_superspectate"); BADCVAR("g_tdm_teams_override"); + BADCVAR("g_tmayhem_teams_override"); + BADCVAR("g_tmayhem_powerups"); BADCVAR("g_weapon_stay"); BADPRESUFFIX("g_", "_weapon_stay"); BADCVAR("hostname"); BADCVAR("log_file"); @@ -510,6 +532,9 @@ void cvar_changes_init() BADPREFIX("sv_info_"); BADPREFIX("sv_ready_restart_"); + BADPRESUFFIXVALUE("g_", "_weaponarena", "most"); + BADPRESUFFIXVALUE("g_", "_weaponarena", "most_available"); + // mutators that announce themselves properly to the server browser BADCVAR("g_instagib"); BADCVAR("g_new_toys"); @@ -521,6 +546,7 @@ void cvar_changes_init() #undef BADPREFIX #undef BADCVAR #undef BADVALUE +#undef BADPRESUFFIXVALUE if(pureadding) { @@ -589,15 +615,17 @@ spawnfunc(__init_dedicated_server) e = new(info_player_deathmatch); // safeguard against player joining - // assign reflectively to avoid "assignment to world" warning - for (int i = 0, n = numentityfields(); i < n; ++i) { - string k = entityfieldname(i); - if (k == "classname") { - // safeguard against various stuff ;) - putentityfieldstring(i, this, "worldspawn"); - break; - } - } + // assign reflectively to avoid "assignment to world" warning + for (int i = 0, n = numentityfields(); i < n; ++i) + { + string k = entityfieldname(i); + if (k == "classname") + { + // safeguard against various stuff ;) + putentityfieldstring(i, this, "worldspawn"); + break; + } + } // needs to be done so early because of the constants they create static_init(); @@ -656,25 +684,27 @@ void GameplayMode_DelayedInit(entity this) int u = AVAILABLE_TEAMS - d; map_minplayers += (u < d && u + map_minplayers <= m) ? u : -d; } - warmup_limit = -1; } else - map_minplayers = 0; // don't display a minimum if it's not used + map_minplayers = 0; // don't display a minimum if it's not used (g_maxplayers < 0 && g_warmup >= 0) } void InitGameplayMode() { - VoteReset(); + VoteReset(false); // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds get_mi_min_max(1); // assign reflectively to avoid "assignment to world" warning - int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) { - string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0'; - if (v) { - putentityfieldstring(i, world, sprintf("%v", v)); - if (++done == 2) break; - } + for (int i = 0, done = 0, n = numentityfields(); i < n; ++i) + { + string k = entityfieldname(i); + vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0'; + if (v) + { + putentityfieldstring(i, world, sprintf("%v", v)); + if (++done == 2) break; + } } // currently, NetRadiant's limit is 131072 qu for each side // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu @@ -728,8 +758,7 @@ spawnfunc(worldspawn) { if (!server_is_dedicated) { - // force unloading of server pk3 files when starting a listen server - // localcmd("\nfs_rescan\n"); // FIXME: does more harm than good, has unintended side effects. What we really want is to unload temporary pk3s only + // DP unloads dlcache pk3s before starting a listen server since https://gitlab.com/xonotic/darkplaces/-/merge_requests/134 // restore csqc_progname too string expect = "csprogs.dat"; wantrestart = cvar_string("csqc_progname") != expect; @@ -858,9 +887,6 @@ spawnfunc(worldspawn) GameRules_limit_fallbacks(); - if(warmup_limit == 0) - warmup_limit = autocvar_timelimit * 60; - player_count = 0; bot_waypoints_for_items = autocvar_g_waypoints_for_items; if(bot_waypoints_for_items == 1) @@ -891,8 +917,22 @@ spawnfunc(worldspawn) MapInfo_Enumerate(); MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1); - q3compat = BITSET(q3compat, Q3COMPAT_ARENA, fexists(strcat("scripts/", mapname, ".arena"))); - q3compat = BITSET(q3compat, Q3COMPAT_DEFI, fexists(strcat("scripts/", mapname, ".defi"))); + q3compat = BITSET(q3compat, Q3COMPAT_ARENA, _MapInfo_FindArenaFile(mapname, ".arena") != ""); + q3compat = BITSET(q3compat, Q3COMPAT_DEFI, _MapInfo_FindArenaFile(mapname, ".defi") != ""); + + // quake 3 music support + if(world.music || world.noise) + { + // prefer .music over .noise + string chosen_music; + if(world.music) + chosen_music = world.music; + else + chosen_music = world.noise; + + string newstuff = strcat(clientstuff, "cd loop \"", chosen_music, "\"\n"); + strcpy(clientstuff, newstuff); + } if(whichpack(strcat("maps/", mapname, ".cfg")) != "") { @@ -1017,6 +1057,10 @@ spawnfunc(worldspawn) WinningConditionHelper(this); // set worldstatus + if (autocvar_sv_autopause && server_is_dedicated && !wantrestart) + // INITPRIO_LAST is to soon: bots either didn't join yet or didn't leave yet, see: bot_fixcount() + defer(this, 5, Pause_TryPause_Dedicated); + world_initialized = 1; __spawnfunc_spawn_all(); } @@ -1029,59 +1073,59 @@ spawnfunc(light) bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos) { - float m = e.dphitcontentsmask; - e.dphitcontentsmask = goodcontents | badcontents; - - vector org = boundmin; - vector delta = boundmax - boundmin; - - vector start, end; - start = end = org; - int j; // used after the loop - for(j = 0; j < attempts; ++j) - { - start.x = org.x + random() * delta.x; - start.y = org.y + random() * delta.y; - start.z = org.z + random() * delta.z; - - // rule 1: start inside world bounds, and outside - // solid, and don't start from somewhere where you can - // fall down to evil - tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e); - if (trace_fraction >= 1) - continue; - if (trace_startsolid) - continue; - if (trace_dphitcontents & badcontents) - continue; - if (trace_dphitq3surfaceflags & badsurfaceflags) - continue; - - // rule 2: if we are too high, lower the point - if (trace_fraction * delta.z > maxaboveground) - start = trace_endpos + '0 0 1' * maxaboveground; - vector enddown = trace_endpos; - - // rule 3: make sure we aren't outside the map. This only works - // for somewhat well formed maps. A good rule of thumb is that - // the map should have a convex outside hull. - // these can be traceLINES as we already verified the starting box - vector mstart = start + 0.5 * (e.mins + e.maxs); - traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e); - if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") - continue; - traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e); - if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") - continue; - traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e); - if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") - continue; - traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e); - if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") - continue; - traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e); - if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") - continue; + float m = e.dphitcontentsmask; + e.dphitcontentsmask = goodcontents | badcontents; + + vector org = boundmin; + vector delta = boundmax - boundmin; + + vector start, end; + start = end = org; + int j; // used after the loop + for(j = 0; j < attempts; ++j) + { + start.x = org.x + random() * delta.x; + start.y = org.y + random() * delta.y; + start.z = org.z + random() * delta.z; + + // rule 1: start inside world bounds, and outside + // solid, and don't start from somewhere where you can + // fall down to evil + tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e); + if (trace_fraction >= 1) + continue; + if (trace_startsolid) + continue; + if (trace_dphitcontents & badcontents) + continue; + if (trace_dphitq3surfaceflags & badsurfaceflags) + continue; + + // rule 2: if we are too high, lower the point + if (trace_fraction * delta.z > maxaboveground) + start = trace_endpos + '0 0 1' * maxaboveground; + vector enddown = trace_endpos; + + // rule 3: make sure we aren't outside the map. This only works + // for somewhat well formed maps. A good rule of thumb is that + // the map should have a convex outside hull. + // these can be traceLINES as we already verified the starting box + vector mstart = start + 0.5 * (e.mins + e.maxs); + traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; + traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; + traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; + traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; + traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; // rule 4: we must "see" some spawnpoint or item entity sp = NULL; @@ -1121,40 +1165,40 @@ bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax continue; } - // find a random vector to "look at" - end.x = org.x + random() * delta.x; - end.y = org.y + random() * delta.y; - end.z = org.z + random() * delta.z; - end = start + normalize(end - start) * vlen(delta); - - // rule 4: start TO end must not be too short - tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e); - if(trace_startsolid) - continue; - if(trace_fraction < minviewdistance / vlen(delta)) - continue; - - // rule 5: don't want to look at sky - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) - continue; - - // rule 6: we must not end up in trigger_hurt - if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown)) - continue; - - break; - } - - e.dphitcontentsmask = m; - - if(j < attempts) - { - setorigin(e, start); - e.angles = vectoangles(end - start); - LOG_DEBUG("Needed ", ftos(j + 1), " attempts"); - return true; - } - return false; + // find a random vector to "look at" + end.x = org.x + random() * delta.x; + end.y = org.y + random() * delta.y; + end.z = org.z + random() * delta.z; + end = start + normalize(end - start) * vlen(delta); + + // rule 4: start TO end must not be too short + tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e); + if(trace_startsolid) + continue; + if(trace_fraction < minviewdistance / vlen(delta)) + continue; + + // rule 5: don't want to look at sky + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) + continue; + + // rule 6: we must not end up in trigger_hurt + if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown)) + continue; + + break; + } + + e.dphitcontentsmask = m; + + if(j < attempts) + { + setorigin(e, start); + e.angles = vectoangles(end - start); + LOG_DEBUG("Needed ", ftos(j + 1), " attempts"); + return true; + } + return false; } float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance) @@ -1297,7 +1341,7 @@ void NextLevel() //pos = FindIntermission (); - VoteReset(); + VoteReset(true); DumpStats(true); @@ -2075,16 +2119,31 @@ void readplayerstartcvars() void readlevelcvars() { + serverflags &= ~SERVERFLAG_ALLOW_FULLBRIGHT; if(cvar("sv_allow_fullbright")) serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT; - sv_ready_restart_after_countdown = cvar("sv_ready_restart_after_countdown"); + serverflags &= ~SERVERFLAG_FORBID_PICKUPTIMER; + if(cvar("sv_forbid_pickuptimer")) + serverflags |= SERVERFLAG_FORBID_PICKUPTIMER; - warmup_stage = cvar("g_warmup"); - warmup_limit = cvar("g_warmup_limit"); + sv_ready_restart_after_countdown = cvar("sv_ready_restart_after_countdown"); if(cvar("g_campaign")) warmup_stage = 0; // no warmup during campaign + else + { + warmup_stage = autocvar_g_warmup; + if (warmup_stage < 0 || warmup_stage > 1) + warmup_limit = -1; // don't start until there's enough players + else if (warmup_stage == 1) + { + // this code is duplicated in ReadyCount() + warmup_limit = cvar("g_warmup_limit"); + if(warmup_limit == 0) + warmup_limit = autocvar_timelimit * 60; + } + } g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon"); g_pickup_respawntime_superweapon = cvar("g_pickup_respawntime_superweapon"); @@ -2142,11 +2201,11 @@ void readlevelcvars() g_pickup_ammo_anyway = cvar("g_pickup_ammo_anyway"); g_pickup_weapons_anyway = cvar("g_pickup_weapons_anyway"); - g_weapon_stay = cvar(strcat("g_", GetGametype(), "_weapon_stay")); - if(!g_weapon_stay) - g_weapon_stay = cvar("g_weapon_stay"); + g_weapon_stay = cvar(strcat("g_", GetGametype(), "_weapon_stay")); + if(!g_weapon_stay) + g_weapon_stay = cvar("g_weapon_stay"); - MUTATOR_CALLHOOK(ReadLevelCvars); + MUTATOR_CALLHOOK(ReadLevelCvars); if (!warmup_stage && !autocvar_g_campaign) game_starttime = time + cvar("g_start_delay"); @@ -2158,79 +2217,149 @@ void readlevelcvars() void InitializeEntity(entity e, void(entity this) func, int order) { - entity prev, cur; - - if (!e || e.initialize_entity) - { - // make a proxy initializer entity - entity e_old = e; - e = new(initialize_entity); - e.enemy = e_old; - } - - e.initialize_entity = func; - e.initialize_entity_order = order; - - cur = initialize_entity_first; - prev = NULL; - for (;;) - { - if (!cur || cur.initialize_entity_order > order) - { - // insert between prev and cur - if (prev) - prev.initialize_entity_next = e; - else - initialize_entity_first = e; - e.initialize_entity_next = cur; - return; - } - prev = cur; - cur = cur.initialize_entity_next; - } + entity prev, cur; + + if (!e || e.initialize_entity) + { + // make a proxy initializer entity + entity e_old = e; + e = new(initialize_entity); + e.enemy = e_old; + } + + e.initialize_entity = func; + e.initialize_entity_order = order; + + cur = initialize_entity_first; + prev = NULL; + for (;;) + { + if (!cur || cur.initialize_entity_order > order) + { + // insert between prev and cur + if (prev) + prev.initialize_entity_next = e; + else + initialize_entity_first = e; + e.initialize_entity_next = cur; + return; + } + prev = cur; + cur = cur.initialize_entity_next; + } } void InitializeEntitiesRun() { - entity startoflist = initialize_entity_first; - initialize_entity_first = NULL; - delete_fn = remove_except_protected; - for (entity e = startoflist; e; e = e.initialize_entity_next) - { + entity startoflist = initialize_entity_first; + initialize_entity_first = NULL; + delete_fn = remove_except_protected; + for (entity e = startoflist; e; e = e.initialize_entity_next) + { e.remove_except_protected_forbidden = 1; - } - for (entity e = startoflist; e; ) - { + } + for (entity e = startoflist; e; ) + { e.remove_except_protected_forbidden = 0; - e.initialize_entity_order = 0; - entity next = e.initialize_entity_next; - e.initialize_entity_next = NULL; - var void(entity this) func = e.initialize_entity; - e.initialize_entity = func_null; - if (e.classname == "initialize_entity") - { - entity wrappee = e.enemy; - builtin_remove(e); - e = wrappee; - } - //dprint("Delayed initialization: ", e.classname, "\n"); - if (func) - { - func(e); - } - else - { - eprint(e); - backtrace(strcat("Null function in: ", e.classname, "\n")); - } - e = next; - } - delete_fn = remove_unsafely; + e.initialize_entity_order = 0; + entity next = e.initialize_entity_next; + e.initialize_entity_next = NULL; + var void(entity this) func = e.initialize_entity; + e.initialize_entity = func_null; + if (e.classname == "initialize_entity") + { + entity wrappee = e.enemy; + builtin_remove(e); + e = wrappee; + } + //dprint("Delayed initialization: ", e.classname, "\n"); + if (func) + { + func(e); + } + else + { + eprint(e); + backtrace(strcat("Null function in: ", e.classname, "\n")); + } + e = next; + } + delete_fn = remove_unsafely; } // deferred dropping +// ported from VM_SV_droptofloor TODO: make a common function for the client-side? void DropToFloor_Handler(entity this) { - WITHSELF(this, builtin_droptofloor()); + if(!this || wasfreed(this)) + { + // no modifying free entities + return; + } + + vector end = this.origin; + if (autocvar_sv_mapformat_is_quake3) + end.z -= 4096; + else if (autocvar_sv_mapformat_is_quake2) + end.z -= 128; + else + end.z -= 256; // Quake, QuakeWorld + + // NOTE: SV_NudgeOutOfSolid is used in the engine here + if(autocvar_sv_gameplayfix_droptofloorstartsolid_nudgetocorrect) + { + _Movetype_UnstickEntity(this); + move_out_of_solid(this); + } + + tracebox(this.origin, this.mins, this.maxs, end, MOVE_NORMAL, this); + + if(trace_startsolid && autocvar_sv_gameplayfix_droptofloorstartsolid) + { + vector offset, org; + offset = 0.5 * (this.mins + this.maxs); + offset.z = this.mins.z; + org = this.origin + offset; + traceline(org, end, MOVE_NORMAL, this); + trace_endpos = trace_endpos - offset; + if(trace_startsolid) + { + LOG_DEBUGF("DropToFloor_Handler: %v could not fix badly placed entity", this.origin); + _Movetype_LinkEdict(this, false); + SET_ONGROUND(this); + this.groundentity = NULL; + } + else if(trace_fraction < 1) + { + LOG_DEBUGF("DropToFloor_Handler: %v fixed badly placed entity", this.origin); + setorigin(this, trace_endpos); + if(autocvar_sv_gameplayfix_droptofloorstartsolid_nudgetocorrect) + { + _Movetype_UnstickEntity(this); + move_out_of_solid(this); + } + SET_ONGROUND(this); + this.groundentity = trace_ent; + // if support is destroyed, keep suspended (gross hack for floating items in various maps) + this.move_suspendedinair = true; + } + } + else + { + if(!trace_allsolid && trace_fraction < 1) + { + setorigin(this, trace_endpos); + SET_ONGROUND(this); + this.groundentity = trace_ent; + // if support is destroyed, keep suspended (gross hack for floating items in various maps) + this.move_suspendedinair = true; + } + else + { + // if we can't get the entity out of solid, mark it as on ground so physics doesn't attempt to drop it + // hacky workaround for #2774 + SET_ONGROUND(this); + } + } this.dropped_origin = this.origin; }