diff --git a/data/menu/nullifiedcat/misc.xml b/data/menu/nullifiedcat/misc.xml index c18a9cd4..bafd71bd 100755 --- a/data/menu/nullifiedcat/misc.xml +++ b/data/menu/nullifiedcat/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/data/menu/nullifiedcat/misc/autoequip.xml b/data/menu/nullifiedcat/misc/autoequip.xml deleted file mode 100755 index ae916089..00000000 --- a/data/menu/nullifiedcat/misc/autoequip.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/data/menu/nullifiedcat/misc/autoitem.xml b/data/menu/nullifiedcat/misc/autoitem.xml new file mode 100755 index 00000000..9651f1e4 --- /dev/null +++ b/data/menu/nullifiedcat/misc/autoitem.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/include/hacks/Achievement.hpp b/include/hacks/Achievement.hpp deleted file mode 100644 index 5a040a21..00000000 --- a/include/hacks/Achievement.hpp +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Achievement.h - * - * Created on: Jan 20, 2017 - * Author: nullifiedcat - */ - -#pragma once - -namespace hacks::tf2::achievement -{ - -void Lock(); -void Unlock(); -} // namespace hacks::tf2::achievement diff --git a/include/hacks/hacklist.hpp b/include/hacks/hacklist.hpp index 86cb73f7..e89cf09a 100755 --- a/include/hacks/hacklist.hpp +++ b/include/hacks/hacklist.hpp @@ -24,7 +24,6 @@ #include "FollowBot.hpp" #include "Trigger.hpp" #include "UberSpam.hpp" -#include "Achievement.hpp" #include "Spam.hpp" #include "NavBot.hpp" #include "FollowBot.hpp" diff --git a/include/reclasses/CTFInventoryManager.hpp b/include/reclasses/CTFInventoryManager.hpp index e60be73a..0f31c9da 100644 --- a/include/reclasses/CTFInventoryManager.hpp +++ b/include/reclasses/CTFInventoryManager.hpp @@ -1,5 +1,5 @@ /* - * CTFInventoryManager.cpp + * CTFInventoryManager.hpp * * Created on: Apr 26, 2018 * Author: bencat07 @@ -19,6 +19,7 @@ public: class CEconItemView { public: + int GetDefinitionIndex(); unsigned long long UUID(); }; @@ -28,6 +29,9 @@ public: CTFPlayerInventory() = delete; public: + int GetItemCount(); + CEconItemView *GetItem(int idx); + std::vector GetItemsOfItemDef(int id); CEconItemView *GetFirstItemOfItemDef(int id); }; @@ -42,3 +46,6 @@ public: bool EquipItemInLoadout(int, int, unsigned long long); }; } // namespace re + +extern bool Craft(std::vector item_ids); +extern bool Rent(int item_id); diff --git a/src/hacks/Achievement.cpp b/src/hacks/Achievement.cpp deleted file mode 100644 index fd0e1886..00000000 --- a/src/hacks/Achievement.cpp +++ /dev/null @@ -1,507 +0,0 @@ -/* - * Achievement.cpp - * - * Created on: Jan 20, 2017 - * Author: nullifiedcat - */ - -#include -#include "Misc.hpp" -#include "common.hpp" - -namespace hacks::tf2::achievement -{ -static settings::Boolean safety{ "achievement.safety", "true" }; - -struct Autoequip_unlock_list -{ - std::string name; - int achievement_id; - int item_id; - tf_class player_class; - int slot; - Autoequip_unlock_list(std::string _name, int _ach_id, int _item_id, tf_class _player_class, int _slot) - { - name = _name; - achievement_id = _ach_id; - item_id = _item_id; - player_class = _player_class; - slot = _slot; - } -}; - -// Keep track of possible achievement items -static std::vector primary, secondary, melee, pda2; -// Keep track of stock items -static std::array equip_stock; - -static settings::Int equip_primary{ "achievement.equip-primary", "0" }; -static settings::Int equip_secondary{ "achievement.equip-secondary", "0" }; -static settings::Int equip_melee{ "achievement.equip-melee", "0" }; -static settings::Int equip_pda2{ "achievement.equip-pda2", "0" }; -#if ENABLE_TEXTMODE -static settings::Boolean auto_noisemaker{ "misc.auto-noisemaker", "true" }; -#else -static settings::Boolean auto_noisemaker{ "misc.auto-noisemaker", "false" }; -#endif - -bool checkachmngr() -{ - if (!g_IAchievementMgr) - { - g_IAchievementMgr = g_IEngine->GetAchievementMgr(); - if (!g_IAchievementMgr) - return false; - } - return true; -} -void Lock() -{ - if (!checkachmngr()) - return; - if (safety) - { - ConColorMsg({ 255, 0, 0, 255 }, "Switch `cat set achievement.safety` to false before using any achievement commands!\n"); - return; - } - g_ISteamUserStats->RequestCurrentStats(); - for (int i = 0; i < g_IAchievementMgr->GetAchievementCount(); i++) - { - g_ISteamUserStats->ClearAchievement(g_IAchievementMgr->GetAchievementByIndex(i)->GetName()); - } - g_ISteamUserStats->StoreStats(); - g_ISteamUserStats->RequestCurrentStats(); -} - -void Unlock() -{ - if (!checkachmngr()) - return; - /*auto Invmng = re::CTFInventoryManager::GTFInventoryManager(); - auto Inv = re::CTFPlayerInventory::GTFPlayerInventory(); - auto Item = Inv->GetFirstItemOfItemDef(59); - Invmng->EquipItemInLoadout(0, 0, (unsigned long long - int)Item->uniqueid());*/ - if (safety) - { - ConColorMsg({ 255, 0, 0, 255 }, "Switch `cat set achievement.safety` to false before using any achievement commands!\n"); - return; - } - for (int i = 0; i < g_IAchievementMgr->GetAchievementCount(); i++) - { - g_IAchievementMgr->AwardAchievement(g_IAchievementMgr->GetAchievementByIndex(i)->GetAchievementID()); - } -} - -CatCommand dump_achievement("achievement_dump", "Dump achievements to file (development)", []() { - if (!checkachmngr()) - return; - std::ofstream out("/tmp/cathook_achievements.txt", std::ios::out); - if (out.bad()) - return; - for (int i = 0; i < g_IAchievementMgr->GetAchievementCount(); i++) - { - out << '[' << i << "] " << g_IAchievementMgr->GetAchievementByIndex(i)->GetName() << ' ' << g_IAchievementMgr->GetAchievementByIndex(i)->GetAchievementID() << "\n"; - } - out.close(); -}); -CatCommand unlock_single("achievement_unlock_single", "Unlocks single achievement by ID", [](const CCommand &args) { - if (!checkachmngr()) - return; - char *out = nullptr; - int id = strtol(args.Arg(1), &out, 10); - if (out == args.Arg(1)) - { - logging::Info("NaN achievement ID!"); - return; - } - IAchievement *ach = reinterpret_cast(g_IAchievementMgr->GetAchievementByID(id)); - if (ach) - { - g_IAchievementMgr->AwardAchievement(id); - } -}); -// For some reason it SEGV's when I try to GetAchievementByID(); -CatCommand lock_single("achievement_lock_single", "Locks single achievement by INDEX!", [](const CCommand &args) { - if (!checkachmngr()) - return; - if (args.ArgC() < 2) - { - logging::Info("Actually provide an index"); - return; - } - int id; - try - { - id = std::stoi(args.Arg(1)); - } - catch (std::invalid_argument) - { - logging::Info("NaN achievement ID!"); - return; - } - IAchievement *ach = reinterpret_cast(g_IAchievementMgr->GetAchievementByID(id)); - - int index = -1; - if (ach) - for (int i = 0; i < g_IAchievementMgr->GetAchievementCount(); i++) - { - auto ach2 = g_IAchievementMgr->GetAchievementByIndex(i); - if (ach2->GetAchievementID() == id) - { - index = i; - break; - } - } - if (ach && index != -1) - { - g_ISteamUserStats->RequestCurrentStats(); - auto ach = g_IAchievementMgr->GetAchievementByIndex(index); - g_ISteamUserStats->ClearAchievement(ach->GetName()); - g_ISteamUserStats->StoreStats(); - g_ISteamUserStats->RequestCurrentStats(); - } -}); -CatCommand lock("achievement_lock", "Lock all achievements", Lock); -CatCommand unlock("achievement_unlock", "Unlock all achievements", Unlock); - -static bool accept_notifs; -static bool equip_hats; -static bool equip; -std::vector equip_queue; - -void unlock_achievements_and_accept(std::vector items) -{ - if (!checkachmngr()) - return; - for (auto id : items) - { - IAchievement *ach = reinterpret_cast(g_IAchievementMgr->GetAchievementByID(id)); - if (ach) - { - g_IAchievementMgr->AwardAchievement(id); - } - } - accept_notifs = true; -} - -static CatCommand get_sniper_items("achievement_sniper", "Get all sniper achievement items", []() { - static std::vector sniper_items = { 1136, 1137, 1138 }; - unlock_achievements_and_accept(sniper_items); -}); - -static Timer accept_time{}; -static Timer equip_noisemaker{}; -static Timer cooldowm{}; -static Timer cooldown_2{}; -static CatCommand get_best_hats("achievement_cathats", "Get and equip the bencat hats", []() { - static std::vector bencat_hats = { 1902, 1912, 2006 }; - unlock_achievements_and_accept(bencat_hats); - hacks::shared::misc::generate_schema(); - hacks::shared::misc::Schema_Reload(); - equip_hats = true; -}); - -struct queue_struct -{ - int clazz; - int slot; - unsigned long long uuid; -}; -static std::deque gc_queue; - -bool equip_item(int clazz, int slot, int id) -{ - auto invmng = re::CTFInventoryManager::GTFInventoryManager(); - auto inv = invmng->GTFPlayerInventory(); - // Request to equip a stock item - if (id == -1) - { - // -1 in Unsigned long long would be the maximum value - gc_queue.push_front({ clazz, slot, ULLONG_MAX }); - return true; - } - else - { - auto item_view = inv->GetFirstItemOfItemDef(id); - if (item_view) - { - gc_queue.push_front({ clazz, slot, item_view->UUID() }); - return true; - } - } - return false; -} -bool equip_action_item(int action_item, std::pair classes) -{ - for (int i = classes.first; i <= classes.second; i++) - if (!equip_item(i, 9, action_item)) - return false; - return true; -} - -bool equip_hats_fn(std::vector hats, std::pair classes) -{ - for (int i = classes.first; i <= classes.second; i++) - // 7...8...10. Wait why is it 10 tf2? you make no sense - if (!(equip_item(i, 7, hats[0]) && equip_item(i, 8, hats[1]) && equip_item(i, 10, hats[2]))) - return false; - return true; -} - -static std::vector hat_list = { 302, 940, 941 }; -// TNE didn't want me to put "epic_" infront of it :( -static Timer hat_steal_timer{}; -static Timer gc_timer{}; -// We need this incase a bot joins one class due to being stuck -// and then switches to the other while the stock equip is on -static std::array stock_equip_reset{}; -// We also need a delay between each attempt to not spam gc -static Timer stock_equip_wait{}; - -void CreateMove() -{ - if (gc_queue.size() && gc_timer.test_and_set(3000)) - { - for (int i = 0; i < 10 && gc_queue.size(); i++) - { - queue_struct item = gc_queue.at(gc_queue.size() - 1); - auto invmng = re::CTFInventoryManager::GTFInventoryManager(); - invmng->EquipItemInLoadout(item.clazz, item.slot, item.uuid); - gc_queue.pop_back(); - } - } - if (CE_BAD(LOCAL_E)) - return; - - // Wait 500ms between each call - if (LOCAL_E->m_bAlivePlayer() && stock_equip_wait.test_and_set(500)) - for (int i = 0; i < 4; i++) - { - // We should equip our stock weapons - if (equip_stock[i]) - { - tf_class player_class = (tf_class) g_pLocalPlayer->clazz; - // This is pretty much always true, however, there are exceptions. - int slot = i; - // Non spy can't change PDA (and engineer doesn't need to) - if (i == 3 && player_class != tf_spy) - { - equip_stock[i] = false; - continue; - } - // For spy the primary is in reality a secondary. No idea why they did that - if (player_class == tf_spy && slot == 0) - slot = 1; - // PDA slot ids are way different, adjust - if (slot == 3) - slot = 6; - - // If the timer is running for longer than 10s this is the first call after update - stock_equip_reset[i].test_and_set(10000); - - // After 3 seconds we should stop equipping - if (stock_equip_reset[i].check(3000)) - equip_stock[i] = false; - - // Equip the item (-1 for stock) - equip_item(player_class, slot, -1); - } - } -} - -void Callback(int after, int type) -{ - if (!after) - return; - std::vector equip_from; - // Equip stock weapons - if (after == 100 && type <= 3) - equip_stock[type] = true; - // Store the needed array - switch (type) - { - case 0: - if (after > primary.size()) - return; - equip_from = primary; - // primary - break; - case 1: - if (after > secondary.size()) - return; - equip_from = secondary; - // secondary - break; - case 2: - if (after > melee.size()) - return; - equip_from = melee; - // melee - break; - case 3: - if (after > pda2.size()) - return; - equip_from = pda2; - // PDA 2 - break; - } - if (equip_from.size()) - { - // Needs to get passed std vector of ints - std::vector pass = { equip_from.at(after - 1).achievement_id }; - // Unlock achievements and start accepting - unlock_achievements_and_accept(pass); - // equip queue - equip_queue.push_back(equip_from.at(after - 1)); - // Start equip process - equip = true; - } -} - -void Paint() -{ - // Start accepting - if (accept_notifs) - { - accept_time.update(); - accept_notifs = false; - } - // "Trigger/Accept first notification" aka Achievement items - if (!accept_time.check(5000) && cooldowm.test_and_set(500)) - g_IEngine->ClientCmd_Unrestricted("cl_trigger_first_notification"); - - // Noisemaker code - if (auto_noisemaker && equip_noisemaker.test_and_set(60000)) - { - // Inventory Manager - auto invmng = re::CTFInventoryManager::GTFInventoryManager(); - // Inventory - auto inv = invmng->GTFPlayerInventory(); - - // No Noisemaker in inventory? - if (!inv->GetFirstItemOfItemDef(673) && !inv->GetFirstItemOfItemDef(536)) - accept_time.update(); - else - { - bool success = equip_action_item(673, { tf_scout, tf_engineer }); - if (!success) - { - success = equip_action_item(536, { tf_scout, tf_engineer }); - if (!success) - logging::Info("Bruh moment while equipping noisemaker"); - } - } - } - // Hat equip code - if (equip_hats) - { - // If done start accepting notifications, also time out after a while - if (accept_time.check(5000) && !accept_time.check(10000) && cooldowm.test_and_set(500)) - { - // Inventory Manager - auto invmng = re::CTFInventoryManager::GTFInventoryManager(); - // Inventory - auto inv = invmng->GTFPlayerInventory(); - // Frontline field recorder - auto item_view1 = inv->GetFirstItemOfItemDef(302); - // Gibus - auto item_view2 = inv->GetFirstItemOfItemDef(940); - // Skull Island Tropper - auto item_view3 = inv->GetFirstItemOfItemDef(941); - if (item_view1 && item_view2 && item_view3) - { - if (!accept_time.check(7500)) - { - // Equip these hats on all classes - bool success = equip_hats_fn({ 302, 940, 941 }, { tf_scout, tf_engineer }); - if (success) - { - logging::Info("Equipping hats!"); - equip_hats = false; - } - } - } - } - else if (accept_time.check(10000)) - equip_hats = false; - } - // Equip weapons - if (equip) - { - // After 5 seconds of accept time, start - if (accept_time.check(5000) && !accept_time.check(10000) && cooldown_2.test_and_set(500)) - { - // Watch for each item and equip it - for (int i = 0; i < equip_queue.size(); i++) - { - auto equip = equip_queue.at(i); - bool success = equip_item(equip.player_class, equip.slot, equip.item_id); - - if (success) - { - logging::Info("Equipped Item!"); - equip_queue.erase(equip_queue.begin() + i); - } - } - // We did it - if (!equip_queue.size()) - equip = false; - } - else if (accept_time.check(10000) && cooldown_2.test_and_set(500)) - { - if (equip_queue.size()) - { - logging::Info("Equipping failed!"); - equip_queue.clear(); - } - } - } -}; - -static InitRoutine init([]() { - // Primary list - primary.push_back(Autoequip_unlock_list("Force-A-Nature", 1036, 45, tf_scout, 0)); - primary.push_back(Autoequip_unlock_list("Backburner", 1638, 40, tf_pyro, 0)); - primary.push_back(Autoequip_unlock_list("Natascha", 1538, 41, tf_heavy, 0)); - primary.push_back(Autoequip_unlock_list("Frontier Justice", 1801, 141, tf_engineer, 0)); - primary.push_back(Autoequip_unlock_list("Blutsauger", 1437, 36, tf_medic, 0)); - primary.push_back(Autoequip_unlock_list("Huntsman", 1136, 56, tf_sniper, 0)); - primary.push_back(Autoequip_unlock_list("Ambassador", 1735, 61, tf_spy, 1)); - primary.push_back(Autoequip_unlock_list("Direct hit", 1237, 127, tf_soldier, 0)); - - // Secondary list - secondary.push_back(Autoequip_unlock_list("Bonk!", 1038, 46, tf_scout, 1)); - secondary.push_back(Autoequip_unlock_list("Buff Banner", 1238, 129, tf_soldier, 1)); - secondary.push_back(Autoequip_unlock_list("Flare gun", 1637, 39, tf_pyro, 1)); - secondary.push_back(Autoequip_unlock_list("Chargin' Targe", 1336, 131, tf_demoman, 1)); - secondary.push_back(Autoequip_unlock_list("Scottish Resistance", 1638, 130, tf_demoman, 1)); - secondary.push_back(Autoequip_unlock_list("Sandvich", 1537, 42, tf_heavy, 1)); - secondary.push_back(Autoequip_unlock_list("Wrangler", 1803, 140, tf_engineer, 1)); - secondary.push_back(Autoequip_unlock_list("Kritzkrieg", 1438, 35, tf_medic, 1)); - secondary.push_back(Autoequip_unlock_list("Jarate", 1137, 58, tf_sniper, 1)); - secondary.push_back(Autoequip_unlock_list("Razorback", 1138, 57, tf_sniper, 1)); - - // Melee list - melee.push_back(Autoequip_unlock_list("Sandman", 1037, 44, tf_scout, 2)); - melee.push_back(Autoequip_unlock_list("Equalizer", 1236, 128, tf_soldier, 2)); - melee.push_back(Autoequip_unlock_list("Axtinguisher", 1639, 38, tf_pyro, 2)); - melee.push_back(Autoequip_unlock_list("Eyelander", 1337, 132, tf_demoman, 2)); - melee.push_back(Autoequip_unlock_list("Killing Gloves of Boxing", 1539, 43, tf_heavy, 2)); - melee.push_back(Autoequip_unlock_list("Gunslinger", 1802, 142, tf_engineer, 2)); - melee.push_back(Autoequip_unlock_list("Ubersaw", 1439, 37, tf_medic, 2)); - - // PDA 2 - pda2.push_back(Autoequip_unlock_list("Cloak and dagger", 1736, 60, tf_spy, 6)); - pda2.push_back(Autoequip_unlock_list("Deadringer", 1737, 59, tf_spy, 6)); - - // Callbacks - equip_primary.installChangeCallback([](settings::VariableBase &, int after) { Callback(after, 0); }); - equip_secondary.installChangeCallback([](settings::VariableBase &, int after) { Callback(after, 1); }); - equip_melee.installChangeCallback([](settings::VariableBase &, int after) { Callback(after, 2); }); - equip_pda2.installChangeCallback([](settings::VariableBase &, int after) { Callback(after, 3); }); - - EC::Register(EC::CreateMove, CreateMove, "cm_achivement"); - EC::Register(EC::Paint, Paint, "paint_achivement"); -}); -} // namespace hacks::tf2::achievement diff --git a/src/hacks/AutoItem.cpp b/src/hacks/AutoItem.cpp new file mode 100644 index 00000000..a8e2a7b5 --- /dev/null +++ b/src/hacks/AutoItem.cpp @@ -0,0 +1,523 @@ +/* + This file is part of Cathook. + + Cathook is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Cathook is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Cathook. If not, see . +*/ + +// Codeowners: aUniqueUser + +#include +#include +#include "common.hpp" + +namespace hacks::tf2::autoitem +{ + +static settings::Boolean enable{ "auto-item.enable", "false" }; +static settings::Int interval{ "auto-item.time", "30000" }; + +// stock by default +static settings::Boolean weapons{ "auto-item.weapons", "false" }; +static settings::String primary{ "auto-item.weapons.primary", "-1" }; +static settings::String secondary{ "auto-item.weapons.secondary", "-1" }; +static settings::String melee{ "auto-item.weapons.melee", "-1" }; + +// ben cat hats by default +static settings::Boolean hats{ "auto-item.hats", "false" }; +static settings::Int hat1{ "auto-item.hats.1", "940" }; +static settings::Int hat2{ "auto-item.hats.2", "941" }; +static settings::Int hat3{ "auto-item.hats.3", "302" }; + +static settings::Boolean debug{ "auto-item.debug", "false" }; + +#define Debug(...) \ + if (*debug) \ + logging::Info("AutoItem.cpp: " __VA_ARGS__) + +#if ENABLE_TEXTMODE +static settings::Boolean autoNoisemaker{ "misc.auto-noisemaker", "true" }; +#else +static settings::Boolean autoNoisemaker{ "misc.auto-noisemaker", "false" }; +#endif + +// 536 is Birthday noisemaker +// 673 is Christmas noisemaker +static int noisemaker_id = 536; + +std::vector> ach_items; +std::vector> craft_groups; + +bool checkAchMgr() +{ + if (!g_IAchievementMgr) + { + g_IAchievementMgr = g_IEngine->GetAchievementMgr(); + if (!g_IAchievementMgr) + return false; + } + return true; +} + +void Lock() +{ + if (!checkAchMgr()) + return; + g_ISteamUserStats->RequestCurrentStats(); + for (int i = 0; i < g_IAchievementMgr->GetAchievementCount(); i++) + { + g_ISteamUserStats->ClearAchievement(g_IAchievementMgr->GetAchievementByIndex(i)->GetName()); + } + g_ISteamUserStats->StoreStats(); + g_ISteamUserStats->RequestCurrentStats(); +} + +void Unlock() +{ + if (!checkAchMgr()) + return; + for (int i = 0; i < g_IAchievementMgr->GetAchievementCount(); i++) + { + g_IAchievementMgr->AwardAchievement(g_IAchievementMgr->GetAchievementByIndex(i)->GetAchievementID()); + } +} + +void unlockSingle(int achID) +{ + if (!checkAchMgr()) + return; + auto *ach = reinterpret_cast(g_IAchievementMgr->GetAchievementByID(achID)); + if (ach) + { + g_IAchievementMgr->AwardAchievement(achID); + } +} + +std::pair isAchItem(int id) +{ + for (auto &i : ach_items) + { + if (std::get<0>(i) == id) + return std::make_pair(std::get<1>(i), std::get<2>(i)); + } + return std::make_pair(0, ""); +} + +void getItem(int id, bool rent = true) +{ + Debug("Trying to get item, %i", id); + auto index = isAchItem(id); + if (index.first != 0) + { + unlockSingle(index.first); + g_IEngine->ClientCmd_Unrestricted("cl_trigger_first_notification"); + } + else if (rent) + Rent(id); + else + Debug("Failed to get item %i", id); +} + +static bool equipItem(int clazz, int slot, int id, bool get = true, bool allowRent = false) +{ + auto invmng = re::CTFInventoryManager::GTFInventoryManager(); + auto inv = invmng->GTFPlayerInventory(); + + // Slot correction for spy + if (g_pLocalPlayer->clazz == tf_spy) + { + // Secondary -> Cloak + if (slot == 1) + slot = 6; + } + + if (get) + { + auto item_view = inv->GetFirstItemOfItemDef(id); + if (!item_view) + { + getItem(id, allowRent); + return false; + } + } + if (id == -1) + return invmng->EquipItemInLoadout(clazz, slot, -1); + + auto item_view = inv->GetFirstItemOfItemDef(id); + if (item_view) + return invmng->EquipItemInLoadout(clazz, slot, item_view->UUID()); + + return false; +} + +void parseRvars() +{ + craft_groups.clear(); + + std::vector tmp_vec; + + boost::split(tmp_vec, *primary, boost::is_any_of(";-")); + craft_groups.emplace_back(tmp_vec); + + boost::split(tmp_vec, *secondary, boost::is_any_of(";-")); + craft_groups.emplace_back(tmp_vec); + + boost::split(tmp_vec, *melee, boost::is_any_of(";-")); + craft_groups.emplace_back(tmp_vec); +} + +bool use_fallback = false; +int first_item_attempts = 0; +void getAndEquipWeapon(std::string str, int clazz, int slot) +{ + auto invmng = re::CTFInventoryManager::GTFInventoryManager(); + auto inv = invmng->GTFPlayerInventory(); + + if (str == "-1") + { + equipItem(clazz, slot, -1, false, false); + return; + } + + // Single item, rent or simply get via achievement, use fallback if needed and specified. + if (str.find(',') == std::string::npos && str.find(';') == std::string::npos) + { + std::vector ids_split_str; + std::vector ids_split; + try + { + if (str.find('/') != std::string::npos) + { + boost::split(ids_split_str, str, boost::is_any_of("/")); + for (auto &id : ids_split_str) + { + ids_split.emplace_back(std::stoi(id)); + } + use_fallback = true; + } + else + ids_split.emplace_back(std::stoi(str)); + } + catch (std::invalid_argument &e) + { + Debug("invalid_argument error making ids_split vector."); + return; + } + + auto item_view = inv->GetFirstItemOfItemDef(ids_split.at(0)); + + // Try the first item 3 times before moving to fallback + if (use_fallback) + { + if (!item_view && first_item_attempts >= 3) + equipItem(clazz, slot, ids_split.at(1), true, true); + else + { + equipItem(clazz, slot, ids_split.at(0), true, true); + first_item_attempts++; + } + } + else + equipItem(clazz, slot, ids_split.at(0), true, true); + } + else + { + // Using auto-craft + int result; + int loc = str.find('-'); + if (loc != std::string::npos) + { + try + { + result = std::stoi(str.substr(loc + 1, str.length())); + } + catch (std::invalid_argument &e) + { + Debug("invalid_argument error making result integer."); + return; + } + + auto item_view = inv->GetFirstItemOfItemDef(result); + if (item_view) + { + equipItem(clazz, slot, result, false, false); + return; + } + + std::vector ids_rec_str; + std::vector ids_rec; + + for (auto &group_str : craft_groups[slot]) + { + Debug("Crafting group: %s", group_str.c_str()); + ids_rec.clear(); + ids_rec_str.clear(); + + // Last group should be the just the result, skip it + if (group_str == std::to_string(result)) + continue; + + // Split this crafting group into IDs + boost::split(ids_rec_str, group_str, boost::is_any_of(",")); + + try + { + // Convert to ints + for (auto &id : ids_rec_str) + { + ids_rec.emplace_back(std::stoi(id)); + } + } + catch (std::invalid_argument &e) + { + Debug("invalid_argument error making rec ids vector."); + return; + } + + // See if we have the requirements to perform this craft, if not try to get them. + int rec_req_amount_have = 0; + for (auto &id : ids_rec) + { + // For now we should just assume the user has + // provided a way for getting needed scrap + if (id == 5000 || id == 5001 || id == 5002) + { + rec_req_amount_have++; + continue; + } + + // In this loop id is a item id of 1 part of the crafting group + auto item_view = inv->GetFirstItemOfItemDef(id); + if (item_view) + rec_req_amount_have++; + else + { + auto index = isAchItem(id); + if (index.first != 0) + { + if (!checkAchMgr()) + return; + if (g_IAchievementMgr->HasAchieved(index.second.c_str())) + { + Debug("Cant get specified ach item %i because it has already been unlocked! moving to next item.", id); + // User's fault if they put a ach item they unlocked and have used for crafting already/deleted. + rec_req_amount_have++; + continue; + } + Debug("Getting ach item %i, required for this craft.", id); + getItem(id, false); + return; + } + else + { + Debug("Missing required crafting materials! %i is NOT a ach item!", id); + return; + } + } + } + // Only attempt crafting if we have the correct amount of items. + if (rec_req_amount_have == ids_rec.size()) + { + Craft(ids_rec); + g_IEngine->ClientCmd_Unrestricted("cl_trigger_first_notification"); + } + } + } + else + { + Debug("No result item, can't auto-craft."); + return; + } + } +} + +static Timer t{}; +void CreateMove() +{ + if (!enable || CE_BAD(LOCAL_E) || !t.test_and_set(*interval)) + return; + + int clazz = g_pLocalPlayer->clazz; + auto invmng = re::CTFInventoryManager::GTFInventoryManager(); + auto inv = invmng->GTFPlayerInventory(); + + // Only run if we are playing a valid class + if (clazz != 0) + { + if (weapons) + { + getAndEquipWeapon(*primary, clazz, 0); + getAndEquipWeapon(*secondary, clazz, 1); + getAndEquipWeapon(*melee, clazz, 2); + } + if (hats) + { + static int offset = 0; + const static int slots[3] = { 7, 8, 10 }; + + equipItem(clazz, slots[offset], *hat1); + equipItem(clazz, slots[(offset + 1) % 3], *hat2); + equipItem(clazz, slots[(offset + 2) % 3], *hat3); + offset = (offset + 1) % 3; + } + if (autoNoisemaker && inv->GetFirstItemOfItemDef(noisemaker_id)) + { + equipItem(clazz, 9, noisemaker_id, false, false); + } + } +} + +CatCommand dump_achievement("achievement_dump", "Dump achievements to file (development)", []() { + if (!checkAchMgr()) + return; + std::ofstream out("/tmp/cathook_achievements.txt", std::ios::out); + if (out.bad()) + return; + for (int i = 0; i < g_IAchievementMgr->GetAchievementCount(); i++) + { + out << '[' << i << "] " << g_IAchievementMgr->GetAchievementByIndex(i)->GetName() << ' ' << g_IAchievementMgr->GetAchievementByIndex(i)->GetAchievementID() << "\n"; + } + out.close(); +}); + +CatCommand unlock_single("achievement_unlock_single", "Unlocks single achievement by ID", [](const CCommand &args) { + if (!checkAchMgr()) + return; + char *out = nullptr; + int id = strtol(args.Arg(1), &out, 10); + if (out == args.Arg(1)) + { + logging::Info("NaN achievement ID!"); + return; + } + unlockSingle(id); +}); + +// For some reason it SEGV's when I try to GetAchievementByID(); +CatCommand lock_single("achievement_lock_single", "Locks single achievement by INDEX!", [](const CCommand &args) { + if (!checkAchMgr()) + return; + if (args.ArgC() < 2) + { + logging::Info("Actually provide an index"); + return; + } + int id; + try + { + id = std::stoi(args.Arg(1)); + } + catch (std::invalid_argument) + { + logging::Info("Bad achievement ID!"); + return; + } + auto *ach = reinterpret_cast(g_IAchievementMgr->GetAchievementByID(id)); + + int index = -1; + if (ach) + for (int i = 0; i < g_IAchievementMgr->GetAchievementCount(); i++) + { + auto ach2 = g_IAchievementMgr->GetAchievementByIndex(i); + if (ach2->GetAchievementID() == id) + { + index = i; + break; + } + } + if (ach && index != -1) + { + g_ISteamUserStats->RequestCurrentStats(); + auto ach = g_IAchievementMgr->GetAchievementByIndex(index); + g_ISteamUserStats->ClearAchievement(ach->GetName()); + g_ISteamUserStats->StoreStats(); + g_ISteamUserStats->RequestCurrentStats(); + } +}); + +CatCommand rent_item("rent_item", "testrun a item by ID", [](const CCommand &args) { + char *out = nullptr; + int id = strtol(args.Arg(1), &out, 10); + if (out == args.Arg(1)) + { + logging::Info("Bad item ID!"); + return; + } + Rent(id); +}); + +CatCommand lock("achievement_lock", "Lock all achievements", Lock); +CatCommand unlock("achievement_unlock", "Unlock all achievements", Unlock); + +static InitRoutine init([]() { + primary.installChangeCallback([](settings::VariableBase &var, const std::string &after) { parseRvars(); }); + secondary.installChangeCallback([](settings::VariableBase &var, const std::string &after) { parseRvars(); }); + melee.installChangeCallback([](settings::VariableBase &var, const std::string &after) { parseRvars(); }); + + EC::Register(EC::CreateMove, CreateMove, "autoitem_cm"); + + std::time_t theTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + std::tm *aTime = std::localtime(&theTime); + + int day = aTime->tm_mday; + int month = aTime->tm_mon + 1; // Month is 0 - 11, add 1 to get a jan-dec 1-12 concept + + // We only want to Use the christmas noisemaker around christmas time, let's use 12th of december+ til 12th of january + if ((month == 12 && day >= 12) || (month == 1 && day <= 12)) + noisemaker_id = 673; + + /* haha emplace_back go brrrrrr */ + ach_items.emplace_back(45, 1036, "TF_SCOUT_ACHIEVE_PROGRESS1"); // Force-A-Nature - Scout Milestone 1 + ach_items.emplace_back(44, 1037, "TF_SCOUT_ACHIEVE_PROGRESS2"); // Sandman - Scout Milestone 2 + ach_items.emplace_back(46, 1038, "TF_SCOUT_ACHIEVE_PROGRESS3"); // Bonk! Atomic Punch - Scout Milestone 3 + ach_items.emplace_back(128, 1236, "TF_SOLDIER_ACHIEVE_PROGRESS1"); // Equalizer - Soldier Milestone 1 + ach_items.emplace_back(127, 1237, "TF_SOLDIER_ACHIEVE_PROGRESS2"); // Direct Hit - Soldier Milestone 2 + ach_items.emplace_back(129, 1238, "TF_SOLDIER_ACHIEVE_PROGRESS3"); // Buff Banner - Soldier Milestone 3 + ach_items.emplace_back(39, 1637, "TF_PYRO_ACHIEVE_PROGRESS1"); // Flare Gun - Pyro Milestone 1 + ach_items.emplace_back(40, 1638, "TF_PYRO_ACHIEVE_PROGRESS2"); // Backburner - Pyro Milestone 2 + ach_items.emplace_back(38, 1639, "TF_PYRO_ACHIEVE_PROGRESS3"); // Axtinguisher - Pyro Milestone 3 + ach_items.emplace_back(131, 1336, "TF_DEMOMAN_ACHIEVE_PROGRESS1"); // Chargin' Targe - Demoman Milestone 1 + ach_items.emplace_back(132, 1337, "TF_DEMOMAN_ACHIEVE_PROGRESS2"); // Eyelander - Demoman Milestone 2 + ach_items.emplace_back(130, 1338, "TF_DEMOMAN_ACHIEVE_PROGRESS3"); // Scottish Resistance - Demoman Milestone 3 + ach_items.emplace_back(42, 1537, "TF_HEAVY_ACHIEVE_PROGRESS1"); // Sandvich - Heavy Milestone 1 + ach_items.emplace_back(41, 1538, "TF_HEAVY_ACHIEVE_PROGRESS2"); // Natascha - Heavy Milestone 2 + ach_items.emplace_back(43, 1539, "TF_HEAVY_ACHIEVE_PROGRESS3"); // Killing Gloves of Boxing - Heavy Milestone 3 + ach_items.emplace_back(141, 1801, "TF_ENGINEER_ACHIEVE_PROGRESS1"); // Frontier Justice - Engineer Milestone 1 + ach_items.emplace_back(142, 1802, "TF_ENGINEER_ACHIEVE_PROGRESS2"); // Gunslinger - Engineer Milestone 2 + ach_items.emplace_back(140, 1803, "TF_ENGINEER_ACHIEVE_PROGRESS3"); // Wrangler - Engineer Milestone 3 + ach_items.emplace_back(36, 1437, "TF_MEDIC_ACHIEVE_PROGRESS1"); // Blutsauger - Medic Milestone 1 + ach_items.emplace_back(35, 1438, "TF_MEDIC_ACHIEVE_PROGRESS2"); // Kritzkrieg - Medic Milestone 2 + ach_items.emplace_back(37, 1439, "TF_MEDIC_ACHIEVE_PROGRESS3"); // Ubersaw - Medic Milestone 3 + ach_items.emplace_back(56, 1136, "TF_SNIPER_ACHIEVE_PROGRESS1"); // Huntsman - Sniper Milestone 1 + ach_items.emplace_back(58, 1137, "TF_SNIPER_ACHIEVE_PROGRESS2"); // Jarate - Sniper Milestone 2 + ach_items.emplace_back(57, 1138, "TF_SNIPER_ACHIEVE_PROGRESS3"); // Razorback - Sniper Milestone 3 + ach_items.emplace_back(61, 1735, "TF_SPY_ACHIEVE_PROGRESS1"); // Ambassador - Spy Milestone 1 + ach_items.emplace_back(60, 1736, "TF_SPY_ACHIEVE_PROGRESS2"); // Cloak and Dagger - Spy Milestone 2 + ach_items.emplace_back(59, 1737, "TF_SPY_ACHIEVE_PROGRESS3"); // Dead Ringer - Spy Milestone 3 + ach_items.emplace_back(1123, 1928, "TF_HALLOWEEN_DOOMSDAY_MILESTONE"); // Necro Smasher - Carnival of Carnage: Step Right Up + ach_items.emplace_back(940, 1902, "TF_HALLOWEEN_DOMINATE_FOR_HAT"); // Ghostly Gibus - Ghastly Gibus Grab + ach_items.emplace_back(115, 1901, "TF_HALLOWEEN_COLLECT_PUMPKINS"); // Mildly Disturbing Halloween Mask - Candy Coroner + ach_items.emplace_back(278, 1906, "TF_HALLOWEEN_BOSS_KILL"); // Horseless Headless Horsemann's Head - Sleepy Holl0WND + ach_items.emplace_back(302, 2006, "TF_REPLAY_YOUTUBE_VIEWS_TIER2"); // Frontline Field Recorder - Local Cinema Star + ach_items.emplace_back(581, 1910, "TF_HALLOWEEN_EYEBOSS_KILL"); // MONOCULUS! - Optical Defusion + ach_items.emplace_back(668, 2212, "TF_MAPS_FOUNDRY_ACHIEVE_PROGRESS1"); // Full Head Of Steam - Foundry Milestone + ach_items.emplace_back(756, 2412, "TF_MAPS_DOOMSDAY_ACHIEVE_PROGRESS1"); // Gentle Munitionne of Leisure - Doomsday Milestone + ach_items.emplace_back(941, 1912, "TF_HALLOWEEN_MERASMUS_COLLECT_LOOT"); // Skull Island Topper - A Lovely Vacation Spot + ach_items.emplace_back(581, 1911, "TF_HALLOWEEN_LOOT_ISLAND"); // Bombinomicon - Dive Into a Good Book + ach_items.emplace_back(744, 156, "TF_DOMINATE_FOR_GOGGLES"); // Pyrovision Goggles - A Fresh Pair of Eyes + ach_items.emplace_back(1164, 167, "TF_PASS_TIME_GRIND"); // Civilian Grade JACK Hat - Jackpot! + ach_items.emplace_back(1169, 166, "TF_PASS_TIME_HAT"); // Military Grade JACK Hat - Tune Merasmus's Multi-Dimensional Television + ach_items.emplace_back(1170, 166, "TF_PASS_TIME_HAT"); // PASS Time Miniature Half JACK - Tune Merasmus's Multi-Dimensional Television + ach_items.emplace_back(267, 1909, "TF_HALLOWEEN_BOSS_KILL_MELEE"); // Haunted Metal Scrap - Gored! +}); +} // namespace hacks::tf2::autoitem diff --git a/src/hacks/CMakeLists.txt b/src/hacks/CMakeLists.txt index 4ed45a94..b9792714 100755 --- a/src/hacks/CMakeLists.txt +++ b/src/hacks/CMakeLists.txt @@ -1,7 +1,7 @@ set(files "${CMAKE_CURRENT_LIST_DIR}/AutoJoin.cpp" "${CMAKE_CURRENT_LIST_DIR}/CatBot.cpp" "${CMAKE_CURRENT_LIST_DIR}/Spam.cpp" - "${CMAKE_CURRENT_LIST_DIR}/Achievement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/AutoItem.cpp" "${CMAKE_CURRENT_LIST_DIR}/Aimbot.cpp" "${CMAKE_CURRENT_LIST_DIR}/AutoViewmodel.cpp" "${CMAKE_CURRENT_LIST_DIR}/MiscAimbot.cpp" diff --git a/src/reclasses/CTFInventoryManager.cpp b/src/reclasses/CTFInventoryManager.cpp index 10b5a0ba..9ec1172f 100644 --- a/src/reclasses/CTFInventoryManager.cpp +++ b/src/reclasses/CTFInventoryManager.cpp @@ -6,6 +6,7 @@ */ #include "common.hpp" #include "e8call.hpp" +#include "DetourHook.hpp" using namespace re; CTFInventoryManager *CTFInventoryManager::GTFInventoryManager() @@ -31,6 +32,31 @@ CTFPlayerInventory *CTFInventoryManager::GTFPlayerInventory() return vfunc(this, offsets::PlatformOffset(21, offsets::undefined, 22), 0)(this);*/ } +int CTFPlayerInventory::GetItemCount() +{ + return *(int *) ((char *) this + 80); +} + +#define SIZE_OF_ITEMVIEW 228 +CEconItemView *CTFPlayerInventory::GetItem(int idx) +{ + uintptr_t item_start = *(uintptr_t *) ((char *) this + 68); + uintptr_t item_offset = idx * SIZE_OF_ITEMVIEW; + return (CEconItemView *) (item_start + item_offset); +} + +std::vector CTFPlayerInventory::GetItemsOfItemDef(int id) +{ + std::vector uuid_vec; + for (int i = 0; i < this->GetItemCount(); i++) + { + auto item = this->GetItem(i); + if (item->GetDefinitionIndex() == id) + uuid_vec.push_back(item->UUID()); + } + return uuid_vec; +} + CEconItemView *CTFPlayerInventory::GetFirstItemOfItemDef(int id) { typedef CEconItemView *(*GetFirstItemOfItemDef_t)(int16_t, void *); @@ -39,6 +65,11 @@ CEconItemView *CTFPlayerInventory::GetFirstItemOfItemDef(int id) return GetFirstItemOfItemDef_fn(id, this); } +int CEconItemView::GetDefinitionIndex() +{ + return *(ushort *) ((uintptr_t) this + 0x24); +} + unsigned long long CEconItemView::UUID() { unsigned long long value = *(unsigned long long *) ((char *) this + 56); @@ -53,7 +84,123 @@ static CatCommand equip_debug("equip_debug", "Debug auto equip stuff", []() { auto item_view = inv->GetFirstItemOfItemDef(56); if (item_view) { - logging::Info("%llu %llu", item_view->UUID()); + logging::Info("%llu", item_view->UUID()); logging::Info("Equip item: %d", invmng->EquipItemInLoadout(tf_sniper, 0, item_view->UUID())); } }); + +static CatCommand list_debug("equip_list_debug", "Debug item def listing", []() { + auto invmng = CTFInventoryManager::GTFInventoryManager(); + auto inv = invmng->GTFPlayerInventory(); + auto items = inv->GetItemsOfItemDef(5000); + for (auto item : items) + logging::Info("%llu", item); +}); + +// Crafting slots on crafting page +#define CRAFTING_SLOTS_INPUT_ROWS 3 +#define CRAFTING_SLOTS_INPUT_COLUMNS 4 +#define CRAFTING_SLOTS_INPUTPANELS (CRAFTING_SLOTS_INPUT_ROWS * CRAFTING_SLOTS_INPUT_COLUMNS) + +class CCraftingPanel_reclass2 +{ +public: + char unknown_data[560]; + // Items in the input model panels + unsigned long long m_InputItems[CRAFTING_SLOTS_INPUTPANELS]; + const int *m_ItemPanelCriteria[CRAFTING_SLOTS_INPUTPANELS]; + + char unknown_data2[72]; + int m_iCurrentlySelectedRecipe; + + char unknown_data3[50]; + + int m_iCraftingAttempts; +}; + +// Pass the Definition Indexes of the items you want to craft with (https://wiki.alliedmods.net/Team_Fortress_2_Item_Definition_Indexes) +bool Craft(std::vector item_ids) +{ + static auto craft_func = gSignatures.GetClientSignature("55 89 E5 57 56 53 83 EC 6C C7 45 ? 00 00 00 00 " + "8B 45 ? C7 45 ? 00 00 00 00 C7 45 ? 00 00 00 " + "00 C7 45 ? 00 00 00 00 C7 45 ? 00 00 00 00 83 80 ? ? ? ? 01"); + + // Return early to avoid a crash + static auto patch_addr = craft_func + 0x22e; + static BytePatch patch(patch_addr, { 0x83, 0xc4, 0x6c, 0x5b, 0x5e, 0x5f, 0x5d, 0xc3 }); + patch.Patch(); + + CCraftingPanel_reclass2 panel{}; + auto invmng = CTFInventoryManager::GTFInventoryManager(); + auto inv = invmng->GTFPlayerInventory(); + + std::vector item_UUIDs; + + // Iterate all desired items + for (auto &id : item_ids) + { + auto items = inv->GetItemsOfItemDef(id); + // Iterate all the items matching the ID + for (auto &item : items) + { + // Only add entries which are not added yet + bool failed = false; + for (auto &uuid : item_UUIDs) + if (uuid == item) + failed = true; + + // Continue to next entry on failure + if (failed) + continue; + else + { + item_UUIDs.push_back(item); + break; + } + } + } + + // No items found + if (item_UUIDs.empty()) + return false; + + // Add items + for (int i = 0; i < item_UUIDs.size(); i++) + { + panel.m_InputItems[i] = item_UUIDs[i]; + } + + // Select "Custom" Recipe + panel.m_iCurrentlySelectedRecipe = -2; + + // Call the Craft function + typedef void (*Craft_t)(CCraftingPanel_reclass2 *); + Craft_t Craft_fn = (Craft_t) craft_func; + Craft_fn(&panel); + + // Undo our patches to the game + patch.Shutdown(); + return true; +} + +bool Rent(int item_id) +{ + typedef void (*DoPreviewItem_t)(void *, int); + static auto DoPreviewItem_addr = gSignatures.GetClientSignature("55 89 E5 56 53 81 EC 50 01 00 00 65 A1 ? ? ? ? 89 45 ? 31 C0 0F B7 75"); + static auto id = (DoPreviewItem_addr + 0xf3); + + // Request the item instead of testing if we can request it + static BytePatch id_patch(id, { 0xa7 }); + id_patch.Patch(); + + auto DoPreviewItem_fn = (DoPreviewItem_t) DoPreviewItem_addr; + + // The nullptr is "this" but it's unused in the function + DoPreviewItem_fn(nullptr, item_id); + + // Reset the id + id_patch.Shutdown(); + return true; +} + +static CatCommand debug_panel("equip_debug_panel", "Debug the Crafting panel that gets used", []() { Craft({ 5000, 5000, 5000 }); });