Merge pull request #210 from Shichiha/patch-4

Better CustomTeleports
This commit is contained in:
Callow 2022-07-13 10:51:52 +03:00 committed by GitHub
commit a30ec4f726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 329 additions and 254 deletions

View File

@ -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<std::string> 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<std::string> 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;

View File

@ -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<Hotkey> 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<Teleport> Teleports;
std::filesystem::path dir = std::filesystem::current_path() / "teleports";
private:
std::vector<std::pair<std::string, app::Vector3>> teleports;
std::set<unsigned int> checkedIndices;
std::set<unsigned int> 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();
};
}