yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
entity_workbench.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <vector>
5
6#include "absl/strings/str_format.h"
7
14#include "imgui/imgui.h"
16
17namespace yaze {
18namespace editor {
19
20namespace {
21
23 switch (type) {
25 return "Exit";
27 return "Entrance";
29 return "Item";
31 return "Sprite";
33 return "Transport";
35 return "Music";
37 return "Tilemap";
39 return "Properties";
41 return "Dungeon Sprite";
42 default:
43 return "Unknown";
44 }
45}
46
47} // namespace
48
50 (void)p_open;
51 const auto ctx = CurrentOverworldWindowContext();
52 if (!ctx)
53 return;
54
55 auto* editor = ctx.editor;
56 if (editor->is_lyra_preview()) {
57 ImGui::TextDisabled("Lyra preview: entity editing unavailable");
58 return;
59 }
60
61 // Special triggers are tile properties, not entrance entities. Keep their
62 // on-demand inspector reachable even when no marker is selected.
63 const Rom* rom = editor->rom();
64 if (rom != inspected_rom_ ||
65 (rom && (rom->data() != inspected_rom_data_ ||
66 rom->filename() != inspected_filename_)) ||
68 special_entrances_->inspected_map != editor->current_map_id())) {
69 special_entrances_.reset();
70 }
71 if (ImGui::Button(special_entrances_ ? "Refresh special entrances"
72 : "Inspect special entrances")) {
74 rom ? std::optional<zelda3::SpecialEntranceInspection>(
75 zelda3::InspectSpecialEntrances(*rom, editor->overworld(),
76 editor->current_map_id()))
77 : std::nullopt;
78 inspected_rom_ = rom;
79 inspected_rom_data_ = rom ? rom->data() : nullptr;
80 inspected_filename_ = rom ? rom->filename() : "";
81 }
83 const auto& inspection = *special_entrances_;
84 if (!inspection.available) {
85 ImGui::TextWrapped("%s", inspection.provenance.c_str());
86 } else {
87 ImGui::Text("%zu matching positions", inspection.matches.size());
88 for (size_t i = 0; i < inspection.matches.size(); ++i) {
89 const auto& match = inspection.matches[i];
90 const auto& row = inspection.rows[match.row_index];
91 const int destination = row.destination_area;
92 const std::string name =
93 destination >= 0
95 "overworld_map", destination,
96 absl::StrFormat("Special area $%02X", destination))
97 : "Unresolved destination";
98 ImGui::PushID(static_cast<int>(i));
99 if (ImGui::Button(absl::StrFormat("%s ($%02X) - tile (%d,%d)", name,
100 destination, match.world_x,
101 match.world_y)
102 .c_str())) {
103 editor->LocateOverworldTile(match.screen_id, match.world_x,
104 match.world_y);
105 }
106 ImGui::PopID();
107 }
108 if (ImGui::TreeNode("Technical details and provenance")) {
109 ImGui::TextWrapped("%s", inspection.provenance.c_str());
110 ImGui::TextWrapped("%s", inspection.limitation.c_str());
111 if (inspection.overlay_may_replace_tiles) {
112 ImGui::TextWrapped(
113 "This area has an overlay; the runtime sampled "
114 "tile may depend on overlay state.");
115 }
116 for (const auto& match : inspection.matches) {
117 const auto& row = inspection.rows[match.row_index];
118 ImGui::Text(
119 "Screen $%02X parent $%02X at (%d,%d): Map16 $%03X, "
120 "word $%04X, mask $%03X -> fake exit $%04X "
121 "(arrival direction $%04X)",
122 match.screen_id, match.parent_area, match.tile_x, match.tile_y,
123 match.tile16_id, match.first_word, match.masked_trigger,
124 row.destination_marker, row.direction);
125 }
126 if (ImGui::Button("Copy special entrance details")) {
127 std::string details = inspection.provenance + "\n";
128 for (const auto& match : inspection.matches) {
129 const auto& row = inspection.rows[match.row_index];
130 details += absl::StrFormat(
131 "screen=$%02X parent=$%02X tile=(%d,%d) map16=$%03X "
132 "word=$%04X mask=$%03X marker=$%04X\n",
133 match.screen_id, match.parent_area, match.tile_x, match.tile_y,
134 match.tile16_id, match.first_word, match.masked_trigger,
135 row.destination_marker);
136 }
137 ImGui::SetClipboardText(details.c_str());
138 }
139 ImGui::TreePop();
140 }
141 }
142 ImGui::Separator();
143 }
144
145 auto* active_entity = editor->current_entity();
146 if (active_entity == nullptr) {
147 ImGui::TextDisabled(tr("No entity selected"));
148 return;
149 }
150
151 ImGui::Text(tr("Type: %s"), GetEntityTypeLabel(active_entity->entity_type_));
152 ImGui::Separator();
153
154 if (ImGui::Button(ICON_MD_SETTINGS " Open Popup Editor")) {
155 OpenEditorFor(active_entity);
156 }
157}
158
160 const auto ctx = CurrentOverworldWindowContext();
161 if (ctx) {
162 ctx.editor->SetCurrentEntity(entity);
163 }
164}
165
167 const auto ctx = CurrentOverworldWindowContext();
168 if (!ctx || !entity)
169 return;
170
171 auto* editor = ctx.editor;
172 editor->SetCurrentEntity(entity);
173 editing_entity_ = entity;
174
175 switch (entity->entity_type_) {
177 editor->edit_exit() = *static_cast<zelda3::OverworldExit*>(entity);
178 ImGui::OpenPopup(
180 .c_str());
181 break;
183 editor->edit_entrance() =
184 *static_cast<zelda3::OverworldEntrance*>(entity);
185 ImGui::OpenPopup(
187 .c_str());
188 break;
190 editor->edit_item() = *static_cast<zelda3::OverworldItem*>(entity);
191 ImGui::OpenPopup(
193 .c_str());
194 break;
196 editor->edit_sprite() = *static_cast<zelda3::Sprite*>(entity);
197 ImGui::OpenPopup(
199 .c_str());
200 break;
201 default:
202 break;
203 }
204}
205
207 const auto ctx = CurrentOverworldWindowContext();
208 if (!ctx || !entity) {
209 return;
210 }
211 ctx.editor->SetCurrentEntity(entity);
212 ImGui::OpenPopup(kContextMenuPopupId);
213}
214
216 const auto ctx = CurrentOverworldWindowContext();
217 if (!ctx)
218 return;
219
220 auto* editor = ctx.editor;
221 auto* editing_entity = editing_entity_;
222
223 zelda3::GameEntity* entity_to_edit = nullptr;
224 if (ImGui::BeginPopup(kContextMenuPopupId)) {
225 entity_to_edit = DrawEntityContextMenu();
226 ImGui::EndPopup();
227 }
228 if (entity_to_edit) {
229 OpenEditorFor(entity_to_edit);
230 }
231
232 if (ImGui::BeginPopupModal("Entity Insert Error", nullptr,
233 ImGuiWindowFlags_AlwaysAutoResize)) {
234 ImGui::TextWrapped("%s", editor->insert_error().c_str());
235 ImGui::Spacing();
236 if (ImGui::Button(tr("OK"), ImVec2(120.0f, 0.0f))) {
237 editor->insert_error().clear();
238 ImGui::CloseCurrentPopup();
239 }
240 ImGui::EndPopup();
241 }
242
243 if (DrawExitEditorPopup(editor->edit_exit())) {
244 if (editing_entity &&
245 editing_entity->entity_type_ == zelda3::GameEntity::EntityType::kExit) {
246 *static_cast<zelda3::OverworldExit*>(editing_entity) =
247 editor->edit_exit();
248 editor->NotifyEntityModified(editing_entity);
249 }
250 }
251 if (DrawOverworldEntrancePopup(editor->edit_entrance())) {
252 if (editing_entity && editing_entity->entity_type_ ==
254 *static_cast<zelda3::OverworldEntrance*>(editing_entity) =
255 editor->edit_entrance();
256 editor->NotifyEntityModified(editing_entity);
257 }
258 }
259 if (DrawItemEditorPopup(editor->edit_item())) {
260 if (editing_entity &&
261 editing_entity->entity_type_ == zelda3::GameEntity::EntityType::kItem) {
262 if (editor->edit_item().deleted) {
263 (void)editor->DeleteSelectedItem();
264 } else {
265 *static_cast<zelda3::OverworldItem*>(editing_entity) =
266 editor->edit_item();
267 editor->NotifyEntityModified(editing_entity);
268 }
269 }
270 }
271 if (DrawSpriteEditorPopup(editor->edit_sprite())) {
272 if (editing_entity && editing_entity->entity_type_ ==
274 *static_cast<zelda3::Sprite*>(editing_entity) = editor->edit_sprite();
275 editor->NotifyEntityModified(editing_entity);
276 }
277 }
278
279 const bool exit_popup_open = ImGui::IsPopupOpen(
280 gui::MakePopupId(gui::EditorNames::kOverworld, "Exit Editor").c_str());
281 const bool entrance_popup_open = ImGui::IsPopupOpen(
283 .c_str());
284 const bool item_popup_open = ImGui::IsPopupOpen(
285 gui::MakePopupId(gui::EditorNames::kOverworld, "Item Editor").c_str());
286 const bool sprite_popup_open = ImGui::IsPopupOpen(
287 gui::MakePopupId(gui::EditorNames::kOverworld, "Sprite Editor").c_str());
288 if (!exit_popup_open && !entrance_popup_open && !item_popup_open &&
289 !sprite_popup_open) {
290 editing_entity_ = nullptr;
291 }
292}
293
295 EntityMutationService* mutation_service) {
296 const auto ctx = CurrentOverworldWindowContext();
297 if (!ctx || !mutation_service)
298 return;
299
300 auto* editor = ctx.editor;
301 auto request = editor->TakePendingEntityInsertion();
302 if (!request)
303 return;
304
305 auto res = mutation_service->InsertEntity(request->type, request->target,
306 request->item_id);
307
308 if (res.ok()) {
309 OpenEditorFor(res.entity);
310 editor->NotifyEntityModified(res.entity);
311 } else {
312 editor->insert_error() = res.error_message;
313 ImGui::OpenPopup("Entity Insert Error");
314 }
315}
316
318 const auto ctx = CurrentOverworldWindowContext();
319 if (!ctx)
320 return nullptr;
321
322 auto* editor = ctx.editor;
323 auto* active_entity = editor->current_entity();
324 if (!active_entity)
325 return nullptr;
326
327 zelda3::GameEntity* entity_to_edit = nullptr;
328 std::vector<gui::MenuItemSpec> items;
329 items.emplace_back(
330 "Edit Properties...", ICON_MD_EDIT,
331 [&entity_to_edit, active_entity]() { entity_to_edit = active_entity; });
332
333 if (active_entity->entity_type_ == zelda3::GameEntity::EntityType::kExit) {
334 auto* exit = static_cast<zelda3::OverworldExit*>(active_entity);
335 items.emplace_back("Jump to Room", ICON_MD_LINK, [editor, exit]() {
336 editor->RequestJumpToRoom(exit->room_id_);
337 });
338 } else if (active_entity->entity_type_ ==
340 auto* entrance = static_cast<zelda3::OverworldEntrance*>(active_entity);
341 items.emplace_back("Jump to Entrance", ICON_MD_LINK, [editor, entrance]() {
342 editor->RequestJumpToEntrance(entrance->entrance_id_);
343 });
344 }
346 return entity_to_edit;
347}
348
350
351} // namespace editor
352} // namespace yaze
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
auto filename() const
Definition rom.h:207
auto data() const
Definition rom.h:201
Editor-level service responsible for overworld entity mutations.
MutationResult InsertEntity(const std::string &type, ImVec2 pos, int map_id, int game_state, uint8_t item_id=0)
Dispatches entity insertion based on type string.
Authoritative component for entity editing state and UI.
std::optional< zelda3::SpecialEntranceInspection > special_entrances_
void OpenEditorFor(zelda3::GameEntity *entity)
void ProcessPendingInsertion(EntityMutationService *mutation_service)
static constexpr const char * kContextMenuPopupId
void Draw(bool *p_open) override
Draw the panel content.
void OpenContextMenuFor(zelda3::GameEntity *entity)
void SetActiveEntity(zelda3::GameEntity *entity)
Base class for all overworld and dungeon entities.
Definition common.h:31
enum yaze::zelda3::GameEntity::EntityType entity_type_
Represents an overworld exit that transitions from dungeon to overworld.
A class for managing sprites in the overworld and underworld.
Definition sprite.h:39
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_LINK
Definition icons.h:1090
#define ICON_MD_EDIT
Definition icons.h:645
const char * GetEntityTypeLabel(zelda3::GameEntity::EntityType type)
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite)
Definition entity.cc:538
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance)
Definition entity.cc:119
bool DrawItemEditorPopup(zelda3::OverworldItem &item)
Definition entity.cc:406
OverworldWindowContext CurrentOverworldWindowContext()
bool DrawExitEditorPopup(zelda3::OverworldExit &exit)
Definition entity.cc:204
constexpr const char * kOverworld
Definition popup_id.h:53
std::string MakePopupId(size_t session_id, const std::string &editor_name, const std::string &popup_name)
Generate session-aware popup IDs to prevent conflicts in multi-editor layouts.
Definition popup_id.h:23
void RenderMenuItems(const std::vector< MenuItemSpec > &items)
SpecialEntranceInspection InspectSpecialEntrances(const Rom &rom, Overworld &overworld, int map_id)
#define REGISTER_PANEL(PanelClass)
Auto-registration macro for panels with default constructors.
Room transition destination.
Definition zelda.h:448
static std::string GetLabel(const std::string &resource_type, int id, const std::string &default_value="")
Get a label by resource type and ID.