8#include "absl/strings/str_format.h"
18#include "imgui/imgui.h"
32 card_registry->
RegisterCard({.card_id =
"screen.dungeon_maps", .display_name =
"Dungeon Maps",
34 .shortcut_hint =
"Alt+1", .priority = 10});
35 card_registry->RegisterCard({.card_id =
"screen.inventory_menu", .display_name =
"Inventory Menu",
37 .shortcut_hint =
"Alt+2", .priority = 20});
38 card_registry->RegisterCard({.card_id =
"screen.overworld_map", .display_name =
"Overworld Map",
40 .shortcut_hint =
"Alt+3", .priority = 30});
41 card_registry->RegisterCard({.card_id =
"screen.title_screen", .display_name =
"Title Screen",
43 .shortcut_hint =
"Alt+4", .priority = 40});
44 card_registry->RegisterCard({.card_id =
"screen.naming_screen", .display_name =
"Naming Screen",
46 .shortcut_hint =
"Alt+5", .priority = 50});
49 card_registry->ShowCard(
"screen.title_screen");
67 for (
int i = 0; i < 4; i++) {
68 sheets_[i].SetPalette(*
rom()->mutable_dungeon_palette(3));
76 const int tile8_width = 128;
77 const int tile8_height = 128;
78 std::vector<uint8_t> tile8_data(tile8_width * tile8_height);
81 for (
int sheet_idx = 0; sheet_idx < 4; sheet_idx++) {
82 const auto& sheet =
sheets_[sheet_idx];
83 int dest_y_offset = sheet_idx * 32;
85 for (
int y = 0; y < 32; y++) {
86 for (
int x = 0; x < 128; x++) {
87 int src_index = y * 128 + x;
88 int dest_index = (dest_y_offset + y) * 128 + x;
90 if (src_index < sheet.size() && dest_index < tile8_data.size()) {
91 tile8_data[dest_index] = sheet.data()[src_index];
106 return absl::OkStatus();
126 bool* dungeon_maps_visible = card_registry->GetVisibilityFlag(
"screen.dungeon_maps");
127 if (dungeon_maps_visible && *dungeon_maps_visible) {
128 if (dungeon_maps_card.
Begin(dungeon_maps_visible)) {
131 dungeon_maps_card.
End();
135 bool* inventory_menu_visible = card_registry->GetVisibilityFlag(
"screen.inventory_menu");
136 if (inventory_menu_visible && *inventory_menu_visible) {
137 if (inventory_menu_card.
Begin(inventory_menu_visible)) {
140 inventory_menu_card.
End();
144 bool* overworld_map_visible = card_registry->GetVisibilityFlag(
"screen.overworld_map");
145 if (overworld_map_visible && *overworld_map_visible) {
146 if (overworld_map_card.
Begin(overworld_map_visible)) {
149 overworld_map_card.
End();
153 bool* title_screen_visible = card_registry->GetVisibilityFlag(
"screen.title_screen");
154 if (title_screen_visible && *title_screen_visible) {
155 if (title_screen_card.
Begin(title_screen_visible)) {
158 title_screen_card.
End();
162 bool* naming_screen_visible = card_registry->GetVisibilityFlag(
"screen.naming_screen");
163 if (naming_screen_visible && *naming_screen_visible) {
164 if (naming_screen_card.
Begin(naming_screen_visible)) {
167 naming_screen_card.
End();
179 static bool create =
false;
180 if (!create &&
rom()->is_loaded()) {
186 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Error loading inventory: %s",
194 if (ImGui::BeginTable(
"InventoryScreen", 4, ImGuiTableFlags_Resizable)) {
195 ImGui::TableSetupColumn(
"Canvas");
196 ImGui::TableSetupColumn(
"Tilesheet");
197 ImGui::TableSetupColumn(
"Item Icons");
198 ImGui::TableSetupColumn(
"Palette");
199 ImGui::TableHeadersRow();
201 ImGui::TableNextColumn();
208 ImGui::TableNextColumn();
215 ImGui::TableNextColumn();
218 ImGui::TableNextColumn();
234 if (ImGui::BeginTable(
"InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
236 ImGui::TableSetupColumn(
"#drawTool");
237 ImGui::TableSetupColumn(
"#sep1");
238 ImGui::TableSetupColumn(
"#zoomOut");
239 ImGui::TableSetupColumn(
"#zoomIN");
240 ImGui::TableSetupColumn(
"#sep2");
241 ImGui::TableSetupColumn(
"#bg2Tool");
242 ImGui::TableSetupColumn(
"#bg3Tool");
243 ImGui::TableSetupColumn(
"#itemTool");
245 ImGui::TableNextColumn();
249 ImGui::TableNextColumn();
253 ImGui::TableNextColumn();
255 ImGui::TableNextColumn();
259 ImGui::TableNextColumn();
263 ImGui::TableNextColumn();
265 ImGui::TableNextColumn();
269 ImGui::TableNextColumn();
279 if (ImGui::BeginChild(
"##ItemIconsList", ImVec2(0, 0),
true,
280 ImGuiWindowFlags_HorizontalScrollbar)) {
281 ImGui::Text(
"Item Icons (2x2 tiles each)");
286 ImGui::TextWrapped(
"No item icons loaded. Icons will be loaded when the "
287 "inventory is initialized.");
293 if (ImGui::BeginTable(
"##IconsTable", 2,
294 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
295 ImGui::TableSetupColumn(
"Icon Name");
296 ImGui::TableSetupColumn(
"Tile Data");
297 ImGui::TableHeadersRow();
299 for (
size_t i = 0; i < icons.size(); i++) {
300 const auto& icon = icons[i];
302 ImGui::TableNextRow();
303 ImGui::TableNextColumn();
306 if (ImGui::Selectable(icon.name.c_str(),
false,
307 ImGuiSelectableFlags_SpanAllColumns)) {
311 ImGui::TableNextColumn();
313 ImGui::Text(
"TL:%04X TR:%04X", icon.tile_tl, icon.tile_tr);
315 ImGui::Text(
"BL:%04X BR:%04X", icon.tile_bl, icon.tile_br);
323 "NOTE: Individual icon editing will be implemented in the future "
324 "Oracle of Secrets menu editor. Each icon is composed of 4 tile words "
325 "representing a 2x2 arrangement of 8x8 tiles in SNES tile format "
326 "(vhopppcc cccccccc).");
340 auto boss_room = current_dungeon.boss_room;
343 std::vector<int> tile_ids_to_render;
344 std::vector<ImVec2> tile_positions;
349 if (current_dungeon.floor_rooms[
floor_number][j] != 0x0F) {
350 int tile16_id = current_dungeon.floor_gfx[
floor_number][j];
351 int posX = ((j % 5) * 32);
352 int posY = ((j / 5) * 32);
355 tile_ids_to_render.push_back(tile16_id);
356 tile_positions.emplace_back(posX * 2, posY * 2);
361 for (
size_t idx = 0; idx < tile_ids_to_render.size(); ++idx) {
362 int tile16_id = tile_ids_to_render[idx];
363 ImVec2 pos = tile_positions[idx];
367 const int tile_x = (tile16_id % tiles_per_row) * 16;
368 const int tile_y = (tile16_id / tiles_per_row) * 16;
370 std::vector<uint8_t> tile_data(16 * 16);
371 int tile_data_offset = 0;
387 if (cached_tile && cached_tile->is_active()) {
389 if (!cached_tile->texture()) {
400 if (current_dungeon.floor_rooms[
floor_number][j] != 0x0F) {
401 int posX = ((j % 5) * 32);
402 int posY = ((j / 5) * 32);
404 if (current_dungeon.floor_rooms[
floor_number][j] == boss_room) {
430 if (ImGui::BeginTabBar(
"##DungeonMapTabs")) {
432 current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
433 for (
int i = 0; i < nbr_floors; i++) {
434 int basement_num = current_dungeon.nbr_of_basement - i;
435 std::string tab_name = absl::StrFormat(
"Basement %d", basement_num);
436 if (i >= current_dungeon.nbr_of_basement) {
437 tab_name = absl::StrFormat(
"Floor %d",
438 i - current_dungeon.nbr_of_basement + 1);
440 if (ImGui::BeginTabItem(tab_name.data())) {
454 const auto button_size = ImVec2(130, 0);
456 if (ImGui::Button(
"Add Floor", button_size) &&
457 current_dungeon.nbr_of_floor < 8) {
458 current_dungeon.nbr_of_floor++;
462 if (ImGui::Button(
"Remove Floor", button_size) &&
463 current_dungeon.nbr_of_floor > 0) {
464 current_dungeon.nbr_of_floor--;
468 if (ImGui::Button(
"Add Basement", button_size) &&
469 current_dungeon.nbr_of_basement < 8) {
470 current_dungeon.nbr_of_basement++;
474 if (ImGui::Button(
"Remove Basement", button_size) &&
475 current_dungeon.nbr_of_basement > 0) {
476 current_dungeon.nbr_of_basement--;
480 if (ImGui::Button(
"Copy Floor", button_size)) {
484 if (ImGui::Button(
"Paste Floor", button_size)) {
508 if (ImGui::BeginChild(
"##DungeonMapTiles", ImVec2(0, 0),
true)) {
550 std::vector<uint8_t> tile_data(64);
551 for (
int py = 0; py < 8; py++) {
552 for (
int px = 0; px < 8; px++) {
553 int src_x = tile_x + px;
554 int src_y = tile_y + py;
556 int dst_index = py * 8 + px;
570 if (cached_tile8 && cached_tile8->is_active()) {
572 if (!cached_tile8->texture()) {
589 if (selected_tile && selected_tile->is_active()) {
591 if (!selected_tile->texture()) {
608 if (ImGui::Button(
"Modify Tile16")) {
649 static std::vector<std::string> dungeon_names = {
650 "Sewers/Sanctuary",
"Hyrule Castle",
"Eastern Palace",
651 "Desert Palace",
"Tower of Hera",
"Agahnim's Tower",
652 "Palace of Darkness",
"Swamp Palace",
"Skull Woods",
653 "Thieves' Town",
"Ice Palace",
"Misery Mire",
654 "Turtle Rock",
"Ganon's Tower"};
656 if (ImGui::BeginTable(
"DungeonMapsTable", 4,
657 ImGuiTableFlags_Resizable |
658 ImGuiTableFlags_Reorderable |
659 ImGuiTableFlags_Hideable)) {
660 ImGui::TableSetupColumn(
"Dungeon");
661 ImGui::TableSetupColumn(
"Map");
662 ImGui::TableSetupColumn(
"Rooms Gfx");
663 ImGui::TableSetupColumn(
"Tiles Gfx");
664 ImGui::TableHeadersRow();
666 ImGui::TableNextColumn();
667 for (
int i = 0; i < dungeon_names.size(); i++) {
671 if (ImGui::IsItemClicked()) {
676 ImGui::TableNextColumn();
679 ImGui::TableNextColumn();
682 ImGui::TableNextColumn();
695 ImGui::Text(
"For use with custom inserted graphics assembly patches.");
696 if (ImGui::Button(
"Load GFX from BIN file"))
705 if (!bin_file.empty()) {
706 std::ifstream file(bin_file, std::ios::binary);
707 if (file.is_open()) {
709 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
710 std::istreambuf_iterator<char>());
716 std::vector<std::vector<uint8_t>> gfx_sheets;
717 for (
int i = 0; i < 4; i++) {
718 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
719 converted_bin.begin() + ((i + 1) * 0x1000));
728 status_ = absl::InternalError(
"Failed to load dungeon map tile16");
740 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Error loading title screen: %s",
748 ImGui::Text(
"Title screen not loaded. Ensure ROM is loaded.");
760 ImGui::OpenPopup(
"SaveSuccess");
767 if (ImGui::BeginPopup(
"SaveSuccess")) {
768 ImGui::Text(
"Title screen saved successfully!");
790 if (ImGui::BeginTable(
"TitleScreenTable", 2,
791 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
792 ImGui::TableSetupColumn(
"Title Screen (Composite)");
793 ImGui::TableSetupColumn(
"Tile Selector");
794 ImGui::TableHeadersRow();
797 ImGui::TableNextColumn();
801 ImGui::TableNextColumn();
814 if (composite_bitmap.is_active()) {
823 int tile_x =
static_cast<int>(click_pos.x) / 8;
824 int tile_y =
static_cast<int>(click_pos.y) / 8;
826 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
827 int tilemap_index = tile_y * 32 + tile_x;
866 if (bg1_bitmap.is_active()) {
875 int tile_x =
static_cast<int>(click_pos.x) / 8;
876 int tile_y =
static_cast<int>(click_pos.y) / 8;
878 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
879 int tilemap_index = tile_y * 32 + tile_x;
909 if (bg2_bitmap.is_active()) {
918 int tile_x =
static_cast<int>(click_pos.x) / 8;
919 int tile_y =
static_cast<int>(click_pos.y) / 8;
921 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
922 int tilemap_index = tile_y * 32 + tile_x;
952 if (tiles8_bitmap.is_active()) {
961 int tile_x =
static_cast<int>(click_pos.x) / 8;
962 int tile_y =
static_cast<int>(click_pos.y) / 8;
963 int tiles_per_row = 128 / 8;
981 ImGui::SetNextItemWidth(100);
994 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Error loading overworld map: %s",
1002 ImGui::Text(
"Overworld map not loaded. Ensure ROM is loaded.");
1014 ImGui::OpenPopup(
"OWSaveSuccess");
1032 if (ImGui::Button(
"Load Custom Map...")) {
1034 if (!path.empty()) {
1037 ImGui::OpenPopup(
"CustomMapLoadError");
1042 if (ImGui::Button(
"Save Custom Map...")) {
1044 if (!path.empty()) {
1047 ImGui::OpenPopup(
"CustomMapSaveSuccess");
1056 if (ImGui::BeginPopup(
"CustomMapLoadError")) {
1057 ImGui::Text(
"Error loading custom map: %s",
status_.message().data());
1060 if (ImGui::BeginPopup(
"CustomMapSaveSuccess")) {
1061 ImGui::Text(
"Custom map saved successfully!");
1066 if (ImGui::BeginPopup(
"OWSaveSuccess")) {
1067 ImGui::Text(
"Overworld map saved successfully!");
1072 if (ImGui::BeginTable(
"OWMapTable", 3,
1073 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
1074 ImGui::TableSetupColumn(
"Map Canvas");
1075 ImGui::TableSetupColumn(
"Tileset");
1076 ImGui::TableSetupColumn(
"Palette");
1077 ImGui::TableHeadersRow();
1080 ImGui::TableNextColumn();
1085 if (map_bitmap.is_active()) {
1094 int tile_x =
static_cast<int>(click_pos.x) / 8;
1095 int tile_y =
static_cast<int>(click_pos.y) / 8;
1097 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1098 int tile_index = tile_x + (tile_y * 64);
1122 ImGui::TableNextColumn();
1127 if (tiles8_bitmap.is_active()) {
1135 int tile_x =
static_cast<int>(click_pos.x) / 8;
1136 int tile_y =
static_cast<int>(click_pos.y) / 8;
1145 ImGui::TableNextColumn();
1156 static bool show_bg1 =
true;
1157 static bool show_bg2 =
true;
1158 static bool show_bg3 =
true;
1160 static bool drawing_bg1 =
true;
1161 static bool drawing_bg2 =
false;
1162 static bool drawing_bg3 =
false;
1164 ImGui::Checkbox(
"Show BG1", &show_bg1);
1166 ImGui::Checkbox(
"Show BG2", &show_bg2);
1168 ImGui::Checkbox(
"Draw BG1", &drawing_bg1);
1170 ImGui::Checkbox(
"Draw BG2", &drawing_bg2);
1172 ImGui::Checkbox(
"Draw BG3", &drawing_bg3);
project::ResourceLabelManager * resource_label()
auto mutable_dungeon_palette(int i)
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
void DrawTitleScreenEditor()
void DrawDungeonMapsRoomGfx()
Draw dungeon room graphics editor with enhanced tile16 editing.
gfx::SnesPalette palette_
void DrawTitleScreenBlocksetSelector()
bool title_screen_loaded_
gui::Canvas ow_map_canvas_
gfx::Tilemap tile16_blockset_
void DrawTitleScreenBG2Canvas()
gui::Canvas tilemap_canvas_
void DrawInventoryItemIcons()
std::array< gfx::TileInfo, 4 > current_tile16_info
absl::Status Load() override
absl::Status Update() override
gui::Canvas screen_canvas_
void DrawTitleScreenBG1Canvas()
gui::Canvas title_bg1_canvas_
zelda3::OverworldMapScreen ow_map_screen_
void Initialize() override
int selected_title_tile16_
gui::Canvas current_tile_canvas_
void DrawDungeonMapsEditor()
Draw dungeon maps editor with enhanced ROM hacking features.
gui::Canvas title_bg2_canvas_
void DrawInventoryToolset()
gfx::Tilemap tile8_tilemap_
void DrawTitleScreenCompositeCanvas()
zelda3::TitleScreen title_screen_
void DrawOverworldMapEditor()
zelda3::Inventory inventory_
EditingMode current_mode_
zelda3::DungeonMapLabels dungeon_map_labels_
bool paste_button_pressed
gui::Canvas ow_tileset_canvas_
gui::Canvas title_blockset_canvas_
void DrawDungeonMapToolset()
gui::Canvas tilesheet_canvas_
void DrawNamingScreenEditor()
void DrawDungeonMapScreen(int i)
void DrawDungeonMapsTabs()
std::vector< zelda3::DungeonMap > dungeon_maps_
void DrawInventoryMenuEditor()
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Represents a bitmap image optimized for SNES ROM hacking.
const uint8_t * data() const
const SnesPalette & palette() const
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
void set_data(const std::vector< uint8_t > &data)
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap.
void Get16x16Tile(int tile_x, int tile_y, std::vector< uint8_t > &tile_data, int &tile_data_offset)
Extract a 16x16 tile from the bitmap (SNES metatile size)
RAII timer for automatic timing management.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
ImVector< ImVec2 > * mutable_points()
int GetTileIdFromMousePos()
bool DrawTileSelector(int size, int size_y=0)
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
void DrawBitmapTable(const BitmapTable &gfx_bin)
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
const ImVector< ImVec2 > & points() const
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
void DrawText(const std::string &text, int x, int y)
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
absl::Status Create(Rom *rom)
Initialize and load inventory screen data from ROM.
auto & mutable_lw_tiles()
absl::Status SaveCustomMap(const std::string &file_path, bool use_dark_world)
Save map data to external binary file.
auto & mutable_dw_tiles()
absl::Status LoadCustomMap(const std::string &file_path)
Load custom map from external binary file.
absl::Status Save(Rom *rom)
Save changes back to ROM.
absl::Status RenderMapLayer(bool use_dark_world)
Render map tiles into bitmap.
absl::Status Create(Rom *rom)
Initialize and load overworld map data from ROM.
auto & composite_bitmap()
auto & mutable_bg1_buffer()
auto & mutable_bg2_buffer()
absl::Status RenderCompositeLayer(bool show_bg1, bool show_bg2)
Render composite layer with BG1 on top of BG2 with transparency.
absl::Status Save(Rom *rom)
absl::Status RenderBG2Layer()
Render BG2 tilemap into bitmap pixels Converts tile IDs from tiles_bg2_buffer_ into pixel data.
absl::Status Create(Rom *rom)
Initialize and load title screen data from ROM.
absl::Status RenderBG1Layer()
Render BG1 tilemap into bitmap pixels Converts tile IDs from tiles_bg1_buffer_ into pixel data.
#define ICON_MD_MORE_VERT
#define ICON_MD_EDIT_ATTRIBUTES
#define ICON_MD_INVENTORY
#define PRINT_IF_ERROR(expression)
#define RETURN_IF_ERROR(expression)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr uint32_t kRedPen
void RenderTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
void ModifyTile16(Tilemap &tilemap, const std::vector< uint8_t > &data, const TileInfo &top_left, const TileInfo &top_right, const TileInfo &bottom_left, const TileInfo &bottom_right, int sheet_offset, int tile_id)
void UpdateTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
std::vector< uint8_t > SnesTo8bppSheet(std::span< uint8_t > sheet, int bpp, int num_sheets)
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, const std::string &title, ImGuiColorEditFlags flags)
Full inline palette editor with color picker and copy options.
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
std::string HexByte(uint8_t byte, HexStringParams params)
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom, const std::vector< uint8_t > &gfx_data, bool bin_mode)
Load the dungeon map tile16 from the ROM.
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom)
Save the dungeon map tile16 to the ROM.
absl::StatusOr< std::vector< DungeonMap > > LoadDungeonMaps(Rom &rom, DungeonMapLabels &dungeon_map_labels)
Load the dungeon maps from the ROM.
Main namespace for the application.
EditorCardRegistry * card_registry
void CacheTile(int tile_id, Bitmap &&bitmap)
Cache a tile bitmap.
Bitmap * GetTile(int tile_id)
Get a cached tile by ID.
Pair tile_size
Size of individual tiles (8x8 or 16x16)
TileCache tile_cache
Smart tile cache with LRU eviction.
Pair map_size
Size of tilemap in tiles.
Bitmap atlas
Master bitmap containing all tiles.
std::vector< std::array< gfx::TileInfo, 4 > > tile_info
Tile metadata (4 tiles per 16x16)
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)