diff --git a/build_names b/build_names index 04cd2951..5e199666 100755 --- a/build_names +++ b/build_names @@ -23,4 +23,3 @@ libtinfo.so.5.9.4.2 libudev.so.1.6.6.4.7 libwrap.so.0.7.6.1.5 libz.so.1.2.1.1.2.4 - diff --git a/data/menu/nullifiedcat/catbot.xml b/data/menu/nullifiedcat/catbot.xml index a8e78ecf..9ecace83 100755 --- a/data/menu/nullifiedcat/catbot.xml +++ b/data/menu/nullifiedcat/catbot.xml @@ -61,6 +61,7 @@ + diff --git a/include/core/netvars.hpp b/include/core/netvars.hpp index cee7796b..bf3170fc 100755 --- a/include/core/netvars.hpp +++ b/include/core/netvars.hpp @@ -63,6 +63,7 @@ public: offset_t hActiveWeapon; offset_t flChargedDamage; offset_t iUpgradeLevel; + offset_t m_hBuilder; offset_t iPipeType; offset_t iBuildingHealth; offset_t iBuildingMaxHealth; diff --git a/src/core/netvars.cpp b/src/core/netvars.cpp index 9ac29553..d94f25b9 100644 --- a/src/core/netvars.cpp +++ b/src/core/netvars.cpp @@ -120,6 +120,8 @@ void NetVars::Init() "DT_TFSniperRifle", "SniperRifleLocalData", "m_flChargedDamage"); this->iUpgradeLevel = gNetvars.get_offset("DT_BaseObject", "m_iUpgradeLevel"); + this->m_hBuilder = + gNetvars.get_offset("DT_BaseObject", "m_hBuilder"); this->iPipeType = gNetvars.get_offset("DT_TFProjectile_Pipebomb", "m_iType"); this->iBuildingHealth = diff --git a/src/hacks/NavBot.cpp b/src/hacks/NavBot.cpp index 7a642aaa..46a22fc5 100644 --- a/src/hacks/NavBot.cpp +++ b/src/hacks/NavBot.cpp @@ -12,6 +12,7 @@ namespace hacks::tf2::NavBot static settings::Bool enable("navbot.enable", "false"); static settings::Bool spy_mode("navbot.spy-mode", "false"); static settings::Bool heavy_mode("navbot.heavy-mode", "false"); +static settings::Bool engi_mode("navbot.engi-mode", "false"); static settings::Bool primary_only("navbot.primary-only", "true"); static settings::Bool enable_fb{ "navbot.medbot", "false" }; @@ -157,16 +158,27 @@ CachedEntity *NearestEnemy() Timer cdr{}; Timer cd2{}; Timer cd3{}; +Timer engi_spot_cd{}; +std::vector preferred_sniper_spots; std::vector sniper_spots; +std::vector nest_spots; void Init() { sniper_spots.clear(); + preferred_sniper_spots.clear(); + nest_spots.clear(); for (auto area : nav::areas) + { + if (area.m_attributeFlags & NAV_MESH_NO_HOSTAGES) + preferred_sniper_spots.push_back(area.m_center); + if (area.m_attributeFlags & NAV_MESH_RUN) + nest_spots.push_back(area.m_center); for (auto hide : area.m_hidingSpots) if (hide.IsGoodSniperSpot() || hide.IsIdealSniperSpot() || hide.IsExposed()) sniper_spots.push_back(hide.m_pos); - logging::Info("%d", sniper_spots.size()); + } + logging::Info("Sniper spots: %d, Manual Sniper Spots: %d, Sentry Spots: %d", sniper_spots.size(), preferred_sniper_spots.size(), nest_spots.size()); } void initonce() { @@ -175,6 +187,7 @@ void initonce() cdr.update(); cd2.update(); cd3.update(); + engi_spot_cd.update(); return; } @@ -191,7 +204,7 @@ void UpdateSlot() { int slot = re::C_BaseCombatWeapon::GetSlot(weapon); int newslot = 1; - if (spy_mode) + if (spy_mode || engi_mode) newslot = 3; if (slot != newslot - 1) g_IEngine->ClientCmd_Unrestricted( @@ -199,6 +212,76 @@ void UpdateSlot() } } } +enum BuildingNum +{ + DISPENSER = 0, + TELEPORT_ENT, + SENTRY, + TELEPORT_EXT, +}; +std::vector GetBuildings() + { + float bestscr = FLT_MAX; + std::vector buildings; + for (int i = 0; i < HIGHEST_ENTITY; i++) + { + CachedEntity *ent = ENTITY(i); + if (CE_BAD(ent)) + continue; + if (ent->m_Type() != ENTITY_BUILDING) + continue; + if ((CE_INT(ent, netvar.m_hBuilder) & 0xFFF) != g_pLocalPlayer->entity_idx) + continue; + if (ent->m_vecOrigin().DistTo(LOCAL_E->m_vecOrigin()) < bestscr) + { + buildings.push_back(i); + bestscr = ent->m_vecOrigin().DistTo(LOCAL_E->m_vecOrigin()); + } + } + return buildings; +} +int cost[4] = {100, 50, 130, 50}; +int GetBestBuilding(int metal) +{ + bool hasSentry, hasDispenser; + if (!GetBuildings().empty()) + for (auto build : GetBuildings()) + { + CachedEntity *building = ENTITY(build); + if (building->m_iClassID() == CL_CLASS(CObjectSentrygun)) + hasSentry = true; + if (building->m_iClassID() == CL_CLASS(CObjectDispenser)) + hasDispenser = true; + } + if (metal >= cost[SENTRY] && !hasSentry) + return SENTRY; + else if (metal >= cost[DISPENSER] && !hasDispenser) + return DISPENSER; + if (hasSentry && hasDispenser) + return 3; + return -1; +} +int GetClosestBuilding() +{ + float bestscr = FLT_MAX; + int BestBuilding = -1; + for (int i = 0; i < HIGHEST_ENTITY; i++) + { + CachedEntity *ent = ENTITY(i); + if (CE_BAD(ent)) + continue; + if (ent->m_Type() != ENTITY_BUILDING) + continue; + if ((CE_INT(ent, netvar.m_hBuilder) & 0xFFF) != g_pLocalPlayer->entity_idx) + continue; + if (ent->m_vecOrigin().DistTo(LOCAL_E->m_vecOrigin()) < bestscr) + { + BestBuilding = i; + bestscr = ent->m_vecOrigin().DistTo(LOCAL_E->m_vecOrigin()); + } + } + return BestBuilding; +} int follow_target = 0; void CreateMove() { @@ -212,42 +295,40 @@ void CreateMove() { CachedEntity *med = nearestHealth(); if (CE_GOOD(med)) - { nav::NavTo(med->m_vecOrigin(), true, true, 7); - } } if (HasLowAmmo() && cdr.test_and_set(5000)) { CachedEntity *ammo = nearestAmmo(); if (CE_GOOD(ammo)) - { nav::NavTo(ammo->m_vecOrigin(), true, true, 6); - } } if ((!HasLowHealth() && nav::priority == 7) || (!HasLowAmmo() && nav::priority == 6)) nav::clearInstructions(); if (enable) { - if (!nav::ReadyForCommands && !spy_mode && !heavy_mode) + if (!nav::ReadyForCommands && !spy_mode && !heavy_mode && !engi_mode) cd3.update(); - bool isready = (spy_mode || heavy_mode) ? 1 : nav::ReadyForCommands; - int waittime = (spy_mode || heavy_mode) ? 100 : 5000; + bool isready = (spy_mode || heavy_mode || engi_mode) ? 1 : nav::ReadyForCommands; + int waittime = (spy_mode || heavy_mode || engi_mode) ? 100 : 5000; if (isready && cd3.test_and_set(waittime)) { - if (!spy_mode && !heavy_mode) + if (!spy_mode && !heavy_mode && !engi_mode) { cd3.update(); Vector random_spot; if (cd2.test_and_set(5000)) Init(); - if (!sniper_spots.size()) + if (!sniper_spots.size() && !preferred_sniper_spots.size()) return; - int rng = rand() % sniper_spots.size(); - random_spot = sniper_spots.at(rng); + auto snip_spot = preferred_sniper_spots.size() ? preferred_sniper_spots : sniper_spots; + int rng = rand() % snip_spot.size(); + random_spot = snip_spot.at(rng); if (random_spot.z) nav::NavTo(random_spot, true, true); + return; } - else + else if (!engi_mode) { CachedEntity *tar = NearestEnemy(); if (CE_BAD(tar) && last_tar == -1 && nav::ReadyForCommands) @@ -255,10 +336,11 @@ void CreateMove() Vector random_spot; if (cd2.test_and_set(5000)) Init(); - if (!sniper_spots.size()) + if (!sniper_spots.size() && !preferred_sniper_spots.size()) return; - int rng = rand() % sniper_spots.size(); - random_spot = sniper_spots.at(rng); + auto snip_spot = preferred_sniper_spots.size() ? preferred_sniper_spots : sniper_spots; + int rng = rand() % snip_spot.size(); + random_spot = snip_spot.at(rng); if (random_spot.z) nav::NavTo(random_spot, false); return; @@ -279,6 +361,120 @@ void CreateMove() } } } + // Engi Mode + else + { + // Init things + if (cd2.test_and_set(5000)) + Init(); + // If No spots set just return + if (nest_spots.empty()) + return; + // Get Metal (offset of MAX metal is +8 and current metal +12) + int metal = CE_INT(LOCAL_E, netvar.m_iAmmo + 12); + // Best spot storage + static Vector best_spot{}; + // Get Best spot based on distance + if (engi_spot_cd.test_and_set(10000)) + { + float bestscr = FLT_MAX; + for (auto spot : nest_spots) + { + if (spot.DistTo(LOCAL_E->m_vecOrigin()) < bestscr) + { + bestscr = spot.DistTo(LOCAL_E->m_vecOrigin()); + best_spot = spot; + } + } + } + if (nav::priority == 1) + { + CachedEntity *ammo = nearestAmmo(); + if (CE_GOOD(ammo)) + { + nav::NavTo(ammo->m_vecOrigin(), false, true); + return; + } + } + // If Near The best spot and ready for commands + if (best_spot.DistTo(LOCAL_E->m_vecOrigin()) < 300.0f && (nav::ReadyForCommands || nav::priority == 1)) + { + // Get the closest Building + int ClosestBuilding = GetClosestBuilding(); + // If A Building was found + if (ClosestBuilding != -1) + { + CachedEntity *ent = ENTITY(ClosestBuilding); + // If we have more than 25 metal and the building is damaged or not fully upgraded hit it with the wrench + if (metal > 25 && (CE_INT(ent, netvar.iUpgradeLevel) < 3 || CE_INT(ent, netvar.iBuildingHealth) < CE_INT(ent, netvar.iBuildingMaxHealth))) + { + auto collide = RAW_ENT(ent)->GetCollideable(); + Vector min = ent->m_vecOrigin() + collide->OBBMins(); + Vector max = ent->m_vecOrigin() + collide->OBBMaxs(); + // Distance check + if (min.DistTo(g_pLocalPlayer->v_Eye) > re::C_TFWeaponBaseMelee::GetSwingRange(RAW_ENT(LOCAL_W)) && max.DistTo(g_pLocalPlayer->v_Eye) > re::C_TFWeaponBaseMelee::GetSwingRange(RAW_ENT(LOCAL_W)) && GetBuildingPosition(ent).DistTo(g_pLocalPlayer->v_Eye) > re::C_TFWeaponBaseMelee::GetSwingRange(RAW_ENT(LOCAL_W))) + { + float min = min.DistTo(g_pLocalPlayer->v_Eye); + float max = max.DistTo(g_pLocalPlayer->v_Eye); + float center = GetBuildingPosition(ent).DistTo(g_pLocalPlayer->v_Eye); + float closest = fminf(min, fminf(max, center)); + nav::NavTo(closest, false, false); + } + Vector tr = GetBuildingPosition(ent) - g_pLocalPlayer->v_Eye; + Vector angles; + VectorAngles(tr, angles); + // Clamping is important + fClampAngle(angles); + current_user_cmd->viewangles = angles; + current_user_cmd->buttons |= IN_ATTACK; + g_pLocalPlayer->bUseSilentAngles = true; + return; + } + } + // Get A building, Sentry > Dispenser + int tobuild = GetBestBuilding(metal); + // If not enough metal then Find ammo + if (tobuild == -1) + { + CachedEntity *ammo = nearestAmmo(); + if (CE_GOOD(ammo)) + { + nav::NavTo(ammo->m_vecOrigin(), false, true); + return; + } + // Ammo is dormant, go and find it! + else if (sniper_spots.size() && nav::ReadyForCommands) + { + Vector random_spot; + if (cd2.test_and_set(5000)) + Init(); + if (!sniper_spots.size() && !preferred_sniper_spots.size()) + return; + auto snip_spot = preferred_sniper_spots.size() ? preferred_sniper_spots : sniper_spots; + int rng = rand() % snip_spot.size(); + random_spot = snip_spot.at(rng); + if (random_spot.z) + nav::NavTo(random_spot, false, false, 1); + return; + } + } + // Build Building + else if (tobuild != 3) + { + // Make ENgi look slightly down + current_user_cmd->viewangles.x = 20.0f; + // Build buildings in a 360° angle around player + current_user_cmd->viewangles.y = 90.0f * (tobuild + 1); + // Build new one + g_IEngine->ServerCmd(format("build ", tobuild).c_str(), true); + current_user_cmd->buttons |= IN_ATTACK; + g_pLocalPlayer->bUseSilentAngles = true; + } + } + // If not near best spot then navigate to it + else if (nav::ReadyForCommands) + nav::NavTo(best_spot, false, true); + } } } else if (enable_fb)