diff --git a/.gitmodules b/.gitmodules index 9ec01a35..84ae995f 100755 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "ucccccp"] path = external/ucccccp url = https://github.com/nullworks/ucccccp.git +[submodule "external/co-library"] + path = external/co-library + url = https://github.com/nullworks/co-library.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 5468a864..ce8916c5 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${CMAKE_BUILD_TYPE_VALUES}) cmake_minimum_required(VERSION 3.0) project(cathook VERSION 0.0.1) + +set(CMAKE_CXX_STANDARD 17) add_library(cathook SHARED "") set(GameSpecific 1 CACHE BOOL "Build for specific target game (As opposed to universal, but slower, lib)") @@ -31,6 +33,7 @@ set(Textmode 0 CACHE BOOL "Various textmode-only features for bots") set(EnableTextmodeStdin 0 CACHE BOOL "Textmode Stdin -> Console bridge (EXPERIMENTAL)") set(EnableWarnings 1 CACHE BOOL "Enable compile warnings") set(EnableNullGraphics 0 CACHE BOOL "Enable experimental textmode hooks (CRASHES)") +set(EnableOnlineFeatures 1 CACHE BOOL "Enable online features (WIP)") if(NOT EnableVisuals) set(EnableGUI 0) @@ -62,6 +65,11 @@ if(EnableIPC) target_link_libraries(cathook SimpleIPC) endif() +if(EnableOnlineFeatures) + add_subdirectory(external/co-library) + target_link_libraries(cathook co-library) +endif() + if(EnableVisuals) add_subdirectory(external/libglez) target_include_directories(cathook PRIVATE include/visual) diff --git a/external/co-library b/external/co-library new file mode 160000 index 00000000..393948a6 --- /dev/null +++ b/external/co-library @@ -0,0 +1 @@ +Subproject commit 393948a6bf7ee4a62f5a3bd82d94c05d4bc405aa diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 744abdef..588f81d7 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -39,7 +39,8 @@ target_sources(cathook PRIVATE "${CMAKE_CURRENT_LIST_DIR}/velocity.hpp" "${CMAKE_CURRENT_LIST_DIR}/votelogger.hpp" "${CMAKE_CURRENT_LIST_DIR}/MiscTemporary.hpp" - "${CMAKE_CURRENT_LIST_DIR}/Options.hpp") + "${CMAKE_CURRENT_LIST_DIR}/Options.hpp" + "${CMAKE_CURRENT_LIST_DIR}/PlayerTools.hpp") target_include_directories(cathook PRIVATE "${CMAKE_CURRENT_LIST_DIR}") @@ -50,6 +51,7 @@ add_subdirectory(hacks) add_subdirectory(hooks) add_subdirectory(reclasses) add_subdirectory(sdk) +add_subdirectory(online) if(EnableVisuals) add_subdirectory(visual) diff --git a/include/PlayerTools.hpp b/include/PlayerTools.hpp new file mode 100644 index 00000000..4e991551 --- /dev/null +++ b/include/PlayerTools.hpp @@ -0,0 +1,44 @@ +/* + Created on 23.06.18. +*/ + +#pragma once + +#include "config.h" +#include + +#if ENABLE_VISUALS +#include +#endif + +class CachedEntity; + +namespace player_tools +{ + +enum class IgnoreReason +{ + DO_NOT_IGNORE, + IS_HOOVY, + IS_TAUNTING, + LOCAL_PLAYER_LIST, + ONLINE_NO_TARGET, + ONLINE_FRIENDLY_SOFTWARE, + DEVELOPER, + OTHER +}; + +IgnoreReason shouldTargetSteamId(unsigned id); +IgnoreReason shouldTarget(CachedEntity *player); + +bool shouldAlwaysRenderEspSteamId(unsigned id); +bool shouldAlwaysRenderEsp(CachedEntity *entity); + +#if ENABLE_VISUALS +std::optional forceEspColorSteamId(unsigned id); +std::optional forceEspColor(CachedEntity *entity); +#endif + +void onKilledBy(CachedEntity *entity); + +} \ No newline at end of file diff --git a/include/globals.h b/include/globals.h index b626eeaf..801e09cf 100755 --- a/include/globals.h +++ b/include/globals.h @@ -23,7 +23,6 @@ extern ConVar *cl_interpolate; extern CatVar event_log; extern CatVar cathook; // Master switch -extern CatVar ignore_taunting; extern bool *bSendPackets; extern CatVar show_antiaim; extern CatVar force_thirdperson; diff --git a/include/hoovy.hpp b/include/hoovy.hpp index f0d7eb34..b9b52259 100755 --- a/include/hoovy.hpp +++ b/include/hoovy.hpp @@ -7,5 +7,7 @@ #pragma once +class CachedEntity; + void UpdateHoovyList(); bool IsHoovy(CachedEntity *entity); diff --git a/include/online/CMakeLists.txt b/include/online/CMakeLists.txt new file mode 100644 index 00000000..a08e5342 --- /dev/null +++ b/include/online/CMakeLists.txt @@ -0,0 +1,2 @@ +target_sources(cathook PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/Online.hpp") \ No newline at end of file diff --git a/include/online/Online.hpp b/include/online/Online.hpp new file mode 100644 index 00000000..de384892 --- /dev/null +++ b/include/online/Online.hpp @@ -0,0 +1,40 @@ +/* + Created on 23.06.18. +*/ + +#pragma once + +#include +#include +#include +#include + +namespace online +{ + +struct user_data +{ + bool is_anonymous{ false }; + bool is_using_friendly_software{ false }; + bool is_steamid_verified{ false }; + std::string username{}; + std::vector shown_groups{}; + std::string software_name{}; + bool has_software{ false }; + bool no_target{ false }; + bool is_developer{}; +#if ENABLE_VISUALS + bool has_color{ false }; + colors::rgba_t color{}; + bool rainbow{ false }; +#endif +}; + +/* + * Identify unidentified users, send online status, etc + */ +void update(); + +user_data *getUserData(unsigned steamId); + +} \ No newline at end of file diff --git a/include/visual/EffectChams.hpp b/include/visual/EffectChams.hpp index d94fc032..2bfe231f 100644 --- a/include/visual/EffectChams.hpp +++ b/include/visual/EffectChams.hpp @@ -36,7 +36,7 @@ public: void SetEntityColor(CachedEntity *ent, rgba_t color); rgba_t ChamsColor(IClientEntity *entity); bool ShouldRenderChams(IClientEntity *entity); - void RenderChams(int idx); + void RenderChams(IClientEntity *entity); void BeginRenderChams(); void EndRenderChams(); void RenderChamsRecursive(IClientEntity *entity); @@ -54,4 +54,4 @@ public: extern EffectChams g_EffectChams; extern CScreenSpaceEffectRegistration *g_pEffectChams; -} \ No newline at end of file +} diff --git a/include/visual/colors.hpp b/include/visual/colors.hpp index 416e2575..094d0034 100755 --- a/include/visual/colors.hpp +++ b/include/visual/colors.hpp @@ -48,6 +48,8 @@ struct rgba_t constexpr rgba_t(float _r, float _g, float _b, float _a = 1.0f) : r(_r), g(_g), b(_b), a(_a){}; + explicit rgba_t(const char hex[6]); + constexpr operator glez::rgba() const { return *reinterpret_cast(this); @@ -76,6 +78,16 @@ struct rgba_t } }; +constexpr bool operator==(const rgba_t& lhs, const rgba_t& rhs) +{ + return rhs.r == lhs.r && rhs.g == lhs.g && rhs.b == lhs.b && rhs.a == lhs.a; +} + +constexpr bool operator!=(const rgba_t& lhs, const rgba_t& rhs) +{ + return !(lhs == rhs); +} + constexpr rgba_t FromRGBA8(float r, float g, float b, float a) { return rgba_t{ r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 944a3379..291c9552 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,7 +32,8 @@ target_sources(cathook PRIVATE "${CMAKE_CURRENT_LIST_DIR}/velocity.cpp" "${CMAKE_CURRENT_LIST_DIR}/votelogger.cpp" "${CMAKE_CURRENT_LIST_DIR}/MiscTemporary.cpp" - "${CMAKE_CURRENT_LIST_DIR}/Options.cpp") + "${CMAKE_CURRENT_LIST_DIR}/Options.cpp" + "${CMAKE_CURRENT_LIST_DIR}/PlayerTools.cpp") add_subdirectory(core) add_subdirectory(classinfo) @@ -41,6 +42,7 @@ add_subdirectory(hacks) add_subdirectory(hooks) add_subdirectory(reclasses) add_subdirectory(sdk) +add_subdirectory(online) if(EnableVisuals) add_subdirectory(visual) diff --git a/src/PlayerTools.cpp b/src/PlayerTools.cpp new file mode 100644 index 00000000..4a508b43 --- /dev/null +++ b/src/PlayerTools.cpp @@ -0,0 +1,161 @@ +/* + Created on 23.06.18. +*/ + +#include +#include +#include +#include +#include +#include "PlayerTools.hpp" +#include "entitycache.hpp" + +static std::unordered_map betrayal_list{}; + +static CatCommand forgive_all("pt_forgive_all", "Clear betrayal list", []() { + betrayal_list.clear(); +}); + +namespace settings +{ + +static CatVar online_notarget(CV_SWITCH, "pt_ignore_notarget", "1", "Ignore notarget", "Ignore online players with notarget role"); +static CatVar hoovy(CV_SWITCH, "pt_ignore_hoovy", "1", "Ignore hoovy"); +static CatVar online_friendly_software(CV_SWITCH, "pt_ignore_friendly_software", "1", "Ignore friendly software", "Ignore CO-compatible software"); +static CatVar online_only_verified(CV_SWITCH, "pt_ignore_only_verified", "0", "Only ignore verified", "If online checks are enabled, only apply ignore if SteamID is verified (not recommended right now)"); +static CatVar online_anonymous(CV_SWITCH, "pt_ignore_anonymous", "1", "Ignore anonymous", "Apply ignore checks to anonymous accounts too"); +static CatVar betrayal_limit(CV_INT, "pt_betrayal_limit", "3", "Betrayal limit", "Stop ignoring a player after N kills while you ignored them"); +static CatVar taunting(CV_SWITCH, "pt_ignore_taunting", "1", "Ignore taunting", "Don't shoot taunting players"); + +} + +namespace player_tools +{ + +IgnoreReason shouldTargetSteamId(unsigned id) +{ + if (id == 0) + return IgnoreReason::DO_NOT_IGNORE; + + if (settings::betrayal_limit) + { + if (betrayal_list[id] > int(settings::betrayal_limit)) + return IgnoreReason::DO_NOT_IGNORE; + } + + auto& pl = playerlist::AccessData(id); + if (playerlist::IsFriendly(pl.state)) + return IgnoreReason::LOCAL_PLAYER_LIST; + + auto *co = online::getUserData(id); + if (co) + { + bool check_verified = !settings::online_only_verified || co->is_steamid_verified; + bool check_anonymous = settings::online_anonymous || !co->is_anonymous; + + if (check_verified && check_anonymous) + { + if (settings::online_notarget && co->no_target) + return IgnoreReason::ONLINE_NO_TARGET; + if (settings::online_friendly_software && co->is_using_friendly_software) + return IgnoreReason::ONLINE_FRIENDLY_SOFTWARE; + } + // Always check developer status, no exceptions + if (co->is_developer) + return IgnoreReason::DEVELOPER; + } + + return IgnoreReason::DO_NOT_IGNORE; +} +IgnoreReason shouldTarget(CachedEntity *entity) +{ + if (entity->m_Type() == ENTITY_PLAYER) + { + if (settings::hoovy && IsHoovy(entity)) + return IgnoreReason::IS_HOOVY; + if (settings::taunting && HasCondition(entity)) + return IgnoreReason::IS_TAUNTING; + + return shouldTargetSteamId(entity->player_info.friendsID); + } + + return IgnoreReason::DO_NOT_IGNORE; +} + +bool shouldAlwaysRenderEspSteamId(unsigned id) +{ + if (id == 0) + return false; + + auto& pl = playerlist::AccessData(id); + if (pl.state != playerlist::k_EState::DEFAULT) + return true; + + auto *co = online::getUserData(id); + if (co) + return true; + + return false; +} +bool shouldAlwaysRenderEsp(CachedEntity *entity) +{ + if (entity->m_Type() == ENTITY_PLAYER) + { + return shouldAlwaysRenderEspSteamId(entity->player_info.friendsID); + } + + return false; +} + +#if ENABLE_VISUALS +std::optional forceEspColorSteamId(unsigned id) +{ + if (id == 0) + return std::nullopt; + + auto pl = playerlist::Color(id); + if (pl != colors::empty) + return std::optional{ pl }; + + auto *co = online::getUserData(id); + if (co) + { + if (co->has_color) + return std::optional{ co->color }; + if (co->rainbow) + return std::optional{ colors::RainbowCurrent() }; + } + + return std::nullopt; +} +std::optional forceEspColor(CachedEntity *entity) +{ + if (entity->m_Type() == ENTITY_PLAYER) + { + return forceEspColorSteamId(entity->player_info.friendsID); + } + + return std::nullopt; +} +#endif + +void onKilledBy(unsigned id) +{ + auto reason = shouldTargetSteamId(id); + if (reason != IgnoreReason::DO_NOT_IGNORE) + { + // We ignored the gamer, but they still shot us + if (betrayal_list.find(id) == betrayal_list.end()) + betrayal_list[id] = 0; + betrayal_list[id]++; + } +} + +void onKilledBy(CachedEntity *entity) +{ + onKilledBy(entity->player_info.friendsID); +} + + + +} \ No newline at end of file diff --git a/src/globals.cpp b/src/globals.cpp index a0999d5a..d9f80a15 100755 --- a/src/globals.cpp +++ b/src/globals.cpp @@ -33,8 +33,6 @@ CatVar force_name(CV_STRING, "name", "", "Force name"); CatVar cathook(CV_SWITCH, "enabled", "1", "CatHook enabled", "Disabling this completely disables cathook (can be re-enabled)"); -CatVar ignore_taunting(CV_SWITCH, "ignore_taunting", "1", "Ignore taunting", - "Aimbot/Triggerbot won't attack taunting enemies"); // CatVar send_packets(CV_SWITCH, "sendpackets", "1", "Send packets", "Internal // use"); CatVar show_antiaim(CV_SWITCH, "thirdperson_angles", "1", "Real TP angles", diff --git a/src/hack.cpp b/src/hack.cpp index 2416049c..1d203214 100644 --- a/src/hack.cpp +++ b/src/hack.cpp @@ -532,6 +532,13 @@ free(logname);*/ #if ENABLE_VISUALS hacks::shared::esp::Init(); #endif +#if not ENABLE_VISUALS + hack::command_stack().push("exec cat_autoexec_textmode"); +#endif + hack::command_stack().push("exec cat_autoexec"); + hack::command_stack().push("cat_killsay_reload"); + hack::command_stack().push("cat_spam_reload"); + logging::Info("Clearing initializer stack"); while (!init_stack().empty()) { @@ -540,12 +547,6 @@ free(logname);*/ } logging::Info("Initializer stack done"); -#if not ENABLE_VISUALS - hack::command_stack().push("exec cat_autoexec_textmode"); -#endif - hack::command_stack().push("exec cat_autoexec"); - hack::command_stack().push("cat_killsay_reload"); - hack::command_stack().push("cat_spam_reload"); hack::initialized = true; } diff --git a/src/hacks/Aimbot.cpp b/src/hacks/Aimbot.cpp index 80d8d60d..241d9822 100644 --- a/src/hacks/Aimbot.cpp +++ b/src/hacks/Aimbot.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "common.hpp" namespace hacks::shared::aimbot @@ -60,8 +61,6 @@ static CatVar static CatVar ignore_vaccinator( CV_SWITCH, "aimbot_ignore_vaccinator", "1", "Ignore Vaccinator", "Hitscan weapons won't fire if enemy is vaccinated against bullets"); -static CatVar ignore_hoovy(CV_SWITCH, "aimbot_ignore_hoovy", "0", - "Ignore Hoovies", "Aimbot won't attack hoovies"); static CatVar ignore_cloak(CV_SWITCH, "aimbot_ignore_cloak", "1", "Ignore cloaked", "Don't aim at invisible enemies"); static CatVar ignore_deadringer(CV_SWITCH, "aimbot_ignore_deadringer", "1", @@ -574,8 +573,10 @@ bool IsTargetStateGood(CachedEntity *entity) } } - // Taunting - if (ignore_taunting && HasCondition(entity)) + // Some global checks + if (player_tools::shouldTarget(entity) != player_tools::IgnoreReason::DO_NOT_IGNORE) + return false; + if (hacks::shared::catbot::should_ignore_player(entity)) return false; // Invulnerable players, ex: uber, bonk if (IsPlayerInvulnerable(entity)) @@ -605,19 +606,7 @@ bool IsTargetStateGood(CachedEntity *entity) HasCondition(entity)) return false; } - // Friendly player - if (playerlist::IsFriendly(playerlist::AccessData(entity).state)) - return false; - IF_GAME(IsTF()) - { - // Hoovys - if (ignore_hoovy && IsHoovy(entity)) - { - return false; - } - } - if (hacks::shared::catbot::should_ignore_player(entity)) - return false; + // Preform hitbox prediction int hitbox = BestHitbox(entity); AimbotCalculatedData_s &cd = calculated_data_array[entity->m_IDX]; diff --git a/src/hacks/AutoDetonator.cpp b/src/hacks/AutoDetonator.cpp index cedf1381..57b81330 100644 --- a/src/hacks/AutoDetonator.cpp +++ b/src/hacks/AutoDetonator.cpp @@ -5,6 +5,7 @@ * Author: nullifiedcat & Lighty */ +#include #include "common.hpp" namespace hacks::tf::autodetonator @@ -53,19 +54,16 @@ bool IsTarget(CachedEntity *ent) // Dont detonate on dead players if (!ent->m_bAlivePlayer()) return false; - // Dont detonate on friendly players - if (playerlist::IsFriendly(playerlist::AccessData(ent).state)) - return false; + // Global checks + if (player_tools::shouldTarget(ent) != player_tools::IgnoreReason::DO_NOT_IGNORE) + return false; IF_GAME(IsTF()) { // Dont target invulnerable players, ex: uber, bonk if (IsPlayerInvulnerable(ent)) return false; - // If settings allow, ignore taunting players - if (ignore_taunting && HasCondition(ent)) - return false; // If settings allow, dont target cloaked players if (legit && IsPlayerInvisible(ent)) diff --git a/src/hacks/AutoHeal.cpp b/src/hacks/AutoHeal.cpp index 54f04446..81039558 100644 --- a/src/hacks/AutoHeal.cpp +++ b/src/hacks/AutoHeal.cpp @@ -24,7 +24,7 @@ int m_iNewTarget{ 0 }; static CatVar pop_uber_auto(CV_SWITCH, "autoheal_uber", "1", "AutoUber", "Use ubercharge automatically"); static CatVar - pop_uber_percent(CV_FLOAT, "autoheal_uber_health", "30", + pop_uber_percent(CV_FLOAT, "autoheal_uber_health", "0", "Pop uber if health% <", "When under a percentage of health, use ubercharge"); static CatVar share_uber( @@ -351,9 +351,17 @@ bool IsPopped() bool ShouldChargePlayer(int idx) { CachedEntity *target = ENTITY(idx); + const int health = target->m_iHealth(); + if (float(pop_uber_percent) > 0) + { + const float pophealth = target->m_iMaxHealth() * (float(pop_uber_percent) / 100); + if (health < pophealth) + return true; + } + else + { const float damage_accum_duration = g_GlobalVars->curtime - data[idx].accum_damage_start; - const int health = target->m_iHealth(); if (!data[idx].accum_damage_start) return false; if (health > 30 && data[idx].accum_damage < 45) @@ -366,6 +374,8 @@ bool ShouldChargePlayer(int idx) if (health < 30 && data[idx].accum_damage > 10) return true; return false; + } + return false; } bool ShouldPop() diff --git a/src/hacks/AutoSticky.cpp b/src/hacks/AutoSticky.cpp index 10db1ca5..408f8fc9 100644 --- a/src/hacks/AutoSticky.cpp +++ b/src/hacks/AutoSticky.cpp @@ -7,6 +7,7 @@ #include "common.hpp" #include +#include namespace hacks::tf::autosticky { @@ -58,8 +59,9 @@ bool IsTarget(CachedEntity *ent) // Dont detonate on dead players if (!ent->m_bAlivePlayer()) return false; - // Dont detonate on friendly players - if (playerlist::IsFriendly(playerlist::AccessData(ent).state)) + + // Global checks + if (player_tools::shouldTarget(ent) != player_tools::IgnoreReason::DO_NOT_IGNORE) return false; IF_GAME(IsTF()) diff --git a/src/hacks/ESP.cpp b/src/hacks/ESP.cpp index 51bc4483..94c2493f 100644 --- a/src/hacks/ESP.cpp +++ b/src/hacks/ESP.cpp @@ -7,6 +7,8 @@ #include #include +#include +#include #include "common.hpp" namespace hacks::shared::esp @@ -146,6 +148,11 @@ static CatVar entity_model(CV_SWITCH, "esp_model_name", "0", "Model name ESP", static CatVar entity_id(CV_SWITCH, "esp_entity_id", "1", "Entity ID", "Used with Entity ESP. Shows entityID"); +// Online +static CatVar online(CV_SWITCH, "esp_online", "1", "Show online info", "Username, etc"); +static CatVar online_groups(CV_SWITCH, "esp_online_groups", "1", "Show online groups", "Admin, developer, etc"); +static CatVar online_software(CV_SWITCH, "esp_online_software", "1", "Show software", "cathook, lmaobox, etc"); + // CatVar draw_hitbox(CV_SWITCH, "esp_hitbox", "1", "Draw Hitbox"); // Unknown @@ -1153,10 +1160,12 @@ void _FASTCALL ProcessEntity(CachedEntity *ent) if (!g_IEngine->GetPlayerInfo(ent->m_IDX, &info)) return; + online::user_data *data = online ? online::getUserData(info.friendsID) : nullptr; + // TODO, check if u can just use "ent->m_bEnemy()" instead of m_iTeam // Legit mode handling if (legit && ent->m_iTeam() != g_pLocalPlayer->team && - playerlist::IsDefault(info.friendsID)) + playerlist::IsDefault(info.friendsID) && !(data)) { if (IsPlayerInvisible(ent)) return; // Invis check @@ -1168,6 +1177,23 @@ void _FASTCALL ProcessEntity(CachedEntity *ent) // return; } + if (data) + { + AddEntityString(ent, "CO: " + data->username, colors::yellow); + if (data->is_steamid_verified) + AddEntityString(ent, "Verified SteamID", colors::green); + if (online_groups) + for (auto& s: data->shown_groups) + AddEntityString(ent, s, colors::orange); + if (online_software && data->has_software) + { + if (data->is_using_friendly_software) + AddEntityString(ent, "Software: " + data->software_name); + else + AddEntityString(ent, "Software: " + data->software_name, colors::red); + } + } + // Powerup handling if (powerup_esp) { @@ -1177,8 +1203,7 @@ void _FASTCALL ProcessEntity(CachedEntity *ent) } // Dont understand reasoning for this check - if (ent->m_bEnemy() || teammates || - !playerlist::IsDefault(info.friendsID)) + if (ent->m_bEnemy() || teammates || player_tools::shouldAlwaysRenderEsp(ent)) { // Playername diff --git a/src/hacks/FollowBot.cpp b/src/hacks/FollowBot.cpp index 10287413..fa130ac3 100644 --- a/src/hacks/FollowBot.cpp +++ b/src/hacks/FollowBot.cpp @@ -23,7 +23,7 @@ static CatVar draw_crumb(CV_SWITCH, "fb_draw", "1", "Draw crumbs", "Self explanitory"); static CatVar follow_distance(CV_INT, "fb_distance", "175", "Follow Distance", "How close the bots should stay to the target"); -static CatVar follow_activation(CV_INT, "fb_activation", "175", +static CatVar follow_activation(CV_INT, "fb_activation", "1000", "Activation Distance", "How close a player should be until the " "followbot will pick them as a target"); @@ -45,14 +45,49 @@ static CatVar always_medigun(CV_SWITCH, "fb_always_medigun", "0", "Always Medigun", "Always use medigun"); static CatVar sync_taunt(CV_SWITCH, "fb_sync_taunt", "0", "Synced taunt", "Taunt when follow target does"); -static CatVar change(CV_SWITCH, "fb_switch", "1", "Change followbot target", +static CatVar change(CV_SWITCH, "fb_switch", "0", "Change followbot target", "Always change roaming target when possible"); +static CatVar autojump(CV_SWITCH, "fb_autojump", "1", "Autojump", + "Automatically jump if stuck"); +static CatVar afk(CV_SWITCH, "fb_afk", "1", "Switch target if AFK", + "Automatically switch target if the target is afk"); +static CatVar afktime( + CV_INT, "fb_afk_time", "15000", "Max AFK Time", + "Max time in ms spent standing still before player gets declared afk"); + // Something to store breadcrumbs created by followed players static std::vector breadcrumbs; static const int crumb_limit = 64; // limit // Followed entity, externed for highlight color int follow_target = 0; +bool inited; + +Timer lastTaunt{}; //time since taunt was last executed, used to avoid kicks +std::array afkTicks; //for how many ms the player hasn't been moving + +void checkAFK() +{ + for (int i = 0; i < g_GlobalVars->maxClients; i++) + { + auto entity = ENTITY(i); + if (CE_BAD(entity)) + continue; + if (!CE_VECTOR(entity, netvar.vVelocity).IsZero(5.0f)) + { + afkTicks[i].update(); + } + } +} + +void init() +{ + for (int i; i < afkTicks.size(); i++) + { + afkTicks[i].update(); + } + inited = true; +} void WorldTick() { @@ -61,6 +96,8 @@ void WorldTick() follow_target = 0; return; } + if (!inited) + init(); // We need a local player to control if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer()) @@ -69,6 +106,9 @@ void WorldTick() return; } + if (afk) + checkAFK(); + // Still good check if (follow_target) { @@ -122,6 +162,12 @@ void WorldTick() continue; if (entity == LOCAL_E) // Follow self lol continue; + if (entity->m_bEnemy()) + continue; + if (afk && afkTicks[i].check(int(afktime))) //don't follow target that was determined afk + continue; + if (IsPlayerDisguised(entity) || IsPlayerInvisible(entity)) + continue; if (!entity->m_bAlivePlayer()) // Dont follow dead players continue; if (follow_activation && @@ -148,29 +194,51 @@ void WorldTick() entity->m_flDistance()) // favor closer entitys continue; // ooooo, a target - follow_target = entity->m_IDX; + follow_target = i; + afkTicks[i].update(); //set afk time to 0 } } // last check for entity before we continue if (!follow_target) return; - // If the player is close enough, we dont need to follow the path CachedEntity *followtar = ENTITY(follow_target); // wtf is this needed if (CE_BAD(followtar)) return; - auto tar_orig = followtar->m_vecOrigin(); - auto loc_orig = LOCAL_E->m_vecOrigin(); - auto dist_to_target = loc_orig.DistTo(tar_orig); - if (dist_to_target < 30) - breadcrumbs.clear(); + // Check if we are following a disguised/spy + if (IsPlayerDisguised(followtar) || IsPlayerInvisible(followtar)) + { + follow_target = 0; + return; + } + //check if target is afk + if (afk) + { + if (afkTicks[follow_target].check(int(afktime))) + { + follow_target = 0; + return; + } + + } // Update timer on new target static Timer idle_time{}; if (breadcrumbs.empty()) idle_time.update(); + // If the player is close enough, we dont need to follow the path + auto tar_orig = followtar->m_vecOrigin(); + auto loc_orig = LOCAL_E->m_vecOrigin(); + auto dist_to_target = loc_orig.DistTo(tar_orig); + + if ((dist_to_target < (float) follow_distance) && + VisCheckEntFromEnt(LOCAL_E, followtar)) + { + idle_time.update(); + } + // New crumbs, we add one if its empty so we have something to follow if ((breadcrumbs.empty() || tar_orig.DistTo(breadcrumbs.at(breadcrumbs.size() - 1)) > 40.0F) && @@ -179,26 +247,32 @@ void WorldTick() // Prune old and close crumbs that we wont need anymore, update idle timer // too - while (breadcrumbs.size() > 1 && loc_orig.DistTo(breadcrumbs.at(0)) < 60.f) + for (int i = 0; i < breadcrumbs.size(); i++) { - idle_time.update(); - breadcrumbs.erase(breadcrumbs.begin()); + if (loc_orig.DistTo(breadcrumbs.at(i)) < 60.f) + { + idle_time.update(); + for (int j = 0; j <= i; j++) + breadcrumbs.erase(breadcrumbs.begin()); + } + } + + //moved because its worthless otherwise + if (sync_taunt && HasCondition(followtar) && lastTaunt.test_and_set(1000)) { + g_IEngine->ClientCmd("taunt"); } // Follow the crumbs when too far away, or just starting to follow if (dist_to_target > (float) follow_distance) { // Check for idle - if (idle_time.check(3000) || - (breadcrumbs.size() > 1 && LOCAL_E->m_vecVelocity.IsZero(5.0f))) + if (autojump && idle_time.check(3000)) g_pUserCmd->buttons |= IN_JUMP; if (idle_time.test_and_set(5000)) { follow_target = 0; return; } - if (sync_taunt && HasCondition(ENTITY(follow_target))) - g_IEngine->ClientCmd("taunt"); static float last_slot_check = 0.0f; if (g_GlobalVars->curtime < last_slot_check) last_slot_check = 0.0f; diff --git a/src/hacks/Trigger.cpp b/src/hacks/Trigger.cpp index 2e1f1c12..b32b846a 100644 --- a/src/hacks/Trigger.cpp +++ b/src/hacks/Trigger.cpp @@ -9,6 +9,7 @@ #include #include "common.hpp" #include +#include namespace hacks::shared::triggerbot { @@ -272,6 +273,10 @@ bool IsTargetStateGood(CachedEntity *entity) if (!entity->m_bEnemy() && !teammates) return false; + // Global checks + if (player_tools::shouldTarget(entity) != player_tools::IgnoreReason::DO_NOT_IGNORE) + return false; + IF_GAME(IsTF()) { // If settings allow waiting for charge, and current charge cant @@ -291,9 +296,6 @@ bool IsTargetStateGood(CachedEntity *entity) return false; } } - // If settings allow, ignore taunting players - if (ignore_taunting && HasCondition(entity)) - return false; // Dont target invulnerable players, ex: uber, bonk if (IsPlayerInvulnerable(entity)) return false; @@ -307,17 +309,6 @@ bool IsTargetStateGood(CachedEntity *entity) HasCondition(entity)) return false; } - // Dont target players marked as friendly - if (playerlist::IsFriendly(playerlist::AccessData(entity).state)) - return false; - IF_GAME(IsTF()) - { - // If settings allow, ignore hoovys - if (ignore_hoovy && IsHoovy(entity)) - { - return false; - } - } // Head hitbox detection if (HeadPreferable(entity)) diff --git a/src/hooks/Paint.cpp b/src/hooks/Paint.cpp index 6e1fc4a7..5fd4d6cb 100644 --- a/src/hooks/Paint.cpp +++ b/src/hooks/Paint.cpp @@ -6,6 +6,7 @@ */ #include +#include #include "common.hpp" #include "hitrate.hpp" #include "hack.hpp" @@ -31,6 +32,7 @@ DEFINE_HOOKED_METHOD(Paint, void, IEngineVGui *this_, PaintMode_t mode) { hitrate::Update(); } + online::update(); #if ENABLE_IPC static Timer nametimer{}; if (nametimer.test_and_set(1000 * 10)) diff --git a/src/online/CMakeLists.txt b/src/online/CMakeLists.txt new file mode 100644 index 00000000..ecbbb1c2 --- /dev/null +++ b/src/online/CMakeLists.txt @@ -0,0 +1,2 @@ +target_sources(cathook PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/Online.cpp") \ No newline at end of file diff --git a/src/online/Online.cpp b/src/online/Online.cpp new file mode 100644 index 00000000..1290fa4c --- /dev/null +++ b/src/online/Online.cpp @@ -0,0 +1,289 @@ +/* + Created on 23.06.18. +*/ + +#include +#include + +#include +#include +#include +#include + +#undef null + +#include +#include +#include +#include + +namespace online +{ + +void saveApiKeyAndHost(std::string host); +void claimSteamId(); + +static co::OnlineService cathookOnlineService{}; +static std::unordered_map> data{}; +static std::unordered_map identify_queue{}; +static Timer identify_timer{}; +static bool identify_stale{ false }; +static std::string api_key{}; + +static CatVar enable(CV_SWITCH, "online", "1", "Enable online features"); +static CatCommand login("online_login", "Login", [](const CCommand& args) { + if (args.ArgC() != 3) + { + logging::Info("\nUsage: online_login \"\"\nKey will be saved in your data folder"); + return; + } + std::string host(args.Arg(2)); + logging::Info("[CO] Host = %s", host.c_str()); + try { + cathookOnlineService.setHost(host); + } catch (std::exception& ex) + { + logging::Info("[CO] Error setting host: %s", ex.what()); + return; + } + std::string key(args.Arg(1)); + try + { + cathookOnlineService.login(key, [key, host](co::ApiCallResult result, std::optional me) { + if (result == co::ApiCallResult::OK) + { + logging::Info("[CO] Successfully logged in. Welcome, %s", me->username.c_str()); + api_key = key; + saveApiKeyAndHost(host); + claimSteamId(); + } + else + { + logging::Info("[CO] There was an error logging in: code %d", result); + } + }); + } catch (std::exception& ex) { + logging::Info("[CO] Exception: %s", ex.what()); + } +}); +static CatCommand flush("online_flush_cache", "Flush player cache", [](const CCommand& args) { + data.clear(); + identify_queue.clear(); + identify_stale = true; +}); + +// INTERNAL METHODS + +void claimSteamId() +{ + auto id = g_ISteamUser->GetSteamID(); + logging::Info("[CO] Claiming SteamID %u", id.GetAccountID()); + try { + cathookOnlineService.gameStartup(id.GetAccountID()); + } catch (std::exception& ex) { + logging::Info("[CO] Exception: %s", ex.what()); + } +} + +bool tryLoadApiKeyAndHost() +{ + std::ifstream keyfile(DATA_PATH "/api_key", std::ios::in); + if (keyfile) + { + std::string host{}; + keyfile >> api_key >> host; + if (!api_key.empty() && !host.empty()) + { + try { + cathookOnlineService.setHost(host); + } catch (std::exception& ex) { + logging::Info("Error while setting host: %s", ex.what()); + } + return true; + } + } + return false; +} + +void saveApiKeyAndHost(std::string host) +{ + std::ofstream keyfile(DATA_PATH "/api_key", std::ios::out); + if (!keyfile) + { + logging::Info("[CO] Something went wrong while saving API key"); + return; + } + keyfile << api_key << '\n' << host << '\n'; +} + +void queueUserForIdentification(unsigned steamId) +{ + + identify_queue[steamId] = false; + identify_timer.update(); + identify_stale = true; +} + +void markSteamIdNonOnline(unsigned id) +{ + logging::Info("[CO] %u - not online", id); + data[id] = std::nullopt; +} + +void processOnlineIdentity(unsigned id, co::identified_user& user) +{ + logging::Info("[CO] %u - online", id); + user_data udata{}; + udata.username = user.username; + udata.is_anonymous = (user.username == "anonymous"); + udata.is_steamid_verified = user.steamid_verified; + for (auto& i: user.groups) + { + if (i.display_name.has_value()) + udata.shown_groups.push_back(*i.display_name); + if (i.name == "notarget") + udata.no_target = true; + if (i.name == "owner" || i.name == "contributor") + { + udata.is_developer = true; +#if ENABLE_VISUALS + udata.rainbow = true; +#endif + } + } +#if ENABLE_VISUALS + if (user.color.has_value()) + { + udata.has_color = true; + udata.color = colors::rgba_t(user.color->c_str()); + } +#endif + if (user.uses_software.has_value()) + { + udata.has_software = true; + udata.is_using_friendly_software = user.uses_software->friendly; + udata.software_name = user.uses_software->name; + } + data[id] = std::move(udata); +} + +void processIdentifyResponse(std::vector input, co::identified_user_group& group) +{ + logging::Info("[CO] Processing identify response containing %u / %u entries", group.users.size(), input.size()); + for (auto i: input) + { + auto u = group.users.find(i); + if (u == group.users.end()) + markSteamIdNonOnline(i); + else + processOnlineIdentity(i, (*u).second); + + identify_queue.erase(i); + logging::Info("[CO] Removed %u from identify queue, left %u\n", i, identify_queue.size()); + } +} + +void sendIdentifyRequest() +{ + std::vector steamIds{}; + auto it = identify_queue.begin(); + // Create a list of up to 32 steamId's + for (int i = 0; i < 32 && it != identify_queue.end(); ++i, ++it) + { + if (!it->second) + { + it->second = true; + steamIds.push_back(it->first); + } + } + logging::Info("[CO] Sending identify request for %u players", steamIds.size()); + try { + cathookOnlineService.userIdentify(steamIds, (std::function)>)[steamIds](co::ApiCallResult result, std::optional group) { + if (result == co::ApiCallResult::OK) + { + processIdentifyResponse(steamIds, *group); + } + else + { + logging::Info("[CO] Something went wrong while identifying %u players: code %d", steamIds.size(), result); + for (auto i: steamIds) + { + identify_queue[i] = false; + } + identify_stale = true; + } + }); + } catch (std::exception& ex) { + logging::Info("[CO] Exception: %s", ex.what()); + } +} + +InitRoutine init([]() { + cathookOnlineService.setErrorHandler((std::function)[](std::string error) { + logging::Info("[CO] Error: %s", error.c_str()); + }); + if (tryLoadApiKeyAndHost()) + { + logging::Info("[CO] API key loaded successfully"); + try { + cathookOnlineService.login(api_key, [](co::ApiCallResult result, std::optional me) { + if (result == co::ApiCallResult::OK) + { + logging::Info("[CO] Successfully logged in. Welcome, %s", me->username.c_str()); + claimSteamId(); + } + else + { + logging::Info("[CO] There was an error logging in: code %d", result); + } + }); + } catch (std::exception& ex) { + logging::Info("[CO] Exception: %s", ex.what()); + } + } +}); + +// EXTERNAL METHODS + +void update() +{ + if (!enable) + return; + // Only send a request after 3 seconds passed since last unknown steamId was added to the queue + if (!api_key.empty() && identify_stale && identify_timer.check(3000) && !identify_queue.empty()) + { + sendIdentifyRequest(); + identify_stale = false; + } + try { + cathookOnlineService.processPendingCalls(); + } catch (std::exception& ex) { + logging::Info("[CO] Exception: %s", ex.what()); + } +} + +user_data *getUserData(unsigned steamId) +{ + if (!enable) + return nullptr; + + if (!steamId) + return nullptr; + + auto it = data.find(steamId); + // User not identified + if (it == data.end()) + { + // Queue user for identification + if (identify_queue.find(steamId) == identify_queue.end()) + queueUserForIdentification(steamId); + return nullptr; + } + // SteamID belongs to online user + if (it->second.has_value()) + return &*it->second; + // SteamID does not belong to online user + return nullptr; +} + +} \ No newline at end of file diff --git a/src/visual/EffectChams.cpp b/src/visual/EffectChams.cpp index 55dec1e0..eb13fcea 100644 --- a/src/visual/EffectChams.cpp +++ b/src/visual/EffectChams.cpp @@ -227,8 +227,6 @@ bool EffectChams::ShouldRenderChams(IClientEntity *entity) if (entity->entindex() < 0) return false; CachedEntity *ent = ENTITY(entity->entindex()); - if (CE_BAD(ent)) - return false; if (ent->m_IDX == LOCAL_E->m_IDX && !chamsself) return false; switch (ent->m_Type()) @@ -319,12 +317,9 @@ void EffectChams::RenderChamsRecursive(IClientEntity *entity) } } -void EffectChams::RenderChams(int idx) +void EffectChams::RenderChams(IClientEntity *entity) { CMatRenderContextPtr ptr(GET_RENDER_CONTEXT); - IClientEntity *entity = g_IEntityList->GetClientEntity(idx); - if (entity && !entity->IsDormant()) - { if (ShouldRenderChams(entity)) { rgba_t color = ChamsColor(entity); @@ -348,7 +343,6 @@ void EffectChams::RenderChams(int idx) g_IVModelRender->ForcedMaterialOverride(flat ? mat_unlit : mat_lit); RenderChamsRecursive(entity); - } } } } @@ -366,11 +360,10 @@ void EffectChams::Render(int x, int y, int w, int h) BeginRenderChams(); for (int i = 1; i < HIGHEST_ENTITY; i++) { - IClientEntity *ent = g_IEntityList->GetClientEntity(i); - if (ent && !ent->IsDormant()) - { - RenderChams(i); - } + IClientEntity *entity = g_IEntityList->GetClientEntity(i); + if (!entity || entity->IsDormant() || CE_BAD(ENTITY(i))) + return; + RenderChams(entity); } EndRenderChams(); } diff --git a/src/visual/colors.cpp b/src/visual/colors.cpp index ea4a2555..0dd6badc 100644 --- a/src/visual/colors.cpp +++ b/src/visual/colors.cpp @@ -5,6 +5,8 @@ * Author: nullifiedcat */ +#include +#include #include "common.hpp" static CatVar user_red_blue(CV_INT, "esp_color_red_b", "0", "Red Team: Blue", @@ -130,9 +132,10 @@ rgba_t colors::EntityF(CachedEntity *ent) else if (ent->m_iTeam() == TEAM_RED) result = red_v; } - plclr = playerlist::Color(ent); - if (plclr.a) - result = plclr; + + auto o = player_tools::forceEspColor(ent); + if (o.has_value()) + return *o; } } @@ -144,3 +147,30 @@ rgba_t colors::RainbowCurrent() return colors::FromHSL(fabs(sin(g_GlobalVars->curtime / 2.0f)) * 360.0f, 0.85f, 0.9f); } + +static unsigned char hexToChar(char i) +{ + if (i >= '0' && i <= '9') + return i - '0'; + if (i >= 'a' && i <= 'f') + return i - 'a' + 10; + if (i >= 'A' && i <= 'F') + return i - 'A' + 10; + return 0; +} + +static unsigned int hexToByte(char hi, char lo) +{ + return (hexToChar(hi) << 4) | (hexToChar(lo)); +} + +colors::rgba_t::rgba_t(const char hex[6]) +{ + auto ri = hexToByte(hex[0], hex[1]); + auto gi = hexToByte(hex[2], hex[3]); + auto bi = hexToByte(hex[4], hex[5]); + r = float(ri) / 255.0f; + g = float(gi) / 255.0f; + b = float(bi) / 255.0f; + a = 1.0f; +}