feat: Add INI and JSON format support for project files and update resource label handling

This commit is contained in:
scawful
2025-10-04 01:41:55 -04:00
parent 0ff96fac45
commit 164f89e1bb
6 changed files with 327 additions and 4 deletions

65
assets/zelda3.yaze Normal file
View File

@@ -0,0 +1,65 @@
# Default Zelda3 Project File
# All resource names are embedded and always available to AI agents
# This project uses embedded labels - no external labels file required
[project]
name = The Legend of Zelda: A Link to the Past
description = Default Zelda3 project with all embedded resource labels
author = Nintendo
version = 1.0.0
created_date = 2025-10-04
last_modified = 2025-10-04
yaze_version = 0.1.0
tags = zelda3, reference, default
[files]
rom_filename = zelda3.sfc
code_folder =
assets_folder = assets
patches_folder = patches
labels_filename =
symbols_filename =
output_folder = build
rom_backup_folder = backups
[feature_flags]
log_instructions = false
save_dungeon_maps = false
save_graphics_sheet = false
load_custom_overworld = false
apply_zs_custom_overworld_asm = false
[workspace]
font_global_scale = 1.0
dark_mode = true
ui_theme = default
autosave_enabled = true
autosave_interval_secs = 300
backup_on_save = true
show_grid = true
show_collision = false
last_layout_preset = default
[build]
build_script =
output_folder = build
git_repository =
track_changes = true
build_configurations = debug, release
# Embedded Labels Information
# This project includes the following embedded resource names:
# - 296 room names (dungeons, bosses, treasure rooms)
# - 133 entrance names (dungeons, caves, houses, shops)
# - 256 sprite names (enemies, NPCs, bosses, items)
# - 26 overlord names (factories, traps, special objects)
# - 160 overworld map names (Light World, Dark World, Special Areas)
# - 100 item names (swords, shields, medallions, bottles)
# - 48 music track names
# - 32 graphics sheet names
# - 8 room effect names
# - 13 room tag names
# - 60 tile type names (collision, slopes, water, ice, stairs)
#
# Use InitializeEmbeddedLabels() to load all default labels
# Custom labels can be added in [labels_<type>] sections

View File

@@ -15,6 +15,11 @@
#include "imgui/imgui.h"
#include "yaze_config.h"
#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
#include "nlohmann/json.hpp"
using json = nlohmann::json;
#endif
namespace yaze {
namespace core {
@@ -111,7 +116,27 @@ absl::Status YazeProject::Open(const std::string& project_path) {
// Determine format and load accordingly
if (project_path.ends_with(".yaze")) {
format = ProjectFormat::kYazeNative;
return LoadFromYazeFormat(project_path);
// Try to detect if it's JSON format by peeking at first character
std::ifstream file(project_path);
if (file.is_open()) {
char first_char;
file.get(first_char);
file.close();
#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
if (first_char == '{') {
std::cout << "📄 Detected JSON format project file\n";
return LoadFromJsonFormat(project_path);
}
#endif
// Default to INI format
return LoadFromYazeFormat(project_path);
}
return absl::InvalidArgumentError(
absl::StrFormat("Cannot open project file: %s", project_path));
} else if (project_path.ends_with(".zsproj")) {
format = ProjectFormat::kZScreamCompat;
return ImportFromZScreamFormat(project_path);
@@ -868,5 +893,131 @@ std::string YazeProject::GetLabel(const std::string& resource_type, int id,
: default_value;
}
// ============================================================================
// JSON Format Support (Optional)
// ============================================================================
#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
absl::Status YazeProject::LoadFromJsonFormat(const std::string& project_path) {
std::ifstream file(project_path);
if (!file.is_open()) {
return absl::InvalidArgumentError(
absl::StrFormat("Cannot open JSON project file: %s", project_path));
}
try {
json j;
file >> j;
// Parse project metadata
if (j.contains("yaze_project")) {
auto& proj = j["yaze_project"];
if (proj.contains("name")) name = proj["name"].get<std::string>();
if (proj.contains("description")) metadata.description = proj["description"].get<std::string>();
if (proj.contains("author")) metadata.author = proj["author"].get<std::string>();
if (proj.contains("version")) metadata.version = proj["version"].get<std::string>();
if (proj.contains("created")) metadata.created_date = proj["created"].get<std::string>();
if (proj.contains("modified")) metadata.last_modified = proj["modified"].get<std::string>();
// Files
if (proj.contains("rom_filename")) rom_filename = proj["rom_filename"].get<std::string>();
if (proj.contains("code_folder")) code_folder = proj["code_folder"].get<std::string>();
if (proj.contains("assets_folder")) assets_folder = proj["assets_folder"].get<std::string>();
if (proj.contains("patches_folder")) patches_folder = proj["patches_folder"].get<std::string>();
if (proj.contains("labels_filename")) labels_filename = proj["labels_filename"].get<std::string>();
if (proj.contains("symbols_filename")) symbols_filename = proj["symbols_filename"].get<std::string>();
// Embedded labels flag
if (proj.contains("use_embedded_labels")) {
use_embedded_labels = proj["use_embedded_labels"].get<bool>();
}
// Feature flags
if (proj.contains("feature_flags")) {
auto& flags = proj["feature_flags"];
if (flags.contains("kLogInstructions"))
feature_flags.kLogInstructions = flags["kLogInstructions"].get<bool>();
if (flags.contains("kSaveDungeonMaps"))
feature_flags.kSaveDungeonMaps = flags["kSaveDungeonMaps"].get<bool>();
if (flags.contains("kSaveGraphicsSheet"))
feature_flags.kSaveGraphicsSheet = flags["kSaveGraphicsSheet"].get<bool>();
}
// Workspace settings
if (proj.contains("workspace_settings")) {
auto& ws = proj["workspace_settings"];
if (ws.contains("auto_save_enabled"))
workspace_settings.autosave_enabled = ws["auto_save_enabled"].get<bool>();
if (ws.contains("auto_save_interval"))
workspace_settings.autosave_interval_secs = ws["auto_save_interval"].get<float>();
}
// Build settings
if (proj.contains("build_script")) build_script = proj["build_script"].get<std::string>();
if (proj.contains("output_folder")) output_folder = proj["output_folder"].get<std::string>();
if (proj.contains("git_repository")) git_repository = proj["git_repository"].get<std::string>();
if (proj.contains("track_changes")) track_changes = proj["track_changes"].get<bool>();
}
return absl::OkStatus();
} catch (const json::exception& e) {
return absl::InvalidArgumentError(
absl::StrFormat("JSON parse error: %s", e.what()));
}
}
absl::Status YazeProject::SaveToJsonFormat() {
json j;
auto& proj = j["yaze_project"];
// Metadata
proj["version"] = metadata.version;
proj["name"] = name;
proj["author"] = metadata.author;
proj["description"] = metadata.description;
proj["created"] = metadata.created_date;
proj["modified"] = metadata.last_modified;
// Files
proj["rom_filename"] = rom_filename;
proj["code_folder"] = code_folder;
proj["assets_folder"] = assets_folder;
proj["patches_folder"] = patches_folder;
proj["labels_filename"] = labels_filename;
proj["symbols_filename"] = symbols_filename;
proj["output_folder"] = output_folder;
// Embedded labels
proj["use_embedded_labels"] = use_embedded_labels;
// Feature flags
proj["feature_flags"]["kLogInstructions"] = feature_flags.kLogInstructions;
proj["feature_flags"]["kSaveDungeonMaps"] = feature_flags.kSaveDungeonMaps;
proj["feature_flags"]["kSaveGraphicsSheet"] = feature_flags.kSaveGraphicsSheet;
// Workspace settings
proj["workspace_settings"]["auto_save_enabled"] = workspace_settings.autosave_enabled;
proj["workspace_settings"]["auto_save_interval"] = workspace_settings.autosave_interval_secs;
// Build settings
proj["build_script"] = build_script;
proj["git_repository"] = git_repository;
proj["track_changes"] = track_changes;
// Write to file
std::ofstream file(filepath);
if (!file.is_open()) {
return absl::InvalidArgumentError(
absl::StrFormat("Cannot write JSON project file: %s", filepath));
}
file << j.dump(2); // Pretty print with 2-space indent
return absl::OkStatus();
}
#endif // YAZE_ENABLE_JSON_PROJECT_FORMAT
} // namespace core
} // namespace yaze

View File

@@ -154,6 +154,11 @@ private:
absl::Status SaveToYazeFormat();
absl::Status ImportFromZScreamFormat(const std::string& project_path);
#ifdef YAZE_ENABLE_JSON_PROJECT_FORMAT
absl::Status LoadFromJsonFormat(const std::string& project_path);
absl::Status SaveToJsonFormat();
#endif
void InitializeDefaults();
std::string GenerateProjectId() const;
};

View File

@@ -284,20 +284,35 @@ void Room::CopyRoomGraphicsToBuffer() {
}
void Room::RenderRoomGraphics() {
std::printf("\n=== RenderRoomGraphics Room %d ===\n", room_id_);
CopyRoomGraphicsToBuffer();
std::printf("1. Graphics buffer copied\n");
gfx::Arena::Get().bg1().DrawFloor(rom()->vector(), tile_address,
tile_address_floor, floor1_graphics_);
gfx::Arena::Get().bg2().DrawFloor(rom()->vector(), tile_address,
tile_address_floor, floor2_graphics_);
std::printf("2. Floor pattern drawn\n");
// Render layout and object tiles to background buffers
RenderObjectsToBackground();
std::printf("3. Objects rendered to buffer\n");
gfx::Arena::Get().bg1().DrawBackground(std::span<uint8_t>(current_gfx16_));
gfx::Arena::Get().bg2().DrawBackground(std::span<uint8_t>(current_gfx16_));
std::printf("4. Background drawn from buffer\n");
auto& bg1_bmp = gfx::Arena::Get().bg1().bitmap();
auto& bg2_bmp = gfx::Arena::Get().bg2().bitmap();
std::printf("5. BG1 bitmap: active=%d, size=%dx%d, data_size=%zu\n",
bg1_bmp.is_active(), bg1_bmp.width(), bg1_bmp.height(), bg1_bmp.vector().size());
auto bg1_palette =
rom()->mutable_palette_group()->get_group("dungeon_main")[0].palette(0);
if (!gfx::Arena::Get().bg1().bitmap().is_active()) {
std::printf("6a. Creating new bitmap textures\n");
core::Renderer::Get().CreateAndRenderBitmap(
0x200, 0x200, 0x200, gfx::Arena::Get().bg1().bitmap().vector(),
gfx::Arena::Get().bg1().bitmap(), bg1_palette);
@@ -305,10 +320,96 @@ void Room::RenderRoomGraphics() {
0x200, 0x200, 0x200, gfx::Arena::Get().bg2().bitmap().vector(),
gfx::Arena::Get().bg2().bitmap(), bg1_palette);
} else {
std::printf("6b. Updating existing bitmap textures\n");
// Update the bitmap
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().bg1().bitmap());
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().bg2().bitmap());
}
std::printf("7. BG1 has texture: %d\n", bg1_bmp.texture() != nullptr);
std::printf("=== RenderRoomGraphics Complete ===\n\n");
}
void Room::RenderObjectsToBackground() {
if (!rom_ || !rom_->is_loaded()) {
std::printf("RenderObjectsToBackground: ROM not loaded\n");
return;
}
std::printf("RenderObjectsToBackground: Room %d has %zu objects\n", room_id_, tile_objects_.size());
// Get references to the background buffers
auto& bg1 = gfx::Arena::Get().bg1();
auto& bg2 = gfx::Arena::Get().bg2();
// Render tile objects to their respective layers
int rendered_count = 0;
for (const auto& obj : tile_objects_) {
// Ensure object has tiles loaded
auto mutable_obj = const_cast<RoomObject&>(obj);
mutable_obj.EnsureTilesLoaded();
// Get tiles with error handling
auto tiles_result = obj.GetTiles();
if (!tiles_result.ok()) {
std::printf(" Object at (%d,%d) failed to load tiles: %s\n",
obj.x_, obj.y_, tiles_result.status().ToString().c_str());
continue;
}
if (tiles_result->empty()) {
std::printf(" Object at (%d,%d) has no tiles\n", obj.x_, obj.y_);
continue;
}
const auto& tiles = *tiles_result;
std::printf(" Object at (%d,%d) has %zu tiles\n", obj.x_, obj.y_, tiles.size());
// Calculate object position in tile coordinates (each position is an 8x8 tile)
int obj_x = obj.x_; // X position in 8x8 tile units
int obj_y = obj.y_; // Y position in 8x8 tile units
// Determine which layer this object belongs to
bool is_bg2 = (obj.layer_ == RoomObject::LayerType::BG2);
auto& target_buffer = is_bg2 ? bg2 : bg1;
// Draw each Tile16 from the object
// Each Tile16 is a 16x16 tile made of 4 TileInfo (8x8) tiles
for (size_t i = 0; i < tiles.size(); i++) {
const auto& tile16 = tiles[i];
// Calculate tile16 position (in 16x16 units, so multiply by 2 for 8x8 units)
int base_x = obj_x + ((i % 4) * 2); // Assume 4-tile16 width for now
int base_y = obj_y + ((i / 4) * 2);
// Each Tile16 contains 4 TileInfo objects arranged as:
// [0][1] (top-left, top-right)
// [2][3] (bottom-left, bottom-right)
const auto& tile_infos = tile16.tiles_info;
// Draw the 4 sub-tiles of this Tile16
for (int sub_tile = 0; sub_tile < 4; sub_tile++) {
int tile_x = base_x + (sub_tile % 2);
int tile_y = base_y + (sub_tile / 2);
// Bounds check
if (tile_x < 0 || tile_x >= 64 || tile_y < 0 || tile_y >= 64) {
continue;
}
// Convert TileInfo to word format: (vflip<<15) | (hflip<<14) | (over<<13) | (palette<<10) | tile_id
uint16_t tile_word = gfx::TileInfoToWord(tile_infos[sub_tile]);
// Set the tile in the buffer
target_buffer.SetTileAt(tile_x, tile_y, tile_word);
rendered_count++;
}
}
}
std::printf("RenderObjectsToBackground: Rendered %d tiles total\n", rendered_count);
// Note: Layout objects rendering would go here if needed
// For now, focusing on regular tile objects which is what ZScream primarily renders
}
void Room::LoadAnimatedGraphics() {

View File

@@ -273,9 +273,9 @@ const std::vector<std::string>& Zelda3Labels::GetTileTypeNames() {
}
// Convert all labels to structured map for project embedding
std::map<std::string, std::map<std::string, std::string>>
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
Zelda3Labels::ToResourceLabels() {
std::map<std::string, std::map<std::string, std::string>> labels;
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> labels;
// Rooms
const auto& rooms = GetRoomNames();

View File

@@ -3,6 +3,7 @@
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
namespace yaze {
@@ -59,7 +60,7 @@ struct Zelda3Labels {
* @brief Convert all labels to a structured map for project embedding
* @return Map of resource type -> (id -> name) for all resources
*/
static std::map<std::string, std::map<std::string, std::string>> ToResourceLabels();
static std::unordered_map<std::string, std::unordered_map<std::string, std::string>> ToResourceLabels();
/**
* @brief Get a label by resource type and ID