diff --git a/src/app/core/common.cc b/src/app/core/common.cc index 03f84ecd..e41477ae 100644 --- a/src/app/core/common.cc +++ b/src/app/core/common.cc @@ -17,8 +17,12 @@ namespace core { std::shared_ptr ExperimentFlags::flags_; -std::string UppercaseHexByte(uint8_t byte) { - std::string result = absl::StrFormat("0x%02X", byte); +std::string UppercaseHexByte(uint8_t byte, bool leading) { + if (leading) { + std::string result = absl::StrFormat("0x%02X", byte); + return result; + } + std::string result = absl::StrFormat("%02X", byte); return result; } std::string UppercaseHexWord(uint16_t word) { diff --git a/src/app/core/common.h b/src/app/core/common.h index 505ec449..7870ccb9 100644 --- a/src/app/core/common.h +++ b/src/app/core/common.h @@ -65,13 +65,16 @@ class ExperimentFlags { bool kSaveOverworldMaps = false; // Save overworld entrances to the ROM. - bool kSaveOverworldEntrances = false; + bool kSaveOverworldEntrances = true; // Save overworld exits to the ROM. - bool kSaveOverworldExits = false; + bool kSaveOverworldExits = true; + + // Save overworld items to the ROM. + bool kSaveOverworldItems = true; // Save overworld properties to the ROM. - bool kSaveOverworldProperties = false; + bool kSaveOverworldProperties = true; } overworld; }; @@ -232,7 +235,7 @@ class ImGuiIdIssuer { } }; -std::string UppercaseHexByte(uint8_t byte); +std::string UppercaseHexByte(uint8_t byte, bool leading = false); std::string UppercaseHexWord(uint16_t word); std::string UppercaseHexLong(uint32_t dword); diff --git a/src/app/core/constants.h b/src/app/core/constants.h index 036ca779..898b7114 100644 --- a/src/app/core/constants.h +++ b/src/app/core/constants.h @@ -68,6 +68,15 @@ } \ } +#define RETURN_VOID_IF_ERROR(expression) \ + { \ + auto error = expression; \ + if (!error.ok()) { \ + std::cout << error.ToString() << std::endl; \ + return; \ + } \ + } + #define RETURN_IF_ERROR(expression) \ { \ auto error = expression; \ @@ -136,7 +145,7 @@ namespace app { namespace core { constexpr uint32_t kRedPen = 0xFF0000FF; -constexpr float kYazeVersion = 0.06; +constexpr float kYazeVersion = 0.07; // ============================================================================ // Magic numbers diff --git a/src/app/editor/dungeon_editor.cc b/src/app/editor/dungeon_editor.cc index f6b23848..af3cc966 100644 --- a/src/app/editor/dungeon_editor.cc +++ b/src/app/editor/dungeon_editor.cc @@ -309,6 +309,11 @@ void DungeonEditor::DrawDungeonTabView() { for (int n = 0; n < active_rooms_.Size;) { bool open = true; + if (active_rooms_[n] > sizeof(zelda3::dungeon::kRoomNames) / 4) { + active_rooms_.erase(active_rooms_.Data + n); + continue; + } + if (ImGui::BeginTabItem( zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), &open, ImGuiTabItemFlags_None)) { diff --git a/src/app/editor/dungeon_editor.h b/src/app/editor/dungeon_editor.h index 12c876c4..6216489a 100644 --- a/src/app/editor/dungeon_editor.h +++ b/src/app/editor/dungeon_editor.h @@ -42,6 +42,8 @@ class DungeonEditor : public Editor, absl::Status Undo() override { return absl::OkStatus(); } absl::Status Redo() override { return absl::OkStatus(); } + void add_room(int i) { active_rooms_.push_back(i); } + private: void UpdateDungeonRoomView(); diff --git a/src/app/editor/master_editor.cc b/src/app/editor/master_editor.cc index e4818edc..4dd08c69 100644 --- a/src/app/editor/master_editor.cc +++ b/src/app/editor/master_editor.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "absl/status/status.h" @@ -122,6 +123,31 @@ void MasterEditor::SetupScreen(std::shared_ptr renderer) { rom()->SetupRenderer(renderer); } +namespace { +// Function to switch the active tab in a tab bar +void SetTabBarTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) { + if (tab_bar == NULL) return; + + // Find the tab item with the specified tab_id + // for (int i = 0; i < tab_bar->Tabs.Size; i++) { + ImGuiTabItem* tab_item = &tab_bar->Tabs[tab_id]; + // if (tab_item->ID == tab_id) { + // Set the tab item as active + tab_item->LastFrameVisible = -1; + tab_item->LastFrameSelected = -1; + tab_bar->VisibleTabId = tab_id; + tab_bar->VisibleTabWasSubmitted = true; + tab_bar->SelectedTabId = tab_id; + tab_bar->NextSelectedTabId = tab_id; + tab_bar->ReorderRequestTabId = tab_id; + tab_bar->CurrFrameVisible = -1; + + // break; + // } + // } +} +} // namespace + absl::Status MasterEditor::Update() { NewMasterFrame(); @@ -138,15 +164,22 @@ absl::Status MasterEditor::Update() { } TAB_BAR("##TabBar") + auto current_tab_bar = ImGui::GetCurrentContext()->CurrentTabBar; - gui::RenderTabItem("Overworld", [&]() { - current_editor_ = &overworld_editor_; - status_ = overworld_editor_.Update(); - }); + if (overworld_editor_.jump_to_tab() == -1) { + gui::RenderTabItem("Overworld", [&]() { + current_editor_ = &overworld_editor_; + status_ = overworld_editor_.Update(); + }); + } gui::RenderTabItem("Dungeon", [&]() { current_editor_ = &dungeon_editor_; status_ = dungeon_editor_.Update(); + if (overworld_editor_.jump_to_tab() != -1) { + dungeon_editor_.add_room(overworld_editor_.jump_to_tab()); + overworld_editor_.jump_to_tab_ = -1; + } }); gui::RenderTabItem("Graphics", @@ -336,6 +369,8 @@ void MasterEditor::DrawFileMenu() { &mutable_flags()->overworld.kSaveOverworldEntrances); Checkbox("Save Overworld Exits", &mutable_flags()->overworld.kSaveOverworldExits); + Checkbox("Save Overworld Items", + &mutable_flags()->overworld.kSaveOverworldItems); Checkbox("Save Overworld Properties", &mutable_flags()->overworld.kSaveOverworldProperties); ImGui::EndMenu(); @@ -537,16 +572,16 @@ void MasterEditor::DrawHelpMenu() { void MasterEditor::SaveRom() { if (flags()->kSaveDungeonMaps) { status_ = screen_editor_.SaveDungeonMaps(); - PRINT_IF_ERROR(status_); + RETURN_VOID_IF_ERROR(status_); } if (flags()->overworld.kSaveOverworldMaps) { if (overworld_editor_.overworld()->CreateTile32Tilemap()) { status_ = overworld_editor_.overworld()->SaveMap16Tiles(); - PRINT_IF_ERROR(status_); + RETURN_VOID_IF_ERROR(status_); status_ = overworld_editor_.overworld()->SaveMap32Tiles(); - PRINT_IF_ERROR(status_); + RETURN_VOID_IF_ERROR(status_); status_ = overworld_editor_.overworld()->SaveOverworldMaps(); - PRINT_IF_ERROR(status_); + RETURN_VOID_IF_ERROR(status_); } else { status_ = absl::InternalError( "Failed to save Overworld maps, aborting ROM save."); @@ -555,15 +590,19 @@ void MasterEditor::SaveRom() { } if (flags()->overworld.kSaveOverworldEntrances) { status_ = overworld_editor_.overworld()->SaveEntrances(); - PRINT_IF_ERROR(status_); + RETURN_VOID_IF_ERROR(status_); } if (flags()->overworld.kSaveOverworldExits) { status_ = overworld_editor_.overworld()->SaveExits(); - PRINT_IF_ERROR(status_); + RETURN_VOID_IF_ERROR(status_); + } + if (flags()->overworld.kSaveOverworldItems) { + status_ = overworld_editor_.overworld()->SaveItems(); + RETURN_VOID_IF_ERROR(status_); } if (flags()->overworld.kSaveOverworldProperties) { status_ = overworld_editor_.overworld()->SaveMapProperties(); - PRINT_IF_ERROR(status_); + RETURN_VOID_IF_ERROR(status_); } status_ = rom()->SaveToFile(backup_rom_); diff --git a/src/app/editor/master_editor.h b/src/app/editor/master_editor.h index 5ecdd6d6..bfd34437 100644 --- a/src/app/editor/master_editor.h +++ b/src/app/editor/master_editor.h @@ -1,6 +1,8 @@ #ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H #define YAZE_APP_EDITOR_MASTER_EDITOR_H +#define IMGUI_DEFINE_MATH_OPERATORS 1 + #include #include #include diff --git a/src/app/editor/overworld_editor.cc b/src/app/editor/overworld_editor.cc index 15159da3..063dc075 100644 --- a/src/app/editor/overworld_editor.cc +++ b/src/app/editor/overworld_editor.cc @@ -74,10 +74,6 @@ absl::Status OverworldEditor::Update() { static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; - - // We demonstrate using the full viewport area or the work area (without - // menu-bars, task-bars etc.) Based on your use case you may want one or the - // other. const ImGuiViewport *viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos); ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize @@ -109,9 +105,7 @@ absl::Status OverworldEditor::Update() { } absl::Status OverworldEditor::UpdateOverworldEdit() { - // Draws the toolset for editing the Overworld. RETURN_IF_ERROR(DrawToolset()) - if (BeginTable(kOWEditTable.data(), 2, kOWEditFlags, ImVec2(0, 0))) { TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch, ImGui::GetContentRegionAvail().x); @@ -127,100 +121,6 @@ absl::Status OverworldEditor::UpdateOverworldEdit() { return absl::OkStatus(); } -absl::Status OverworldEditor::UpdateUsageStats() { - if (BeginTable("##UsageStatsTable", 3, kOWEditFlags, ImVec2(0, 0))) { - TableSetupColumn("Entrances"); - TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch, - ImGui::GetContentRegionAvail().x); - TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 256); - TableHeadersRow(); - TableNextRow(); - - TableNextColumn(); - ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar); - for (int i = 0; i < 0x81; i++) { - std::string str = absl::StrFormat("%#x", i); - if (ImGui::Selectable(str.c_str(), selected_entrance_ == i, - overworld_.Entrances().at(i).deleted - ? ImGuiSelectableFlags_Disabled - : 0)) { - selected_entrance_ = i; - selected_usage_map_ = overworld_.Entrances().at(i).map_id_; - properties_canvas_.set_highlight_tile_id(selected_usage_map_); - } - } - ImGui::EndChild(); - - TableNextColumn(); - DrawUsageGrid(); - TableNextColumn(); - DrawOverworldProperties(); - ImGui::EndTable(); - } - return absl::OkStatus(); -} - -void OverworldEditor::CalculateUsageStats() { - absl::flat_hash_map entrance_usage; - for (auto each_entrance : overworld_.Entrances()) { - if (each_entrance.map_id_ < 0x40 + (current_world_ * 0x40) && - each_entrance.map_id_ >= (current_world_ * 0x40)) { - entrance_usage[each_entrance.entrance_id_]++; - } - } -} - -void OverworldEditor::DrawUsageGrid() { - // Create a grid of 8x8 squares - int totalSquares = 128; - int squaresWide = 8; - int squaresTall = (totalSquares + squaresWide - 1) / - squaresWide; // Ceiling of totalSquares/squaresWide - - // Loop through each row - for (int row = 0; row < squaresTall; ++row) { - ImGui::NewLine(); - - for (int col = 0; col < squaresWide; ++col) { - if (row * squaresWide + col >= totalSquares) { - break; - } - // Determine if this square should be highlighted - bool highlight = selected_usage_map_ == (row * squaresWide + col); - - // Set highlight color if needed - if (highlight) { - ImGui::PushStyleColor( - ImGuiCol_Button, - ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color - } - - // Create a button or selectable for each square - if (ImGui::Button("##square", ImVec2(20, 20))) { - // Switch over to the room editor tab - // and add a room tab by the ID of the square - // that was clicked - } - - // Reset style if it was highlighted - if (highlight) { - ImGui::PopStyleColor(); - } - - // Check if the square is hovered - if (ImGui::IsItemHovered()) { - // Display a tooltip with all the room properties - } - - // Keep squares in the same line - ImGui::SameLine(); - } - } -} - -// ---------------------------------------------------------------------------- - absl::Status OverworldEditor::DrawToolset() { static bool show_gfx_group = false; static bool show_properties = false; @@ -369,35 +269,6 @@ absl::Status OverworldEditor::DrawToolset() { return absl::OkStatus(); } -void OverworldEditor::DrawOverworldProperties() { - static bool init_properties = false; - - if (!init_properties) { - for (int i = 0; i < 0x40; i++) { - std::string area_graphics_str = absl::StrFormat( - - "0x%02hX", overworld_.overworld_map(i)->area_graphics()); - properties_canvas_.mutable_labels(0)->push_back(area_graphics_str); - } - for (int i = 0; i < 0x40; i++) { - std::string area_palette_str = absl::StrFormat( - "0x%02hX", overworld_.overworld_map(i)->area_palette()); - properties_canvas_.mutable_labels(1)->push_back(area_palette_str); - } - init_properties = true; - } - - if (ImGui::Button("Area Graphics")) { - properties_canvas_.set_current_labels(0); - } - - if (ImGui::Button("Area Palette")) { - properties_canvas_.set_current_labels(1); - } - - properties_canvas_.UpdateInfoGrid(ImVec2(512, 512), 16, 1.0f, 64); -} - // ---------------------------------------------------------------------------- void OverworldEditor::RefreshChildMap(int map_index) { @@ -572,9 +443,25 @@ void OverworldEditor::DrawOverworldMapSettings() { // ---------------------------------------------------------------------------- -namespace { -void MoveEntranceOnGrid(zelda3::OverworldEntrance &entrance, ImVec2 canvas_p0, - ImVec2 scrolling) { +namespace entity_internal { + +bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity, + ImVec2 canvas_p0, ImVec2 scrolling) { + // Get the mouse position relative to the canvas + const ImGuiIO &io = ImGui::GetIO(); + const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); + const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); + + // Check if the mouse is hovering over the entity + if (mouse_pos.x >= entity.x_ && mouse_pos.x <= entity.x_ + 16 && + mouse_pos.y >= entity.y_ && mouse_pos.y <= entity.y_ + 16) { + return true; + } + return false; +} + +void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0, + ImVec2 scrolling, bool free_movement = false) { // Get the mouse position relative to the canvas const ImGuiIO &io = ImGui::GetIO(); const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); @@ -583,151 +470,203 @@ void MoveEntranceOnGrid(zelda3::OverworldEntrance &entrance, ImVec2 canvas_p0, // Calculate the new position on the 16x16 grid int new_x = static_cast(mouse_pos.x) / 16 * 16; int new_y = static_cast(mouse_pos.y) / 16 * 16; + if (free_movement) { + new_x = static_cast(mouse_pos.x) / 8 * 8; + new_y = static_cast(mouse_pos.y) / 8 * 8; + } - // Update the entrance position - entrance.x_ = new_x; - entrance.y_ = new_y; + // Update the entity position + entity->set_x(new_x); + entity->set_y(new_y); } -void DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance) { +void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0, + ImVec2 scrolling, bool &is_dragging_entity, + zelda3::OverworldEntity *&dragged_entity, + zelda3::OverworldEntity *¤t_entity, + bool free_movement = false) { + std::string entity_type = "Entity"; + if (entity->type_ == zelda3::OverworldEntity::EntityType::kExit) { + entity_type = "Exit"; + } else if (entity->type_ == zelda3::OverworldEntity::EntityType::kEntrance) { + entity_type = "Entrance"; + } else if (entity->type_ == zelda3::OverworldEntity::EntityType::kSprite) { + entity_type = "Sprite"; + } else if (entity->type_ == zelda3::OverworldEntity::EntityType::kItem) { + entity_type = "Item"; + } + if (IsMouseHoveringOverEntity(*entity, canvas_p0, scrolling) && + ImGui::IsMouseDragging(ImGuiMouseButton_Left) && !is_dragging_entity) { + dragged_entity = entity; + is_dragging_entity = true; + } else if (IsMouseHoveringOverEntity(*entity, canvas_p0, scrolling) && + ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { + current_entity = entity; + ImGui::OpenPopup(absl::StrFormat("%s editor", entity_type.c_str()).c_str()); + } else if (is_dragging_entity && dragged_entity == entity && + ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement); + entity->UpdateMapProperties(entity->map_id_); + is_dragging_entity = false; + dragged_entity = nullptr; + } else if (is_dragging_entity && dragged_entity == entity) { + if (ImGui::BeginDragDropSource()) { + ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity, + sizeof(zelda3::OverworldEntity)); + Text("Moving %s ID: %s", entity_type.c_str(), + core::UppercaseHexByte(entity->entity_id_).c_str()); + ImGui::EndDragDropSource(); + } + MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement); + entity->x_ = dragged_entity->x_; + entity->y_ = dragged_entity->y_; + entity->UpdateMapProperties(entity->map_id_); + } +} + +} // namespace entity_internal + +namespace entrance_internal { + +bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance) { + static bool set_done = false; + if (set_done) { + set_done = false; + } if (ImGui::BeginPopupModal("Entrance editor", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { - static uint16_t map_id = entrance.map_id_; - static int entrance_id = entrance.entrance_id_; - static int x = entrance.x_; - static int y = entrance.y_; + gui::InputHex("Map ID", &entrance.map_id_); + gui::InputHexByte("Entrance ID", &entrance.entrance_id_, + kInputFieldSize + 20); + gui::InputHex("X", &entrance.x_); + gui::InputHex("Y", &entrance.y_); - gui::InputHexWord("Map ID", &map_id, kInputFieldSize + 20); - gui::InputHexByte("Entrance ID", &entrance.entrance_id_, kInputFieldSize); - ImGui::SetNextItemWidth(100.f); - ImGui::InputInt("X", &x); - ImGui::SetNextItemWidth(100.f); - ImGui::InputInt("Y", &y); - - if (ImGui::Button("OK")) { - // Implement what happens when OK is pressed - entrance.map_id_ = map_id; - entrance.entrance_id_ = entrance_id; - entrance.x_ = x; - entrance.y_ = y; + if (ImGui::Button(ICON_MD_DONE)) { ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - - if (ImGui::Button("Cancel")) { - // Implement what happens when Cancel is pressed + if (ImGui::Button(ICON_MD_CANCEL)) { + set_done = true; ImGui::CloseCurrentPopup(); } - ImGui::EndPopup(); } + return set_done; } -} // namespace +} // namespace entrance_internal -bool OverworldEditor::IsMouseHoveringOverEntrance( - const zelda3::OverworldEntrance &entrance, ImVec2 canvas_p0, - ImVec2 scrolling) { - // Get the mouse position relative to the canvas - const ImGuiIO &io = ImGui::GetIO(); - const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); - const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); - - // Check if the mouse is hovering over the entrance - if (mouse_pos.x >= entrance.x_ && mouse_pos.x <= entrance.x_ + 16 && - mouse_pos.y >= entrance.y_ && mouse_pos.y <= entrance.y_ + 16) { - return true; - } - return false; -} - -void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, - ImVec2 scrolling) { +void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, + bool holes) { + int i = 0; for (auto &each : overworld_.Entrances()) { if (each.map_id_ < 0x40 + (current_world_ * 0x40) && - each.map_id_ >= (current_world_ * 0x40)) { - ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, - ImVec4(210, 24, 210, 150)); + each.map_id_ >= (current_world_ * 0x40) && !each.deleted) { + // Make this yellow + auto color = ImVec4(255, 255, 0, 100); + if (each.is_hole_) { + color = ImVec4(255, 255, 255, 200); + } + if (each.deleted) { + color = ImVec4(0, 0, 0, 0); + } + ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, color); std::string str = core::UppercaseHexByte(each.entrance_id_); - // Check if this entrance is being clicked and dragged if (current_mode == EditingMode::ENTRANCES) { - if (IsMouseHoveringOverEntrance(each, canvas_p0, scrolling) && - ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { - dragged_entrance_ = &each; - is_dragging_entrance_ = true; - if (ImGui::BeginDragDropSource()) { - ImGui::SetDragDropPayload("ENTRANCE_PAYLOAD", &each, - sizeof(zelda3::OverworldEntrance)); - Text("Moving Entrance ID: %s", str.c_str()); - ImGui::EndDragDropSource(); - } - } else if (IsMouseHoveringOverEntrance(each, canvas_p0, scrolling) && - ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - current_entrance_ = &each; - ImGui::OpenPopup("Entrance editor"); - } else if (is_dragging_entrance_ && dragged_entrance_ == &each && - ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { - MoveEntranceOnGrid(*dragged_entrance_, canvas_p0, scrolling); - each.x_ = dragged_entrance_->x_; - each.y_ = dragged_entrance_->y_; - each.UpdateMapProperties(each.map_id_); - is_dragging_entrance_ = false; - dragged_entrance_ = nullptr; - } else if (is_dragging_entrance_ && dragged_entrance_ == &each) { - MoveEntranceOnGrid(*dragged_entrance_, canvas_p0, scrolling); - each.x_ = dragged_entrance_->x_; - each.y_ = dragged_entrance_->y_; - each.UpdateMapProperties(each.map_id_); + entity_internal::HandleEntityDragging(&each, canvas_p0, scrolling, + is_dragging_entity_, + dragged_entity_, current_entity_); + + if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0, + scrolling) && + ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + jump_to_tab_ = each.entrance_id_; + } + + if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0, + scrolling) && + ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { + current_entrance_id_ = i; + current_entrance_ = each; } } - ow_map_canvas_.DrawText(str, each.x_ - 4, each.y_ - 2); + ow_map_canvas_.DrawText(str, each.x_, each.y_); } + i++; } - DrawOverworldEntrancePopup(*current_entrance_); -} - -namespace { -bool IsMouseHoveringOverExit(const zelda3::OverworldExit &exit, - ImVec2 canvas_p0, ImVec2 scrolling) { - // Get the mouse position relative to the canvas - const ImGuiIO &io = ImGui::GetIO(); - const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); - const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); - - // Check if the mouse is hovering over the entrance - if (mouse_pos.x >= exit.x_ && mouse_pos.x <= exit.x_ + 16 && - mouse_pos.y >= exit.y_ && mouse_pos.y <= exit.y_ + 16) { - return true; + if (entrance_internal::DrawOverworldEntrancePopup( + overworld_.Entrances()[current_entrance_id_])) { + overworld_.Entrances()[current_entrance_id_] = current_entrance_; } - return false; } -void DrawExitEditorPopup(zelda3::OverworldExit &exit) { +namespace exit_internal { + +bool DrawExitEditorPopup(zelda3::OverworldExit &exit) { + static bool set_done = false; + if (set_done) { + set_done = false; + } if (ImGui::BeginPopupModal("Exit editor", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { - static int room = 385; - static int scrollY = 0, centerY = 143, yPos = 128; - static int scrollX = 256, centerX = 397, xPos = 496; - static int doorType = 0; // Normal door: None = 0, Wooden = 1, Bombable = 2 - static int fancyDoorType = - 0; // Fancy door: None = 0, Sanctuary = 1, Palace = 2 - static int map = 128, unk1 = 0, unk2 = 0; - static int linkPosture = 2, spriteGFX = 12; - static int bgGFX = 47, palette = 10, sprPal = 8; - static int top = 0, bottom = 32, left = 256, right = 256; + // Normal door: None = 0, Wooden = 1, Bombable = 2 + static int doorType = exit.door_type_1_; + // Fancy door: None = 0, Sanctuary = 1, Palace = 2 + static int fancyDoorType = exit.door_type_2_; + + static int xPos = 0; + static int yPos = 0; + + // Special overworld exit properties + static int centerY = 0; + static int centerX = 0; + static int unk1 = 0; + static int unk2 = 0; + static int linkPosture = 0; + static int spriteGFX = 0; + static int bgGFX = 0; + static int palette = 0; + static int sprPal = 0; + static int top = 0; + static int bottom = 0; + static int left = 0; + static int right = 0; static int leftEdgeOfMap = 0; - ImGui::InputScalar("Room", ImGuiDataType_U8, &exit.room_id_); - ImGui::InputInt("Scroll Y", &scrollY); - ImGui::InputInt("Center Y", ¢erY); - ImGui::InputInt("Y pos", &yPos); - ImGui::InputInt("Scroll X", &scrollX); - ImGui::InputInt("Center X", ¢erX); - ImGui::InputInt("X pos", &xPos); + gui::InputHexWord("Room", &exit.room_id_); + ImGui::SameLine(); + gui::InputHex("Entity ID", &exit.entity_id_, 4); + gui::InputHex("Map", &exit.map_id_); + ImGui::SameLine(); + ImGui::Checkbox("Automatic", &exit.is_automatic_); + + gui::InputHex("X Positon", &exit.x_); + ImGui::SameLine(); + gui::InputHex("Y Position", &exit.y_); + + gui::InputHexByte("X Camera", &exit.x_camera_); + ImGui::SameLine(); + gui::InputHexByte("Y Camera", &exit.y_camera_); + + gui::InputHexWord("X Scroll", &exit.x_scroll_); + ImGui::SameLine(); + gui::InputHexWord("Y Scroll", &exit.y_scroll_); + + ImGui::Separator(); + + static bool show_properties = false; + ImGui::Checkbox("Show properties", &show_properties); + if (show_properties) { + ImGui::Text("Deleted? %s", exit.deleted ? "true" : "false"); + ImGui::Text("Hole? %s", exit.is_hole_ ? "true" : "false"); + ImGui::Text("Large Map? %s", exit.large_map_ ? "true" : "false"); + } + + gui::TextWithSeparators("Unimplemented below"); ImGui::RadioButton("None", &doorType, 0); ImGui::SameLine(); @@ -736,102 +675,205 @@ void DrawExitEditorPopup(zelda3::OverworldExit &exit) { ImGui::RadioButton("Bombable", &doorType, 2); // If door type is not None, input positions if (doorType != 0) { - ImGui::InputInt("Door X pos", - &xPos); // Placeholder for door's X position - ImGui::InputInt("Door Y pos", - &yPos); // Placeholder for door's Y position + gui::InputHex("Door X pos", &xPos); + gui::InputHex("Door Y pos", &yPos); } ImGui::RadioButton("None##Fancy", &fancyDoorType, 0); ImGui::SameLine(); - ImGui::RadioButton("Sanctuary", &fancyDoorType - - , - 1); + ImGui::RadioButton("Sanctuary", &fancyDoorType, 1); ImGui::SameLine(); ImGui::RadioButton("Palace", &fancyDoorType, 2); // If fancy door type is not None, input positions if (fancyDoorType != 0) { // Placeholder for fancy door's X position - ImGui::InputInt("Fancy Door X pos", &xPos); + gui::InputHex("Fancy Door X pos", &xPos); // Placeholder for fancy door's Y position - ImGui::InputInt("Fancy Door Y pos", &yPos); + gui::InputHex("Fancy Door Y pos", &yPos); } - ImGui::InputInt("Map", &map); - ImGui::InputInt("Unk1", &unk1); - ImGui::InputInt("Unk2", &unk2); + static bool special_exit = false; + ImGui::Checkbox("Special exit", &special_exit); + if (special_exit) { + gui::InputHex("Center X", ¢erX); - ImGui::InputInt("Link's posture", &linkPosture); - ImGui::InputInt("Sprite GFX", &spriteGFX); - ImGui::InputInt("BG GFX", &bgGFX); - ImGui::InputInt("Palette", &palette); - ImGui::InputInt("Spr Pal", &sprPal); + gui::InputHex("Center Y", ¢erY); + gui::InputHex("Unk1", &unk1); + gui::InputHex("Unk2", &unk2); - ImGui::InputInt("Top", &top); - ImGui::InputInt("Bottom", &bottom); - ImGui::InputInt("Left", &left); - ImGui::InputInt("Right", &right); + gui::InputHex("Link's posture", &linkPosture); + gui::InputHex("Sprite GFX", &spriteGFX); + gui::InputHex("BG GFX", &bgGFX); + gui::InputHex("Palette", &palette); + gui::InputHex("Spr Pal", &sprPal); - ImGui::InputInt("Left edge of map", &leftEdgeOfMap); + gui::InputHex("Top", &top); + gui::InputHex("Bottom", &bottom); + gui::InputHex("Left", &left); + gui::InputHex("Right", &right); - if (ImGui::Button("OK")) { - // Implement what happens when OK is pressed + gui::InputHex("Left edge of map", &leftEdgeOfMap); + } + + if (ImGui::Button(ICON_MD_DONE)) { ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - if (ImGui::Button("Cancel")) { - // Implement what happens when Cancel is pressed + if (ImGui::Button(ICON_MD_CANCEL)) { + set_done = true; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_DELETE)) { + exit.deleted = true; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } + + return set_done; } -} // namespace + +} // namespace exit_internal void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) { int i = 0; - for (auto &each : *overworld_.exits()) { + for (auto &each : *overworld_.mutable_exits()) { if (each.map_id_ < 0x40 + (current_world_ * 0x40) && - each.map_id_ >= (current_world_ * 0x40)) { + each.map_id_ >= (current_world_ * 0x40) && !each.deleted) { ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, ImVec4(255, 255, 255, 150)); - std::string str = absl::StrFormat("%#x", each.entrance_id_); - ow_map_canvas_.DrawText(str, each.x_ - 4, each.y_ - 2); + if (current_mode == EditingMode::EXITS) { + each.entity_id_ = i; + entity_internal::HandleEntityDragging( + &each, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(), + is_dragging_entity_, dragged_entity_, current_entity_, true); - // Check if this entrance is being clicked and dragged - if (IsMouseHoveringOverExit(each, canvas_p0, scrolling) && - ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - current_exit_ = i; - ImGui::OpenPopup("Exit editor"); + if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0, + scrolling) && + ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + jump_to_tab_ = each.room_id_; + } + + if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0, + scrolling) && + ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { + current_exit_id_ = i; + current_exit_ = each; + current_entity_ = &each; + current_entity_->entity_id_ = i; + ImGui::OpenPopup("Exit editor"); + } } + + std::string str = core::UppercaseHexByte(i); + ow_map_canvas_.DrawText(str, each.x_, each.y_); } i++; } - DrawExitEditorPopup(overworld_.mutable_exits()->at(current_exit_)); + if (exit_internal::DrawExitEditorPopup( + overworld_.mutable_exits()->at(current_exit_id_))) { + overworld_.mutable_exits()->at(current_exit_id_) = current_exit_; + } +} + +namespace item_internal { + +bool DrawItemEditorPopup(zelda3::OverworldItem &item) { + static bool set_done = false; + if (set_done) { + set_done = false; + } + if (ImGui::BeginPopupModal("Item editor", NULL, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::BeginChild("ScrollRegion", ImVec2(0, 150), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar); + ImGui::BeginGroup(); + for (int i = 0; i < zelda3::kSecretItemNames.size(); i++) { + if (ImGui::Selectable(zelda3::kSecretItemNames[i].c_str(), + item.id == i)) { + item.id = i; + } + } + ImGui::EndGroup(); + ImGui::EndChild(); + + if (ImGui::Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_CLOSE)) { + set_done = true; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_DELETE)) { + item.deleted = true; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + return set_done; +} + +} // namespace item_internal + +void OverworldEditor::DrawOverworldItems() { + int i = 0; + for (auto &item : *overworld_.mutable_all_items()) { + // Get the item's bitmap and real X and Y positions + if (item.room_map_id < 0x40 + (current_world_ * 0x40) && + item.room_map_id >= (current_world_ * 0x40) && !item.deleted) { + std::string item_name = zelda3::kSecretItemNames[item.id]; + + ow_map_canvas_.DrawRect(item.x_, item.y_, 16, 16, ImVec4(255, 0, 0, 150)); + if (current_mode == EditingMode::ITEMS) { + if (entity_internal::IsMouseHoveringOverEntity( + item, ow_map_canvas_.zero_point(), + ow_map_canvas_.scrolling()) && + ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { + current_item_id_ = i; + current_item_ = item; + } + + // Check if this item is being clicked and dragged + entity_internal::HandleEntityDragging( + &item, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(), + is_dragging_entity_, dragged_entity_, current_entity_); + } + ow_map_canvas_.DrawText(item_name, item.x_, item.y_); + } + i++; + } + + if (item_internal::DrawItemEditorPopup( + overworld_.mutable_all_items()->at(current_item_id_))) { + overworld_.mutable_all_items()->at(current_item_id_) = current_item_; + } } // ---------------------------------------------------------------------------- void OverworldEditor::DrawOverworldSprites() { - for (const auto &sprite : overworld_.Sprites(game_state_)) { - // Get the sprite's bitmap and real X and Y positions - auto id = sprite.id(); - const gfx::Bitmap &sprite_bitmap = sprite_previews_[id]; - int realX = sprite.GetRealX(); - int realY = sprite.GetRealY(); + for (auto &sprite : *overworld_.mutable_sprites(game_state_)) { + int map_id = sprite.map_id(); + int map_x = sprite.map_x(); + int map_y = sprite.map_y(); - // Draw the sprite's bitmap onto the canvas at its real X and Y positions - ow_map_canvas_.DrawBitmap(sprite_bitmap, realX, realY); - ow_map_canvas_.DrawRect(realX, realY, sprite.Width(), sprite.Height(), - ImVec4(255, 0, 0, 150)); - std::string str = absl::StrFormat("%s", sprite.Name()); - ow_map_canvas_.DrawText(str, realX - 4, realY - 2); + if (map_id < 0x40 + (current_world_ * 0x40) && + map_id >= (current_world_ * 0x40) && !sprite.deleted()) { + // auto globalx = (((map_id & 0x7) * 512) + (map_x * 16)); + // auto globaly = (((map_id & 0x3F) / 8 * 512) + (map_y * 16)); + + ow_map_canvas_.DrawRect(map_x, map_y, 16, 16, + /*magenta*/ ImVec4(255, 0, 255, 150)); + ow_map_canvas_.DrawText(absl::StrFormat("%s", sprite.Name()), map_x, + map_y); + } } } @@ -956,7 +998,7 @@ void OverworldEditor::CheckForOverworldEdits() { } void OverworldEditor::CheckForCurrentMap() { - // 4096x4096, 512x512 maps and some are larges maps 1024x1024 + // 4096x4096, 512x512 maps and some are larges maps 1024x1024 auto mouse_position = ImGui::GetIO().MousePos; constexpr int small_map_size = 512; auto large_map_size = 1024; @@ -1008,9 +1050,8 @@ void OverworldEditor::DrawOverworldCanvas() { DrawOverworldEntrances(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling()); DrawOverworldExits(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling()); - if (flags()->overworld.kDrawOverworldSprites) { - DrawOverworldSprites(); - } + DrawOverworldItems(); + DrawOverworldSprites(); if (ImGui::IsItemHovered()) CheckForCurrentMap(); CheckForOverworldEdits(); } @@ -1187,6 +1228,35 @@ absl::Status OverworldEditor::LoadSpriteGraphics() { return absl::OkStatus(); } +void OverworldEditor::DrawOverworldProperties() { + static bool init_properties = false; + + if (!init_properties) { + for (int i = 0; i < 0x40; i++) { + std::string area_graphics_str = absl::StrFormat( + + "0x%02hX", overworld_.overworld_map(i)->area_graphics()); + properties_canvas_.mutable_labels(0)->push_back(area_graphics_str); + } + for (int i = 0; i < 0x40; i++) { + std::string area_palette_str = absl::StrFormat( + "0x%02hX", overworld_.overworld_map(i)->area_palette()); + properties_canvas_.mutable_labels(1)->push_back(area_palette_str); + } + init_properties = true; + } + + if (ImGui::Button("Area Graphics")) { + properties_canvas_.set_current_labels(0); + } + + if (ImGui::Button("Area Palette")) { + properties_canvas_.set_current_labels(1); + } + + properties_canvas_.UpdateInfoGrid(ImVec2(512, 512), 16, 1.0f, 64); +} + absl::Status OverworldEditor::DrawExperimentalModal() { ImGui::Begin("Experimental", &show_experimental); @@ -1236,6 +1306,127 @@ absl::Status OverworldEditor::DrawExperimentalModal() { return absl::OkStatus(); } +absl::Status OverworldEditor::UpdateUsageStats() { + if (BeginTable("##UsageStatsTable", 3, kOWEditFlags, ImVec2(0, 0))) { + TableSetupColumn("Entrances"); + TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch, + ImGui::GetContentRegionAvail().x); + TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 256); + TableHeadersRow(); + TableNextRow(); + + TableNextColumn(); + ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar); + for (int i = 0; i < 0x81; i++) { + std::string str = absl::StrFormat("%#x", i); + if (ImGui::Selectable(str.c_str(), selected_entrance_ == i, + overworld_.Entrances().at(i).deleted + ? ImGuiSelectableFlags_Disabled + : 0)) { + selected_entrance_ = i; + selected_usage_map_ = overworld_.Entrances().at(i).map_id_; + properties_canvas_.set_highlight_tile_id(selected_usage_map_); + } + } + ImGui::EndChild(); + + TableNextColumn(); + DrawUsageGrid(); + TableNextColumn(); + DrawOverworldProperties(); + ImGui::EndTable(); + } + return absl::OkStatus(); +} + +void OverworldEditor::CalculateUsageStats() { + absl::flat_hash_map entrance_usage; + for (auto each_entrance : overworld_.Entrances()) { + if (each_entrance.map_id_ < 0x40 + (current_world_ * 0x40) && + each_entrance.map_id_ >= (current_world_ * 0x40)) { + entrance_usage[each_entrance.entrance_id_]++; + } + } +} + +void OverworldEditor::DrawUsageGrid() { + // Create a grid of 8x8 squares + int totalSquares = 128; + int squaresWide = 8; + int squaresTall = (totalSquares + squaresWide - 1) / + squaresWide; // Ceiling of totalSquares/squaresWide + + // Loop through each row + for (int row = 0; row < squaresTall; ++row) { + ImGui::NewLine(); + + for (int col = 0; col < squaresWide; ++col) { + if (row * squaresWide + col >= totalSquares) { + break; + } + // Determine if this square should be highlighted + bool highlight = selected_usage_map_ == (row * squaresWide + col); + + // Set highlight color if needed + if (highlight) { + ImGui::PushStyleColor( + ImGuiCol_Button, + ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color + } + + // Create a button or selectable for each square + if (ImGui::Button("##square", ImVec2(20, 20))) { + // Switch over to the room editor tab + // and add a room tab by the ID of the square + // that was clicked + } + + // Reset style if it was highlighted + if (highlight) { + ImGui::PopStyleColor(); + } + + // Check if the square is hovered + if (ImGui::IsItemHovered()) { + // Display a tooltip with all the room properties + } + + // Keep squares in the same line + ImGui::SameLine(); + } + } +} + +void OverworldEditor::LoadAnimatedMaps() { + int world_index = 0; + static std::vector animated_built(0x40, false); + if (!animated_built[world_index]) { + animated_maps_[world_index] = maps_bmp_[world_index]; + auto &map = *overworld_.mutable_overworld_map(world_index); + map.DrawAnimatedTiles(); + map.BuildTileset(); + map.BuildTiles16Gfx(overworld_.tiles16().size()); + OWBlockset blockset; + if (current_world_ == 0) { + blockset = overworld_.map_tiles().light_world; + } else if (current_world_ == 1) { + blockset = overworld_.map_tiles().dark_world; + } else { + blockset = overworld_.map_tiles().special_world; + } + map.BuildBitmap(blockset); + + gui::BuildAndRenderBitmapPipeline(0x200, 0x200, 0x200, map.BitmapData(), + *rom(), animated_maps_[world_index], + *map.mutable_current_palette()); + + animated_built[world_index] = true; + } +} + +// ---------------------------------------------------------------------------- + void OverworldEditor::DrawDebugWindow() { ImGui::Text("Current Map: %d", current_map_); ImGui::Text("Current Tile16: %d", current_tile16_); diff --git a/src/app/editor/overworld_editor.h b/src/app/editor/overworld_editor.h index 1480bc5b..1a869b24 100644 --- a/src/app/editor/overworld_editor.h +++ b/src/app/editor/overworld_editor.h @@ -71,6 +71,9 @@ class OverworldEditor : public Editor, auto overworld() { return &overworld_; } + int jump_to_tab() { return jump_to_tab_; } + int jump_to_tab_ = -1; + void Shutdown() { for (auto &bmp : tile16_individual_) { bmp.Cleanup(); @@ -90,10 +93,6 @@ class OverworldEditor : public Editor, private: absl::Status UpdateOverworldEdit(); - absl::Status UpdateUsageStats(); - - void DrawUsageGrid(); - void CalculateUsageStats(); absl::Status DrawToolset(); void DrawOverworldMapSettings(); @@ -103,17 +102,16 @@ class OverworldEditor : public Editor, void RefreshMapPalette(); void RefreshMapProperties(); - void DrawOverworldProperties(); - - void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling); + void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling, + bool holes = false); void DrawOverworldExits(ImVec2 zero, ImVec2 scrolling); - void DrawOverworldMaps(); + void DrawOverworldItems(); void DrawOverworldSprites(); + void DrawOverworldMaps(); void DrawOverworldEdits(); void RenderUpdatedMapBitmap(const ImVec2 &click_position, const Bytes &tile_data); - void CheckForOverworldEdits(); void CheckForCurrentMap(); void DrawOverworldCanvas(); @@ -124,8 +122,16 @@ class OverworldEditor : public Editor, void DrawTileSelector(); absl::Status LoadSpriteGraphics(); + + void DrawOverworldProperties(); + absl::Status DrawExperimentalModal(); + absl::Status UpdateUsageStats(); + void DrawUsageGrid(); + void CalculateUsageStats(); + + void LoadAnimatedMaps(); void DrawDebugWindow(); auto gfx_group_editor() const { return gfx_group_editor_; } @@ -149,7 +155,6 @@ class OverworldEditor : public Editor, int current_tile16_ = 0; int selected_tile_ = 0; int game_state_ = 0; - int current_exit_ = 0; int selected_entrance_ = 0; int selected_usage_map_ = 0xFFFF; @@ -178,10 +183,16 @@ class OverworldEditor : public Editor, bool overworld_canvas_fullscreen_ = false; bool middle_mouse_dragging_ = false; - bool IsMouseHoveringOverEntrance(const zelda3::OverworldEntrance &entrance, - ImVec2 canvas_p, ImVec2 scrolling); - zelda3::OverworldEntrance *dragged_entrance_; - zelda3::OverworldEntrance *current_entrance_; + bool is_dragging_entity_ = false; + zelda3::OverworldEntity *dragged_entity_; + zelda3::OverworldEntity *current_entity_; + + int current_entrance_id_ = 0; + zelda3::OverworldEntrance current_entrance_; + int current_exit_id_ = 0; + zelda3::OverworldExit current_exit_; + int current_item_id_ = 0; + zelda3::OverworldItem current_item_; bool show_experimental = false; std::string ow_tilemap_filename_ = ""; @@ -221,6 +232,8 @@ class OverworldEditor : public Editor, gfx::BitmapTable current_graphics_set_; gfx::BitmapTable sprite_previews_; + gfx::BitmapTable animated_maps_; + absl::Status status_; }; } // namespace editor diff --git a/src/app/gui/canvas.cc b/src/app/gui/canvas.cc index 952a575f..e6326d66 100644 --- a/src/app/gui/canvas.cc +++ b/src/app/gui/canvas.cc @@ -87,9 +87,8 @@ void Canvas::DrawBackground(ImVec2 canvas_size, bool can_drag) { // Pan (we use a zero mouse threshold when there's no context menu) if (const float mouse_threshold_for_pan = enable_context_menu_ ? -1.0f : 0.0f; - is_active && - ImGui::IsMouseDragging(ImGuiMouseButton_Right, - mouse_threshold_for_pan)) { + is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, + mouse_threshold_for_pan)) { scrolling_.x += io.MouseDelta.x; scrolling_.y += io.MouseDelta.y; } @@ -459,9 +458,16 @@ void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) { canvas_p0_.y + scrolling_.y + y + h); draw_list_->AddRectFilled(origin, size, IM_COL32(color.x, color.y, color.z, color.w)); + // Add a black outline + ImVec2 outline_origin(origin.x - 1, origin.y - 1); + ImVec2 outline_size(size.x + 1, size.y + 1); + draw_list_->AddRect(outline_origin, outline_size, IM_COL32(0, 0, 0, 255)); } void Canvas::DrawText(std::string text, int x, int y) { + draw_list_->AddText(ImVec2(canvas_p0_.x + scrolling_.x + x + 1, + canvas_p0_.y + scrolling_.y + y + 1), + IM_COL32(0, 0, 0, 255), text.data()); draw_list_->AddText( ImVec2(canvas_p0_.x + scrolling_.x + x, canvas_p0_.y + scrolling_.y + y), IM_COL32(255, 255, 255, 255), text.data()); diff --git a/src/app/gui/input.cc b/src/app/gui/input.cc index 3700b9b3..63cfdb72 100644 --- a/src/app/gui/input.cc +++ b/src/app/gui/input.cc @@ -135,6 +135,13 @@ bool InputHex(const char* label, uint64_t* data) { ImGuiInputTextFlags_CharsHexadecimal); } +bool InputHex(const char* label, int* data, int num_digits, float input_width) { + const std::string format = "%0" + std::to_string(num_digits) + "X"; + return ImGui::InputScalarLeft(label, ImGuiDataType_S32, data, &kStepOneHex, + &kStepFastHex, format.c_str(), input_width, + ImGuiInputTextFlags_CharsHexadecimal); +} + bool InputHexShort(const char* label, uint32_t* data) { return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex, &kStepFastHex, "%06X", diff --git a/src/app/gui/input.h b/src/app/gui/input.h index f3896791..5466adcd 100644 --- a/src/app/gui/input.h +++ b/src/app/gui/input.h @@ -21,6 +21,8 @@ IMGUI_API bool InputHexWithScrollwheel(const char* label, uint32_t* data, float input_width = 50.f); IMGUI_API bool InputHex(const char* label, uint64_t* data); +IMGUI_API bool InputHex(const char* label, int* data, int num_digits = 4, + float input_width = 50.f); IMGUI_API bool InputHexShort(const char* label, uint32_t* data); IMGUI_API bool InputHexWord(const char* label, uint16_t* data, float input_width = 50.f); diff --git a/src/app/rom.cc b/src/app/rom.cc index 059da09d..b273333d 100644 --- a/src/app/rom.cc +++ b/src/app/rom.cc @@ -378,6 +378,7 @@ absl::Status ROM::LoadFromBytes(const Bytes& data) { } absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) { + absl::Status non_firing_status; if (rom_data_.empty()) { return absl::InternalError("ROM data is empty."); } @@ -404,8 +405,13 @@ absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) { std::replace(backup_filename.begin(), backup_filename.end(), ' ', '_'); // Now, copy the original file to the backup file - std::filesystem::copy(filename, backup_filename, - std::filesystem::copy_options::overwrite_existing); + try { + std::filesystem::copy(filename, backup_filename, + std::filesystem::copy_options::overwrite_existing); + } catch (const std::filesystem::filesystem_error& e) { + non_firing_status = absl::InternalError(absl::StrCat( + "Could not create backup file: ", backup_filename, " - ", e.what())); + } } // Run the other save functions @@ -444,6 +450,10 @@ absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) { absl::StrCat("Error while writing to ROM file: ", filename)); } + if (!non_firing_status.ok()) { + return non_firing_status; + } + return absl::OkStatus(); } diff --git a/src/app/rom.h b/src/app/rom.h index 3a3e33c9..2f2af634 100644 --- a/src/app/rom.h +++ b/src/app/rom.h @@ -417,6 +417,15 @@ class ROM : public core::ExperimentFlags { return absl::OkStatus(); } + absl::Status WriteWord(int addr, uint16_t value) { + if (addr + 1 >= rom_data_.size()) { + return absl::InvalidArgumentError("Address out of range"); + } + rom_data_[addr] = (uint8_t)(value & 0xFF); + rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF); + return absl::OkStatus(); + } + absl::Status WriteShort(uint32_t addr, uint16_t value) { if (addr + 1 >= rom_data_.size()) { return absl::InvalidArgumentError("Address out of range"); @@ -426,6 +435,16 @@ class ROM : public core::ExperimentFlags { return absl::OkStatus(); } + absl::Status WriteLong(uint32_t addr, uint32_t value) { + if (addr + 2 >= rom_data_.size()) { + return absl::InvalidArgumentError("Address out of range"); + } + rom_data_[addr] = (uint8_t)(value & 0xFF); + rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF); + rom_data_[addr + 2] = (uint8_t)((value >> 16) & 0xFF); + return absl::OkStatus(); + } + absl::Status WriteVector(int addr, std::vector data) { if (addr + data.size() > rom_data_.size()) { return absl::InvalidArgumentError("Address and data size out of range"); @@ -532,7 +551,8 @@ class ROM : public core::ExperimentFlags { const uchar* operator&() { return rom_data_.data(); } ushort toint16(int offset) { - return (ushort)((rom_data_[offset + 1]) << 8) | rom_data_[offset]; + return (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8)); + // return (ushort)((rom_data_[offset + 1]) << 8) | rom_data_[offset]; } void SetupRenderer(std::shared_ptr renderer) { diff --git a/src/app/zelda3/overworld.cc b/src/app/zelda3/overworld.cc index 46d4a5ff..07955449 100644 --- a/src/app/zelda3/overworld.cc +++ b/src/app/zelda3/overworld.cc @@ -141,10 +141,9 @@ absl::Status Overworld::Load(ROM &rom) { FetchLargeMaps(); LoadEntrances(); LoadExits(); + LoadSprites(); + RETURN_IF_ERROR(LoadItems()); RETURN_IF_ERROR(LoadOverworldMaps()) - if (flags()->overworld.kDrawOverworldSprites) { - LoadSprites(); - } is_loaded_ = true; return absl::OkStatus(); @@ -213,10 +212,10 @@ absl::Status Overworld::SaveOverworldMaps() { } // Compress single_map_1 and single_map_2 - ASSIGN_OR_RETURN( - auto a, gfx::lc_lz2::CompressOverworld(single_map_1, 0, 256)) - ASSIGN_OR_RETURN( - auto b, gfx::lc_lz2::CompressOverworld(single_map_2, 0, 256)) + ASSIGN_OR_RETURN(auto a, + gfx::lc_lz2::CompressOverworld(single_map_1, 0, 256)) + ASSIGN_OR_RETURN(auto b, + gfx::lc_lz2::CompressOverworld(single_map_2, 0, 256)) if (a.empty() || b.empty()) { return absl::AbortedError("Error compressing map gfx."); } @@ -557,13 +556,17 @@ absl::Status Overworld::SaveMap16Tiles() { int tpos = kMap16Tiles; // 3760 for (int i = 0; i < NumberOfMap16; i += 1) { - RETURN_IF_ERROR(rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_))) + RETURN_IF_ERROR( + rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_))) tpos += 2; - RETURN_IF_ERROR(rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_))) + RETURN_IF_ERROR( + rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_))) tpos += 2; - RETURN_IF_ERROR(rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_))) + RETURN_IF_ERROR( + rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_))) tpos += 2; - RETURN_IF_ERROR(rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_))) + RETURN_IF_ERROR( + rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_))) tpos += 2; } return absl::OkStatus(); @@ -936,6 +939,85 @@ absl::Status Overworld::SaveExits() { return absl::OkStatus(); } +absl::Status Overworld::SaveItems() { + std::vector> roomItems(128); + + for (int i = 0; i < 128; i++) { + roomItems[i] = std::vector(); + for (const OverworldItem &item : all_items_) { + if (item.room_map_id == i) { + roomItems[i].push_back(item); + if (item.id == 0x86) { + rom()->WriteWord(0x16DC5 + (i * 2), + (item.game_x_ + (item.game_y_ * 64)) * 2); + } + } + } + } + + int dataPos = overworldItemsPointers + 0x100; + + std::vector itemPointers(128); + std::vector itemPointersReuse(128); + int emptyPointer = 0; + + for (int i = 0; i < 128; i++) { + itemPointersReuse[i] = -1; + for (int ci = 0; ci < i; ci++) { + if (roomItems[i].empty()) { + itemPointersReuse[i] = -2; + break; + } + // Unclear: this.compareItemsArrays(roomItems[i].ToArray(), + // roomItems[ci].ToArray()) Commenting out for now if + // (this.compareItemsArrays(roomItems[i].ToArray(), + // roomItems[ci].ToArray())) { + // itemPointersReuse[i] = ci; + // break; + // } + } + } + + for (int i = 0; i < 128; i++) { + if (itemPointersReuse[i] == -1) { + itemPointers[i] = dataPos; + for (const OverworldItem &item : roomItems[i]) { + short mapPos = + static_cast(((item.game_y_ << 6) + item.game_x_) << 1); + + uint32_t data = static_cast(mapPos & 0xFF) | + static_cast(mapPos >> 8) | + static_cast(item.id); + rom()->WriteLong(dataPos, data); + // WriteType::PotItemData); + + dataPos += 3; + } + + emptyPointer = dataPos; + rom()->WriteWord(dataPos, 0xFFFF); + dataPos += 2; + } else if (itemPointersReuse[i] == -2) { + itemPointers[i] = emptyPointer; + } else { + itemPointers[i] = itemPointers[itemPointersReuse[i]]; + } + + int snesaddr = core::PcToSnes(itemPointers[i]); + rom()->WriteWord(overworldItemsPointers + (i * 2), snesaddr); + } + + if (dataPos > overworldItemsEndData) { + return absl::AbortedError("Too many items"); + } + + if (flags()->kLogToConsole) { + std::cout << "End of Items : " << dataPos << std::endl; + } + + return absl::OkStatus(); +} + void Overworld::LoadExits() { const int NumberOfOverworldExits = 0x4F; std::vector exits; @@ -987,7 +1069,7 @@ void Overworld::LoadExits() { << " DoorType2: " << exit_door_type_2 << std::endl; } - if (px == 0xFFFF && py == 0xFFFF) { + if ((px & py) == 0xFFFF) { exit.deleted = true; } @@ -996,6 +1078,55 @@ void Overworld::LoadExits() { all_exits_ = exits; } +absl::Status Overworld::LoadItems() { + ASSIGN_OR_RETURN(int pointer, rom()->ReadLong(zelda3::overworldItemsAddress)); + int oointerPC = core::SnesToPc(pointer); // 1BC2F9 -> 0DC2F9 + for (int i = 0; i < 128; i++) { + int addr = (pointer & 0xFF0000) + // 1B + (rom()->data()[oointerPC + (i * 2) + 1] << 8) + // F9 + rom()->data()[oointerPC + (i * 2)]; // 3C + + addr = core::SnesToPc(addr); + + if (overworld_maps_[i].IsLargeMap()) { + if (overworld_maps_[i].Parent() != (uint8_t)i) { + continue; + } + } + + while (true) { + uint8_t b1 = rom()->data()[addr]; + uint8_t b2 = rom()->data()[addr + 1]; + uint8_t b3 = rom()->data()[addr + 2]; + + if (b1 == 0xFF && b2 == 0xFF) { + break; + } + + int p = (((b2 & 0x1F) << 8) + b1) >> 1; + + int x = p % 64; + int y = p >> 6; + + int fakeID = i; + if (fakeID >= 64) { + fakeID -= 64; + } + + int sy = fakeID / 8; + int sx = fakeID - (sy * 8); + + all_items_.emplace_back(zelda3::OverworldItem( + b3, (ushort)i, (x * 16) + (sx * 512), (y * 16) + (sy * 512), false)); + auto size = all_items_.size(); + all_items_.at(size - 1).game_x = (uint8_t)x; + all_items_.at(size - 1).game_y = (uint8_t)y; + addr += 3; + } + } + return absl::OkStatus(); +} + void Overworld::LoadSprites() { for (int i = 0; i < 3; i++) { all_sprites_.emplace_back(); @@ -1025,7 +1156,7 @@ void Overworld::LoadSpritesFromMap(int sprite_start, int sprite_count, int ptrPos = sprite_start + (i * 2); int sprite_address = - core::SnesToPc((0x09 << 0x10) + rom()->toint16(ptrPos)); + core::SnesToPc((0x09 << 0x10) | rom()->toint16(ptrPos)); while (true) { uchar b1 = rom_[sprite_address]; uchar b2 = rom_[sprite_address + 1]; @@ -1033,20 +1164,20 @@ void Overworld::LoadSpritesFromMap(int sprite_start, int sprite_count, if (b1 == 0xFF) break; int editor_map_index = i; - if (editor_map_index >= 128) - editor_map_index -= 128; - else if (editor_map_index >= 64) - editor_map_index -= 64; - + if (sprite_index != 0) { + if (editor_map_index >= 128) + editor_map_index -= 128; + else if (editor_map_index >= 64) + editor_map_index -= 64; + } int mapY = (editor_map_index / 8); int mapX = (editor_map_index % 8); int realX = ((b2 & 0x3F) * 16) + mapX * 512; int realY = ((b1 & 0x3F) * 16) + mapY * 512; - auto graphics_bytes = overworld_maps_[i].AreaGraphics(); all_sprites_[sprite_index][i].InitSprite( - graphics_bytes, (uchar)i, b3, (uchar)(b2 & 0x3F), (uchar)(b1 & 0x3F), - realX, realY); + overworld_maps_[i].AreaGraphics(), (uchar)i, b3, (uchar)(b2 & 0x3F), + (uchar)(b1 & 0x3F), realX, realY); all_sprites_[sprite_index][i].Draw(); sprite_address += 3; @@ -1133,8 +1264,6 @@ absl::Status Overworld::LoadPrototype(ROM &rom, } } - // LoadSprites(); - is_loaded_ = true; return absl::OkStatus(); } diff --git a/src/app/zelda3/overworld.h b/src/app/zelda3/overworld.h index 2c6311e1..b51cb309 100644 --- a/src/app/zelda3/overworld.h +++ b/src/app/zelda3/overworld.h @@ -21,6 +21,143 @@ namespace yaze { namespace app { namespace zelda3 { +class OverworldEntity { + public: + enum EntityType { + kEntrance = 0, + kExit = 1, + kItem = 2, + kSprite = 3, + kTransport = 4, + kMusic = 5, + kTilemap = 6, + kProperties = 7 + } type_; + int x_; + int y_; + int game_x_; + int game_y_; + int entity_id_; + int map_id_; + + auto set_x(int x) { x_ = x; } + auto set_y(int y) { y_ = y; } + + OverworldEntity() = default; + + virtual void UpdateMapProperties(short map_id) = 0; +}; + +// List of secret item names +const std::vector kSecretItemNames = { + "Nothing", // 0 + "Green Rupee", // 1 + "Rock hoarder", // 2 + "Bee", // 3 + "Health pack", // 4 + "Bomb", // 5 + "Heart ", // 6 + "Blue Rupee", // 7 + "Key", // 8 + "Arrow", // 9 + "Bomb", // 10 + "Heart", // 11 + "Magic", // 12 + "Full Magic", // 13 + "Cucco", // 14 + "Green Soldier", // 15 + "Bush Stal", // 16 + "Blue Soldier", // 17 + "Landmine", // 18 + "Heart", // 19 + "Fairy", // 20 + "Heart", // 21 + "Nothing ", // 22 + "Hole", // 23 + "Warp", // 24 + "Staircase", // 25 + "Bombable", // 26 + "Switch" // 27 +}; + +constexpr int overworldItemsPointers = 0xDC2F9; +constexpr int overworldItemsAddress = 0xDC8B9; // 1BC2F9 +constexpr int overworldItemsBank = 0xDC8BF; +constexpr int overworldItemsEndData = 0xDC89C; // 0DC89E + +class OverworldItem : public OverworldEntity { + public: + bool bg2 = false; + uint8_t game_x; + uint8_t game_y; + uint8_t id; + uint16_t room_map_id; + int unique_id = 0; + bool deleted = false; + OverworldItem() = default; + + /// + /// Initializes a new instance of the + /// class. + /// + /// The ID. + /// The dungeon room ID or overworld area ID. + /// The in editor X position. The in editor Y position. Whether + /// the Item is on BG2 or not. + OverworldItem(uint8_t id, uint16_t room_map_id, int x, int y, bool bg2) { + this->id = id; + this->x_ = x; + this->y_ = y; + this->bg2 = bg2; + this->room_map_id = room_map_id; + this->map_id_ = room_map_id; + this->entity_id_ = id; + this->type_ = kItem; + + int map_x = room_map_id - ((room_map_id / 8) * 8); + int map_y = room_map_id / 8; + + this->game_x = static_cast(std::abs(x - (map_x * 512)) / 16); + this->game_y = static_cast(std::abs(y - (map_y * 512)) / 16); + // this->unique_id = ROM.unique_item_id++; + } + + /// + /// Updates the item info when needed. Generally when moving items around + /// in editor. + /// + /// The dungeon room ID or overworld area ID where + /// the item was moved to. + void UpdateMapProperties(int16_t room_map_id) override { + this->room_map_id = static_cast(room_map_id); + + if (room_map_id >= 64) { + room_map_id -= 64; + } + + int map_x = room_map_id - ((room_map_id / 8) * 8); + int map_y = room_map_id / 8; + + this->game_x = + static_cast(std::abs(this->x_ - (map_x * 512)) / 16); + this->game_y = + static_cast(std::abs(this->y_ - (map_y * 512)) / 16); + + std::cout << "Item: " << std::hex << std::setw(2) << std::setfill('0') + << static_cast(this->id) << " MapId: " << std::hex + << std::setw(2) << std::setfill('0') + << static_cast(this->room_map_id) + << " X: " << static_cast(this->game_x) + << " Y: " << static_cast(this->game_y) << std::endl; + } + + OverworldItem Copy() { + return OverworldItem(this->id, this->room_map_id, this->x_, this->y_, + this->bg2); + } +}; + constexpr int OWExitRoomId = 0x15D8A; // 0x15E07 Credits sequences // 105C2 Ending maps // 105E2 Sprite Group Table for Ending @@ -50,10 +187,8 @@ constexpr int OWExitUnk1Whirlpool = 0x16BF5; // JP = ;016E91 constexpr int OWExitUnk2Whirlpool = 0x16C17; // JP = ;016EB3 constexpr int OWWhirlpoolPosition = 0x16CF8; // JP = ;016F94 -class OverworldExit { +class OverworldExit : public OverworldEntity { public: - int x_; - int y_; ushort y_scroll_; ushort x_scroll_; uchar y_player_; @@ -69,23 +204,21 @@ class OverworldExit { uchar entrance_id_; uchar area_x_; uchar area_y_; - short map_id_; bool is_hole_ = false; bool deleted = false; bool is_automatic_ = false; + bool large_map_ = false; + OverworldExit() = default; OverworldExit(ushort room_id, uchar map_id, ushort vram_location, ushort y_scroll, ushort x_scroll, ushort player_y, ushort player_x, ushort camera_y, ushort camera_x, uchar scroll_mod_y, uchar scroll_mod_x, ushort door_type_1, ushort door_type_2) - : x_(player_x), - y_(player_y), - map_pos_(vram_location), + : map_pos_(vram_location), entrance_id_(0), area_x_(0), area_y_(0), - map_id_(map_id), is_hole_(false), room_id_(room_id), y_scroll_(y_scroll), @@ -98,6 +231,12 @@ class OverworldExit { scroll_mod_x_(scroll_mod_x), door_type_1_(door_type_1), door_type_2_(door_type_2) { + // Initialize entity variables + this->x_ = player_x; + this->y_ = player_y; + this->map_id_ = map_id; + this->type_ = kExit; + int mapX = (map_id_ - ((map_id_ / 8) * 8)); int mapY = (map_id_ / 8); @@ -130,14 +269,14 @@ class OverworldExit { } // Overworld overworld - void UpdateMapProperties(uchar map_id, bool large_map = false) { + void UpdateMapProperties(short map_id) override { map_id_ = map_id; int large = 256; int mapid = map_id; if (map_id < 128) { - large = large_map ? 768 : 256; + large = large_map_ ? 768 : 256; // if (overworld.overworld_map(map_id)->Parent() != map_id) { // mapid = overworld.overworld_map(map_id)->Parent(); // } @@ -223,29 +362,27 @@ constexpr int OWHoleArea = 0xDB826; //(0x13 entries, 1 byte each) corresponding entrance numbers constexpr int OWHoleEntrance = 0xDB84C; -class OverworldEntrance { +class OverworldEntrance : public OverworldEntity { public: - int x_; - int y_; ushort map_pos_; uchar entrance_id_; uchar area_x_; uchar area_y_; - short map_id_; bool is_hole_ = false; bool deleted = false; + OverworldEntrance() = default; OverworldEntrance(int x, int y, uchar entrance_id, short map_id, ushort map_pos, bool hole) - : x_(x), - y_(y), - map_pos_(map_pos), - entrance_id_(entrance_id), - map_id_(map_id), - is_hole_(hole) { + : map_pos_(map_pos), entrance_id_(entrance_id), is_hole_(hole) { + x_ = x; + y_ = y; + map_id_ = map_id; + entity_id_ = entrance_id; + type_ = kEntrance; + int mapX = (map_id_ - ((map_id_ / 8) * 8)); int mapY = (map_id_ / 8); - area_x_ = (uchar)((std::abs(x - (mapX * 512)) / 16)); area_y_ = (uchar)((std::abs(y - (mapY * 512)) / 16)); } @@ -255,7 +392,7 @@ class OverworldEntrance { is_hole_); } - void UpdateMapProperties(short map_id) { + void UpdateMapProperties(short map_id) override { map_id_ = map_id; if (map_id_ >= 64) { @@ -289,10 +426,6 @@ constexpr int overworldSpecialPALGroup = 0x16831; constexpr int overworldSpritesBegining = 0x4C881; constexpr int overworldSpritesAgahnim = 0x4CA21; constexpr int overworldSpritesZelda = 0x4C901; -constexpr int overworldItemsPointers = 0xDC2F9; -constexpr int overworldItemsAddress = 0xDC8B9; // 1BC2F9 -constexpr int overworldItemsBank = 0xDC8BF; -constexpr int overworldItemsEndData = 0xDC89C; // 0DC89E constexpr int mapGfx = 0x7C9C; constexpr int overlayPointers = 0x77664; constexpr int overlayPointersBank = 0x0E; @@ -353,6 +486,7 @@ class Overworld : public SharedROM, public core::ExperimentFlags { absl::Status SaveLargeMaps(); absl::Status SaveEntrances(); absl::Status SaveExits(); + absl::Status SaveItems(); bool CreateTile32Tilemap(bool onlyShow = false); absl::Status SaveMap16Tiles(); @@ -368,10 +502,14 @@ class Overworld : public SharedROM, public core::ExperimentFlags { std::vector tiles16() const { return tiles16_; } auto Sprites(int state) const { return all_sprites_[state]; } + auto mutable_sprites(int state) { return &all_sprites_[state]; } auto AreaGraphics() const { return overworld_maps_[current_map_].AreaGraphics(); } auto &Entrances() { return all_entrances_; } + auto mutable_entrances() { return &all_entrances_; } + auto &holes() { return all_holes_; } + auto mutable_holes() { return &all_holes_; } auto AreaPalette() const { return overworld_maps_[current_map_].AreaPalette(); } @@ -387,6 +525,9 @@ class Overworld : public SharedROM, public core::ExperimentFlags { auto map_tiles() const { return map_tiles_; } auto mutable_map_tiles() { return &map_tiles_; } + auto all_items() const { return all_items_; } + auto mutable_all_items() { return &all_items_; } + auto &ref_all_items() { return all_items_; } absl::Status LoadPrototype(ROM &rom_, const std::string &tilemap_filename); @@ -410,6 +551,7 @@ class Overworld : public SharedROM, public core::ExperimentFlags { void FetchLargeMaps(); void LoadEntrances(); void LoadExits(); + absl::Status LoadItems(); void LoadSprites(); void LoadSpritesFromMap(int spriteStart, int spriteCount, int spriteIndex); @@ -429,6 +571,7 @@ class Overworld : public SharedROM, public core::ExperimentFlags { std::vector all_entrances_; std::vector all_holes_; std::vector all_exits_; + std::vector all_items_; std::vector> all_sprites_; std::vector> usage_stats_; diff --git a/src/app/zelda3/overworld_map.cc b/src/app/zelda3/overworld_map.cc index 7c8e9c0f..dc481b0b 100644 --- a/src/app/zelda3/overworld_map.cc +++ b/src/app/zelda3/overworld_map.cc @@ -301,6 +301,37 @@ void OverworldMap::LoadMainBlocksets() { } } +// For animating water tiles on the overworld map. +// We want to swap out static_graphics_[07] with the next sheet +// Usually it is 5A, so we make it 5B instead. +// There is a middle frame which contains tiles from the bottom half +// of the 5A sheet, so this will need some special manipulation to make work +// during the BuildBitmap step (or a new one specifically for animating). +void OverworldMap::DrawAnimatedTiles() { + std::cout << "static_graphics_[6] = " + << core::UppercaseHexByte(static_graphics_[6]) << std::endl; + std::cout << "static_graphics_[7] = " + << core::UppercaseHexByte(static_graphics_[7]) << std::endl; + std::cout << "static_graphics_[8] = " + << core::UppercaseHexByte(static_graphics_[8]) << std::endl; + if (static_graphics_[7] == 0x5B) { + static_graphics_[7] = 0x5A; + } else { + if (static_graphics_[7] == 0x59) { + static_graphics_[7] = 0x58; + } + static_graphics_[7] = 0x5B; + } + // if (static_graphics_[7] == 0x5A) { + // static_graphics_[7] = 0x5B; + // } else { + // if (static_graphics_[7] == 0x58) { + // static_graphics_[7] = 0x59; + // } + // static_graphics_[7] = 0x5A; + // } +} + void OverworldMap::LoadAreaGraphicsBlocksets() { for (int i = 0; i < 4; i++) { uchar value = rom_[rom_.version_constants().kOverworldGfxGroups1 + diff --git a/src/app/zelda3/overworld_map.h b/src/app/zelda3/overworld_map.h index 80e4675b..dad32d22 100644 --- a/src/app/zelda3/overworld_map.h +++ b/src/app/zelda3/overworld_map.h @@ -36,6 +36,8 @@ class OverworldMap { absl::Status BuildTiles16Gfx(int count); absl::Status BuildBitmap(OWBlockset& world_blockset); + void DrawAnimatedTiles(); + auto Tile16Blockset() const { return current_blockset_; } auto AreaGraphics() const { return current_gfx_; } auto AreaPalette() const { return current_palette_; } diff --git a/src/app/zelda3/sprite/sprite.h b/src/app/zelda3/sprite/sprite.h index 81b876ad..62dde690 100644 --- a/src/app/zelda3/sprite/sprite.h +++ b/src/app/zelda3/sprite/sprite.h @@ -43,13 +43,18 @@ class Sprite { auto y() const { return y_; } auto nx() const { return nx_; } auto ny() const { return ny_; } + auto map_id() const { return map_id_; } + auto map_x() const { return map_x_; } + auto map_y() const { return map_y_; } + auto layer() const { return layer_; } auto subtype() const { return subtype_; } auto& keyDrop() const { return key_drop_; } auto Width() const { return bounding_box_.w; } auto Height() const { return bounding_box_.h; } - std::string Name() const { return name_; } + std::string& Name() { return name_; } + auto deleted() const { return deleted_; } private: Bytes current_gfx_; @@ -80,6 +85,8 @@ class Sprite { int height_ = 16; int key_drop_; + + bool deleted_ = false; }; } // namespace zelda3