Rewrite backtrack

Increases hitrate immensely, also aims to lower code complexity (for modules like aimbot and triggerbot) and does "proper" backtracking, aka teleporting the entity back to where they were originally
This commit is contained in:
BenCat07 2021-03-22 18:36:15 +01:00 committed by LightCat
parent 3ef56a4e95
commit 71f1cb2189
14 changed files with 669 additions and 879 deletions

View File

@ -46,6 +46,8 @@ public:
offset_t iFlags;
offset_t iHealth;
offset_t m_vecOrigin;
// sentry
offset_t m_iAmmoShells; // sentry shells
offset_t m_iAmmoRockets; // use only with if (GetLevel() == 3)
@ -184,6 +186,7 @@ public:
offset_t m_nSequence;
offset_t m_flSimulationTime;
offset_t m_flAnimTime;
offset_t m_flCycle;
offset_t m_angRotation;
offset_t m_hOwnerEntity;

View File

@ -102,5 +102,8 @@ enum hitbox_t
foot_L = 14,
hip_R = 15,
knee_R = 16,
foot_R = 17
foot_R = 17,
// Always last
HITBOXES_SIZE
};

View File

@ -1,5 +1,6 @@
#pragma once
#include "common.hpp"
#include "entityhitboxcache.hpp"
namespace hacks::tf2::backtrack
{
@ -18,75 +19,45 @@ public:
}
};
class hitboxData
{
public:
Vector center{ 0.0f, 0.0f, 0.0f };
Vector min{ 0.0f, 0.0f, 0.0f };
Vector max{ 0.0f, 0.0f, 0.0f };
};
class BacktrackData
{
public:
int tickcount{};
int entidx{};
std::array<hitboxData, 18> hitboxes{};
Vector m_vecOrigin{};
Vector m_vecAngles{};
Vector m_vecMins{};
Vector m_vecMaxs{};
float m_flSimulationTime{};
bool has_updated{};
bool in_range{};
std::vector<matrix3x4_t> bones{};
Vector m_vecOrigin{};
Vector m_vecAngles{};
float simtime;
float animtime;
float cycle;
int sequence;
std::array<hitbox_cache::CachedHitbox, HITBOXES_SIZE> hitboxes{};
};
// Stuff that has to be accessible from outside, mostly functions
extern settings::Float latency;
extern settings::Int bt_slots;
void adjustPing(INetChannel *ch);
bool backtrackEnabled();
bool isTickInRange(int tickcount);
void MoveToTick(BacktrackData data);
void RestoreEntity(int entidx);
bool hasData();
std::optional<BacktrackData> getData();
extern std::vector<std::vector<BacktrackData>> bt_data;
#if ENABLE_VISUALS
extern settings::Boolean chams;
extern settings::Boolean chams_wireframe;
extern settings::Int chams_ticks;
extern settings::Rgba chams_color;
extern settings::Boolean chams_solid;
extern settings::Boolean chams_overlay;
extern settings::Rgba chams_color_overlay;
extern settings::Float chams_envmap_tint_r;
extern settings::Float chams_envmap_tint_g;
extern settings::Float chams_envmap_tint_b;
#endif
// Check if backtrack is enabled
extern bool isBacktrackEnabled;
#if ENABLE_VISUALS
// Drawing Backtrack chams
extern bool isDrawing;
#endif
// Event callbacks
void CreateMove();
void CreateMoveLate();
#if ENABLE_VISUALS
void Draw();
#endif
void LevelShutdown();
void adjustPing(INetChannel *);
void updateDatagram();
void resetData(int);
bool isGoodTick(BacktrackData &);
bool defaultTickFilter(CachedEntity *, BacktrackData);
bool defaultEntFilter(CachedEntity *);
// Various functions for getting backtrack ticks
std::vector<BacktrackData> getGoodTicks(int);
std::optional<BacktrackData> getBestTick(CachedEntity *, std::function<bool(CachedEntity *, BacktrackData &, std::optional<BacktrackData> &)>);
std::optional<BacktrackData> getClosestEntTick(CachedEntity *, Vector, std::function<bool(CachedEntity *, BacktrackData)>);
std::optional<std::pair<CachedEntity *, BacktrackData>> getClosestTick(Vector, std::function<bool(CachedEntity *)>, std::function<bool(CachedEntity *, BacktrackData)>);
void SetBacktrackData(CachedEntity *ent, BacktrackData);
} // namespace hacks::tf2::backtrack

View File

@ -8,17 +8,14 @@
#pragma once
#include "common.hpp"
#include "Backtrack.hpp"
namespace hacks::tf2::backtrack
{
class BacktrackData;
}
namespace hacks::shared::triggerbot
{
void CreateMove();
bool ShouldShoot();
bool IsTargetStateGood(CachedEntity *entity, hacks::tf2::backtrack::BacktrackData *bt_data = nullptr);
bool IsTargetStateGood(CachedEntity *entity, std::optional<tf2::backtrack::BacktrackData> bt_data = std::nullopt);
CachedEntity *FindEntInSight(float range, bool no_players = false);
bool HeadPreferable(CachedEntity *target);
bool UpdateAimkey();

View File

@ -35,8 +35,8 @@ public:
inline static int SetAbsOrigin(IClientEntity *self, Vector const &origin)
{
typedef int (*SetAbsOrigin_t)(IClientEntity *, Vector const &);
uintptr_t addr = e8call_direct(gSignatures.GetClientSignature("E8 ? ? ? ? EB 7D 8B 42 04"));
SetAbsOrigin_t SetAbsOrigin_fn = SetAbsOrigin_t(addr);
static uintptr_t addr = e8call_direct(gSignatures.GetClientSignature("E8 ? ? ? ? EB 7D 8B 42 04"));
static SetAbsOrigin_t SetAbsOrigin_fn = SetAbsOrigin_t(addr);
return SetAbsOrigin_fn(self, origin);
}

View File

@ -29,7 +29,9 @@ void NetVars::Init()
this->m_Collision = gNetvars.get_offset("DT_BaseEntity", "m_Collision");
this->m_flSimulationTime = gNetvars.get_offset("DT_BaseEntity", "m_flSimulationTime");
this->m_flAnimTime = gNetvars.get_offset("DT_BaseEntity", "AnimTimeMustBeFirst", "m_flAnimTime");
this->m_flCycle = gNetvars.get_offset("DT_BaseAnimating", "serveranimdata", "m_flCycle");
this->m_angRotation = gNetvars.get_offset("DT_BaseEntity", "m_angRotation");
this->m_vecOrigin = gNetvars.get_offset("DT_BaseEntity", "m_vecOrigin");
IF_GAME(IsTF2())
{

View File

@ -261,15 +261,38 @@ bool shouldMeleeCrit()
{
if (!melee || g_pLocalPlayer->weapon_mode != weapon_melee)
return false;
if (hacks::tf2::backtrack::isBacktrackEnabled)
if (hacks::tf2::backtrack::backtrackEnabled())
{
// Closest tick for melee (default filter carry)
auto closest_tick = hacks::tf2::backtrack::getClosestTick(LOCAL_E->m_vecOrigin(), hacks::tf2::backtrack::defaultEntFilter, hacks::tf2::backtrack::defaultTickFilter);
// Closest tick for melee
std::optional<hacks::tf2::backtrack::BacktrackData> closest_tick = std::nullopt;
float best_distance = FLT_MAX;
if (hacks::tf2::backtrack::hasData())
{
closest_tick = hacks::tf2::backtrack::getData();
}
else
{
for (auto &ent_data : hacks::tf2::backtrack::bt_data)
{
for (auto &tick : ent_data)
{
if (!tick.in_range)
continue;
float distance = tick.m_vecOrigin.DistTo(g_pLocalPlayer->v_Origin);
if (distance < best_distance)
{
best_distance = distance;
closest_tick = tick;
}
}
}
}
// Valid backtrack target
if (closest_tick)
{
// Out of range, don't crit
if ((*closest_tick).second.m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin()) >= re::C_TFWeaponBaseMelee::GetSwingRange(RAW_ENT(LOCAL_W)) + 150.0f)
if (closest_tick->m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin()) >= re::C_TFWeaponBaseMelee::GetSwingRange(RAW_ENT(LOCAL_W)) + 150.0f)
return false;
}
else

View File

@ -140,7 +140,7 @@ matrix3x4_t *EntityHitboxCache::GetBones(int numbones)
numbones = MAXSTUDIOBONES;
}
if (bones.size() < (size_t) numbones)
if (bones.size() != (size_t) numbones)
bones.resize(numbones);
if (g_Settings.is_create_move)
{

View File

@ -154,9 +154,27 @@ float cur_proj_speed{ 0.0f };
float cur_proj_grav{ 0.0f };
float cur_proj_start_vel{ 0.0f };
bool shouldBacktrack()
bool shouldbacktrack_cache = false;
void updateShouldBacktrack()
{
return !projectile_mode && (*backtrackAimbot || force_backtrack_aimbot) && hacks::tf2::backtrack::isBacktrackEnabled;
if (!hacks::tf2::backtrack::backtrackEnabled() || hacks::tf2::backtrack::hasData() || projectile_mode || !(*backtrackAimbot || force_backtrack_aimbot))
shouldbacktrack_cache = false;
else
shouldbacktrack_cache = true;
}
bool shouldBacktrack(CachedEntity *ent)
{
if (!shouldbacktrack_cache)
return false;
if (ent && ent->m_Type() != ENTITY_PLAYER)
return false;
if (hacks::tf2::backtrack::bt_data.size() < ent->m_IDX)
return false;
if (hacks::tf2::backtrack::bt_data[ent->m_IDX - 1].empty())
return false;
return true;
}
// Am I holding Hitman's Heatmaker ?
@ -165,23 +183,6 @@ static bool CarryingHeatmaker()
return CE_INT(LOCAL_W, netvar.iItemDefinitionIndex) == 752;
}
// Backtrack filter for aimbot
bool aimbotTickFilter(CachedEntity *ent, hacks::tf2::backtrack::BacktrackData tick)
{
// FOV check
if (fov > 0.0f)
{
float fov_scr = GetFov(g_pLocalPlayer->v_OrigViewangles, g_pLocalPlayer->v_Eye, tick.hitboxes.at(0).center);
// Failed FOV check
if (fov_scr > fov)
return false;
}
// Not hitscan, no vischeck needed
if (g_pLocalPlayer->weapon_mode != weapon_hitscan)
return true;
// Return visibility
return IsEntityVectorVisible(ent, tick.hitboxes.at(head).center, true, MASK_SHOT);
}
static void doAutoZoom(bool target_found)
{
bool isIdle = target_found ? false : hacks::shared::followbot::isIdle();
@ -235,6 +236,7 @@ static void CreateMove()
slow_aim = *normal_slow_aim;
fov = *normal_fov;
updateShouldBacktrack();
spectatorUpdate();
if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer() || CE_BAD(LOCAL_W))
@ -413,11 +415,11 @@ bool MouseMoving()
{
if ((g_GlobalVars->curtime - last_mouse_check) < 0.02)
{
auto previous = SDL_GetMouseState(&PreviousX, &PreviousY);
SDL_GetMouseState(&PreviousX, &PreviousY);
}
else
{
auto current = SDL_GetMouseState(&CurrentX, &CurrentY);
SDL_GetMouseState(&CurrentX, &CurrentY);
last_mouse_check = g_GlobalVars->curtime;
}
@ -509,8 +511,23 @@ CachedEntity *RetrieveBestTarget(bool aimkey_state)
{
if (CE_GOOD(target_last))
{
if (shouldBacktrack(target_last))
{
for (auto &bt_tick : hacks::tf2::backtrack::bt_data[target_last->m_IDX - 1])
{
if (bt_tick.in_range)
{
hacks::tf2::backtrack::MoveToTick(bt_tick);
if (IsTargetStateGood(target_last))
return target_last;
// Restore if bad target
hacks::tf2::backtrack::RestoreEntity(target_last->m_IDX);
}
}
}
// Check if previous target is still good
if (IsTargetStateGood(target_last))
else if (IsTargetStateGood(target_last))
{
// If it is then return it again
return target_last;
@ -520,15 +537,38 @@ CachedEntity *RetrieveBestTarget(bool aimkey_state)
float target_highest_score, scr = 0.0f;
CachedEntity *ent;
CachedEntity *target_highest_ent = 0;
target_highest_score = -256;
CachedEntity *target_highest_ent = 0;
target_highest_score = -256;
std::optional<hacks::tf2::backtrack::BacktrackData> bt_tick = std::nullopt;
for (int i = 1; i <= HIGHEST_ENTITY; i++)
{
ent = ENTITY(i);
if (CE_BAD(ent))
continue; // Check for null and dormant
// Check whether the current ent is good enough to target
if (IsTargetStateGood(ent))
bool isTargetGood = false;
static std::optional<hacks::tf2::backtrack::BacktrackData> temp_bt_tick = std::nullopt;
if (shouldBacktrack(ent))
{
for (auto &bt_tick : hacks::tf2::backtrack::bt_data[ent->m_IDX - 1])
{
if (bt_tick.in_range)
{
hacks::tf2::backtrack::MoveToTick(bt_tick);
if (IsTargetStateGood(ent))
{
isTargetGood = true;
temp_bt_tick = bt_tick;
break;
}
hacks::tf2::backtrack::RestoreEntity(ent->m_IDX);
}
}
}
else
isTargetGood = IsTargetStateGood(ent);
if (isTargetGood)
{
// Distance Priority, Uses this is melee is used
if (GetWeaponMode() == weaponmode::weapon_melee || (int) priority_mode == 2)
@ -561,10 +601,17 @@ CachedEntity *RetrieveBestTarget(bool aimkey_state)
{
target_highest_score = scr;
target_highest_ent = ent;
bt_tick = temp_bt_tick;
}
}
// Restore tick
if (shouldBacktrack(ent))
hacks::tf2::backtrack::RestoreEntity(i);
}
if (target_highest_ent && bt_tick)
hacks::tf2::backtrack::MoveToTick(*bt_tick);
return target_highest_ent;
}
@ -596,34 +643,17 @@ bool IsTargetStateGood(CachedEntity *entity)
else
{
float swingrange = EffectiveTargetingRange();
if (!shouldBacktrack() || entity->m_Type() != ENTITY_PLAYER)
{
int hb = BestHitbox(entity);
if (hb == -1)
return false;
Vector newangle = GetAimAtAngles(g_pLocalPlayer->v_Eye, entity->hitboxes.GetHitbox(hb)->center, LOCAL_E);
trace_t trace;
Ray_t ray;
trace::filter_default.SetSelf(RAW_ENT(g_pLocalPlayer->entity));
ray.Init(g_pLocalPlayer->v_Eye, GetForwardVector(g_pLocalPlayer->v_Eye, newangle, swingrange, LOCAL_E));
g_ITrace->TraceRay(ray, MASK_SHOT_HULL, &trace::filter_default, &trace);
if ((IClientEntity *) trace.m_pEnt != RAW_ENT(entity))
return false;
}
else
{
// This does vischecks and everything
auto data = hacks::tf2::backtrack::getClosestEntTick(entity, LOCAL_E->m_vecOrigin(), aimbotTickFilter);
// No data found
if (!data)
return false;
// Is within range?
Vector melee_vec = data->m_vecOrigin;
melee_vec.z = g_pLocalPlayer->v_Eye.z;
// Out of range
if (melee_vec.DistTo(g_pLocalPlayer->v_Eye) >= swingrange)
return false;
}
int hb = BestHitbox(entity);
if (hb == -1)
return false;
Vector newangle = GetAimAtAngles(g_pLocalPlayer->v_Eye, entity->hitboxes.GetHitbox(hb)->center, LOCAL_E);
trace_t trace;
Ray_t ray;
trace::filter_default.SetSelf(RAW_ENT(g_pLocalPlayer->entity));
ray.Init(g_pLocalPlayer->v_Eye, GetForwardVector(g_pLocalPlayer->v_Eye, newangle, swingrange, LOCAL_E));
g_ITrace->TraceRay(ray, MASK_SHOT_HULL, &trace::filter_default, &trace);
if ((IClientEntity *) trace.m_pEnt != RAW_ENT(entity))
return false;
}
}
// Rage only check
@ -731,15 +761,7 @@ bool IsTargetStateGood(CachedEntity *entity)
Vector pos = GetBuildingPosition(ENTITY(sentry));
if (hitbox == -1 || !entity->hitboxes.GetHitbox(cd.hitbox))
return false;
if (shouldBacktrack())
{
// This does vischecks and everything
auto data = hacks::tf2::backtrack::getClosestEntTick(entity, LOCAL_E->m_vecOrigin(), aimbotTickFilter);
// No data found
if (!data)
return false;
}
else if (!IsVectorVisible(pos, entity->hitboxes.GetHitbox(cd.hitbox)->center, false, ENTITY(sentry)))
if (!IsVectorVisible(pos, entity->hitboxes.GetHitbox(cd.hitbox)->center, false, ENTITY(sentry)))
return false;
}
if (fov > 0.0f && cd.fov > fov && tickcount > hacks::shared::aimbot::last_target_ignore_timer)
@ -878,15 +900,7 @@ bool IsTargetStateGood(CachedEntity *entity)
if (sentry == -1)
return false;
Vector pos = GetBuildingPosition(ENTITY(sentry));
if (shouldBacktrack())
{
// This does vischecks and everything
auto data = hacks::tf2::backtrack::getClosestEntTick(entity, LOCAL_E->m_vecOrigin(), aimbotTickFilter);
// No data found
if (!data)
return false;
}
else if (!IsVectorVisible(pos, entity->m_vecOrigin(), false))
if (!IsVectorVisible(pos, entity->m_vecOrigin(), false))
return false;
}
if (fov > 0.0f && cd.fov > fov)
@ -924,15 +938,7 @@ void Aim(CachedEntity *entity)
auto hitboxmin = hb->min;
auto hitboxmax = hb->max;
auto hitboxcenter = hb->center;
if (shouldBacktrack())
{
// This does vischecks and everything
auto data = hacks::tf2::backtrack::getClosestEntTick(entity, LOCAL_E->m_vecOrigin(), aimbotTickFilter);
auto bt_hb = data->hitboxes.at(cd.hitbox);
hitboxcenter = bt_hb.center;
hitboxmin = bt_hb.min;
hitboxmax = bt_hb.max;
}
// get positions
minx = hitboxmin.x;
miny = hitboxmin.y;
@ -971,15 +977,8 @@ void Aim(CachedEntity *entity)
if (silent && !slow_aim)
g_pLocalPlayer->bUseSilentAngles = true;
// Set tick count to targets (backtrack messes with this)
if (!shouldBacktrack() && nolerp && entity->m_IDX <= g_IEngine->GetMaxClients())
if (!shouldBacktrack(entity) && nolerp && entity->m_IDX <= g_IEngine->GetMaxClients())
current_user_cmd->tick_count = TIME_TO_TICKS(CE_FLOAT(entity, netvar.m_flSimulationTime));
// Set Backtrack data
if (shouldBacktrack() && entity->m_Type() == ENTITY_PLAYER)
{
auto data = hacks::tf2::backtrack::getClosestEntTick(entity, LOCAL_E->m_vecOrigin(), aimbotTickFilter);
if (data)
hacks::tf2::backtrack::SetBacktrackData(entity, *data);
}
aimed_this_tick = true;
// Finish function
return;
@ -1095,84 +1094,71 @@ Vector PredictEntity(CachedEntity *entity, bool vischeck)
// Pull out predicted data
AimbotCalculatedData_s &cd = calculated_data_array[entity->m_IDX];
Vector &result = cd.aim_position;
if (cd.predict_tick == tickcount && cd.predict_type == vischeck)
if (cd.predict_tick == tickcount && cd.predict_type == vischeck && !shouldBacktrack(entity))
return result;
if (!shouldBacktrack() || entity->m_Type() != ENTITY_PLAYER)
// Players
if ((entity->m_Type() == ENTITY_PLAYER))
{
// Players
if ((entity->m_Type() == ENTITY_PLAYER))
// If using projectiles, predict a vector
if (projectileAimbotRequired)
{
// If using projectiles, predict a vector
if (projectileAimbotRequired)
{
std::pair<Vector, Vector> tmp_result;
// Use prediction engine if user settings allow
if (engine_projpred)
tmp_result = ProjectilePrediction_Engine(entity, cd.hitbox, cur_proj_speed, cur_proj_grav, 0, cur_proj_start_vel);
else
tmp_result = ProjectilePrediction(entity, cd.hitbox, cur_proj_speed, cur_proj_grav, PlayerGravityMod(entity), cur_proj_start_vel);
// Don't use the intial velocity compensated one in vischecks
if (vischeck)
result = tmp_result.first;
else
result = tmp_result.second;
}
std::pair<Vector, Vector> tmp_result;
// Use prediction engine if user settings allow
if (engine_projpred)
tmp_result = ProjectilePrediction_Engine(entity, cd.hitbox, cur_proj_speed, cur_proj_grav, 0, cur_proj_start_vel);
else
{
// If using extrapolation, then predict a vector
if (extrapolate)
result = SimpleLatencyPrediction(entity, cd.hitbox);
// else just grab strait from the hitbox
else
GetHitbox(entity, cd.hitbox, result);
}
}
// Buildings
else if (entity->m_Type() == ENTITY_BUILDING)
{
if (projectileAimbotRequired)
{
std::pair<Vector, Vector> tmp_result;
tmp_result = BuildingPrediction(entity, GetBuildingPosition(entity), cur_proj_speed, cur_proj_grav, cur_proj_start_vel);
tmp_result = ProjectilePrediction(entity, cd.hitbox, cur_proj_speed, cur_proj_grav, PlayerGravityMod(entity), cur_proj_start_vel);
// Don't use the intial velocity compensated one in vischecks
if (vischeck)
result = tmp_result.first;
else
result = tmp_result.second;
}
// Don't use the intial velocity compensated one in vischecks
if (vischeck)
result = tmp_result.first;
else
result = GetBuildingPosition(entity);
result = tmp_result.second;
}
// NPCs (Skeletons, merasmus, etc)
else if (entity->m_Type() == ENTITY_NPC)
{
result = entity->hitboxes.GetHitbox(std::max(0, entity->hitboxes.GetNumHitboxes() / 2 - 1))->center;
}
// Other
else
{
result = entity->m_vecOrigin();
// If using extrapolation, then predict a vector
if (extrapolate)
result = SimpleLatencyPrediction(entity, cd.hitbox);
// else just grab strait from the hitbox
else
GetHitbox(entity, cd.hitbox, result);
}
cd.predict_tick = tickcount;
cd.predict_type = vischeck;
cd.fov = GetFov(g_pLocalPlayer->v_OrigViewangles, g_pLocalPlayer->v_Eye, result);
}
// Buildings
else if (entity->m_Type() == ENTITY_BUILDING)
{
if (projectileAimbotRequired)
{
std::pair<Vector, Vector> tmp_result;
tmp_result = BuildingPrediction(entity, GetBuildingPosition(entity), cur_proj_speed, cur_proj_grav, cur_proj_start_vel);
// Don't use the intial velocity compensated one in vischecks
if (vischeck)
result = tmp_result.first;
else
result = tmp_result.second;
}
else
result = GetBuildingPosition(entity);
}
// NPCs (Skeletons, merasmus, etc)
else if (entity->m_Type() == ENTITY_NPC)
{
result = entity->hitboxes.GetHitbox(std::max(0, entity->hitboxes.GetNumHitboxes() / 2 - 1))->center;
}
// Other
else
{
auto data = hacks::tf2::backtrack::getClosestEntTick(entity, LOCAL_E->m_vecOrigin(), aimbotTickFilter);
if (data)
{
result = data->hitboxes.at(cd.hitbox).center;
cd.predict_tick = tickcount;
cd.predict_type = vischeck;
cd.fov = GetFov(g_pLocalPlayer->v_OrigViewangles, g_pLocalPlayer->v_Eye, result);
}
result = entity->m_vecOrigin();
}
cd.predict_tick = tickcount;
cd.predict_type = vischeck;
cd.fov = GetFov(g_pLocalPlayer->v_OrigViewangles, g_pLocalPlayer->v_Eye, result);
// Return the found vector
return result;
}
@ -1303,38 +1289,15 @@ int BestHitbox(CachedEntity *target)
return 12;
}
// Backtracking and preferred hitbox
if (shouldBacktrack())
{
auto data = hacks::tf2::backtrack::getClosestEntTick(target, LOCAL_E->m_vecOrigin(), aimbotTickFilter);
if (data)
{
// First check preferred hitbox
if (IsEntityVectorVisible(target, (*data).hitboxes[preferred].center, false))
return preferred;
// Then check the rest
if (*backtrackVischeckAll)
for (int j = head; j < foot_R; j++)
{
if (IsEntityVectorVisible(target, (*data).hitboxes[j].center, false))
return j;
}
else if (IsEntityVectorVisible(target, (*data).hitboxes.at(head).center, false))
return 0;
}
// Nothing found, falling through to further below
}
else if (target->hitboxes.VisibilityCheck(preferred))
// preferred hitbox
if (target->hitboxes.VisibilityCheck(preferred))
return preferred;
// Else attempt to find any hitbox at all
if (!shouldBacktrack())
for (int i = projectile_mode ? 1 : 0; i < target->hitboxes.GetNumHitboxes() && i < 6; i++)
{
if (target->hitboxes.VisibilityCheck(i))
return i;
}
for (int i = projectile_mode ? 1 : 0; i < target->hitboxes.GetNumHitboxes() && i < 6; i++)
{
if (target->hitboxes.VisibilityCheck(i))
return i;
}
}
break;
case 1:
@ -1381,28 +1344,17 @@ bool VischeckPredictedEntity(CachedEntity *entity)
{
// Retrieve predicted data
AimbotCalculatedData_s &cd = calculated_data_array[entity->m_IDX];
if (cd.vcheck_tick == tickcount)
if (cd.vcheck_tick == tickcount && !shouldBacktrack(entity))
return cd.visible;
if (!shouldBacktrack() || entity->m_Type() != ENTITY_PLAYER)
{
// Update info
cd.vcheck_tick = tickcount;
if (extrapolate || projectileAimbotRequired || entity->m_Type() != ENTITY_PLAYER)
cd.visible = IsEntityVectorVisible(entity, PredictEntity(entity, true), true);
else
{
trace_t trace;
cd.visible = IsEntityVectorVisible(entity, PredictEntity(entity, true), false, MASK_SHOT, &trace);
if (cd.visible && cd.hitbox == head && trace.hitbox != head)
cd.visible = false;
}
}
// Update info
cd.vcheck_tick = tickcount;
if (extrapolate || projectileAimbotRequired || entity->m_Type() != ENTITY_PLAYER)
cd.visible = IsEntityVectorVisible(entity, PredictEntity(entity, true), true);
else
{
auto data = hacks::tf2::backtrack::getClosestEntTick(entity, LOCAL_E->m_vecOrigin(), aimbotTickFilter);
if (data && IsEntityVectorVisible(entity, data->hitboxes.at((cd.hitbox == -1 || cd.hitbox >= 18) ? 0 : cd.hitbox).center, false, MASK_SHOT))
cd.visible = true;
else
trace_t trace;
cd.visible = IsEntityVectorVisible(entity, PredictEntity(entity, true), false, MASK_SHOT, &trace);
if (cd.visible && cd.hitbox == head && trace.hitbox != head)
cd.visible = false;
}
return cd.visible;

View File

@ -321,32 +321,24 @@ static bool doRageBackstab()
static bool legit_stab = false;
static Vector newangle_apply;
bool backtrackFilter(CachedEntity *ent, hacks::tf2::backtrack::BacktrackData tick, std::optional<hacks::tf2::backtrack::BacktrackData> &best_tick)
bool IsTickGood(hacks::tf2::backtrack::BacktrackData tick)
{
Vector target_worldspace;
VectorLerp(tick.m_vecMins, tick.m_vecMaxs, 0.5f, target_worldspace);
target_worldspace += tick.m_vecOrigin;
Vector distcheck = target_worldspace;
distcheck.z = g_pLocalPlayer->v_Eye.z;
CachedEntity *ent = ENTITY(tick.entidx);
Vector target_vec = tick.m_vecOrigin;
// Angle check
Vector newangle;
if (legit_stab)
newangle = g_pLocalPlayer->v_OrigViewangles;
else
newangle = GetAimAtAngles(g_pLocalPlayer->v_Eye, distcheck, LOCAL_E);
if (!angleCheck(ent, target_worldspace, newangle) && !canFaceStab(ent))
Vector target_worldspace = target_vec;
target_worldspace += (RAW_ENT(ent)->GetCollideable()->OBBMins() + RAW_ENT(ent)->GetCollideable()->OBBMaxs()) / 2.0f;
Vector angle = GetAimAtAngles(g_pLocalPlayer->v_Eye, target_worldspace);
if (!angleCheck(ent, target_worldspace, angle))
return false;
// Check if we can hit the enemy
if (doMovedSwingTrace(ent, tick.m_vecOrigin))
trace_t trace;
if (doMovedSwingTrace(ent, target_vec))
{
// Check if this tick is closer
if (!best_tick || (*best_tick).m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin()) > tick.m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin()))
{
newangle_apply = newangle;
return true;
}
newangle_apply = angle;
return true;
}
return false;
@ -359,29 +351,29 @@ static bool doBacktrackStab(bool legit = false)
// Set for our filter
legit_stab = legit;
// Get the Best tick
for (int i = 0; i <= g_IEngine->GetMaxClients(); i++)
for (int i = 1; i <= g_IEngine->GetMaxClients(); i++)
{
CachedEntity *ent = ENTITY(i);
// Targeting checks
if (CE_BAD(ent) || !ent->m_bAlivePlayer() || !ent->m_bEnemy() || !player_tools::shouldTarget(ent) || IsPlayerInvulnerable(ent))
continue;
// Get the best tick for that ent
auto tick_data = hacks::tf2::backtrack::getBestTick(ent, backtrackFilter);
// We found something matching the criterias, break out
if (tick_data)
for (auto &bt_tick : hacks::tf2::backtrack::bt_data[i - 1])
{
stab_data = *tick_data;
stab_ent = ent;
break;
if (bt_tick.in_range && IsTickGood(bt_tick))
{
// We found something matching the criterias, break out
stab_data = bt_tick;
stab_ent = ent;
break;
}
}
}
// We found a good ent
if (stab_ent)
{
hacks::tf2::backtrack::SetBacktrackData(stab_ent, stab_data);
hacks::tf2::backtrack::MoveToTick(stab_data);
current_user_cmd->buttons |= IN_ATTACK;
current_user_cmd->viewangles = newangle_apply;
g_pLocalPlayer->bUseSilentAngles = true;
@ -406,6 +398,8 @@ void CreateMove()
return;
if (!CanShoot())
return;
bool shouldBacktrack = backtrack::backtrackEnabled() && !backtrack::hasData();
switch (*mode)
{
case 0:
@ -415,7 +409,7 @@ void CreateMove()
doRageBackstab();
break;
case 2:
if (hacks::tf2::backtrack::isBacktrackEnabled)
if (shouldBacktrack)
{
if (*hacks::tf2::backtrack::latency <= 190 && doRageBackstab())
break;
@ -427,7 +421,7 @@ void CreateMove()
}
break;
case 3:
if (hacks::tf2::backtrack::isBacktrackEnabled)
if (shouldBacktrack)
{
if (*hacks::tf2::backtrack::latency <= 190 && doLegitBackstab())
break;

View File

@ -1,38 +1,14 @@
/*
* Remade on May the 3rd 2020
* Author: BenCat07
*
*/
#include "common.hpp"
#include "Backtrack.hpp"
#include "PlayerTools.hpp"
#include "memory"
namespace hacks::tf2::backtrack
{
// Internal rvars
static settings::Boolean enabled{ "backtrack.enabled", "false" };
static settings::Boolean draw{ "backtrack.draw", "false" };
static settings::Boolean enabled("backtrack.enabled", "false");
settings::Float latency("backtrack.latency", "0");
static settings::Int bt_slots("backtrack.slots", "0");
static std::vector<CIncomingSequence> sequences;
static int current_tickcount;
static std::vector<std::unique_ptr<std::array<BacktrackData, 67>>> backtrack_data;
static int lastincomingsequence{ 0 };
// Used to make transition smooth(er)
static float latency_rampup = 0.0f;
// Which data to apply in the late CreateMove
static CachedEntity *bt_ent = nullptr;
static std::optional<BacktrackData> bt_data;
static bool isEnabled();
static float getLatency();
static int getTicks();
static bool getBestInternalTick(CachedEntity *, BacktrackData &, std::optional<BacktrackData> &);
static void ApplyBacktrack();
settings::Float latency{ "backtrack.latency", "0" };
settings::Int bt_slots{ "backtrack.slots", "0" };
#if ENABLE_VISUALS
static settings::Boolean draw("backtrack.draw", "false");
settings::Boolean chams{ "backtrack.chams", "false" };
settings::Boolean chams_wireframe{ "backtrack.chams.wireframe", "false" };
settings::Int chams_ticks{ "backtrack.chams.ticks", "1" };
@ -44,63 +20,73 @@ settings::Float chams_envmap_tint_g{ "backtrack.chams.envmap.tint.g", "0" };
settings::Float chams_envmap_tint_b{ "backtrack.chams.envmap.tint.b", "1" };
#endif
static bool isEnabled();
#define MAX_BACKTRACK_TICKS 66
// Check if backtrack is enabled
bool isBacktrackEnabled;
#if ENABLE_VISUALS
// Drawing Backtrack chams
bool isDrawing;
#endif
static bool isBacktrackEnabled = false;
// Apply Backtrack
void ApplyBacktrack()
// Is any backtrack tick set for a player currently?
static std::optional<BacktrackData> set_data;
bool hasData()
{
if (!isBacktrackEnabled)
return;
if (bt_ent && bt_data)
return (bool) set_data;
}
std::optional<BacktrackData> getData()
{
return set_data;
}
static int lastincomingsequence = 0;
std::deque<CIncomingSequence> sequences;
static float latency_rampup = 0.0f;
// Store draw positions in CreateMove, also store the selected tick one too
static std::vector<Vector> draw_positions;
static std::optional<Vector> red_position;
std::vector<std::vector<BacktrackData>> bt_data;
// Update our sequences
void updateDatagram()
{
INetChannel *ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
if (ch)
{
current_user_cmd->tick_count = (*bt_data).tickcount;
CE_FLOAT(bt_ent, netvar.m_angEyeAngles) = (*bt_data).m_vecAngles.x;
CE_FLOAT(bt_ent, netvar.m_angEyeAngles + 4) = (*bt_data).m_vecAngles.y;
CE_FLOAT(bt_ent, netvar.m_flSimulationTime) = (*bt_data).m_flSimulationTime;
int m_nInSequenceNr = ch->m_nInSequenceNr;
int instate = ch->m_nInReliableState;
if (m_nInSequenceNr > lastincomingsequence)
{
lastincomingsequence = m_nInSequenceNr;
sequences.insert(sequences.begin(), CIncomingSequence(instate, m_nInSequenceNr, g_GlobalVars->realtime));
}
if (sequences.size() > 2048)
sequences.pop_back();
}
}
// Update tick to apply
void SetBacktrackData(CachedEntity *ent, BacktrackData tick)
// Latency to add for backtrack
float getLatency()
{
bt_ent = ent;
bt_data = tick;
INetChannel *ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
// Track what actual latency we have
float real_latency = 0.0f;
// If we have a netchannel (just in case) set real latency to it
if (ch)
real_latency = ch->GetLatency(FLOW_OUTGOING) * 1000.0f;
// Clamp and apply rampup, also ensure we do not go out of the 1000.0f bounds
float backtrack_latency = latency_rampup * std::clamp(*latency, 0.0f, 800.0f - real_latency);
return backtrack_latency;
}
// Get Best tick for Backtrack (crosshair/fov based)
bool getBestInternalTick(CachedEntity *, BacktrackData &data, std::optional<BacktrackData> &best_tick)
bool isTickInRange(int tickcount)
{
// Best Score
float bestScore = FLT_MAX;
// Are we using a melee weapon?
bool is_melee = false;
if (g_pLocalPlayer->weapon_mode == weapon_melee)
is_melee = true;
// Get the FOV of the best tick if available
if (best_tick)
bestScore = GetFov(g_pLocalPlayer->v_OrigViewangles, g_pLocalPlayer->v_Eye, (*best_tick).hitboxes.at(head).center);
float FOVDistance = GetFov(g_pLocalPlayer->v_OrigViewangles, g_pLocalPlayer->v_Eye, data.hitboxes.at(head).center);
if (FOVDistance >= bestScore)
return false;
// Found new best fov, now vischeck
if (FOVDistance < bestScore)
{
// Vischeck check
if (!is_melee && !IsVectorVisible(g_pLocalPlayer->v_Eye, data.hitboxes.at(head).center, false))
return false;
return true;
}
// Should never be called but gcc likes to complain anyways
return false;
int delta_tickcount = abs(tickcount - current_user_cmd->tick_count + TIME_TO_TICKS(getLatency() / 1000.0f));
return TICKS_TO_TIME(delta_tickcount) <= 0.2f - TICKS_TO_TIME(2);
}
// Is backtrack enabled?
@ -145,207 +131,6 @@ bool isEnabled()
return false;
}
// Main Tracking logic
void CreateMove()
{
// Update enabled status
isBacktrackEnabled = isEnabled();
if (!isBacktrackEnabled)
{
latency_rampup = 0.0f;
return;
}
// Return if local entity is bad (Still have backtrack run while dead so ping does not fluctuate heavily)
if (CE_BAD(LOCAL_E))
{
latency_rampup = 0.0f;
return;
}
// Make Latency not instantly spike
latency_rampup += 1.0f / 66.0f;
latency_rampup = std::min(latency_rampup, 1.0f);
updateDatagram();
// Don't run rest
if (CE_BAD(LOCAL_W) || !LOCAL_E->m_bAlivePlayer())
return;
// Clear data
bt_ent = nullptr;
bt_data.reset();
current_tickcount = current_user_cmd->tick_count;
// the "Base" Backtracking
std::optional<BacktrackData> best_data;
CachedEntity *best_ent = nullptr;
for (int i = 1; i <= g_IEngine->GetMaxClients(); i++)
{
if (i == g_pLocalPlayer->entity_idx)
{
resetData(i);
continue;
}
CachedEntity *ent = ENTITY(i);
if (CE_BAD(ent) || !ent->m_bAlivePlayer() || ent->m_Type() != ENTITY_PLAYER || !ent->m_bEnemy())
{
resetData(i);
continue;
}
if (!ent->hitboxes.GetHitbox(0))
{
resetData(i);
continue;
}
auto &backtrack_ent = backtrack_data.at(i - 1);
// Have no data, create it
if (!backtrack_ent)
backtrack_ent.reset(new std::array<BacktrackData, 67>);
int current_index = current_user_cmd->tick_count % getTicks();
// Our current tick
auto &current_tick = (*backtrack_ent).at(current_index);
// Previous tick
int last_index = current_index - 1;
if (last_index < 0)
last_index = getTicks() - 1;
auto &previous_tick = (*backtrack_ent).at(last_index);
// Update basics
current_tick.tickcount = current_user_cmd->tick_count;
current_tick.m_vecAngles.x = CE_FLOAT(ent, netvar.m_angEyeAngles);
current_tick.m_vecAngles.y = CE_FLOAT(ent, netvar.m_angEyeAngles + 4);
current_tick.m_vecOrigin = ent->m_vecOrigin();
current_tick.m_vecMins = RAW_ENT(ent)->GetCollideable()->OBBMins();
current_tick.m_vecMaxs = RAW_ENT(ent)->GetCollideable()->OBBMaxs();
current_tick.m_flSimulationTime = CE_FLOAT(ent, netvar.m_flSimulationTime);
// Update hitboxes
ent->hitboxes.InvalidateCache();
for (size_t i = 0; i < 18; i++)
{
current_tick.hitboxes[i].center = ent->hitboxes.GetHitbox(i)->center;
current_tick.hitboxes[i].min = ent->hitboxes.GetHitbox(i)->min;
current_tick.hitboxes[i].max = ent->hitboxes.GetHitbox(i)->max;
}
// Copy bones (for chams/glow)
auto model = (const model_t *) RAW_ENT(ent)->GetModel();
if (model)
{
auto shdr = g_IModelInfo->GetStudiomodel(model);
if (shdr)
{
int numbones = shdr->numbones;
if (numbones != current_tick.bones.size())
current_tick.bones.resize(numbones);
memcpy((void *) &current_tick.bones[0], (void *) &ent->hitboxes.bones[0], sizeof(matrix3x4_t) * numbones);
}
else
current_tick.bones.resize(0);
}
else
current_tick.bones.resize(0);
// Check if tick updated or not (fakelag)
current_tick.has_updated = !previous_tick.m_flSimulationTime || previous_tick.m_flSimulationTime != current_tick.m_flSimulationTime;
// Special Case that invalidates all the previous ticks
// if the new tick is too far away all the other ones get marked as not updated and thus invalid
if (current_tick.m_vecOrigin.AsVector2D().DistTo(previous_tick.m_vecOrigin.AsVector2D()) > 64)
{
for (auto &tick : *backtrack_ent)
{
// Older than current tick, mark invalid
if (tick.tickcount < current_tick.tickcount)
tick.has_updated = false;
}
}
// Get best tick for this ent
std::optional<BacktrackData> data = getBestTick(ent, getBestInternalTick);
// Check if actually the best tick we have in total
if (data && (!best_data || getBestInternalTick(ent, *data, best_data)))
{
best_data = data;
best_ent = ent;
}
}
if (best_data && best_ent)
SetBacktrackData(best_ent, *best_data);
}
void CreateMoveLate()
{
ApplyBacktrack();
}
#if ENABLE_VISUALS
// Drawing
void Draw()
{
if (!isBacktrackEnabled || !draw)
return;
if (CE_BAD(LOCAL_E))
return;
for (int i = 0; i <= g_IEngine->GetMaxClients(); i++)
{
auto data = getGoodTicks(i);
if (data.empty())
continue;
for (auto &tick : data)
{
auto hbpos = tick.hitboxes.at(head).center;
auto min = tick.hitboxes.at(head).min;
auto max = tick.hitboxes.at(head).max;
if (!hbpos.x && !hbpos.y && !hbpos.z)
continue;
Vector out;
if (draw::WorldToScreen(hbpos, out))
{
float size = 0.0f;
if (abs(max.x - min.x) > abs(max.y - min.y))
size = abs(max.x - min.x);
else
size = abs(max.y - min.y);
rgba_t draw_color = colors::green;
// Found our target tick
if (bt_ent && tick.tickcount == (*bt_data).tickcount && i == bt_ent->m_IDX)
draw_color = colors::red_s;
draw::Rectangle(out.x, out.y, size / 4, size / 4, draw_color);
}
}
}
}
#endif
// Resize our backtrackdata
void LevelInit()
{
backtrack_data.resize(g_IEngine->GetMaxClients());
}
// Reset things
void LevelShutdown()
{
lastincomingsequence = 0;
sequences.clear();
}
// Change Datagram data
void adjustPing(INetChannel *ch)
{
@ -362,207 +147,235 @@ void adjustPing(INetChannel *ch)
}
}
// Latency to add for backtrack
float getLatency()
// Move target entity to tick
void MoveToTick(BacktrackData data)
{
INetChannel *ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
// Track what actual latency we have
float real_latency = 0.0f;
CachedEntity *target = ENTITY(data.entidx);
// If we have a netchannel (just in case) set real latency to it
if (ch)
real_latency = ch->GetLatency(FLOW_OUTGOING) * 1000.0f;
// Set entity data to match the target data
re::C_BasePlayer::SetAbsOrigin(RAW_ENT(target), data.m_vecOrigin);
re::C_BasePlayer::GetEyeAngles(RAW_ENT(target)) = data.m_vecAngles;
// Fix the latency
float backtrack_latency = *latency - real_latency;
// Need to reconstruct a bunch of data
target->hitboxes.InvalidateCache();
// Clamp and apply rampup
backtrack_latency = latency_rampup * std::clamp(backtrack_latency, 0.0f, std::max(800.0f - real_latency, 0.0f));
// Mark all the hitboxes as valid so we don't recalc them and use the old data
// We already have
for (int i = hitbox_t::head; i <= foot_R; i++)
{
target->hitboxes.m_CacheValidationFlags[i] = true;
target->hitboxes.m_CacheInternal.at(i) = data.hitboxes.at(i);
}
return backtrack_latency;
// Sync animation properly
CE_FLOAT(target, netvar.m_flSimulationTime) = data.simtime;
CE_FLOAT(target, netvar.m_flCycle) = data.cycle;
CE_FLOAT(target, netvar.m_flAnimTime) = data.animtime;
CE_INT(target, netvar.m_nSequence) = data.sequence;
// Thanks to the epic doghook developers (mainly F1ssion and MrSteyk)
// I do not have to find all of these signatures and dig through ida
struct BoneCache;
typedef BoneCache *(*GetBoneCache_t)(unsigned);
typedef void (*BoneCacheUpdateBones_t)(BoneCache *, matrix3x4_t * bones, unsigned, float time);
static auto hitbox_bone_cache_handle_offset = *(unsigned *) (gSignatures.GetClientSignature("8B 86 ? ? ? ? 89 04 24 E8 ? ? ? ? 85 C0 89 C3 74 48") + 2);
static auto studio_get_bone_cache = (GetBoneCache_t) gSignatures.GetClientSignature("55 89 E5 56 53 BB ? ? ? ? 83 EC 50 C7 45 D8");
static auto bone_cache_update_bones = (BoneCacheUpdateBones_t) gSignatures.GetClientSignature("55 89 E5 57 31 FF 56 53 83 EC 1C 8B 5D 08 0F B7 53 10");
auto hitbox_bone_cache_handle = CE_VAR(target, hitbox_bone_cache_handle_offset, unsigned);
if (hitbox_bone_cache_handle)
{
BoneCache *bone_cache = studio_get_bone_cache(hitbox_bone_cache_handle);
if (bone_cache && !data.bones.empty())
bone_cache_update_bones(bone_cache, data.bones.data(), data.bones.size(), g_GlobalVars->curtime);
}
// Copy old bones to avoid really really slow setupbones logic
target->hitboxes.bones = data.bones;
target->hitboxes.bones_setup = true;
// We need to update their positions so the rays actually work, this requires some hacky stuff
uintptr_t collisionprop = (uintptr_t) RAW_ENT(target) + netvar.m_Collision;
typedef void (*UpdateParition_t)(uintptr_t prop);
static auto sig_update = gSignatures.GetClientSignature("55 89 E5 57 56 53 83 EC 3C 8B 5D ? 8B 43 ? 8B 90");
static UpdateParition_t UpdatePartition_fn = (UpdateParition_t) sig_update;
// Mark for update
int *entity_flags = (int *) ((uintptr_t) RAW_ENT(target) + 400);
// (EFL_DIRTY_SURROUNDING_COLLISION_BOUNDS | EFL_DIRTY_SPATIAL_PARTITION)
*entity_flags |= (1 << 14) | (1 << 15);
// Update
UpdatePartition_fn(collisionprop);
set_data = data;
}
// Update our sequences
void updateDatagram()
// Restore ent to original state
void RestoreEntity(int entidx)
{
INetChannel *ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
if (ch)
MoveToTick(bt_data[entidx - 1][0]);
// Undo tick setting
set_data = std::nullopt;
}
void CreateMoveEarly()
{
draw_positions.clear();
isBacktrackEnabled = isEnabled();
if (!isBacktrackEnabled)
{
int m_nInSequenceNr = ch->m_nInSequenceNr;
int instate = ch->m_nInReliableState;
if (m_nInSequenceNr > lastincomingsequence)
latency_rampup = 0.0f;
bt_data.clear();
return;
}
if (CE_GOOD(LOCAL_E))
updateDatagram();
else
sequences.clear();
latency_rampup += g_GlobalVars->interval_per_tick;
latency_rampup = std::min(1.0f, latency_rampup);
if ((int) bt_data.size() != g_IEngine->GetMaxClients())
bt_data.resize(g_IEngine->GetMaxClients());
for (int i = 1; i <= g_IEngine->GetMaxClients(); i++)
{
CachedEntity *ent = ENTITY(i);
int index = i - 1;
auto &ent_data = bt_data[index];
if (CE_BAD(ent) || !ent->m_bAlivePlayer() || HasCondition<TFCond_HalloweenGhostMode>(ent) || !ent->m_bEnemy())
{
lastincomingsequence = m_nInSequenceNr;
sequences.insert(sequences.begin(), CIncomingSequence(instate, m_nInSequenceNr, g_GlobalVars->realtime));
ent_data.clear();
continue;
}
BacktrackData data{};
data.entidx = i;
data.m_vecAngles = ent->m_vecAngle();
data.m_vecOrigin = ent->m_vecOrigin();
data.tickcount = current_user_cmd->tick_count;
data.simtime = CE_FLOAT(ent, netvar.m_flSimulationTime);
data.animtime = CE_FLOAT(ent, netvar.m_flAnimTime);
data.cycle = CE_FLOAT(ent, netvar.m_flCycle);
data.sequence = CE_INT(ent, netvar.m_nSequence);
ent->hitboxes.GetHitbox(0);
// Copy bones (for chams/glow)
data.bones = ent->hitboxes.bones;
for (int i = head; i <= foot_R; i++)
data.hitboxes.at(i) = *ent->hitboxes.GetHitbox(i);
ent_data.insert(ent_data.begin(), data);
if (ent_data.size() > MAX_BACKTRACK_TICKS)
ent_data.pop_back();
for (auto &tick : ent_data)
{
tick.in_range = isTickInRange(tick.tickcount);
if (!tick.in_range)
continue;
#if ENABLE_VISUALS
if (draw)
{
MoveToTick(tick);
Vector draw_pos = ent->hitboxes.GetHitbox(0)->center;
draw_positions.push_back(draw_pos);
RestoreEntity(i);
}
#endif
}
if (sequences.size() > 2048)
sequences.pop_back();
}
}
// Get How many ticks we should Store and use
int getTicks()
void CreateMoveLate()
{
float max_lat = getLatency() + 200.0f;
if (!isBacktrackEnabled)
return;
// Clamp
max_lat = std::min(1000.0f, max_lat);
red_position = std::nullopt;
// Get difference
int ticks = TIME_TO_TICKS(max_lat / 1000.0f);
return ticks;
};
// Bad player
if (CE_BAD(LOCAL_E) || HasCondition<TFCond_HalloweenGhostMode>(LOCAL_E) || !LOCAL_E->m_bAlivePlayer())
return;
void resetData(int entidx)
{
// Clear everything
backtrack_data.at(entidx - 1).reset();
}
bool isGoodTick(BacktrackData &tick)
{
// This tick hasn't updated since the last one, Entity might be dropping packets
if (!tick.has_updated)
return false;
// How big a difference is between the ping we fake and the tick passed?
int delta_ticks = current_tickcount - TIME_TO_TICKS(getLatency() / 1000.0f) - tick.tickcount;
// Difference may not be greater than 200ms, shave off a few ticks just to be safe.
if (fabsf(TICKS_TO_TIME(delta_ticks)) <= 0.2f - TICKS_TO_TIME(3))
return true;
return false;
}
// Read only vector of good ticks
std::vector<BacktrackData> getGoodTicks(int entidx)
{
std::vector<BacktrackData> to_return;
// Invalid
if (entidx <= 0 || (int) backtrack_data.size() < entidx || !backtrack_data.at(entidx - 1))
return to_return;
// Check all ticks
for (auto &tick : *backtrack_data.at(entidx - 1))
if (isGoodTick(tick))
to_return.push_back(tick);
// Sort so that oldest ticks come first
std::sort(to_return.begin(), to_return.end(), [](BacktrackData &a, BacktrackData &b) { return a.tickcount < b.tickcount; });
return to_return;
}
// This function is so other files can Easily get the best tick matching their criteria
std::optional<BacktrackData> getBestTick(CachedEntity *ent, std::function<bool(CachedEntity *ent, BacktrackData &data, std::optional<BacktrackData> &best_tick)> callback)
{
std::optional<BacktrackData> best_tick;
// No data recorded
if (ent->m_IDX <= 0 || backtrack_data.size() < ent->m_IDX || !backtrack_data.at(ent->m_IDX - 1))
return std::nullopt;
// Let the callback do the lifting
for (auto &tick : getGoodTicks(ent->m_IDX))
// Call the callback, and if we have a new best tick assign it
if (callback(ent, tick, best_tick))
best_tick = tick;
// Return best result
return best_tick;
}
// Default filter method. Checks for vischeck on Hitscan weapons.
bool defaultTickFilter(CachedEntity *ent, BacktrackData tick)
{
// Not hitscan, no vischeck needed
if (g_pLocalPlayer->weapon_mode != weapon_hitscan)
return true;
// Return visibility
return IsEntityVectorVisible(ent, tick.hitboxes.at(head).center, true, MASK_SHOT);
}
bool defaultEntFilter(CachedEntity *ent)
{
// Dormant
if (CE_BAD(ent))
return false;
// Should we even target them
if (!player_tools::shouldTarget(ent))
return false;
return true;
}
// Get Closest tick of a specific entity
std::optional<BacktrackData> getClosestEntTick(CachedEntity *ent, Vector vec, std::function<bool(CachedEntity *, BacktrackData)> tick_filter)
{
std::optional<BacktrackData> return_value;
// No entry
if (ent->m_IDX <= 0 || backtrack_data.size() < ent->m_IDX || !backtrack_data.at(ent->m_IDX - 1))
return return_value;
float distance = FLT_MAX;
// Go through all Good ticks
for (auto &tick : getGoodTicks(ent->m_IDX))
// No data set yet, try to get nearest to cursor
if (!set_data && !g_pLocalPlayer->bUseSilentAngles)
{
// Found Closer tick
if (tick.m_vecOrigin.DistTo(vec) < distance)
float cursor_distance = FLT_MAX;
for (auto &ent_data : bt_data)
{
// Does the tick pass the filter
if (tick_filter(ent, tick))
for (auto &tick_data : ent_data)
{
return_value = tick;
distance = tick.m_vecOrigin.DistTo(vec);
if (isTickInRange(tick_data.tickcount))
{
float distance = GetFov(LOCAL_E->m_vecAngle(), g_pLocalPlayer->v_Eye, tick_data.hitboxes.at(0).center);
if (distance < cursor_distance)
{
cursor_distance = distance;
set_data = tick_data;
}
}
}
}
}
return return_value;
// Still no data set, be sad and return
if (!set_data)
return;
current_user_cmd->tick_count = set_data->tickcount;
red_position = set_data->hitboxes.at(0).center;
RestoreEntity(set_data->entidx);
set_data = std::nullopt;
}
// Get Closest tick of any (enemy) entity, Second Parameter is to allow custom filters for entity criteria, third for ticks. We provide defaults for vischecks + melee for the second one
std::optional<std::pair<CachedEntity *, BacktrackData>> getClosestTick(Vector vec, std::function<bool(CachedEntity *)> ent_filter, std::function<bool(CachedEntity *, BacktrackData)> tick_filter)
void Shutdown()
{
float distance = FLT_MAX;
CachedEntity *best_ent = nullptr;
BacktrackData best_data;
std::optional<std::pair<CachedEntity *, BacktrackData>> return_val;
for (int i = 0; i <= g_IEngine->GetMaxClients(); i++)
{
CachedEntity *ent = ENTITY(i);
// These checks are always present
if (CE_INVALID(ent) || !ent->m_bAlivePlayer() || !ent->m_bEnemy())
continue;
// true = passes check
if (!ent_filter(ent))
continue;
auto closest_entdata = getClosestEntTick(ent, vec, tick_filter);
// Closer than the stuff we have
if (closest_entdata && (*closest_entdata).m_vecOrigin.DistTo(vec) <= distance)
{
distance = (*closest_entdata).m_vecOrigin.DistTo(vec);
best_data = *closest_entdata;
best_ent = ent;
}
}
if (best_ent)
return_val = std::pair<CachedEntity *, BacktrackData>(best_ent, best_data);
return return_val;
bt_data.clear();
sequences.clear();
lastincomingsequence = 0;
}
bool backtrackEnabled()
{
return isBacktrackEnabled;
}
#if ENABLE_VISUALS
void Draw()
{
if (!isBacktrackEnabled || !draw)
return;
if (!g_IEngine->IsInGame())
return;
Vector screen;
for (auto &pos : draw_positions)
{
if (draw::WorldToScreen(pos, screen))
draw::Rectangle(screen.x - 2, screen.y - 2, 4, 4, colors::green);
}
if (red_position && draw::WorldToScreen(*red_position, screen))
draw::Rectangle(screen.x - 2, screen.y - 2, 4, 4, colors::red);
}
#endif
static InitRoutine init([]() {
EC::Register(EC::CreateMove, CreateMove, "backtrack_cm", EC::early);
EC::Register(EC::CreateMoveWarp, CreateMove, "backtrack_cmw", EC::early);
EC::Register(EC::CreateMove, CreateMoveLate, "backtrack_cmlate", EC::very_late);
EC::Register(EC::CreateMoveWarp, CreateMoveLate, "backtrack_cmwlate", EC::very_late);
EC::Register(EC::CreateMove, CreateMoveEarly, "bt_update", EC::very_early);
EC::Register(EC::CreateMove, CreateMoveLate, "bt_createmove", EC::very_late);
EC::Register(EC::Shutdown, Shutdown, "bt_shutdown");
EC::Register(EC::LevelInit, Shutdown, "bt_shutdown");
#if ENABLE_VISUALS
EC::Register(EC::Draw, Draw, "backtrack_draw");
EC::Register(EC::Draw, Draw, "bt_draw");
#endif
EC::Register(EC::LevelShutdown, LevelShutdown, "backtrack_levelshutdown");
EC::Register(EC::LevelInit, LevelInit, "backtrack_levelinit");
LevelInit();
});
} // namespace hacks::tf2::backtrack

View File

@ -27,12 +27,17 @@ static Timer previous_entity_delay{};
// TODO: Refactor this jank
std::pair<CachedEntity *, Vector> FindBestEnt(bool teammate, bool Predict, bool zcheck, bool demoknight_mode, float range)
{
CachedEntity *bestent = nullptr;
float bestscr = FLT_MAX;
CachedEntity *bestent = nullptr;
float bestscr = FLT_MAX;
std::optional<backtrack::BacktrackData> best_data = std::nullopt;
Vector predicted{};
// Too long since we focused it
if (previous_entity_delay.check(100))
prevent = -1;
bool shouldBacktrack = backtrack::backtrackEnabled() && !backtrack::hasData();
for (int i = 0; i < 1; i++)
{
if (prevent != -1)
@ -51,22 +56,44 @@ std::pair<CachedEntity *, Vector> FindBestEnt(bool teammate, bool Predict, bool
target = ProjectilePrediction(ent, 1, sandwich_speed, grav, PlayerGravityMod(ent), initial_vel).second;
else
target = ent->hitboxes.GetHitbox(1)->center;
if (!hacks::tf2::backtrack::isBacktrackEnabled && !IsEntityVectorVisible(ent, target))
if (!shouldBacktrack && !IsEntityVectorVisible(ent, target))
continue;
if (zcheck && (ent->m_vecOrigin().z - LOCAL_E->m_vecOrigin().z) > 200.0f)
continue;
float scr = ent->m_flDistance();
if (hacks::tf2::backtrack::isBacktrackEnabled && demoknight_mode)
float scr = ent->m_flDistance();
std::optional<backtrack::BacktrackData> data = std::nullopt;
if (!shouldBacktrack && demoknight_mode)
{
auto data = hacks::tf2::backtrack::getClosestEntTick(ent, LOCAL_E->m_vecOrigin(), hacks::tf2::backtrack::defaultTickFilter);
// No entity
if (!data)
scr = FLT_MAX;
else
if (backtrack::bt_data.size() >= ent->m_IDX)
{
target = (*data).m_vecOrigin;
scr = (*data).m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin());
float tick_score = FLT_MAX;
for (auto &tick : backtrack::bt_data.at(ent->m_IDX - 1))
{
if (!tick.in_range)
continue;
float dist = tick.m_vecOrigin.DistTo(ent->m_vecOrigin());
if (dist < tick_score)
{
backtrack::MoveToTick(tick);
if (IsEntityVectorVisible(ent, ent->hitboxes.GetHitbox(1)->center))
{
data = tick;
tick_score = dist;
}
backtrack::RestoreEntity(ent->m_IDX);
}
}
// No entity
if (!data)
scr = FLT_MAX;
else
{
target = (*data).m_vecOrigin;
scr = (*data).m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin());
}
}
else
scr = FLT_MAX;
}
// Demoknight
if (demoknight_mode)
@ -91,6 +118,8 @@ std::pair<CachedEntity *, Vector> FindBestEnt(bool teammate, bool Predict, bool
if (bestent && predicted.z)
{
previous_entity_delay.update();
if (demoknight_mode && best_data)
backtrack::MoveToTick(*best_data);
return { bestent, predicted };
}
}
@ -109,22 +138,45 @@ std::pair<CachedEntity *, Vector> FindBestEnt(bool teammate, bool Predict, bool
target = ProjectilePrediction(ent, 1, sandwich_speed, grav, PlayerGravityMod(ent)).second;
else
target = ent->hitboxes.GetHitbox(1)->center;
if (!hacks::tf2::backtrack::isBacktrackEnabled && !IsEntityVectorVisible(ent, target))
if (!shouldBacktrack && !IsEntityVectorVisible(ent, target))
continue;
if (zcheck && (ent->m_vecOrigin().z - LOCAL_E->m_vecOrigin().z) > 200.0f)
continue;
float scr = ent->m_flDistance();
if (hacks::tf2::backtrack::isBacktrackEnabled && demoknight_mode)
float scr = ent->m_flDistance();
std::optional<backtrack::BacktrackData> data = std::nullopt;
if (!shouldBacktrack && demoknight_mode)
{
auto data = hacks::tf2::backtrack::getClosestEntTick(ent, LOCAL_E->m_vecOrigin(), hacks::tf2::backtrack::defaultTickFilter);
// No entity
if (!data)
scr = FLT_MAX;
else
if (backtrack::bt_data.size() >= ent->m_IDX)
{
target = (*data).m_vecOrigin;
scr = (*data).m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin());
float tick_score = FLT_MAX;
for (auto &tick : backtrack::bt_data.at(ent->m_IDX - 1))
{
if (!tick.in_range)
continue;
float dist = tick.m_vecOrigin.DistTo(ent->m_vecOrigin());
if (dist < tick_score)
{
backtrack::MoveToTick(tick);
if (IsEntityVectorVisible(ent, ent->hitboxes.GetHitbox(1)->center))
{
data = tick;
tick_score = dist;
}
backtrack::RestoreEntity(ent->m_IDX);
}
}
// No entity
if (!data)
scr = FLT_MAX;
else
{
target = (*data).m_vecOrigin;
scr = (*data).m_vecOrigin.DistTo(LOCAL_E->m_vecOrigin());
}
}
else
scr = FLT_MAX;
}
// Demoknight
if (demoknight_mode)
@ -144,8 +196,12 @@ std::pair<CachedEntity *, Vector> FindBestEnt(bool teammate, bool Predict, bool
predicted = target;
bestscr = scr;
prevent = ent->m_IDX;
if (shouldBacktrack)
best_data = data;
}
}
if (demoknight_mode && best_data)
backtrack::MoveToTick(*best_data);
return { bestent, predicted };
}
static float slow_change_dist_y{};

View File

@ -8,7 +8,6 @@
#include <hacks/Trigger.hpp>
#include "common.hpp"
#include <hacks/Backtrack.hpp>
#include <PlayerTools.hpp>
#include <settings/Bool.hpp>
#include "Backtrack.hpp"
@ -40,46 +39,6 @@ float target_time = 0.0f;
int last_hb_traced = 0;
Vector forward;
// Filters for backtrack
bool tick_filter(CachedEntity *entity, hacks::tf2::backtrack::BacktrackData tick)
{
// Check if it intersects any hitbox
int num_hitboxes = 18;
// Only need head hitbox
if (HeadPreferable(entity))
num_hitboxes = 1;
for (int i = 0; i < num_hitboxes; i++)
{
auto hitbox = tick.hitboxes.at(i);
auto min = hitbox.min;
auto max = hitbox.max;
// Get the min and max for the hitbox
Vector minz(fminf(min.x, max.x), fminf(min.y, max.y), fminf(min.z, max.z));
Vector maxz(fmaxf(min.x, max.x), fmaxf(min.y, max.y), fmaxf(min.z, max.z));
// Shrink the hitbox here
Vector size = maxz - minz;
// Use entire hitbox if accuracy unset
Vector smod = size * 0.05f * (*accuracy > 0 ? *accuracy : 20);
// Save the changes to the vectors
minz += smod;
maxz -= smod;
// Trace and test if it hits the smaller hitbox, if it hits we can return true
Vector hit;
// Found a good one
if (CheckLineBox(minz, maxz, g_pLocalPlayer->v_Eye, forward, hit))
{
// Is tick visible
if (IsVectorVisible(g_pLocalPlayer->v_Eye, hitbox.center))
return true;
}
}
return false;
}
// The main function of the triggerbot
void CreateMove()
{
@ -95,39 +54,51 @@ void CreateMove()
// Get an ent in front of the player
CachedEntity *ent = nullptr;
std::optional<hacks::tf2::backtrack::BacktrackData> bt_data;
if (!hacks::tf2::backtrack::isBacktrackEnabled)
ent = FindEntInSight(EffectiveTargetingRange());
// Backtrack, use custom filter to check if tick is in crosshair
bool state_good = false;
bool shouldBacktrack = tf2::backtrack::backtrackEnabled() && !hacks::tf2::backtrack::hasData();
if (shouldBacktrack)
{
float target_range = EffectiveTargetingRange();
for (auto &ent_data : tf2::backtrack::bt_data)
{
if (state_good)
break;
for (auto &tick_data : ent_data)
{
if (!tick_data.in_range)
continue;
tf2::backtrack::MoveToTick(tick_data);
ent = FindEntInSight(target_range);
// Restore the data
tf2::backtrack::RestoreEntity(tick_data.entidx);
if (ent)
{
state_good = IsTargetStateGood(ent, tick_data);
if (state_good)
break;
else
tf2::backtrack::RestoreEntity(tick_data.entidx);
}
}
}
}
else
{
// Set up forward Vector
forward = GetForwardVector(EffectiveTargetingRange(), LOCAL_E);
// Call closest tick with our Tick filter func
auto closest_data = hacks::tf2::backtrack::getClosestTick(g_pLocalPlayer->v_Eye, hacks::tf2::backtrack::defaultEntFilter, tick_filter);
// No results, try to grab a building
if (!closest_data)
{
ent = FindEntInSight(EffectiveTargetingRange(), true);
}
else
{
// Assign entity
ent = (*closest_data).first;
bt_data = (*closest_data).second;
hacks::tf2::backtrack::SetBacktrackData(ent, *bt_data);
}
ent = FindEntInSight(EffectiveTargetingRange());
}
// Check if dormant or null to prevent crashes
if (CE_BAD(ent))
return;
if (!shouldBacktrack)
state_good = IsTargetStateGood(ent, std::nullopt);
// Determine whether the triggerbot should shoot, then act accordingly
if (IsTargetStateGood(ent, bt_data ? &*bt_data : nullptr))
if (state_good)
{
target_time = backup_time;
if (delay)
@ -235,8 +206,10 @@ bool ShouldShoot()
}
// A second check to determine whether a target is good enough to be aimed at
bool IsTargetStateGood(CachedEntity *entity, hacks::tf2::backtrack::BacktrackData *tick)
bool IsTargetStateGood(CachedEntity *entity, std::optional<tf2::backtrack::BacktrackData> bt_data)
{
if (bt_data)
tf2::backtrack::MoveToTick(*bt_data);
// Check for Players
if (entity->m_Type() == ENTITY_PLAYER)
{
@ -283,46 +256,41 @@ bool IsTargetStateGood(CachedEntity *entity, hacks::tf2::backtrack::BacktrackDat
return false;
}
// Backtrack did these in the tick check
if (!tick)
// Head hitbox detection
if (HeadPreferable(entity))
{
// Head hitbox detection
if (HeadPreferable(entity))
if (last_hb_traced != hitbox_t::head)
return false;
}
// If usersettings tell us to use accuracy improvements and the cached
// hitbox isnt null, then we check if it hits here
if (*accuracy)
{
// Get a cached hitbox for the one traced
hitbox_cache::CachedHitbox *hb = entity->hitboxes.GetHitbox(last_hb_traced);
// Check for null
if (hb)
{
if (last_hb_traced != hitbox_t::head)
// Get the min and max for the hitbox
Vector minz(fminf(hb->min.x, hb->max.x), fminf(hb->min.y, hb->max.y), fminf(hb->min.z, hb->max.z));
Vector maxz(fmaxf(hb->min.x, hb->max.x), fmaxf(hb->min.y, hb->max.y), fmaxf(hb->min.z, hb->max.z));
// Shrink the hitbox here
Vector size = maxz - minz;
Vector smod = size * 0.05f * *accuracy;
// Save the changes to the vectors
minz += smod;
maxz -= smod;
// Trace and test if it hits the smaller hitbox, if it fails
// we
// return false
Vector hit;
if (!CheckLineBox(minz, maxz, g_pLocalPlayer->v_Eye, forward, hit))
return false;
}
// If usersettings tell us to use accuracy improvements and the cached
// hitbox isnt null, then we check if it hits here
if (*accuracy)
{
// Get a cached hitbox for the one traced
hitbox_cache::CachedHitbox *hb = entity->hitboxes.GetHitbox(last_hb_traced);
// Check for null
if (hb)
{
// Get the min and max for the hitbox
Vector minz(fminf(hb->min.x, hb->max.x), fminf(hb->min.y, hb->max.y), fminf(hb->min.z, hb->max.z));
Vector maxz(fmaxf(hb->min.x, hb->max.x), fmaxf(hb->min.y, hb->max.y), fmaxf(hb->min.z, hb->max.z));
// Shrink the hitbox here
Vector size = maxz - minz;
Vector smod = size * 0.05f * *accuracy;
// Save the changes to the vectors
minz += smod;
maxz -= smod;
// Trace and test if it hits the smaller hitbox, if it fails
// we
// return false
Vector hit;
if (!CheckLineBox(minz, maxz, g_pLocalPlayer->v_Eye, forward, hit))
return false;
}
}
}
// Target passed the tests so return true
return true;
@ -407,7 +375,7 @@ CachedEntity *FindEntInSight(float range, bool no_players)
g_ITrace->TraceRay(ray, 0x4200400B, &trace::filter_default, &trace);
// Return an ent if that is what we hit
if (trace.m_pEnt)
if (trace.DidHit() && trace.m_pEnt && (IClientEntity *) trace.m_pEnt != g_IEntityList->GetClientEntity(0))
{
last_hb_traced = trace.hitbox;
CachedEntity *ent = ENTITY(((IClientEntity *) trace.m_pEnt)->entindex());

View File

@ -532,7 +532,7 @@ void ApplyChams(ChamColors colors, bool recurse, bool render_original, bool over
DEFINE_HOOKED_METHOD(DrawModelExecute, void, IVModelRender *this_, const DrawModelState_t &state, const ModelRenderInfo_t &info, matrix3x4_t *bone)
{
if (!isHackActive() || effect_glow::g_EffectGlow.drawing || chams_attachment_drawing || (*clean_screenshots && g_IEngine->IsTakingScreenshot()) || CE_BAD(LOCAL_E) || (!enable && !no_hats && !no_arms && !blend_zoom && !arms_chams && !local_weapon_chams && !(hacks::tf2::backtrack::chams && hacks::tf2::backtrack::isBacktrackEnabled)))
if (!isHackActive() || effect_glow::g_EffectGlow.drawing || chams_attachment_drawing || (*clean_screenshots && g_IEngine->IsTakingScreenshot()) || CE_BAD(LOCAL_E) || (!enable && !no_hats && !no_arms && !blend_zoom && !arms_chams && !local_weapon_chams /*&& !(hacks::tf2::backtrack::chams && hacks::tf2::backtrack::isBacktrackEnabled)*/))
return original::DrawModelExecute(this_, state, info, bone);
PROF_SECTION(DrawModelExecute);
@ -770,35 +770,43 @@ DEFINE_HOOKED_METHOD(DrawModelExecute, void, IVModelRender *this_, const DrawMod
}
}
// Backtrack chams
using namespace hacks::tf2;
if (backtrack::chams && backtrack::isBacktrackEnabled)
namespace bt = hacks::tf2::backtrack;
if (bt::chams && bt::backtrackEnabled())
{
// TODO: Allow for a fade between the entity's color and a specified color, it would look cool but i'm lazy
if (ent->m_bAlivePlayer())
if (ent->m_bAlivePlayer() && (int) bt::bt_data.size() >= info.entity_index > 0)
{
// Get ticks
auto good_ticks = backtrack::getGoodTicks(info.entity_index);
auto ticks = bt::bt_data.at(info.entity_index - 1);
std::vector<bt::BacktrackData> good_ticks;
for (auto &tick : ticks)
{
if (tick.in_range)
good_ticks.push_back(tick);
}
if (!good_ticks.empty())
{
// Setup chams according to user settings
ChamColors backtrack_colors;
backtrack_colors.rgba = *backtrack::chams_color;
backtrack_colors.rgba_overlay = *backtrack::chams_color_overlay;
backtrack_colors.envmap_r = *backtrack::chams_envmap_tint_r;
backtrack_colors.envmap_g = *backtrack::chams_envmap_tint_g;
backtrack_colors.envmap_b = *backtrack::chams_envmap_tint_b;
backtrack_colors.rgba = *bt::chams_color;
backtrack_colors.rgba_overlay = *bt::chams_color_overlay;
backtrack_colors.envmap_r = *bt::chams_envmap_tint_r;
backtrack_colors.envmap_g = *bt::chams_envmap_tint_g;
backtrack_colors.envmap_b = *bt::chams_envmap_tint_b;
for (unsigned i = 0; i <= (unsigned) std::max(*backtrack::chams_ticks, 1); i++)
for (unsigned i = 0; i <= (unsigned) std::max(*bt::chams_ticks, 1); i++)
{
// Can't draw more than we have
if (i >= good_ticks.size())
break;
if (!good_ticks[i].bones.empty())
ApplyChams(backtrack_colors, false, false, *backtrack::chams_overlay, false, *backtrack::chams_wireframe, false, entity, this_, state, info, &good_ticks[i].bones[0]);
ApplyChams(backtrack_colors, false, false, *bt::chams_overlay, false, *bt::chams_wireframe, false, entity, this_, state, info, good_ticks[i].bones.data());
}
}
}
}
// Reset it!
g_IVModelRender->ForcedMaterialOverride(nullptr);
g_IVRenderView->SetColorModulation(original_color);
@ -818,7 +826,7 @@ DEFINE_HOOKED_METHOD(DrawModelExecute, void, IVModelRender *this_, const DrawMod
return;
}
// Don't do it when we are trying to enforce backtrack chams
if (!hacks::tf2::backtrack::isDrawing)
return original::DrawModelExecute(this_, state, info, bone);
// if (!hacks::tf2::backtrack::isDrawing)
return original::DrawModelExecute(this_, state, info, bone);
} // namespace hooked_methods
} // namespace hooked_methods