1#ifndef YAZE_APP_EDITOR_HACK_ORACLE_UI_PROGRESSION_DASHBOARD_PANEL_H
2#define YAZE_APP_EDITOR_HACK_ORACLE_UI_PROGRESSION_DASHBOARD_PANEL_H
21#include "imgui/imgui.h"
38 std::string
GetId()
const override {
return "oracle.progression_dashboard"; }
39 std::string
GetDisplayName()
const override {
return "Game State Dashboard"; }
44 return "Inspect progression, SRAM-derived flags, and live game state";
49 return project !=
nullptr && project->project_opened() &&
53 return "Progression tools are not available for the active hack project";
60 void Draw(
bool* p_open)
override {
76 manifest_ = backend->ResolveManifest(project);
77 }
else if (project && project->hack_manifest.loaded()) {
108 ImGui::Text(
"SRAM (.srm)");
112 {
"SRAM (.srm)",
"srm"},
116 if (ImGui::Button(
"Import...")) {
117 std::string file_path =
119 if (!file_path.empty()) {
137 if (ImGui::Button(
"Clear")) {
144 backend->ClearProgressionState(*
manifest_);
153 ImGui::TextDisabled(
"Loaded: %s", p.filename().string().c_str());
154 if (ImGui::IsItemHovered()) {
158 ImGui::TextDisabled(
"Loaded: (none)");
162 ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.35f, 1.0f),
"Error: %s",
173 ImGui::Text(
"Live SRAM (Mesen2)");
176 ImGui::TextColored(ImVec4(0.25f, 0.85f, 0.35f, 1.0f),
"Connected");
178 ImGui::TextDisabled(
"Disconnected");
181 if (ImGui::SmallButton(
"Sync from Mesen")) {
189 ImGui::SetNextItemWidth(70.0f);
190 ImGui::SliderFloat(
"##LiveRefreshInterval",
195 ImGui::TextDisabled(
"Use a Mesen panel to connect (shared client).");
199 ImGui::TextColored(ImVec4(1.0f, 0.55f, 0.35f, 1.0f),
"Live sync: %s",
211 const auto backend_existing = backend->GetProgressionState(*
manifest_);
212 if (backend_existing.has_value() &&
227 ImGui::Text(
"Crystal Tracker");
230 float item_width = 44.0f;
232 for (
int d = 1; d <= 7; ++d) {
236 ImVec4 color = complete ? ImVec4(0.2f, 0.8f, 0.3f, 1.0f)
237 : ImVec4(0.3f, 0.3f, 0.3f, 0.6f);
241 {{ImGuiCol_Button, color},
242 {ImGuiCol_ButtonHovered,
243 ImVec4(color.x + 0.1f, color.y + 0.1f, color.z + 0.1f, 1.0f)}});
246 snprintf(label,
sizeof(label),
"D%d", d);
247 if (ImGui::Button(label, ImVec2(item_width, 36.0f))) {
261 ImGui::Text(
"Game State");
265 const char* phases[] = {
"Start",
"Loom Beach",
"Kydrog Complete",
269 float bar_width = ImGui::GetContentRegionAvail().x;
270 float segment = bar_width /
static_cast<float>(phase_count);
272 ImVec2 bar_pos = ImGui::GetCursorScreenPos();
273 ImDrawList* draw_list = ImGui::GetWindowDrawList();
275 for (
int i = 0; i < phase_count; ++i) {
276 ImVec2 seg_min(bar_pos.x + segment * i, bar_pos.y);
277 ImVec2 seg_max(bar_pos.x + segment * (i + 1), bar_pos.y + 24.0f);
280 : IM_COL32(50, 50, 50, 180);
282 draw_list->AddRectFilled(seg_min, seg_max, fill, 3.0f);
283 draw_list->AddRect(seg_min, seg_max, IM_COL32(80, 80, 80, 255), 3.0f);
286 ImVec2 text_pos(seg_min.x + 4, seg_min.y + 4);
287 draw_list->AddText(text_pos, IM_COL32(220, 220, 220, 255), phases[i]);
290 ImGui::Dummy(ImVec2(0, 30));
295 ImGui::Text(
"Dungeon Completion");
303 DungeonInfo dungeons[] = {
304 {
"D1 Mushroom Grotto", 1}, {
"D2 Tail Palace", 2},
305 {
"D3 Kalyxo Castle", 3}, {
"D4 Zora Temple", 4},
306 {
"D5 Glacia Estate", 5}, {
"D6 Goron Mines", 6},
307 {
"D7 Dragon Ship", 7}, {
"FOS Fortress", 0},
308 {
"SOP Shrine of Power", 0}, {
"SOW Shrine of Wisdom", 0},
311 ImGui::Columns(2,
"dungeon_grid",
false);
312 for (
const auto& dungeon : dungeons) {
313 bool complete =
false;
314 if (dungeon.number >= 1 && dungeon.number <= 7) {
318 ImVec4 color = complete ? ImVec4(0.1f, 0.6f, 0.2f, 1.0f)
319 : ImVec4(0.25f, 0.25f, 0.25f, 1.0f);
323 ImGui::Selectable(dungeon.label, complete,
324 ImGuiSelectableFlags_Disabled);
332 if (!ImGui::TreeNode(
"Story Flags"))
354 const char*
const* labels) {
355 for (
int bit = 0; bit < 8; ++bit) {
356 bool set = (value & (1 << bit)) != 0;
358 set ? ImVec4(0.3f, 0.7f, 0.3f, 1.0f) : ImVec4(0.2f, 0.2f, 0.2f, 0.6f);
361 snprintf(buf,
sizeof(buf),
"%s##%s_%d", labels[bit], id_prefix, bit);
365 ImGui::SmallButton(buf);
373 if (!ImGui::TreeNode(
"Manual Controls"))
382 if (ImGui::SliderInt(
"Crystal Bits", &crystal_int, 0, 127)) {
386 if (ImGui::Button(
"Clear All")) {
391 if (ImGui::Button(
"Complete All")) {
428 if (event.
type ==
"frame_complete" ||
429 event.
type ==
"breakpoint_hit" || event.
type ==
"all") {
430 live_refresh_pending_.store(true);
439 const double now = ImGui::GetTime();
445 auto status =
live_client_->Subscribe({
"frame_complete",
"breakpoint_hit"});
463 const double now = ImGui::GetTime();
480 auto state_or = backend->ReadProgressionStateFromLiveSram(*
live_client_);
481 if (!state_or.ok()) {
493 constexpr uint32_t kBaseAddress = 0x7EF000;
494 constexpr uint16_t kStartOffset =
496 constexpr uint16_t kEndOffset =
498 constexpr size_t kReadLength = kEndOffset - kStartOffset + 1;
499 constexpr uint32_t kReadAddress = kBaseAddress + kStartOffset;
501 auto bytes_or =
live_client_->ReadBlock(kReadAddress, kReadLength);
502 if (!bytes_or.ok()) {
506 if (bytes_or->size() < kReadLength) {
511 const auto read_byte = [&](uint16_t offset) -> uint8_t {
512 return (*bytes_or)[offset - kStartOffset];
567 "Bit0",
"HallOfSecrets",
"PendantQuest",
"Bit3",
568 "ElderMet",
"Bit5",
"Bit6",
"FortressComplete",
572 "Bit0",
"Bit1",
"KydrogEncounter",
573 "Bit3",
"DekuSoulFreed",
"BookOfSecrets",
Loads and queries the hack manifest JSON for yaze-ASM integration.
void ClearOracleProgressionState()
std::optional< OracleProgressionState > oracle_progression_state() const
bool loaded() const
Check if the manifest has been loaded.
void SetOracleProgressionState(const OracleProgressionState &state)
Visual dashboard of Oracle game state from SRAM data.
void EnsureLiveSubscription()
std::string live_sync_error_
static void DrawBitGrid(const char *id_prefix, uint8_t value, const char *const *labels)
core::OracleProgressionState state_
std::string GetIcon() const override
Material Design icon for this panel.
std::string GetWorkflowDescription() const override
Optional workflow description for menus/command palette.
static bool StatesEqual(const core::OracleProgressionState &a, const core::OracleProgressionState &b)
void ProcessPendingLiveRefresh()
bool live_subscription_active_
std::string last_srm_error_
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
std::shared_ptr< emu::mesen::MesenSocketClient > live_client_
WindowLifecycle GetWindowLifecycle() const override
Get the lifecycle category for this window.
workflow::HackWorkflowBackend * GetWorkflowBackend() const
~ProgressionDashboardPanel() override
void DrawSrmImportControls()
std::string GetDisabledTooltip() const override
Get tooltip text when panel is disabled.
void Draw(bool *p_open) override
Draw the panel content.
float GetPreferredWidth() const override
Get preferred width for this panel (optional)
std::string GetId() const override
Unique identifier for this panel.
workflow::ProgressionCapability * GetProgressionBackend() const
void DrawLiveSyncControls()
static constexpr const char * oosprog2_labels_[8]
std::atomic< bool > live_refresh_pending_
std::string GetWorkflowGroup() const override
Optional workflow group for hack-centric actions.
double last_subscribe_attempt_time_
float live_refresh_interval_seconds_
double last_live_refresh_time_
emu::mesen::EventListenerId live_listener_id_
core::HackManifest * manifest_
bool IsEnabled() const override
Check if this panel is currently enabled.
void PushStateToManifestIfNeeded()
static constexpr const char * oosprog_labels_[8]
void DrawManualControls()
std::string GetEditorCategory() const override
Editor category this panel belongs to.
void DrawCrystalTracker()
void DetachLiveListener()
void RefreshLiveClientBinding()
std::string loaded_srm_path_
bool RefreshStateFromLiveSram()
Base interface for all logical window content components.
virtual absl::StatusOr< core::OracleProgressionState > LoadProgressionStateFromFile(const std::string &filepath) const =0
static std::shared_ptr< MesenSocketClient > & GetClient()
RAII guard for ImGui style colors.
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define ICON_MD_DASHBOARD
absl::StatusOr< OracleProgressionState > LoadOracleProgressionFromSrmFile(const std::string &srm_path)
::yaze::project::YazeProject * current_project()
Get the current project instance.
workflow::HackWorkflowBackend * hack_workflow_backend()
workflow::ProgressionCapability * hack_progression_backend()
Editors are the view controllers for the application.
WindowLifecycle
Defines lifecycle behavior for editor windows.
@ CrossEditor
User can pin to persist across editors.
Oracle of Secrets game progression state parsed from SRAM.
static constexpr uint16_t kSideQuestOffset
static uint8_t GetCrystalMask(int dungeon_number)
Get the crystal bitmask for a dungeon number (1-7).
static constexpr uint16_t kPendantOffset
static constexpr uint16_t kGameStateOffset
int GetCrystalCount() const
Count completed dungeons using popcount on crystal bitfield.
static constexpr uint16_t kOosProgOffset
bool IsDungeonComplete(int dungeon_number) const
Check if a specific dungeon is complete.
static constexpr uint16_t kOosProg2Offset
std::string GetGameStateName() const
Get human-readable name for the current game state.
static constexpr uint16_t kCrystalOffset
Event from Mesen2 subscription.
std::vector< FileDialogFilter > filters