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