diff --git a/include/MiscTemporary.hpp b/include/MiscTemporary.hpp index 56aa9065..62714149 100755 --- a/include/MiscTemporary.hpp +++ b/include/MiscTemporary.hpp @@ -41,6 +41,10 @@ extern int stored_buttons; #if ENABLE_VISUALS extern bool freecam_is_toggled; #endif +namespace hacks::tf2::misc_aimbot +{ +bool ShouldHitBuilding(CachedEntity *ent); +} namespace hooked_methods { void sendAchievementKv(int value); diff --git a/include/hacks/Misc.hpp b/include/hacks/Misc.hpp index 63e9ae3f..2c6ec11c 100644 --- a/include/hacks/Misc.hpp +++ b/include/hacks/Misc.hpp @@ -18,7 +18,7 @@ void CreateMove(); #if ENABLE_VISUALS void DrawText(); #endif - +int getCarriedBuilding(); extern int last_number; extern float last_bucket; diff --git a/include/hacks/NavBot.hpp b/include/hacks/NavBot.hpp index b5bc557c..dfdb140d 100644 --- a/include/hacks/NavBot.hpp +++ b/include/hacks/NavBot.hpp @@ -6,6 +6,7 @@ namespace hacks::tf2::NavBot bool init(bool first_cm); namespace task { + enum task : uint8_t { none = 0, @@ -15,28 +16,49 @@ enum task : uint8_t ammo, dispenser, followbot, - outofbounds + outofbounds, + engineer }; + +enum engineer_task : uint8_t +{ + nothing = 0, + // Build a new building + goto_build_spot, + // Go to an existing building + goto_building, + build_building, + // Originally were going to be added seperately, but we already have autorepair and upgrade seperately + // upgrade_building, + // repair_building, + upgradeorrepair_building, + // Well time to just run at people and gun them (Rip old name: YEEEEEEEEEEEEEEHAW) + staynear_engineer +}; + +extern engineer_task current_engineer_task; + struct Task { task id; int priority; - Task(task _id) + Task(task id) { - id = _id; - priority = _id == none ? 0 : 5; + this->id = id; + priority = id == none ? 0 : 5; } - Task(task _id, int _priority) + Task(task id, int priority) { - id = _id; - priority = _priority; + this->id = id; + this->priority = priority; } operator task() { return id; } }; -constexpr std::array blocking_tasks{ followbot, outofbounds }; + +constexpr std::array blocking_tasks{ followbot, outofbounds, engineer }; extern Task current_task; } // namespace task struct bot_class_config diff --git a/src/hacks/Misc.cpp b/src/hacks/Misc.cpp index f1a409b7..c223d62f 100644 --- a/src/hacks/Misc.cpp +++ b/src/hacks/Misc.cpp @@ -109,7 +109,7 @@ static ConVar *teammatesPushaway{ nullptr }; int getCarriedBuilding() { - if (CE_BYTE(LOCAL_E, netvar.m_bCarryingObject)) + if (CE_INT(LOCAL_E, netvar.m_bCarryingObject)) return HandleToIDX(CE_INT(LOCAL_E, netvar.m_hCarriedObject)); for (int i = 1; i < MAX_ENTITIES; i++) { @@ -118,7 +118,7 @@ int getCarriedBuilding() continue; if (HandleToIDX(CE_INT(ent, netvar.m_hBuilder)) != LOCAL_E->m_IDX) continue; - if (!CE_BYTE(ent, netvar.m_bPlacing)) + if (!CE_INT(ent, netvar.m_bPlacing)) continue; return i; } diff --git a/src/hacks/MiscAimbot.cpp b/src/hacks/MiscAimbot.cpp index 1a054aac..3abd8319 100644 --- a/src/hacks/MiscAimbot.cpp +++ b/src/hacks/MiscAimbot.cpp @@ -290,7 +290,7 @@ static settings::Int autoupgrade_sentry_level("autoupgrade.sentry.level", "3"); static settings::Int autoupgrade_dispenser_level("autoupgrade.dispenser.level", "3"); static settings::Int autoupgrade_teleport_level("autoupgrade.teleport.level", "2"); -static bool ShouldHitBuilding(CachedEntity *ent) +bool ShouldHitBuilding(CachedEntity *ent) { if (!autoupgrade_enabled && !autorepair_enabled) return false; @@ -386,7 +386,7 @@ static void BuildingAimbot() int cur_ammo = CE_INT(LOCAL_E, netvar.m_iAmmo + 12); float wrench_range = re::C_TFWeaponBaseMelee::GetSwingRange(RAW_ENT(LOCAL_W)); // Center is further away than actual hit range for buildings, so add some more - wrench_range += (float) 50.f; + wrench_range += 50.f; if (cur_ammo == 0) return; diff --git a/src/hacks/NavBot.cpp b/src/hacks/NavBot.cpp index 50b603e1..a53f837b 100644 --- a/src/hacks/NavBot.cpp +++ b/src/hacks/NavBot.cpp @@ -5,6 +5,8 @@ #include "Aimbot.hpp" #include "FollowBot.hpp" #include "soundcache.hpp" +#include "Misc.hpp" +#include "MiscTemporary.hpp" namespace hacks::tf2::NavBot { @@ -13,6 +15,7 @@ static settings::Boolean enabled("navbot.enabled", "false"); static settings::Boolean stay_near("navbot.stay-near", "true"); static settings::Boolean heavy_mode("navbot.other-mode", "false"); static settings::Boolean spy_mode("navbot.spy-mode", "false"); +static settings::Boolean engineer_mode("navbot.engineer-mode", "false"); static settings::Boolean get_health("navbot.get-health-and-ammo", "true"); static settings::Float jump_distance("navbot.autojump.trigger-distance", "300"); static settings::Boolean autojump("navbot.autojump.enabled", "false"); @@ -22,30 +25,82 @@ static settings::Int spy_ignore_time("navbot.spy-ignore-time", "5000"); // -Forward declarations- bool init(bool first_cm); static bool navToSniperSpot(); +static bool navToBuildingSpot(); static bool stayNear(); -static bool getDispenserHealthAndAmmo(); -static bool getHealthAndAmmo(); +static bool stayNearEngineer(); +static bool getDispenserHealthAndAmmo(int metal = -1); +static bool getHealthAndAmmo(int metal = -1); static void autoJump(); static void updateSlot(); +static void update_building_spots(); +static bool engineerLogic(); static std::pair getNearestPlayerDistance(bool vischeck = true); +using task::current_engineer_task; using task::current_task; // -Variables- static std::vector> sniper_spots; +static std::vector> building_spots; +static std::vector blacklisted_build_spots; +// Our Buildings. We need this so we can remove them on object_destroyed. +static std::vector local_buildings; +// Needed for blacklisting +static CNavArea *current_build_area; // How long should the bot wait until pathing again? static Timer wait_until_path{}; +// Engineer version of above +static Timer wait_until_path_engineer{}; // Time before following target cloaked spy again static std::array spy_cloak{}; // Don't spam spy path thanks static Timer spy_path{}; +// Big wait between updating Engineer building spots +static Timer engineer_update{}; +// Recheck Building Area +static Timer engineer_recheck{}; +// Timer for resetting Build attempts +static Timer build_timer{}; +// Timer for wait between rotation and checking if we can place +static Timer rotation_timer{}; +// Uses to check how long until we should resend the "build" command +static Timer build_command_timer{}; +// Dispenser Nav cooldown +static Timer dispenser_nav_timer{}; +// Last Yaw used for building +static float build_current_rotation = -180.0f; +// How many times have we tried to place? +static int build_attempts = 0; +// Enum for Building types +enum Building +{ + None = -1, + Dispenser = 0, + TP_Entrace, + Sentry, + TP_Exit +}; + +// Successfully built? (Unknown == Bot is still trying to build and isn't sure if it will work or not yet) +enum success_build +{ + Failure = 0, + Unknown, + Success +}; + // What is the bot currently doing namespace task { -Task current_task = task::none; -} +Task current_task = task::none; +engineer_task current_engineer_task = engineer_task::nothing; +} // namespace task + constexpr bot_class_config DIST_SPY{ 10.0f, 50.0f, 1000.0f }; constexpr bot_class_config DIST_OTHER{ 100.0f, 200.0f, 300.0f }; constexpr bot_class_config DIST_SNIPER{ 1000.0f, 1500.0f, 3000.0f }; +constexpr bot_class_config DIST_ENGINEER{ 600.0f, 1000.0f, 2500.0f }; +// Gunslinger Engineers really don't cate at all +constexpr bot_class_config DIST_GUNSLINGER_ENGINEER{ 100.0f, 300.0f, 500.0f }; static void CreateMove() { @@ -65,17 +120,41 @@ static void CreateMove() autoJump(); if (primary_only) updateSlot(); + if (engineer_mode) + { + if (LOCAL_E && CE_GOOD(LOCAL_E)) + { + for (int i = g_IEngine->GetMaxClients() + 1; i < MAX_ENTITIES; i++) + { + CachedEntity *ent = ENTITY(i); + if (!ent || CE_BAD(ent) || ent->m_bEnemy() || !ent->m_bAlivePlayer()) + continue; + if (HandleToIDX(CE_INT(ent, netvar.m_hBuilder)) != LOCAL_E->m_IDX) + continue; + if (std::find(local_buildings.begin(), local_buildings.end(), ent) == local_buildings.end()) + local_buildings.push_back(ent); + } + update_building_spots(); + } + } if (get_health) { - if (getDispenserHealthAndAmmo()) + int metal = 0; + if (engineer_mode) + metal = CE_INT(LOCAL_E, netvar.m_iAmmo + 12); + if ((dispenser_nav_timer.test_and_set(1000) && getDispenserHealthAndAmmo(metal))) return; - if (getHealthAndAmmo()) + if (getHealthAndAmmo(metal)) return; } + + if (engineer_mode) + if (engineerLogic()) + return; + if (blocking) return; - // Spy can just walk into the enemy if (spy_mode) { @@ -127,6 +206,8 @@ bool init(bool first_cm) return false; if (!inited) { + blacklisted_build_spots.clear(); + local_buildings.clear(); sniper_spots.clear(); // Add all sniper spots to vector for (auto &area : nav::navfile->m_areas) @@ -140,6 +221,128 @@ bool init(bool first_cm) return true; } +struct area_struct +{ + // The Area + CNavArea *area; + // Distance away from enemies + float min_distance; + // Valid enemies to area + std::vector enemy_list; + + CNavArea *first() + { + return area; + } + float second() + { + return min_distance; + } + std::vector third() + { + return enemy_list; + } +}; + +void update_building_spots() +{ + if (engineer_update.test_and_set(10000)) + { + building_spots.clear(); + // Store in here to reduce operations + std::vector enemy_positions; + // Stores valid areas, the float is the minimum distance away from enemies, needed for sorting later + std::vector areas; + + for (int i = 0; i <= g_IEngine->GetMaxClients(); i++) + { + CachedEntity *ent = ENTITY(i); + // Grab only Enemies and only if they are in soundcache + if (!ent || CE_INVALID(ent) || !ent->m_bAlivePlayer() || !ent->m_bEnemy()) + continue; + if (ent->m_vecDormantOrigin()) + enemy_positions.push_back(*ent->m_vecDormantOrigin()); + } + auto config = &DIST_ENGINEER; + if (HasWeapon(LOCAL_E, 142)) + config = &DIST_GUNSLINGER_ENGINEER; + for (auto &area : nav::navfile->m_areas) + { + // Blacklisted for building + if (std::find(blacklisted_build_spots.begin(), blacklisted_build_spots.end(), &area) != blacklisted_build_spots.end()) + continue; + + // These positions we should vischeck + std::vector vischeck_positions; + + // Minimum distance the area was away from enemies + float min_dist_away = FLT_MAX; + + // Area Center + auto area_pos = area.m_center; + // Don't want to instantly hit the floor + area_pos.z += 42.0f; + + // Found enemy below min/above max range away from area + bool enemy_found = false; + bool out_of_reach = true; + + for (auto &pos : enemy_positions) + { + auto dist = area_pos.DistTo(pos); + if (dist < config->min) + { + enemy_found = true; + break; + } + // Found someone within min and max range + if (dist < config->max) + { + out_of_reach = false; + // Should vischeck this one + vischeck_positions.push_back(&pos); + if (dist < min_dist_away) + min_dist_away = dist; + } + } + // Too close/Too far away + if (enemy_found || out_of_reach) + continue; + + // Area is valid (Distance wise) + areas.push_back({ &area, min_dist_away, vischeck_positions }); + } + + // Sort, be as close to preferred as possible + std::sort(areas.begin(), areas.end(), [&](area_struct a, area_struct b) { return std::abs(a.second() - config->preferred) < std::abs(b.second() - config->preferred); }); + + // Still need to do vischeck stuff + for (auto &area : areas) + { + // Is the enemy able to see the area? + bool can_see_area = false; + // Area Center + auto area_pos = area.first()->m_center; + // Don't want to instantly hit the floor + area_pos.z += 42.0f; + // Loop all valid enemies + for (auto pos : area.third()) + { + if (IsVectorVisible(area_pos, *pos)) + { + can_see_area = true; + break; + } + } + // Someone can see the area. Abort! (Gunslinger Engineer does not care) + if (can_see_area && !HasWeapon(LOCAL_E, 142)) + continue; + // All good! + building_spots.push_back(std::pair(area.first(), area.first()->m_center)); + } + } +} + static bool navToSniperSpot() { // Don't path if you already have commands. But also don't error out. @@ -165,6 +368,315 @@ static bool navToSniperSpot() return false; } +static bool navToBuildingSpot() +{ + // Don't path if you already have commands. But also don't error out. + if (!nav::ReadyForCommands || (current_task != task::engineer && current_task != task::none)) + return true; + // Wait a bit before pathing again + if (!wait_until_path_engineer.test_and_set(2000)) + return false; + // Max 10 attempts + for (int attempts = 0; attempts < 10 && attempts < building_spots.size(); attempts++) + { + // Get a Building spot + auto &area = building_spots[attempts]; + // Check if spot is considered safe (no sentry, no sticky) + if (!nav::isSafe(area.first)) + continue; + // Try to nav there + if (nav::navTo(area.second, 1, true, true, false)) + { + current_task = { task::engineer, 1 }; + current_build_area = area.first; + current_engineer_task = task::engineer_task::goto_build_spot; + return true; + } + } + return false; +} + +static Building selectBuilding() +{ + int metal = CE_INT(LOCAL_E, netvar.m_iAmmo + 12); + int metal_sentry = 130; + + // We have a mini sentry, costs less + if (HasWeapon(LOCAL_E, 142)) + metal_sentry = 100; + + // Do we already have these? + bool sentry_built = false; + bool dispenser_built = false; + // Loop all buildings + for (auto &building : local_buildings) + { + if (building->m_iClassID() == CL_CLASS(CObjectSentrygun)) + sentry_built = true; + else if (building->m_iClassID() == CL_CLASS(CObjectDispenser)) + dispenser_built = true; + } + + if (metal >= metal_sentry && !sentry_built) + return Sentry; + else if (metal >= 100 && !dispenser_built) + return Dispenser; + return None; +} + +static success_build buildBuilding() +{ + int metal = CE_INT(LOCAL_E, netvar.m_iAmmo + 12); + // Out of Metal + if (metal < 100) + return Failure; + // Last building + static Building last_building = selectBuilding(); + // Get best building to build right now + Building building = selectBuilding(); + // Reset Rotation on these conditions + if (building != last_building || build_timer.check(1000)) + { + // We changed the target building, means it was successful! + if (!build_timer.check(1000)) + return Success; + + // No building + if (building == None) + return Failure; + + build_attempts = 0; + build_current_rotation = -180.0f; + build_timer.update(); + } + // No building + if (building == None) + return Failure; + + last_building = building; + build_timer.update(); + if (rotation_timer.test_and_set(300)) + { + // Look slightly downwards for building process + current_user_cmd->viewangles.x = 20.0f; + // Set Yaw + current_user_cmd->viewangles.y = build_current_rotation; + // Rotate + build_current_rotation += 20.0f; + build_attempts++; + // Put building in hand if not already + if (hacks::shared::misc::getCarriedBuilding() == -1 && build_command_timer.test_and_set(50)) + g_IEngine->ClientCmd_Unrestricted(format("build ", building).c_str()); + } + else if (rotation_timer.check(200)) + { + if (hacks::shared::misc::getCarriedBuilding() != -1) + { + int carried_building = hacks::shared::misc::getCarriedBuilding(); + // It works! Place building + if (CE_INT(ENTITY(carried_building), netvar.m_bCanPlace)) + current_user_cmd->buttons |= IN_ATTACK; + } + } + // Bad area + if (build_attempts >= 14) + { + blacklisted_build_spots.push_back(current_build_area); + return Failure; + } + return Unknown; +} + +static bool navToBuilding(CachedEntity *target = nullptr) +{ + if (local_buildings.size()) + { + int priority = 5; + if (current_engineer_task == task::staynear_engineer) + priority = 7; + // Just grab target and nav there + if (target) + if (nav::navTo(target->m_vecOrigin(), priority)) + { + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::goto_building; + return true; + } + // Nav to random building + for (auto &building : local_buildings) + { + if (nav::navTo(building->m_vecOrigin(), priority)) + { + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::goto_building; + return true; + } + } + } + return false; +} + +static bool engineerLogic() +{ + std::vector new_building_list; + for (auto &building : local_buildings) + if (CE_VALID(building) && !CE_INT(building, netvar.m_bPlacing)) + new_building_list.push_back(building); + local_buildings = new_building_list; + + // Overwrites and Not yet running engineer task + if ((current_task != task::engineer || current_engineer_task == task::engineer_task::nothing || current_engineer_task == task::engineer_task::staynear_engineer) && current_task != task::health && current_task != task::ammo) + { + // Already have a building + if (local_buildings.size()) + { + int metal = CE_INT(LOCAL_E, netvar.m_iAmmo + 12); + if (metal) + for (auto &building : local_buildings) + // Hey hit the building thanks (gunslinger engineer shouldn't care) + if (hacks::tf2::misc_aimbot::ShouldHitBuilding(building) && !HasWeapon(LOCAL_E, 142)) + { + if (navToBuilding(building)) + return true; + } + + // Let's terrify some people (gunslinger engineer) + if (HasWeapon(LOCAL_E, 142)) + stayNearEngineer(); + + else if (selectBuilding() != None) + { + // If we're near our buildings and have the metal, build another one + for (auto &building : local_buildings) + if (building->m_vecOrigin().DistTo(LOCAL_E->m_vecOrigin()) <= 300.0f) + { + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::build_building; + return true; + } + // We're too far away, go to building + else if (navToBuilding()) + return true; + } + // If it's metal we're missing, get some metal + else if (metal < 100) + { + if ((dispenser_nav_timer.test_and_set(1000) && getDispenserHealthAndAmmo(metal)) || getHealthAndAmmo(metal)) + return true; + } + // Else just Roam around the map and kill people + else if (stayNearEngineer()) + return true; + } + // Nav to a Building spot + else if (navToBuildingSpot()) + { + engineer_recheck.update(); + return false; + } + } + // Metal + int metal = CE_INT(LOCAL_E, netvar.m_iAmmo + 12); + if ((dispenser_nav_timer.test_and_set(1000) && getDispenserHealthAndAmmo(metal)) || getHealthAndAmmo(metal)) + return true; + switch (current_engineer_task) + { + // Upgrade/repair + case (task::engineer_task::upgradeorrepair_building): + { + if (metal) + if (local_buildings.size()) + for (auto &building : local_buildings) + { + if (building->m_vecOrigin().DistTo(LOCAL_E->m_vecOrigin()) <= 300.0f) + { + if (hacks::tf2::misc_aimbot::ShouldHitBuilding(building)) + break; + } + } + + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::nothing; + } + // Going to existing building + case (task::engineer_task::goto_building): + { + if (nav::ReadyForCommands) + { + bool found = false; + if (local_buildings.size()) + for (auto &building : local_buildings) + { + if (building->m_vecOrigin().DistTo(LOCAL_E->m_vecOrigin()) <= 300.0f) + { + if (metal && hacks::tf2::misc_aimbot::ShouldHitBuilding(building)) + { + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::upgradeorrepair_building; + } + if (current_engineer_task != task::engineer_task::upgradeorrepair_building) + { + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::nothing; + } + found = true; + } + } + if (found) + break; + + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::nothing; + } + break; + } + // Going to spot to build + case (task::engineer_task::goto_build_spot): + { + // We Arrived, time to (try) to build! + if (nav::ReadyForCommands) + { + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::build_building; + } + else if (engineer_recheck.test_and_set(15000)) + { + if (navToBuildingSpot()) + { + engineer_recheck.update(); + return true; + } + } + break; + } + // Build building + case (task::engineer_task::build_building): + { + auto status = buildBuilding(); + // Failed, Get a new Task + if (status == Failure) + { + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::nothing; + } + else if (status == Success) + { + + current_task = { task::engineer, 4 }; + current_engineer_task = task::engineer_task::nothing; + return true; + } + // Still building + else if (status == Unknown) + return true; + break; + } + default: + break; + } + return false; +} + static std::pair getNearestPlayerDistance(bool vischeck) { float distance = FLT_MAX; @@ -204,7 +716,7 @@ static bool isValidNearPosition(Vector vec, Vector target, const bot_class_confi } // Returns true if began pathing -static bool stayNearPlayer(CachedEntity *&ent, const bot_class_config &config, CNavArea *&result) +static bool stayNearPlayer(CachedEntity *&ent, const bot_class_config &config, CNavArea *&result, bool engineer = false) { if (!CE_VALID(ent)) return false; @@ -244,8 +756,14 @@ static bool stayNearPlayer(CachedEntity *&ent, const bot_class_config &config, C { if (nav::navTo(i->m_center, 7, true, false)) { - result = i; - current_task = task::stay_near; + result = i; + if (engineer) + { + current_task = { task::engineer, 4 }; + current_engineer_task = task::staynear_engineer; + } + else + current_task = task::stay_near; return true; } } @@ -255,8 +773,14 @@ static bool stayNearPlayer(CachedEntity *&ent, const bot_class_config &config, C auto it = select_randomly(areas.begin(), areas.end()); if (nav::navTo((*it.base())->m_center, 7, true, false)) { - result = *it.base(); - current_task = task::stay_near; + result = *it.base(); + if (engineer) + { + current_task = { task::engineer, 4 }; + current_engineer_task = task::staynear_engineer; + } + else + current_task = task::stay_near; return true; } } @@ -300,6 +824,95 @@ static bool stayNearPlayers(const bot_class_config &config, CachedEntity *&resul } } // namespace stayNearHelpers +// stayNear()'s Little Texan brother +static bool stayNearEngineer() +{ + static CachedEntity *last_target = nullptr; + static CNavArea *last_area = nullptr; + + // What distances do we have to use? + const bot_class_config *config = &DIST_OTHER; + + // Check if someone is too close to us and then target them instead + std::pair nearest = getNearestPlayerDistance(); + if (nearest.first && nearest.first != last_target && nearest.second < config->min) + if (stayNearHelpers::stayNearPlayer(nearest.first, *config, last_area, true)) + { + last_target = nearest.first; + return true; + } + bool valid_dormant = false; + if (CE_VALID(last_target) && RAW_ENT(last_target)->IsDormant()) + { + if (last_target->m_vecDormantOrigin()) + valid_dormant = true; + } + if (current_task == task::stay_near) + { + static Timer invalid_area_time{}; + static Timer invalid_target_time{}; + // Do we already have a stay near target? Check if its still good. + if (CE_GOOD(last_target) || valid_dormant) + invalid_target_time.update(); + else + invalid_area_time.update(); + // Check if we still have LOS and are close enough/far enough + Vector position; + if (CE_GOOD(last_target) || valid_dormant) + { + position = *last_target->m_vecDormantOrigin(); + } + if ((CE_GOOD(last_target) || valid_dormant) && stayNearHelpers::isValidNearPosition(last_area->m_center, position, *config)) + invalid_area_time.update(); + + if ((CE_GOOD(last_target) || valid_dormant) && (!g_pPlayerResource->isAlive(last_target->m_IDX) || !last_target->m_bEnemy() || !player_tools::shouldTarget(last_target) || !spy_cloak[last_target->m_IDX].check(*spy_ignore_time) || (hacks::shared::aimbot::ignore_cloak && IsPlayerInvisible(last_target)))) + { + if (hacks::shared::aimbot::ignore_cloak && IsPlayerInvisible(last_target)) + spy_cloak[last_target->m_IDX].update(); + nav::clearInstructions(); + current_engineer_task = task::engineer_task::nothing; + } + else if (invalid_area_time.test_and_set(300)) + { + current_engineer_task = task::engineer_task::nothing; + } + else if (invalid_target_time.test_and_set(5000)) + { + current_engineer_task = task::engineer_task::nothing; + } + } + // Are we doing nothing? Check if our current location can still attack our + // last target + if (current_engineer_task != task::engineer_task::staynear_engineer && (CE_GOOD(last_target) || valid_dormant) && g_pPlayerResource->isAlive(last_target->m_IDX) && last_target->m_bEnemy()) + { + if (hacks::shared::aimbot::ignore_cloak && IsPlayerInvisible(last_target)) + spy_cloak[last_target->m_IDX].update(); + if (spy_cloak[last_target->m_IDX].check(*spy_ignore_time)) + { + Vector position = *last_target->m_vecDormantOrigin(); + + if (stayNearHelpers::isValidNearPosition(g_pLocalPlayer->v_Origin, position, *config)) + return true; + // If not, can we try pathing to our last target again? + if (stayNearHelpers::stayNearPlayer(last_target, *config, last_area, true)) + return true; + } + last_target = nullptr; + } + + static Timer wait_until_stay_near{}; + if (current_task == task::engineer_task::staynear_engineer) + { + return true; + } + else if (wait_until_stay_near.test_and_set(1000)) + { + // We're doing nothing? Do something! + return stayNearHelpers::stayNearPlayers(*config, last_target, last_area); + } + + return false; +} // Main stay near function static bool stayNear() { @@ -445,7 +1058,7 @@ static std::vector getDispensers() return dispensers; } -static bool getDispenserHealthAndAmmo() +static bool getDispenserHealthAndAmmo(int metal) { // Timeout for standing next to dispenser static Timer dispenser_timeout{}; @@ -453,6 +1066,12 @@ static bool getDispenserHealthAndAmmo() static Timer dispenser_cooldown{}; float health = static_cast(LOCAL_E->m_iHealth()) / LOCAL_E->m_iMaxHealth(); bool lowAmmo = hasLowAmmo(); + if (metal != -1) + { + lowAmmo = metal < 100 && selectBuilding() == None; + if (current_engineer_task == task::engineer_task::upgradeorrepair_building) + lowAmmo = metal == 0; + } // Check if we should cancel this task if (current_task == task::dispenser) { @@ -510,10 +1129,16 @@ static bool getDispenserHealthAndAmmo() return false; } -static bool getHealthAndAmmo() +static bool getHealthAndAmmo(int metal) { float health = static_cast(LOCAL_E->m_iHealth()) / LOCAL_E->m_iMaxHealth(); bool lowAmmo = hasLowAmmo(); + if (metal != -1) + { + lowAmmo = metal < 100 && selectBuilding() == None; + if (current_engineer_task == task::engineer_task::upgradeorrepair_building) + lowAmmo = metal == 0; + } // Check if we should cancel this task if (current_task == task::health || current_task == task::ammo) { @@ -643,6 +1268,18 @@ static slots getBestSlot(slots active_slot) else return secondary; } + case tf_engineer: + { + // Use wrench, because we are trying to build + if (current_task == task::engineer) + { + if (current_engineer_task == task::engineer_task::build_building) + return active_slot; + if (current_engineer_task == task::engineer_task::upgradeorrepair_building) + return melee; + } + return primary; + } default: { if (nearest.second <= 400) @@ -674,7 +1311,36 @@ static void updateSlot() } } -static InitRoutine runinit([]() { EC::Register(EC::CreateMove, CreateMove, "navbot", EC::early); }); +class ObjectDestroyListener : public IGameEventListener2 +{ + virtual void FireGameEvent(IGameEvent *event) + { + if (!isHackActive() || !engineer_mode) + return; + // Get index of destroyed object + int index = event->GetInt("index"); + // Destroyed Entity + CachedEntity *ent = ENTITY(index); + // Get Entry in the vector + auto it = std::find(local_buildings.begin(), local_buildings.end(), ent); + // If found, erase + if (it != local_buildings.end()) + local_buildings.erase(it); + } +}; + +ObjectDestroyListener &listener() +{ + static ObjectDestroyListener object{}; + return object; +} + +static InitRoutine runinit([]() { + g_IEventManager2->AddListener(&listener(), "object_destroyed", false); + EC::Register(EC::CreateMove, CreateMove, "navbot", EC::early); + EC::Register( + EC::Shutdown, []() { g_IEventManager2->RemoveListener(&listener()); }, "navbot_shutdown"); +}); void change(settings::VariableBase &, bool) {