diff --git a/src/Client/Module/Modules/TotemCounter/TotemCounter.cpp b/src/Client/Module/Modules/TotemCounter/TotemCounter.cpp index 51cad938d..d8cccdaf4 100644 --- a/src/Client/Module/Modules/TotemCounter/TotemCounter.cpp +++ b/src/Client/Module/Modules/TotemCounter/TotemCounter.cpp @@ -2,36 +2,50 @@ #include "Events/Game/TickEvent.hpp" - +#include "Modules/ClickGUI/ClickGUI.hpp" void TotemCounter::onEnable() { Listen(this, TickEvent, &TotemCounter::onTick) Listen(this, RenderEvent, &TotemCounter::onRender) + Listen(this, PacketEvent, &TotemCounter::onPacketEvent) + + popsById.clear(); + Module::onEnable(); } void TotemCounter::onDisable() { Deafen(this, TickEvent, &TotemCounter::onTick) Deafen(this, RenderEvent, &TotemCounter::onRender) + Deafen(this, PacketEvent, &TotemCounter::onPacketEvent) Module::onDisable(); } void TotemCounter::defaultConfig() { setDef("text", (std::string)"Totems: {value}"); + setDef("textUsed", (std::string)"Pops: {value}"); setDef("onlyRenderWhenHoldingTotem", false); + setDef("mode", (std::string)"Current"); + setDef("notifyOnPop", false); + setDef("listenForOthers", false); Module::defaultConfig("all"); - } void TotemCounter::settingsRender(float settingsOffset) { initSettingsPage(); - addToggle("Only render when holding totem.", "", "onlyRenderWhenHoldingTotem"); + addDropdown("Mode", "Choose the working mode.", std::vector{"Current", "Pop", "Both"}, "mode", true); + addToggle("Text Label: Totem Held Only", "Only show the label text when holding a totem.", "onlyRenderWhenHoldingTotem"); defaultAddSettings("main"); extraPadding(); addHeader("Text"); - defaultAddSettings("text"); + addTextBox("Normal Format", "Use {value} for the value.", 0, "text"); + addTextBox("Pops Format", "Use {value} for the value.", 0, "textUsed"); + addSlider("Text Scale", "", "textscale", 2.0f); + addDropdown("Text Alignment", "", std::vector{"Left", "Center", "Right"}, "textalignment", true); + addToggle("Text Shadow", "Displays a shadow under the text", "textShadow"); + addConditionalSlider(getOps("textShadow"), "Shadow Offset", "How far the shadow will be.", "textShadowOffset", 0.02f, 0.001f); extraPadding(); addHeader("Colors"); @@ -41,6 +55,13 @@ void TotemCounter::settingsRender(float settingsOffset) { addHeader("Misc"); defaultAddSettings("misc"); + addHeader("Chat Notfications"); + addButton("Clear Stored Pops", "I'm keeping this here until I figure out a proper way for this.", "Click Here", [this](){popsById.clear();}); + addToggle("Notify on Pop", "Notifies in chat when a totem pop happens", "notifyOnPop"); + addConditionalToggle(getOps("notifyOnPop") ,"Notifier: Listen for Others", "Notifies in chat when other player(s) pop their totems as well.", "listenForOthers"); + extraPadding(); + + FlarialGUI::UnsetScrollView(); resetPadding(); } @@ -100,11 +121,78 @@ void TotemCounter::onTick(TickEvent& event) { totems = lastTotemCount; } + + if (SDK::getCurrentScreen() == "progress_screen") popsById.clear(); +} + +void TotemCounter::onPacketEvent(PacketEvent& event) +{ + auto eep = reinterpret_cast(event.getPacket()); + // is enabled, notification on, packet is actorevent, ID is talismanactivate, server is not the hive (is there even a minigame that actually uses it?) + if (!isEnabled() || !getOps("notifyOnPop") || eep->getId() != MinecraftPacketIds::ActorEvent || SDK::getServerIP() == "geo.hivebedrock.network") return; + + auto id = eep->RuntimeID; + auto actor = SDK::clientInstance->getLocalPlayer()->getLevel()->getRuntimeEntity(id, false); + if (!actor) return Logger::debug("what the skibidi toilet"); + + const std::string baseName = Utils::sanitizeName(actor->getNametag() ? *actor->getNametag() : std::string("blahaj")); + const std::string playerName = (id == SDK::clientInstance->getLocalPlayer()->getRuntimeIDComponent()->runtimeID) ? std::string("You") : baseName; + + if (eep->EventID == ActorEvent::TalismanActivate) + { + // Workaround until I figure out how to properly filter packets. + static std::unordered_map lastPopAt; + const auto now = std::chrono::steady_clock::now(); + auto& t = lastPopAt[id]; + if (now - t < std::chrono::milliseconds(100)) return; + t = now; + + int pops = ++popsById[id]; + + SDK::clientInstance->getGuiData()->displayClientMessage("[§cTotem Counter§r] " + playerName + " popped §7" + std::to_string(pops) + "§r totem" + (pops > 1 ? "s" : "") + "."); + } else if (eep->EventID == ActorEvent::Death) + { + if (popsById[id] == 0 || (!getOps("listenForOthers") && id != SDK::clientInstance->getLocalPlayer()->getRuntimeIDComponent()->runtimeID)) return; + SDK::clientInstance->getGuiData()->displayClientMessage("[§cTotem Counter§r] " + playerName + " died after popping §7" + std::to_string(popsById[id]) + "§r totem" + (popsById[id] > 1 ? "s" : "") + "."); + popsById[id] = 0; + } } void TotemCounter::onRender(RenderEvent& event) { - if (!this->isEnabled() || !shouldRender) return; + if (!this->isEnabled() || !shouldRender || SDK::getCurrentScreen() != "hud_screen") return; + + // you wanted this + auto replaceValueToken = [](const std::string& tmpl, const std::string& val) -> std::string { + std::string upper; + upper.reserve(tmpl.size()); + for (char c: tmpl) upper += (char)std::toupper(c); + const std::string search = "{VALUE}"; + size_t pos = upper.find(search); + std::string out = tmpl; + if (pos != std::string::npos) out.replace(pos, search.length(), val); + return out; + }; + + std::string mode = "Current"; + if (this->settings.getSettingByName("mode") != nullptr) mode = getOps("mode"); + + std::string finalText; + + const std::string playerPops = SDK::clientInstance->getLocalPlayer() ? std::to_string(popsById[SDK::clientInstance->getLocalPlayer()->getRuntimeIDComponent()->runtimeID]) : "0"; + + if (mode == "Current") { + std::string tmpl = this->settings.getSettingByName("text") != nullptr ? getOps("text") : std::string(); + finalText = replaceValueToken(tmpl, FlarialGUI::cached_to_string(totems)); + } else if (mode == "Pop") { + std::string popsTmpl = this->settings.getSettingByName("textUsed") != nullptr ? getOps("textUsed") : std::string(); + finalText = replaceValueToken(popsTmpl, playerPops); + } else { // Both + std::string tmpl = this->settings.getSettingByName("text") != nullptr ? getOps("text") : std::string(); + std::string popsTmpl = this->settings.getSettingByName("textUsed") != nullptr ? getOps("textUsed") : std::string(); + finalText = replaceValueToken(tmpl, FlarialGUI::cached_to_string(totems)); + finalText += "\n"; // genius + finalText += replaceValueToken(popsTmpl, playerPops); + } - auto totemsStr = FlarialGUI::cached_to_string(totems); - this->normalRender(35, totemsStr); + normalRenderCore(35, finalText); } diff --git a/src/Client/Module/Modules/TotemCounter/TotemCounter.hpp b/src/Client/Module/Modules/TotemCounter/TotemCounter.hpp index 7c46e9fc4..e0710b2f9 100644 --- a/src/Client/Module/Modules/TotemCounter/TotemCounter.hpp +++ b/src/Client/Module/Modules/TotemCounter/TotemCounter.hpp @@ -3,6 +3,7 @@ #include "../Module.hpp" #include "Events/Game/TickEvent.hpp" #include "Events/Render/RenderEvent.hpp" +#include "SDK/Client/Network/Packet/EntityEventPacket.hpp" class TotemCounter : public Module { @@ -13,6 +14,8 @@ class TotemCounter : public Module { int lastTotemCount = 0; int tickCounter = 0; + std::unordered_map popsById; + public: TotemCounter() : Module("Totem Counter", "Counts how many totems you have\nin your inventory.", @@ -31,4 +34,6 @@ class TotemCounter : public Module { void onTick(TickEvent& event); void onRender(RenderEvent& event); -}; \ No newline at end of file + + void onPacketEvent(PacketEvent& event); +}; diff --git a/src/SDK/Client/Level/Level.hpp b/src/SDK/Client/Level/Level.hpp index 55f7ec320..418619494 100644 --- a/src/SDK/Client/Level/Level.hpp +++ b/src/SDK/Client/Level/Level.hpp @@ -144,4 +144,10 @@ class Level { std::string getWorldFolderName() { return hat::member_at(this, GET_OFFSET("Level::worldFolderName")); } std::vector getRuntimeActorList(); + + Actor* getRuntimeEntity(uint64_t actorId, bool getRemoved) + { + static auto off = GET_OFFSET("Level::getRuntimeEntity"); + return Memory::CallVFuncI(off, this, actorId, getRemoved); + } }; diff --git a/src/Utils/Memory/Game/Offset/OffsetInit.cpp b/src/Utils/Memory/Game/Offset/OffsetInit.cpp index a186445ae..b53436c23 100644 --- a/src/Utils/Memory/Game/Offset/OffsetInit.cpp +++ b/src/Utils/Memory/Game/Offset/OffsetInit.cpp @@ -40,6 +40,7 @@ void OffsetInit::init21100() { ADD_OFFSET("Level::hitResult", 0x1E8); ADD_OFFSET("Level::getPlayerMap", 0x4E8); + ADD_OFFSET("Level::getRuntimeEntity", 60); ADD_OFFSET("NetworkSystem::remoteConnectorComposite", 0xF0); ADD_OFFSET("MinecraftGame::textureGroup", 0x758); @@ -102,6 +103,7 @@ void OffsetInit::init2180() { ADD_OFFSET("Level::hitResult", 0x250); ADD_OFFSET("Level::getPlayerMap", 0x960); ADD_OFFSET("Level::worldFolderName", 0x2C0); + ADD_OFFSET("Level::getRuntimeEntity", 58); ADD_OFFSET("ChatScreenController::refreshChatMessages", 0xC80); } @@ -201,6 +203,8 @@ void OffsetInit::init2150() { ADD_OFFSET("Dimension::weather", 0x1D0); ADD_OFFSET("Weather::rainLevel", 0x38); ADD_OFFSET("Weather::lightningLevel", 0x40); + + ADD_OFFSET("Level::getRuntimeEntity", 57); ADD_OFFSET("MinecraftUIRenderContext::getTexture", 29); }