diff --git a/cheat-library/src/user/cheat/teleport/CustomTeleports.cpp b/cheat-library/src/user/cheat/teleport/CustomTeleports.cpp index b99dd18..cb29205 100644 --- a/cheat-library/src/user/cheat/teleport/CustomTeleports.cpp +++ b/cheat-library/src/user/cheat/teleport/CustomTeleports.cpp @@ -22,8 +22,8 @@ namespace cheat::feature NF(f_Next, "Teleport Next", "CustomTeleports", Hotkey(VK_OEM_6)), NF(f_Previous, "Teleport Previous", "CustomTeleports", Hotkey(VK_OEM_4)) { - f_Next.value().PressedEvent += MY_METHOD_HANDLER(CustomTeleports::OnNextKeyPressed); - f_Previous.value().PressedEvent += MY_METHOD_HANDLER(CustomTeleports::OnPreviousKeyPressed); + f_Next.value().PressedEvent += MY_METHOD_HANDLER(CustomTeleports::OnNext); + f_Previous.value().PressedEvent += MY_METHOD_HANDLER(CustomTeleports::OnPrevious); } const FeatureGUIInfo& CustomTeleports::GetGUIInfo() const { @@ -31,244 +31,373 @@ namespace cheat::feature return info; } + void CustomTeleports::CheckFolder() + { + if (!std::filesystem::exists(dir)) + std::filesystem::create_directory(dir); + else return; + } + + bool CustomTeleports::ValidateTeleport(std::string name) + { + for (auto &Teleport : Teleports) + if (Teleport.name == name) + return false; + if (name.find_first_of("\\/:*?\"<>|") != std::string::npos) + return false; + return true; + } + + Teleport CustomTeleports::Teleport_(std::string name, app::Vector3 position, std::string description) + { + Teleport t(name, position, description); + return t; + } + + void CustomTeleports::SerializeTeleport(Teleport t) + { + Teleports.push_back(t); + LOG_INFO("Teleport '%s' Loaded", t.name.c_str()); + CheckFolder(); + std::ofstream ofs(dir / (t.name + ".json")); + nlohmann::json j; + try + { + j["name"] = t.name; + j["position"] = {t.position.x, t.position.y, t.position.z}; + j["description"] = t.description; + ofs << j; + ofs.close(); + LOG_INFO("Teleport '%s' Serialized.", t.name.c_str()); + } catch (std::exception e) + { + ofs.close(); + LOG_ERROR("Failed to serialize teleport: %s: %s", t.name.c_str(), e.what()); + } + } + + Teleport CustomTeleports::SerializeFromJson(std::string json, bool fromfile) + { + nlohmann::json j; + try { j = nlohmann::json::parse(json);} + catch (nlohmann::json::parse_error &e) + { + LOG_ERROR("Invalid JSON Format"); + LOG_ERROR("Failed to parse JSON: %s", e.what()); + } + std::string teleportName; + teleportName = j["name"]; + if (j["name"].is_null() && fromfile) + { + LOG_ERROR("No name found! Using File Name"); + teleportName = std::filesystem::path(json).stem().filename().string(); + } + std::string description; + if (j["description"].is_null()) description = ""; + else description = j["description"]; + return Teleport_(teleportName, {j["position"][0], j["position"][1], j["position"][2]}, description); + } + + void CustomTeleports::ReloadTeleports() + { + auto result = std::filesystem::directory_iterator(dir); + Teleports.clear(); + + for (auto &file : result) + { + if (file.path().extension() == ".json") + { + std::ifstream ifs(file.path()); + std::string json; + std::getline(ifs, json); + SerializeTeleport(SerializeFromJson(json, true)); + } + } + } + + float PositionDistance(app::Vector3 a, app::Vector3 b) + { + return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2) + pow(a.z - b.z, 2)); + } + + void CustomTeleports::OnTeleportKeyPressed(bool next) + { + if (!f_Enabled || selectedIndex < 0) + return; + + auto &mapTeleport = MapTeleport::GetInstance(); + app::Vector3 position; + + if (selectedByClick) + { + position = Teleports.at(selectedIndex).position; + selectedByClick = false; + } + else + { + std::vector list(checkedIndices.begin(), checkedIndices.end()); + if (selectedIndex == list.back() ? next : selectedIndex == list.front()) + return; + auto index = std::distance(list.begin(), std::find(list.begin(), list.end(), selectedIndex)); + position = Teleports.at(list.at(index + (next ? 1 : -1))).position; + selectedIndex = list.at(index + (next ? 1 : -1)); + } + mapTeleport.TeleportTo(position); + UpdateIndexName(); + } + + void CustomTeleports::OnPrevious() + { + OnTeleportKeyPressed(false); + } + void CustomTeleports::OnNext() + { + OnTeleportKeyPressed(true); + } + + + void CustomTeleports::UpdateIndexName() + { + std::string name(selectedIndex == -1 || checkedIndices.empty() ? "" : Teleports.at(selectedIndex).name); + + // abbreviate teleport names that are too long + if (name.length() > 15) + { + std::string shortened; + std::regex numsExp("[\\d]+"); + std::regex firstCharsExp("\\b[A-Za-z]"); + + std::sregex_iterator wordItr(name.begin(), name.end(), firstCharsExp); + while (wordItr != std::sregex_iterator()) + { + for (unsigned i = 0; i < wordItr->size(); i++) + { + shortened.append((*wordItr)[i]); + } + wordItr++; + } + + std::sregex_iterator numItr(name.begin(), name.end(), numsExp); + while (numItr != std::sregex_iterator()) + { + for (unsigned i = 0; i < numItr->size(); i++) + { + shortened.append(" "); + shortened.append((*numItr)[i]); + } + numItr++; + } + name = shortened; + } + selectedIndexName = name; + } + void CustomTeleports::DrawMain() { - auto& entityManager = game::EntityManager::instance(); - auto& MapTeleport = MapTeleport::GetInstance(); - static std::string teleportName; - static std::string search; - app::Vector3 pos = app::ActorUtils_GetAvatarPos(nullptr); + // Buffers + static std::string nameBuffer_; + static std::string searchBuffer_; + static std::string JSONBuffer_; + static std::string descriptionBuffer_; - ImGui::InputText("Teleport name", &teleportName); + ImGui::InputText("Name", &nameBuffer_); + ImGui::InputText("Description", &descriptionBuffer_); if (ImGui::Button("Add Teleport")) { - // check if name is valid and doesnt contain special characters - if (teleportName.find_first_of("\\/:*?\"<>|") != std::string::npos) - return; - - // check if already added - if (std::any_of(teleports.begin(), teleports.end(), [](const auto& pair) - { return pair.first == teleportName; })) - return; - selectedIndex = -1; UpdateIndexName(); - teleports.push_back({ teleportName, pos }); - - auto dir = std::filesystem::current_path(); - dir /= "teleports"; - if (!std::filesystem::exists(dir)) - std::filesystem::create_directory(dir); - std::ofstream ofs(dir / (teleportName + ".json")); - nlohmann::json j; - j["name"] = teleportName; - j["position"] = { pos.x, pos.y, pos.z }; - ofs << j; - teleportName.clear(); + SerializeTeleport(Teleport_(nameBuffer_, app::ActorUtils_GetAvatarPos(nullptr), descriptionBuffer_)); + nameBuffer_ = ""; + descriptionBuffer_ = ""; } ImGui::SameLine(); + if (ImGui::Button("Reload")) { selectedIndex = -1; UpdateIndexName(); checkedIndices.clear(); - auto dir = std::filesystem::current_path(); - dir /= "teleports"; - auto result = std::filesystem::directory_iterator(dir); - teleports.clear(); - for (auto& file : result) - { - if (file.path().extension() != ".json") - continue; - - std::string name = file.path().stem().string(); - if (file.is_directory()) - continue; - - std::ifstream ifs(file.path()); - nlohmann::json j; - ifs >> j; - teleports.push_back({ j["name"], {j["position"][0], j["position"][1], j["position"][2]} }); - LOG_INFO("Loaded teleport %s", name.c_str()); - } + ReloadTeleports(); } + ImGui::SameLine(); - // open directory if (ImGui::Button("Open Folder")) { - auto dir = std::filesystem::current_path(); - dir /= "teleports"; + CheckFolder(); ShellExecuteA(NULL, "open", dir.string().c_str(), NULL, NULL, SW_SHOW); } + ImGui::SameLine(); - static std::string jsonInput; if (ImGui::Button("Load from JSON")) { selectedIndex = -1; UpdateIndexName(); - auto dir = std::filesystem::current_path(); - dir /= "teleports"; - LOG_INFO("Defined dir"); - if (!std::filesystem::exists(dir)) - std::filesystem::create_directory(dir); - nlohmann::json j; - try - { - j = nlohmann::json::parse(jsonInput); - } - catch (nlohmann::json::parse_error& e) - { - LOG_ERROR("Failed to parse JSON: %s", e.what()); - return; - } - LOG_INFO("Parsed JSON"); - std::string teleportName = j["name"]; - app::Vector3 pos = { j["position"][0], j["position"][1], j["position"][2] }; - teleports.push_back({ teleportName, pos }); - LOG_INFO("Loaded teleport %s", teleportName.c_str()); - std::ofstream ofs(dir / (teleportName + ".json")); - ofs << jsonInput; - jsonInput.clear(); + SerializeTeleport(SerializeFromJson(JSONBuffer_, false)); + JSONBuffer_ = ""; } - ImGui::InputTextMultiline("JSON input", &jsonInput, ImVec2(0, 50), ImGuiInputTextFlags_AllowTabInput); + ImGui::InputTextMultiline("JSON input", &JSONBuffer_, ImVec2(0, 50), ImGuiInputTextFlags_AllowTabInput); ConfigWidget("Teleport Next", f_Next, true, "Press to teleport next of selected"); ConfigWidget("Teleport Previous", f_Previous, true, "Press to teleport previous of selected"); - ConfigWidget("Enable", - f_Enabled, - "Enable teleport-through-list functionality\n" \ - "Usage:\n" \ - "1. Put Checkmark to the teleports you want to teleport using hotkey\n" \ - "2. Single click the teleport (with checkmark) to select where you want to start\n" \ - "3. You can now press Next or Previous Hotkey to Teleport through the Checklist\n" \ - "Initially it will teleport the player to the selection made\n" \ - "Note: Double click or click the arrow to open teleport details"); + ConfigWidget("Enable", f_Enabled, + "Enable teleport-through-list functionality\n" + "Usage:\n" + "1. Put Checkmark to the teleports you want to teleport using hotkey\n" + "2. Single click the teleport (with checkmark) to select where you want to start\n" + "3. You can now press Next or Previous Hotkey to Teleport through the Checklist\n" + "Initially it will teleport the player to the selection made\n" + "Note: Double click or click the arrow to open teleport details"); ImGui::SameLine(); + if (ImGui::Button("Delete Checked")) { - if (!teleports.empty()) { - std::vector teleportNames; - // get all teleport names by index - for (auto& i : checkedIndices) { - teleportNames.push_back(teleports.at(i).first); - if (selectedIndex == i) selectedIndex = -1; + if (!Teleports.empty()) + { + if (checkedIndices.empty()) + { + LOG_INFO("No teleports selected"); + return; } - - for (auto& name : teleportNames) { - auto dir = std::filesystem::current_path(); - dir /= "teleports"; - // delete file - std::filesystem::remove(dir / (name + ".json")); - // remove from list - teleports.erase(std::remove_if(teleports.begin(), teleports.end(), [&name](const auto& pair) - { return pair.first == name; }), teleports.end()); + std::vector teleportNames; + for (auto &Teleport : Teleports) + teleportNames.push_back(Teleport.name); + for (auto &index : checkedIndices) + { + std::filesystem::remove(dir / (teleportNames[index] + ".json")); + LOG_INFO("Deleted teleport %s", teleportNames[index].c_str()); } checkedIndices.clear(); UpdateIndexName(); - } - + ReloadTeleports(); + } else {LOG_INFO("No teleports to delete");} } ImGui::SameLine(); - HelpMarker("Warning: This will delete the file from the directory and\nremove the teleport from the list. It will be lost forever."); + HelpMarker("Warning: This will delete the file from the directory and\n \ + remove the teleport from the list. It will be lost forever."); if (ImGui::TreeNode("Teleports")) { - - // using natural sort instead of ascii sort - std::sort(teleports.begin(), teleports.end(), [](const auto& a, const auto& b) - { return StrCmpLogicalW(std::wstring(a.first.begin(), a.first.end()).c_str(), std::wstring(b.first.begin(), b.first.end()).c_str()) < 0; }); - - bool allSearchChecked = std::includes(checkedIndices.begin(), checkedIndices.end() ,searchIndices.begin(), searchIndices.end()) && !searchIndices.empty(); - bool allChecked = (checkedIndices.size() == teleports.size() && !teleports.empty()) || allSearchChecked; + std::sort(Teleports.begin(), Teleports.end(), [](const auto &a, const auto &b) + { return StrCmpLogicalW(std::wstring(a.name.begin(), a.name.end()).c_str(), std::wstring(b.name.begin(), b.name.end()).c_str()) < 0; }); + bool allChecked = checkedIndices.size() == Teleports.size() && !Teleports.empty(); + bool allSearchChecked = checkedIndices.size() == searchIndices.size() && !searchIndices.empty(); ImGui::Checkbox("All", &allChecked); - if (ImGui::IsItemClicked()) { - if (!teleports.empty()) { - if (allChecked) { + if (ImGui::IsItemClicked()) + { + if (!Teleports.empty()) + { + if (allChecked) + { selectedIndex = -1; - if (!searchIndices.empty()) { - for (const auto& i : searchIndices) { + if (!searchIndices.empty()) + for (const auto &i : searchIndices) checkedIndices.erase(i); - } - } - else { + else checkedIndices.clear(); - } - } - else { - if (!searchIndices.empty()) { - checkedIndices.insert(searchIndices.begin(), searchIndices.end()); - } - else { - for (int i = 0; i < teleports.size(); i++) - checkedIndices.insert(i); - } } + else if (!searchIndices.empty()) + checkedIndices.insert(searchIndices.begin(), searchIndices.end()); + else + for (int i = 0; i < Teleports.size(); i++) + checkedIndices.insert(i); UpdateIndexName(); } } ImGui::SameLine(); - ImGui::InputText("Search", &search); + ImGui::InputText("Search", &searchBuffer_); unsigned int index = 0; searchIndices.clear(); - for (const auto& [teleportName, position] : teleports) - { - // find without case sensitivity - if (search.empty() || std::search(teleportName.begin(), teleportName.end(), search.begin(), search.end(), [](char a, char b) - { return std::tolower(a) == std::tolower(b); }) != teleportName.end()) - { - // sets are sorted by default and does not allow duplicates - // which works in favor here. - if (!search.empty()) { - searchIndices.insert(index); - } - bool checked = std::any_of(checkedIndices.begin(), checkedIndices.end(), [&index](const auto& i) { return i == index; }); + unsigned int maxNameLength = 0; + for (auto &Teleport : Teleports) + if (Teleport.name.length() > maxNameLength) + maxNameLength = Teleport.name.length(); + ImGui::BeginTable("Teleports", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_NoSavedSettings); + ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 20); + ImGui::TableSetupColumn("Commands", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, maxNameLength * 8 + 10); + ImGui::TableSetupColumn("Position"); + ImGui::TableHeadersRow(); + ImGuiTreeNodeFlags nodeFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; + + for (const auto &[name, position, description] : Teleports) + { + if (searchBuffer_.empty() || std::search(name.begin(), name.end(), searchBuffer_.begin(), searchBuffer_.end(), [](char a, char b) + { return std::tolower(a) == std::tolower(b); }) != name.end()) + { + if (!searchBuffer_.empty()) + searchIndices.insert(index); + bool checked = std::any_of(checkedIndices.begin(), checkedIndices.end(), [&index](const auto &i) + { return i == index; }); bool selected = index == selectedIndex; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d", index); + ImGui::TableNextColumn(); ImGui::Checkbox(("##Index" + std::to_string(index)).c_str(), &checked); - if (ImGui::IsItemClicked(0)) { - if (checked) { - if (selected) selectedIndex = -1; + if (ImGui::IsItemClicked(0)) + { + if (checked) + { + if (selected) + selectedIndex = -1; checkedIndices.erase(index); } - else { + else checkedIndices.insert(index); - } UpdateIndexName(); } + ImGui::SameLine(); if (ImGui::Button(("TP##Button" + std::to_string(index)).c_str())) { - auto& mapTeleport = MapTeleport::GetInstance(); - mapTeleport.TeleportTo(position); + auto &manager = game::EntityManager::instance(); + auto avatar = manager.avatar(); + if (avatar->moveComponent() == nullptr) + { + LOG_ERROR("Avatar has no move component, Is scene loaded?"); + return; + } + if (PositionDistance(position, app::ActorUtils_GetAvatarPos(nullptr)) > 60.0f) + MapTeleport::GetInstance().TeleportTo(position); + else + manager.avatar()->setAbsolutePosition(position); + } + + ImGui::SameLine(); + if (ImGui::Button(("Select##Button" + std::to_string(index)).c_str())) + { + selectedIndex = index; + selectedByClick = true; + UpdateIndexName(); } ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, selected ? IM_COL32(40, 90, 175, 255) : IM_COL32(255, 255, 255, 255)); - ImGuiTreeNodeFlags nodeFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; - if (selected) nodeFlags |= ImGuiTreeNodeFlags_Selected; - bool node_open = ImGui::TreeNodeEx(teleportName.data(), nodeFlags); - if (ImGui::IsItemClicked() && checked) { - if (!selected) { - selectedIndex = index; - selectedByClick = true; - } - else { - selectedIndex = -1; - selectedByClick = false; - } - UpdateIndexName(); - } - if (node_open) - { - ImGui::Text("Position: %.3f, %.3f, %.3f", position.x, position.y, position.z); - ImGui::TreePop(); - } + + if (selected) + nodeFlags |= ImGuiTreeNodeFlags_Selected; ImGui::PopStyleColor(); + ImGui::TableNextColumn(); + + ImGui::Text("%s", name.c_str()); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("%s", description.c_str()); + ImGui::Text("Distance: %.2f", PositionDistance(position, app::ActorUtils_GetAvatarPos(nullptr))); + ImGui::EndTooltip(); + } + ImGui::TableNextColumn(); + ImGui::Text("%f, %f, %f", position.x, position.y, position.z); } index++; } - ImGui::TreePop(); + ImGui::EndTable(); } + + if (selectedIndex != -1) + ImGui::Text("Selected: [%d] %s", selectedIndex, Teleports[selectedIndex].name.c_str()); } bool CustomTeleports::NeedStatusDraw() const @@ -281,88 +410,7 @@ namespace cheat::feature ImGui::Text("Custom Teleport\n[%s]", selectedIndexName); } - void CustomTeleports::OnNextKeyPressed() - { - if (!f_Enabled || selectedIndex < 0) - return; - - auto& mapTeleport = MapTeleport::GetInstance(); - app::Vector3 position; - - if (selectedByClick) { - position = teleports.at(selectedIndex).second; - selectedByClick = false; - } - else { - std::vector list(checkedIndices.begin(), checkedIndices.end()); - if (selectedIndex == list.back()) - return; - - auto index = std::distance(list.begin(), std::find(list.begin(), list.end(), selectedIndex)); - position = teleports.at(list.at(index + 1)).second; - selectedIndex = list.at(index + 1); - } - mapTeleport.TeleportTo(position); - UpdateIndexName(); - } - - void CustomTeleports::OnPreviousKeyPressed() - { - if (!f_Enabled || selectedIndex < 0) - return; - - auto& mapTeleport = MapTeleport::GetInstance(); - app::Vector3 position; - - if (selectedByClick) { - position = teleports.at(selectedIndex).second; - selectedByClick = false; - } - else { - std::vector list(checkedIndices.begin(), checkedIndices.end()); - if (selectedIndex == list.front()) - return; - - auto index = std::distance(list.begin(), std::find(list.begin(), list.end(), selectedIndex)); - position = teleports.at(list.at(index - 1)).second; - selectedIndex = list.at(index - 1); - } - mapTeleport.TeleportTo(position); - UpdateIndexName(); - } - - void CustomTeleports::UpdateIndexName() { - std::string name(selectedIndex == -1 || checkedIndices.empty() ? "" : teleports.at(selectedIndex).first); - - // abbreviate teleport names that are too long - if (name.length() > 15) { - std::string shortened; - std::regex numsExp("[\\d]+"); - std::regex firstCharsExp("\\b[A-Za-z]"); - - std::sregex_iterator wordItr(name.begin(), name.end(), firstCharsExp); - while (wordItr != std::sregex_iterator()) { - for (unsigned i = 0; i < wordItr->size(); i++) { - shortened.append((*wordItr)[i]); - } - wordItr++; - } - - std::sregex_iterator numItr(name.begin(), name.end(), numsExp); - while (numItr != std::sregex_iterator()) { - for (unsigned i = 0; i < numItr->size(); i++) { - shortened.append(" "); - shortened.append((*numItr)[i]); - } - numItr++; - } - name = shortened; - } - selectedIndexName = name; - - } - - CustomTeleports& CustomTeleports::GetInstance() + CustomTeleports &CustomTeleports::GetInstance() { static CustomTeleports instance; return instance; diff --git a/cheat-library/src/user/cheat/teleport/CustomTeleports.h b/cheat-library/src/user/cheat/teleport/CustomTeleports.h index aba3865..eb03b34 100644 --- a/cheat-library/src/user/cheat/teleport/CustomTeleports.h +++ b/cheat-library/src/user/cheat/teleport/CustomTeleports.h @@ -8,6 +8,20 @@ namespace cheat::feature { + class Teleport + { + public: + Teleport(std::string name, app::Vector3 position, std::string description) + { + this->name = name; + this->position = position; + this->description = description; + } + std::string name; + app::Vector3 position; + std::string description; + }; + class CustomTeleports : public Feature { public: @@ -17,21 +31,34 @@ namespace cheat::feature config::Field f_Previous; static CustomTeleports& GetInstance(); const FeatureGUIInfo& GetGUIInfo() const override; - void DrawMain() override; + void CheckFolder(); + bool ValidateTeleport(std::string name); + Teleport Teleport_(std::string name, app::Vector3 position, std::string description); + void SerializeTeleport(Teleport t); + void ReloadTeleports(); + Teleport SerializeFromJson(std::string json, bool fromfile); + + + void DrawMain() override; virtual bool NeedStatusDraw() const override; void DrawStatus() override; + + std::vector Teleports; + std::filesystem::path dir = std::filesystem::current_path() / "teleports"; private: - std::vector> teleports; std::set checkedIndices; std::set searchIndices; bool selectedByClick = false; int selectedIndex = -1; + std::string selectedName; std::string selectedIndexName; CustomTeleports(); - void OnNextKeyPressed(); - void OnPreviousKeyPressed(); + void OnTeleportKeyPressed(bool next); + void OnPrevious(); + void OnNext(); void UpdateIndexName(); }; + } \ No newline at end of file