19using ImGui::BeginChild;
24using ImGui::Selectable;
30 ImVec2 canvas_p0, ImVec2 scrolling,
33 const ImGuiIO& io = ImGui::GetIO();
34 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
35 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
38 float scaled_x = entity.
x_ * scale;
39 float scaled_y = entity.
y_ * scale;
40 float scaled_size = 16.0f * scale;
43 return mouse_pos.x >= scaled_x && mouse_pos.x <= scaled_x + scaled_size &&
44 mouse_pos.y >= scaled_y && mouse_pos.y <= scaled_y + scaled_size;
50 const ImGuiIO& io = ImGui::GetIO();
53 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
56 float scaled_x = entity.
x_ * rt.
scale;
57 float scaled_y = entity.
y_ * rt.
scale;
58 float scaled_size = 16.0f * rt.
scale;
61 return mouse_pos.x >= scaled_x && mouse_pos.x <= scaled_x + scaled_size &&
62 mouse_pos.y >= scaled_y && mouse_pos.y <= scaled_y + scaled_size;
66 ImVec2 scrolling,
bool free_movement,
float scale) {
68 const ImGuiIO& io = ImGui::GetIO();
69 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
70 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
73 float world_x = mouse_pos.x / scale;
74 float world_y = mouse_pos.y / scale;
77 int grid_size = free_movement ? 8 : 16;
78 int new_x =
static_cast<int>(world_x) / grid_size * grid_size;
79 int new_y =
static_cast<int>(world_y) / grid_size * grid_size;
87 bool set_done =
false;
91 if (ImGui::BeginPopup(
"Entrance Inserter")) {
92 static int entrance_id = 0;
93 if (ImGui::IsWindowAppearing()) {
101 ImGui::CloseCurrentPopup();
106 ImGui::CloseCurrentPopup();
115 static bool set_done =
false;
121 if (ImGui::BeginPopupModal(
124 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
125 if (ImGui::IsWindowAppearing()) {
138 ImGui::Checkbox(
"Is Hole", &entrance.
is_hole_);
142 if (Button(
"Save")) {
144 ImGui::CloseCurrentPopup();
147 if (Button(
"Delete")) {
150 ImGui::CloseCurrentPopup();
153 if (Button(
"Cancel")) {
154 ImGui::CloseCurrentPopup();
163 if (ImGui::BeginPopup(
"Exit Inserter")) {
164 static int exit_id = 0;
165 static int room_id = 0;
166 static int x_pos = 0;
167 static int y_pos = 0;
169 if (ImGui::IsWindowAppearing()) {
176 ImGui::Text(
"Insert New Exit");
184 if (Button(
"Create Exit")) {
187 ImGui::CloseCurrentPopup();
191 if (Button(
"Cancel")) {
192 ImGui::CloseCurrentPopup();
200 static bool set_done =
false;
205 if (ImGui::BeginPopupModal(
207 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
209 static int doorType = 0;
211 static int fancyDoorType = 0;
217 static int centerY = 0;
218 static int centerX = 0;
221 static int linkPosture = 0;
222 static int spriteGFX = 0;
223 static int bgGFX = 0;
224 static int palette = 0;
225 static int sprPal = 0;
227 static int bottom = 0;
229 static int right = 0;
230 static int leftEdgeOfMap = 0;
231 static bool special_exit =
false;
232 static bool show_properties =
false;
234 if (ImGui::IsWindowAppearing()) {
256 special_exit =
false;
257 show_properties =
false;
281 Checkbox(
"Show properties", &show_properties);
282 if (show_properties) {
283 Text(
"Deleted? %s", exit.
deleted_ ?
"true" :
"false");
284 Text(
"Hole? %s", exit.
is_hole_ ?
"true" :
"false");
285 Text(
"Auto-calc scroll/camera? %s",
287 Text(
"Map ID: 0x%02X", exit.
map_id_);
293 if (ImGui::RadioButton(
"None", &doorType, 0))
296 if (ImGui::RadioButton(
"Wooden", &doorType, 1))
299 if (ImGui::RadioButton(
"Bombable", &doorType, 2))
308 if (ImGui::RadioButton(
"None##Fancy", &fancyDoorType, 0))
311 if (ImGui::RadioButton(
"Sanctuary", &fancyDoorType, 1))
314 if (ImGui::RadioButton(
"Palace", &fancyDoorType, 2))
318 if (fancyDoorType != 0) {
325 Checkbox(
"Special exit", &special_exit);
349 ImGui::CloseCurrentPopup();
356 ImGui::CloseCurrentPopup();
363 ImGui::CloseCurrentPopup();
374 if (ImGui::BeginPopup(
"Item Inserter")) {
375 static size_t new_item_id = 0;
377 BeginChild(
"ScrollRegion", ImVec2(150, 150),
true,
378 ImGuiWindowFlags_AlwaysVerticalScrollbar);
389 ImGui::CloseCurrentPopup();
394 ImGui::CloseCurrentPopup();
402 bool set_done =
false;
403 if (ImGui::BeginPopupModal(
405 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
406 BeginChild(
"ScrollRegion", ImVec2(150, 150),
true,
407 ImGuiWindowFlags_AlwaysVerticalScrollbar);
421 ImGui::CloseCurrentPopup();
425 ImGui::CloseCurrentPopup();
431 ImGui::CloseCurrentPopup();
443 static ImGuiTextFilter filter;
444 static std::vector<SpriteItem> items;
448 for (
int i = 0; i < 256; ++i) {
453 filter.Draw(
"Filter", 180);
455 if (ImGui::BeginTable(
"##sprites", 2,
456 ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
457 ImGui::TableSetupColumn(
"ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
460 ImGui::TableHeadersRow();
463 if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs()) {
464 if (sort_specs->SpecsDirty) {
466 sort_specs->SpecsDirty =
false;
471 for (
const auto& item : items) {
472 if (filter.PassFilter(item.name)) {
473 ImGui::TableNextRow();
474 ImGui::TableSetColumnIndex(0);
476 ImGui::TableSetColumnIndex(1);
478 if (Selectable(item.name, selected_id == item.id,
479 ImGuiSelectableFlags_SpanAllColumns)) {
480 selected_id = item.id;
481 onSpriteSelect(item.id);
490 if (ImGui::BeginPopup(
"Sprite Inserter")) {
491 static int new_sprite_id = 0;
492 static int x_pos = 0;
493 static int y_pos = 0;
495 if (ImGui::IsWindowAppearing()) {
501 ImGui::Text(
"Add New Sprite");
504 BeginChild(
"ScrollRegion", ImVec2(250, 200),
true,
505 ImGuiWindowFlags_AlwaysVerticalScrollbar);
511 ImGui::Text(
"Position:");
515 if (Button(
"Add Sprite")) {
521 ImGui::CloseCurrentPopup();
525 if (Button(
"Cancel")) {
526 ImGui::CloseCurrentPopup();
534 static bool set_done =
false;
539 if (ImGui::BeginPopupModal(
542 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
543 static int selected_id = 0;
544 if (ImGui::IsWindowAppearing()) {
545 selected_id = sprite.
id();
548 BeginChild(
"ScrollRegion", ImVec2(350, 350),
true,
549 ImGuiWindowFlags_AlwaysVerticalScrollbar);
551 Text(
"%s", sprite.
name().c_str());
564 ImGui::CloseCurrentPopup();
569 ImGui::CloseCurrentPopup();
575 ImGui::CloseCurrentPopup();
585 const std::vector<gfx::Tile16>& tiles16,
586 const std::array<uint8_t, 0x200>& all_tiles_types) {
587 static bool set_done =
false;
594 "Diggable Tiles Editor")
596 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
597 static ImGuiTextFilter filter;
598 static int patch_mode = 0;
600 static std::string export_message;
604 Text(
"Diggable Tiles: %d / 512", diggable_count);
608 filter.Draw(
"Filter by Tile ID", 200);
610 if (Button(
"Clear Filter")) {
615 BeginChild(
"TileList", ImVec2(400, 300),
true,
616 ImGuiWindowFlags_AlwaysVerticalScrollbar);
621 for (uint16_t tile_id = 0;
625 snprintf(id_str,
sizeof(id_str),
"$%03X", tile_id);
627 if (!filter.PassFilter(id_str)) {
631 bool is_diggable = diggable_tiles->
IsDiggable(tile_id);
633 tiles16[tile_id], all_tiles_types);
636 std::optional<gui::StyleColorGuard> dig_color;
638 if (would_be_diggable) {
639 dig_color.emplace(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
641 dig_color.emplace(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.2f, 1.0f));
645 if (Checkbox(id_str, &is_diggable)) {
651 if (ImGui::IsItemHovered()) {
652 ImGui::SetTooltip(
"Tile $%03X - %s", tile_id,
653 would_be_diggable ?
"Auto-detected as diggable"
654 :
"Manually configured");
671 diggable_tiles->
Clear();
672 for (uint16_t tile_id = 0;
681 if (ImGui::IsItemHovered()) {
683 "Set diggable status based on tile types.\n"
684 "A tile is diggable if all 4 component tiles\n"
685 "have type 0x48 or 0x4A (diggable ground).");
692 if (ImGui::IsItemHovered()) {
694 "Reset to vanilla diggable tiles:\n$034, $035, $071, "
695 "$0DA, $0E1, $0E2, $0F8, $10D, $10E, $10F");
700 diggable_tiles->
Clear();
706 if (ImGui::CollapsingHeader(
"ASM Patch Export")) {
710 ImGui::RadioButton(
"Vanilla", &patch_mode, 0);
712 ImGui::RadioButton(
"ZS Compatible", &patch_mode, 1);
714 ImGui::RadioButton(
"Custom", &patch_mode, 2);
718 if (patch_mode == 2) {
735 *diggable_tiles, patch_config);
736 const std::string out_path =
"diggable_tiles_patch.asm";
737 std::ofstream out(out_path, std::ios::trunc);
739 out << patch_content;
741 export_message =
"Exported patch to " + out_path;
743 export_message =
"Failed to write " + out_path;
746 if (!export_message.empty()) {
747 ImGui::TextWrapped(
"%s", export_message.c_str());
758 ImGui::CloseCurrentPopup();
762 ImGui::CloseCurrentPopup();
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.
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.
void UpdateMapProperties(uint16_t map_id, const void *context=nullptr) override
Update entity properties based on map position.
auto set_deleted(bool deleted)
#define ICON_MD_FILE_DOWNLOAD
#define ICON_MD_AUTO_FIX_HIGH
#define ICON_MD_RESTART_ALT
void DrawExitInserterPopup()
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite)
void DrawItemInsertPopup()
bool DrawEntranceInserterPopup()
void DrawSpriteInserterPopup()
void DrawSpriteTable(std::function< void(int)> onSpriteSelect, int &selected_id)
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance)
bool DrawItemEditorPopup(zelda3::OverworldItem &item)
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.
void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, ImVec2 scrolling, bool free_movement, float scale)
Move entity to grid-aligned position based on mouse.
constexpr float kInputFieldSize
@ SpriteItemColumnID_Name
bool DrawExitEditorPopup(zelda3::OverworldExit &exit)
bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity, ImVec2 canvas_p0, ImVec2 scrolling, float scale)
Check if mouse is hovering over an entity.
constexpr const char * kOverworld
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
bool InputHex(const char *label, uint64_t *data)
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.
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
void TextWithSeparators(const absl::string_view &text)
const std::vector< std::string > kSecretItemNames
const std::string kSpriteDefaultNames[256]
constexpr int kMaxDiggableTileId
static const ImGuiTableSortSpecs * s_current_sort_specs
static void SortWithSortSpecs(ImGuiTableSortSpecs *sort_specs, std::vector< SpriteItem > &items)
Configuration for diggable tiles ASM patch generation.
uint32_t freespace_address
bool use_zs_compatible_mode