Created the initial Logic Tracker.

This commit is contained in:
Anthony Stewart 2025-06-14 18:23:16 -05:00 committed by xxAtrain223
commit 1d09631215
8 changed files with 378 additions and 29 deletions

View file

@ -5,6 +5,7 @@
#include "variables.h"
#include <sstream>
#include <stack>
#include <unordered_map>
#include <variant>
@ -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<std::tuple<std::string, std::string, int, std::string, LogicExpression::ValueVariant>>
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<LogicExpression::ValueVariant>(recordCallback);
// Helper function to convert path string to a vector of integers for sorting
auto pathToVector = [](const std::string& path) {
std::vector<int> 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<ExpressionEvaluation*> 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<bool>(value)) {
return std::get<bool>(value) ? "true" : "false";
} else if (std::holds_alternative<int>(value)) {
return std::to_string(std::get<int>(value));
} else if (std::holds_alternative<uint8_t>(value)) {
return std::to_string(std::get<uint8_t>(value));
} else if (std::holds_alternative<uint16_t>(value)) {
return std::to_string(std::get<uint16_t>(value));
} else {
return "unknown";
}
}

View file

@ -22,7 +22,34 @@ class LogicExpression {
// Add optional callback parameter to Evaluate
template <typename T> T Evaluate(const EvaluationCallback& callback = nullptr) const {
return Impl::GetValue<T>(impl->Evaluate("0", 0, callback));
return GetValue<T>(impl->Evaluate("0", 0, callback));
}
template <typename T> static T GetValue(const ValueVariant& value) {
if constexpr (std::is_same_v<T, bool>) {
if (std::holds_alternative<bool>(value))
return std::get<bool>(value);
if (std::holds_alternative<int>(value))
return std::get<int>(value) != 0;
if (std::holds_alternative<uint8_t>(value))
return std::get<uint8_t>(value) != 0;
if (std::holds_alternative<uint16_t>(value))
return std::get<uint16_t>(value) != 0;
throw std::bad_variant_access();
} else if constexpr (std::is_same_v<T, int>)
return std::get<int>(value);
else if constexpr (std::is_same_v<T, uint8_t>)
return std::holds_alternative<uint8_t>(value) ? std::get<uint8_t>(value)
: static_cast<uint8_t>(std::get<int>(value));
else if constexpr (std::is_same_v<T, uint16_t>)
return std::holds_alternative<uint16_t>(value) ? std::get<uint16_t>(value)
: static_cast<uint16_t>(std::get<int>(value));
else if constexpr (std::is_enum_v<T>)
return static_cast<T>(std::get<int>(value));
else if constexpr (std::is_same_v<T, ValueVariant>)
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 <typename T> static T GetValue(const ValueVariant& value) {
if constexpr (std::is_same_v<T, bool>) {
if (std::holds_alternative<bool>(value))
return std::get<bool>(value);
if (std::holds_alternative<int>(value))
return std::get<int>(value) != 0;
if (std::holds_alternative<uint8_t>(value))
return std::get<uint8_t>(value) != 0;
if (std::holds_alternative<uint16_t>(value))
return std::get<uint16_t>(value) != 0;
throw std::bad_variant_access();
} else if constexpr (std::is_same_v<T, int>)
return std::get<int>(value);
else if constexpr (std::is_same_v<T, uint8_t>)
return std::holds_alternative<uint8_t>(value) ? std::get<uint8_t>(value)
: static_cast<uint8_t>(std::get<int>(value));
else if constexpr (std::is_same_v<T, uint16_t>)
return std::holds_alternative<uint16_t>(value) ? std::get<uint16_t>(value)
: static_cast<uint16_t>(std::get<int>(value));
else if constexpr (std::is_enum_v<T>)
return static_cast<T>(std::get<int>(value));
else if constexpr (std::is_same_v<T, ValueVariant>)
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<ExpressionEvaluation> Children;
};
ExpressionEvaluation EvaluateExpression(std::string condition);
std::string ToString(const LogicExpression::ValueVariant& value);

View file

@ -69,7 +69,6 @@ typedef enum {
#define DEFINE_TRIAL_KEY(enum) enum,
typedef enum {
TK_NONE,
#include "randomizer_types/trialKey.h"
TK_MAX,
} TrialKey;

View file

@ -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();
}
}

View file

@ -0,0 +1,207 @@
#include "randomizer_logic_tracker.h"
#include "location_access.h"
#include "logic_expression.h"
#include <src/Context.h>
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<ExpressionEvaluation> ChildDay;
std::unique_ptr<ExpressionEvaluation> ChildNight;
std::unique_ptr<ExpressionEvaluation> AdultDay;
std::unique_ptr<ExpressionEvaluation> AdultNight;
};
std::string CheckName;
std::vector<Region> Regions;
};
static std::vector<LogicTrackerCheck> 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<ExpressionEvaluation>(EvaluateExpression(locationAccess.GetConditionStr()));
logic->IsChild = false;
logic->AtDay = false;
}
if (region.childNight) {
logic->IsChild = true;
logic->AtNight = true;
regionAgeTime.ChildNight =
std::make_unique<ExpressionEvaluation>(EvaluateExpression(locationAccess.GetConditionStr()));
logic->IsChild = false;
logic->AtNight = false;
}
if (region.adultDay) {
logic->IsAdult = true;
logic->AtDay = true;
regionAgeTime.AdultDay =
std::make_unique<ExpressionEvaluation>(EvaluateExpression(locationAccess.GetConditionStr()));
logic->IsAdult = false;
logic->AtDay = false;
}
if (region.adultNight) {
logic->IsAdult = true;
logic->AtNight = true;
regionAgeTime.AdultNight =
std::make_unique<ExpressionEvaluation>(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<bool>(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<bool>(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<bool>(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<bool>(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;
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <libultraship/libultraship.h>
class LogicTrackerWindow : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;
void DrawElement() override;
static void ShowRandomizerCheck(RandomizerCheck check);
protected:
void InitElement() override;
void UpdateElement() override;
};

View file

@ -97,6 +97,7 @@ std::shared_ptr<RandomizerSettingsWindow> mRandomizerSettingsWindow;
std::shared_ptr<SohModalWindow> mModalWindow;
std::shared_ptr<Notification::Window> mNotificationWindow;
std::shared_ptr<TimeDisplayWindow> mTimeDisplayWindow;
std::shared_ptr<LogicTrackerWindow> mLogicTrackerWindow;
UIWidgets::Colors GetMenuThemeColor() {
return mSohMenu->GetMenuThemeColor();
@ -202,6 +203,8 @@ void SetupGuiElements() {
mNotificationWindow->Show();
mTimeDisplayWindow = std::make_shared<TimeDisplayWindow>(CVAR_WINDOW("TimeDisplayEnabled"), "Additional Timers");
gui->AddGuiWindow(mTimeDisplayWindow);
mLogicTrackerWindow = std::make_shared<LogicTrackerWindow>(CVAR_WINDOW("LogicTrackerEnabled"), "Logic Tracker");
gui->AddGuiWindow(mLogicTrackerWindow);
}
void Destroy() {

View file

@ -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();