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 bool set_done =
false;
122 if (ImGui::BeginPopupModal(
125 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
126 if (ImGui::IsWindowAppearing()) {
130 ImGui::Text(tr(
"Entrance ID: %d"), entrance.
entrance_id_);
139 ImGui::Text(
"Kind: %s", entrance.
is_hole_ ?
"Hole" :
"Entrance");
143 if (Button(tr(
"Save"))) {
145 ImGui::CloseCurrentPopup();
148 if (Button(tr(
"Delete"))) {
151 ImGui::CloseCurrentPopup();
154 if (Button(tr(
"Cancel"))) {
155 ImGui::CloseCurrentPopup();
164 if (ImGui::BeginPopup(
"Exit Inserter")) {
165 static int exit_id = 0;
166 static int room_id = 0;
167 static int x_pos = 0;
168 static int y_pos = 0;
170 if (ImGui::IsWindowAppearing()) {
177 ImGui::Text(tr(
"Insert New Exit"));
185 if (Button(tr(
"Create Exit"))) {
188 ImGui::CloseCurrentPopup();
192 if (Button(tr(
"Cancel"))) {
193 ImGui::CloseCurrentPopup();
201 bool set_done =
false;
202 if (ImGui::BeginPopupModal(
204 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
206 static int doorType = 0;
208 static int fancyDoorType = 0;
214 static int centerY = 0;
215 static int centerX = 0;
218 static int linkPosture = 0;
219 static int spriteGFX = 0;
220 static int bgGFX = 0;
221 static int palette = 0;
222 static int sprPal = 0;
224 static int bottom = 0;
226 static int right = 0;
227 static int leftEdgeOfMap = 0;
228 static bool special_exit =
false;
229 static bool show_properties =
false;
231 if (ImGui::IsWindowAppearing()) {
253 special_exit =
false;
254 show_properties =
false;
276 ImGui::EndDisabled();
280 Checkbox(tr(
"Show properties"), &show_properties);
281 if (show_properties) {
282 Text(tr(
"Deleted? %s"), exit.
deleted_ ?
"true" :
"false");
283 Text(tr(
"Hole? %s"), exit.
is_hole_ ?
"true" :
"false");
284 Text(tr(
"Auto-calc scroll/camera? %s"),
286 Text(tr(
"Map ID: 0x%02X"), exit.
map_id_);
292 if (ImGui::RadioButton(tr(
"None"), &doorType, 0))
295 if (ImGui::RadioButton(tr(
"Wooden"), &doorType, 1))
298 if (ImGui::RadioButton(tr(
"Bombable"), &doorType, 2))
307 if (ImGui::RadioButton(tr(
"None##Fancy"), &fancyDoorType, 0))
310 if (ImGui::RadioButton(tr(
"Sanctuary"), &fancyDoorType, 1))
313 if (ImGui::RadioButton(tr(
"Palace"), &fancyDoorType, 2))
317 if (fancyDoorType != 0) {
324 Checkbox(tr(
"Special exit"), &special_exit);
348 ImGui::CloseCurrentPopup();
355 ImGui::CloseCurrentPopup();
362 ImGui::CloseCurrentPopup();
373 if (ImGui::BeginPopup(
"Item Inserter")) {
374 static size_t new_item_id = 0;
375 Text(tr(
"Add Item"));
376 BeginChild(
"ScrollRegion", ImVec2(150, 150),
true,
377 ImGuiWindowFlags_AlwaysVerticalScrollbar);
388 ImGui::CloseCurrentPopup();
393 ImGui::CloseCurrentPopup();
401 bool set_done =
false;
402 if (ImGui::BeginPopupModal(
404 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
405 BeginChild(
"ScrollRegion", ImVec2(150, 150),
true,
406 ImGuiWindowFlags_AlwaysVerticalScrollbar);
420 ImGui::CloseCurrentPopup();
424 ImGui::CloseCurrentPopup();
430 ImGui::CloseCurrentPopup();
442 static ImGuiTextFilter filter;
443 static std::vector<SpriteItem> items;
447 for (
int i = 0; i < 256; ++i) {
452 filter.Draw(
"Filter", 180);
454 if (ImGui::BeginTable(
"##sprites", 2,
455 ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
456 ImGui::TableSetupColumn(
"ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
459 ImGui::TableHeadersRow();
462 if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs()) {
463 if (sort_specs->SpecsDirty) {
465 sort_specs->SpecsDirty =
false;
470 for (
const auto& item : items) {
471 if (filter.PassFilter(item.name)) {
472 ImGui::TableNextRow();
473 ImGui::TableSetColumnIndex(0);
475 ImGui::TableSetColumnIndex(1);
477 if (Selectable(item.name, selected_id == item.id,
478 ImGuiSelectableFlags_SpanAllColumns)) {
479 selected_id = item.id;
480 onSpriteSelect(item.id);
489 if (ImGui::BeginPopup(
"Sprite Inserter")) {
490 static int new_sprite_id = 0;
491 static int x_pos = 0;
492 static int y_pos = 0;
494 if (ImGui::IsWindowAppearing()) {
500 ImGui::Text(tr(
"Add New Sprite"));
503 BeginChild(
"ScrollRegion", ImVec2(250, 200),
true,
504 ImGuiWindowFlags_AlwaysVerticalScrollbar);
510 ImGui::Text(tr(
"Position:"));
514 if (Button(tr(
"Add Sprite"))) {
520 ImGui::CloseCurrentPopup();
524 if (Button(tr(
"Cancel"))) {
525 ImGui::CloseCurrentPopup();
533 static bool set_done =
false;
538 if (ImGui::BeginPopupModal(
541 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
542 static int selected_id = 0;
543 if (ImGui::IsWindowAppearing()) {
544 selected_id = sprite.
id();
547 BeginChild(
"ScrollRegion", ImVec2(350, 350),
true,
548 ImGuiWindowFlags_AlwaysVerticalScrollbar);
550 Text(
"%s", sprite.
name().c_str());
563 ImGui::CloseCurrentPopup();
568 ImGui::CloseCurrentPopup();
574 ImGui::CloseCurrentPopup();
584 const std::vector<gfx::Tile16>& tiles16,
585 const std::array<uint8_t, 0x200>& all_tiles_types) {
586 static bool set_done =
false;
593 "Diggable Tiles Editor")
595 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
596 static ImGuiTextFilter filter;
597 static int patch_mode = 0;
599 static std::string export_message;
603 Text(tr(
"Diggable Tiles: %d / 512"), diggable_count);
607 filter.Draw(
"Filter by Tile ID", 200);
609 if (Button(tr(
"Clear Filter"))) {
614 BeginChild(
"TileList", ImVec2(400, 300),
true,
615 ImGuiWindowFlags_AlwaysVerticalScrollbar);
620 for (uint16_t tile_id = 0;
624 snprintf(id_str,
sizeof(id_str),
"$%03X", tile_id);
626 if (!filter.PassFilter(id_str)) {
630 bool is_diggable = diggable_tiles->
IsDiggable(tile_id);
632 tiles16[tile_id], all_tiles_types);
635 std::optional<gui::StyleColorGuard> dig_color;
637 if (would_be_diggable) {
638 dig_color.emplace(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
640 dig_color.emplace(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.2f, 1.0f));
644 if (Checkbox(id_str, &is_diggable)) {
650 if (ImGui::IsItemHovered()) {
651 ImGui::SetTooltip(tr(
"Tile $%03X - %s"), tile_id,
652 would_be_diggable ?
"Auto-detected as diggable"
653 :
"Manually configured");
670 diggable_tiles->
Clear();
671 for (uint16_t tile_id = 0;
680 if (ImGui::IsItemHovered()) {
682 tr(
"Set diggable status based on tile types.\n"
683 "A tile is diggable if all 4 component tiles\n"
684 "have type 0x48 or 0x4A (diggable ground)."));
691 if (ImGui::IsItemHovered()) {
693 tr(
"Reset to vanilla diggable tiles:\n$034, $035, $071, "
694 "$0DA, $0E1, $0E2, $0F8, $10D, $10E, $10F"));
699 diggable_tiles->
Clear();
705 if (ImGui::CollapsingHeader(tr(
"ASM Patch Export"))) {
708 Text(tr(
"Patch Mode:"));
709 ImGui::RadioButton(tr(
"Vanilla"), &patch_mode, 0);
711 ImGui::RadioButton(tr(
"ZS Compatible"), &patch_mode, 1);
713 ImGui::RadioButton(tr(
"Custom"), &patch_mode, 2);
717 if (patch_mode == 2) {
734 *diggable_tiles, patch_config);
735 const std::string out_path =
"diggable_tiles_patch.asm";
736 std::ofstream out(out_path, std::ios::trunc);
738 out << patch_content;
740 export_message =
"Exported patch to " + out_path;
742 export_message =
"Failed to write " + out_path;
745 if (!export_message.empty()) {
746 ImGui::TextWrapped(
"%s", export_message.c_str());
757 ImGui::CloseCurrentPopup();
761 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