mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-08-22 14:23:44 -07:00
Created the initial Logic Tracker.
This commit is contained in:
parent
0c19604a38
commit
1d09631215
8 changed files with 378 additions and 29 deletions
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -69,7 +69,6 @@ typedef enum {
|
|||
|
||||
#define DEFINE_TRIAL_KEY(enum) enum,
|
||||
typedef enum {
|
||||
TK_NONE,
|
||||
#include "randomizer_types/trialKey.h"
|
||||
TK_MAX,
|
||||
} TrialKey;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
207
soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp
Normal file
207
soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp
Normal 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;
|
||||
}
|
15
soh/soh/Enhancements/randomizer/randomizer_logic_tracker.h
Normal file
15
soh/soh/Enhancements/randomizer/randomizer_logic_tracker.h
Normal 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;
|
||||
};
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue