Add Dungeon Room and Object Selection Features
- Introduced new classes for DungeonRoomSelector and DungeonObjectSelector to enhance room and object management within the dungeon editor. - Implemented UI components for selecting rooms and entrances, allowing users to easily navigate and manage dungeon layouts. - Added functionality for rendering room graphics and object previews, improving the visual editing experience. - Updated the DungeonEditor class to integrate the new selectors, streamlining the overall editing workflow. - Enhanced error handling and validation in room and object management processes to ensure robust functionality.
This commit is contained in:
415
src/app/editor/dungeon/dungeon_canvas_viewer.cc
Normal file
415
src/app/editor/dungeon/dungeon_canvas_viewer.cc
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
#include "dungeon_canvas_viewer.h"
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "app/core/window.h"
|
||||||
|
#include "app/gfx/arena.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/dungeon/object_renderer.h"
|
||||||
|
#include "app/zelda3/dungeon/room.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "util/hex.h"
|
||||||
|
|
||||||
|
namespace yaze::editor {
|
||||||
|
|
||||||
|
using core::Renderer;
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
void DungeonCanvasViewer::DrawDungeonTabView() {
|
||||||
|
static int next_tab_id = 0;
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("MyTabBar", ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_TabListPopupButton)) {
|
||||||
|
if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) {
|
||||||
|
if (std::find(active_rooms_.begin(), active_rooms_.end(), current_active_room_tab_) != active_rooms_.end()) {
|
||||||
|
next_tab_id++;
|
||||||
|
}
|
||||||
|
active_rooms_.push_back(next_tab_id++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit our regular tabs
|
||||||
|
for (int n = 0; n < active_rooms_.Size;) {
|
||||||
|
bool open = true;
|
||||||
|
|
||||||
|
if (active_rooms_[n] > sizeof(zelda3::kRoomNames) / 4) {
|
||||||
|
active_rooms_.erase(active_rooms_.Data + n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem(zelda3::kRoomNames[active_rooms_[n]].data(), &open, ImGuiTabItemFlags_None)) {
|
||||||
|
current_active_room_tab_ = n;
|
||||||
|
DrawDungeonCanvas(active_rooms_[n]);
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open)
|
||||||
|
active_rooms_.erase(active_rooms_.Data + n);
|
||||||
|
else
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
|
||||||
|
// Validate room_id and ROM
|
||||||
|
if (room_id < 0 || room_id >= 128) {
|
||||||
|
ImGui::Text("Invalid room ID: %d", room_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
ImGui::Text("ROM not loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
|
if (rooms_) {
|
||||||
|
gui::InputHexByte("Layout", &(*rooms_)[room_id].layout);
|
||||||
|
ImGui::SameLine();
|
||||||
|
gui::InputHexByte("Blockset", &(*rooms_)[room_id].blockset);
|
||||||
|
ImGui::SameLine();
|
||||||
|
gui::InputHexByte("Spriteset", &(*rooms_)[room_id].spriteset);
|
||||||
|
ImGui::SameLine();
|
||||||
|
gui::InputHexByte("Palette", &(*rooms_)[room_id].palette);
|
||||||
|
|
||||||
|
gui::InputHexByte("Floor1", &(*rooms_)[room_id].floor1);
|
||||||
|
ImGui::SameLine();
|
||||||
|
gui::InputHexByte("Floor2", &(*rooms_)[room_id].floor2);
|
||||||
|
ImGui::SameLine();
|
||||||
|
gui::InputHexWord("Message ID", &(*rooms_)[room_id].message_id_);
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
if (Button("Load Room Graphics")) {
|
||||||
|
(void)LoadAndRenderRoomGraphics(room_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
canvas_.DrawBackground();
|
||||||
|
canvas_.DrawContextMenu();
|
||||||
|
|
||||||
|
if (rooms_ && rom_->is_loaded()) {
|
||||||
|
auto& room = (*rooms_)[room_id];
|
||||||
|
|
||||||
|
// Automatically load room graphics if not already loaded
|
||||||
|
if (room.blocks().empty()) {
|
||||||
|
(void)LoadAndRenderRoomGraphics(room_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load room objects if not already loaded
|
||||||
|
if (room.GetTileObjects().empty()) {
|
||||||
|
room.LoadObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render background layers with proper positioning
|
||||||
|
RenderRoomBackgroundLayers(room_id);
|
||||||
|
|
||||||
|
// Render room objects on top of background using the room's palette
|
||||||
|
if (current_palette_id_ < current_palette_group_.size()) {
|
||||||
|
auto room_palette = current_palette_group_[current_palette_id_];
|
||||||
|
for (const auto& object : room.GetTileObjects()) {
|
||||||
|
RenderObjectInCanvas(object, room_palette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_.DrawGrid();
|
||||||
|
canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonCanvasViewer::RenderObjectInCanvas(const zelda3::RoomObject &object,
|
||||||
|
const gfx::SnesPalette &palette) {
|
||||||
|
// Validate ROM is loaded
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a mutable copy of the object to ensure tiles are loaded
|
||||||
|
auto mutable_object = object;
|
||||||
|
mutable_object.set_rom(rom_);
|
||||||
|
mutable_object.EnsureTilesLoaded();
|
||||||
|
|
||||||
|
// Check if tiles were loaded successfully
|
||||||
|
if (mutable_object.tiles().empty()) {
|
||||||
|
return; // Skip objects without tiles
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert room coordinates to canvas coordinates using helper function
|
||||||
|
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
|
||||||
|
|
||||||
|
// Check if object is within canvas bounds (accounting for scrolling)
|
||||||
|
if (!IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
|
||||||
|
return; // Skip objects outside visible area
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the object to a bitmap
|
||||||
|
auto render_result = object_renderer_.RenderObject(mutable_object, palette);
|
||||||
|
if (!render_result.ok()) {
|
||||||
|
return; // Skip if rendering failed
|
||||||
|
}
|
||||||
|
|
||||||
|
auto object_bitmap = std::move(render_result.value());
|
||||||
|
|
||||||
|
// Set the palette for the bitmap
|
||||||
|
object_bitmap.SetPalette(palette);
|
||||||
|
|
||||||
|
// Render the bitmap to a texture so it can be drawn
|
||||||
|
core::Renderer::Get().RenderBitmap(&object_bitmap);
|
||||||
|
|
||||||
|
// Draw the object bitmap to the canvas
|
||||||
|
canvas_.DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonCanvasViewer::DisplayObjectInfo(const zelda3::RoomObject &object,
|
||||||
|
int canvas_x, int canvas_y) {
|
||||||
|
// Display object information as text overlay
|
||||||
|
std::string info_text = absl::StrFormat("ID:%d X:%d Y:%d S:%d", object.id_,
|
||||||
|
object.x_, object.y_, object.size_);
|
||||||
|
|
||||||
|
// Draw text at the object position
|
||||||
|
canvas_.DrawText(info_text, canvas_x, canvas_y - 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonCanvasViewer::RenderLayoutObjects(const zelda3::RoomLayout &layout,
|
||||||
|
const gfx::SnesPalette &palette) {
|
||||||
|
// Render layout objects (walls, floors, etc.) as simple colored rectangles
|
||||||
|
for (const auto &layout_obj : layout.GetObjects()) {
|
||||||
|
// Convert room coordinates to canvas coordinates using helper function
|
||||||
|
auto [canvas_x, canvas_y] =
|
||||||
|
RoomToCanvasCoordinates(layout_obj.x(), layout_obj.y());
|
||||||
|
|
||||||
|
// Check if layout object is within canvas bounds
|
||||||
|
if (!IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
|
||||||
|
continue; // Skip objects outside visible area
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose color based on object type
|
||||||
|
gfx::SnesColor color;
|
||||||
|
switch (layout_obj.type()) {
|
||||||
|
case zelda3::RoomLayoutObject::Type::kWall:
|
||||||
|
color = gfx::SnesColor(0x7FFF); // Gray
|
||||||
|
break;
|
||||||
|
case zelda3::RoomLayoutObject::Type::kFloor:
|
||||||
|
color = gfx::SnesColor(0x4210); // Dark brown
|
||||||
|
break;
|
||||||
|
case zelda3::RoomLayoutObject::Type::kCeiling:
|
||||||
|
color = gfx::SnesColor(0x739C); // Light gray
|
||||||
|
break;
|
||||||
|
case zelda3::RoomLayoutObject::Type::kPit:
|
||||||
|
color = gfx::SnesColor(0x0000); // Black
|
||||||
|
break;
|
||||||
|
case zelda3::RoomLayoutObject::Type::kWater:
|
||||||
|
color = gfx::SnesColor(0x001F); // Blue
|
||||||
|
break;
|
||||||
|
case zelda3::RoomLayoutObject::Type::kStairs:
|
||||||
|
color = gfx::SnesColor(0x7E0F); // Yellow
|
||||||
|
break;
|
||||||
|
case zelda3::RoomLayoutObject::Type::kDoor:
|
||||||
|
color = gfx::SnesColor(0xF800); // Red
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = gfx::SnesColor(0x7C1F); // Magenta for unknown
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a simple rectangle for the layout object
|
||||||
|
canvas_.DrawRect(canvas_x, canvas_y, 16, 16,
|
||||||
|
gui::ConvertSnesColorToImVec4(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coordinate conversion helper functions
|
||||||
|
std::pair<int, int> DungeonCanvasViewer::RoomToCanvasCoordinates(int room_x,
|
||||||
|
int room_y) const {
|
||||||
|
// Convert room coordinates (16x16 tile units) to canvas coordinates (pixels)
|
||||||
|
return {room_x * 16, room_y * 16};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> DungeonCanvasViewer::CanvasToRoomCoordinates(int canvas_x,
|
||||||
|
int canvas_y) const {
|
||||||
|
// Convert canvas coordinates (pixels) to room coordinates (16x16 tile units)
|
||||||
|
return {canvas_x / 16, canvas_y / 16};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DungeonCanvasViewer::IsWithinCanvasBounds(int canvas_x, int canvas_y,
|
||||||
|
int margin) const {
|
||||||
|
// Check if coordinates are within canvas bounds with optional margin
|
||||||
|
auto canvas_width = canvas_.width();
|
||||||
|
auto canvas_height = canvas_.height();
|
||||||
|
return (canvas_x >= -margin && canvas_y >= -margin &&
|
||||||
|
canvas_x <= canvas_width + margin &&
|
||||||
|
canvas_y <= canvas_height + margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Room graphics management methods
|
||||||
|
absl::Status DungeonCanvasViewer::LoadAndRenderRoomGraphics(int room_id) {
|
||||||
|
if (room_id < 0 || room_id >= 128) {
|
||||||
|
return absl::InvalidArgumentError("Invalid room ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return absl::FailedPreconditionError("ROM not loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rooms_) {
|
||||||
|
return absl::FailedPreconditionError("Room data not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& room = (*rooms_)[room_id];
|
||||||
|
|
||||||
|
// Load room graphics with proper blockset
|
||||||
|
room.LoadRoomGraphics(room.blockset);
|
||||||
|
|
||||||
|
// Load the room's palette with bounds checking
|
||||||
|
if (room.palette < rom_->paletteset_ids.size() &&
|
||||||
|
!rom_->paletteset_ids[room.palette].empty()) {
|
||||||
|
auto dungeon_palette_ptr = rom_->paletteset_ids[room.palette][0];
|
||||||
|
auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr);
|
||||||
|
if (palette_id.ok()) {
|
||||||
|
current_palette_group_id_ = palette_id.value() / 180;
|
||||||
|
if (current_palette_group_id_ < rom_->palette_group().dungeon_main.size()) {
|
||||||
|
auto full_palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
|
||||||
|
ASSIGN_OR_RETURN(current_palette_group_,
|
||||||
|
gfx::CreatePaletteGroupFromLargePalette(full_palette));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the room graphics to the graphics arena
|
||||||
|
room.RenderRoomGraphics();
|
||||||
|
|
||||||
|
// Update the background layers with proper palette
|
||||||
|
RETURN_IF_ERROR(UpdateRoomBackgroundLayers(room_id));
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status DungeonCanvasViewer::UpdateRoomBackgroundLayers(int room_id) {
|
||||||
|
if (room_id < 0 || room_id >= 128) {
|
||||||
|
return absl::InvalidArgumentError("Invalid room ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return absl::FailedPreconditionError("ROM not loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rooms_) {
|
||||||
|
return absl::FailedPreconditionError("Room data not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& room = (*rooms_)[room_id];
|
||||||
|
|
||||||
|
// Validate palette group access
|
||||||
|
if (current_palette_group_id_ >= rom_->palette_group().dungeon_main.size()) {
|
||||||
|
return absl::FailedPreconditionError("Invalid palette group ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current room's palette
|
||||||
|
auto current_palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
|
||||||
|
|
||||||
|
// Update BG1 (background layer 1) with proper palette
|
||||||
|
if (room.blocks().size() >= 8) {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
int block = room.blocks()[i];
|
||||||
|
if (block >= 0 && block < gfx::Arena::Get().gfx_sheets().size()) {
|
||||||
|
if (current_palette_id_ < current_palette_group_.size()) {
|
||||||
|
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
|
||||||
|
current_palette_group_[current_palette_id_], 0);
|
||||||
|
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update BG2 (background layer 2) with sprite auxiliary palette
|
||||||
|
if (room.blocks().size() >= 16) {
|
||||||
|
auto sprites_aux1_pal_group = rom_->palette_group().sprites_aux1;
|
||||||
|
if (current_palette_id_ < sprites_aux1_pal_group.size()) {
|
||||||
|
for (int i = 8; i < 16; i++) {
|
||||||
|
int block = room.blocks()[i];
|
||||||
|
if (block >= 0 && block < gfx::Arena::Get().gfx_sheets().size()) {
|
||||||
|
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
|
||||||
|
sprites_aux1_pal_group[current_palette_id_], 0);
|
||||||
|
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
|
||||||
|
if (room_id < 0 || room_id >= 128) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rooms_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get canvas dimensions to limit rendering
|
||||||
|
int canvas_width = canvas_.width();
|
||||||
|
int canvas_height = canvas_.height();
|
||||||
|
|
||||||
|
// Validate canvas dimensions
|
||||||
|
if (canvas_width <= 0 || canvas_height <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the room's background layers using the graphics arena
|
||||||
|
// BG1 (background layer 1) - main room graphics
|
||||||
|
auto& bg1_bitmap = gfx::Arena::Get().bg1().bitmap();
|
||||||
|
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) {
|
||||||
|
// Scale the background to fit the canvas
|
||||||
|
float scale_x = static_cast<float>(canvas_width) / bg1_bitmap.width();
|
||||||
|
float scale_y = static_cast<float>(canvas_height) / bg1_bitmap.height();
|
||||||
|
float scale = std::min(scale_x, scale_y);
|
||||||
|
|
||||||
|
int scaled_width = static_cast<int>(bg1_bitmap.width() * scale);
|
||||||
|
int scaled_height = static_cast<int>(bg1_bitmap.height() * scale);
|
||||||
|
int offset_x = (canvas_width - scaled_width) / 2;
|
||||||
|
int offset_y = (canvas_height - scaled_height) / 2;
|
||||||
|
|
||||||
|
canvas_.DrawBitmap(bg1_bitmap, offset_x, offset_y, scale, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BG2 (background layer 2) - sprite graphics (overlay)
|
||||||
|
auto& bg2_bitmap = gfx::Arena::Get().bg2().bitmap();
|
||||||
|
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) {
|
||||||
|
// Scale the background to fit the canvas
|
||||||
|
float scale_x = static_cast<float>(canvas_width) / bg2_bitmap.width();
|
||||||
|
float scale_y = static_cast<float>(canvas_height) / bg2_bitmap.height();
|
||||||
|
float scale = std::min(scale_x, scale_y);
|
||||||
|
|
||||||
|
int scaled_width = static_cast<int>(bg2_bitmap.width() * scale);
|
||||||
|
int scaled_height = static_cast<int>(bg2_bitmap.height() * scale);
|
||||||
|
int offset_x = (canvas_width - scaled_width) / 2;
|
||||||
|
int offset_y = (canvas_height - scaled_height) / 2;
|
||||||
|
|
||||||
|
canvas_.DrawBitmap(bg2_bitmap, offset_x, offset_y, scale, 200); // Semi-transparent overlay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace yaze::editor
|
||||||
91
src/app/editor/dungeon/dungeon_canvas_viewer.h
Normal file
91
src/app/editor/dungeon/dungeon_canvas_viewer.h
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_CANVAS_VIEWER_H
|
||||||
|
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_CANVAS_VIEWER_H
|
||||||
|
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/dungeon/object_renderer.h"
|
||||||
|
#include "app/zelda3/dungeon/room.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handles the main dungeon canvas rendering and interaction
|
||||||
|
*/
|
||||||
|
class DungeonCanvasViewer {
|
||||||
|
public:
|
||||||
|
explicit DungeonCanvasViewer(Rom* rom = nullptr) : rom_(rom), object_renderer_(rom) {}
|
||||||
|
|
||||||
|
void DrawDungeonTabView();
|
||||||
|
void DrawDungeonCanvas(int room_id);
|
||||||
|
|
||||||
|
void set_rom(Rom* rom) {
|
||||||
|
rom_ = rom;
|
||||||
|
object_renderer_.SetROM(rom);
|
||||||
|
}
|
||||||
|
Rom* rom() const { return rom_; }
|
||||||
|
|
||||||
|
// Room data access
|
||||||
|
void set_rooms(std::array<zelda3::Room, 0x128>* rooms) { rooms_ = rooms; }
|
||||||
|
void set_active_rooms(const ImVector<int>& rooms) { active_rooms_ = rooms; }
|
||||||
|
void set_current_active_room_tab(int tab) { current_active_room_tab_ = tab; }
|
||||||
|
|
||||||
|
// Palette access
|
||||||
|
void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; }
|
||||||
|
void set_current_palette_id(uint64_t id) { current_palette_id_ = id; }
|
||||||
|
void set_current_palette_group(const gfx::PaletteGroup& group) { current_palette_group_ = group; }
|
||||||
|
|
||||||
|
// Canvas access
|
||||||
|
gui::Canvas& canvas() { return canvas_; }
|
||||||
|
const gui::Canvas& canvas() const { return canvas_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RenderObjectInCanvas(const zelda3::RoomObject &object,
|
||||||
|
const gfx::SnesPalette &palette);
|
||||||
|
void DisplayObjectInfo(const zelda3::RoomObject &object, int canvas_x,
|
||||||
|
int canvas_y);
|
||||||
|
void RenderLayoutObjects(const zelda3::RoomLayout &layout,
|
||||||
|
const gfx::SnesPalette &palette);
|
||||||
|
|
||||||
|
// Coordinate conversion helpers
|
||||||
|
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const;
|
||||||
|
std::pair<int, int> CanvasToRoomCoordinates(int canvas_x, int canvas_y) const;
|
||||||
|
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const;
|
||||||
|
|
||||||
|
// Room graphics management
|
||||||
|
absl::Status LoadAndRenderRoomGraphics(int room_id);
|
||||||
|
absl::Status UpdateRoomBackgroundLayers(int room_id);
|
||||||
|
void RenderRoomBackgroundLayers(int room_id);
|
||||||
|
|
||||||
|
Rom* rom_ = nullptr;
|
||||||
|
gui::Canvas canvas_{"##DungeonCanvas", ImVec2(0x200, 0x200)};
|
||||||
|
zelda3::ObjectRenderer object_renderer_;
|
||||||
|
|
||||||
|
// Room data
|
||||||
|
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
|
||||||
|
ImVector<int> active_rooms_;
|
||||||
|
int current_active_room_tab_ = 0;
|
||||||
|
|
||||||
|
// Palette data
|
||||||
|
uint64_t current_palette_group_id_ = 0;
|
||||||
|
uint64_t current_palette_id_ = 0;
|
||||||
|
gfx::PaletteGroup current_palette_group_;
|
||||||
|
|
||||||
|
// Object rendering cache
|
||||||
|
struct ObjectRenderCache {
|
||||||
|
int object_id;
|
||||||
|
int object_x, object_y, object_size;
|
||||||
|
uint64_t palette_hash;
|
||||||
|
gfx::Bitmap rendered_bitmap;
|
||||||
|
bool is_valid;
|
||||||
|
};
|
||||||
|
std::vector<ObjectRenderCache> object_render_cache_;
|
||||||
|
uint64_t last_palette_hash_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -91,7 +91,7 @@ absl::Status DungeonEditor::Load() {
|
|||||||
gfx::CreatePaletteGroupFromLargePalette(full_palette_));
|
gfx::CreatePaletteGroupFromLargePalette(full_palette_));
|
||||||
|
|
||||||
CalculateUsageStats();
|
CalculateUsageStats();
|
||||||
|
|
||||||
// Initialize the new editor system
|
// Initialize the new editor system
|
||||||
if (dungeon_editor_system_) {
|
if (dungeon_editor_system_) {
|
||||||
auto status = dungeon_editor_system_->Initialize();
|
auto status = dungeon_editor_system_->Initialize();
|
||||||
@@ -99,7 +99,7 @@ absl::Status DungeonEditor::Load() {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is_loaded_ = true;
|
is_loaded_ = true;
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ absl::Status DungeonEditor::Update() {
|
|||||||
status_ = UpdateDungeonRoomView();
|
status_ = UpdateDungeonRoomView();
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::BeginTabItem("Usage Statistics")) {
|
if (ImGui::BeginTabItem("Usage Statistics")) {
|
||||||
if (is_loaded_) {
|
if (is_loaded_) {
|
||||||
DrawUsageStats();
|
DrawUsageStats();
|
||||||
@@ -221,15 +221,16 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() {
|
|||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BeginTable("#DungeonEditTable", 4, kDungeonTableFlags, ImVec2(0, 0))) {
|
// Simplified 3-column layout: Room/Entrance Selector | Canvas | Object Selector/Editor
|
||||||
TableSetupColumn("Room Selector");
|
if (BeginTable("#DungeonEditTable", 3, kDungeonTableFlags, ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Room/Entrance Selector", ImGuiTableColumnFlags_WidthFixed, 250);
|
||||||
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||||
ImGui::GetContentRegionAvail().x);
|
ImGui::GetContentRegionAvail().x);
|
||||||
TableSetupColumn("Editing Panels");
|
TableSetupColumn("Object Selector/Editor", ImGuiTableColumnFlags_WidthFixed, 300);
|
||||||
TableSetupColumn("Object Selector");
|
|
||||||
TableHeadersRow();
|
TableHeadersRow();
|
||||||
TableNextRow();
|
TableNextRow();
|
||||||
|
|
||||||
|
// Column 1: Room and Entrance Selector
|
||||||
TableNextColumn();
|
TableNextColumn();
|
||||||
if (ImGui::BeginTabBar("##DungeonRoomTabBar")) {
|
if (ImGui::BeginTabBar("##DungeonRoomTabBar")) {
|
||||||
TAB_ITEM("Rooms");
|
TAB_ITEM("Rooms");
|
||||||
@@ -241,14 +242,22 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() {
|
|||||||
ImGui::EndTabBar();
|
ImGui::EndTabBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Column 2: Main Canvas
|
||||||
TableNextColumn();
|
TableNextColumn();
|
||||||
DrawDungeonTabView();
|
DrawDungeonTabView();
|
||||||
|
|
||||||
|
// Column 3: Object Selector and Editor
|
||||||
TableNextColumn();
|
TableNextColumn();
|
||||||
DrawIntegratedEditingPanels();
|
if (ImGui::BeginTabBar("##ObjectEditorTabBar")) {
|
||||||
|
TAB_ITEM("Graphics");
|
||||||
TableNextColumn();
|
DrawTileSelector();
|
||||||
DrawTileSelector();
|
END_TAB_ITEM();
|
||||||
|
TAB_ITEM("Editor");
|
||||||
|
DrawIntegratedEditingPanels();
|
||||||
|
END_TAB_ITEM();
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
@@ -489,6 +498,7 @@ void DungeonEditor::DrawDungeonTabView() {
|
|||||||
|
|
||||||
if (BeginTabItem(zelda3::kRoomNames[active_rooms_[n]].data(), &open,
|
if (BeginTabItem(zelda3::kRoomNames[active_rooms_[n]].data(), &open,
|
||||||
ImGuiTabItemFlags_None)) {
|
ImGuiTabItemFlags_None)) {
|
||||||
|
current_active_room_tab_ = n; // Track which tab is currently active
|
||||||
DrawDungeonCanvas(active_rooms_[n]);
|
DrawDungeonCanvas(active_rooms_[n]);
|
||||||
EndTabItem();
|
EndTabItem();
|
||||||
}
|
}
|
||||||
@@ -505,6 +515,17 @@ void DungeonEditor::DrawDungeonTabView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
||||||
|
// Validate room_id and ROM
|
||||||
|
if (room_id < 0 || room_id >= rooms_.size()) {
|
||||||
|
ImGui::Text("Invalid room ID: %d", room_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
ImGui::Text("ROM not loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::BeginGroup();
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
gui::InputHexByte("Layout", &rooms_[room_id].layout);
|
gui::InputHexByte("Layout", &rooms_[room_id].layout);
|
||||||
@@ -536,30 +557,38 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
|||||||
(void)ReloadAllRoomGraphics();
|
(void)ReloadAllRoomGraphics();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool show_objects = false;
|
// Debug and control popup
|
||||||
ImGui::Checkbox("Show Object Outlines", &show_objects);
|
static bool show_debug_popup = false;
|
||||||
|
if (ImGui::Button("Room Debug Info")) {
|
||||||
static bool render_objects = true;
|
show_debug_popup = true;
|
||||||
ImGui::Checkbox("Render Objects", &render_objects);
|
|
||||||
|
|
||||||
static bool show_object_info = false;
|
|
||||||
ImGui::Checkbox("Show Object Info", &show_object_info);
|
|
||||||
|
|
||||||
static bool show_layout_objects = false;
|
|
||||||
ImGui::Checkbox("Show Layout Objects", &show_layout_objects);
|
|
||||||
|
|
||||||
static bool show_debug_info = false;
|
|
||||||
ImGui::Checkbox("Show Debug Info", &show_debug_info);
|
|
||||||
|
|
||||||
if (ImGui::Button("Clear Object Cache")) {
|
|
||||||
object_render_cache_.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
if (show_debug_popup) {
|
||||||
ImGui::Text("Cache: %zu objects", object_render_cache_.size());
|
ImGui::OpenPopup("Room Debug Info");
|
||||||
|
show_debug_popup = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Object statistics and metadata
|
if (ImGui::BeginPopupModal("Room Debug Info", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
if (show_debug_info) {
|
static bool show_objects = false;
|
||||||
|
ImGui::Checkbox("Show Object Outlines", &show_objects);
|
||||||
|
|
||||||
|
static bool render_objects = true;
|
||||||
|
ImGui::Checkbox("Render Objects", &render_objects);
|
||||||
|
|
||||||
|
static bool show_object_info = false;
|
||||||
|
ImGui::Checkbox("Show Object Info", &show_object_info);
|
||||||
|
|
||||||
|
static bool show_layout_objects = false;
|
||||||
|
ImGui::Checkbox("Show Layout Objects", &show_layout_objects);
|
||||||
|
|
||||||
|
if (ImGui::Button("Clear Object Cache")) {
|
||||||
|
object_render_cache_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text("Cache: %zu objects", object_render_cache_.size());
|
||||||
|
|
||||||
|
// Object statistics and metadata
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("Room Statistics:");
|
ImGui::Text("Room Statistics:");
|
||||||
ImGui::Text("Objects: %zu", rooms_[room_id].GetTileObjects().size());
|
ImGui::Text("Objects: %zu", rooms_[room_id].GetTileObjects().size());
|
||||||
@@ -595,39 +624,44 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
|||||||
ImGui::Text("Walls: %zu", walls.size());
|
ImGui::Text("Walls: %zu", walls.size());
|
||||||
ImGui::Text("Floors: %zu", floors.size());
|
ImGui::Text("Floors: %zu", floors.size());
|
||||||
ImGui::Text("Doors: %zu", doors.size());
|
ImGui::Text("Doors: %zu", doors.size());
|
||||||
}
|
|
||||||
|
|
||||||
// Object selection and editing
|
// Object selection and editing
|
||||||
static int selected_object_id = -1;
|
static int selected_object_id = -1;
|
||||||
if (ImGui::Button("Select Object")) {
|
if (ImGui::Button("Select Object")) {
|
||||||
// This would open an object selection dialog
|
// This would open an object selection dialog
|
||||||
// For now, just cycle through objects
|
// For now, just cycle through objects
|
||||||
if (!rooms_[room_id].GetTileObjects().empty()) {
|
if (!rooms_[room_id].GetTileObjects().empty()) {
|
||||||
selected_object_id =
|
selected_object_id =
|
||||||
(selected_object_id + 1) % rooms_[room_id].GetTileObjects().size();
|
(selected_object_id + 1) % rooms_[room_id].GetTileObjects().size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (selected_object_id >= 0 &&
|
if (selected_object_id >= 0 &&
|
||||||
selected_object_id < (int)rooms_[room_id].GetTileObjects().size()) {
|
selected_object_id < (int)rooms_[room_id].GetTileObjects().size()) {
|
||||||
const auto &selected_obj =
|
const auto &selected_obj =
|
||||||
rooms_[room_id].GetTileObjects()[selected_object_id];
|
rooms_[room_id].GetTileObjects()[selected_object_id];
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("Selected Object:");
|
ImGui::Text("Selected Object:");
|
||||||
ImGui::Text("ID: 0x%02X", selected_obj.id_);
|
ImGui::Text("ID: 0x%02X", selected_obj.id_);
|
||||||
ImGui::Text("Position: (%d, %d)", selected_obj.x_, selected_obj.y_);
|
ImGui::Text("Position: (%d, %d)", selected_obj.x_, selected_obj.y_);
|
||||||
ImGui::Text("Size: 0x%02X", selected_obj.size_);
|
ImGui::Text("Size: 0x%02X", selected_obj.size_);
|
||||||
ImGui::Text("Layer: %d", static_cast<int>(selected_obj.layer_));
|
ImGui::Text("Layer: %d", static_cast<int>(selected_obj.layer_));
|
||||||
ImGui::Text("Tile Count: %d", selected_obj.GetTileCount());
|
ImGui::Text("Tile Count: %d", selected_obj.GetTileCount());
|
||||||
|
|
||||||
// Object editing controls
|
// Object editing controls
|
||||||
if (ImGui::Button("Edit Object")) {
|
if (ImGui::Button("Edit Object")) {
|
||||||
// This would open an object editing dialog
|
// This would open an object editing dialog
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Delete Object")) {
|
||||||
|
// This would remove the object from the room
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button("Delete Object")) {
|
if (ImGui::Button("Close")) {
|
||||||
// This would remove the object from the room
|
ImGui::CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::EndGroup();
|
ImGui::EndGroup();
|
||||||
@@ -640,77 +674,19 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
|||||||
(void)LoadAndRenderRoomGraphics(room_id);
|
(void)LoadAndRenderRoomGraphics(room_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load room objects if not already loaded
|
||||||
|
if (rooms_[room_id].GetTileObjects().empty()) {
|
||||||
|
rooms_[room_id].LoadObjects();
|
||||||
|
}
|
||||||
|
|
||||||
// Render background layers with proper positioning
|
// Render background layers with proper positioning
|
||||||
RenderRoomBackgroundLayers(room_id);
|
RenderRoomBackgroundLayers(room_id);
|
||||||
|
|
||||||
// Render room objects on top of background
|
// Render room objects on top of background using the room's palette
|
||||||
for (const auto& object : rooms_[room_id].GetTileObjects()) {
|
if (current_palette_id_ < current_palette_group_.size()) {
|
||||||
RenderObjectInCanvas(object, current_palette_group_[current_palette_id_]);
|
auto room_palette = current_palette_group_[current_palette_id_];
|
||||||
}
|
for (const auto& object : rooms_[room_id].GetTileObjects()) {
|
||||||
|
RenderObjectInCanvas(object, room_palette);
|
||||||
if (show_objects || render_objects) {
|
|
||||||
// Get the current room's palette for object rendering
|
|
||||||
auto current_palette =
|
|
||||||
rom()->palette_group().dungeon_main[current_palette_group_id_];
|
|
||||||
|
|
||||||
// Clear cache if palette changed
|
|
||||||
uint64_t current_palette_hash = 0;
|
|
||||||
for (size_t i = 0; i < current_palette.size() && i < 16; ++i) {
|
|
||||||
current_palette_hash ^=
|
|
||||||
std::hash<uint16_t>{}(current_palette[i].snes()) + 0x9e3779b9 +
|
|
||||||
(current_palette_hash << 6) + (current_palette_hash >> 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_palette_hash != last_palette_hash_) {
|
|
||||||
object_render_cache_.clear();
|
|
||||||
last_palette_hash_ = current_palette_hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render layout objects (walls, floors, etc.) first
|
|
||||||
if (show_layout_objects) {
|
|
||||||
RenderLayoutObjects(rooms_[room_id].GetLayout(), current_palette);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rooms_[room_id].GetTileObjects().empty()) {
|
|
||||||
// Load the objects for the room
|
|
||||||
rooms_[room_id].LoadObjects();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render regular room objects
|
|
||||||
for (const auto &object : rooms_[room_id].GetTileObjects()) {
|
|
||||||
// Convert room coordinates to canvas coordinates using helper function
|
|
||||||
auto [canvas_x, canvas_y] =
|
|
||||||
RoomToCanvasCoordinates(object.x_, object.y_);
|
|
||||||
|
|
||||||
if (show_objects) {
|
|
||||||
// Draw object outline - use size_ to determine dimensions
|
|
||||||
int outline_width = 16; // Default 16x16
|
|
||||||
int outline_height = 16;
|
|
||||||
|
|
||||||
// Calculate dimensions based on object size
|
|
||||||
if (object.size_ > 0) {
|
|
||||||
// Size encoding: bits 0-1 = width, bits 2-3 = height
|
|
||||||
int width_bits = object.size_ & 0x03;
|
|
||||||
int height_bits = (object.size_ >> 2) & 0x03;
|
|
||||||
|
|
||||||
outline_width = (width_bits + 1) * 16;
|
|
||||||
outline_height = (height_bits + 1) * 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use canvas coordinates for outline
|
|
||||||
canvas_.DrawOutline(canvas_x, canvas_y, outline_width,
|
|
||||||
outline_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (render_objects) {
|
|
||||||
// Render the actual object using ObjectRenderer
|
|
||||||
RenderObjectInCanvas(object, current_palette);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (show_object_info) {
|
|
||||||
// Display object information
|
|
||||||
DisplayObjectInfo(object, canvas_x, canvas_y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -720,6 +696,11 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
|||||||
|
|
||||||
void DungeonEditor::RenderObjectInCanvas(const zelda3::RoomObject &object,
|
void DungeonEditor::RenderObjectInCanvas(const zelda3::RoomObject &object,
|
||||||
const gfx::SnesPalette &palette) {
|
const gfx::SnesPalette &palette) {
|
||||||
|
// Validate ROM is loaded
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a mutable copy of the object to ensure tiles are loaded
|
// Create a mutable copy of the object to ensure tiles are loaded
|
||||||
auto mutable_object = object;
|
auto mutable_object = object;
|
||||||
mutable_object.set_rom(rom_);
|
mutable_object.set_rom(rom_);
|
||||||
@@ -859,21 +840,52 @@ void DungeonEditor::DrawRoomGraphics() {
|
|||||||
room_gfx_canvas_.DrawContextMenu();
|
room_gfx_canvas_.DrawContextMenu();
|
||||||
room_gfx_canvas_.DrawTileSelector(32);
|
room_gfx_canvas_.DrawTileSelector(32);
|
||||||
if (is_loaded_) {
|
if (is_loaded_) {
|
||||||
auto blocks = rooms_[current_room_id_].blocks();
|
// Use the currently active room tab
|
||||||
|
int active_room_id = 0;
|
||||||
|
if (!active_rooms_.empty() && current_active_room_tab_ < active_rooms_.Size) {
|
||||||
|
active_room_id = active_rooms_[current_active_room_tab_];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto blocks = rooms_[active_room_id].blocks();
|
||||||
|
|
||||||
|
// Load graphics for this room if not already loaded
|
||||||
|
if (blocks.empty()) {
|
||||||
|
(void)LoadAndRenderRoomGraphics(active_room_id);
|
||||||
|
blocks = rooms_[active_room_id].blocks();
|
||||||
|
}
|
||||||
|
|
||||||
int current_block = 0;
|
int current_block = 0;
|
||||||
|
const int max_blocks_per_row = 4; // Limit blocks per row to fit canvas
|
||||||
|
const int block_width = 0x100; // 256 pixels per block
|
||||||
|
const int block_height = 0x40; // 64 pixels per block
|
||||||
|
|
||||||
for (int block : blocks) {
|
for (int block : blocks) {
|
||||||
int offset = height * (current_block + 1);
|
if (current_block >= 16) break; // Only show first 16 blocks
|
||||||
int top_left_y = room_gfx_canvas_.zero_point().y + 2;
|
|
||||||
if (current_block >= 1) {
|
// Ensure the graphics sheet is loaded and has a valid texture
|
||||||
top_left_y = room_gfx_canvas_.zero_point().y + height * current_block;
|
if (block < gfx::Arena::Get().gfx_sheets().size()) {
|
||||||
|
auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
|
||||||
|
|
||||||
|
// Calculate position in a grid layout instead of horizontal concatenation
|
||||||
|
int row = current_block / max_blocks_per_row;
|
||||||
|
int col = current_block % max_blocks_per_row;
|
||||||
|
|
||||||
|
int x = room_gfx_canvas_.zero_point().x + 2 + (col * block_width);
|
||||||
|
int y = room_gfx_canvas_.zero_point().y + 2 + (row * block_height);
|
||||||
|
|
||||||
|
// Ensure we don't exceed canvas bounds
|
||||||
|
if (x + block_width <= room_gfx_canvas_.zero_point().x + room_gfx_canvas_.width() &&
|
||||||
|
y + block_height <= room_gfx_canvas_.zero_point().y + room_gfx_canvas_.height()) {
|
||||||
|
|
||||||
|
// Only draw if the texture is valid
|
||||||
|
if (gfx_sheet.texture() != 0) {
|
||||||
|
room_gfx_canvas_.draw_list()->AddImage(
|
||||||
|
(ImTextureID)(intptr_t)gfx_sheet.texture(),
|
||||||
|
ImVec2(x, y),
|
||||||
|
ImVec2(x + block_width, y + block_height));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
room_gfx_canvas_.draw_list()->AddImage(
|
|
||||||
(ImTextureID)(intptr_t)gfx::Arena::Get()
|
|
||||||
.gfx_sheets()[block]
|
|
||||||
.texture(),
|
|
||||||
ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y),
|
|
||||||
ImVec2(room_gfx_canvas_.zero_point().x + 0x100,
|
|
||||||
room_gfx_canvas_.zero_point().y + offset));
|
|
||||||
current_block += 1;
|
current_block += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -951,9 +963,9 @@ void DungeonEditor::DrawObjectRenderer() {
|
|||||||
|
|
||||||
// Render object preview if available
|
// Render object preview if available
|
||||||
if (object_loaded_ && preview_object_.id_ >= 0) {
|
if (object_loaded_ && preview_object_.id_ >= 0) {
|
||||||
// Render preview object at center of canvas
|
// Render preview object at center of canvas (object_canvas_ is 256x256)
|
||||||
int preview_x = 8 * 16; // Center horizontally (8 tiles * 16 pixels)
|
int preview_x = 128 - 16; // Center horizontally (256/2 - 16 for 32x32 object)
|
||||||
int preview_y = 8 * 16; // Center vertically
|
int preview_y = 128 - 16; // Center vertically
|
||||||
|
|
||||||
auto preview_result =
|
auto preview_result =
|
||||||
object_renderer_.RenderObject(preview_object_, preview_palette_);
|
object_renderer_.RenderObject(preview_object_, preview_palette_);
|
||||||
@@ -1283,7 +1295,7 @@ void DungeonEditor::DrawObjectEditor() {
|
|||||||
// Object count and selection info
|
// Object count and selection info
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("Objects: %zu", object_editor_->GetObjectCount());
|
ImGui::Text("Objects: %zu", object_editor_->GetObjectCount());
|
||||||
|
|
||||||
auto selection = object_editor_->GetSelection();
|
auto selection = object_editor_->GetSelection();
|
||||||
if (!selection.selected_objects.empty()) {
|
if (!selection.selected_objects.empty()) {
|
||||||
ImGui::Text("Selected: %zu objects", selection.selected_objects.size());
|
ImGui::Text("Selected: %zu objects", selection.selected_objects.size());
|
||||||
@@ -1309,11 +1321,11 @@ void DungeonEditor::DrawSpriteEditor() {
|
|||||||
// Display current room sprites
|
// Display current room sprites
|
||||||
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
||||||
auto sprites_result = dungeon_editor_system_->GetSpritesByRoom(current_room);
|
auto sprites_result = dungeon_editor_system_->GetSpritesByRoom(current_room);
|
||||||
|
|
||||||
if (sprites_result.ok()) {
|
if (sprites_result.ok()) {
|
||||||
auto sprites = sprites_result.value();
|
auto sprites = sprites_result.value();
|
||||||
ImGui::Text("Sprites in room %d: %zu", current_room, sprites.size());
|
ImGui::Text("Sprites in room %d: %zu", current_room, sprites.size());
|
||||||
|
|
||||||
for (const auto &sprite : sprites) {
|
for (const auto &sprite : sprites) {
|
||||||
ImGui::Text("ID: %d, Type: %d, Position: (%d, %d)", sprite.sprite_id,
|
ImGui::Text("ID: %d, Type: %d, Position: (%d, %d)", sprite.sprite_id,
|
||||||
static_cast<int>(sprite.type), sprite.x, sprite.y);
|
static_cast<int>(sprite.type), sprite.x, sprite.y);
|
||||||
@@ -1343,7 +1355,7 @@ void DungeonEditor::DrawSpriteEditor() {
|
|||||||
sprite_data.x = new_sprite_x;
|
sprite_data.x = new_sprite_x;
|
||||||
sprite_data.y = new_sprite_y;
|
sprite_data.y = new_sprite_y;
|
||||||
sprite_data.layer = new_sprite_layer;
|
sprite_data.layer = new_sprite_layer;
|
||||||
|
|
||||||
auto status = dungeon_editor_system_->AddSprite(sprite_data);
|
auto status = dungeon_editor_system_->AddSprite(sprite_data);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
ImGui::Text("Error adding sprite: %s", status.message().data());
|
ImGui::Text("Error adding sprite: %s", status.message().data());
|
||||||
@@ -1363,13 +1375,13 @@ void DungeonEditor::DrawItemEditor() {
|
|||||||
// Display current room items
|
// Display current room items
|
||||||
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
||||||
auto items_result = dungeon_editor_system_->GetItemsByRoom(current_room);
|
auto items_result = dungeon_editor_system_->GetItemsByRoom(current_room);
|
||||||
|
|
||||||
if (items_result.ok()) {
|
if (items_result.ok()) {
|
||||||
auto items = items_result.value();
|
auto items = items_result.value();
|
||||||
ImGui::Text("Items in room %d: %zu", current_room, items.size());
|
ImGui::Text("Items in room %d: %zu", current_room, items.size());
|
||||||
|
|
||||||
for (const auto &item : items) {
|
for (const auto &item : items) {
|
||||||
ImGui::Text("ID: %d, Type: %d, Position: (%d, %d), Hidden: %s",
|
ImGui::Text("ID: %d, Type: %d, Position: (%d, %d), Hidden: %s",
|
||||||
item.item_id, static_cast<int>(item.type), item.x, item.y,
|
item.item_id, static_cast<int>(item.type), item.x, item.y,
|
||||||
item.is_hidden ? "Yes" : "No");
|
item.is_hidden ? "Yes" : "No");
|
||||||
}
|
}
|
||||||
@@ -1399,7 +1411,7 @@ void DungeonEditor::DrawItemEditor() {
|
|||||||
item_data.y = new_item_y;
|
item_data.y = new_item_y;
|
||||||
item_data.room_id = current_room;
|
item_data.room_id = current_room;
|
||||||
item_data.is_hidden = new_item_hidden;
|
item_data.is_hidden = new_item_hidden;
|
||||||
|
|
||||||
auto status = dungeon_editor_system_->AddItem(item_data);
|
auto status = dungeon_editor_system_->AddItem(item_data);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
ImGui::Text("Error adding item: %s", status.message().data());
|
ImGui::Text("Error adding item: %s", status.message().data());
|
||||||
@@ -1420,16 +1432,16 @@ void DungeonEditor::DrawEntranceEditor() {
|
|||||||
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
||||||
auto entrances_result =
|
auto entrances_result =
|
||||||
dungeon_editor_system_->GetEntrancesByRoom(current_room);
|
dungeon_editor_system_->GetEntrancesByRoom(current_room);
|
||||||
|
|
||||||
if (entrances_result.ok()) {
|
if (entrances_result.ok()) {
|
||||||
auto entrances = entrances_result.value();
|
auto entrances = entrances_result.value();
|
||||||
ImGui::Text("Entrances in room %d: %zu", current_room, entrances.size());
|
ImGui::Text("Entrances in room %d: %zu", current_room, entrances.size());
|
||||||
|
|
||||||
for (const auto &entrance : entrances) {
|
for (const auto &entrance : entrances) {
|
||||||
ImGui::Text(
|
ImGui::Text(
|
||||||
"ID: %d, Type: %d, Target Room: %d, Target Position: (%d, %d)",
|
"ID: %d, Type: %d, Target Room: %d, Target Position: (%d, %d)",
|
||||||
entrance.entrance_id, static_cast<int>(entrance.type),
|
entrance.entrance_id, static_cast<int>(entrance.type),
|
||||||
entrance.target_room_id, entrance.target_x, entrance.target_y);
|
entrance.target_room_id, entrance.target_x, entrance.target_y);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("Error loading entrances: %s",
|
ImGui::Text("Error loading entrances: %s",
|
||||||
@@ -1472,13 +1484,13 @@ void DungeonEditor::DrawDoorEditor() {
|
|||||||
// Display current room doors
|
// Display current room doors
|
||||||
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
||||||
auto doors_result = dungeon_editor_system_->GetDoorsByRoom(current_room);
|
auto doors_result = dungeon_editor_system_->GetDoorsByRoom(current_room);
|
||||||
|
|
||||||
if (doors_result.ok()) {
|
if (doors_result.ok()) {
|
||||||
auto doors = doors_result.value();
|
auto doors = doors_result.value();
|
||||||
ImGui::Text("Doors in room %d: %zu", current_room, doors.size());
|
ImGui::Text("Doors in room %d: %zu", current_room, doors.size());
|
||||||
|
|
||||||
for (const auto &door : doors) {
|
for (const auto &door : doors) {
|
||||||
ImGui::Text("ID: %d, Position: (%d, %d), Direction: %d, Target Room: %d",
|
ImGui::Text("ID: %d, Position: (%d, %d), Direction: %d, Target Room: %d",
|
||||||
door.door_id, door.x, door.y, door.direction,
|
door.door_id, door.x, door.y, door.direction,
|
||||||
door.target_room_id);
|
door.target_room_id);
|
||||||
}
|
}
|
||||||
@@ -1522,7 +1534,7 @@ void DungeonEditor::DrawDoorEditor() {
|
|||||||
door_data.is_locked = door_locked;
|
door_data.is_locked = door_locked;
|
||||||
door_data.requires_key = door_requires_key;
|
door_data.requires_key = door_requires_key;
|
||||||
door_data.key_type = door_key_type;
|
door_data.key_type = door_key_type;
|
||||||
|
|
||||||
auto status = dungeon_editor_system_->AddDoor(door_data);
|
auto status = dungeon_editor_system_->AddDoor(door_data);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
ImGui::Text("Error adding door: %s", status.message().data());
|
ImGui::Text("Error adding door: %s", status.message().data());
|
||||||
@@ -1542,14 +1554,14 @@ void DungeonEditor::DrawChestEditor() {
|
|||||||
// Display current room chests
|
// Display current room chests
|
||||||
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
||||||
auto chests_result = dungeon_editor_system_->GetChestsByRoom(current_room);
|
auto chests_result = dungeon_editor_system_->GetChestsByRoom(current_room);
|
||||||
|
|
||||||
if (chests_result.ok()) {
|
if (chests_result.ok()) {
|
||||||
auto chests = chests_result.value();
|
auto chests = chests_result.value();
|
||||||
ImGui::Text("Chests in room %d: %zu", current_room, chests.size());
|
ImGui::Text("Chests in room %d: %zu", current_room, chests.size());
|
||||||
|
|
||||||
for (const auto &chest : chests) {
|
for (const auto &chest : chests) {
|
||||||
ImGui::Text("ID: %d, Position: (%d, %d), Big: %s, Item: %d, Quantity: %d",
|
ImGui::Text("ID: %d, Position: (%d, %d), Big: %s, Item: %d, Quantity: %d",
|
||||||
chest.chest_id, chest.x, chest.y,
|
chest.chest_id, chest.x, chest.y,
|
||||||
chest.is_big_chest ? "Yes" : "No", chest.item_id,
|
chest.is_big_chest ? "Yes" : "No", chest.item_id,
|
||||||
chest.item_quantity);
|
chest.item_quantity);
|
||||||
}
|
}
|
||||||
@@ -1581,7 +1593,7 @@ void DungeonEditor::DrawChestEditor() {
|
|||||||
chest_data.is_big_chest = chest_big;
|
chest_data.is_big_chest = chest_big;
|
||||||
chest_data.item_id = chest_item_id;
|
chest_data.item_id = chest_item_id;
|
||||||
chest_data.item_quantity = chest_item_quantity;
|
chest_data.item_quantity = chest_item_quantity;
|
||||||
|
|
||||||
auto status = dungeon_editor_system_->AddChest(chest_data);
|
auto status = dungeon_editor_system_->AddChest(chest_data);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
ImGui::Text("Error adding chest: %s", status.message().data());
|
ImGui::Text("Error adding chest: %s", status.message().data());
|
||||||
@@ -1971,10 +1983,10 @@ void DungeonEditor::DrawCompactPropertiesEditor() {
|
|||||||
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
||||||
auto properties_result =
|
auto properties_result =
|
||||||
dungeon_editor_system_->GetRoomProperties(current_room);
|
dungeon_editor_system_->GetRoomProperties(current_room);
|
||||||
|
|
||||||
if (properties_result.ok()) {
|
if (properties_result.ok()) {
|
||||||
auto properties = properties_result.value();
|
auto properties = properties_result.value();
|
||||||
|
|
||||||
static char room_name[128] = {0};
|
static char room_name[128] = {0};
|
||||||
static int dungeon_id = 0;
|
static int dungeon_id = 0;
|
||||||
static int floor_level = 0;
|
static int floor_level = 0;
|
||||||
@@ -2006,7 +2018,7 @@ void DungeonEditor::DrawCompactPropertiesEditor() {
|
|||||||
new_properties.is_boss_room = is_boss_room;
|
new_properties.is_boss_room = is_boss_room;
|
||||||
new_properties.is_save_room = is_save_room;
|
new_properties.is_save_room = is_save_room;
|
||||||
new_properties.music_id = music_id;
|
new_properties.music_id = music_id;
|
||||||
|
|
||||||
auto status = dungeon_editor_system_->SetRoomProperties(current_room,
|
auto status = dungeon_editor_system_->SetRoomProperties(current_room,
|
||||||
new_properties);
|
new_properties);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
@@ -2020,7 +2032,7 @@ void DungeonEditor::DrawCompactPropertiesEditor() {
|
|||||||
// Dungeon settings summary
|
// Dungeon settings summary
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("Dungeon Settings");
|
ImGui::Text("Dungeon Settings");
|
||||||
|
|
||||||
auto dungeon_settings_result = dungeon_editor_system_->GetDungeonSettings();
|
auto dungeon_settings_result = dungeon_editor_system_->GetDungeonSettings();
|
||||||
if (dungeon_settings_result.ok()) {
|
if (dungeon_settings_result.ok()) {
|
||||||
auto settings = dungeon_settings_result.value();
|
auto settings = dungeon_settings_result.value();
|
||||||
@@ -2060,11 +2072,30 @@ absl::Status DungeonEditor::LoadAndRenderRoomGraphics(int room_id) {
|
|||||||
return absl::InvalidArgumentError("Invalid room ID");
|
return absl::InvalidArgumentError("Invalid room ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return absl::FailedPreconditionError("ROM not loaded");
|
||||||
|
}
|
||||||
|
|
||||||
auto& room = rooms_[room_id];
|
auto& room = rooms_[room_id];
|
||||||
|
|
||||||
// Load room graphics with proper blockset
|
// Load room graphics with proper blockset
|
||||||
(void)room.LoadRoomGraphics(room.blockset);
|
(void)room.LoadRoomGraphics(room.blockset);
|
||||||
|
|
||||||
|
// Load the room's palette with bounds checking
|
||||||
|
if (room.palette < rom()->paletteset_ids.size() &&
|
||||||
|
!rom()->paletteset_ids[room.palette].empty()) {
|
||||||
|
auto dungeon_palette_ptr = rom()->paletteset_ids[room.palette][0];
|
||||||
|
auto palette_id = rom()->ReadWord(0xDEC4B + dungeon_palette_ptr);
|
||||||
|
if (palette_id.ok()) {
|
||||||
|
current_palette_group_id_ = palette_id.value() / 180;
|
||||||
|
if (current_palette_group_id_ < rom()->palette_group().dungeon_main.size()) {
|
||||||
|
full_palette_ = rom()->palette_group().dungeon_main[current_palette_group_id_];
|
||||||
|
ASSIGN_OR_RETURN(current_palette_group_,
|
||||||
|
gfx::CreatePaletteGroupFromLargePalette(full_palette_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Render the room graphics to the graphics arena
|
// Render the room graphics to the graphics arena
|
||||||
(void)room.RenderRoomGraphics();
|
(void)room.RenderRoomGraphics();
|
||||||
|
|
||||||
@@ -2091,8 +2122,17 @@ absl::Status DungeonEditor::UpdateRoomBackgroundLayers(int room_id) {
|
|||||||
return absl::InvalidArgumentError("Invalid room ID");
|
return absl::InvalidArgumentError("Invalid room ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return absl::FailedPreconditionError("ROM not loaded");
|
||||||
|
}
|
||||||
|
|
||||||
auto& room = rooms_[room_id];
|
auto& room = rooms_[room_id];
|
||||||
|
|
||||||
|
// Validate palette group access
|
||||||
|
if (current_palette_group_id_ >= rom()->palette_group().dungeon_main.size()) {
|
||||||
|
return absl::FailedPreconditionError("Invalid palette group ID");
|
||||||
|
}
|
||||||
|
|
||||||
// Get the current room's palette
|
// Get the current room's palette
|
||||||
auto current_palette = rom()->palette_group().dungeon_main[current_palette_group_id_];
|
auto current_palette = rom()->palette_group().dungeon_main[current_palette_group_id_];
|
||||||
|
|
||||||
@@ -2100,10 +2140,12 @@ absl::Status DungeonEditor::UpdateRoomBackgroundLayers(int room_id) {
|
|||||||
if (room.blocks().size() >= 8) {
|
if (room.blocks().size() >= 8) {
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
int block = room.blocks()[i];
|
int block = room.blocks()[i];
|
||||||
if (block < gfx::Arena::Get().gfx_sheets().size()) {
|
if (block >= 0 && block < gfx::Arena::Get().gfx_sheets().size()) {
|
||||||
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
|
if (current_palette_id_ < current_palette_group_.size()) {
|
||||||
current_palette_group_[current_palette_id_], 0);
|
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
|
||||||
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
|
current_palette_group_[current_palette_id_], 0);
|
||||||
|
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2111,12 +2153,14 @@ absl::Status DungeonEditor::UpdateRoomBackgroundLayers(int room_id) {
|
|||||||
// Update BG2 (background layer 2) with sprite auxiliary palette
|
// Update BG2 (background layer 2) with sprite auxiliary palette
|
||||||
if (room.blocks().size() >= 16) {
|
if (room.blocks().size() >= 16) {
|
||||||
auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1;
|
auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1;
|
||||||
for (int i = 8; i < 16; i++) {
|
if (current_palette_id_ < sprites_aux1_pal_group.size()) {
|
||||||
int block = room.blocks()[i];
|
for (int i = 8; i < 16; i++) {
|
||||||
if (block < gfx::Arena::Get().gfx_sheets().size()) {
|
int block = room.blocks()[i];
|
||||||
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
|
if (block >= 0 && block < gfx::Arena::Get().gfx_sheets().size()) {
|
||||||
sprites_aux1_pal_group[current_palette_id_], 0);
|
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
|
||||||
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
|
sprites_aux1_pal_group[current_palette_id_], 0);
|
||||||
|
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2129,60 +2173,52 @@ void DungeonEditor::RenderRoomBackgroundLayers(int room_id) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& room = rooms_[room_id];
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return;
|
||||||
// Render BG1 (background layer 1) - main room graphics
|
|
||||||
if (room.blocks().size() >= 8) {
|
|
||||||
// Calculate the total size of all BG1 blocks
|
|
||||||
int bg1_width = 0;
|
|
||||||
int bg1_height = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
int block = room.blocks()[i];
|
|
||||||
if (block < gfx::Arena::Get().gfx_sheets().size()) {
|
|
||||||
auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
|
|
||||||
bg1_width += gfx_sheet.width();
|
|
||||||
bg1_height = std::max(bg1_height, gfx_sheet.height());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render BG1 blocks in sequence
|
|
||||||
int current_x = 0;
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
int block = room.blocks()[i];
|
|
||||||
if (block < gfx::Arena::Get().gfx_sheets().size()) {
|
|
||||||
auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
|
|
||||||
canvas_.DrawBitmap(gfx_sheet, current_x, 0, 1.0f, 255);
|
|
||||||
current_x += gfx_sheet.width();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render BG2 (background layer 2) - sprite graphics
|
auto& room = rooms_[room_id];
|
||||||
if (room.blocks().size() >= 16) {
|
|
||||||
// Calculate the total size of all BG2 blocks
|
// Get canvas dimensions to limit rendering
|
||||||
int bg2_width = 0;
|
int canvas_width = canvas_.width();
|
||||||
int bg2_height = 0;
|
int canvas_height = canvas_.height();
|
||||||
|
|
||||||
|
// Validate canvas dimensions
|
||||||
|
if (canvas_width <= 0 || canvas_height <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the room's background layers using the graphics arena
|
||||||
|
// BG1 (background layer 1) - main room graphics
|
||||||
|
auto& bg1_bitmap = gfx::Arena::Get().bg1().bitmap();
|
||||||
|
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) {
|
||||||
|
// Scale the background to fit the canvas
|
||||||
|
float scale_x = static_cast<float>(canvas_width) / bg1_bitmap.width();
|
||||||
|
float scale_y = static_cast<float>(canvas_height) / bg1_bitmap.height();
|
||||||
|
float scale = std::min(scale_x, scale_y);
|
||||||
|
|
||||||
for (int i = 8; i < 16; i++) {
|
int scaled_width = static_cast<int>(bg1_bitmap.width() * scale);
|
||||||
int block = room.blocks()[i];
|
int scaled_height = static_cast<int>(bg1_bitmap.height() * scale);
|
||||||
if (block < gfx::Arena::Get().gfx_sheets().size()) {
|
int offset_x = (canvas_width - scaled_width) / 2;
|
||||||
auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
|
int offset_y = (canvas_height - scaled_height) / 2;
|
||||||
bg2_width += gfx_sheet.width();
|
|
||||||
bg2_height = std::max(bg2_height, gfx_sheet.height());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render BG2 blocks in sequence (overlay on BG1)
|
canvas_.DrawBitmap(bg1_bitmap, offset_x, offset_y, scale, 255);
|
||||||
int current_x = 0;
|
}
|
||||||
for (int i = 8; i < 16; i++) {
|
|
||||||
int block = room.blocks()[i];
|
// BG2 (background layer 2) - sprite graphics (overlay)
|
||||||
if (block < gfx::Arena::Get().gfx_sheets().size()) {
|
auto& bg2_bitmap = gfx::Arena::Get().bg2().bitmap();
|
||||||
auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
|
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) {
|
||||||
canvas_.DrawBitmap(gfx_sheet, current_x, 0, 1.0f, 200); // Semi-transparent
|
// Scale the background to fit the canvas
|
||||||
current_x += gfx_sheet.width();
|
float scale_x = static_cast<float>(canvas_width) / bg2_bitmap.width();
|
||||||
}
|
float scale_y = static_cast<float>(canvas_height) / bg2_bitmap.height();
|
||||||
}
|
float scale = std::min(scale_x, scale_y);
|
||||||
|
|
||||||
|
int scaled_width = static_cast<int>(bg2_bitmap.width() * scale);
|
||||||
|
int scaled_height = static_cast<int>(bg2_bitmap.height() * scale);
|
||||||
|
int offset_x = (canvas_width - scaled_width) / 2;
|
||||||
|
int offset_y = (canvas_height - scaled_height) / 2;
|
||||||
|
|
||||||
|
canvas_.DrawBitmap(bg2_bitmap, offset_x, offset_y, scale, 200); // Semi-transparent overlay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
#include "zelda3/dungeon/room.h"
|
#include "zelda3/dungeon/room.h"
|
||||||
#include "zelda3/dungeon/room_entrance.h"
|
#include "zelda3/dungeon/room_entrance.h"
|
||||||
#include "zelda3/dungeon/room_object.h"
|
#include "zelda3/dungeon/room_object.h"
|
||||||
|
#include "dungeon_room_selector.h"
|
||||||
|
#include "dungeon_canvas_viewer.h"
|
||||||
|
#include "dungeon_object_selector.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace editor {
|
namespace editor {
|
||||||
@@ -190,6 +193,7 @@ class DungeonEditor : public Editor {
|
|||||||
uint64_t current_palette_group_id_ = 0;
|
uint64_t current_palette_group_id_ = 0;
|
||||||
|
|
||||||
ImVector<int> active_rooms_;
|
ImVector<int> active_rooms_;
|
||||||
|
int current_active_room_tab_ = 0; // Track which room tab is currently active
|
||||||
|
|
||||||
GfxGroupEditor gfx_group_editor_;
|
GfxGroupEditor gfx_group_editor_;
|
||||||
PaletteEditor palette_editor_;
|
PaletteEditor palette_editor_;
|
||||||
|
|||||||
672
src/app/editor/dungeon/dungeon_object_selector.cc
Normal file
672
src/app/editor/dungeon/dungeon_object_selector.cc
Normal file
@@ -0,0 +1,672 @@
|
|||||||
|
#include "dungeon_object_selector.h"
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "app/core/window.h"
|
||||||
|
#include "app/gfx/arena.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/dungeon/object_renderer.h"
|
||||||
|
#include "app/zelda3/dungeon/room.h"
|
||||||
|
#include "app/zelda3/dungeon/dungeon_editor_system.h"
|
||||||
|
#include "app/zelda3/dungeon/dungeon_object_editor.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "util/hex.h"
|
||||||
|
|
||||||
|
namespace yaze::editor {
|
||||||
|
|
||||||
|
using core::Renderer;
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawTileSelector() {
|
||||||
|
if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||||
|
if (ImGui::BeginTabItem("Room Graphics")) {
|
||||||
|
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3);
|
||||||
|
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
DrawRoomGraphics();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem("Object Renderer")) {
|
||||||
|
DrawObjectRenderer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawObjectRenderer() {
|
||||||
|
if (ImGui::BeginTable("DungeonObjectEditorTable", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV, ImVec2(0, 0))) {
|
||||||
|
ImGui::TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch, ImGui::GetContentRegionAvail().x);
|
||||||
|
ImGui::TableSetupColumn("Canvas");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
BeginChild("DungeonObjectButtons", ImVec2(250, 0), true);
|
||||||
|
|
||||||
|
static int selected_object = 0;
|
||||||
|
int i = 0;
|
||||||
|
for (const auto object_name : zelda3::Type1RoomObjectNames) {
|
||||||
|
if (ImGui::Selectable(object_name.data(), selected_object == i)) {
|
||||||
|
selected_object = i;
|
||||||
|
|
||||||
|
// Create a test object and render it
|
||||||
|
auto test_object = zelda3::RoomObject(i, 8, 8, 0x12, 0); // Center in canvas
|
||||||
|
test_object.set_rom(rom_);
|
||||||
|
test_object.EnsureTilesLoaded();
|
||||||
|
|
||||||
|
// Get current palette
|
||||||
|
if (rom_ && rom_->is_loaded()) {
|
||||||
|
auto palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
|
||||||
|
|
||||||
|
// Render object preview
|
||||||
|
auto result = object_renderer_.GetObjectPreview(test_object, palette);
|
||||||
|
if (result.ok()) {
|
||||||
|
object_loaded_ = true;
|
||||||
|
preview_object_ = test_object; // Store for rendering
|
||||||
|
preview_palette_ = palette;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
// Right side of the table - Canvas
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1), true);
|
||||||
|
|
||||||
|
object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||||
|
object_canvas_.DrawContextMenu();
|
||||||
|
object_canvas_.DrawTileSelector(32);
|
||||||
|
object_canvas_.DrawGrid(32.0f);
|
||||||
|
|
||||||
|
// Render object preview if available
|
||||||
|
if (object_loaded_ && preview_object_.id_ >= 0) {
|
||||||
|
// Render preview object at center of canvas (object_canvas_ is 256x256)
|
||||||
|
int preview_x = 128 - 16; // Center horizontally (256/2 - 16 for 32x32 object)
|
||||||
|
int preview_y = 128 - 16; // Center vertically
|
||||||
|
|
||||||
|
auto preview_result = object_renderer_.RenderObject(preview_object_, preview_palette_);
|
||||||
|
if (preview_result.ok()) {
|
||||||
|
auto preview_bitmap = std::move(preview_result.value());
|
||||||
|
preview_bitmap.SetPalette(preview_palette_);
|
||||||
|
core::Renderer::Get().RenderBitmap(&preview_bitmap);
|
||||||
|
object_canvas_.DrawBitmap(preview_bitmap, preview_x, preview_y, 1.0f, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
EndChild();
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object_loaded_) {
|
||||||
|
ImGui::Begin("Object Preview", &object_loaded_, 0);
|
||||||
|
ImGui::Text("Object ID: 0x%02X", preview_object_.id_);
|
||||||
|
ImGui::Text("Position: (%d, %d)", preview_object_.x_, preview_object_.y_);
|
||||||
|
ImGui::Text("Size: 0x%02X", preview_object_.size_);
|
||||||
|
ImGui::Text("Layer: %d", static_cast<int>(preview_object_.layer_));
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawRoomGraphics() {
|
||||||
|
const auto height = 0x40;
|
||||||
|
room_gfx_canvas_.DrawBackground();
|
||||||
|
room_gfx_canvas_.DrawContextMenu();
|
||||||
|
room_gfx_canvas_.DrawTileSelector(32);
|
||||||
|
|
||||||
|
if (rom_ && rom_->is_loaded() && rooms_) {
|
||||||
|
int active_room_id = current_room_id_;
|
||||||
|
auto& room = (*rooms_)[active_room_id];
|
||||||
|
auto blocks = room.blocks();
|
||||||
|
|
||||||
|
// Load graphics for this room if not already loaded
|
||||||
|
if (blocks.empty()) {
|
||||||
|
room.LoadRoomGraphics(room.blockset);
|
||||||
|
blocks = room.blocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
int current_block = 0;
|
||||||
|
const int max_blocks_per_row = 4; // Limit blocks per row to fit canvas
|
||||||
|
const int block_width = 0x100; // 256 pixels per block
|
||||||
|
const int block_height = 0x40; // 64 pixels per block
|
||||||
|
|
||||||
|
for (int block : blocks) {
|
||||||
|
if (current_block >= 16) break; // Only show first 16 blocks
|
||||||
|
|
||||||
|
// Ensure the graphics sheet is loaded and has a valid texture
|
||||||
|
if (block < gfx::Arena::Get().gfx_sheets().size()) {
|
||||||
|
auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
|
||||||
|
|
||||||
|
// Calculate position in a grid layout instead of horizontal concatenation
|
||||||
|
int row = current_block / max_blocks_per_row;
|
||||||
|
int col = current_block % max_blocks_per_row;
|
||||||
|
|
||||||
|
int x = room_gfx_canvas_.zero_point().x + 2 + (col * block_width);
|
||||||
|
int y = room_gfx_canvas_.zero_point().y + 2 + (row * block_height);
|
||||||
|
|
||||||
|
// Ensure we don't exceed canvas bounds
|
||||||
|
if (x + block_width <= room_gfx_canvas_.zero_point().x + room_gfx_canvas_.width() &&
|
||||||
|
y + block_height <= room_gfx_canvas_.zero_point().y + room_gfx_canvas_.height()) {
|
||||||
|
|
||||||
|
// Only draw if the texture is valid
|
||||||
|
if (gfx_sheet.texture() != 0) {
|
||||||
|
room_gfx_canvas_.draw_list()->AddImage(
|
||||||
|
(ImTextureID)(intptr_t)gfx_sheet.texture(),
|
||||||
|
ImVec2(x, y),
|
||||||
|
ImVec2(x + block_width, y + block_height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_block += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
room_gfx_canvas_.DrawGrid(32.0f);
|
||||||
|
room_gfx_canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawIntegratedEditingPanels() {
|
||||||
|
if (!dungeon_editor_system_ || !object_editor_ || !*dungeon_editor_system_ || !*object_editor_) {
|
||||||
|
ImGui::Text("Editor systems not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a tabbed interface for different editing modes
|
||||||
|
if (ImGui::BeginTabBar("##EditingPanels")) {
|
||||||
|
// Object Editor Tab
|
||||||
|
if (ImGui::BeginTabItem("Objects")) {
|
||||||
|
DrawCompactObjectEditor();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprite Editor Tab
|
||||||
|
if (ImGui::BeginTabItem("Sprites")) {
|
||||||
|
DrawCompactSpriteEditor();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item Editor Tab
|
||||||
|
if (ImGui::BeginTabItem("Items")) {
|
||||||
|
DrawCompactItemEditor();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entrance Editor Tab
|
||||||
|
if (ImGui::BeginTabItem("Entrances")) {
|
||||||
|
DrawCompactEntranceEditor();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Door Editor Tab
|
||||||
|
if (ImGui::BeginTabItem("Doors")) {
|
||||||
|
DrawCompactDoorEditor();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chest Editor Tab
|
||||||
|
if (ImGui::BeginTabItem("Chests")) {
|
||||||
|
DrawCompactChestEditor();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties Tab
|
||||||
|
if (ImGui::BeginTabItem("Properties")) {
|
||||||
|
DrawCompactPropertiesEditor();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawCompactObjectEditor() {
|
||||||
|
if (!object_editor_ || !*object_editor_) {
|
||||||
|
ImGui::Text("Object editor not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& editor = **object_editor_;
|
||||||
|
|
||||||
|
ImGui::Text("Object Editor");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
// Display current editing mode
|
||||||
|
auto mode = editor.GetMode();
|
||||||
|
const char *mode_names[] = {"Select", "Insert", "Delete", "Edit", "Layer", "Preview"};
|
||||||
|
ImGui::Text("Mode: %s", mode_names[static_cast<int>(mode)]);
|
||||||
|
|
||||||
|
// Compact mode selection
|
||||||
|
if (ImGui::Button("Select"))
|
||||||
|
editor.SetMode(zelda3::DungeonObjectEditor::Mode::kSelect);
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Insert"))
|
||||||
|
editor.SetMode(zelda3::DungeonObjectEditor::Mode::kInsert);
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Edit"))
|
||||||
|
editor.SetMode(zelda3::DungeonObjectEditor::Mode::kEdit);
|
||||||
|
|
||||||
|
// Layer and object type selection
|
||||||
|
int current_layer = editor.GetCurrentLayer();
|
||||||
|
if (ImGui::SliderInt("Layer", ¤t_layer, 0, 2)) {
|
||||||
|
editor.SetCurrentLayer(current_layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int current_object_type = editor.GetCurrentObjectType();
|
||||||
|
if (ImGui::InputInt("Object Type", ¤t_object_type, 1, 16)) {
|
||||||
|
if (current_object_type >= 0 && current_object_type <= 0x3FF) {
|
||||||
|
editor.SetCurrentObjectType(current_object_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick configuration checkboxes
|
||||||
|
auto config = editor.GetConfig();
|
||||||
|
if (ImGui::Checkbox("Snap to Grid", &config.snap_to_grid)) {
|
||||||
|
editor.SetConfig(config);
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Checkbox("Show Grid", &config.show_grid)) {
|
||||||
|
editor.SetConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object count and selection info
|
||||||
|
Separator();
|
||||||
|
ImGui::Text("Objects: %zu", editor.GetObjectCount());
|
||||||
|
|
||||||
|
auto selection = editor.GetSelection();
|
||||||
|
if (!selection.selected_objects.empty()) {
|
||||||
|
ImGui::Text("Selected: %zu", selection.selected_objects.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undo/Redo buttons
|
||||||
|
Separator();
|
||||||
|
if (ImGui::Button("Undo") && editor.CanUndo()) {
|
||||||
|
editor.Undo();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Redo") && editor.CanRedo()) {
|
||||||
|
editor.Redo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawCompactSpriteEditor() {
|
||||||
|
if (!dungeon_editor_system_ || !*dungeon_editor_system_) {
|
||||||
|
ImGui::Text("Dungeon editor system not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& system = **dungeon_editor_system_;
|
||||||
|
|
||||||
|
ImGui::Text("Sprite Editor");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
// Display current room sprites
|
||||||
|
auto current_room = system.GetCurrentRoom();
|
||||||
|
auto sprites_result = system.GetSpritesByRoom(current_room);
|
||||||
|
|
||||||
|
if (sprites_result.ok()) {
|
||||||
|
auto sprites = sprites_result.value();
|
||||||
|
ImGui::Text("Sprites in room %d: %zu", current_room, sprites.size());
|
||||||
|
|
||||||
|
// Show first few sprites in compact format
|
||||||
|
int display_count = std::min(3, static_cast<int>(sprites.size()));
|
||||||
|
for (int i = 0; i < display_count; ++i) {
|
||||||
|
const auto &sprite = sprites[i];
|
||||||
|
ImGui::Text("ID:%d Type:%d (%d,%d)", sprite.sprite_id,
|
||||||
|
static_cast<int>(sprite.type), sprite.x, sprite.y);
|
||||||
|
}
|
||||||
|
if (sprites.size() > 3) {
|
||||||
|
ImGui::Text("... and %zu more", sprites.size() - 3);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Error loading sprites");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick sprite placement
|
||||||
|
Separator();
|
||||||
|
ImGui::Text("Quick Add Sprite");
|
||||||
|
|
||||||
|
static int new_sprite_id = 0;
|
||||||
|
static int new_sprite_x = 0;
|
||||||
|
static int new_sprite_y = 0;
|
||||||
|
|
||||||
|
ImGui::InputInt("ID", &new_sprite_id);
|
||||||
|
ImGui::InputInt("X", &new_sprite_x);
|
||||||
|
ImGui::InputInt("Y", &new_sprite_y);
|
||||||
|
|
||||||
|
if (ImGui::Button("Add Sprite")) {
|
||||||
|
zelda3::DungeonEditorSystem::SpriteData sprite_data;
|
||||||
|
sprite_data.sprite_id = new_sprite_id;
|
||||||
|
sprite_data.type = zelda3::DungeonEditorSystem::SpriteType::kEnemy;
|
||||||
|
sprite_data.x = new_sprite_x;
|
||||||
|
sprite_data.y = new_sprite_y;
|
||||||
|
sprite_data.layer = 0;
|
||||||
|
|
||||||
|
auto status = system.AddSprite(sprite_data);
|
||||||
|
if (!status.ok()) {
|
||||||
|
ImGui::Text("Error adding sprite");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawCompactItemEditor() {
|
||||||
|
if (!dungeon_editor_system_ || !*dungeon_editor_system_) {
|
||||||
|
ImGui::Text("Dungeon editor system not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& system = **dungeon_editor_system_;
|
||||||
|
|
||||||
|
ImGui::Text("Item Editor");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
// Display current room items
|
||||||
|
auto current_room = system.GetCurrentRoom();
|
||||||
|
auto items_result = system.GetItemsByRoom(current_room);
|
||||||
|
|
||||||
|
if (items_result.ok()) {
|
||||||
|
auto items = items_result.value();
|
||||||
|
ImGui::Text("Items in room %d: %zu", current_room, items.size());
|
||||||
|
|
||||||
|
// Show first few items in compact format
|
||||||
|
int display_count = std::min(3, static_cast<int>(items.size()));
|
||||||
|
for (int i = 0; i < display_count; ++i) {
|
||||||
|
const auto &item = items[i];
|
||||||
|
ImGui::Text("ID:%d Type:%d (%d,%d)", item.item_id,
|
||||||
|
static_cast<int>(item.type), item.x, item.y);
|
||||||
|
}
|
||||||
|
if (items.size() > 3) {
|
||||||
|
ImGui::Text("... and %zu more", items.size() - 3);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Error loading items");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick item placement
|
||||||
|
Separator();
|
||||||
|
ImGui::Text("Quick Add Item");
|
||||||
|
|
||||||
|
static int new_item_id = 0;
|
||||||
|
static int new_item_x = 0;
|
||||||
|
static int new_item_y = 0;
|
||||||
|
|
||||||
|
ImGui::InputInt("ID", &new_item_id);
|
||||||
|
ImGui::InputInt("X", &new_item_x);
|
||||||
|
ImGui::InputInt("Y", &new_item_y);
|
||||||
|
|
||||||
|
if (ImGui::Button("Add Item")) {
|
||||||
|
zelda3::DungeonEditorSystem::ItemData item_data;
|
||||||
|
item_data.item_id = new_item_id;
|
||||||
|
item_data.type = zelda3::DungeonEditorSystem::ItemType::kKey;
|
||||||
|
item_data.x = new_item_x;
|
||||||
|
item_data.y = new_item_y;
|
||||||
|
item_data.room_id = current_room;
|
||||||
|
item_data.is_hidden = false;
|
||||||
|
|
||||||
|
auto status = system.AddItem(item_data);
|
||||||
|
if (!status.ok()) {
|
||||||
|
ImGui::Text("Error adding item");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawCompactEntranceEditor() {
|
||||||
|
if (!dungeon_editor_system_ || !*dungeon_editor_system_) {
|
||||||
|
ImGui::Text("Dungeon editor system not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& system = **dungeon_editor_system_;
|
||||||
|
|
||||||
|
ImGui::Text("Entrance Editor");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
// Display current room entrances
|
||||||
|
auto current_room = system.GetCurrentRoom();
|
||||||
|
auto entrances_result = system.GetEntrancesByRoom(current_room);
|
||||||
|
|
||||||
|
if (entrances_result.ok()) {
|
||||||
|
auto entrances = entrances_result.value();
|
||||||
|
ImGui::Text("Entrances: %zu", entrances.size());
|
||||||
|
|
||||||
|
for (const auto &entrance : entrances) {
|
||||||
|
ImGui::Text("ID:%d -> Room:%d (%d,%d)", entrance.entrance_id,
|
||||||
|
entrance.target_room_id, entrance.target_x,
|
||||||
|
entrance.target_y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Error loading entrances");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick room connection
|
||||||
|
Separator();
|
||||||
|
ImGui::Text("Connect Rooms");
|
||||||
|
|
||||||
|
static int target_room_id = 0;
|
||||||
|
static int source_x = 0;
|
||||||
|
static int source_y = 0;
|
||||||
|
static int target_x = 0;
|
||||||
|
static int target_y = 0;
|
||||||
|
|
||||||
|
ImGui::InputInt("Target Room", &target_room_id);
|
||||||
|
ImGui::InputInt("Source X", &source_x);
|
||||||
|
ImGui::InputInt("Source Y", &source_y);
|
||||||
|
ImGui::InputInt("Target X", &target_x);
|
||||||
|
ImGui::InputInt("Target Y", &target_y);
|
||||||
|
|
||||||
|
if (ImGui::Button("Connect")) {
|
||||||
|
auto status = system.ConnectRooms(current_room, target_room_id, source_x, source_y, target_x, target_y);
|
||||||
|
if (!status.ok()) {
|
||||||
|
ImGui::Text("Error connecting rooms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawCompactDoorEditor() {
|
||||||
|
if (!dungeon_editor_system_ || !*dungeon_editor_system_) {
|
||||||
|
ImGui::Text("Dungeon editor system not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& system = **dungeon_editor_system_;
|
||||||
|
|
||||||
|
ImGui::Text("Door Editor");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
// Display current room doors
|
||||||
|
auto current_room = system.GetCurrentRoom();
|
||||||
|
auto doors_result = system.GetDoorsByRoom(current_room);
|
||||||
|
|
||||||
|
if (doors_result.ok()) {
|
||||||
|
auto doors = doors_result.value();
|
||||||
|
ImGui::Text("Doors: %zu", doors.size());
|
||||||
|
|
||||||
|
for (const auto &door : doors) {
|
||||||
|
ImGui::Text("ID:%d (%d,%d) -> Room:%d", door.door_id, door.x, door.y,
|
||||||
|
door.target_room_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Error loading doors");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick door creation
|
||||||
|
Separator();
|
||||||
|
ImGui::Text("Add Door");
|
||||||
|
|
||||||
|
static int door_x = 0;
|
||||||
|
static int door_y = 0;
|
||||||
|
static int door_direction = 0;
|
||||||
|
static int door_target_room = 0;
|
||||||
|
|
||||||
|
ImGui::InputInt("X", &door_x);
|
||||||
|
ImGui::InputInt("Y", &door_y);
|
||||||
|
ImGui::SliderInt("Dir", &door_direction, 0, 3);
|
||||||
|
ImGui::InputInt("Target", &door_target_room);
|
||||||
|
|
||||||
|
if (ImGui::Button("Add Door")) {
|
||||||
|
zelda3::DungeonEditorSystem::DoorData door_data;
|
||||||
|
door_data.room_id = current_room;
|
||||||
|
door_data.x = door_x;
|
||||||
|
door_data.y = door_y;
|
||||||
|
door_data.direction = door_direction;
|
||||||
|
door_data.target_room_id = door_target_room;
|
||||||
|
door_data.target_x = door_x;
|
||||||
|
door_data.target_y = door_y;
|
||||||
|
door_data.is_locked = false;
|
||||||
|
door_data.requires_key = false;
|
||||||
|
door_data.key_type = 0;
|
||||||
|
|
||||||
|
auto status = system.AddDoor(door_data);
|
||||||
|
if (!status.ok()) {
|
||||||
|
ImGui::Text("Error adding door");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawCompactChestEditor() {
|
||||||
|
if (!dungeon_editor_system_ || !*dungeon_editor_system_) {
|
||||||
|
ImGui::Text("Dungeon editor system not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& system = **dungeon_editor_system_;
|
||||||
|
|
||||||
|
ImGui::Text("Chest Editor");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
// Display current room chests
|
||||||
|
auto current_room = system.GetCurrentRoom();
|
||||||
|
auto chests_result = system.GetChestsByRoom(current_room);
|
||||||
|
|
||||||
|
if (chests_result.ok()) {
|
||||||
|
auto chests = chests_result.value();
|
||||||
|
ImGui::Text("Chests: %zu", chests.size());
|
||||||
|
|
||||||
|
for (const auto &chest : chests) {
|
||||||
|
ImGui::Text("ID:%d (%d,%d) Item:%d", chest.chest_id, chest.x, chest.y,
|
||||||
|
chest.item_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Error loading chests");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick chest creation
|
||||||
|
Separator();
|
||||||
|
ImGui::Text("Add Chest");
|
||||||
|
|
||||||
|
static int chest_x = 0;
|
||||||
|
static int chest_y = 0;
|
||||||
|
static int chest_item_id = 0;
|
||||||
|
static bool chest_big = false;
|
||||||
|
|
||||||
|
ImGui::InputInt("X", &chest_x);
|
||||||
|
ImGui::InputInt("Y", &chest_y);
|
||||||
|
ImGui::InputInt("Item ID", &chest_item_id);
|
||||||
|
ImGui::Checkbox("Big", &chest_big);
|
||||||
|
|
||||||
|
if (ImGui::Button("Add Chest")) {
|
||||||
|
zelda3::DungeonEditorSystem::ChestData chest_data;
|
||||||
|
chest_data.room_id = current_room;
|
||||||
|
chest_data.x = chest_x;
|
||||||
|
chest_data.y = chest_y;
|
||||||
|
chest_data.is_big_chest = chest_big;
|
||||||
|
chest_data.item_id = chest_item_id;
|
||||||
|
chest_data.item_quantity = 1;
|
||||||
|
|
||||||
|
auto status = system.AddChest(chest_data);
|
||||||
|
if (!status.ok()) {
|
||||||
|
ImGui::Text("Error adding chest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonObjectSelector::DrawCompactPropertiesEditor() {
|
||||||
|
if (!dungeon_editor_system_ || !*dungeon_editor_system_) {
|
||||||
|
ImGui::Text("Dungeon editor system not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& system = **dungeon_editor_system_;
|
||||||
|
|
||||||
|
ImGui::Text("Room Properties");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
auto current_room = system.GetCurrentRoom();
|
||||||
|
auto properties_result = system.GetRoomProperties(current_room);
|
||||||
|
|
||||||
|
if (properties_result.ok()) {
|
||||||
|
auto properties = properties_result.value();
|
||||||
|
|
||||||
|
static char room_name[128] = {0};
|
||||||
|
static int dungeon_id = 0;
|
||||||
|
static int floor_level = 0;
|
||||||
|
static bool is_boss_room = false;
|
||||||
|
static bool is_save_room = false;
|
||||||
|
static int music_id = 0;
|
||||||
|
|
||||||
|
// Copy current values
|
||||||
|
strncpy(room_name, properties.name.c_str(), sizeof(room_name) - 1);
|
||||||
|
dungeon_id = properties.dungeon_id;
|
||||||
|
floor_level = properties.floor_level;
|
||||||
|
is_boss_room = properties.is_boss_room;
|
||||||
|
is_save_room = properties.is_save_room;
|
||||||
|
music_id = properties.music_id;
|
||||||
|
|
||||||
|
ImGui::InputText("Name", room_name, sizeof(room_name));
|
||||||
|
ImGui::InputInt("Dungeon ID", &dungeon_id);
|
||||||
|
ImGui::InputInt("Floor", &floor_level);
|
||||||
|
ImGui::InputInt("Music", &music_id);
|
||||||
|
ImGui::Checkbox("Boss Room", &is_boss_room);
|
||||||
|
ImGui::Checkbox("Save Room", &is_save_room);
|
||||||
|
|
||||||
|
if (ImGui::Button("Save Properties")) {
|
||||||
|
zelda3::DungeonEditorSystem::RoomProperties new_properties;
|
||||||
|
new_properties.room_id = current_room;
|
||||||
|
new_properties.name = room_name;
|
||||||
|
new_properties.dungeon_id = dungeon_id;
|
||||||
|
new_properties.floor_level = floor_level;
|
||||||
|
new_properties.is_boss_room = is_boss_room;
|
||||||
|
new_properties.is_save_room = is_save_room;
|
||||||
|
new_properties.music_id = music_id;
|
||||||
|
|
||||||
|
auto status = system.SetRoomProperties(current_room, new_properties);
|
||||||
|
if (!status.ok()) {
|
||||||
|
ImGui::Text("Error saving properties");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Error loading properties");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dungeon settings summary
|
||||||
|
Separator();
|
||||||
|
ImGui::Text("Dungeon Settings");
|
||||||
|
|
||||||
|
auto dungeon_settings_result = system.GetDungeonSettings();
|
||||||
|
if (dungeon_settings_result.ok()) {
|
||||||
|
auto settings = dungeon_settings_result.value();
|
||||||
|
ImGui::Text("Dungeon: %s", settings.name.c_str());
|
||||||
|
ImGui::Text("Rooms: %d", settings.total_rooms);
|
||||||
|
ImGui::Text("Start: %d", settings.starting_room_id);
|
||||||
|
ImGui::Text("Boss: %d", settings.boss_room_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace yaze::editor
|
||||||
82
src/app/editor/dungeon/dungeon_object_selector.h
Normal file
82
src/app/editor/dungeon/dungeon_object_selector.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_SELECTOR_H
|
||||||
|
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_SELECTOR_H
|
||||||
|
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/dungeon/object_renderer.h"
|
||||||
|
#include "app/zelda3/dungeon/dungeon_object_editor.h"
|
||||||
|
#include "app/zelda3/dungeon/dungeon_editor_system.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handles object selection, preview, and editing UI
|
||||||
|
*/
|
||||||
|
class DungeonObjectSelector {
|
||||||
|
public:
|
||||||
|
explicit DungeonObjectSelector(Rom* rom = nullptr) : rom_(rom), object_renderer_(rom) {}
|
||||||
|
|
||||||
|
void DrawTileSelector();
|
||||||
|
void DrawObjectRenderer();
|
||||||
|
void DrawIntegratedEditingPanels();
|
||||||
|
|
||||||
|
void set_rom(Rom* rom) {
|
||||||
|
rom_ = rom;
|
||||||
|
object_renderer_.SetROM(rom);
|
||||||
|
}
|
||||||
|
Rom* rom() const { return rom_; }
|
||||||
|
|
||||||
|
// Editor system access
|
||||||
|
void set_dungeon_editor_system(std::unique_ptr<zelda3::DungeonEditorSystem>* system) {
|
||||||
|
dungeon_editor_system_ = system;
|
||||||
|
}
|
||||||
|
void set_object_editor(std::shared_ptr<zelda3::DungeonObjectEditor>* editor) {
|
||||||
|
object_editor_ = editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Room data access
|
||||||
|
void set_rooms(std::array<zelda3::Room, 0x128>* rooms) { rooms_ = rooms; }
|
||||||
|
void set_current_room_id(int room_id) { current_room_id_ = room_id; }
|
||||||
|
|
||||||
|
// Palette access
|
||||||
|
void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawRoomGraphics();
|
||||||
|
void DrawCompactObjectEditor();
|
||||||
|
void DrawCompactSpriteEditor();
|
||||||
|
void DrawCompactItemEditor();
|
||||||
|
void DrawCompactEntranceEditor();
|
||||||
|
void DrawCompactDoorEditor();
|
||||||
|
void DrawCompactChestEditor();
|
||||||
|
void DrawCompactPropertiesEditor();
|
||||||
|
|
||||||
|
Rom* rom_ = nullptr;
|
||||||
|
gui::Canvas room_gfx_canvas_{"##RoomGfxCanvas", ImVec2(0x100 + 1, 0x10 * 0x40 + 1)};
|
||||||
|
gui::Canvas object_canvas_;
|
||||||
|
zelda3::ObjectRenderer object_renderer_;
|
||||||
|
|
||||||
|
// Editor systems
|
||||||
|
std::unique_ptr<zelda3::DungeonEditorSystem>* dungeon_editor_system_ = nullptr;
|
||||||
|
std::shared_ptr<zelda3::DungeonObjectEditor>* object_editor_ = nullptr;
|
||||||
|
|
||||||
|
// Room data
|
||||||
|
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
|
||||||
|
int current_room_id_ = 0;
|
||||||
|
|
||||||
|
// Palette data
|
||||||
|
uint64_t current_palette_group_id_ = 0;
|
||||||
|
|
||||||
|
// Object preview system
|
||||||
|
zelda3::RoomObject preview_object_{0, 0, 0, 0, 0};
|
||||||
|
gfx::SnesPalette preview_palette_;
|
||||||
|
bool object_loaded_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
124
src/app/editor/dungeon/dungeon_room_selector.cc
Normal file
124
src/app/editor/dungeon/dungeon_room_selector.cc
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#include "dungeon_room_selector.h"
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/zelda3/dungeon/room.h"
|
||||||
|
#include "app/zelda3/dungeon/room_entrance.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "util/hex.h"
|
||||||
|
|
||||||
|
namespace yaze::editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
|
||||||
|
void DungeonRoomSelector::DrawRoomSelector() {
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
ImGui::Text("ROM not loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gui::InputHexWord("Room ID", ¤t_room_id_, 50.f, true);
|
||||||
|
|
||||||
|
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)9);
|
||||||
|
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
int i = 0;
|
||||||
|
for (const auto each_room_name : zelda3::kRoomNames) {
|
||||||
|
rom_->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
current_room_id_ == i, "Dungeon Room Names", util::HexByte(i),
|
||||||
|
each_room_name.data());
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
current_room_id_ = i;
|
||||||
|
if (!active_rooms_.contains(i)) {
|
||||||
|
active_rooms_.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonRoomSelector::DrawEntranceSelector() {
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
ImGui::Text("ROM not loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entrances_) {
|
||||||
|
ImGui::Text("Entrances not loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto current_entrance = (*entrances_)[current_entrance_id_];
|
||||||
|
gui::InputHexWord("Entrance ID", ¤t_entrance.entrance_id_);
|
||||||
|
gui::InputHexWord("Room ID", ¤t_entrance.room_);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon ID", ¤t_entrance.dungeon_id_, 50.f, true);
|
||||||
|
gui::InputHexByte("Blockset", ¤t_entrance.blockset_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Music", ¤t_entrance.music_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("Floor", ¤t_entrance.floor_);
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
gui::InputHexWord("Player X ", ¤t_entrance.x_position_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Player Y ", ¤t_entrance.y_position_);
|
||||||
|
|
||||||
|
gui::InputHexWord("Camera X", ¤t_entrance.camera_trigger_x_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Camera Y", ¤t_entrance.camera_trigger_y_);
|
||||||
|
|
||||||
|
gui::InputHexWord("Scroll X ", ¤t_entrance.camera_x_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Scroll Y ", ¤t_entrance.camera_y_);
|
||||||
|
|
||||||
|
gui::InputHexWord("Exit", ¤t_entrance.exit_, 50.f, true);
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
ImGui::Text("Camera Boundaries");
|
||||||
|
Separator();
|
||||||
|
ImGui::Text("\t\t\t\t\tNorth East South West");
|
||||||
|
gui::InputHexByte("Quadrant", ¤t_entrance.camera_boundary_qn_, 50.f,
|
||||||
|
true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_qe_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_qs_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_qw_, 50.f, true);
|
||||||
|
|
||||||
|
gui::InputHexByte("Full room", ¤t_entrance.camera_boundary_fn_, 50.f,
|
||||||
|
true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_fe_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_fs_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_fw_, 50.f, true);
|
||||||
|
|
||||||
|
if (BeginChild("EntranceSelector", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
for (int i = 0; i < 0x85 + 7; i++) {
|
||||||
|
rom_->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
current_entrance_id_ == i, "Dungeon Entrance Names",
|
||||||
|
util::HexByte(i), zelda3::kEntranceNames[i].data());
|
||||||
|
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
current_entrance_id_ = i;
|
||||||
|
if (!active_rooms_.contains(i)) {
|
||||||
|
active_rooms_.push_back((*entrances_)[i].room_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace yaze::editor
|
||||||
55
src/app/editor/dungeon/dungeon_room_selector.h
Normal file
55
src/app/editor/dungeon/dungeon_room_selector.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_SELECTOR_H
|
||||||
|
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_SELECTOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/zelda3/dungeon/room_entrance.h"
|
||||||
|
#include "zelda3/dungeon/room.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handles room and entrance selection UI
|
||||||
|
*/
|
||||||
|
class DungeonRoomSelector {
|
||||||
|
public:
|
||||||
|
explicit DungeonRoomSelector(Rom* rom = nullptr) : rom_(rom) {}
|
||||||
|
|
||||||
|
void DrawRoomSelector();
|
||||||
|
void DrawEntranceSelector();
|
||||||
|
|
||||||
|
void set_rom(Rom* rom) { rom_ = rom; }
|
||||||
|
Rom* rom() const { return rom_; }
|
||||||
|
|
||||||
|
// Room selection
|
||||||
|
void set_current_room_id(uint16_t room_id) { current_room_id_ = room_id; }
|
||||||
|
int current_room_id() const { return current_room_id_; }
|
||||||
|
|
||||||
|
void set_active_rooms(const ImVector<int>& rooms) { active_rooms_ = rooms; }
|
||||||
|
const ImVector<int>& active_rooms() const { return active_rooms_; }
|
||||||
|
ImVector<int>& mutable_active_rooms() { return active_rooms_; }
|
||||||
|
|
||||||
|
// Entrance selection
|
||||||
|
void set_current_entrance_id(int entrance_id) { current_entrance_id_ = entrance_id; }
|
||||||
|
int current_entrance_id() const { return current_entrance_id_; }
|
||||||
|
|
||||||
|
// Room data access
|
||||||
|
void set_rooms(std::array<zelda3::Room, 0x128>* rooms) { rooms_ = rooms; }
|
||||||
|
void set_entrances(std::array<zelda3::RoomEntrance, 0x8C>* entrances) { entrances_ = entrances; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Rom* rom_ = nullptr;
|
||||||
|
uint16_t current_room_id_ = 0;
|
||||||
|
int current_entrance_id_ = 0;
|
||||||
|
ImVector<int> active_rooms_;
|
||||||
|
|
||||||
|
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
|
||||||
|
std::array<zelda3::RoomEntrance, 0x8C>* entrances_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -2,6 +2,9 @@ set(
|
|||||||
YAZE_APP_EDITOR_SRC
|
YAZE_APP_EDITOR_SRC
|
||||||
app/editor/editor_manager.cc
|
app/editor/editor_manager.cc
|
||||||
app/editor/dungeon/dungeon_editor.cc
|
app/editor/dungeon/dungeon_editor.cc
|
||||||
|
app/editor/dungeon/dungeon_room_selector.cc
|
||||||
|
app/editor/dungeon/dungeon_canvas_viewer.cc
|
||||||
|
app/editor/dungeon/dungeon_object_selector.cc
|
||||||
app/editor/overworld/overworld_editor.cc
|
app/editor/overworld/overworld_editor.cc
|
||||||
app/editor/sprite/sprite_editor.cc
|
app/editor/sprite/sprite_editor.cc
|
||||||
app/editor/music/music_editor.cc
|
app/editor/music/music_editor.cc
|
||||||
|
|||||||
@@ -233,20 +233,47 @@ constexpr int kGfxBufferRoomSpriteStride = 2048;
|
|||||||
constexpr int kGfxBufferRoomSpriteLastLineOffset = 0x88;
|
constexpr int kGfxBufferRoomSpriteLastLineOffset = 0x88;
|
||||||
|
|
||||||
void Room::CopyRoomGraphicsToBuffer() {
|
void Room::CopyRoomGraphicsToBuffer() {
|
||||||
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto gfx_buffer_data = rom()->mutable_graphics_buffer();
|
auto gfx_buffer_data = rom()->mutable_graphics_buffer();
|
||||||
|
if (!gfx_buffer_data || gfx_buffer_data->empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Copy room graphics to buffer
|
// Copy room graphics to buffer
|
||||||
int sheet_pos = 0;
|
int sheet_pos = 0;
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
|
// Validate block index
|
||||||
|
if (blocks_[i] < 0 || blocks_[i] > 255) {
|
||||||
|
sheet_pos += kGfxBufferRoomOffset;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int data = 0;
|
int data = 0;
|
||||||
int block_offset = blocks_[i] * kGfxBufferRoomOffset;
|
int block_offset = blocks_[i] * kGfxBufferRoomOffset;
|
||||||
|
|
||||||
|
// Validate block_offset bounds
|
||||||
|
if (block_offset < 0 || block_offset >= static_cast<int>(gfx_buffer_data->size())) {
|
||||||
|
sheet_pos += kGfxBufferRoomOffset;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
while (data < kGfxBufferRoomOffset) {
|
while (data < kGfxBufferRoomOffset) {
|
||||||
uint8_t map_byte = (*gfx_buffer_data)[data + block_offset];
|
int buffer_index = data + block_offset;
|
||||||
if (i < 4) {
|
if (buffer_index >= 0 && buffer_index < static_cast<int>(gfx_buffer_data->size())) {
|
||||||
map_byte += kGfxBufferRoomSpriteLastLineOffset;
|
uint8_t map_byte = (*gfx_buffer_data)[buffer_index];
|
||||||
}
|
if (i < 4) {
|
||||||
|
map_byte += kGfxBufferRoomSpriteLastLineOffset;
|
||||||
|
}
|
||||||
|
|
||||||
current_gfx16_[data + sheet_pos] = map_byte;
|
// Validate current_gfx16_ access
|
||||||
|
int gfx_index = data + sheet_pos;
|
||||||
|
if (gfx_index >= 0 && gfx_index < static_cast<int>(sizeof(current_gfx16_))) {
|
||||||
|
current_gfx16_[gfx_index] = map_byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
data++;
|
data++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,21 +312,62 @@ void Room::RenderRoomGraphics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Room::LoadAnimatedGraphics() {
|
void Room::LoadAnimatedGraphics() {
|
||||||
int gfx_ptr = SnesToPc(rom()->version_constants().kGfxAnimatedPointer);
|
if (!rom_ || !rom_->is_loaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto gfx_buffer_data = rom()->mutable_graphics_buffer();
|
auto gfx_buffer_data = rom()->mutable_graphics_buffer();
|
||||||
|
if (!gfx_buffer_data || gfx_buffer_data->empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto rom_data = rom()->vector();
|
auto rom_data = rom()->vector();
|
||||||
|
if (rom_data.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate animated_frame_ bounds
|
||||||
|
if (animated_frame_ < 0 || animated_frame_ > 10) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate background_tileset_ bounds
|
||||||
|
if (background_tileset_ < 0 || background_tileset_ > 255) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gfx_ptr = SnesToPc(rom()->version_constants().kGfxAnimatedPointer);
|
||||||
|
if (gfx_ptr < 0 || gfx_ptr >= static_cast<int>(rom_data.size())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int data = 0;
|
int data = 0;
|
||||||
while (data < 512) {
|
while (data < 512) {
|
||||||
uint8_t map_byte =
|
// Validate buffer access for first operation
|
||||||
(*gfx_buffer_data)[data + (92 * 2048) + (512 * animated_frame_)];
|
int first_offset = data + (92 * 2048) + (512 * animated_frame_);
|
||||||
current_gfx16_[data + (7 * 2048)] = map_byte;
|
if (first_offset >= 0 && first_offset < static_cast<int>(gfx_buffer_data->size())) {
|
||||||
|
uint8_t map_byte = (*gfx_buffer_data)[first_offset];
|
||||||
map_byte =
|
|
||||||
(*gfx_buffer_data)[data +
|
// Validate current_gfx16_ access
|
||||||
(rom_data[gfx_ptr + background_tileset_] * 2048) +
|
int gfx_offset = data + (7 * 2048);
|
||||||
(512 * animated_frame_)];
|
if (gfx_offset >= 0 && gfx_offset < static_cast<int>(sizeof(current_gfx16_))) {
|
||||||
current_gfx16_[data + (7 * 2048) - 512] = map_byte;
|
current_gfx16_[gfx_offset] = map_byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate buffer access for second operation
|
||||||
|
int tileset_index = rom_data[gfx_ptr + background_tileset_];
|
||||||
|
int second_offset = data + (tileset_index * 2048) + (512 * animated_frame_);
|
||||||
|
if (second_offset >= 0 && second_offset < static_cast<int>(gfx_buffer_data->size())) {
|
||||||
|
uint8_t map_byte = (*gfx_buffer_data)[second_offset];
|
||||||
|
|
||||||
|
// Validate current_gfx16_ access
|
||||||
|
int gfx_offset = data + (7 * 2048) - 512;
|
||||||
|
if (gfx_offset >= 0 && gfx_offset < static_cast<int>(sizeof(current_gfx16_))) {
|
||||||
|
current_gfx16_[gfx_offset] = map_byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data++;
|
data++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -873,9 +873,24 @@ void Sprite::DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate input parameters
|
||||||
|
if (sizex <= 0 || sizey <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srcx < 0 || srcy < 0 || pal < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
x += 16;
|
x += 16;
|
||||||
y += 16;
|
y += 16;
|
||||||
int drawid_ = (srcx + (srcy * 16)) + 512;
|
int drawid_ = (srcx + (srcy * 16)) + 512;
|
||||||
|
|
||||||
|
// Validate drawid_ is within reasonable bounds
|
||||||
|
if (drawid_ < 0 || drawid_ > 4096) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto yl = 0; yl < sizey * 8; yl++) {
|
for (auto yl = 0; yl < sizey * 8; yl++) {
|
||||||
for (auto xl = 0; xl < (sizex * 8) / 2; xl++) {
|
for (auto xl = 0; xl < (sizex * 8) / 2; xl++) {
|
||||||
int mx = xl;
|
int mx = xl;
|
||||||
@@ -893,12 +908,21 @@ void Sprite::DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
|
|||||||
|
|
||||||
int tx = ((drawid_ / 0x10) * 0x400) +
|
int tx = ((drawid_ / 0x10) * 0x400) +
|
||||||
((drawid_ - ((drawid_ / 0x10) * 0x10)) * 8);
|
((drawid_ - ((drawid_ / 0x10) * 0x10)) * 8);
|
||||||
auto pixel = current_gfx_[tx + (yl * 0x80) + xl];
|
|
||||||
|
// Validate graphics buffer access
|
||||||
|
int gfx_index = tx + (yl * 0x80) + xl;
|
||||||
|
if (gfx_index < 0 || gfx_index >= static_cast<int>(current_gfx_.size())) {
|
||||||
|
continue; // Skip this pixel if out of bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pixel = current_gfx_[gfx_index];
|
||||||
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
|
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
|
||||||
// position
|
// position
|
||||||
int index = (x) + (y * 64) + (mx + (my * 0x80));
|
int index = (x) + (y * 64) + (mx + (my * 0x80));
|
||||||
|
|
||||||
if (index >= 0 && index <= 4096) {
|
// Validate preview buffer access
|
||||||
|
if (index >= 0 && index < static_cast<int>(preview_gfx_.size()) &&
|
||||||
|
index <= 4096) {
|
||||||
preview_gfx_[index] = (uint8_t)((pixel & 0x0F) + 112 + (pal * 8));
|
preview_gfx_[index] = (uint8_t)((pixel & 0x0F) + 112 + (pal * 8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user