1#ifndef YAZE_APP_EDITOR_ORACLE_PANELS_PROGRESSION_DASHBOARD_PANEL_H
2#define YAZE_APP_EDITOR_ORACLE_PANELS_PROGRESSION_DASHBOARD_PANEL_H
20#include "imgui/imgui.h"
37 std::string
GetId()
const override {
return "oracle.progression_dashboard"; }
39 return "Progression Dashboard";
48 void Draw(
bool* p_open)
override {
58 if (project && project->hack_manifest.loaded()) {
89 ImGui::Text(
"SRAM (.srm)");
93 {
"SRAM (.srm)",
"srm"},
97 if (ImGui::Button(
"Import...")) {
98 std::string file_path =
100 if (!file_path.empty()) {
114 if (ImGui::Button(
"Clear")) {
126 ImGui::TextDisabled(
"Loaded: %s", p.filename().string().c_str());
127 if (ImGui::IsItemHovered()) {
131 ImGui::TextDisabled(
"Loaded: (none)");
135 ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.35f, 1.0f),
"Error: %s",
146 ImGui::Text(
"Live SRAM (Mesen2)");
149 ImGui::TextColored(ImVec4(0.25f, 0.85f, 0.35f, 1.0f),
"Connected");
151 ImGui::TextDisabled(
"Disconnected");
154 if (ImGui::SmallButton(
"Sync from Mesen")) {
162 ImGui::SetNextItemWidth(70.0f);
164 0.05f, 0.5f,
"%.2fs");
168 ImGui::TextDisabled(
"Use a Mesen panel to connect (shared client).");
172 ImGui::TextColored(ImVec4(1.0f, 0.55f, 0.35f, 1.0f),
"Live sync: %s",
190 ImGui::Text(
"Crystal Tracker");
193 float item_width = 44.0f;
195 for (
int d = 1; d <= 7; ++d) {
200 complete ? ImVec4(0.2f, 0.8f, 0.3f, 1.0f)
201 : ImVec4(0.3f, 0.3f, 0.3f, 0.6f);
205 {{ImGuiCol_Button, color},
206 {ImGuiCol_ButtonHovered,
207 ImVec4(color.x + 0.1f, color.y + 0.1f, color.z + 0.1f, 1.0f)}});
210 snprintf(label,
sizeof(label),
"D%d", d);
211 if (ImGui::Button(label, ImVec2(item_width, 36.0f))) {
217 if (d < 7) ImGui::SameLine();
224 ImGui::Text(
"Game State");
228 const char* phases[] = {
"Start",
"Loom Beach",
"Kydrog Complete",
232 float bar_width = ImGui::GetContentRegionAvail().x;
233 float segment = bar_width /
static_cast<float>(phase_count);
235 ImVec2 bar_pos = ImGui::GetCursorScreenPos();
236 ImDrawList* draw_list = ImGui::GetWindowDrawList();
238 for (
int i = 0; i < phase_count; ++i) {
239 ImVec2 seg_min(bar_pos.x + segment * i, bar_pos.y);
240 ImVec2 seg_max(bar_pos.x + segment * (i + 1), bar_pos.y + 24.0f);
243 ? IM_COL32(60, 140, 200, 220)
244 : IM_COL32(50, 50, 50, 180);
246 draw_list->AddRectFilled(seg_min, seg_max, fill, 3.0f);
247 draw_list->AddRect(seg_min, seg_max, IM_COL32(80, 80, 80, 255), 3.0f);
250 ImVec2 text_pos(seg_min.x + 4, seg_min.y + 4);
251 draw_list->AddText(text_pos, IM_COL32(220, 220, 220, 255), phases[i]);
254 ImGui::Dummy(ImVec2(0, 30));
259 ImGui::Text(
"Dungeon Completion");
267 DungeonInfo dungeons[] = {
268 {
"D1 Mushroom Grotto", 1}, {
"D2 Tail Palace", 2},
269 {
"D3 Kalyxo Castle", 3}, {
"D4 Zora Temple", 4},
270 {
"D5 Glacia Estate", 5}, {
"D6 Goron Mines", 6},
271 {
"D7 Dragon Ship", 7}, {
"FOS Fortress", 0},
272 {
"SOP Shrine of Power", 0}, {
"SOW Shrine of Wisdom", 0},
275 ImGui::Columns(2,
"dungeon_grid",
false);
276 for (
const auto& dungeon : dungeons) {
277 bool complete =
false;
278 if (dungeon.number >= 1 && dungeon.number <= 7) {
283 complete ? ImVec4(0.1f, 0.6f, 0.2f, 1.0f)
284 : ImVec4(0.25f, 0.25f, 0.25f, 1.0f);
288 ImGui::Selectable(dungeon.label, complete,
289 ImGuiSelectableFlags_Disabled);
297 if (!ImGui::TreeNode(
"Story Flags"))
return;
318 const char*
const* labels) {
319 for (
int bit = 0; bit < 8; ++bit) {
320 bool set = (value & (1 << bit)) != 0;
321 ImVec4 color = set ? ImVec4(0.3f, 0.7f, 0.3f, 1.0f)
322 : ImVec4(0.2f, 0.2f, 0.2f, 0.6f);
325 snprintf(buf,
sizeof(buf),
"%s##%s_%d", labels[bit], id_prefix, bit);
329 ImGui::SmallButton(buf);
331 if (bit < 7) ImGui::SameLine();
336 if (!ImGui::TreeNode(
"Manual Controls"))
return;
344 if (ImGui::SliderInt(
"Crystal Bits", &crystal_int, 0, 127)) {
348 if (ImGui::Button(
"Clear All")) {
353 if (ImGui::Button(
"Complete All")) {
390 if (event.
type ==
"frame_complete" ||
391 event.
type ==
"breakpoint_hit" || event.
type ==
"all") {
392 live_refresh_pending_.store(true);
401 const double now = ImGui::GetTime();
407 auto status =
live_client_->Subscribe({
"frame_complete",
"breakpoint_hit"});
425 const double now = ImGui::GetTime();
441 constexpr uint32_t kBaseAddress = 0x7EF000;
444 constexpr size_t kReadLength = kEndOffset - kStartOffset + 1;
445 constexpr uint32_t kReadAddress = kBaseAddress + kStartOffset;
447 auto bytes_or =
live_client_->ReadBlock(kReadAddress, kReadLength);
448 if (!bytes_or.ok()) {
452 if (bytes_or->size() < kReadLength) {
457 const auto read_byte = [&](uint16_t offset) -> uint8_t {
458 return (*bytes_or)[offset - kStartOffset];
503 "Bit0",
"HallOfSecrets",
"PendantQuest",
"Bit3",
504 "ElderMet",
"Bit5",
"Bit6",
"FortressComplete",
508 "Bit0",
"Bit1",
"KydrogEncounter",
"Bit3",
509 "DekuSoulFreed",
"BookOfSecrets",
"Bit6",
"Bit7",
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)
Base interface for all logical panel components.
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.
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.
~ProgressionDashboardPanel() override
void DrawSrmImportControls()
void Draw(bool *p_open) override
Draw the panel content.
float GetPreferredWidth() const override
Get preferred width for this panel (optional)
PanelCategory GetPanelCategory() const override
Get the lifecycle category for this panel.
std::string GetId() const override
Unique identifier for this panel.
static constexpr const char * oosprog_labels_[8]
void DrawLiveSyncControls()
double last_subscribe_attempt_time_
float live_refresh_interval_seconds_
std::shared_ptr< emu::mesen::MesenSocketClient > live_client_
double last_live_refresh_time_
static constexpr const char * oosprog2_labels_[8]
emu::mesen::EventListenerId live_listener_id_
void PushStateToManifestIfNeeded()
void DrawManualControls()
std::string GetEditorCategory() const override
Editor category this panel belongs to.
void DrawCrystalTracker()
void DetachLiveListener()
void RefreshLiveClientBinding()
std::string loaded_srm_path_
std::atomic< bool > live_refresh_pending_
core::HackManifest * manifest_
bool RefreshStateFromLiveSram()
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.
Editors are the view controllers for the application.
PanelCategory
Defines lifecycle behavior for editor panels.
@ 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