/* * Backtrack.cpp * * Created on: May 15, 2018 * Author: bencat07 */ #include "common.hpp" #include "hacks/Aimbot.hpp" #include "hacks/Backtrack.hpp" #include #include #include "PlayerTools.hpp" #include namespace hacks::shared::backtrack { static settings::Boolean draw_bt{ "backtrack.draw", "false" }; static settings::Boolean draw_skeleton{ "backtrack.draw-skeleton", "false" }; static settings::Float mindistance{ "backtrack.min-distance", "60" }; static settings::Int slots{ "backtrack.slots", "0" }; settings::Boolean enable{ "backtrack.enable", "false" }; settings::Boolean backtrack_chams_glow{ "backtrack.chams_glow", "true" }; settings::Int latency{ "backtrack.latency", "0" }; void EmptyBacktrackData(BacktrackData &i); std::pair getBestEntBestTick(); bool shouldBacktrack(); BacktrackData headPositions[33][66]{}; int lastincomingsequencenumber = 0; bool isBacktrackEnabled = false; bool Vischeck_Success = false; circular_buf sequences{ 2048 }; void UpdateIncomingSequences() { INetChannel *ch = (INetChannel *) g_IEngine->GetNetChannelInfo(); if (ch) { int m_nInSequenceNr = ch->m_nInSequenceNr; int instate = ch->m_nInReliableState; if (m_nInSequenceNr > lastincomingsequencenumber) { lastincomingsequencenumber = m_nInSequenceNr; sequences.push_front(CIncomingSequence(instate, m_nInSequenceNr, g_GlobalVars->realtime)); } if (sequences.size() > 2048) sequences.pop_back(); } } void AddLatencyToNetchan(INetChannel *ch) { if (!isBacktrackEnabled) return; float Latency = *latency; if (Latency > 1000.0f) Latency = 800.0f; Latency -= getRealLatency(); if (Latency < 0.0f) Latency = 0.0f; for (auto &seq : sequences) { if (g_GlobalVars->realtime - seq.curtime > Latency / 1000.0f) { ch->m_nInReliableState = seq.inreliablestate; ch->m_nInSequenceNr = seq.sequencenr; break; } } } void Init() { for (int i = 0; i < 33; i++) for (int j = 0; j < 66; j++) headPositions[i][j] = {}; BestTick = iBestTarget = -1; } int BestTick = -1; int iBestTarget = -1; bool istickvalid[32][66]{}; bool istickinvalid[32][66]{}; static void Run() { if (!shouldBacktrack()) { isBacktrackEnabled = false; return; } UpdateIncomingSequences(); isBacktrackEnabled = true; if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer() || CE_BAD(LOCAL_W)) return; if (g_Settings.bInvalid) return; if (!current_user_cmd) return; for (auto &a : istickvalid) for (auto &b : a) b = false; for (auto &a : istickinvalid) for (auto &b : a) b = false; CUserCmd *cmd = current_user_cmd; float bestFov = 99999; float prev_distance = 9999; std::pair bestEntBestTick = getBestEntBestTick(); BestTick = bestEntBestTick.second; iBestTarget = bestEntBestTick.first; // Fill backtrack data (stored in headPositions) { PROF_SECTION(cm_bt_ent_loop) for (int i = 1; i <= g_IEngine->GetMaxClients(); i++) { CachedEntity *pEntity = ENTITY(i); if (CE_BAD(pEntity) || !pEntity->m_bAlivePlayer()) { for (BacktrackData &btd : headPositions[i]) btd.simtime = FLT_MAX; continue; } if (!pEntity->m_bEnemy()) continue; if (pEntity->m_Type() != ENTITY_PLAYER) continue; if (!pEntity->hitboxes.GetHitbox(0)) continue; if (HasCondition(pEntity)) continue; if (!*bSendPackets) headPositions[i][cmd->command_number % getTicks()] = {}; else { auto &hbd = headPositions[i][cmd->command_number % getTicks()]; float _viewangles = CE_VECTOR(pEntity, netvar.m_angEyeAngles).y; hbd.viewangles = (_viewangles > 180) ? _viewangles - 360 : _viewangles; hbd.simtime = CE_FLOAT(pEntity, netvar.m_flSimulationTime); hbd.entorigin = pEntity->InternalEntity()->GetAbsOrigin(); hbd.tickcount = cmd->tick_count; pEntity->hitboxes.InvalidateCache(); for (size_t i = 0; i < 18; i++) { hbd.hitboxes[i].center = pEntity->hitboxes.GetHitbox(i)->center; hbd.hitboxes[i].min = pEntity->hitboxes.GetHitbox(i)->min; hbd.hitboxes[i].max = pEntity->hitboxes.GetHitbox(i)->max; } hbd.collidable.min = RAW_ENT(pEntity)->GetCollideable()->OBBMins() + hbd.entorigin; hbd.collidable.max = RAW_ENT(pEntity)->GetCollideable()->OBBMaxs() + hbd.entorigin; hbd.collidable.center = (hbd.collidable.min + hbd.collidable.max) / 2; memcpy((void *) hbd.bones, (void *) pEntity->hitboxes.bones, sizeof(matrix3x4_t) * 128); } } } if (iBestTarget != -1 && CanShoot()) { CachedEntity *tar = ENTITY(iBestTarget); if (CE_GOOD(tar)) { if (cmd->buttons & IN_ATTACK) { // ok just in case if (CE_BAD(tar)) return; auto i = headPositions[iBestTarget][BestTick]; cmd->tick_count = i.tickcount; Vector &angles = NET_VECTOR(RAW_ENT(tar), netvar.m_angEyeAngles); float &simtime = NET_FLOAT(RAW_ENT(tar), netvar.m_flSimulationTime); angles.y = i.viewangles; simtime = i.simtime; } } } } CatCommand print_bones("debug_print_bones", "debug print bone id + name", []() { if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer()) return; // Get player model const model_t *model = RAW_ENT(LOCAL_E)->GetModel(); if (not model) return; // Get Studio models (for bones) studiohdr_t *hdr = g_IModelInfo->GetStudiomodel(model); if (not hdr) return; // Get the name of the bones for (int i = 0; i < hdr->numbones; i++) logging::Info(format(std::string(hdr->pBone(i)->pszName()), " ", i).c_str()); }); static std::vector bones_leg_r = { 17, 16, 15 }; static std::vector bones_leg_l = { 14, 13, 12 }; static std::vector bones_bottom = { 15, 1, 12 }; static std::vector bones_spine = { 1, 2, 3, 4, 5, 0 }; static std::vector bones_arm_r = { 9, 10, 11 }; static std::vector bones_arm_l = { 6, 7, 8 }; static std::vector bones_up = { 9, 5, 6 }; #if ENABLE_VISUALS void DrawBone(std::vector hitbox, std::array hitboxes) { for (int i = 0; i < hitbox.size() - 1; i++) { Vector bone1 = hitboxes.at(hitbox.at(i)).center; Vector bone2 = hitboxes.at(hitbox.at(i + 1)).center; Vector draw_position1, draw_position2; if (draw::WorldToScreen(bone1, draw_position1) && draw::WorldToScreen(bone2, draw_position2)) draw::Line(draw_position1.x, draw_position1.y, draw_position2.x - draw_position1.x, draw_position2.y - draw_position1.y, colors::white, 1.0f); } } #endif static void Draw() { #if ENABLE_VISUALS if (!isBacktrackEnabled) return; // :b:ones for non drawable ents if (draw_skeleton) for (int i = 0; i <= g_IEngine->GetMaxClients(); i++) { CachedEntity *ent = ENTITY(i); if (CE_BAD(ent) || !ent->m_bAlivePlayer() || i == g_IEngine->GetLocalPlayer()) continue; auto head_pos = headPositions[i]; // Usable vector instead of ptr to c style array, also used to filter valid and invalid ticks std::vector usable; for (int i = 0; i < 66; i++) { if (ValidTick(head_pos[i], ent)) usable.push_back(head_pos[i]); } // Crash much? if (usable.size()) { DrawBone(bones_leg_l, usable[0].hitboxes); DrawBone(bones_leg_r, usable[0].hitboxes); DrawBone(bones_bottom, usable[0].hitboxes); DrawBone(bones_spine, usable[0].hitboxes); DrawBone(bones_arm_l, usable[0].hitboxes); DrawBone(bones_arm_r, usable[0].hitboxes); DrawBone(bones_up, usable[0].hitboxes); } } if (!draw_bt) return; for (int i = 0; i <= g_IEngine->GetMaxClients(); i++) { CachedEntity *ent = ENTITY(i); if (CE_BAD(ent)) continue; for (int j = 0; j < getTicks(); j++) { if (!ValidTick(headPositions[i][j], ent)) continue; auto hbpos = headPositions[i][j].hitboxes.at(head).center; auto min = headPositions[i][j].hitboxes.at(head).min; auto max = headPositions[i][j].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); if (i == iBestTarget && j == BestTick) draw::Rectangle(out.x, out.y, size / 2, size / 2, colors::red); else draw::Rectangle(out.x, out.y, size / 4, size / 4, colors::green); } } } #endif } // Internal only, use isBacktrackEnabled var instead bool shouldBacktrack() { if (!*enable) return false; CachedEntity *wep = g_pLocalPlayer->weapon(); if (CE_BAD(wep)) return false; if (*slots == 0) return true; int slot = re::C_BaseCombatWeapon::GetSlot(RAW_ENT(wep)); switch ((int) slots) { case 1: if (slot == 0) return true; break; case 2: if (slot == 1) return true; break; case 3: if (slot == 2) return true; break; case 4: if (slot == 0 || slot == 1) return true; break; case 5: if (slot == 0 || slot == 2) return true; break; case 6: if (slot == 1 || slot == 2) return true; break; } return false; } float getRealLatency() { auto ch = (INetChannel *) g_IEngine->GetNetChannelInfo(); if (!ch) return 0.0f; float Latency = ch->GetLatency(FLOW_OUTGOING); static auto cl_updaterate = g_ICvar->FindVar("cl_updaterate"); if (cl_updaterate && cl_updaterate->GetFloat() > 0.001f) Latency += -0.5f / cl_updaterate->GetFloat(); else if (!cl_updaterate) cl_updaterate = g_ICvar->FindVar("cl_updaterate"); return MAX(0.0f, Latency) * 1000.f; } float getLatency() { auto ch = (INetChannel *) g_IEngine->GetNetChannelInfo(); if (!ch) return 0; float Latency = *latency; if (Latency > 1000.0f) Latency = 800.0f; Latency -= getRealLatency(); if (Latency < 0.0f) Latency = 0.0f; return Latency; } int getTicks() { return max(min(int(getLatency() / 200.0f * 13.0f) + 12, 65), 12); } bool ValidTick(BacktrackData &i, CachedEntity *ent) { if (!(fabsf(NET_FLOAT(RAW_ENT(ent), netvar.m_flSimulationTime) * 1000.0f - getLatency() - i.simtime * 1000.0f) < 200.0f)) return false; return true; } void EmptyBacktrackData(BacktrackData &i) { i = {}; } // This func is internal only std::pair getBestEntBestTick() { int bestEnt = -1; int bestTick = -1; bool vischeck_priority = false; Vischeck_Success = false; if (GetWeaponMode() == weapon_melee) { float bestDist = 9999.0f; for (int i = 0; i <= g_IEngine->GetMaxClients(); i++) { CachedEntity *tar = ENTITY(i); if (CE_GOOD(tar)) { if (tar != LOCAL_E && tar->m_bEnemy()) { for (int j = 0; j < getTicks(); j++) { if (ValidTick(headPositions[i][j], ENTITY(i))) { float dist = headPositions[i][j].hitboxes.at(spine_3).center.DistTo(g_pLocalPlayer->v_Eye); if (dist < bestDist) { bestEnt = i; bestTick = j; bestDist = dist; Vischeck_Success = true; vischeck_priority = true; } } } } } } } else { float bestFov = 180.0f; for (int i = 0; i <= g_IEngine->GetMaxClients(); i++) { CachedEntity *tar = ENTITY(i); if (CE_GOOD(tar)) { if (tar != LOCAL_E && tar->m_bEnemy()) { for (int j = 0; j < getTicks(); j++) { if (ValidTick(headPositions[i][j], tar)) { float FOVDistance = GetFov(g_pLocalPlayer->v_OrigViewangles, g_pLocalPlayer->v_Eye, headPositions[i][j].hitboxes.at(head).center); if (FOVDistance > bestFov && vischeck_priority) continue; bool Vischeck_suceeded = IsVectorVisible(g_pLocalPlayer->v_Eye, headPositions[i][j].hitboxes.at(0).center, true); if (FOVDistance < bestFov || (Vischeck_suceeded && !vischeck_priority)) { bestEnt = i; bestTick = j; bestFov = FOVDistance; if (Vischeck_suceeded) { Vischeck_Success = true; vischeck_priority = true; } } } } } } } } return std::make_pair(bestEnt, bestTick); } static InitRoutine EC([]() { EC::Register(EC::LevelInit, Init, "INIT_Backtrack", EC::average); EC::Register(EC::CreateMove, Run, "CM_Backtrack", EC::early); #if ENABLE_VISUALS EC::Register(EC::Draw, Draw, "DRAW_Backtrack", EC::average); #endif }); static CatCommand debug_flowout("debug_flowout", "test", []() { auto ch = (INetChannel *) g_IEngine->GetNetChannelInfo(); logging::Info("Out Avg: %f In Avg: %f Out current: %f In Current: %f", 1000.0f * ch->GetAvgLatency(FLOW_OUTGOING), 1000.0f * ch->GetAvgLatency(FLOW_INCOMING), 1000.0f * ch->GetLatency(FLOW_OUTGOING), 1000.0f * ch->GetLatency(FLOW_INCOMING)); }); static CatCommand debug_richpresence("debug_presence", "Debug stuff", []() { g_ISteamFriends->SetRichPresence("steam_display", "#TF_RichPresence_State_PlayingGeneric"); g_ISteamFriends->SetRichPresence("currentmap", "Cathooking"); }); } // namespace hacks::shared::backtrack