yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
entity.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <fstream>
5#include <string>
6
10#include "app/gui/core/style.h"
12#include "imgui.h"
13#include "util/hex.h"
14#include "zelda3/common.h"
16
17namespace yaze {
18namespace editor {
19
20using ImGui::BeginChild;
21using ImGui::Button;
22using ImGui::Checkbox;
23using ImGui::EndChild;
24using ImGui::SameLine;
25using ImGui::Selectable;
26using ImGui::Text;
27
28constexpr float kInputFieldSize = 30.f;
29
31 ImVec2 canvas_p0, ImVec2 scrolling,
32 float scale) {
33 // Get the mouse position relative to the canvas
34 const ImGuiIO& io = ImGui::GetIO();
35 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
36 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
37
38 // Scale entity bounds to match canvas zoom level
39 float scaled_x = entity.x_ * scale;
40 float scaled_y = entity.y_ * scale;
41 float scaled_size = 16.0f * scale;
42
43 // Check if the mouse is hovering over the scaled entity bounds
44 return mouse_pos.x >= scaled_x && mouse_pos.x <= scaled_x + scaled_size &&
45 mouse_pos.y >= scaled_y && mouse_pos.y <= scaled_y + scaled_size;
46}
47
49 const gui::CanvasRuntime& rt) {
50 // Use runtime geometry to compute mouse position relative to canvas
51 const ImGuiIO& io = ImGui::GetIO();
52 const ImVec2 origin(rt.canvas_p0.x + rt.scrolling.x,
53 rt.canvas_p0.y + rt.scrolling.y);
54 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
55
56 // Scale entity bounds to match canvas zoom level
57 float scaled_x = entity.x_ * rt.scale;
58 float scaled_y = entity.y_ * rt.scale;
59 float scaled_size = 16.0f * rt.scale;
60
61 // Check if the mouse is hovering over the scaled entity bounds
62 return mouse_pos.x >= scaled_x && mouse_pos.x <= scaled_x + scaled_size &&
63 mouse_pos.y >= scaled_y && mouse_pos.y <= scaled_y + scaled_size;
64}
65
66void MoveEntityOnGrid(zelda3::GameEntity* entity, ImVec2 canvas_p0,
67 ImVec2 scrolling, bool free_movement, float scale) {
68 // Get the mouse position relative to the canvas
69 const ImGuiIO& io = ImGui::GetIO();
70 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
71 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
72
73 // Convert screen position to world position accounting for scale
74 float world_x = mouse_pos.x / scale;
75 float world_y = mouse_pos.y / scale;
76
77 // Calculate the new position on the 16x16 or 8x8 grid (in world coordinates)
78 // Overworld sprite records encode positions in 16-pixel units, even when
79 // the general entity free-movement modifier is held.
80 int grid_size =
81 free_movement && entity->entity_type_ != zelda3::GameEntity::kSprite ? 8
82 : 16;
83 int new_x = static_cast<int>(world_x) / grid_size * grid_size;
84 int new_y = static_cast<int>(world_y) / grid_size * grid_size;
85
86 // Update the entity position
87 entity->set_x(new_x);
88 entity->set_y(new_y);
89}
90
92 bool set_done = false;
93 if (set_done) {
94 set_done = false;
95 }
96 if (ImGui::BeginPopup("Entrance Inserter")) {
97 static int entrance_id = 0;
98 if (ImGui::IsWindowAppearing()) {
99 entrance_id = 0;
100 }
101
102 gui::InputHex("Entrance ID", &entrance_id);
103
104 if (Button(ICON_MD_DONE)) {
105 set_done = true;
106 ImGui::CloseCurrentPopup();
107 }
108
109 SameLine();
110 if (Button(ICON_MD_CANCEL)) {
111 ImGui::CloseCurrentPopup();
112 }
113
114 ImGui::EndPopup();
115 }
116 return set_done;
117}
118
120 static bool set_done = false;
121 if (set_done) {
122 set_done = false;
123 return true;
124 }
125
126 if (ImGui::BeginPopupModal(
128 .c_str(),
129 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
130 if (ImGui::IsWindowAppearing()) {
131 // Reset state if needed
132 }
133
134 ImGui::Text(tr("Entrance ID: %d"), entrance.entrance_id_);
135 ImGui::Separator();
136
137 gui::InputHexWord("Map ID", &entrance.map_id_);
138 gui::InputHexByte("Entrance ID", &entrance.entrance_id_,
139 kInputFieldSize + 20);
140 gui::InputHex("X Position", &entrance.x_);
141 gui::InputHex("Y Position", &entrance.y_);
142
143 ImGui::Checkbox(tr("Is Hole"), &entrance.is_hole_);
144
145 ImGui::Separator();
146
147 if (Button(tr("Save"))) {
148 set_done = true;
149 ImGui::CloseCurrentPopup();
150 }
151 ImGui::SameLine();
152 if (Button(tr("Delete"))) {
153 entrance.deleted = true;
154 set_done = true;
155 ImGui::CloseCurrentPopup();
156 }
157 ImGui::SameLine();
158 if (Button(tr("Cancel"))) {
159 ImGui::CloseCurrentPopup();
160 }
161
162 ImGui::EndPopup();
163 }
164 return set_done;
165}
166
168 if (ImGui::BeginPopup("Exit Inserter")) {
169 static int exit_id = 0;
170 static int room_id = 0;
171 static int x_pos = 0;
172 static int y_pos = 0;
173
174 if (ImGui::IsWindowAppearing()) {
175 exit_id = 0;
176 room_id = 0;
177 x_pos = 0;
178 y_pos = 0;
179 }
180
181 ImGui::Text(tr("Insert New Exit"));
182 ImGui::Separator();
183
184 gui::InputHex("Exit ID", &exit_id);
185 gui::InputHex("Room ID", &room_id);
186 gui::InputHex("X Position", &x_pos);
187 gui::InputHex("Y Position", &y_pos);
188
189 if (Button(tr("Create Exit"))) {
190 // This would need to be connected to the overworld editor to actually
191 // create the exit
192 ImGui::CloseCurrentPopup();
193 }
194
195 SameLine();
196 if (Button(tr("Cancel"))) {
197 ImGui::CloseCurrentPopup();
198 }
199
200 ImGui::EndPopup();
201 }
202}
203
205 static bool set_done = false;
206 if (set_done) {
207 set_done = false;
208 return true;
209 }
210 if (ImGui::BeginPopupModal(
211 gui::MakePopupId(gui::EditorNames::kOverworld, "Exit Editor").c_str(),
212 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
213 // Normal door: None = 0, Wooden = 1, Bombable = 2
214 static int doorType = 0;
215 // Fancy door: None = 0, Sanctuary = 1, Palace = 2
216 static int fancyDoorType = 0;
217
218 static int xPos = 0;
219 static int yPos = 0;
220
221 // Special overworld exit properties
222 static int centerY = 0;
223 static int centerX = 0;
224 static int unk1 = 0;
225 static int unk2 = 0;
226 static int linkPosture = 0;
227 static int spriteGFX = 0;
228 static int bgGFX = 0;
229 static int palette = 0;
230 static int sprPal = 0;
231 static int top = 0;
232 static int bottom = 0;
233 static int left = 0;
234 static int right = 0;
235 static int leftEdgeOfMap = 0;
236 static bool special_exit = false;
237 static bool show_properties = false;
238
239 if (ImGui::IsWindowAppearing()) {
240 // Reset state from entity
241 doorType = exit.door_type_1_;
242 fancyDoorType = exit.door_type_2_;
243 xPos = 0; // Unknown mapping
244 yPos = 0; // Unknown mapping
245
246 // Reset other static vars to avoid pollution
247 centerY = 0;
248 centerX = 0;
249 unk1 = 0;
250 unk2 = 0;
251 linkPosture = 0;
252 spriteGFX = 0;
253 bgGFX = 0;
254 palette = 0;
255 sprPal = 0;
256 top = 0;
257 bottom = 0;
258 left = 0;
259 right = 0;
260 leftEdgeOfMap = 0;
261 special_exit = false;
262 show_properties = false;
263 }
264
265 gui::InputHexWord("Room", &exit.room_id_);
266 SameLine();
267 gui::InputHex("Entity ID", &exit.entity_id_, 4);
268 gui::InputHexWord("Map", &exit.map_id_);
269 SameLine();
270 Checkbox(tr("Automatic"), &exit.is_automatic_);
271
272 gui::InputHex("X Positon", &exit.x_);
273 SameLine();
274 gui::InputHex("Y Position", &exit.y_);
275
276 gui::InputHexWord("X Camera", &exit.x_camera_);
277 SameLine();
278 gui::InputHexWord("Y Camera", &exit.y_camera_);
279
280 gui::InputHexWord("X Scroll", &exit.x_scroll_);
281 SameLine();
282 gui::InputHexWord("Y Scroll", &exit.y_scroll_);
283
284 ImGui::Separator();
285
286 Checkbox(tr("Show properties"), &show_properties);
287 if (show_properties) {
288 Text(tr("Deleted? %s"), exit.deleted_ ? "true" : "false");
289 Text(tr("Hole? %s"), exit.is_hole_ ? "true" : "false");
290 Text(tr("Auto-calc scroll/camera? %s"),
291 exit.is_automatic_ ? "true" : "false");
292 Text(tr("Map ID: 0x%02X"), exit.map_id_);
293 Text(tr("Game coords: (%d, %d)"), exit.game_x_, exit.game_y_);
294 }
295
296 gui::TextWithSeparators("Advanced Door/Exit Controls");
297
298 if (ImGui::RadioButton(tr("None"), &doorType, 0))
299 exit.door_type_1_ = doorType;
300 SameLine();
301 if (ImGui::RadioButton(tr("Wooden"), &doorType, 1))
302 exit.door_type_1_ = doorType;
303 SameLine();
304 if (ImGui::RadioButton(tr("Bombable"), &doorType, 2))
305 exit.door_type_1_ = doorType;
306
307 // If door type is not None, input positions
308 if (doorType != 0) {
309 gui::InputHex("Door X pos", &xPos);
310 gui::InputHex("Door Y pos", &yPos);
311 }
312
313 if (ImGui::RadioButton(tr("None##Fancy"), &fancyDoorType, 0))
314 exit.door_type_2_ = fancyDoorType;
315 SameLine();
316 if (ImGui::RadioButton(tr("Sanctuary"), &fancyDoorType, 1))
317 exit.door_type_2_ = fancyDoorType;
318 SameLine();
319 if (ImGui::RadioButton(tr("Palace"), &fancyDoorType, 2))
320 exit.door_type_2_ = fancyDoorType;
321
322 // If fancy door type is not None, input positions
323 if (fancyDoorType != 0) {
324 // Placeholder for fancy door's X position
325 gui::InputHex("Fancy Door X pos", &xPos);
326 // Placeholder for fancy door's Y position
327 gui::InputHex("Fancy Door Y pos", &yPos);
328 }
329
330 Checkbox(tr("Special exit"), &special_exit);
331 if (special_exit) {
332 gui::InputHex("Center X", &centerX);
333
334 gui::InputHex("Center Y", &centerY);
335 gui::InputHex("Unk1", &unk1);
336 gui::InputHex("Unk2", &unk2);
337
338 gui::InputHex("Link's posture", &linkPosture);
339 gui::InputHex("Sprite GFX", &spriteGFX);
340 gui::InputHex("BG GFX", &bgGFX);
341 gui::InputHex("Palette", &palette);
342 gui::InputHex("Spr Pal", &sprPal);
343
344 gui::InputHex("Top", &top);
345 gui::InputHex("Bottom", &bottom);
346 gui::InputHex("Left", &left);
347 gui::InputHex("Right", &right);
348
349 gui::InputHex("Left edge of map", &leftEdgeOfMap);
350 }
351
352 if (Button(ICON_MD_DONE)) {
353 set_done = true; // FIX: Save changes when Done is clicked
354 ImGui::CloseCurrentPopup();
355 }
356
357 SameLine();
358
359 if (Button(ICON_MD_CANCEL)) {
360 // FIX: Discard changes - don't set set_done
361 ImGui::CloseCurrentPopup();
362 }
363
364 SameLine();
365 if (Button(ICON_MD_DELETE)) {
366 exit.deleted_ = true;
367 set_done = true; // FIX: Save deletion when Delete is clicked
368 ImGui::CloseCurrentPopup();
369 }
370
371 ImGui::EndPopup();
372 }
373
374 return set_done;
375}
376
378 // Contents of the Context Menu
379 if (ImGui::BeginPopup("Item Inserter")) {
380 static size_t new_item_id = 0;
381 Text(tr("Add Item"));
382 BeginChild("ScrollRegion", ImVec2(150, 150), true,
383 ImGuiWindowFlags_AlwaysVerticalScrollbar);
384 for (size_t i = 0; i < zelda3::kSecretItemNames.size(); i++) {
385 if (Selectable(zelda3::kSecretItemNames[i].c_str(), i == new_item_id)) {
386 new_item_id = i;
387 }
388 }
389 EndChild();
390
391 if (Button(ICON_MD_DONE)) {
392 // Add the new item to the overworld
393 new_item_id = 0;
394 ImGui::CloseCurrentPopup();
395 }
396 SameLine();
397
398 if (Button(ICON_MD_CANCEL)) {
399 ImGui::CloseCurrentPopup();
400 }
401
402 ImGui::EndPopup();
403 }
404}
405
407 bool set_done = false;
408 if (ImGui::BeginPopupModal(
409 gui::MakePopupId(gui::EditorNames::kOverworld, "Item Editor").c_str(),
410 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
411 BeginChild("ScrollRegion", ImVec2(150, 150), true,
412 ImGuiWindowFlags_AlwaysVerticalScrollbar);
413 ImGui::BeginGroup();
414 for (size_t i = 0; i < zelda3::kSecretItemNames.size(); i++) {
415 if (Selectable(zelda3::kSecretItemNames[i].c_str(), item.id_ == i)) {
416 item.id_ = i;
417 item.entity_id_ = i;
418 item.UpdateMapProperties(item.map_id_, nullptr);
419 }
420 }
421 ImGui::EndGroup();
422 EndChild();
423
424 if (Button(ICON_MD_DONE)) {
425 set_done = true;
426 ImGui::CloseCurrentPopup();
427 }
428 SameLine();
429 if (Button(ICON_MD_CLOSE)) {
430 ImGui::CloseCurrentPopup();
431 }
432 SameLine();
433 if (Button(ICON_MD_DELETE)) {
434 item.deleted = true;
435 set_done = true;
436 ImGui::CloseCurrentPopup();
437 }
438
439 ImGui::EndPopup();
440 }
441 return set_done;
442}
443
444const ImGuiTableSortSpecs* SpriteItem::s_current_sort_specs = nullptr;
445
446void DrawSpriteTable(std::function<void(int)> onSpriteSelect,
447 int& selected_id) {
448 static ImGuiTextFilter filter;
449 static std::vector<SpriteItem> items;
450
451 // Initialize items if empty
452 if (items.empty()) {
453 for (int i = 0; i < 256; ++i) {
454 items.push_back(SpriteItem{i, zelda3::kSpriteDefaultNames[i].data()});
455 }
456 }
457
458 filter.Draw("Filter", 180);
459
460 if (ImGui::BeginTable("##sprites", 2,
461 ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
462 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
464 ImGui::TableSetupColumn("Name", 0, 0.0f, SpriteItemColumnID_Name);
465 ImGui::TableHeadersRow();
466
467 // Handle sorting
468 if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs()) {
469 if (sort_specs->SpecsDirty) {
470 SpriteItem::SortWithSortSpecs(sort_specs, items);
471 sort_specs->SpecsDirty = false;
472 }
473 }
474
475 // Display filtered and sorted items
476 for (const auto& item : items) {
477 if (filter.PassFilter(item.name)) {
478 ImGui::TableNextRow();
479 ImGui::TableSetColumnIndex(0);
480 Text("%d", item.id);
481 ImGui::TableSetColumnIndex(1);
482
483 if (Selectable(item.name, selected_id == item.id,
484 ImGuiSelectableFlags_SpanAllColumns)) {
485 selected_id = item.id;
486 onSpriteSelect(item.id);
487 }
488 }
489 }
490 ImGui::EndTable();
491 }
492}
493
495 if (ImGui::BeginPopup("Sprite Inserter")) {
496 static int new_sprite_id = 0;
497 static int x_pos = 0;
498 static int y_pos = 0;
499
500 if (ImGui::IsWindowAppearing()) {
501 new_sprite_id = 0;
502 x_pos = 0;
503 y_pos = 0;
504 }
505
506 ImGui::Text(tr("Add New Sprite"));
507 ImGui::Separator();
508
509 BeginChild("ScrollRegion", ImVec2(250, 200), true,
510 ImGuiWindowFlags_AlwaysVerticalScrollbar);
511 DrawSpriteTable([](int selected_id) { new_sprite_id = selected_id; },
512 new_sprite_id);
513 EndChild();
514
515 ImGui::Separator();
516 ImGui::Text(tr("Position:"));
517 gui::InputHex("X Position", &x_pos);
518 gui::InputHex("Y Position", &y_pos);
519
520 if (Button(tr("Add Sprite"))) {
521 // This would need to be connected to the overworld editor to actually
522 // create the sprite
523 new_sprite_id = 0;
524 x_pos = 0;
525 y_pos = 0;
526 ImGui::CloseCurrentPopup();
527 }
528 SameLine();
529
530 if (Button(tr("Cancel"))) {
531 ImGui::CloseCurrentPopup();
532 }
533
534 ImGui::EndPopup();
535 }
536}
537
539 static bool set_done = false;
540 if (set_done) {
541 set_done = false;
542 return true;
543 }
544 if (ImGui::BeginPopupModal(
546 .c_str(),
547 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
548 static int selected_id = 0;
549 if (ImGui::IsWindowAppearing()) {
550 selected_id = sprite.id();
551 }
552
553 BeginChild("ScrollRegion", ImVec2(350, 350), true,
554 ImGuiWindowFlags_AlwaysVerticalScrollbar);
555 ImGui::BeginGroup();
556 Text("%s", sprite.name().c_str());
557
559 [&sprite](int id) {
560 sprite.set_id(id);
561 sprite.UpdateMapProperties(sprite.map_id(), nullptr);
562 },
563 selected_id);
564 ImGui::EndGroup();
565 EndChild();
566
567 if (Button(ICON_MD_DONE)) {
568 set_done = true; // FIX: Save changes when Done is clicked
569 ImGui::CloseCurrentPopup();
570 }
571 SameLine();
572 if (Button(ICON_MD_CLOSE)) {
573 // FIX: Discard changes - don't set set_done
574 ImGui::CloseCurrentPopup();
575 }
576 SameLine();
577 if (Button(ICON_MD_DELETE)) {
578 sprite.set_deleted(true);
579 set_done = true; // FIX: Save deletion when Delete is clicked
580 ImGui::CloseCurrentPopup();
581 }
582
583 ImGui::EndPopup();
584 }
585 return set_done;
586}
587
589 zelda3::DiggableTiles* diggable_tiles,
590 const std::vector<gfx::Tile16>& tiles16,
591 const std::array<uint8_t, 0x200>& all_tiles_types) {
592 static bool set_done = false;
593 if (set_done) {
594 set_done = false;
595 return true;
596 }
597
598 if (ImGui::BeginPopupModal(gui::MakePopupId(gui::EditorNames::kOverworld,
599 "Diggable Tiles Editor")
600 .c_str(),
601 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
602 static ImGuiTextFilter filter;
603 static int patch_mode = 0; // 0=Vanilla, 1=ZS Compatible, 2=Custom
604 static zelda3::DiggableTilesPatchConfig patch_config;
605 static std::string export_message;
606
607 // Stats header
608 int diggable_count = diggable_tiles->GetDiggableCount();
609 Text(tr("Diggable Tiles: %d / 512"), diggable_count);
610 ImGui::Separator();
611
612 // Filter
613 filter.Draw("Filter by Tile ID", 200);
614 SameLine();
615 if (Button(tr("Clear Filter"))) {
616 filter.Clear();
617 }
618
619 // Scrollable tile list
620 BeginChild("TileList", ImVec2(400, 300), true,
621 ImGuiWindowFlags_AlwaysVerticalScrollbar);
622
623 // Display tiles in a grid-like format
624 int cols = 8;
625 int col = 0;
626 for (uint16_t tile_id = 0;
627 tile_id < zelda3::kMaxDiggableTileId && tile_id < tiles16.size();
628 ++tile_id) {
629 char id_str[16];
630 snprintf(id_str, sizeof(id_str), "$%03X", tile_id);
631
632 if (!filter.PassFilter(id_str)) {
633 continue;
634 }
635
636 bool is_diggable = diggable_tiles->IsDiggable(tile_id);
637 bool would_be_diggable = zelda3::DiggableTiles::IsTile16Diggable(
638 tiles16[tile_id], all_tiles_types);
639
640 // Color coding: green if auto-detected, yellow if manually set
641 std::optional<gui::StyleColorGuard> dig_color;
642 if (is_diggable) {
643 if (would_be_diggable) {
644 dig_color.emplace(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
645 } else {
646 dig_color.emplace(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.2f, 1.0f));
647 }
648 }
649
650 if (Checkbox(id_str, &is_diggable)) {
651 diggable_tiles->SetDiggable(tile_id, is_diggable);
652 }
653
654 dig_color.reset();
655
656 if (ImGui::IsItemHovered()) {
657 ImGui::SetTooltip(tr("Tile $%03X - %s"), tile_id,
658 would_be_diggable ? "Auto-detected as diggable"
659 : "Manually configured");
660 }
661
662 col++;
663 if (col < cols) {
664 SameLine();
665 } else {
666 col = 0;
667 }
668 }
669
670 EndChild();
671
672 ImGui::Separator();
673
674 // Action buttons
675 if (Button(ICON_MD_AUTO_FIX_HIGH " Auto-Detect")) {
676 diggable_tiles->Clear();
677 for (uint16_t tile_id = 0;
678 tile_id < zelda3::kMaxDiggableTileId && tile_id < tiles16.size();
679 ++tile_id) {
680 if (zelda3::DiggableTiles::IsTile16Diggable(tiles16[tile_id],
681 all_tiles_types)) {
682 diggable_tiles->SetDiggable(tile_id, true);
683 }
684 }
685 }
686 if (ImGui::IsItemHovered()) {
687 ImGui::SetTooltip(
688 tr("Set diggable status based on tile types.\n"
689 "A tile is diggable if all 4 component tiles\n"
690 "have type 0x48 or 0x4A (diggable ground)."));
691 }
692
693 SameLine();
694 if (Button(ICON_MD_RESTART_ALT " Vanilla Defaults")) {
695 diggable_tiles->SetVanillaDefaults();
696 }
697 if (ImGui::IsItemHovered()) {
698 ImGui::SetTooltip(
699 tr("Reset to vanilla diggable tiles:\n$034, $035, $071, "
700 "$0DA, $0E1, $0E2, $0F8, $10D, $10E, $10F"));
701 }
702
703 SameLine();
704 if (Button(ICON_MD_CLEAR " Clear All")) {
705 diggable_tiles->Clear();
706 }
707
708 ImGui::Separator();
709
710 // Patch export section
711 if (ImGui::CollapsingHeader(tr("ASM Patch Export"))) {
712 ImGui::Indent();
713
714 Text(tr("Patch Mode:"));
715 ImGui::RadioButton(tr("Vanilla"), &patch_mode, 0);
716 SameLine();
717 ImGui::RadioButton(tr("ZS Compatible"), &patch_mode, 1);
718 SameLine();
719 ImGui::RadioButton(tr("Custom"), &patch_mode, 2);
720
721 patch_config.use_zs_compatible_mode = (patch_mode == 1);
722
723 if (patch_mode == 2) {
724 // Custom address inputs
725 static int hook_addr = patch_config.hook_address;
726 static int table_addr = patch_config.table_address;
727 static int freespace_addr = patch_config.freespace_address;
728
729 gui::InputHex("Hook Address", &hook_addr);
730 gui::InputHex("Table Address", &table_addr);
731 gui::InputHex("Freespace", &freespace_addr);
732
733 patch_config.hook_address = hook_addr;
734 patch_config.table_address = table_addr;
735 patch_config.freespace_address = freespace_addr;
736 }
737
738 if (Button(ICON_MD_FILE_DOWNLOAD " Export .asm Patch")) {
739 std::string patch_content = zelda3::DiggableTilesPatch::GeneratePatch(
740 *diggable_tiles, patch_config);
741 const std::string out_path = "diggable_tiles_patch.asm";
742 std::ofstream out(out_path, std::ios::trunc);
743 if (out.good()) {
744 out << patch_content;
745 out.close();
746 export_message = "Exported patch to " + out_path;
747 } else {
748 export_message = "Failed to write " + out_path;
749 }
750 }
751 if (!export_message.empty()) {
752 ImGui::TextWrapped("%s", export_message.c_str());
753 }
754
755 ImGui::Unindent();
756 }
757
758 ImGui::Separator();
759
760 // Save/Cancel buttons
761 if (Button(ICON_MD_DONE " Save")) {
762 set_done = true;
763 ImGui::CloseCurrentPopup();
764 }
765 SameLine();
766 if (Button(ICON_MD_CANCEL " Cancel")) {
767 ImGui::CloseCurrentPopup();
768 }
769
770 ImGui::EndPopup();
771 }
772
773 return set_done;
774}
775
776} // namespace editor
777} // namespace yaze
static std::string GeneratePatch(const DiggableTiles &diggable_tiles, const DiggableTilesPatchConfig &config={})
Generate ASM patch code for the diggable tiles table.
Manages diggable tile state as a 512-bit bitfield.
int GetDiggableCount() const
Get the count of tiles marked as diggable.
void SetVanillaDefaults()
Reset to vanilla diggable tiles.
void SetDiggable(uint16_t tile_id, bool diggable)
Set or clear the diggable bit for a Map16 tile ID.
static bool IsTile16Diggable(const gfx::Tile16 &tile16, const std::array< uint8_t, 0x200 > &all_tiles_types)
Check if a Tile16 should be diggable based on its component tiles.
bool IsDiggable(uint16_t tile_id) const
Check if a Map16 tile ID is marked as diggable.
void Clear()
Clear all diggable bits.
Base class for all overworld and dungeon entities.
Definition common.h:31
enum yaze::zelda3::GameEntity::EntityType entity_type_
auto set_x(int x)
Definition common.h:62
auto set_y(int y)
Definition common.h:63
Represents an overworld exit that transitions from dungeon to overworld.
void UpdateMapProperties(uint16_t room_map_id, const void *context=nullptr) override
Update entity properties based on map position.
A class for managing sprites in the overworld and underworld.
Definition sprite.h:39
auto id() const
Definition sprite.h:107
auto name() const
Definition sprite.h:131
void UpdateMapProperties(uint16_t map_id, const void *context=nullptr) override
Update entity properties based on map position.
Definition sprite.cc:328
auto map_id() const
Definition sprite.h:113
auto set_id(uint8_t id)
Definition sprite.h:108
auto set_deleted(bool deleted)
Definition sprite.h:133
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_FILE_DOWNLOAD
Definition icons.h:744
#define ICON_MD_AUTO_FIX_HIGH
Definition icons.h:218
#define ICON_MD_CLEAR
Definition icons.h:416
#define ICON_MD_DONE
Definition icons.h:607
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_RESTART_ALT
Definition icons.h:1602
void DrawExitInserterPopup()
Definition entity.cc:167
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite)
Definition entity.cc:538
void DrawItemInsertPopup()
Definition entity.cc:377
bool DrawEntranceInserterPopup()
Definition entity.cc:91
void DrawSpriteInserterPopup()
Definition entity.cc:494
void DrawSpriteTable(std::function< void(int)> onSpriteSelect, int &selected_id)
Definition entity.cc:446
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance)
Definition entity.cc:119
bool DrawItemEditorPopup(zelda3::OverworldItem &item)
Definition entity.cc:406
bool DrawDiggableTilesEditorPopup(zelda3::DiggableTiles *diggable_tiles, const std::vector< gfx::Tile16 > &tiles16, const std::array< uint8_t, 0x200 > &all_tiles_types)
Draw popup dialog for editing diggable tiles configuration.
Definition entity.cc:588
void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, ImVec2 scrolling, bool free_movement, float scale)
Move entity to grid-aligned position based on mouse.
Definition entity.cc:66
constexpr float kInputFieldSize
Definition entity.cc:28
@ SpriteItemColumnID_ID
Definition entity.h:69
@ SpriteItemColumnID_Name
Definition entity.h:70
bool DrawExitEditorPopup(zelda3::OverworldExit &exit)
Definition entity.cc:204
bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity, ImVec2 canvas_p0, ImVec2 scrolling, float scale)
Check if mouse is hovering over an entity.
Definition entity.cc:30
constexpr const char * kOverworld
Definition popup_id.h:53
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:489
bool InputHex(const char *label, uint64_t *data)
Definition input.cc:470
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
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:510
void TextWithSeparators(const absl::string_view &text)
Definition style.cc:1344
const std::vector< std::string > kSecretItemNames
const std::string kSpriteDefaultNames[256]
Definition sprite.cc:16
constexpr int kMaxDiggableTileId
static const ImGuiTableSortSpecs * s_current_sort_specs
Definition entity.h:77
static void SortWithSortSpecs(ImGuiTableSortSpecs *sort_specs, std::vector< SpriteItem > &items)
Definition entity.h:79
Configuration for diggable tiles ASM patch generation.