yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
crumble_floor_view.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <string_view>
5
10#include "core/project.h"
11#include "imgui/imgui.h"
12#include "rom/rom.h"
13#include "zelda3/dungeon/room.h"
14
15namespace yaze::editor {
16
17bool HasOracleCrumbleTag(const Rom& rom, const project::YazeProject* project,
18 const zelda3::Room& room) {
19 if (!project || !project->hack_manifest.loaded()) {
20 return false;
21 }
22 const auto& tags = project->hack_manifest.room_tags();
23 const bool has_source =
24 std::any_of(tags.begin(), tags.end(), [](const auto& tag) {
25 return tag.enabled && tag.address == 0x01CC08 &&
26 std::string_view(tag.source)
27 .starts_with("Dungeons/crumblefloor_tag.asm:");
28 });
29 if (!has_source) {
30 return false;
31 }
32 // USDASM Underworld_HandleRoomTags.vectors. Do not use the manifest tag_id:
33 // older Oracle generators infer a linear dispatch order that is incorrect.
34 for (const auto tag : {room.tag1(), room.tag2()}) {
35 const int id = static_cast<int>(tag);
36 if (id < 0 || id > 0x3F) {
37 continue;
38 }
39 const auto vector = rom.ReadWord(0xC27D + id * 2);
40 if (vector.ok() && *vector == 0xCC08) {
41 return true;
42 }
43 }
44 return false;
45}
46
49 const auto& layout = room.bg1_buffer();
50 const auto& objects = room.object_bg1_buffer();
51 const auto& coverage = objects.coverage_data();
52 for (int y = 0; y < 32; ++y) {
53 for (int x = 0; x < 32; ++x) {
54 const size_t pixel = y * 16 * 512 + x * 16;
55 const bool object_written =
56 pixel < coverage.size() && coverage[pixel] != 0;
57 words[y * 32 + x] =
58 (object_written ? objects : layout).GetTileAt(x * 2, y * 2);
59 }
60 }
61 return words;
62}
63
64void CrumbleFloorView::Sync(const Rom* rom, const project::YazeProject* project,
65 const zelda3::Room& room) {
66 const bool supported = rom && HasOracleCrumbleTag(*rom, project, room);
67 const auto words = ReadCrumbleFloorWords(room);
68 if (rom_ != rom || project_ != project || room_id_ != room.id() ||
69 words_ != words || supported_ != supported) {
70 const bool had_route = !model_.route().empty();
71 model_.Reset(words);
72 rehearsing_ = false;
73 message_ = had_route ? "Preview reset: room or floor data changed." : "";
74 }
75 rom_ = rom;
76 project_ = project;
77 room_id_ = room.id();
78 supported_ = supported;
79 words_ = words;
80}
81
82void CrumbleFloorView::DrawControls(bool interactive) {
83 gui::AutoWidgetScope scope("Dungeon/Crumble");
84 if (!supported_) {
85 rehearsing_ = false;
86 ImGui::TextWrapped(
87 "Crumble preview needs an active Oracle crumble tag and its source "
88 "manifest.");
89 return;
90 }
91 const bool has_floor =
92 std::any_of(words_.begin(), words_.end(), [](auto word) {
93 const auto cell = ClassifyCrumbleFloorWord(word);
94 return cell == CrumbleFloorCell::kIntact ||
95 cell == CrumbleFloorCell::kCracked;
96 });
97 if (!has_floor) {
98 rehearsing_ = false;
99 ImGui::TextWrapped(
100 "Crumble tag is active, but BG1 has no matching floor cells. "
101 "Inspect or place a floor pattern before rehearsing.");
102 return;
103 }
104 ImGui::BeginDisabled(!interactive);
105 ImGui::Checkbox("Rehearse floor##crumble", &rehearsing_);
106 gui::AutoRegisterLastItem("checkbox", "Rehearse",
107 "Read-only floor rehearsal");
108 ImGui::SameLine();
109 if (ImGui::SmallButton("Back##crumble")) {
110 model_.Back();
111 message_.clear();
112 }
113 gui::AutoRegisterLastItem("button", "Back");
114 ImGui::SameLine();
115 if (ImGui::SmallButton("Reset##crumble")) {
117 message_.clear();
118 }
119 gui::AutoRegisterLastItem("button", "Reset");
120 ImGui::EndDisabled();
121 ImGui::TextWrapped(
122 "Green: intact. Amber: cracked. Red: pit. Leaving a cell wears it; "
123 "standing still does not.");
124 if (rehearsing_) {
125 ImGui::TextWrapped(
126 "Click a highlighted cell, then adjacent cells. Escape exits. "
127 "Floor-only model: collision, enemies, jumps, Minish and room entry "
128 "are excluded. No ROM edits.");
129 }
130 if (!message_.empty()) {
131 ImGui::TextWrapped("%s", message_.c_str());
132 }
133}
134
136 bool interactive, bool shortcut_focus) {
137 if (!supported_ || !rt.draw_list) {
138 return false;
139 }
140 const bool owns_input = rehearsing_ && interactive;
141 if (owns_input && shortcut_focus && !ImGui::GetIO().WantTextInput &&
142 ImGui::IsKeyPressed(ImGuiKey_Escape)) {
143 rehearsing_ = false;
144 }
145 const DungeonCanvasTransform transform(rt.canvas_p0, rt.scrolling, rt.scale);
146 const auto [x, y] = transform.ScreenToRoomTiles(ImGui::GetMousePos(), 16);
147 if (owns_input && rehearsing_ && rt.hovered && ImGui::IsWindowHovered() &&
148 ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
149 !ImGui::IsMouseDown(ImGuiMouseButton_Middle) &&
150 !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
151 const auto status = model_.Step(x, y);
152 message_ = status.ok() ? "" : std::string(status.message());
153 }
154
155 const auto& theme = AgentUI::GetTheme();
156 rt.draw_list->PushClipRect(
157 rt.canvas_p0,
158 ImVec2(rt.canvas_p0.x + rt.canvas_sz.x, rt.canvas_p0.y + rt.canvas_sz.y),
159 true);
160 for (int i = 0; i < CrumbleFloorModel::kCellCount; ++i) {
161 const auto cell = model_.cell(i);
162 if (cell == CrumbleFloorCell::kOther) {
163 continue;
164 }
165 const auto color = ImGui::GetColorU32(
166 cell == CrumbleFloorCell::kIntact ? theme.status_success
167 : cell == CrumbleFloorCell::kCracked ? theme.status_warning
168 : theme.status_error);
169 const auto p =
170 transform.RoomPixelsToScreen(ImVec2((i % 32) * 16, (i / 32) * 16));
171 const auto q = transform.RoomPixelsToScreen(
172 ImVec2((i % 32 + 1) * 16, (i / 32 + 1) * 16));
173 rt.draw_list->AddRectFilled(p, q, (color & 0x00FFFFFF) | 0x33000000);
174 rt.draw_list->AddRect(p, q, color);
175 }
176 const auto center = [&](int cell) {
177 return transform.RoomPixelsToScreen(
178 ImVec2((cell % 32) * 16 + 8, (cell / 32) * 16 + 8));
179 };
180 for (size_t i = 1; i < model_.route().size(); ++i) {
181 rt.draw_list->AddLine(center(model_.route()[i - 1]),
182 center(model_.route()[i]),
183 ImGui::GetColorU32(ImGuiCol_Text), 2.0f);
184 }
185 if (model_.current()) {
186 rt.draw_list->AddCircleFilled(center(*model_.current()),
187 4.0f * transform.scale(),
188 ImGui::GetColorU32(ImGuiCol_Text));
189 }
190 rt.draw_list->PopClipRect();
191 return owns_input;
192}
193
194} // namespace yaze::editor
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
absl::StatusOr< uint16_t > ReadWord(int offset) const
Definition rom.cc:793
const std::vector< RoomTagEntry > & room_tags() const
Get all room tags.
bool loaded() const
Check if the manifest has been loaded.
CrumbleFloorCell cell(int index) const
absl::Status Step(int x, int y)
std::optional< int > current() const
std::array< uint16_t, kCellCount > Words
const std::vector< int > & route() const
void Sync(const Rom *rom, const project::YazeProject *project, const zelda3::Room &room)
const project::YazeProject * project_
CrumbleFloorModel::Words words_
bool DrawCanvas(const gui::CanvasRuntime &rt, bool interactive, bool shortcut_focus)
void DrawControls(bool interactive)
std::pair< int, int > ScreenToRoomTiles(ImVec2 screen, int tile_size) const
RAII scope that enables automatic widget registration.
TagKey tag2() const
Definition room.h:988
TagKey tag1() const
Definition room.h:987
auto & bg1_buffer()
Definition room.h:1145
auto & object_bg1_buffer()
Definition room.h:1149
int id() const
Definition room.h:1012
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
CrumbleFloorModel::Words ReadCrumbleFloorWords(const zelda3::Room &room)
bool HasOracleCrumbleTag(const Rom &rom, const project::YazeProject *project, const zelda3::Room &room)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
Modern project structure with comprehensive settings consolidation.
Definition project.h:287
core::HackManifest hack_manifest
Definition project.h:360
Automatic widget registration helpers for ImGui Test Engine integration.