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:
@@ -6,6 +6,7 @@
|
||||
#include "util/file_util.h"
|
||||
#include "app/core/window.h"
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/types/snes_tile.h"
|
||||
#include "app/gfx/render/tilemap.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));
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -189,8 +193,10 @@ absl::Status LoadDungeonMapGfxFromBinary(Rom &rom,
|
||||
converted_bin.begin() + ((i + 1) * 0x1000));
|
||||
sheets[i] = gfx::Bitmap(128, 32, 8, gfx_sheets[i]);
|
||||
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();
|
||||
|
||||
@@ -3,80 +3,46 @@
|
||||
#include "app/gfx/backend/irenderer.h"
|
||||
#include "app/core/window.h"
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/types/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/snes.h"
|
||||
|
||||
namespace yaze {
|
||||
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);
|
||||
for (int i = 0; i < 256 * 256; i++) {
|
||||
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_.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();
|
||||
}
|
||||
|
||||
absl::Status Inventory::BuildTileset() {
|
||||
absl::Status Inventory::BuildTileset(Rom* rom) {
|
||||
tilesheets_.reserve(6 * 0x2000);
|
||||
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;
|
||||
for (int i = 0; i < 0x4000; i++) {
|
||||
test_.push_back(tilesheets_[i]);
|
||||
@@ -85,11 +51,104 @@ absl::Status Inventory::BuildTileset() {
|
||||
test_.push_back(tilesheets_[i]);
|
||||
}
|
||||
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];
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,21 +12,51 @@ namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
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 {
|
||||
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 &tilesheet() { return tilesheets_bmp_; }
|
||||
auto &palette() { return palette_; }
|
||||
|
||||
void LoadRom(Rom *rom) { rom_ = rom; }
|
||||
auto rom() { return rom_; }
|
||||
auto &item_icons() { return item_icons_; }
|
||||
|
||||
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_;
|
||||
gfx::Bitmap bitmap_;
|
||||
@@ -36,9 +66,9 @@ class Inventory {
|
||||
gfx::Bitmap tilesheets_bmp_;
|
||||
gfx::SnesPalette palette_;
|
||||
|
||||
Rom *rom_;
|
||||
gui::Canvas canvas_;
|
||||
std::vector<gfx::TileInfo> tiles_;
|
||||
std::vector<ItemIcon> item_icons_;
|
||||
};
|
||||
|
||||
} // namespace zelda3
|
||||
|
||||
@@ -3,121 +3,295 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/render/tilemap.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/snes.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
void TitleScreen::Create() {
|
||||
tiles8Bitmap.Create(128, 512, 8, std::vector<uint8_t>(0x20000));
|
||||
tilesBG1Bitmap.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
|
||||
tilesBG2Bitmap.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
|
||||
oamBGBitmap.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
|
||||
BuildTileset();
|
||||
LoadTitleScreen();
|
||||
absl::Status TitleScreen::Create(Rom* rom) {
|
||||
if (!rom || !rom->is_loaded()) {
|
||||
return absl::InvalidArgumentError("ROM is not loaded");
|
||||
}
|
||||
|
||||
// Initialize bitmaps for each layer
|
||||
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};
|
||||
|
||||
// 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.
|
||||
// for (int i = 0; i < 8; i++) {
|
||||
// staticgfx[i] = GfxGroups.mainGfx[titleScreenTilesGFX][i];
|
||||
// }
|
||||
// Get ROM graphics buffer (contains 3BPP/4BPP SNES format data)
|
||||
const auto& gfx_buffer = rom->graphics_buffer();
|
||||
auto& tiles8_data = tiles8_bitmap_.mutable_data();
|
||||
|
||||
staticgfx[8] = 115 + 0;
|
||||
// staticgfx[9] = (GfxGroups.spriteGfx[titleScreenSpritesGFX][3] + 115);
|
||||
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;
|
||||
// Load and convert each graphics sheet from 3BPP SNES format to 8BPP indexed
|
||||
// Each sheet is 2048 bytes in 3BPP format -> converts to 0x1000 bytes (4096) in 8BPP
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 2048; j++) {
|
||||
uint8_t mapByte = allgfxData[j + (staticgfx[i] * 2048)];
|
||||
switch (i) {
|
||||
case 0:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
mapByte += 0x88;
|
||||
break;
|
||||
}
|
||||
int sheet_id = staticgfx[i];
|
||||
int source_offset = sheet_id * 2048;
|
||||
|
||||
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() {
|
||||
int pos =
|
||||
(rom_[0x138C + 3] << 16) + (rom_[0x1383 + 3] << 8) + rom_[0x137A + 3];
|
||||
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
tilesBG1Buffer[i] = 492;
|
||||
tilesBG2Buffer[i] = 492;
|
||||
}
|
||||
absl::Status TitleScreen::LoadTitleScreen(Rom* rom) {
|
||||
// Title screen tilemap is stored in compressed format
|
||||
// The tilemap data pointer is stored at ROM address 0x0137A (3 bytes)
|
||||
ASSIGN_OR_RETURN(uint8_t byte0, rom->ReadByte(0x137A + 3));
|
||||
ASSIGN_OR_RETURN(uint8_t byte1, rom->ReadByte(0x1383 + 3));
|
||||
ASSIGN_OR_RETURN(uint8_t byte2, rom->ReadByte(0x138C + 3));
|
||||
|
||||
int pos = (byte2 << 16) + (byte1 << 8) + byte0;
|
||||
pos = SnesToPc(pos);
|
||||
|
||||
while ((rom_[pos] & 0x80) != 0x80) {
|
||||
int dest_addr = pos; // $03 and $04
|
||||
pos += 2;
|
||||
short length = pos;
|
||||
bool increment64 = (length & 0x8000) == 0x8000;
|
||||
bool fixsource = (length & 0x4000) == 0x4000;
|
||||
// Initialize buffers with default empty tile
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
tiles_bg1_buffer_[i] = 0x492;
|
||||
tiles_bg2_buffer_[i] = 0x492;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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;
|
||||
while (j < (length / 2) + 1) {
|
||||
uint16_t tiledata = (uint16_t)pos;
|
||||
if (dest_addr >= 0x1000) {
|
||||
// destAddr -= 0x1000;
|
||||
if (dest_addr < 0x2000) {
|
||||
tilesBG1Buffer[dest_addr - 0x1000] = tiledata;
|
||||
}
|
||||
} else {
|
||||
if (dest_addr < 0x1000) {
|
||||
tilesBG2Buffer[dest_addr] = tiledata;
|
||||
}
|
||||
for (int j = 0; j < (length / 2) + 1; j++) {
|
||||
ASSIGN_OR_RETURN(uint16_t tiledata, rom->ReadWord(pos));
|
||||
|
||||
// Determine which layer this tile belongs to
|
||||
if (dest_addr >= 0x1000 && dest_addr < 0x2000) {
|
||||
// BG1 layer
|
||||
tiles_bg1_buffer_[dest_addr - 0x1000] = tiledata;
|
||||
} else if (dest_addr < 0x1000) {
|
||||
// BG2 layer
|
||||
tiles_bg2_buffer_[dest_addr] = tiledata;
|
||||
}
|
||||
|
||||
// Advance destination address
|
||||
if (increment64) {
|
||||
dest_addr += 32;
|
||||
} else {
|
||||
dest_addr++;
|
||||
}
|
||||
|
||||
// Advance source position
|
||||
if (!fixsource) {
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
jj += 2;
|
||||
j++;
|
||||
}
|
||||
|
||||
if (fixsource) {
|
||||
pos += 2;
|
||||
} else {
|
||||
pos = posB + jj;
|
||||
pos = posB + ((length / 2) + 1) * 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
|
||||
|
||||
@@ -1,73 +1,91 @@
|
||||
#ifndef 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/render/tilemap.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/gfx/types/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
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 {
|
||||
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:
|
||||
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;
|
||||
int my_click_ = 0;
|
||||
int mx_dist_ = 0;
|
||||
int my_dist_ = 0;
|
||||
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};
|
||||
/**
|
||||
* @brief Load title screen tilemap data from ROM
|
||||
* @param rom ROM instance to read tilemap from
|
||||
*/
|
||||
absl::Status LoadTitleScreen(Rom* rom);
|
||||
|
||||
uint16_t bossRoom = 0x000F;
|
||||
uint16_t selected_tile = 0;
|
||||
uint16_t tilesBG1Buffer[0x1000]; // 0x1000
|
||||
uint16_t tilesBG2Buffer[0x1000]; // 0x1000
|
||||
uint8_t mapdata; // 64 * 64
|
||||
uint8_t dwmapdata; // 64 * 64
|
||||
/**
|
||||
* @brief Render BG1 tilemap into bitmap pixels
|
||||
* Converts tile IDs from tiles_bg1_buffer_ into pixel data
|
||||
*/
|
||||
absl::Status RenderBG1Layer();
|
||||
|
||||
bool mDown = false;
|
||||
bool swordSelected = false;
|
||||
bool darkWorld = false;
|
||||
bool currentDungeonChanged = false;
|
||||
bool editedFromEditor = false;
|
||||
bool mouseDown = false;
|
||||
bool mdown = false;
|
||||
/**
|
||||
* @brief Render BG2 tilemap into bitmap pixels
|
||||
* Converts tile IDs from tiles_bg2_buffer_ into pixel data
|
||||
*/
|
||||
absl::Status RenderBG2Layer();
|
||||
|
||||
Rom rom_;
|
||||
int pal_selected_ = 2;
|
||||
|
||||
gfx::OamTile oam_data[10];
|
||||
gfx::OamTile selected_oam_tile;
|
||||
gfx::OamTile last_selected_oam_tile;
|
||||
std::array<uint16_t, 0x1000> tiles_bg1_buffer_; // BG1 tilemap (32x32 tiles)
|
||||
std::array<uint16_t, 0x1000> tiles_bg2_buffer_; // BG2 tilemap (32x32 tiles)
|
||||
|
||||
gfx::Bitmap tilesBG1Bitmap; // 0x80000
|
||||
gfx::Bitmap tilesBG2Bitmap; // 0x80000
|
||||
gfx::Bitmap oamBGBitmap; // 0x80000
|
||||
gfx::Bitmap tiles8Bitmap; // 0x20000
|
||||
gfx::OamTile oam_data_[10];
|
||||
|
||||
gfx::Bitmap tiles_bg1_bitmap_; // Rendered BG1 layer
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user