From 1d09631215cdf68523425fff05bde686515f3ba3 Mon Sep 17 00:00:00 2001 From: Anthony Stewart Date: Sat, 14 Jun 2025 18:23:16 -0500 Subject: [PATCH] Created the initial Logic Tracker. --- .../randomizer/logic_expression.cpp | 104 +++++++++ .../randomizer/logic_expression.h | 68 +++--- .../Enhancements/randomizer/randomizerTypes.h | 1 - .../randomizer/randomizer_check_tracker.cpp | 8 + .../randomizer/randomizer_logic_tracker.cpp | 207 ++++++++++++++++++ .../randomizer/randomizer_logic_tracker.h | 15 ++ soh/soh/SohGui/SohGui.cpp | 3 + soh/soh/SohGui/SohGui.hpp | 1 + 8 files changed, 378 insertions(+), 29 deletions(-) create mode 100644 soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp create mode 100644 soh/soh/Enhancements/randomizer/randomizer_logic_tracker.h diff --git a/soh/soh/Enhancements/randomizer/logic_expression.cpp b/soh/soh/Enhancements/randomizer/logic_expression.cpp index 825fec604..0baed56d3 100644 --- a/soh/soh/Enhancements/randomizer/logic_expression.cpp +++ b/soh/soh/Enhancements/randomizer/logic_expression.cpp @@ -5,6 +5,7 @@ #include "variables.h" #include +#include #include #include @@ -1032,3 +1033,106 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& return false; } + +ExpressionEvaluation EvaluateExpression(std::string condition) { + const auto& expression = LogicExpression::Parse(condition); + + // Create a vector to store the evaluation sequence + std::vector> + evaluationSequence; + + // Define a callback that records each evaluation step + auto recordCallback = [&evaluationSequence](const std::string& exprStr, const std::string& path, int depth, + const std::string& type, const LogicExpression::ValueVariant& result) { + evaluationSequence.emplace_back(exprStr, path, depth, type, result); + }; + + // Evaluate the expression with the callback + auto finalResult = expression->Evaluate(recordCallback); + + // Helper function to convert path string to a vector of integers for sorting + auto pathToVector = [](const std::string& path) { + std::vector result; + std::stringstream ss(path); + std::string segment; + + while (std::getline(ss, segment, '.')) { + try { + result.push_back(std::stoi(segment)); + } catch (const std::exception&) { + // If it's not a valid integer, just skip it + result.push_back(0); + } + } + + return result; + }; + + // Sort the evaluation sequence by path + std::sort(evaluationSequence.begin(), evaluationSequence.end(), [&pathToVector](const auto& a, const auto& b) { + const auto& pathA = std::get<1>(a); + const auto& pathB = std::get<1>(b); + + auto vecA = pathToVector(pathA); + auto vecB = pathToVector(pathB); + + // Compare each component of the path + size_t i = 0; + while (i < vecA.size() && i < vecB.size()) { + if (vecA[i] != vecB[i]) { + return vecA[i] < vecB[i]; + } + i++; + } + + // If one path is a prefix of the other, the shorter one comes first + return vecA.size() < vecB.size(); + }); + + ExpressionEvaluation evaluation; + evaluation.Expression = std::get<0>(evaluationSequence[0]); + evaluation.Depth = std::get<2>(evaluationSequence[0]); + evaluation.Type = std::get<3>(evaluationSequence[0]); + evaluation.Result = std::get<4>(evaluationSequence[0]); + + // Stack to keep track of parent nodes at each depth + std::stack parentStack; + parentStack.push(&evaluation); + + // Process remaining evaluations to build the tree + for (size_t i = 1; i < evaluationSequence.size(); ++i) { + ExpressionEvaluation child; + child.Expression = std::get<0>(evaluationSequence[i]); + child.Depth = std::get<2>(evaluationSequence[i]); + child.Type = std::get<3>(evaluationSequence[i]); + child.Result = std::get<4>(evaluationSequence[i]); + + // Pop parents from stack if we're at a shallower depth + while (!parentStack.empty() && parentStack.top()->Depth >= child.Depth) { + parentStack.pop(); + } + + // Add child to current parent + if (!parentStack.empty()) { + parentStack.top()->Children.push_back(std::move(child)); + // If this child might have children, push it onto the stack + parentStack.push(&(parentStack.top()->Children.back())); + } + } + + return evaluation; +} + +std::string ToString(const LogicExpression::ValueVariant& value) { + if (std::holds_alternative(value)) { + return std::get(value) ? "true" : "false"; + } else if (std::holds_alternative(value)) { + return std::to_string(std::get(value)); + } else if (std::holds_alternative(value)) { + return std::to_string(std::get(value)); + } else if (std::holds_alternative(value)) { + return std::to_string(std::get(value)); + } else { + return "unknown"; + } +} diff --git a/soh/soh/Enhancements/randomizer/logic_expression.h b/soh/soh/Enhancements/randomizer/logic_expression.h index b23b96d22..c8681469c 100644 --- a/soh/soh/Enhancements/randomizer/logic_expression.h +++ b/soh/soh/Enhancements/randomizer/logic_expression.h @@ -22,7 +22,34 @@ class LogicExpression { // Add optional callback parameter to Evaluate template T Evaluate(const EvaluationCallback& callback = nullptr) const { - return Impl::GetValue(impl->Evaluate("0", 0, callback)); + return GetValue(impl->Evaluate("0", 0, callback)); + } + + template static T GetValue(const ValueVariant& value) { + if constexpr (std::is_same_v) { + if (std::holds_alternative(value)) + return std::get(value); + if (std::holds_alternative(value)) + return std::get(value) != 0; + if (std::holds_alternative(value)) + return std::get(value) != 0; + if (std::holds_alternative(value)) + return std::get(value) != 0; + throw std::bad_variant_access(); + } else if constexpr (std::is_same_v) + return std::get(value); + else if constexpr (std::is_same_v) + return std::holds_alternative(value) ? std::get(value) + : static_cast(std::get(value)); + else if constexpr (std::is_same_v) + return std::holds_alternative(value) ? std::get(value) + : static_cast(std::get(value)); + else if constexpr (std::is_enum_v) + return static_cast(std::get(value)); + else if constexpr (std::is_same_v) + return value; + else + static_assert(sizeof(T) == 0, "Unsupported function parameter type"); } private: @@ -48,33 +75,6 @@ class LogicExpression { // Helper to get a string representation of the type std::string GetTypeString() const; - template static T GetValue(const ValueVariant& value) { - if constexpr (std::is_same_v) { - if (std::holds_alternative(value)) - return std::get(value); - if (std::holds_alternative(value)) - return std::get(value) != 0; - if (std::holds_alternative(value)) - return std::get(value) != 0; - if (std::holds_alternative(value)) - return std::get(value) != 0; - throw std::bad_variant_access(); - } else if constexpr (std::is_same_v) - return std::get(value); - else if constexpr (std::is_same_v) - return std::holds_alternative(value) ? std::get(value) - : static_cast(std::get(value)); - else if constexpr (std::is_same_v) - return std::holds_alternative(value) ? std::get(value) - : static_cast(std::get(value)); - else if constexpr (std::is_enum_v) - return static_cast(std::get(value)); - else if constexpr (std::is_same_v) - return value; - else - static_assert(sizeof(T) == 0, "Unsupported function parameter type"); - } - private: std::string GetExprErrorContext() const; // Updated to pass callback to children @@ -298,3 +298,15 @@ class LogicExpression { friend class Parser; friend bool IsEnumConstant(const std::string& s); }; + +struct ExpressionEvaluation { + std::string Expression; + int Depth; + std::string Type; + LogicExpression::ValueVariant Result; + std::vector Children; +}; + +ExpressionEvaluation EvaluateExpression(std::string condition); + +std::string ToString(const LogicExpression::ValueVariant& value); \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index d870504e2..13490e66f 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -69,7 +69,6 @@ typedef enum { #define DEFINE_TRIAL_KEY(enum) enum, typedef enum { - TK_NONE, #include "randomizer_types/trialKey.h" TK_MAX, } TrialKey; diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index d6334b6a0..5bcbd6b3e 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -1,6 +1,7 @@ #include "randomizer_check_tracker.h" #include "randomizer_entrance_tracker.h" #include "randomizer_item_tracker.h" +#include "randomizer_logic_tracker.h" #include "randomizerTypes.h" #include "soh/OTRGlobals.h" #include "soh/cvar_prefixes.h" @@ -1925,6 +1926,13 @@ void DrawLocation(RandomizerCheck rc) { break; } } + + ImGui::SameLine(); + ImGui::PushID((std::to_string(rc) + "_SHOW_LOGIC").c_str()); + if (ImGui::Button("Show Logic")) { + LogicTrackerWindow::ShowRandomizerCheck(rc); + } + ImGui::PopID(); } } diff --git a/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp new file mode 100644 index 000000000..4379a875e --- /dev/null +++ b/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp @@ -0,0 +1,207 @@ +#include "randomizer_logic_tracker.h" + +#include "location_access.h" +#include "logic_expression.h" + +#include + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; +uint64_t GetUnixTimestamp(); +} + +struct LogicTrackerCheck { + struct Region { + std::string RegionName; + std::unique_ptr ChildDay; + std::unique_ptr ChildNight; + std::unique_ptr AdultDay; + std::unique_ptr AdultNight; + }; + + std::string CheckName; + std::vector Regions; +}; + +static std::vector checks; + +void LogicTrackerWindow::ShowRandomizerCheck(RandomizerCheck check) { + checks.clear(); + + const auto& location = Rando::StaticData::GetLocation(check); + + LogicTrackerCheck logicTrackerCheck; + logicTrackerCheck.CheckName = location->GetName(); + + for (const auto& region : areaTable) { + for (const auto& locationAccess : region.locations) { + if (locationAccess.GetLocation() == check) { + LogicTrackerCheck::Region regionAgeTime; + regionAgeTime.RegionName = region.regionName; + + if (region.childDay) { + logic->IsChild = true; + logic->AtDay = true; + + regionAgeTime.ChildDay = + std::make_unique(EvaluateExpression(locationAccess.GetConditionStr())); + + logic->IsChild = false; + logic->AtDay = false; + } + if (region.childNight) { + logic->IsChild = true; + logic->AtNight = true; + + regionAgeTime.ChildNight = + std::make_unique(EvaluateExpression(locationAccess.GetConditionStr())); + + logic->IsChild = false; + logic->AtNight = false; + } + if (region.adultDay) { + logic->IsAdult = true; + logic->AtDay = true; + + regionAgeTime.AdultDay = + std::make_unique(EvaluateExpression(locationAccess.GetConditionStr())); + + logic->IsAdult = false; + logic->AtDay = false; + } + if (region.adultNight) { + logic->IsAdult = true; + logic->AtNight = true; + + regionAgeTime.AdultNight = + std::make_unique(EvaluateExpression(locationAccess.GetConditionStr())); + + logic->IsAdult = false; + logic->AtNight = false; + } + + logicTrackerCheck.Regions.emplace_back(std::move(regionAgeTime)); + } + } + } + + checks.emplace_back(std::move(logicTrackerCheck)); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Logic Tracker")->Show(); +} + +std::string TruncateText(const std::string& text, float maxWidth) { + ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); + if (textSize.x <= maxWidth) + return text; + + std::string truncated = text; + // Remove characters until the text fits with the ellipsis appended + while (!truncated.empty() && ImGui::CalcTextSize((truncated + "...").c_str()).x > maxWidth) { + truncated.pop_back(); + } + return truncated + "..."; +} + +static void DrawExpression(const ExpressionEvaluation& expression) { + ImGuiTreeNodeFlags treeNodeFlags = + expression.Children.empty() ? ImGuiTreeNodeFlags_Leaf : ImGuiTreeNodeFlags_DefaultOpen; + + float availableWidth = ImGui::GetContentRegionAvail().x - ImGui::GetTreeNodeToLabelSpacing(); + auto text = TruncateText(ToString(expression.Result) + " = " + expression.Expression, availableWidth); + + if (ImGui::TreeNodeEx(&expression, treeNodeFlags, "%s", text.c_str())) { + if (!expression.Children.empty()) { + for (const auto& child : expression.Children) { + DrawExpression(child); + } + } + ImGui::TreePop(); + } +} + +static void DrawCheckRegion(const LogicTrackerCheck::Region& region) { + if (ImGui::TreeNodeEx(region.RegionName.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + if (region.ChildDay != nullptr) { + bool resultString = LogicExpression::GetValue(region.ChildDay->Result); + if (ImGui::TreeNodeEx(region.ChildDay.get(), ImGuiTreeNodeFlags_DefaultOpen, "Child-Day Result: %s", + resultString ? "true" : "false")) { + DrawExpression(*region.ChildDay); + ImGui::TreePop(); + } + } else { + if (ImGui::TreeNodeEx("Child-Day Inaccessible", ImGuiTreeNodeFlags_Leaf)) { + ImGui::TreePop(); + } + } + + if (region.ChildNight != nullptr) { + bool resultString = LogicExpression::GetValue(region.ChildNight->Result); + if (ImGui::TreeNodeEx(region.ChildNight.get(), ImGuiTreeNodeFlags_DefaultOpen, "Child-Night Result: %s", + resultString ? "true" : "false")) { + DrawExpression(*region.ChildNight); + ImGui::TreePop(); + } + } else { + if (ImGui::TreeNodeEx("Child-Night Inaccessible", ImGuiTreeNodeFlags_Leaf)) { + ImGui::TreePop(); + } + } + + if (region.AdultDay != nullptr) { + bool resultString = LogicExpression::GetValue(region.AdultDay->Result); + if (ImGui::TreeNodeEx(region.AdultDay.get(), ImGuiTreeNodeFlags_DefaultOpen, "Adult-Day Result: %s", + resultString ? "true" : "false")) { + DrawExpression(*region.AdultDay); + ImGui::TreePop(); + } + } else { + if (ImGui::TreeNodeEx("Adult-Day Inaccessible", ImGuiTreeNodeFlags_Leaf)) { + ImGui::TreePop(); + } + } + + if (region.AdultNight != nullptr) { + bool resultString = LogicExpression::GetValue(region.AdultNight->Result); + if (ImGui::TreeNodeEx(region.AdultNight.get(), ImGuiTreeNodeFlags_DefaultOpen, "Adult-Night Result: %s", + resultString ? "true" : "false")) { + DrawExpression(*region.AdultNight); + ImGui::TreePop(); + } + } else { + if (ImGui::TreeNodeEx("Adult-Night Inaccessible", ImGuiTreeNodeFlags_Leaf)) { + ImGui::TreePop(); + } + } + + ImGui::TreePop(); + } +} + +static void DrawCheck(const LogicTrackerCheck& check) { + ImGui::SeparatorText(("Check: " + check.CheckName).c_str()); + if (check.Regions.empty()) { + ImGui::Text("No regions found for this check."); + return; + } + for (const auto& region : check.Regions) { + DrawCheckRegion(region); + } +} + +void LogicTrackerWindow::DrawElement() { + for (const auto& check : checks) { + DrawCheck(check); + } +} + +void LogicTrackerWindow::InitElement() { + return; +} + +void LogicTrackerWindow::UpdateElement() { + return; +} diff --git a/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.h new file mode 100644 index 000000000..44b469366 --- /dev/null +++ b/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class LogicTrackerWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + void DrawElement() override; + + static void ShowRandomizerCheck(RandomizerCheck check); + + protected: + void InitElement() override; + void UpdateElement() override; +}; diff --git a/soh/soh/SohGui/SohGui.cpp b/soh/soh/SohGui/SohGui.cpp index e9b27eda1..968241258 100644 --- a/soh/soh/SohGui/SohGui.cpp +++ b/soh/soh/SohGui/SohGui.cpp @@ -97,6 +97,7 @@ std::shared_ptr mRandomizerSettingsWindow; std::shared_ptr mModalWindow; std::shared_ptr mNotificationWindow; std::shared_ptr mTimeDisplayWindow; +std::shared_ptr mLogicTrackerWindow; UIWidgets::Colors GetMenuThemeColor() { return mSohMenu->GetMenuThemeColor(); @@ -202,6 +203,8 @@ void SetupGuiElements() { mNotificationWindow->Show(); mTimeDisplayWindow = std::make_shared(CVAR_WINDOW("TimeDisplayEnabled"), "Additional Timers"); gui->AddGuiWindow(mTimeDisplayWindow); + mLogicTrackerWindow = std::make_shared(CVAR_WINDOW("LogicTrackerEnabled"), "Logic Tracker"); + gui->AddGuiWindow(mLogicTrackerWindow); } void Destroy() { diff --git a/soh/soh/SohGui/SohGui.hpp b/soh/soh/SohGui/SohGui.hpp index d9b7bb644..fb64e4b7a 100644 --- a/soh/soh/SohGui/SohGui.hpp +++ b/soh/soh/SohGui/SohGui.hpp @@ -30,6 +30,7 @@ #include "soh/Enhancements/timesplits/TimeSplits.h" #include "soh/Enhancements/randomizer/Plandomizer.h" #include "SohModals.h" +#include "soh/Enhancements/randomizer/randomizer_logic_tracker.h" namespace SohGui { void SetupHooks();