20using ImGui::BeginChild;
25using ImGui::Selectable;
31 ImVec2 canvas_p0, ImVec2 scrolling,
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);
39 float scaled_x = entity.
x_ * scale;
40 float scaled_y = entity.
y_ * scale;
41 float scaled_size = 16.0f * scale;
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;
51 const ImGuiIO& io = ImGui::GetIO();
54 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
57 float scaled_x = entity.
x_ * rt.
scale;
58 float scaled_y = entity.
y_ * rt.
scale;
59 float scaled_size = 16.0f * rt.
scale;
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;
67 ImVec2 scrolling,
bool free_movement,
float scale) {
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);
74 float world_x = mouse_pos.x / scale;
75 float world_y = mouse_pos.y / scale;
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;
92 bool set_done =
false;
96 if (ImGui::BeginPopup(
"Entrance Inserter")) {
97 static int entrance_id = 0;
98 if (ImGui::IsWindowAppearing()) {
106 ImGui::CloseCurrentPopup();
111 ImGui::CloseCurrentPopup();
120 static bool set_done =
false;
126 if (ImGui::BeginPopupModal(
129 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
130 if (ImGui::IsWindowAppearing()) {
134 ImGui::Text(tr(
"Entrance ID: %d"), entrance.
entrance_id_);
143 ImGui::Checkbox(tr(
"Is Hole"), &entrance.
is_hole_);
147 if (Button(tr(
"Save"))) {
149 ImGui::CloseCurrentPopup();
152 if (Button(tr(
"Delete"))) {
155 ImGui::CloseCurrentPopup();
158 if (Button(tr(
"Cancel"))) {
159 ImGui::CloseCurrentPopup();
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;
174 if (ImGui::IsWindowAppearing()) {
181 ImGui::Text(tr(
"Insert New Exit"));
189 if (Button(tr(
"Create Exit"))) {
192 ImGui::CloseCurrentPopup();
196 if (Button(tr(
"Cancel"))) {
197 ImGui::CloseCurrentPopup();
205 static bool set_done =
false;
210 if (ImGui::BeginPopupModal(
212 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
214 static int doorType = 0;
216 static int fancyDoorType = 0;
222 static int centerY = 0;
223 static int centerX = 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;
232 static int bottom = 0;
234 static int right = 0;
235 static int leftEdgeOfMap = 0;
236 static bool special_exit =
false;
237 static bool show_properties =
false;
239 if (ImGui::IsWindowAppearing()) {
261 special_exit =
false;
262 show_properties =
false;
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"),
292 Text(tr(
"Map ID: 0x%02X"), exit.
map_id_);
298 if (ImGui::RadioButton(tr(
"None"), &doorType, 0))
301 if (ImGui::RadioButton(tr(
"Wooden"), &doorType, 1))
304 if (ImGui::RadioButton(tr(
"Bombable"), &doorType, 2))
313 if (ImGui::RadioButton(tr(
"None##Fancy"), &fancyDoorType, 0))
316 if (ImGui::RadioButton(tr(
"Sanctuary"), &fancyDoorType, 1))
319 if (ImGui::RadioButton(tr(
"Palace"), &fancyDoorType, 2))
323 if (fancyDoorType != 0) {
330 Checkbox(tr(
"Special exit"), &special_exit);
354 ImGui::CloseCurrentPopup();
361 ImGui::CloseCurrentPopup();
368 ImGui::CloseCurrentPopup();
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);
394 ImGui::CloseCurrentPopup();
399 ImGui::CloseCurrentPopup();
407 bool set_done =
false;
408 if (ImGui::BeginPopupModal(
410 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
411 BeginChild(
"ScrollRegion", ImVec2(150, 150),
true,
412 ImGuiWindowFlags_AlwaysVerticalScrollbar);
426 ImGui::CloseCurrentPopup();
430 ImGui::CloseCurrentPopup();
436 ImGui::CloseCurrentPopup();
448 static ImGuiTextFilter filter;
449 static std::vector<SpriteItem> items;
453 for (
int i = 0; i < 256; ++i) {
458 filter.Draw(
"Filter", 180);
460 if (ImGui::BeginTable(
"##sprites", 2,
461 ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
462 ImGui::TableSetupColumn(
"ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
465 ImGui::TableHeadersRow();
468 if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs()) {
469 if (sort_specs->SpecsDirty) {
471 sort_specs->SpecsDirty =
false;
476 for (
const auto& item : items) {
477 if (filter.PassFilter(item.name)) {
478 ImGui::TableNextRow();
479 ImGui::TableSetColumnIndex(0);
481 ImGui::TableSetColumnIndex(1);
483 if (Selectable(item.name, selected_id == item.id,
484 ImGuiSelectableFlags_SpanAllColumns)) {
485 selected_id = item.id;
486 onSpriteSelect(item.id);
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;
500 if (ImGui::IsWindowAppearing()) {
506 ImGui::Text(tr(
"Add New Sprite"));
509 BeginChild(
"ScrollRegion", ImVec2(250, 200),
true,
510 ImGuiWindowFlags_AlwaysVerticalScrollbar);
516 ImGui::Text(tr(
"Position:"));
520 if (Button(tr(
"Add Sprite"))) {
526 ImGui::CloseCurrentPopup();
530 if (Button(tr(
"Cancel"))) {
531 ImGui::CloseCurrentPopup();
539 static bool set_done =
false;
544 if (ImGui::BeginPopupModal(
547 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
548 static int selected_id = 0;
549 if (ImGui::IsWindowAppearing()) {
550 selected_id = sprite.
id();
553 BeginChild(
"ScrollRegion", ImVec2(350, 350),
true,
554 ImGuiWindowFlags_AlwaysVerticalScrollbar);
556 Text(
"%s", sprite.
name().c_str());
569 ImGui::CloseCurrentPopup();
574 ImGui::CloseCurrentPopup();
580 ImGui::CloseCurrentPopup();
590 const std::vector<gfx::Tile16>& tiles16,
591 const std::array<uint8_t, 0x200>& all_tiles_types) {
592 static bool set_done =
false;
599 "Diggable Tiles Editor")
601 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
602 static ImGuiTextFilter filter;
603 static int patch_mode = 0;
605 static std::string export_message;
609 Text(tr(
"Diggable Tiles: %d / 512"), diggable_count);
613 filter.Draw(
"Filter by Tile ID", 200);
615 if (Button(tr(
"Clear Filter"))) {
620 BeginChild(
"TileList", ImVec2(400, 300),
true,
621 ImGuiWindowFlags_AlwaysVerticalScrollbar);
626 for (uint16_t tile_id = 0;
630 snprintf(id_str,
sizeof(id_str),
"$%03X", tile_id);
632 if (!filter.PassFilter(id_str)) {
636 bool is_diggable = diggable_tiles->
IsDiggable(tile_id);
638 tiles16[tile_id], all_tiles_types);
641 std::optional<gui::StyleColorGuard> dig_color;
643 if (would_be_diggable) {
644 dig_color.emplace(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
646 dig_color.emplace(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.2f, 1.0f));
650 if (Checkbox(id_str, &is_diggable)) {
656 if (ImGui::IsItemHovered()) {
657 ImGui::SetTooltip(tr(
"Tile $%03X - %s"), tile_id,
658 would_be_diggable ?
"Auto-detected as diggable"
659 :
"Manually configured");
676 diggable_tiles->
Clear();
677 for (uint16_t tile_id = 0;
686 if (ImGui::IsItemHovered()) {
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)."));
697 if (ImGui::IsItemHovered()) {
699 tr(
"Reset to vanilla diggable tiles:\n$034, $035, $071, "
700 "$0DA, $0E1, $0E2, $0F8, $10D, $10E, $10F"));
705 diggable_tiles->
Clear();
711 if (ImGui::CollapsingHeader(tr(
"ASM Patch Export"))) {
714 Text(tr(
"Patch Mode:"));
715 ImGui::RadioButton(tr(
"Vanilla"), &patch_mode, 0);
717 ImGui::RadioButton(tr(
"ZS Compatible"), &patch_mode, 1);
719 ImGui::RadioButton(tr(
"Custom"), &patch_mode, 2);
723 if (patch_mode == 2) {
740 *diggable_tiles, patch_config);
741 const std::string out_path =
"diggable_tiles_patch.asm";
742 std::ofstream out(out_path, std::ios::trunc);
744 out << patch_content;
746 export_message =
"Exported patch to " + out_path;
748 export_message =
"Failed to write " + out_path;
751 if (!export_message.empty()) {
752 ImGui::TextWrapped(
"%s", export_message.c_str());
763 ImGui::CloseCurrentPopup();
767 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.
enum yaze::zelda3::GameEntity::EntityType entity_type_
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