feat(editor): enhance screen editor with title screen and inventory item icon features
- Implemented title screen editing capabilities, including loading and rendering of title screen layers. - Added inventory item icon management, allowing for the display and selection of item icons within the inventory menu. - Updated the inventory creation process to ensure proper ROM loading and error handling. - Introduced new canvas components for title screen and inventory item icons, improving the user interface for editing. Benefits: - Enhances the functionality of the screen editor, providing users with tools to edit title screens and manage inventory icons effectively. - Improves user experience by ensuring robust error handling and visual feedback during inventory management.
This commit is contained in:
@@ -58,11 +58,19 @@ absl::Status ScreenEditor::Load() {
|
|||||||
zelda3::LoadDungeonMaps(*rom(), dungeon_map_labels_));
|
zelda3::LoadDungeonMaps(*rom(), dungeon_map_labels_));
|
||||||
RETURN_IF_ERROR(zelda3::LoadDungeonMapTile16(
|
RETURN_IF_ERROR(zelda3::LoadDungeonMapTile16(
|
||||||
tile16_blockset_, *rom(), rom()->graphics_buffer(), false));
|
tile16_blockset_, *rom(), rom()->graphics_buffer(), false));
|
||||||
// TODO: Load roomset gfx based on dungeon ID
|
|
||||||
|
// Load graphics sheets and apply dungeon palette
|
||||||
sheets_.try_emplace(0, gfx::Arena::Get().gfx_sheets()[212]);
|
sheets_.try_emplace(0, gfx::Arena::Get().gfx_sheets()[212]);
|
||||||
sheets_.try_emplace(1, gfx::Arena::Get().gfx_sheets()[213]);
|
sheets_.try_emplace(1, gfx::Arena::Get().gfx_sheets()[213]);
|
||||||
sheets_.try_emplace(2, gfx::Arena::Get().gfx_sheets()[214]);
|
sheets_.try_emplace(2, gfx::Arena::Get().gfx_sheets()[214]);
|
||||||
sheets_.try_emplace(3, gfx::Arena::Get().gfx_sheets()[215]);
|
sheets_.try_emplace(3, gfx::Arena::Get().gfx_sheets()[215]);
|
||||||
|
|
||||||
|
// Apply dungeon palette to all sheets
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
sheets_[i].SetPalette(*rom()->mutable_dungeon_palette(3));
|
||||||
|
gfx::Arena::Get().QueueTextureCommand(
|
||||||
|
gfx::Arena::TextureCommandType::CREATE, &sheets_[i]);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
int current_tile8 = 0;
|
int current_tile8 = 0;
|
||||||
int tile_data_offset = 0;
|
int tile_data_offset = 0;
|
||||||
@@ -103,24 +111,30 @@ absl::Status ScreenEditor::Update() {
|
|||||||
// Get visibility flags from card manager and pass to Begin()
|
// Get visibility flags from card manager and pass to Begin()
|
||||||
if (dungeon_maps_card.Begin(card_manager.GetVisibilityFlag("screen.dungeon_maps"))) {
|
if (dungeon_maps_card.Begin(card_manager.GetVisibilityFlag("screen.dungeon_maps"))) {
|
||||||
DrawDungeonMapsEditor();
|
DrawDungeonMapsEditor();
|
||||||
dungeon_maps_card.End();
|
|
||||||
}
|
}
|
||||||
|
dungeon_maps_card.End();
|
||||||
|
|
||||||
if (inventory_menu_card.Begin(card_manager.GetVisibilityFlag("screen.inventory_menu"))) {
|
if (inventory_menu_card.Begin(card_manager.GetVisibilityFlag("screen.inventory_menu"))) {
|
||||||
DrawInventoryMenuEditor();
|
DrawInventoryMenuEditor();
|
||||||
inventory_menu_card.End();
|
|
||||||
}
|
}
|
||||||
|
inventory_menu_card.End();
|
||||||
|
|
||||||
if (overworld_map_card.Begin(card_manager.GetVisibilityFlag("screen.overworld_map"))) {
|
if (overworld_map_card.Begin(card_manager.GetVisibilityFlag("screen.overworld_map"))) {
|
||||||
DrawOverworldMapEditor();
|
DrawOverworldMapEditor();
|
||||||
overworld_map_card.End();
|
|
||||||
}
|
}
|
||||||
|
overworld_map_card.End();
|
||||||
|
|
||||||
if (title_screen_card.Begin(card_manager.GetVisibilityFlag("screen.title_screen"))) {
|
if (title_screen_card.Begin(card_manager.GetVisibilityFlag("screen.title_screen"))) {
|
||||||
DrawTitleScreenEditor();
|
DrawTitleScreenEditor();
|
||||||
title_screen_card.End();
|
|
||||||
}
|
}
|
||||||
|
title_screen_card.End();
|
||||||
|
|
||||||
if (naming_screen_card.Begin(card_manager.GetVisibilityFlag("screen.naming_screen"))) {
|
if (naming_screen_card.Begin(card_manager.GetVisibilityFlag("screen.naming_screen"))) {
|
||||||
DrawNamingScreenEditor();
|
DrawNamingScreenEditor();
|
||||||
naming_screen_card.End();
|
|
||||||
}
|
}
|
||||||
|
naming_screen_card.End();
|
||||||
|
|
||||||
return status_;
|
return status_;
|
||||||
}
|
}
|
||||||
@@ -133,16 +147,23 @@ void ScreenEditor::DrawToolset() {
|
|||||||
void ScreenEditor::DrawInventoryMenuEditor() {
|
void ScreenEditor::DrawInventoryMenuEditor() {
|
||||||
static bool create = false;
|
static bool create = false;
|
||||||
if (!create && rom()->is_loaded()) {
|
if (!create && rom()->is_loaded()) {
|
||||||
status_ = inventory_.Create();
|
status_ = inventory_.Create(rom());
|
||||||
palette_ = inventory_.palette();
|
if (status_.ok()) {
|
||||||
create = true;
|
palette_ = inventory_.palette();
|
||||||
|
create = true;
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading inventory: %s",
|
||||||
|
status_.message().data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawInventoryToolset();
|
DrawInventoryToolset();
|
||||||
|
|
||||||
if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
|
if (ImGui::BeginTable("InventoryScreen", 4, ImGuiTableFlags_Resizable)) {
|
||||||
ImGui::TableSetupColumn("Canvas");
|
ImGui::TableSetupColumn("Canvas");
|
||||||
ImGui::TableSetupColumn("Tilesheet");
|
ImGui::TableSetupColumn("Tilesheet");
|
||||||
|
ImGui::TableSetupColumn("Item Icons");
|
||||||
ImGui::TableSetupColumn("Palette");
|
ImGui::TableSetupColumn("Palette");
|
||||||
ImGui::TableHeadersRow();
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
@@ -160,12 +181,22 @@ void ScreenEditor::DrawInventoryMenuEditor() {
|
|||||||
tilesheet_canvas_.DrawGrid(16.0f);
|
tilesheet_canvas_.DrawGrid(16.0f);
|
||||||
tilesheet_canvas_.DrawOverlay();
|
tilesheet_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
DrawInventoryItemIcons();
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
gui::DisplayPalette(palette_, create);
|
gui::DisplayPalette(palette_, create);
|
||||||
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// TODO(scawful): Future Oracle of Secrets menu editor integration
|
||||||
|
// - Full inventory screen layout editor
|
||||||
|
// - Item slot assignment and positioning
|
||||||
|
// - Heart container and magic meter editor
|
||||||
|
// - Equipment display customization
|
||||||
|
// - A/B button equipment quick-select editor
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenEditor::DrawInventoryToolset() {
|
void ScreenEditor::DrawInventoryToolset() {
|
||||||
@@ -213,6 +244,59 @@ void ScreenEditor::DrawInventoryToolset() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawInventoryItemIcons() {
|
||||||
|
if (ImGui::BeginChild("##ItemIconsList", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||||
|
ImGui::Text("Item Icons (2x2 tiles each)");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
auto& icons = inventory_.item_icons();
|
||||||
|
if (icons.empty()) {
|
||||||
|
ImGui::TextWrapped("No item icons loaded. Icons will be loaded when the "
|
||||||
|
"inventory is initialized.");
|
||||||
|
ImGui::EndChild();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display icons in a table format
|
||||||
|
if (ImGui::BeginTable("##IconsTable", 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
|
||||||
|
ImGui::TableSetupColumn("Icon Name");
|
||||||
|
ImGui::TableSetupColumn("Tile Data");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < icons.size(); i++) {
|
||||||
|
const auto& icon = icons[i];
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
|
// Display icon name with selectable row
|
||||||
|
if (ImGui::Selectable(icon.name.c_str(), false,
|
||||||
|
ImGuiSelectableFlags_SpanAllColumns)) {
|
||||||
|
// TODO: Select this icon for editing
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
// Display tile word data in hex format
|
||||||
|
ImGui::Text("TL:%04X TR:%04X", icon.tile_tl, icon.tile_tr);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text("BL:%04X BR:%04X", icon.tile_bl, icon.tile_br);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextWrapped(
|
||||||
|
"NOTE: Individual icon editing will be implemented in the future "
|
||||||
|
"Oracle of Secrets menu editor. Each icon is composed of 4 tile words "
|
||||||
|
"representing a 2x2 arrangement of 8x8 tiles in SNES tile format "
|
||||||
|
"(vhopppcc cccccccc).");
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
void ScreenEditor::DrawDungeonMapScreen(int i) {
|
void ScreenEditor::DrawDungeonMapScreen(int i) {
|
||||||
gfx::ScopedTimer timer("screen_editor_draw_dungeon_map_screen");
|
gfx::ScopedTimer timer("screen_editor_draw_dungeon_map_screen");
|
||||||
|
|
||||||
@@ -559,6 +643,135 @@ void ScreenEditor::LoadBinaryGfx() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ScreenEditor::DrawTitleScreenEditor() {
|
void ScreenEditor::DrawTitleScreenEditor() {
|
||||||
|
// Initialize title screen on first draw
|
||||||
|
if (!title_screen_loaded_ && rom()->is_loaded()) {
|
||||||
|
status_ = title_screen_.Create(rom());
|
||||||
|
if (!status_.ok()) {
|
||||||
|
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading title screen: %s",
|
||||||
|
status_.message().data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
title_screen_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!title_screen_loaded_) {
|
||||||
|
ImGui::Text("Title screen not loaded. Ensure ROM is loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toolbar with mode controls
|
||||||
|
if (ImGui::Button(ICON_MD_DRAW)) {
|
||||||
|
current_mode_ = EditingMode::DRAW;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button(ICON_MD_SAVE)) {
|
||||||
|
status_ = title_screen_.Save(rom());
|
||||||
|
if (status_.ok()) {
|
||||||
|
ImGui::OpenPopup("SaveSuccess");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text("Selected Tile: %d", selected_title_tile16_);
|
||||||
|
|
||||||
|
// Save success popup
|
||||||
|
if (ImGui::BeginPopup("SaveSuccess")) {
|
||||||
|
ImGui::Text("Title screen saved successfully!");
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout: 3-column table for layers
|
||||||
|
if (ImGui::BeginTable("TitleScreenTable", 3,
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
|
||||||
|
ImGui::TableSetupColumn("BG1 Layer");
|
||||||
|
ImGui::TableSetupColumn("BG2 Layer");
|
||||||
|
ImGui::TableSetupColumn("Tile Selector");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Column 1: BG1 Canvas
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
DrawTitleScreenBG1Canvas();
|
||||||
|
|
||||||
|
// Column 2: BG2 Canvas
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
DrawTitleScreenBG2Canvas();
|
||||||
|
|
||||||
|
// Column 3: Blockset Selector
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
DrawTitleScreenBlocksetSelector();
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawTitleScreenBG1Canvas() {
|
||||||
|
title_bg1_canvas_.DrawBackground();
|
||||||
|
title_bg1_canvas_.DrawContextMenu();
|
||||||
|
|
||||||
|
// Draw BG1 tilemap
|
||||||
|
auto& bg1_bitmap = title_screen_.bg1_bitmap();
|
||||||
|
if (bg1_bitmap.is_active()) {
|
||||||
|
title_bg1_canvas_.DrawBitmap(bg1_bitmap, 0, 0, 2.0f, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle tile painting
|
||||||
|
if (current_mode_ == EditingMode::DRAW && selected_title_tile16_ >= 0) {
|
||||||
|
// TODO: Implement tile painting when user clicks on canvas
|
||||||
|
// This would modify the BG1 buffer and re-render the bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
title_bg1_canvas_.DrawGrid(16.0f);
|
||||||
|
title_bg1_canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawTitleScreenBG2Canvas() {
|
||||||
|
title_bg2_canvas_.DrawBackground();
|
||||||
|
title_bg2_canvas_.DrawContextMenu();
|
||||||
|
|
||||||
|
// Draw BG2 tilemap
|
||||||
|
auto& bg2_bitmap = title_screen_.bg2_bitmap();
|
||||||
|
if (bg2_bitmap.is_active()) {
|
||||||
|
title_bg2_canvas_.DrawBitmap(bg2_bitmap, 0, 0, 2.0f, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle tile painting
|
||||||
|
if (current_mode_ == EditingMode::DRAW && selected_title_tile16_ >= 0) {
|
||||||
|
// TODO: Implement tile painting when user clicks on canvas
|
||||||
|
// This would modify the BG2 buffer and re-render the bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
title_bg2_canvas_.DrawGrid(16.0f);
|
||||||
|
title_bg2_canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawTitleScreenBlocksetSelector() {
|
||||||
|
title_blockset_canvas_.DrawBackground();
|
||||||
|
title_blockset_canvas_.DrawContextMenu();
|
||||||
|
|
||||||
|
// Draw tile8 bitmap (8x8 tiles used to compose tile16)
|
||||||
|
auto& tiles8_bitmap = title_screen_.tiles8_bitmap();
|
||||||
|
if (tiles8_bitmap.is_active()) {
|
||||||
|
title_blockset_canvas_.DrawBitmap(tiles8_bitmap, 0, 0, 2.0f, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle tile selection
|
||||||
|
if (title_blockset_canvas_.DrawTileSelector(16.0f)) {
|
||||||
|
// Calculate selected tile ID from click position
|
||||||
|
if (!title_blockset_canvas_.points().empty()) {
|
||||||
|
auto click_pos = title_blockset_canvas_.points().front();
|
||||||
|
int tile_x = static_cast<int>(click_pos.x) / 16;
|
||||||
|
int tile_y = static_cast<int>(click_pos.y) / 16;
|
||||||
|
int tiles_per_row = 128 / 16; // 8 tiles per row
|
||||||
|
selected_title_tile16_ = tile_x + (tile_y * tiles_per_row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
title_blockset_canvas_.DrawGrid(16.0f);
|
||||||
|
title_blockset_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
// Show selected tile preview
|
||||||
|
if (selected_title_tile16_ >= 0) {
|
||||||
|
ImGui::Text("Selected Tile: %d", selected_title_tile16_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenEditor::DrawNamingScreenEditor() {
|
void ScreenEditor::DrawNamingScreenEditor() {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "zelda3/screen/dungeon_map.h"
|
#include "zelda3/screen/dungeon_map.h"
|
||||||
#include "zelda3/screen/inventory.h"
|
#include "zelda3/screen/inventory.h"
|
||||||
|
#include "zelda3/screen/title_screen.h"
|
||||||
#include "app/gui/app/editor_layout.h"
|
#include "app/gui/app/editor_layout.h"
|
||||||
#include "imgui/imgui.h"
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
@@ -59,10 +60,16 @@ class ScreenEditor : public Editor {
|
|||||||
void DrawOverworldMapEditor();
|
void DrawOverworldMapEditor();
|
||||||
|
|
||||||
void DrawInventoryMenuEditor();
|
void DrawInventoryMenuEditor();
|
||||||
|
void DrawInventoryItemIcons();
|
||||||
void DrawToolset();
|
void DrawToolset();
|
||||||
void DrawDungeonMapToolset();
|
void DrawDungeonMapToolset();
|
||||||
void DrawInventoryToolset();
|
void DrawInventoryToolset();
|
||||||
|
|
||||||
|
// Title screen layer editing
|
||||||
|
void DrawTitleScreenBG1Canvas();
|
||||||
|
void DrawTitleScreenBG2Canvas();
|
||||||
|
void DrawTitleScreenBlocksetSelector();
|
||||||
|
|
||||||
absl::Status LoadDungeonMapTile16(const std::vector<uint8_t>& gfx_data,
|
absl::Status LoadDungeonMapTile16(const std::vector<uint8_t>& gfx_data,
|
||||||
bool bin_mode = false);
|
bool bin_mode = false);
|
||||||
absl::Status SaveDungeonMapTile16();
|
absl::Status SaveDungeonMapTile16();
|
||||||
@@ -105,7 +112,24 @@ class ScreenEditor : public Editor {
|
|||||||
gui::Canvas tilemap_canvas_{"##TilemapCanvas", ImVec2(128 + 2, (192) + 4),
|
gui::Canvas tilemap_canvas_{"##TilemapCanvas", ImVec2(128 + 2, (192) + 4),
|
||||||
gui::CanvasGridSize::k8x8, 2.f};
|
gui::CanvasGridSize::k8x8, 2.f};
|
||||||
|
|
||||||
|
// Title screen canvases
|
||||||
|
// Title screen is 32x32 tiles at 8x8 pixels = 256x256 pixels total
|
||||||
|
gui::Canvas title_bg1_canvas_{"##TitleBG1Canvas", ImVec2(256, 256),
|
||||||
|
gui::CanvasGridSize::k8x8, 2.0f};
|
||||||
|
gui::Canvas title_bg2_canvas_{"##TitleBG2Canvas", ImVec2(256, 256),
|
||||||
|
gui::CanvasGridSize::k8x8, 2.0f};
|
||||||
|
// Blockset is 128 pixels wide x 512 pixels tall (16x64 8x8 tiles)
|
||||||
|
gui::Canvas title_blockset_canvas_{"##TitleBlocksetCanvas",
|
||||||
|
ImVec2(128, 512),
|
||||||
|
gui::CanvasGridSize::k8x8, 2.0f};
|
||||||
|
|
||||||
zelda3::Inventory inventory_;
|
zelda3::Inventory inventory_;
|
||||||
|
zelda3::TitleScreen title_screen_;
|
||||||
|
|
||||||
|
// Title screen state
|
||||||
|
int selected_title_tile16_ = 0;
|
||||||
|
bool title_screen_loaded_ = false;
|
||||||
|
|
||||||
Rom* rom_;
|
Rom* rom_;
|
||||||
absl::Status status_;
|
absl::Status status_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "util/file_util.h"
|
#include "util/file_util.h"
|
||||||
#include "app/core/window.h"
|
#include "app/core/window.h"
|
||||||
#include "app/gfx/core/bitmap.h"
|
#include "app/gfx/core/bitmap.h"
|
||||||
|
#include "app/gfx/resource/arena.h"
|
||||||
#include "app/gfx/types/snes_tile.h"
|
#include "app/gfx/types/snes_tile.h"
|
||||||
#include "app/gfx/render/tilemap.h"
|
#include "app/gfx/render/tilemap.h"
|
||||||
#include "app/gfx/backend/irenderer.h"
|
#include "app/gfx/backend/irenderer.h"
|
||||||
@@ -131,8 +132,11 @@ absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom,
|
|||||||
}
|
}
|
||||||
|
|
||||||
tile16_blockset.atlas.SetPalette(*rom.mutable_dungeon_palette(3));
|
tile16_blockset.atlas.SetPalette(*rom.mutable_dungeon_palette(3));
|
||||||
// TODO: Queue texture for later rendering.
|
|
||||||
// core::Renderer::Get().RenderBitmap(&tile16_blockset.atlas);
|
// Queue texture creation via Arena's deferred system
|
||||||
|
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
|
||||||
|
&tile16_blockset.atlas);
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,8 +193,10 @@ absl::Status LoadDungeonMapGfxFromBinary(Rom &rom,
|
|||||||
converted_bin.begin() + ((i + 1) * 0x1000));
|
converted_bin.begin() + ((i + 1) * 0x1000));
|
||||||
sheets[i] = gfx::Bitmap(128, 32, 8, gfx_sheets[i]);
|
sheets[i] = gfx::Bitmap(128, 32, 8, gfx_sheets[i]);
|
||||||
sheets[i].SetPalette(*rom.mutable_dungeon_palette(3));
|
sheets[i].SetPalette(*rom.mutable_dungeon_palette(3));
|
||||||
// TODO: Queue texture for later rendering.
|
|
||||||
// core::Renderer::Get().RenderBitmap(&sheets[i]);
|
// Queue texture creation via Arena's deferred system
|
||||||
|
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
|
||||||
|
&sheets[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
|
|||||||
@@ -3,80 +3,46 @@
|
|||||||
#include "app/gfx/backend/irenderer.h"
|
#include "app/gfx/backend/irenderer.h"
|
||||||
#include "app/core/window.h"
|
#include "app/core/window.h"
|
||||||
#include "app/gfx/core/bitmap.h"
|
#include "app/gfx/core/bitmap.h"
|
||||||
|
#include "app/gfx/resource/arena.h"
|
||||||
#include "app/gfx/types/snes_tile.h"
|
#include "app/gfx/types/snes_tile.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
|
#include "app/snes.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace zelda3 {
|
namespace zelda3 {
|
||||||
|
|
||||||
absl::Status Inventory::Create() {
|
absl::Status Inventory::Create(Rom* rom) {
|
||||||
|
if (!rom || !rom->is_loaded()) {
|
||||||
|
return absl::InvalidArgumentError("ROM is not loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the tileset first (loads 2BPP graphics)
|
||||||
|
RETURN_IF_ERROR(BuildTileset(rom));
|
||||||
|
|
||||||
|
// Load item icons from ROM
|
||||||
|
RETURN_IF_ERROR(LoadItemIcons(rom));
|
||||||
|
|
||||||
|
// TODO(scawful): For now, create a simple display bitmap
|
||||||
|
// Future: Oracle of Secrets menu editor will handle full menu layout
|
||||||
data_.reserve(256 * 256);
|
data_.reserve(256 * 256);
|
||||||
for (int i = 0; i < 256 * 256; i++) {
|
for (int i = 0; i < 256 * 256; i++) {
|
||||||
data_.push_back(0xFF);
|
data_.push_back(0xFF);
|
||||||
}
|
}
|
||||||
RETURN_IF_ERROR(BuildTileset())
|
|
||||||
for (int i = 0; i < 0x500; i += 0x08) {
|
|
||||||
ASSIGN_OR_RETURN(auto t1, rom()->ReadWord(i + kBowItemPos));
|
|
||||||
ASSIGN_OR_RETURN(auto t2, rom()->ReadWord(i + kBowItemPos + 0x02));
|
|
||||||
ASSIGN_OR_RETURN(auto t3, rom()->ReadWord(i + kBowItemPos + 0x04));
|
|
||||||
ASSIGN_OR_RETURN(auto t4, rom()->ReadWord(i + kBowItemPos + 0x06));
|
|
||||||
tiles_.push_back(gfx::GetTilesInfo(t1));
|
|
||||||
tiles_.push_back(gfx::GetTilesInfo(t2));
|
|
||||||
tiles_.push_back(gfx::GetTilesInfo(t3));
|
|
||||||
tiles_.push_back(gfx::GetTilesInfo(t4));
|
|
||||||
}
|
|
||||||
const int offsets[] = {0x00, 0x08, 0x800, 0x808};
|
|
||||||
auto xx = 0;
|
|
||||||
auto yy = 0;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (const auto& tile : tiles_) {
|
|
||||||
int offset = offsets[i];
|
|
||||||
for (auto y = 0; y < 0x08; ++y) {
|
|
||||||
for (auto x = 0; x < 0x08; ++x) {
|
|
||||||
int mx = x;
|
|
||||||
int my = y;
|
|
||||||
|
|
||||||
if (tile.horizontal_mirror_ != 0) {
|
|
||||||
mx = 0x07 - x;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tile.vertical_mirror_ != 0) {
|
|
||||||
my = 0x07 - y;
|
|
||||||
}
|
|
||||||
|
|
||||||
int xpos = ((tile.id_ % 0x10) * 0x08);
|
|
||||||
int ypos = (((tile.id_ / 0x10)) * 0x400);
|
|
||||||
int source = ypos + xpos + (x + (y * 0x80));
|
|
||||||
|
|
||||||
auto destination = xx + yy + offset + (mx + (my * 0x100));
|
|
||||||
data_[destination] = (test_[source] & 0x0F) + tile.palette_ * 0x08;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == 4) {
|
|
||||||
i = 0;
|
|
||||||
xx += 0x10;
|
|
||||||
if (xx >= 0x100) {
|
|
||||||
yy += 0x1000;
|
|
||||||
xx = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap_.Create(256, 256, 8, data_);
|
bitmap_.Create(256, 256, 8, data_);
|
||||||
bitmap_.SetPalette(palette_);
|
bitmap_.SetPalette(palette_);
|
||||||
// TODO: Queue texture for later rendering.
|
|
||||||
// Renderer::Get().RenderBitmap(&bitmap_);
|
// Queue texture creation via Arena's deferred system
|
||||||
|
gfx::Arena::Get().QueueTextureCommand(
|
||||||
|
gfx::Arena::TextureCommandType::CREATE, &bitmap_);
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::Status Inventory::BuildTileset() {
|
absl::Status Inventory::BuildTileset(Rom* rom) {
|
||||||
tilesheets_.reserve(6 * 0x2000);
|
tilesheets_.reserve(6 * 0x2000);
|
||||||
for (int i = 0; i < 6 * 0x2000; i++) tilesheets_.push_back(0xFF);
|
for (int i = 0; i < 6 * 0x2000; i++) tilesheets_.push_back(0xFF);
|
||||||
ASSIGN_OR_RETURN(tilesheets_, Load2BppGraphics(*rom()));
|
ASSIGN_OR_RETURN(tilesheets_, Load2BppGraphics(*rom));
|
||||||
std::vector<uint8_t> test;
|
std::vector<uint8_t> test;
|
||||||
for (int i = 0; i < 0x4000; i++) {
|
for (int i = 0; i < 0x4000; i++) {
|
||||||
test_.push_back(tilesheets_[i]);
|
test_.push_back(tilesheets_[i]);
|
||||||
@@ -85,11 +51,104 @@ absl::Status Inventory::BuildTileset() {
|
|||||||
test_.push_back(tilesheets_[i]);
|
test_.push_back(tilesheets_[i]);
|
||||||
}
|
}
|
||||||
tilesheets_bmp_.Create(128, 0x130, 64, test_);
|
tilesheets_bmp_.Create(128, 0x130, 64, test_);
|
||||||
auto hud_pal_group = rom()->palette_group().hud;
|
auto hud_pal_group = rom->palette_group().hud;
|
||||||
palette_ = hud_pal_group[0];
|
palette_ = hud_pal_group[0];
|
||||||
tilesheets_bmp_.SetPalette(palette_);
|
tilesheets_bmp_.SetPalette(palette_);
|
||||||
// TODO: Queue texture for later rendering.
|
|
||||||
// Renderer::Get().RenderBitmap(&tilesheets_bmp_);
|
// Queue texture creation via Arena's deferred system
|
||||||
|
gfx::Arena::Get().QueueTextureCommand(
|
||||||
|
gfx::Arena::TextureCommandType::CREATE, &tilesheets_bmp_);
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Inventory::LoadItemIcons(Rom* rom) {
|
||||||
|
// Convert SNES address to PC address
|
||||||
|
int pc_addr = SnesToPc(kItemIconsPtr);
|
||||||
|
|
||||||
|
// Define icon categories and their ROM offsets (relative to kItemIconsPtr)
|
||||||
|
// Based on bank_0D.asm ItemIcons structure
|
||||||
|
struct IconDef {
|
||||||
|
int offset;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bow icons (.bows section)
|
||||||
|
std::vector<IconDef> bow_icons = {
|
||||||
|
{0x00, "No bow"},
|
||||||
|
{0x08, "Empty bow"},
|
||||||
|
{0x10, "Bow and arrows"},
|
||||||
|
{0x18, "Empty silvers bow"},
|
||||||
|
{0x20, "Silver bow and arrows"}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Boomerang icons (.booms section)
|
||||||
|
std::vector<IconDef> boom_icons = {
|
||||||
|
{0x28, "No boomerang"},
|
||||||
|
{0x30, "Blue boomerang"},
|
||||||
|
{0x38, "Red boomerang"}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hookshot icons (.hook section)
|
||||||
|
std::vector<IconDef> hook_icons = {
|
||||||
|
{0x40, "No hookshot"},
|
||||||
|
{0x48, "Hookshot"}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bomb icons (.bombs section)
|
||||||
|
std::vector<IconDef> bomb_icons = {
|
||||||
|
{0x50, "No bombs"},
|
||||||
|
{0x58, "Bombs"}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load all icon categories
|
||||||
|
auto load_icons = [&](const std::vector<IconDef>& icons) -> absl::Status {
|
||||||
|
for (const auto& icon_def : icons) {
|
||||||
|
ItemIcon icon;
|
||||||
|
int icon_addr = pc_addr + icon_def.offset;
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(icon.tile_tl, rom->ReadWord(icon_addr));
|
||||||
|
ASSIGN_OR_RETURN(icon.tile_tr, rom->ReadWord(icon_addr + 2));
|
||||||
|
ASSIGN_OR_RETURN(icon.tile_bl, rom->ReadWord(icon_addr + 4));
|
||||||
|
ASSIGN_OR_RETURN(icon.tile_br, rom->ReadWord(icon_addr + 6));
|
||||||
|
icon.name = icon_def.name;
|
||||||
|
|
||||||
|
item_icons_.push_back(icon);
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
};
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(load_icons(bow_icons));
|
||||||
|
RETURN_IF_ERROR(load_icons(boom_icons));
|
||||||
|
RETURN_IF_ERROR(load_icons(hook_icons));
|
||||||
|
RETURN_IF_ERROR(load_icons(bomb_icons));
|
||||||
|
|
||||||
|
// TODO(scawful): Load remaining icon categories:
|
||||||
|
// - Mushroom/Powder (.shroom)
|
||||||
|
// - Magic powder (.powder)
|
||||||
|
// - Fire rod (.fires)
|
||||||
|
// - Ice rod (.ices)
|
||||||
|
// - Bombos medallion (.bombos)
|
||||||
|
// - Ether medallion (.ether)
|
||||||
|
// - Quake medallion (.quake)
|
||||||
|
// - Lantern (.lamp)
|
||||||
|
// - Hammer (.hammer)
|
||||||
|
// - Flute (.flute)
|
||||||
|
// - Bug net (.net)
|
||||||
|
// - Book of Mudora (.book)
|
||||||
|
// - Bottles (.bottles) - Multiple variants (empty, red potion, green potion, etc.)
|
||||||
|
// - Cane of Somaria (.canes)
|
||||||
|
// - Cane of Byrna (.byrn)
|
||||||
|
// - Magic cape (.cape)
|
||||||
|
// - Magic mirror (.mirror)
|
||||||
|
// - Gloves (.glove)
|
||||||
|
// - Boots (.boots)
|
||||||
|
// - Flippers (.flippers)
|
||||||
|
// - Moon pearl (.pearl)
|
||||||
|
// - Swords (.swords)
|
||||||
|
// - Shields (.shields)
|
||||||
|
// - Armor (.armor)
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,21 +12,51 @@ namespace yaze {
|
|||||||
namespace zelda3 {
|
namespace zelda3 {
|
||||||
|
|
||||||
constexpr int kInventoryStart = 0x6564A;
|
constexpr int kInventoryStart = 0x6564A;
|
||||||
constexpr int kBowItemPos = 0x6F631;
|
// ItemIcons base address in SNES format (0x0DF629)
|
||||||
|
constexpr int kItemIconsPtr = 0x0DF629;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents a single item icon (2x2 tiles = 4 tile words)
|
||||||
|
*/
|
||||||
|
struct ItemIcon {
|
||||||
|
uint16_t tile_tl; // Top-left tile word (vhopppcc cccccccc format)
|
||||||
|
uint16_t tile_tr; // Top-right tile word
|
||||||
|
uint16_t tile_bl; // Bottom-left tile word
|
||||||
|
uint16_t tile_br; // Bottom-right tile word
|
||||||
|
std::string name; // Human-readable name for debugging
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inventory manages the inventory screen graphics and layout.
|
||||||
|
*
|
||||||
|
* The inventory screen consists of a 256x256 bitmap displaying equipment,
|
||||||
|
* items, and UI elements using 2BPP graphics and HUD palette.
|
||||||
|
*/
|
||||||
class Inventory {
|
class Inventory {
|
||||||
public:
|
public:
|
||||||
absl::Status Create();
|
/**
|
||||||
|
* @brief Initialize and load inventory screen data from ROM
|
||||||
|
* @param rom ROM instance to read data from
|
||||||
|
*/
|
||||||
|
absl::Status Create(Rom* rom);
|
||||||
|
|
||||||
auto &bitmap() { return bitmap_; }
|
auto &bitmap() { return bitmap_; }
|
||||||
auto &tilesheet() { return tilesheets_bmp_; }
|
auto &tilesheet() { return tilesheets_bmp_; }
|
||||||
auto &palette() { return palette_; }
|
auto &palette() { return palette_; }
|
||||||
|
auto &item_icons() { return item_icons_; }
|
||||||
void LoadRom(Rom *rom) { rom_ = rom; }
|
|
||||||
auto rom() { return rom_; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
absl::Status BuildTileset();
|
/**
|
||||||
|
* @brief Build the tileset from 2BPP graphics
|
||||||
|
* @param rom ROM instance to read graphics from
|
||||||
|
*/
|
||||||
|
absl::Status BuildTileset(Rom* rom);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load individual item icons from ROM
|
||||||
|
* @param rom ROM instance to read icon data from
|
||||||
|
*/
|
||||||
|
absl::Status LoadItemIcons(Rom* rom);
|
||||||
|
|
||||||
std::vector<uint8_t> data_;
|
std::vector<uint8_t> data_;
|
||||||
gfx::Bitmap bitmap_;
|
gfx::Bitmap bitmap_;
|
||||||
@@ -36,9 +66,9 @@ class Inventory {
|
|||||||
gfx::Bitmap tilesheets_bmp_;
|
gfx::Bitmap tilesheets_bmp_;
|
||||||
gfx::SnesPalette palette_;
|
gfx::SnesPalette palette_;
|
||||||
|
|
||||||
Rom *rom_;
|
|
||||||
gui::Canvas canvas_;
|
gui::Canvas canvas_;
|
||||||
std::vector<gfx::TileInfo> tiles_;
|
std::vector<gfx::TileInfo> tiles_;
|
||||||
|
std::vector<ItemIcon> item_icons_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace zelda3
|
} // namespace zelda3
|
||||||
|
|||||||
@@ -3,121 +3,295 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "app/gfx/core/bitmap.h"
|
#include "app/gfx/core/bitmap.h"
|
||||||
|
#include "app/gfx/render/tilemap.h"
|
||||||
|
#include "app/gfx/resource/arena.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "app/snes.h"
|
#include "app/snes.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace zelda3 {
|
namespace zelda3 {
|
||||||
|
|
||||||
void TitleScreen::Create() {
|
absl::Status TitleScreen::Create(Rom* rom) {
|
||||||
tiles8Bitmap.Create(128, 512, 8, std::vector<uint8_t>(0x20000));
|
if (!rom || !rom->is_loaded()) {
|
||||||
tilesBG1Bitmap.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
|
return absl::InvalidArgumentError("ROM is not loaded");
|
||||||
tilesBG2Bitmap.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
|
}
|
||||||
oamBGBitmap.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
|
|
||||||
BuildTileset();
|
// Initialize bitmaps for each layer
|
||||||
LoadTitleScreen();
|
tiles8_bitmap_.Create(128, 512, 8, std::vector<uint8_t>(0x20000));
|
||||||
|
tiles_bg1_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
|
||||||
|
tiles_bg2_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
|
||||||
|
oam_bg_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
|
||||||
|
|
||||||
|
// Initialize tilemap buffers
|
||||||
|
tiles_bg1_buffer_.fill(0x492); // Default empty tile
|
||||||
|
tiles_bg2_buffer_.fill(0x492);
|
||||||
|
|
||||||
|
// Load palette (title screen uses sprite graphics)
|
||||||
|
auto sprite_pal_group = rom->palette_group().sprites_aux1;
|
||||||
|
palette_ = sprite_pal_group[0];
|
||||||
|
|
||||||
|
// Build tile16 blockset from graphics
|
||||||
|
RETURN_IF_ERROR(BuildTileset(rom));
|
||||||
|
|
||||||
|
// Load tilemap data from ROM
|
||||||
|
RETURN_IF_ERROR(LoadTitleScreen(rom));
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitleScreen::BuildTileset() {
|
absl::Status TitleScreen::BuildTileset(Rom* rom) {
|
||||||
|
// Title screen uses specific graphics sheets
|
||||||
|
// Sheet arrangement for title screen (from ALTTP disassembly):
|
||||||
|
// 8-15: Graphics sheets 115, 115+6, 115+7, 112, etc.
|
||||||
uint8_t staticgfx[16] = {0};
|
uint8_t staticgfx[16] = {0};
|
||||||
|
|
||||||
// Main Blocksets
|
// Title screen specific graphics sheets
|
||||||
|
staticgfx[8] = 115 + 0; // Title logo graphics
|
||||||
|
staticgfx[9] = 115 + 3; // Sprite graphics
|
||||||
|
staticgfx[10] = 115 + 6; // Additional graphics
|
||||||
|
staticgfx[11] = 115 + 7; // Additional graphics
|
||||||
|
staticgfx[12] = 115 + 0; // More sprite graphics
|
||||||
|
staticgfx[13] = 112; // UI graphics
|
||||||
|
staticgfx[14] = 112; // UI graphics
|
||||||
|
staticgfx[15] = 112; // UI graphics
|
||||||
|
|
||||||
// TODO: get the gfx from the GFX class rather than the rom.
|
// Get ROM graphics buffer (contains 3BPP/4BPP SNES format data)
|
||||||
// for (int i = 0; i < 8; i++) {
|
const auto& gfx_buffer = rom->graphics_buffer();
|
||||||
// staticgfx[i] = GfxGroups.mainGfx[titleScreenTilesGFX][i];
|
auto& tiles8_data = tiles8_bitmap_.mutable_data();
|
||||||
// }
|
|
||||||
|
|
||||||
staticgfx[8] = 115 + 0;
|
// Load and convert each graphics sheet from 3BPP SNES format to 8BPP indexed
|
||||||
// staticgfx[9] = (GfxGroups.spriteGfx[titleScreenSpritesGFX][3] + 115);
|
// Each sheet is 2048 bytes in 3BPP format -> converts to 0x1000 bytes (4096) in 8BPP
|
||||||
staticgfx[10] = 115 + 6;
|
|
||||||
staticgfx[11] = 115 + 7;
|
|
||||||
// staticgfx[12] = (GfxGroups.spriteGfx[titleScreenSpritesGFX][0] + 115);
|
|
||||||
staticgfx[13] = 112;
|
|
||||||
staticgfx[14] = 112;
|
|
||||||
staticgfx[15] = 112;
|
|
||||||
|
|
||||||
// Loaded gfx for the current screen (empty at this point)
|
|
||||||
uint8_t* currentmapgfx8Data = tiles8Bitmap.mutable_data().data();
|
|
||||||
|
|
||||||
// All gfx of the game pack of 2048 bytes (4bpp)
|
|
||||||
uint8_t* allgfxData = nullptr;
|
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
for (int j = 0; j < 2048; j++) {
|
int sheet_id = staticgfx[i];
|
||||||
uint8_t mapByte = allgfxData[j + (staticgfx[i] * 2048)];
|
int source_offset = sheet_id * 2048;
|
||||||
switch (i) {
|
|
||||||
case 0:
|
|
||||||
case 3:
|
|
||||||
case 4:
|
|
||||||
case 5:
|
|
||||||
mapByte += 0x88;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentmapgfx8Data[(i * 2048) + j] = mapByte; // Upload used gfx data
|
if (source_offset + 2048 <= gfx_buffer.size()) {
|
||||||
|
// Extract 3BPP sheet data
|
||||||
|
std::vector<uint8_t> sheet_3bpp(gfx_buffer.begin() + source_offset,
|
||||||
|
gfx_buffer.begin() + source_offset + 2048);
|
||||||
|
|
||||||
|
// Convert 3BPP SNES format to 8BPP indexed format
|
||||||
|
auto sheet_8bpp = gfx::SnesTo8bppSheet(sheet_3bpp, 3, 1);
|
||||||
|
|
||||||
|
// Copy converted data to tiles8_bitmap at correct position
|
||||||
|
// Each converted sheet is 0x1000 bytes (128x32 pixels)
|
||||||
|
int dest_offset = i * 0x1000;
|
||||||
|
if (dest_offset + sheet_8bpp.size() <= tiles8_data.size()) {
|
||||||
|
std::copy(sheet_8bpp.begin(), sheet_8bpp.end(),
|
||||||
|
tiles8_data.begin() + dest_offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set palette on tiles8 bitmap
|
||||||
|
tiles8_bitmap_.SetPalette(palette_);
|
||||||
|
|
||||||
|
// Queue texture creation via Arena's deferred system
|
||||||
|
gfx::Arena::Get().QueueTextureCommand(
|
||||||
|
gfx::Arena::TextureCommandType::CREATE, &tiles8_bitmap_);
|
||||||
|
|
||||||
|
// TODO: Build tile16 blockset from tile8 data
|
||||||
|
// This would involve composing 16x16 tiles from 8x8 tiles
|
||||||
|
// For now, we'll use the tile8 data directly
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitleScreen::LoadTitleScreen() {
|
absl::Status TitleScreen::LoadTitleScreen(Rom* rom) {
|
||||||
int pos =
|
// Title screen tilemap is stored in compressed format
|
||||||
(rom_[0x138C + 3] << 16) + (rom_[0x1383 + 3] << 8) + rom_[0x137A + 3];
|
// The tilemap data pointer is stored at ROM address 0x0137A (3 bytes)
|
||||||
|
ASSIGN_OR_RETURN(uint8_t byte0, rom->ReadByte(0x137A + 3));
|
||||||
for (int i = 0; i < 1024; i++) {
|
ASSIGN_OR_RETURN(uint8_t byte1, rom->ReadByte(0x1383 + 3));
|
||||||
tilesBG1Buffer[i] = 492;
|
ASSIGN_OR_RETURN(uint8_t byte2, rom->ReadByte(0x138C + 3));
|
||||||
tilesBG2Buffer[i] = 492;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
int pos = (byte2 << 16) + (byte1 << 8) + byte0;
|
||||||
pos = SnesToPc(pos);
|
pos = SnesToPc(pos);
|
||||||
|
|
||||||
while ((rom_[pos] & 0x80) != 0x80) {
|
// Initialize buffers with default empty tile
|
||||||
int dest_addr = pos; // $03 and $04
|
for (int i = 0; i < 1024; i++) {
|
||||||
pos += 2;
|
tiles_bg1_buffer_[i] = 0x492;
|
||||||
short length = pos;
|
tiles_bg2_buffer_[i] = 0x492;
|
||||||
bool increment64 = (length & 0x8000) == 0x8000;
|
}
|
||||||
bool fixsource = (length & 0x4000) == 0x4000;
|
|
||||||
|
// Read compressed tilemap data
|
||||||
|
// Format: destination address (word), length (word), tile data
|
||||||
|
while (pos < rom->size()) {
|
||||||
|
ASSIGN_OR_RETURN(uint8_t first_byte, rom->ReadByte(pos));
|
||||||
|
if ((first_byte & 0x80) == 0x80) {
|
||||||
|
break; // End of data marker
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(uint16_t dest_addr, rom->ReadWord(pos));
|
||||||
pos += 2;
|
pos += 2;
|
||||||
|
|
||||||
length = (short)((length & 0x07FF));
|
ASSIGN_OR_RETURN(uint16_t length_flags, rom->ReadWord(pos));
|
||||||
|
pos += 2;
|
||||||
|
|
||||||
|
bool increment64 = (length_flags & 0x8000) == 0x8000;
|
||||||
|
bool fixsource = (length_flags & 0x4000) == 0x4000;
|
||||||
|
int length = (length_flags & 0x07FF);
|
||||||
|
|
||||||
int j = 0;
|
|
||||||
int jj = 0;
|
|
||||||
int posB = pos;
|
int posB = pos;
|
||||||
while (j < (length / 2) + 1) {
|
for (int j = 0; j < (length / 2) + 1; j++) {
|
||||||
uint16_t tiledata = (uint16_t)pos;
|
ASSIGN_OR_RETURN(uint16_t tiledata, rom->ReadWord(pos));
|
||||||
if (dest_addr >= 0x1000) {
|
|
||||||
// destAddr -= 0x1000;
|
// Determine which layer this tile belongs to
|
||||||
if (dest_addr < 0x2000) {
|
if (dest_addr >= 0x1000 && dest_addr < 0x2000) {
|
||||||
tilesBG1Buffer[dest_addr - 0x1000] = tiledata;
|
// BG1 layer
|
||||||
}
|
tiles_bg1_buffer_[dest_addr - 0x1000] = tiledata;
|
||||||
} else {
|
} else if (dest_addr < 0x1000) {
|
||||||
if (dest_addr < 0x1000) {
|
// BG2 layer
|
||||||
tilesBG2Buffer[dest_addr] = tiledata;
|
tiles_bg2_buffer_[dest_addr] = tiledata;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advance destination address
|
||||||
if (increment64) {
|
if (increment64) {
|
||||||
dest_addr += 32;
|
dest_addr += 32;
|
||||||
} else {
|
} else {
|
||||||
dest_addr++;
|
dest_addr++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advance source position
|
||||||
if (!fixsource) {
|
if (!fixsource) {
|
||||||
pos += 2;
|
pos += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
jj += 2;
|
|
||||||
j++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fixsource) {
|
if (fixsource) {
|
||||||
pos += 2;
|
pos += 2;
|
||||||
} else {
|
} else {
|
||||||
pos = posB + jj;
|
pos = posB + ((length / 2) + 1) * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pal_selected_ = 2;
|
pal_selected_ = 2;
|
||||||
|
|
||||||
|
// Render tilemaps into bitmap pixels
|
||||||
|
RETURN_IF_ERROR(RenderBG1Layer());
|
||||||
|
RETURN_IF_ERROR(RenderBG2Layer());
|
||||||
|
|
||||||
|
// Apply palettes to layer bitmaps
|
||||||
|
tiles_bg1_bitmap_.SetPalette(palette_);
|
||||||
|
tiles_bg2_bitmap_.SetPalette(palette_);
|
||||||
|
oam_bg_bitmap_.SetPalette(palette_);
|
||||||
|
|
||||||
|
// Queue texture creation for all layer bitmaps
|
||||||
|
gfx::Arena::Get().QueueTextureCommand(
|
||||||
|
gfx::Arena::TextureCommandType::CREATE, &tiles_bg1_bitmap_);
|
||||||
|
gfx::Arena::Get().QueueTextureCommand(
|
||||||
|
gfx::Arena::TextureCommandType::CREATE, &tiles_bg2_bitmap_);
|
||||||
|
gfx::Arena::Get().QueueTextureCommand(
|
||||||
|
gfx::Arena::TextureCommandType::CREATE, &oam_bg_bitmap_);
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status TitleScreen::RenderBG1Layer() {
|
||||||
|
// BG1 layer is 32x32 tiles (256x256 pixels)
|
||||||
|
auto& bg1_data = tiles_bg1_bitmap_.mutable_data();
|
||||||
|
const auto& tile8_bitmap_data = tiles8_bitmap_.vector();
|
||||||
|
|
||||||
|
// Render each tile in the 32x32 tilemap
|
||||||
|
for (int tile_y = 0; tile_y < 32; tile_y++) {
|
||||||
|
for (int tile_x = 0; tile_x < 32; tile_x++) {
|
||||||
|
int tilemap_index = tile_y * 32 + tile_x;
|
||||||
|
uint16_t tile_word = tiles_bg1_buffer_[tilemap_index];
|
||||||
|
|
||||||
|
// Extract tile info from SNES tile word (vhopppcc cccccccc format)
|
||||||
|
int tile_id = tile_word & 0x3FF; // Bits 0-9: tile ID
|
||||||
|
int palette = (tile_word >> 10) & 0x07; // Bits 10-12: palette
|
||||||
|
bool h_flip = (tile_word & 0x4000) != 0; // Bit 14: horizontal flip
|
||||||
|
bool v_flip = (tile_word & 0x8000) != 0; // Bit 15: vertical flip
|
||||||
|
|
||||||
|
// Calculate source position in tiles8_bitmap_ (16 tiles per row, 8x8 each)
|
||||||
|
int src_tile_x = (tile_id % 16) * 8;
|
||||||
|
int src_tile_y = (tile_id / 16) * 8;
|
||||||
|
|
||||||
|
// Copy 8x8 tile pixels from tile8 bitmap to BG1 bitmap
|
||||||
|
for (int py = 0; py < 8; py++) {
|
||||||
|
for (int px = 0; px < 8; px++) {
|
||||||
|
// Apply flipping
|
||||||
|
int src_px = h_flip ? (7 - px) : px;
|
||||||
|
int src_py = v_flip ? (7 - py) : py;
|
||||||
|
|
||||||
|
// Calculate source and destination positions
|
||||||
|
int src_x = src_tile_x + src_px;
|
||||||
|
int src_y = src_tile_y + src_py;
|
||||||
|
int src_pos = src_y * 128 + src_x; // tiles8_bitmap_ is 128 pixels wide
|
||||||
|
|
||||||
|
int dest_x = tile_x * 8 + px;
|
||||||
|
int dest_y = tile_y * 8 + py;
|
||||||
|
int dest_pos = dest_y * 256 + dest_x; // BG1 is 256 pixels wide
|
||||||
|
|
||||||
|
// Copy pixel with palette application
|
||||||
|
if (src_pos < tile8_bitmap_data.size() && dest_pos < bg1_data.size()) {
|
||||||
|
uint8_t pixel_value = tile8_bitmap_data[src_pos];
|
||||||
|
// Apply palette index (each palette has 8 colors, pixel uses lower 3 bits)
|
||||||
|
bg1_data[dest_pos] = (pixel_value & 0x07) | (palette << 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status TitleScreen::RenderBG2Layer() {
|
||||||
|
// BG2 layer is 32x32 tiles (256x256 pixels)
|
||||||
|
auto& bg2_data = tiles_bg2_bitmap_.mutable_data();
|
||||||
|
const auto& tile8_bitmap_data = tiles8_bitmap_.vector();
|
||||||
|
|
||||||
|
// Render each tile in the 32x32 tilemap
|
||||||
|
for (int tile_y = 0; tile_y < 32; tile_y++) {
|
||||||
|
for (int tile_x = 0; tile_x < 32; tile_x++) {
|
||||||
|
int tilemap_index = tile_y * 32 + tile_x;
|
||||||
|
uint16_t tile_word = tiles_bg2_buffer_[tilemap_index];
|
||||||
|
|
||||||
|
// Extract tile info from SNES tile word (vhopppcc cccccccc format)
|
||||||
|
int tile_id = tile_word & 0x3FF; // Bits 0-9: tile ID
|
||||||
|
int palette = (tile_word >> 10) & 0x07; // Bits 10-12: palette
|
||||||
|
bool h_flip = (tile_word & 0x4000) != 0; // Bit 14: horizontal flip
|
||||||
|
bool v_flip = (tile_word & 0x8000) != 0; // Bit 15: vertical flip
|
||||||
|
|
||||||
|
// Calculate source position in tiles8_bitmap_ (16 tiles per row, 8x8 each)
|
||||||
|
int src_tile_x = (tile_id % 16) * 8;
|
||||||
|
int src_tile_y = (tile_id / 16) * 8;
|
||||||
|
|
||||||
|
// Copy 8x8 tile pixels from tile8 bitmap to BG2 bitmap
|
||||||
|
for (int py = 0; py < 8; py++) {
|
||||||
|
for (int px = 0; px < 8; px++) {
|
||||||
|
// Apply flipping
|
||||||
|
int src_px = h_flip ? (7 - px) : px;
|
||||||
|
int src_py = v_flip ? (7 - py) : py;
|
||||||
|
|
||||||
|
// Calculate source and destination positions
|
||||||
|
int src_x = src_tile_x + src_px;
|
||||||
|
int src_y = src_tile_y + src_py;
|
||||||
|
int src_pos = src_y * 128 + src_x; // tiles8_bitmap_ is 128 pixels wide
|
||||||
|
|
||||||
|
int dest_x = tile_x * 8 + px;
|
||||||
|
int dest_y = tile_y * 8 + py;
|
||||||
|
int dest_pos = dest_y * 256 + dest_x; // BG2 is 256 pixels wide
|
||||||
|
|
||||||
|
// Copy pixel with palette application
|
||||||
|
if (src_pos < tile8_bitmap_data.size() && dest_pos < bg2_data.size()) {
|
||||||
|
uint8_t pixel_value = tile8_bitmap_data[src_pos];
|
||||||
|
// Apply palette index (each palette has 8 colors, pixel uses lower 3 bits)
|
||||||
|
bg2_data[dest_pos] = (pixel_value & 0x07) | (palette << 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status TitleScreen::Save(Rom* rom) {
|
||||||
|
// TODO: Implement saving title screen tilemap back to ROM
|
||||||
|
// This would involve compressing the tilemap data and writing it back
|
||||||
|
return absl::UnimplementedError("Title screen saving not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace zelda3
|
} // namespace zelda3
|
||||||
|
|||||||
@@ -1,73 +1,91 @@
|
|||||||
#ifndef YAZE_APP_ZELDA3_SCREEN_H
|
#ifndef YAZE_APP_ZELDA3_SCREEN_H
|
||||||
#define YAZE_APP_ZELDA3_SCREEN_H
|
#define YAZE_APP_ZELDA3_SCREEN_H
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
#include "app/gfx/core/bitmap.h"
|
#include "app/gfx/core/bitmap.h"
|
||||||
|
#include "app/gfx/render/tilemap.h"
|
||||||
|
#include "app/gfx/types/snes_palette.h"
|
||||||
#include "app/gfx/types/snes_tile.h"
|
#include "app/gfx/types/snes_tile.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace zelda3 {
|
namespace zelda3 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief TitleScreen manages the title screen graphics and tilemap data.
|
||||||
|
*
|
||||||
|
* The title screen consists of three layers:
|
||||||
|
* - BG1: Main logo and graphics
|
||||||
|
* - BG2: Background elements
|
||||||
|
* - OAM: Sprite layer (sword, etc.)
|
||||||
|
*
|
||||||
|
* Each layer is stored as a 32x32 tilemap (0x400 tiles = 0x1000 bytes as words)
|
||||||
|
*/
|
||||||
class TitleScreen {
|
class TitleScreen {
|
||||||
public:
|
public:
|
||||||
void Create();
|
/**
|
||||||
|
* @brief Initialize and load title screen data from ROM
|
||||||
|
* @param rom ROM instance to read data from
|
||||||
|
*/
|
||||||
|
absl::Status Create(Rom* rom);
|
||||||
|
|
||||||
|
// Accessors for layer data
|
||||||
|
auto& bg1_buffer() { return tiles_bg1_buffer_; }
|
||||||
|
auto& bg2_buffer() { return tiles_bg2_buffer_; }
|
||||||
|
auto& oam_buffer() { return oam_data_; }
|
||||||
|
|
||||||
|
// Accessors for bitmaps
|
||||||
|
auto& bg1_bitmap() { return tiles_bg1_bitmap_; }
|
||||||
|
auto& bg2_bitmap() { return tiles_bg2_bitmap_; }
|
||||||
|
auto& oam_bitmap() { return oam_bg_bitmap_; }
|
||||||
|
auto& tiles8_bitmap() { return tiles8_bitmap_; }
|
||||||
|
auto& blockset() { return tile16_blockset_; }
|
||||||
|
|
||||||
|
// Palette access
|
||||||
|
auto& palette() { return palette_; }
|
||||||
|
|
||||||
|
// Save changes back to ROM
|
||||||
|
absl::Status Save(Rom* rom);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BuildTileset();
|
/**
|
||||||
void LoadTitleScreen();
|
* @brief Build the tile16 blockset from ROM graphics
|
||||||
|
* @param rom ROM instance to read graphics from
|
||||||
|
*/
|
||||||
|
absl::Status BuildTileset(Rom* rom);
|
||||||
|
|
||||||
int sword_x_ = 0;
|
/**
|
||||||
int mx_click_ = 0;
|
* @brief Load title screen tilemap data from ROM
|
||||||
int my_click_ = 0;
|
* @param rom ROM instance to read tilemap from
|
||||||
int mx_dist_ = 0;
|
*/
|
||||||
int my_dist_ = 0;
|
absl::Status LoadTitleScreen(Rom* rom);
|
||||||
int last_x_ = 0;
|
|
||||||
int last_y_ = 0;
|
|
||||||
int x_in_ = 0;
|
|
||||||
int y_in_ = 0;
|
|
||||||
int dungmap_selected_tile_ = 0;
|
|
||||||
int dungmap_selected_ = 0;
|
|
||||||
int selected_palette_ = 0;
|
|
||||||
int total_floors_ = 0;
|
|
||||||
int current_floor_ = 0;
|
|
||||||
int num_basement_ = 0;
|
|
||||||
int num_floor_ = 0;
|
|
||||||
int selected_map_tile = 0;
|
|
||||||
int current_floor_rooms; // [1][];
|
|
||||||
int current_floor_gfx; // [1][];
|
|
||||||
int copied_data_rooms; // 25
|
|
||||||
int copied_data_gfx; // 25
|
|
||||||
int pal_selected_;
|
|
||||||
int addresses[7] = {0x53de4, 0x53e2c, 0x53e08, 0x53e50,
|
|
||||||
0x53e74, 0x53e98, 0x53ebc};
|
|
||||||
int addressesgfx[7] = {0x53ee0, 0x53f04, 0x53ef2, 0x53f16,
|
|
||||||
0x53f28, 0x53f3a, 0x53f4c};
|
|
||||||
|
|
||||||
uint16_t bossRoom = 0x000F;
|
/**
|
||||||
uint16_t selected_tile = 0;
|
* @brief Render BG1 tilemap into bitmap pixels
|
||||||
uint16_t tilesBG1Buffer[0x1000]; // 0x1000
|
* Converts tile IDs from tiles_bg1_buffer_ into pixel data
|
||||||
uint16_t tilesBG2Buffer[0x1000]; // 0x1000
|
*/
|
||||||
uint8_t mapdata; // 64 * 64
|
absl::Status RenderBG1Layer();
|
||||||
uint8_t dwmapdata; // 64 * 64
|
|
||||||
|
|
||||||
bool mDown = false;
|
/**
|
||||||
bool swordSelected = false;
|
* @brief Render BG2 tilemap into bitmap pixels
|
||||||
bool darkWorld = false;
|
* Converts tile IDs from tiles_bg2_buffer_ into pixel data
|
||||||
bool currentDungeonChanged = false;
|
*/
|
||||||
bool editedFromEditor = false;
|
absl::Status RenderBG2Layer();
|
||||||
bool mouseDown = false;
|
|
||||||
bool mdown = false;
|
|
||||||
|
|
||||||
Rom rom_;
|
int pal_selected_ = 2;
|
||||||
|
|
||||||
gfx::OamTile oam_data[10];
|
std::array<uint16_t, 0x1000> tiles_bg1_buffer_; // BG1 tilemap (32x32 tiles)
|
||||||
gfx::OamTile selected_oam_tile;
|
std::array<uint16_t, 0x1000> tiles_bg2_buffer_; // BG2 tilemap (32x32 tiles)
|
||||||
gfx::OamTile last_selected_oam_tile;
|
|
||||||
|
|
||||||
gfx::Bitmap tilesBG1Bitmap; // 0x80000
|
gfx::OamTile oam_data_[10];
|
||||||
gfx::Bitmap tilesBG2Bitmap; // 0x80000
|
|
||||||
gfx::Bitmap oamBGBitmap; // 0x80000
|
gfx::Bitmap tiles_bg1_bitmap_; // Rendered BG1 layer
|
||||||
gfx::Bitmap tiles8Bitmap; // 0x20000
|
gfx::Bitmap tiles_bg2_bitmap_; // Rendered BG2 layer
|
||||||
|
gfx::Bitmap oam_bg_bitmap_; // Rendered OAM layer
|
||||||
|
gfx::Bitmap tiles8_bitmap_; // 8x8 tile graphics
|
||||||
|
|
||||||
|
gfx::Tilemap tile16_blockset_; // 16x16 tile blockset
|
||||||
|
gfx::SnesPalette palette_; // Title screen palette
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace zelda3
|
} // namespace zelda3
|
||||||
|
|||||||
Reference in New Issue
Block a user