diff --git a/docs/dungeon-integration-tests.md b/docs/dungeon-integration-tests.md deleted file mode 100644 index aa571e82..00000000 --- a/docs/dungeon-integration-tests.md +++ /dev/null @@ -1,226 +0,0 @@ -# Dungeon Integration Tests - -This document describes the comprehensive integration test suite for the Zelda 3 dungeon object rendering system. - -## Overview - -The integration tests provide comprehensive validation of the dungeon object rendering system using both real ROM data and mock implementations. The test suite ensures that all components work together correctly and can handle real-world scenarios. - -## Test Files - -### 1. `dungeon_object_renderer_integration_test.cc` - -**Purpose**: Integration tests using the real Zelda 3 ROM (`build/bin/zelda3.sfc`) - -**Key Features**: -- Tests with actual ROM data from Link to the Past -- Validates against disassembly information from `assets/asm/usdasm/` -- Tests specific rooms mentioned in disassembly (Ganon's room, sewer rooms, Agahnim's tower) -- Comprehensive object type validation -- Performance benchmarking -- Cache effectiveness testing -- Memory usage validation -- ROM integrity validation - -**Test Categories**: -- Basic object rendering functionality -- Multi-palette rendering -- Real room object rendering with specific disassembly rooms -- Performance testing with various object counts -- Cache effectiveness validation -- Object type testing based on disassembly data -- ROM integrity and validation -- Palette validation against vanilla values -- Comprehensive room loading and validation - -### 2. `dungeon_object_renderer_mock_test.cc` - -**Purpose**: Integration tests using mock ROM data for testing without real ROMs - -**Key Features**: -- Mock ROM implementation with realistic data structure -- Mock room generation with different room types -- Mock object creation and rendering -- Performance testing with mock data -- Error handling validation -- Cache functionality testing - -**Mock Components**: -- `MockRom`: Complete mock ROM implementation -- `MockRoomGenerator`: Generates realistic test rooms -- Mock palette data -- Mock object data - -### 3. `dungeon_editor_system_integration_test.cc` - -**Purpose**: Integration tests for the complete dungeon editor system - -**Key Features**: -- Room loading and management -- Object editor integration -- Sprite management (add, update, remove, move) -- Item management (keys, items, hidden items) -- Entrance/exit management and room connections -- Door management with key requirements -- Chest management with item storage -- Room properties and metadata -- Dungeon-wide settings -- Undo/redo functionality -- Validation and error handling -- Performance testing - -## Test Data Sources - -### Real ROM Data -- **ROM File**: `build/bin/zelda3.sfc` (Link to the Past ROM) -- **Disassembly**: `assets/asm/usdasm/` directory -- **Room Data**: Based on room pointers at `0x1F8000` -- **Object Types**: Validated against disassembly object definitions - -### Disassembly Integration -The tests use information from the US disassembly to validate: -- Room IDs and their purposes (Ganon's room: 0x0000, Sewer rooms: 0x0002/0x0012, etc.) -- Object type IDs (chests: 0xF9/0xFA, walls: 0x10, floors: 0x20, etc.) -- Room data structure and pointers -- Palette and graphics data locations - -## Running the Tests - -### Prerequisites -1. Real ROM file at `build/bin/zelda3.sfc` -2. Compiled test suite -3. All dependencies available - -### Command Line -```bash -# Run all dungeon integration tests -./build/bin/yaze_test --gtest_filter="DungeonObjectRendererIntegrationTest.*" - -# Run mock tests only (no ROM required) -./build/bin/yaze_test --gtest_filter="DungeonObjectRendererMockTest.*" - -# Run dungeon editor system tests -./build/bin/yaze_test --gtest_filter="DungeonEditorSystemIntegrationTest.*" - -# Run specific test -./build/bin/yaze_test --gtest_filter="DungeonObjectRendererIntegrationTest.RealRoomObjectRendering" -``` - -### CI/CD Considerations -- Tests are skipped on Linux for automated builds (requires ROM file) -- Mock tests can run in any environment -- Performance tests have reasonable timeouts - -## Test Coverage - -### Object Rendering -- ✅ Basic object rendering -- ✅ Multi-palette rendering -- ✅ Different object types (walls, floors, chests, stairs, doors) -- ✅ Different object sizes and layers -- ✅ Real ROM room data rendering -- ✅ Performance benchmarking -- ✅ Memory usage validation -- ✅ Cache effectiveness - -### Dungeon Editor System -- ✅ Room management (load, save, create, delete) -- ✅ Object editing (insert, delete, move, resize) -- ✅ Sprite management (CRUD operations) -- ✅ Item management (CRUD operations) -- ✅ Entrance/exit management -- ✅ Door management with key requirements -- ✅ Chest management with item storage -- ✅ Room properties and metadata -- ✅ Dungeon-wide settings -- ✅ Undo/redo functionality -- ✅ Validation and error handling - -### Integration Features -- ✅ Real ROM data validation -- ✅ Disassembly data correlation -- ✅ Mock system for testing without ROMs -- ✅ Performance benchmarking -- ✅ Error handling and edge cases -- ✅ Memory management -- ✅ Cache optimization - -## Performance Benchmarks - -The tests include performance benchmarks with expected thresholds: - -- **Object Rendering**: < 100ms for 50 objects -- **Large Object Sets**: < 500ms for 100 objects -- **System Operations**: < 5000ms for 200 operations -- **Cache Hit Rate**: > 50% for repeated operations - -## Validation Points - -### ROM Integrity -- ROM header validation -- Room data pointer validation -- Palette data validation -- Object data structure validation - -### Object Data -- Object type validation against disassembly -- Object size and layer validation -- Object position validation -- Object collision detection - -### System Integration -- Component interaction validation -- Data flow validation -- State management validation -- Error propagation validation - -## Debugging and Troubleshooting - -### Common Issues -1. **ROM Not Found**: Ensure `zelda3.sfc` is in `build/bin/` -2. **Private Member Access**: Some tests skip private member validation -3. **Performance Failures**: Check system resources and adjust thresholds -4. **Memory Issues**: Monitor memory usage during large object tests - -### Debug Output -Tests include extensive debug output: -- Room loading statistics -- Object type discovery -- Performance metrics -- Cache statistics -- Memory usage reports - -## Extending the Tests - -### Adding New Tests -1. Follow existing test patterns -2. Use appropriate test data (real ROM or mock) -3. Include performance benchmarks where relevant -4. Add debug output for troubleshooting - -### Adding New Mock Data -1. Extend `MockRom` class for new data types -2. Update `MockRoomGenerator` for new room types -3. Add validation in corresponding tests - -### Adding New Validation -1. Use disassembly data for validation -2. Include both positive and negative test cases -3. Add performance benchmarks -4. Document expected behavior - -## Maintenance - -### Regular Updates -- Update test data when ROM structure changes -- Validate against new disassembly information -- Adjust performance thresholds based on system changes -- Update mock data to match real ROM structure - -### Monitoring -- Track test execution time -- Monitor memory usage trends -- Watch for performance regressions -- Validate against new ROM versions - -This integration test suite provides comprehensive validation of the dungeon object rendering system, ensuring reliability and performance across all components and use cases. diff --git a/docs/dungeon-object-system.md b/docs/dungeon-object-system.md new file mode 100644 index 00000000..b0029d73 --- /dev/null +++ b/docs/dungeon-object-system.md @@ -0,0 +1,271 @@ +# YAZE Dungeon Object System + +## Overview + +The YAZE Dungeon Object System provides a comprehensive framework for editing and managing dungeon rooms, objects, and layouts in The Legend of Zelda: A Link to the Past. This system combines real-time visual editing with precise data manipulation to create a powerful dungeon creation and modification toolkit. + +## Architecture + +### Core Components + +The dungeon system is built around several key components that work together to provide a seamless editing experience: + +#### 1. DungeonEditor (`src/app/editor/dungeon/dungeon_editor.h`) +The main interface that orchestrates all dungeon editing functionality. It provides: +- **Windowed Canvas System**: Fixed-size canvas that prevents UI layout disruption +- **Tabbed Room Interface**: Multiple rooms can be open simultaneously for easy comparison and editing +- **Integrated Object Placement**: Direct object placement from selector to canvas +- **Real-time Preview**: Live object preview follows mouse cursor during placement + +#### 2. DungeonObjectSelector (`src/app/editor/dungeon/dungeon_object_selector.h`) +Combines object browsing and editing in a unified interface: +- **Object Browser**: Visual grid of all available objects with real-time previews +- **Object Editor**: Integrated editing panels for sprites, items, entrances, doors, and chests +- **Callback System**: Notifies main editor when objects are selected for placement + +#### 3. DungeonCanvasViewer (`src/app/editor/dungeon/dungeon_canvas_viewer.h`) +Specialized canvas for rendering dungeon rooms: +- **Background Layer Rendering**: Proper BG1/BG2 layer composition +- **Object Rendering**: Cached object rendering with palette support +- **Coordinate System**: Seamless translation between room and canvas coordinates + +#### 4. Room Management System (`src/app/zelda3/dungeon/room.h`) +Core data structures for room representation: +- **Room Objects**: Tile-based objects (walls, floors, decorations) +- **Room Layout**: Structural elements and collision data +- **Sprites**: Enemy and NPC placement +- **Entrances/Exits**: Room connections and transitions + +## Object Types and Hierarchies + +### Room Objects +Room objects are the fundamental building blocks of dungeon rooms. They follow a hierarchical structure: + +#### Type 1 Objects (0x00-0xFF) +Basic structural elements: +- **0x10-0x1F**: Wall objects (various types and orientations) +- **0x20-0x2F**: Floor tiles (stone, wood, carpet, etc.) +- **0x30-0x3F**: Decorative elements (torches, statues, pillars) +- **0x40-0x4F**: Interactive elements (switches, blocks) + +#### Type 2 Objects (0x100-0x1FF) +Complex multi-tile objects: +- **0x100-0x10F**: Large wall sections +- **0x110-0x11F**: Complex floor patterns +- **0x120-0x12F**: Multi-tile decorations + +#### Type 3 Objects (0x200+) +Special dungeon-specific objects: +- **0x200-0x20F**: Boss room elements +- **0x210-0x21F**: Puzzle-specific objects +- **0xF9-0xFA**: Chests (small and large) + +### Object Properties +Each object has several key properties: + +```cpp +class RoomObject { + int id_; // Object type identifier + int x_, y_; // Position in room (16x16 tile units) + int size_; // Size modifier (affects rendering) + LayerType layer_; // Rendering layer (0=BG, 1=MID, 2=FG) + // ... additional properties +}; +``` + +## How Object Placement Works + +### Selection Process +1. **Object Browser**: User selects an object from the visual grid +2. **Preview Generation**: Object is rendered with current room palette +3. **Callback Trigger**: Selection notifies main editor via callback +4. **Preview Update**: Main editor receives object and enables placement mode + +### Placement Process +1. **Mouse Tracking**: Preview object follows mouse cursor on canvas +2. **Coordinate Translation**: Mouse position converted to room coordinates +3. **Visual Feedback**: Semi-transparent preview shows placement position +4. **Click Placement**: Left-click places object at current position +5. **Room Update**: Object added to room data and cache cleared for redraw + +### Code Flow +```cpp +// Object selection in DungeonObjectSelector +if (ImGui::Selectable("", is_selected)) { + preview_object_ = selected_object; + object_loaded_ = true; + + // Notify main editor + if (object_selected_callback_) { + object_selected_callback_(preview_object_); + } +} + +// Object placement in DungeonEditor +void PlaceObjectAtPosition(int room_x, int room_y) { + auto new_object = preview_object_; + new_object.x_ = room_x; + new_object.y_ = room_y; + new_object.set_rom(rom_); + new_object.EnsureTilesLoaded(); + + room.AddTileObject(new_object); + object_render_cache_.clear(); // Force redraw +} +``` + +## Rendering Pipeline + +### Object Rendering +The system uses a sophisticated rendering pipeline: + +1. **Tile Loading**: Object tiles loaded from ROM based on object ID +2. **Palette Application**: Room-specific palette applied to object +3. **Bitmap Generation**: Object rendered to bitmap with proper composition +4. **Caching**: Rendered objects cached for performance +5. **Canvas Drawing**: Bitmap drawn to canvas at correct position + +### Performance Optimizations +- **Render Caching**: Objects cached based on ID, position, size, and palette hash +- **Bounds Checking**: Only objects within canvas bounds are rendered +- **Lazy Loading**: Graphics and objects loaded on-demand +- **Palette Hashing**: Efficient cache invalidation when palettes change + +## User Interface Components + +### Three-Column Layout +The dungeon editor uses a carefully designed three-column layout: + +#### Column 1: Room Control Panel (280px fixed) +- **Room Selector**: Browse and select rooms +- **Debug Controls**: Room properties in table format +- **Object Statistics**: Live object counts and cache status + +#### Column 2: Windowed Canvas (800px fixed) +- **Tabbed Interface**: Multiple rooms open simultaneously +- **Fixed Dimensions**: Prevents UI layout disruption +- **Real-time Preview**: Object placement preview follows cursor +- **Layer Visualization**: Proper background/foreground rendering + +#### Column 3: Object Selector/Editor (stretch) +- **Object Browser Tab**: Visual grid of available objects +- **Object Editor Tab**: Integrated editing for sprites, items, etc. +- **Placement Tools**: Object property editing and placement controls + +### Debug and Control Features + +#### Room Properties Table +Real-time editing of room attributes: +``` +Property | Value +------------|-------- +Room ID | 0x001 (1) +Layout | [Hex Input] +Blockset | [Hex Input] +Spriteset | [Hex Input] +Palette | [Hex Input] +Floor 1 | [Hex Input] +Floor 2 | [Hex Input] +Message ID | [Hex Input] +``` + +#### Object Statistics +Live feedback on room contents: +- Total objects count +- Layout objects count +- Sprites count +- Chests count +- Cache status and controls + +## Integration with ROM Data + +### Data Sources +The system integrates with multiple ROM data sources: + +#### Room Headers (`0x1F8000`) +- Room layout index +- Blockset and spriteset references +- Palette assignments +- Floor type definitions + +#### Object Data +- Object definitions and tile mappings +- Size and layer information +- Interaction properties + +#### Graphics Data +- Tile graphics (4bpp SNES format) +- Palette data (15-color palettes) +- Blockset compositions + +### Assembly Integration +The system references the US disassembly (`assets/asm/usdasm/`) for: +- Room data structure validation +- Object type definitions +- Memory layout verification +- Data pointer validation + +## Comparison with ZScream + +### Architectural Differences +YAZE's approach differs from ZScream in several key ways: + +#### Component-Based Architecture +- **YAZE**: Modular components with clear separation of concerns +- **ZScream**: More monolithic approach with integrated functionality + +#### Real-time Rendering +- **YAZE**: Live object preview with mouse tracking +- **ZScream**: Static preview with separate placement step + +#### UI Organization +- **YAZE**: Fixed-width columns prevent layout disruption +- **ZScream**: Resizable panels that can affect overall layout + +#### Caching Strategy +- **YAZE**: Sophisticated object render caching with hash-based invalidation +- **ZScream**: Simpler caching approach + +### Shared Concepts +Both systems share fundamental concepts: +- Object-based room construction +- Layer-based rendering +- ROM data integration +- Visual object browsing + +## Best Practices + +### Performance +1. **Use Render Caching**: Don't clear cache unnecessarily +2. **Bounds Checking**: Only render visible objects +3. **Lazy Loading**: Load graphics and objects on-demand +4. **Efficient Callbacks**: Minimize callback frequency + +### Code Organization +1. **Separation of Concerns**: Keep UI, data, and rendering separate +2. **Clear Interfaces**: Use callbacks for component communication +3. **Error Handling**: Validate ROM data and handle errors gracefully +4. **Memory Management**: Clean up resources properly + +### User Experience +1. **Visual Feedback**: Provide clear object placement preview +2. **Consistent Layout**: Use fixed dimensions for stable UI +3. **Contextual Information**: Show relevant object properties +4. **Efficient Workflow**: Minimize clicks for common operations + +## Future Enhancements + +### Planned Features +1. **Drag and Drop**: Direct object dragging from selector to canvas +2. **Multi-Selection**: Select and manipulate multiple objects +3. **Copy/Paste**: Copy object configurations between rooms +4. **Undo/Redo**: Full edit history management +5. **Template System**: Save and load room templates + +### Technical Improvements +1. **GPU Acceleration**: Move rendering to GPU for better performance +2. **Advanced Caching**: Predictive loading and intelligent cache management +3. **Background Processing**: Asynchronous ROM data loading +4. **Memory Optimization**: Reduce memory footprint for large dungeons + +This documentation provides a comprehensive understanding of how the YAZE dungeon object system works, from high-level architecture to low-level implementation details. The system is designed to be both powerful for advanced users and accessible for newcomers to dungeon editing. diff --git a/src/app/editor/dungeon/dungeon_editor.cc b/src/app/editor/dungeon/dungeon_editor.cc index 5e84de47..df921385 100644 --- a/src/app/editor/dungeon/dungeon_editor.cc +++ b/src/app/editor/dungeon/dungeon_editor.cc @@ -114,6 +114,13 @@ absl::Status DungeonEditor::Load() { object_selector_.set_object_editor(&object_editor_); object_selector_.set_rooms(&rooms_); + // Set up object selection callback + object_selector_.SetObjectSelectedCallback([this](const zelda3::RoomObject& object) { + preview_object_ = object; + object_loaded_ = true; + placement_type_ = kObject; // Automatically switch to object placement mode + }); + is_loaded_ = true; return absl::OkStatus(); } @@ -235,30 +242,24 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() { ImGui::End(); } - // Simplified 3-column layout: Room/Entrance Selector | Canvas | Object Selector/Editor + // Correct 3-column layout as specified if (BeginTable("#DungeonEditTable", 3, kDungeonTableFlags, ImVec2(0, 0))) { TableSetupColumn("Room/Entrance Selector", ImGuiTableColumnFlags_WidthFixed, 250); - TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch, - ImGui::GetContentRegionAvail().x); + TableSetupColumn("Canvas & Properties", ImGuiTableColumnFlags_WidthStretch); TableSetupColumn("Object Selector/Editor", ImGuiTableColumnFlags_WidthFixed, 300); TableHeadersRow(); TableNextRow(); - // Column 1: Room and Entrance Selector (using new component) + // Column 1: Room and Entrance Selector (unchanged) TableNextColumn(); room_selector_.Draw(); - // Column 2: Main Canvas with tabbed room view functionality + // Column 2: Canvas and room properties with tabs TableNextColumn(); - DrawDungeonTabView(); + DrawCanvasAndPropertiesPanel(); - // Column 3: Object Selector and Editor (using new component) + // Column 3: Object selector, room graphics, and object editor TableNextColumn(); - int current_room = current_room_id_; - if (!active_rooms_.empty() && current_active_room_tab_ < active_rooms_.Size) { - current_room = active_rooms_[current_active_room_tab_]; - } - object_selector_.set_current_room_id(current_room); object_selector_.Draw(); ImGui::EndTable(); @@ -316,24 +317,36 @@ void DungeonEditor::DrawToolset() { Text(ICON_MD_MORE_VERT); TableNextColumn(); - if (RadioButton(ICON_MD_FILTER_NONE, background_type_ == kBackgroundAny)) { + if (RadioButton("All", background_type_ == kBackgroundAny)) { background_type_ = kBackgroundAny; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Show all background layers"); + } TableNextColumn(); - if (RadioButton(ICON_MD_FILTER_1, background_type_ == kBackground1)) { + if (RadioButton("BG1", background_type_ == kBackground1)) { background_type_ = kBackground1; } - - TableNextColumn(); - if (RadioButton(ICON_MD_FILTER_2, background_type_ == kBackground2)) { - background_type_ = kBackground2; + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Show background layer 1 only"); } TableNextColumn(); - if (RadioButton(ICON_MD_FILTER_3, background_type_ == kBackground3)) { + if (RadioButton("BG2", background_type_ == kBackground2)) { + background_type_ = kBackground2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Show background layer 2 only"); + } + + TableNextColumn(); + if (RadioButton("BG3", background_type_ == kBackground3)) { background_type_ = kBackground3; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Show background layer 3 only"); + } TableNextColumn(); Text(ICON_MD_MORE_VERT); @@ -402,25 +415,143 @@ void DungeonEditor::DrawToolset() { ImGui::EndTable(); } - // Add layer selector below the main toolset ImGui::Separator(); - ImGui::Text("Layer:"); - ImGui::SameLine(); - if (ImGui::RadioButton("BG1", current_layer_ == 0)) { - current_layer_ = 0; + ImGui::Text("Instructions: Click to place objects, Ctrl+Click to select, drag to move"); +} + +void DungeonEditor::DrawCanvasAndPropertiesPanel() { + if (ImGui::BeginTabBar("CanvasPropertiesTabBar")) { + // Canvas tab - main editing view + if (ImGui::BeginTabItem("Canvas")) { + DrawDungeonTabView(); + ImGui::EndTabItem(); + } + + // Room Properties tab - debug and editing controls + if (ImGui::BeginTabItem("Room Properties")) { + if (ImGui::Button("Room Debug Info")) { + ImGui::OpenPopup("RoomDebugPopup"); + } + + // Room properties popup + if (ImGui::BeginPopup("RoomDebugPopup")) { + DrawRoomPropertiesDebugPopup(); + ImGui::EndPopup(); + } + + // Quick room info display + int current_room = current_room_id_; + if (!active_rooms_.empty() && current_active_room_tab_ < active_rooms_.Size) { + current_room = active_rooms_[current_active_room_tab_]; + } + + if (current_room >= 0 && current_room < rooms_.size()) { + auto& room = rooms_[current_room]; + + ImGui::Text("Current Room: %03X (%d)", current_room, current_room); + ImGui::Text("Objects: %zu", room.GetTileObjects().size()); + ImGui::Text("Sprites: %zu", room.GetSprites().size()); + ImGui::Text("Chests: %zu", room.GetChests().size()); + + ImGui::Separator(); + + // Quick edit controls + gui::InputHexByte("Layout", &room.layout); + gui::InputHexByte("Blockset", &room.blockset); + gui::InputHexByte("Spriteset", &room.spriteset); + gui::InputHexByte("Palette", &room.palette); + + if (ImGui::Button("Reload Room Graphics")) { + (void)LoadAndRenderRoomGraphics(current_room); + } + } + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); } - ImGui::SameLine(); - if (ImGui::RadioButton("BG2", current_layer_ == 1)) { - current_layer_ = 1; - } - ImGui::SameLine(); - if (ImGui::RadioButton("Both", current_layer_ == 2)) { - current_layer_ = 2; +} + +void DungeonEditor::DrawRoomPropertiesDebugPopup() { + int current_room = current_room_id_; + if (!active_rooms_.empty() && current_active_room_tab_ < active_rooms_.Size) { + current_room = active_rooms_[current_active_room_tab_]; } - // Add interaction mode info + if (current_room < 0 || current_room >= rooms_.size()) { + ImGui::Text("Invalid room"); + return; + } + + auto& room = rooms_[current_room]; + + ImGui::Text("Room %03X Debug Information", current_room); + ImGui::Separator(); + + // Room properties table + if (ImGui::BeginTable("RoomPropertiesPopup", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 120); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("Room ID"); + ImGui::TableNextColumn(); ImGui::Text("%03X (%d)", current_room, current_room); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("Layout"); + ImGui::TableNextColumn(); gui::InputHexByte("##layout", &room.layout); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("Blockset"); + ImGui::TableNextColumn(); gui::InputHexByte("##blockset", &room.blockset); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("Spriteset"); + ImGui::TableNextColumn(); gui::InputHexByte("##spriteset", &room.spriteset); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("Palette"); + ImGui::TableNextColumn(); gui::InputHexByte("##palette", &room.palette); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("Floor 1"); + ImGui::TableNextColumn(); gui::InputHexByte("##floor1", &room.floor1); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("Floor 2"); + ImGui::TableNextColumn(); gui::InputHexByte("##floor2", &room.floor2); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("Message ID"); + ImGui::TableNextColumn(); gui::InputHexWord("##message_id", &room.message_id_); + + ImGui::EndTable(); + } + + ImGui::Separator(); + + // Object statistics + ImGui::Text("Object Statistics:"); + ImGui::Text("Total Objects: %zu", room.GetTileObjects().size()); + ImGui::Text("Layout Objects: %zu", room.GetLayout().GetObjects().size()); + ImGui::Text("Sprites: %zu", room.GetSprites().size()); + ImGui::Text("Chests: %zu", room.GetChests().size()); + + ImGui::Separator(); + + if (ImGui::Button("Reload Objects")) { + room.LoadObjects(); + } ImGui::SameLine(); - ImGui::Text("| Click to place, Ctrl+Click to select"); + if (ImGui::Button("Clear Cache")) { + object_render_cache_.clear(); + } + ImGui::SameLine(); + if (ImGui::Button("Close")) { + ImGui::CloseCurrentPopup(); + } } @@ -622,6 +753,20 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) { // Handle mouse input for drag and select functionality HandleCanvasMouseInput(); + // Update preview object position based on mouse cursor + if (object_loaded_ && preview_object_.id_ >= 0 && canvas_.IsMouseHovering()) { + const ImGuiIO& io = ImGui::GetIO(); + ImVec2 mouse_pos = io.MousePos; + ImVec2 canvas_pos = canvas_.zero_point(); + ImVec2 canvas_mouse_pos = ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y); + auto [room_x, room_y] = CanvasToRoomCoordinates( + static_cast(canvas_mouse_pos.x), + static_cast(canvas_mouse_pos.y) + ); + preview_object_.x_ = room_x; + preview_object_.y_ = room_y; + } + if (is_loaded_) { // Automatically load room graphics if not already loaded if (rooms_[room_id].blocks().empty()) { @@ -1111,7 +1256,7 @@ bool DungeonEditor::IsObjectInSelectBox(const zelda3::RoomObject& object) const } void DungeonEditor::PlaceObjectAtPosition(int room_x, int room_y) { - if (!object_loaded_) return; + if (!object_loaded_ || preview_object_.id_ < 0) return; // Get current room int current_room = current_room_id_; @@ -1125,13 +1270,18 @@ void DungeonEditor::PlaceObjectAtPosition(int room_x, int room_y) { auto new_object = preview_object_; new_object.x_ = room_x; new_object.y_ = room_y; + new_object.set_rom(rom_); + new_object.EnsureTilesLoaded(); // Add object to room auto& room = rooms_[current_room]; room.AddTileObject(new_object); - // TODO: Update the room's object list and trigger a redraw - // This would require integration with the room management system + // Clear the object render cache to force redraw + object_render_cache_.clear(); + + // Update the object selector with the new object count + object_selector_.set_current_room_id(current_room); } } // namespace yaze::editor diff --git a/src/app/editor/dungeon/dungeon_editor.h b/src/app/editor/dungeon/dungeon_editor.h index 4fdaae2b..e8fd9788 100644 --- a/src/app/editor/dungeon/dungeon_editor.h +++ b/src/app/editor/dungeon/dungeon_editor.h @@ -89,6 +89,10 @@ class DungeonEditor : public Editor { void DrawDungeonTabView(); void DrawDungeonCanvas(int room_id); + // Enhanced UI methods + void DrawCanvasAndPropertiesPanel(); + void DrawRoomPropertiesDebugPopup(); + // Room selection management void OnRoomSelected(int room_id); diff --git a/src/app/editor/dungeon/dungeon_object_selector.cc b/src/app/editor/dungeon/dungeon_object_selector.cc index ef4edc42..96997c5e 100644 --- a/src/app/editor/dungeon/dungeon_object_selector.cc +++ b/src/app/editor/dungeon/dungeon_object_selector.cc @@ -160,6 +160,11 @@ void DungeonObjectSelector::DrawObjectBrowser() { preview_object_ = test_object; preview_palette_ = palette; object_loaded_ = true; + + // Notify the main editor that an object was selected + if (object_selected_callback_) { + object_selected_callback_(preview_object_); + } } // Draw preview image @@ -252,15 +257,25 @@ void DungeonObjectSelector::DrawObjectBrowser() { } void DungeonObjectSelector::Draw() { - if (ImGui::BeginTabBar("##ObjectEditorTabBar")) { - if (ImGui::BeginTabItem("Graphics")) { + if (ImGui::BeginTabBar("##ObjectSelectorTabBar")) { + // Object Selector tab - for placing objects + if (ImGui::BeginTabItem("Object Selector")) { + DrawObjectBrowser(); + ImGui::EndTabItem(); + } + + // Room Graphics tab - 8 bitmaps viewer + if (ImGui::BeginTabItem("Room Graphics")) { DrawRoomGraphics(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Editor")) { + + // Object Editor tab - experimental editor + if (ImGui::BeginTabItem("Object Editor")) { DrawIntegratedEditingPanels(); ImGui::EndTabItem(); } + ImGui::EndTabBar(); } } diff --git a/src/app/editor/dungeon/dungeon_object_selector.h b/src/app/editor/dungeon/dungeon_object_selector.h index 295c69a1..849aa309 100644 --- a/src/app/editor/dungeon/dungeon_object_selector.h +++ b/src/app/editor/dungeon/dungeon_object_selector.h @@ -50,6 +50,15 @@ class DungeonObjectSelector { void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; } void SetCurrentPaletteGroup(const gfx::PaletteGroup& palette_group) { current_palette_group_ = palette_group; } void SetCurrentPaletteId(uint64_t palette_id) { current_palette_id_ = palette_id; } + + // Object selection callbacks + void SetObjectSelectedCallback(std::function callback) { + object_selected_callback_ = callback; + } + + // Get current preview object for placement + const zelda3::RoomObject& GetPreviewObject() const { return preview_object_; } + bool IsObjectLoaded() const { return object_loaded_; } private: void DrawRoomGraphics(); @@ -84,6 +93,9 @@ class DungeonObjectSelector { zelda3::RoomObject preview_object_{0, 0, 0, 0, 0}; gfx::SnesPalette preview_palette_; bool object_loaded_ = false; + + // Callback for object selection + std::function object_selected_callback_; }; } // namespace editor