Add a ton of projectile prediction improvements

- Huntsman Aimbot now properly aims, works with CanShoot on or off, and with AutoShoot disabled
- Pills are now properly aimed
- Crossbow now targets teammates if they are missing health
- Prediction now no longer passes through walls or gets stuck on janky static prop hitboxes
- Make The Loose cannon work with aimbot and crithack
This commit is contained in:
BenCat07 2021-07-15 20:32:14 +02:00 committed by LightCat
parent 2c074d5598
commit ab2b701e64
6 changed files with 304 additions and 105 deletions

View File

@ -130,6 +130,7 @@ public:
offset_t hObserverTarget;
offset_t flChargeBeginTime;
offset_t flDetonateTime;
offset_t flLastFireTime;
offset_t flObservedCritChance;
offset_t hThrower;

View File

@ -52,6 +52,7 @@ void NetVars::Init()
this->AttributeList = gNetvars.get_offset("DT_EconEntity", "m_AttributeManager", "m_Item",
"m_AttributeList"); // hmmm
this->flChargeBeginTime = gNetvars.get_offset("DT_WeaponPipebombLauncher", "PipebombLauncherLocalData", "m_flChargeBeginTime");
this->flDetonateTime = gNetvars.get_offset("DT_WeaponGrenadeLauncher", "m_flDetonateTime");
this->flLastFireTime = gNetvars.get_offset("DT_TFWeaponBase", "LocalActiveTFWeaponData", "m_flLastFireTime");
this->flObservedCritChance = gNetvars.get_offset("DT_TFWeaponBase", "LocalActiveTFWeaponData", "m_flObservedCritChance");
this->bDistributed = gNetvars.get_offset("DT_CurrencyPack", "m_bDistributed");

View File

@ -314,9 +314,29 @@ bool shouldCrit()
// Melee mode with melee out and in range?
if (shouldMeleeCrit())
return true;
static bool pressed_key_last_tick = false;
static int loose_cannon_countdown = 0;
// Crit key + enabled, for melee, the crit key MUST be set
if (enabled && ((g_pLocalPlayer->weapon_mode != weapon_melee && !crit_key) || crit_key.isKeyDown()))
{
pressed_key_last_tick = true;
return true;
}
// Special code for loose cannon since projectile is delayed
if (!crit_key.isKeyDown() && (pressed_key_last_tick || loose_cannon_countdown))
{
if (pressed_key_last_tick)
loose_cannon_countdown = 7;
pressed_key_last_tick = false;
static unsigned last_tickcount = 0;
if (tickcount != last_tickcount)
{
last_tickcount = tickcount;
loose_cannon_countdown--;
}
if (CE_GOOD(LOCAL_W) && LOCAL_W->m_iClassID() == CL_CLASS(CTFCannon))
return true;
}
// Force crits on sticky launcher
/*if (force_ticks)
{
@ -353,7 +373,7 @@ bool canWeaponCrit(bool draw = false)
// Misc checks
if (!isAllowedToWithdrawFromBucket(weapon))
return false;
if (!draw && !CanShoot() && !isRapidFire(weapon))
if (!draw && !CanShoot() && !isRapidFire(weapon) && LOCAL_W->m_iClassID() != CL_CLASS(CTFCannon))
return false;
if (!draw && CE_INT(LOCAL_W, netvar.iItemDefinitionIndex) == 730 && !can_beggars_crit)
return false;
@ -689,6 +709,9 @@ void CreateMove()
else
return;
}
else if (LOCAL_W->m_iClassID() == CL_CLASS(CTFCannon))
{
}
else if (!can_beggars_crit)
return;
}
@ -1062,49 +1085,54 @@ void LevelShutdown()
}
// Prints basically everything you need to know about crits
static CatCommand debug_print_crit_info("debug_print_crit_info", "Print a bunch of useful crit info", []() {
if (CE_BAD(LOCAL_E))
return;
static CatCommand debug_print_crit_info("debug_print_crit_info", "Print a bunch of useful crit info",
[]()
{
if (CE_BAD(LOCAL_E))
return;
logging::Info("Player specific information:");
logging::Info("Ranged Damage this round: %d", cached_damage - round_damage);
logging::Info("Melee Damage this round: %d", melee_damage);
logging::Info("Crit Damage this round: %d", crit_damage);
logging::Info("Observed crit chance: %f", getObservedCritChance());
if (CE_GOOD(LOCAL_W))
logging::Info("Player specific information:");
logging::Info("Ranged Damage this round: %d", cached_damage - round_damage);
logging::Info("Melee Damage this round: %d", melee_damage);
logging::Info("Crit Damage this round: %d", crit_damage);
logging::Info("Observed crit chance: %f", getObservedCritChance());
if (CE_GOOD(LOCAL_W))
{
IClientEntity *wep = RAW_ENT(LOCAL_W);
weapon_info info(wep);
logging::Info("Weapon specific information:");
logging::Info("Crit bucket: %f", info.crit_bucket);
logging::Info("Needed Crit chance: %f", critMultInfo(wep).second);
logging::Info("Added per shot: %f", added_per_shot);
if (isRapidFire(wep))
logging::Info("Subtracted per Rapidfire crit: %f", getWithdrawAmount(wep));
else
logging::Info("Subtracted per crit: %f", getWithdrawAmount(wep));
logging::Info("Damage Until crit: %d", damageUntilToCrit(wep));
logging::Info("Shots until crit: %d", shotsUntilCrit(wep));
}
});
static InitRoutine init(
[]()
{
IClientEntity *wep = RAW_ENT(LOCAL_W);
weapon_info info(wep);
logging::Info("Weapon specific information:");
logging::Info("Crit bucket: %f", info.crit_bucket);
logging::Info("Needed Crit chance: %f", critMultInfo(wep).second);
logging::Info("Added per shot: %f", added_per_shot);
if (isRapidFire(wep))
logging::Info("Subtracted per Rapidfire crit: %f", getWithdrawAmount(wep));
else
logging::Info("Subtracted per crit: %f", getWithdrawAmount(wep));
logging::Info("Damage Until crit: %d", damageUntilToCrit(wep));
logging::Info("Shots until crit: %d", shotsUntilCrit(wep));
}
});
static InitRoutine init([]() {
EC::Register(EC::CreateMoveLate, CreateMove, "crit_cm");
EC::Register(EC::CreateMoveLate, CreateMove, "crit_cm");
#if ENABLE_VISUALS
EC::Register(EC::Draw, Draw, "crit_draw");
EC::Register(EC::Draw, Draw, "crit_draw");
#endif
EC::Register(EC::LevelShutdown, LevelShutdown, "crit_lvlshutdown");
g_IGameEventManager->AddListener(&listener, false);
HookNetvar({ "DT_TFWeaponBase", "LocalActiveTFWeaponData", "m_flObservedCritChance" }, observed_crit_chance_hook, observedcritchance_nethook);
EC::Register(
EC::Shutdown,
[]() {
g_IGameEventManager->RemoveListener(&listener);
observed_crit_chance_hook.restore();
},
"crit_shutdown");
// Attached in game, out of sync
if (g_IEngine->IsInGame())
is_out_of_sync = true;
});
EC::Register(EC::LevelShutdown, LevelShutdown, "crit_lvlshutdown");
g_IGameEventManager->AddListener(&listener, false);
HookNetvar({ "DT_TFWeaponBase", "LocalActiveTFWeaponData", "m_flObservedCritChance" }, observed_crit_chance_hook, observedcritchance_nethook);
EC::Register(
EC::Shutdown,
[]()
{
g_IGameEventManager->RemoveListener(&listener);
observed_crit_chance_hook.restore();
},
"crit_shutdown");
// Attached in game, out of sync
if (g_IEngine->IsInGame())
is_out_of_sync = true;
});
} // namespace criticals

View File

@ -119,6 +119,11 @@ static void spectatorUpdate()
};
}
static bool playerTeamCheck(CachedEntity *entity)
{
return (int) teammates == 2 || (entity->m_bEnemy() && !teammates) || (!entity->m_bEnemy() && teammates) || (CE_GOOD(LOCAL_W) && LOCAL_W->m_iClassID() == CL_CLASS(CTFCrossbow) && entity->m_iHealth() < entity->m_iMaxHealth());
}
#if ENABLE_VISUALS
static settings::Boolean fov_draw{ "aimbot.fov-circle.enable", "0" };
static settings::Float fovcircle_opacity{ "aimbot.fov-circle.opacity", "0.7" };
@ -347,37 +352,56 @@ static void CreateMove()
// flNextPrimaryAttack meme
if (only_can_shoot && g_pLocalPlayer->weapon()->m_iClassID() != CL_CLASS(CTFMinigun) && g_pLocalPlayer->weapon()->m_iClassID() != CL_CLASS(CTFLaserPointer))
{
// Handle Compound bow
// Handle Huntsman
if (g_pLocalPlayer->weapon()->m_iClassID() == CL_CLASS(CTFCompoundBow))
{
bool release = false;
if (autoshoot)
current_user_cmd->buttons |= IN_ATTACK;
// Grab time when charge began
current_user_cmd->buttons |= IN_ATTACK;
float begincharge = CE_FLOAT(g_pLocalPlayer->weapon(), netvar.flChargeBeginTime);
float charge = g_GlobalVars->curtime - begincharge;
if (!begincharge)
charge = 0.0f;
int damage = std::floor(50.0f + 70.0f * fminf(1.0f, charge));
int charge_damage = std::floor(50.0f + 70.0f * fminf(1.0f, charge)) * 3.0f;
if (!wait_for_charge || (damage >= target_entity->m_iHealth() || charge_damage >= target_entity->m_iHealth()))
if (HasCondition<TFCond_Slowed>(LOCAL_E) && (autoshoot || !(current_user_cmd->buttons & IN_ATTACK)) && (!wait_for_charge || (charge >= 1.0f || damage >= target_entity->m_iHealth() || charge_damage >= target_entity->m_iHealth())))
release = true;
// Shoot projectile
if (release)
DoAutoshoot();
static bool currently_charging_huntsman = false;
// Hunstman started charging
if (CE_FLOAT(g_pLocalPlayer->weapon(), netvar.flChargeBeginTime) != 0)
currently_charging_huntsman = true;
// Huntsman was released
if (!(current_user_cmd->buttons & IN_ATTACK) && currently_charging_huntsman)
{
currently_charging_huntsman = false;
DoAutoshoot();
Aim(target_entity);
}
else
return;
// Not release type weapon
}
// Loose cannon
else if (LOCAL_W->m_iClassID() == CL_CLASS(CTFCannon))
{
// TODO: add logic for charge time
bool release = false;
if (autoshoot)
current_user_cmd->buttons |= IN_ATTACK;
float detonate_time = CE_FLOAT(LOCAL_W, netvar.flDetonateTime);
// Currently charging up
if (detonate_time > g_GlobalVars->curtime)
{
if (wait_for_charge)
{
// Shoot when a straight shot would result in only 100ms left on fuse upon target hit
float best_charge = PredictEntity(target_entity, false).DistTo(g_pLocalPlayer->v_Eye) / cur_proj_speed + 0.1;
if (detonate_time - g_GlobalVars->curtime <= best_charge)
release = true;
}
else
release = true;
}
if (release)
{
DoAutoshoot();
Aim(target_entity);
}
}
// Not release type weapon
else if (LOCAL_W->m_iClassID() == CL_CLASS(CTFPipebombLauncher))
{
float chargebegin = CE_FLOAT(LOCAL_W, netvar.flChargeBeginTime);
@ -655,6 +679,11 @@ CachedEntity *RetrieveBestTarget(bool aimkey_state)
break;
}
}
// Crossbow logic
if (!ent->m_bEnemy() && ent->m_Type() == ENTITY_PLAYER && CE_GOOD(LOCAL_W) && LOCAL_W->m_iClassID() == CL_CLASS(CTFCrossbow))
{
scr = ((ent->m_iMaxHealth() - ent->m_iHealth()) / ent->m_iMaxHealth()) * (*priority_mode == 2 ? 16384.0f : 2000.0f);
}
// Compare the top score to our current ents score
if (scr > target_highest_score)
{
@ -689,7 +718,7 @@ bool IsTargetStateGood(CachedEntity *entity)
if (!entity->m_bAlivePlayer())
return false;
// Teammates
if ((int) teammates != 2 && ((!entity->m_bEnemy() && !teammates) || (entity->m_bEnemy() && teammates)))
if (!playerTeamCheck(entity))
return false;
// Distance
if (EffectiveTargetingRange())
@ -1055,22 +1084,21 @@ void DoAutoshoot(CachedEntity *target_entity)
return;
if (IsPlayerDisguised(g_pLocalPlayer->entity) && !autoshoot_disguised)
return;
// Handle Compound bow
if (g_pLocalPlayer->weapon()->m_iClassID() == CL_CLASS(CTFCompoundBow))
// Handle Huntsman/Loose cannon
if (g_pLocalPlayer->weapon()->m_iClassID() == CL_CLASS(CTFCompoundBow) || g_pLocalPlayer->weapon()->m_iClassID() == CL_CLASS(CTFCannon))
{
// Release hunstman if over huntsmans limit
if (begancharge)
if (!only_can_shoot)
{
current_user_cmd->buttons &= ~IN_ATTACK;
hacks::shared::antiaim::SetSafeSpace(5);
begancharge = false;
}
// Pull string if charge isnt enough
else
{
current_user_cmd->buttons |= IN_ATTACK;
begancharge = true;
if (!begancharge)
{
current_user_cmd->buttons |= IN_ATTACK;
begancharge = true;
return;
}
}
begancharge = false;
current_user_cmd->buttons &= ~IN_ATTACK;
hacks::shared::antiaim::SetSafeSpace(5);
return;
}
else
@ -1491,6 +1519,7 @@ bool UpdateAimkey()
static bool aimkey_flip = false;
static bool pressed_last_tick = false;
bool allow_aimkey = true;
static bool last_allow_aimkey = true;
// Check if aimkey is used
if (aimkey && aimkey_mode)
@ -1516,6 +1545,19 @@ bool UpdateAimkey()
default:
break;
}
// Huntsman and Loose Cannon need special logic since we aim upon m1 being released
if (!autoshoot && CE_GOOD(LOCAL_W) && (LOCAL_W->m_iClassID() == CL_CLASS(CTFCompoundBow) || LOCAL_W->m_iClassID() == CL_CLASS(CTFCannon)))
{
if (!allow_aimkey && last_allow_aimkey)
{
allow_aimkey = true;
last_allow_aimkey = false;
}
else
last_allow_aimkey = allow_aimkey;
}
else
last_allow_aimkey = allow_aimkey;
pressed_last_tick = key_down;
}
// Return whether the aimkey allows aiming
@ -1608,15 +1650,17 @@ void rvarCallback(settings::VariableBase<float> &, float after)
{
force_backtrack_aimbot = after >= 200.0f;
}
static InitRoutine EC([]() {
hacks::tf2::backtrack::latency.installChangeCallback(rvarCallback);
EC::Register(EC::LevelInit, Reset, "INIT_Aimbot", EC::average);
EC::Register(EC::LevelShutdown, Reset, "RESET_Aimbot", EC::average);
EC::Register(EC::CreateMove, CreateMove, "CM_Aimbot", EC::late);
EC::Register(EC::CreateMoveWarp, CreateMoveWarp, "CMW_Aimbot", EC::late);
static InitRoutine EC(
[]()
{
hacks::tf2::backtrack::latency.installChangeCallback(rvarCallback);
EC::Register(EC::LevelInit, Reset, "INIT_Aimbot", EC::average);
EC::Register(EC::LevelShutdown, Reset, "RESET_Aimbot", EC::average);
EC::Register(EC::CreateMove, CreateMove, "CM_Aimbot", EC::late);
EC::Register(EC::CreateMoveWarp, CreateMoveWarp, "CMW_Aimbot", EC::late);
#if ENABLE_VISUALS
EC::Register(EC::Draw, DrawText, "DRAW_Aimbot", EC::average);
EC::Register(EC::Draw, DrawText, "DRAW_Aimbot", EC::average);
#endif
});
});
} // namespace hacks::shared::aimbot

View File

@ -1283,7 +1283,9 @@ bool GetProjectileData(CachedEntity *weapon, float &speed, float &gravity, float
}
case CL_CLASS(CTFCannon):
{
rspeed = 1453.9f;
rspeed = 1453.9f;
rgrav = 1.0f;
rinitial_vel = 200.0f;
break;
}
case CL_CLASS(CTFGrenadeLauncher):
@ -1314,8 +1316,21 @@ bool GetProjectileData(CachedEntity *weapon, float &speed, float &gravity, float
case CL_CLASS(CTFCompoundBow):
{
float chargetime = g_GlobalVars->curtime - CE_FLOAT(weapon, netvar.flChargeBeginTime);
rspeed = RemapValClamped(chargetime, 0.0f, 1.f, 1800, 2600);
rgrav = RemapValClamped(chargetime, 0.0f, 1.f, 0.5, 0.1) - 0.05;
if (CE_FLOAT(weapon, netvar.flChargeBeginTime) == 0)
chargetime = 0;
else
{
static const ConVar *pUpdateRate = g_pCVar->FindVar("cl_updaterate");
if (!pUpdateRate)
pUpdateRate = g_pCVar->FindVar("cl_updaterate");
else
{
chargetime += TICKS_TO_TIME(1);
// chargetime += ROUND_TO_TICKS(MAX(cl_interp->GetFloat(), cl_interp_ratio->GetFloat() / pUpdateRate->GetFloat()));
}
}
rspeed = RemapValClamped(chargetime, 0.0f, 1.f, 1800, 2600);
rgrav = RemapValClamped(chargetime, 0.0f, 1.f, 0.5, 0.1);
break;
}
case CL_CLASS(CTFBat_Giftwrap):
@ -1834,10 +1849,10 @@ Vector getShootPos(Vector angle)
vecOffset = Vector(16.0f, 6.0f, -8.0f);
break;
// Huntsman
/*case CL_CLASS(CTFCompoundBow):
vecOffset = Vector(23.5f, -8.0f, -3.0f);
break;*/
// Huntsman
case CL_CLASS(CTFCompoundBow):
vecOffset = Vector(23.5f, -8.0f, -3.0f);
break;
default:
break;

View File

@ -5,6 +5,7 @@
* Author: nullifiedcat
*/
#include "common.hpp"
#include "navparser.hpp"
#include <settings/Bool.hpp>
#include <boost/circular_buffer.hpp>
@ -144,7 +145,7 @@ Vector PredictStep(Vector pos, Vector &vel, const Vector &acceleration, std::pai
Vector result = pos;
// If we should do strafe prediction, then we still need to do the calculations, but instead of applying them we simply calculate the distance traveled and use that info together with strafe pred
if (strafepred && (grounddistance ? *grounddistance > 0.1f : DistanceToGround(pos, minmax->first, minmax->second) > 0.1f))
if (strafepred && (grounddistance ? *grounddistance > 0.1f : (minmax ? DistanceToGround(pos, minmax->first, minmax->second) : DistanceToGround(pos)) > 0.1f))
{
auto newpos = result + (acceleration / 2.0f) * pow(steplength, 2) + vel * steplength;
// Strafe pred does not predict Z! The player can't control his Z anyway, so it is pointless.
@ -153,9 +154,9 @@ Vector PredictStep(Vector pos, Vector &vel, const Vector &acceleration, std::pai
}
else
result += (acceleration / 2.0f) * pow(steplength, 2) + vel * steplength;
vel += acceleration * steplength;
bool moved_upwards = false;
if (vischeck)
{
Vector modify = result;
@ -166,22 +167,110 @@ Vector PredictStep(Vector pos, Vector &vel, const Vector &acceleration, std::pai
{
PROF_SECTION(PredictTraces)
Ray_t ray;
trace_t trace;
if (minmax)
ray.Init(modify, low, minmax->first, minmax->second);
else
ray.Init(modify, low);
g_ITrace->TraceRay(ray, MASK_PLAYERSOLID, &trace::filter_no_player, &trace);
float dist = pos.z - trace.endpos.z;
if (trace.m_pEnt && std::fabs(dist) < 63.0f)
grounddistance = dist;
// First, ensure we're not slightly below the floor, up to 18 HU will snap up
trace_t upwards_trace;
Ray_t ray;
Vector endpos = result;
endpos.z += 18;
if (minmax)
ray.Init(endpos, result, minmax->first, minmax->second);
else
ray.Init(endpos, result);
g_ITrace->TraceRay(ray, MASK_PLAYERSOLID, &trace::filter_no_player, &upwards_trace);
// We hit something, snap to it
if (upwards_trace.DidHit() && !upwards_trace.startsolid)
{
result = upwards_trace.endpos;
grounddistance = 0.0f;
moved_upwards = true;
}
// Now check actual ground distance
else
{
trace_t trace;
if (minmax)
ray.Init(modify, low, minmax->first, minmax->second);
else
ray.Init(modify, low);
g_ITrace->TraceRay(ray, MASK_PLAYERSOLID, &trace::filter_no_player, &trace);
float dist = pos.z - trace.endpos.z;
if (trace.m_pEnt && grounddistance > -18.0f)
grounddistance = dist;
}
}
}
if (grounddistance)
if (result.z < pos.z - *grounddistance)
result.z = pos.z - *grounddistance;
// Check if we hit a wall, if so, snap to it and distance ourselves a bit from it
if (vischeck && !moved_upwards)
{
{
PROF_SECTION(PredictTraces)
Ray_t ray;
trace_t trace;
if (minmax)
ray.Init(pos, result, minmax->first, minmax->second);
else
ray.Init(pos, result);
g_ITrace->TraceRay(ray, MASK_PLAYERSOLID, &trace::filter_no_player, &trace);
// Hit a wall, scratch along it
if (trace.DidHit())
{
Vector hitpos = trace.endpos;
Vector normal_wall = trace.plane.normal;
Vector normal_trace(hitpos.x - pos.x, hitpos.y - pos.y, 0.0f);
normal_trace = normal_trace.Normalized();
// Angle of impact determines speed
float impact_angle = acos(normal_trace.Dot(normal_wall));
// Ignore floor planes (They have no components we can use)
if (!normal_wall.AsVector2D().IsZero(0.001f))
{
// We can get a plane in Normal form and determine the direction from there
// aka n1*x+n2*y+n3*z=d
float d = normal_wall.Dot(hitpos);
Vector point1;
Vector point2;
// The above will be invalid due to a division by 0
if (normal_wall.y == 0.0f)
{
point1 = Vector((d - normal_wall.y * vel.y - normal_wall.z * hitpos.z) / normal_wall.x, vel.y, hitpos.z);
point2 = Vector((d - normal_wall.y * 2.0f * vel.y - normal_wall.z * hitpos.z) / normal_wall.x, vel.y * 2.0f, hitpos.z);
}
else
{
point1 = Vector(vel.x, (d - normal_wall.x * vel.x - normal_wall.z * hitpos.z) / normal_wall.y, hitpos.z);
point2 = Vector(vel.x * 2.0f, (d - normal_wall.x * vel.x * 2.0f - normal_wall.z * hitpos.z) / normal_wall.y, hitpos.z);
}
hitpos += normal_wall * vel * steplength;
result = hitpos;
// Adjust velocity depending on angle
float speed = vel.Length2D() * (PI - impact_angle);
// Adjust new velocity
Vector2D new_vel = (point2.AsVector2D() - point1.AsVector2D());
// Ensure we have no 0 length
if (new_vel.Length())
{
new_vel /= new_vel.Length();
vel.x = new_vel.x * speed;
vel.y = new_vel.y * speed;
}
}
}
}
}
return result;
}
@ -479,6 +568,7 @@ std::pair<Vector, Vector> ProjectilePrediction(CachedEntity *ent, int hb, float
auto strafe_pred = initializeStrafePrediction(ent);
float currenttime_before = currenttime;
for (int steps = 0; steps < maxsteps; steps++, currenttime += steplength)
{
current = last = PredictStep(last, velocity, acceleration, &minmax, steplength, strafe_pred ? &*strafe_pred : nullptr);
@ -505,7 +595,7 @@ std::pair<Vector, Vector> ProjectilePrediction(CachedEntity *ent, int hb, float
// S = at^2/2 ; t = sqrt(2S/a)*/
Vector result = bestpos + hitbox_offset;
Vector initialvel_result = result;
initialvel_result.z -= proj_startvelocity * besttime;
initialvel_result.z -= proj_startvelocity * (besttime - currenttime_before);
/*logging::Info("[Pred][%d] delta: %.2f %.2f %.2f", result.x - origin.x,
result.y - origin.y, result.z - origin.z);*/
return { result, initialvel_result };
@ -521,10 +611,20 @@ float DistanceToGround(CachedEntity *ent)
float DistanceToGround(Vector origin, Vector mins, Vector maxs)
{
trace_t ground_trace;
// First, ensure we're not slightly below the floor, up to 18 HU will snap up
trace_t upwards_trace;
Ray_t ray;
Vector endpos = origin;
endpos.z -= 8192;
endpos.z += 18;
ray.Init(endpos, origin, mins, maxs);
g_ITrace->TraceRay(ray, MASK_PLAYERSOLID, &trace::filter_no_player, &upwards_trace);
// We hit something, snap to it
if (upwards_trace.DidHit() && !upwards_trace.startsolid)
return -std::fabs(origin.z - upwards_trace.endpos.z);
trace_t ground_trace;
endpos.z = origin.z - 8192;
ray.Init(origin, endpos, mins, maxs);
g_ITrace->TraceRay(ray, MASK_PLAYERSOLID, &trace::filter_no_player, &ground_trace);
return std::fabs(origin.z - ground_trace.endpos.z);
@ -532,10 +632,20 @@ float DistanceToGround(Vector origin, Vector mins, Vector maxs)
float DistanceToGround(Vector origin)
{
trace_t ground_trace;
// First, ensure we're not slightly below the floor, up to 18 HU will snap up
trace_t upwards_trace;
Ray_t ray;
Vector endpos = origin;
endpos.z -= 8192;
endpos.z += 18;
ray.Init(endpos, origin);
g_ITrace->TraceRay(ray, MASK_PLAYERSOLID, &trace::filter_no_player, &upwards_trace);
// We hit something, snap to it
if (upwards_trace.DidHit() && !upwards_trace.startsolid)
return -std::fabs(origin.z - upwards_trace.endpos.z);
trace_t ground_trace;
endpos.z = origin.z - 8192;
ray.Init(origin, endpos);
g_ITrace->TraceRay(ray, MASK_PLAYERSOLID, &trace::filter_no_player, &ground_trace);
return std::fabs(origin.z - ground_trace.endpos.z);