From 71f1cb2189b353ff7a56255d20682ec7d84b0cdb Mon Sep 17 00:00:00 2001 From: BenCat07 Date: Mon, 22 Mar 2021 18:36:15 +0100 Subject: [PATCH] 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 --- include/core/netvars.hpp | 3 + include/enums.hpp | 5 +- include/hacks/Backtrack.hpp | 71 +-- include/hacks/Trigger.hpp | 7 +- include/reclasses/C_BaseEntity.hpp | 4 +- src/core/netvars.cpp | 2 + src/crits.cpp | 31 +- src/entityhitboxcache.cpp | 2 +- src/hacks/Aimbot.cpp | 352 ++++++------- src/hacks/AutoBackstab.cpp | 60 +-- src/hacks/Backtrack.cpp | 703 ++++++++++---------------- src/hacks/MiscAimbot.cpp | 100 +++- src/hacks/Trigger.cpp | 172 +++---- src/hooks/visual/DrawModelExecute.cpp | 36 +- 14 files changed, 669 insertions(+), 879 deletions(-) diff --git a/include/core/netvars.hpp b/include/core/netvars.hpp index 63c4d23f..91ed83a8 100644 --- a/include/core/netvars.hpp +++ b/include/core/netvars.hpp @@ -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; diff --git a/include/enums.hpp b/include/enums.hpp index 7a0e9894..503b9248 100755 --- a/include/enums.hpp +++ b/include/enums.hpp @@ -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 }; diff --git a/include/hacks/Backtrack.hpp b/include/hacks/Backtrack.hpp index 50fa0226..71fc406a 100644 --- a/include/hacks/Backtrack.hpp +++ b/include/hacks/Backtrack.hpp @@ -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 hitboxes{}; - Vector m_vecOrigin{}; - Vector m_vecAngles{}; - - Vector m_vecMins{}; - Vector m_vecMaxs{}; - - float m_flSimulationTime{}; - bool has_updated{}; + bool in_range{}; std::vector bones{}; + + Vector m_vecOrigin{}; + Vector m_vecAngles{}; + float simtime; + float animtime; + float cycle; + int sequence; + std::array 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 getData(); + +extern std::vector> 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 getGoodTicks(int); -std::optional getBestTick(CachedEntity *, std::function &)>); -std::optional getClosestEntTick(CachedEntity *, Vector, std::function); -std::optional> getClosestTick(Vector, std::function, std::function); - -void SetBacktrackData(CachedEntity *ent, BacktrackData); } // namespace hacks::tf2::backtrack diff --git a/include/hacks/Trigger.hpp b/include/hacks/Trigger.hpp index 3634eeaf..d0d8075a 100644 --- a/include/hacks/Trigger.hpp +++ b/include/hacks/Trigger.hpp @@ -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 bt_data = std::nullopt); CachedEntity *FindEntInSight(float range, bool no_players = false); bool HeadPreferable(CachedEntity *target); bool UpdateAimkey(); diff --git a/include/reclasses/C_BaseEntity.hpp b/include/reclasses/C_BaseEntity.hpp index 4f4d1e29..7b14f092 100644 --- a/include/reclasses/C_BaseEntity.hpp +++ b/include/reclasses/C_BaseEntity.hpp @@ -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); } diff --git a/src/core/netvars.cpp b/src/core/netvars.cpp index 3a9dd399..fe7a98cf 100644 --- a/src/core/netvars.cpp +++ b/src/core/netvars.cpp @@ -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()) { diff --git a/src/crits.cpp b/src/crits.cpp index 51de6570..391be64f 100644 --- a/src/crits.cpp +++ b/src/crits.cpp @@ -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 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 diff --git a/src/entityhitboxcache.cpp b/src/entityhitboxcache.cpp index 1791ca25..dfd6bf1d 100644 --- a/src/entityhitboxcache.cpp +++ b/src/entityhitboxcache.cpp @@ -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) { diff --git a/src/hacks/Aimbot.cpp b/src/hacks/Aimbot.cpp index 698a71ae..044e0c42 100644 --- a/src/hacks/Aimbot.cpp +++ b/src/hacks/Aimbot.cpp @@ -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 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 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 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 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 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 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; diff --git a/src/hacks/AutoBackstab.cpp b/src/hacks/AutoBackstab.cpp index 3afba1d6..f671cbee 100644 --- a/src/hacks/AutoBackstab.cpp +++ b/src/hacks/AutoBackstab.cpp @@ -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 &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; diff --git a/src/hacks/Backtrack.cpp b/src/hacks/Backtrack.cpp index 53f99517..87cd0ba4 100644 --- a/src/hacks/Backtrack.cpp +++ b/src/hacks/Backtrack.cpp @@ -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 sequences; -static int current_tickcount; -static std::vector>> 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 bt_data; - -static bool isEnabled(); -static float getLatency(); -static int getTicks(); -static bool getBestInternalTick(CachedEntity *, BacktrackData &, std::optional &); -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 set_data; +bool hasData() { - if (!isBacktrackEnabled) - return; - if (bt_ent && bt_data) + return (bool) set_data; +} + +std::optional getData() +{ + return set_data; +} + +static int lastincomingsequence = 0; +std::deque sequences; +static float latency_rampup = 0.0f; + +// Store draw positions in CreateMove, also store the selected tick one too +static std::vector draw_positions; +static std::optional red_position; + +std::vector> 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 &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 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); - - int current_index = current_user_cmd->tick_count % getTicks(); - - // Our current tick - auto ¤t_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 *) ¤t_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 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(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(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 getGoodTicks(int entidx) -{ - std::vector 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 getBestTick(CachedEntity *ent, std::function &best_tick)> callback) -{ - std::optional 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 getClosestEntTick(CachedEntity *ent, Vector vec, std::function tick_filter) -{ - std::optional 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> getClosestTick(Vector vec, std::function ent_filter, std::function tick_filter) +void Shutdown() { - float distance = FLT_MAX; - CachedEntity *best_ent = nullptr; - BacktrackData best_data; - - std::optional> 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(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 diff --git a/src/hacks/MiscAimbot.cpp b/src/hacks/MiscAimbot.cpp index 52858fd8..97ba9c9b 100644 --- a/src/hacks/MiscAimbot.cpp +++ b/src/hacks/MiscAimbot.cpp @@ -27,12 +27,17 @@ static Timer previous_entity_delay{}; // TODO: Refactor this jank std::pair 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 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 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 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 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 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 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 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{}; diff --git a/src/hacks/Trigger.cpp b/src/hacks/Trigger.cpp index 4fe0622f..a14f848d 100644 --- a/src/hacks/Trigger.cpp +++ b/src/hacks/Trigger.cpp @@ -8,7 +8,6 @@ #include #include "common.hpp" -#include #include #include #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 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 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()); diff --git a/src/hooks/visual/DrawModelExecute.cpp b/src/hooks/visual/DrawModelExecute.cpp index b2f7d8c6..02db38fc 100644 --- a/src/hooks/visual/DrawModelExecute.cpp +++ b/src/hooks/visual/DrawModelExecute.cpp @@ -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 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