Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 95 additions & 7 deletions src/Client/Module/Modules/TotemCounter/TotemCounter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>{"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<std::string>{"Left", "Center", "Right"}, "textalignment", true);
addToggle("Text Shadow", "Displays a shadow under the text", "textShadow");
addConditionalSlider(getOps<bool>("textShadow"), "Shadow Offset", "How far the shadow will be.", "textShadowOffset", 0.02f, 0.001f);
extraPadding();

addHeader("Colors");
Expand All @@ -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<bool>("notifyOnPop") ,"Notifier: Listen for Others", "Notifies in chat when other player(s) pop their totems as well.", "listenForOthers");
extraPadding();


FlarialGUI::UnsetScrollView();
resetPadding();
}
Expand Down Expand Up @@ -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<EntityEventPacket*>(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<bool>("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<uint64_t, std::chrono::steady_clock::time_point> 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<bool>("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<std::string>("mode") != nullptr) mode = getOps<std::string>("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<std::string>("text") != nullptr ? getOps<std::string>("text") : std::string();
finalText = replaceValueToken(tmpl, FlarialGUI::cached_to_string(totems));
} else if (mode == "Pop") {
std::string popsTmpl = this->settings.getSettingByName<std::string>("textUsed") != nullptr ? getOps<std::string>("textUsed") : std::string();
finalText = replaceValueToken(popsTmpl, playerPops);
} else { // Both
std::string tmpl = this->settings.getSettingByName<std::string>("text") != nullptr ? getOps<std::string>("text") : std::string();
std::string popsTmpl = this->settings.getSettingByName<std::string>("textUsed") != nullptr ? getOps<std::string>("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);
}
7 changes: 6 additions & 1 deletion src/Client/Module/Modules/TotemCounter/TotemCounter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -13,6 +14,8 @@ class TotemCounter : public Module {
int lastTotemCount = 0;
int tickCounter = 0;

std::unordered_map<uint64_t, int> popsById;

public:

TotemCounter() : Module("Totem Counter", "Counts how many totems you have\nin your inventory.",
Expand All @@ -31,4 +34,6 @@ class TotemCounter : public Module {
void onTick(TickEvent& event);

void onRender(RenderEvent& event);
};

void onPacketEvent(PacketEvent& event);
};
6 changes: 6 additions & 0 deletions src/SDK/Client/Level/Level.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,10 @@ class Level {
std::string getWorldFolderName() { return hat::member_at<std::string>(this, GET_OFFSET("Level::worldFolderName")); }

std::vector<Actor *> getRuntimeActorList();

Actor* getRuntimeEntity(uint64_t actorId, bool getRemoved)
{
static auto off = GET_OFFSET("Level::getRuntimeEntity");
return Memory::CallVFuncI<Actor*>(off, this, actorId, getRemoved);
}
};
4 changes: 4 additions & 0 deletions src/Utils/Memory/Game/Offset/OffsetInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);

}
Expand Down