7#include "absl/strings/str_format.h"
11#include "imgui/imgui.h"
20 if (
rom_ ==
nullptr) {
21 return absl::InvalidArgumentError(
"ROM is null");
43 return absl::OkStatus();
47 if (
rom_ ==
nullptr) {
48 return absl::InvalidArgumentError(
"ROM is null");
52 return absl::InvalidArgumentError(
"Invalid room ID");
77 return absl::OkStatus();
82 return absl::FailedPreconditionError(
"No room loaded");
88 if (!validation_status.ok()) {
89 return validation_status;
99 return absl::FailedPreconditionError(
"No room loaded");
119 return absl::OkStatus();
124 return absl::FailedPreconditionError(
"No room loaded");
128 if (object_type < 0 || object_type > 0x3FF) {
129 return absl::InvalidArgumentError(
"Invalid object type");
133 return absl::InvalidArgumentError(
"Invalid object size");
136 if (layer < kMinLayer || layer >
kMaxLayer) {
137 return absl::InvalidArgumentError(
"Invalid layer");
153 RoomObject new_object(object_type, x, y, size, layer);
159 for (
const auto& existing_obj :
current_room_->GetTileObjects()) {
161 return absl::FailedPreconditionError(
"Object placement would cause collision");
168 if (!add_status.ok()) {
189 return absl::OkStatus();
194 return absl::FailedPreconditionError(
"No room loaded");
198 return absl::OutOfRangeError(
"Object index out of range");
208 auto remove_status =
current_room_->RemoveObject(object_index);
209 if (!remove_status.ok()) {
210 return remove_status;
215 if (selected_index > object_index) {
217 }
else if (selected_index == object_index) {
235 return absl::OkStatus();
240 return absl::FailedPreconditionError(
"No room loaded");
244 return absl::FailedPreconditionError(
"No objects selected");
255 std::sort(sorted_selection.begin(), sorted_selection.end(), std::greater<size_t>());
258 for (
size_t index : sorted_selection) {
259 if (index < current_room_->GetTileObjectCount()) {
272 return absl::OkStatus();
277 return absl::FailedPreconditionError(
"No room loaded");
281 return absl::OutOfRangeError(
"Object index out of range");
302 test_object.
set_x(new_x);
303 test_object.
set_y(new_y);
305 for (
size_t i = 0; i <
current_room_->GetTileObjects().size(); i++) {
307 return absl::FailedPreconditionError(
"Object move would cause collision");
325 return absl::OkStatus();
330 return absl::FailedPreconditionError(
"No room loaded");
334 return absl::OutOfRangeError(
"Object index out of range");
338 return absl::InvalidArgumentError(
"Invalid object size");
349 object.set_size(new_size);
360 return absl::OkStatus();
365 return absl::FailedPreconditionError(
"No room loaded");
380 int layer_delta = delta > 0 ? 1 : -1;
388 return absl::OkStatus();
391 return absl::OkStatus();
402 return absl::OkStatus();
408 if (object_index < current_room_->GetTileObjectCount()) {
419 return absl::OkStatus();
422 return absl::OkStatus();
432 if (current_size < 0x40) {
433 return current_size + 0x10;
434 }
else if (current_size < 0x80) {
435 return current_size + 0x08;
437 return current_size + 0x04;
441 if (current_size > 0x80) {
442 return current_size - 0x04;
443 }
else if (current_size > 0x40) {
444 return current_size - 0x08;
446 return current_size - 0x10;
457 return absl::FailedPreconditionError(
"No room loaded");
469 if (object_index.has_value()) {
487 if (object_index.has_value()) {
509 if (object_index.has_value()) {
520 return absl::OkStatus();
525 return absl::FailedPreconditionError(
"No room loaded");
536 if (!undo_status.ok()) {
547 return absl::FailedPreconditionError(
"No room loaded");
560 return absl::OkStatus();
565 return absl::FailedPreconditionError(
"No room loaded");
574 if (object_index.has_value()) {
584 return absl::OkStatus();
601 return absl::OkStatus();
606 return absl::FailedPreconditionError(
"No room loaded");
610 return absl::OutOfRangeError(
"Object index out of range");
627 return absl::OkStatus();
645 if (object_type >= 0 && object_type <= 0x3FF) {
657 for (
int i =
static_cast<int>(
current_room_->GetTileObjectCount()) - 1; i >= 0; i--) {
659 return static_cast<size_t>(i);
668 int obj_x =
object.x_ * 16;
669 int obj_y =
object.y_ * 16;
679 if (
object.size_ > 0x80) {
684 return (x >= obj_x && x < obj_x + obj_width &&
685 y >= obj_y && y < obj_y + obj_height);
692 int obj1_x = obj1.
x_ * 16;
693 int obj1_y = obj1.
y_ * 16;
697 int obj2_x = obj2.
x_ * 16;
698 int obj2_y = obj2.
y_ * 16;
703 if (obj1.
size_ > 0x80) {
708 if (obj2.
size_ > 0x80) {
713 return !(obj1_x + obj1_w <= obj2_x ||
714 obj2_x + obj2_w <= obj1_x ||
715 obj1_y + obj1_h <= obj2_y ||
716 obj2_y + obj2_h <= obj1_y);
724 int room_x = screen_x / 16;
725 int room_y = screen_y / 16;
727 return {room_x, room_y};
732 int screen_x = room_x * 16;
733 int screen_y = room_y * 16;
735 return {screen_x, screen_y};
763 return absl::FailedPreconditionError(
"No room loaded");
771 undo_point.
timestamp = std::chrono::steady_clock::now();
784 return absl::OkStatus();
789 return absl::FailedPreconditionError(
"Nothing to undo");
797 current_state.
timestamp = std::chrono::steady_clock::now();
810 return absl::FailedPreconditionError(
"Nothing to redo");
818 current_state.
timestamp = std::chrono::steady_clock::now();
831 return absl::FailedPreconditionError(
"No room loaded");
853 return absl::OkStatus();
874static uint32_t BlendColors(uint32_t base, uint32_t tint) {
875 uint8_t a_tint = (tint >> 24) & 0xFF;
876 if (a_tint == 0)
return base;
878 uint8_t r_base = (base >> 16) & 0xFF;
879 uint8_t g_base = (base >> 8) & 0xFF;
880 uint8_t b_base = base & 0xFF;
882 uint8_t r_tint = (tint >> 16) & 0xFF;
883 uint8_t g_tint = (tint >> 8) & 0xFF;
884 uint8_t b_tint = tint & 0xFF;
886 float alpha = a_tint / 255.0f;
887 uint8_t r = r_base * (1.0f - alpha) + r_tint * alpha;
888 uint8_t g = g_base * (1.0f - alpha) + g_tint * alpha;
889 uint8_t b = b_base * (1.0f - alpha) + b_tint * alpha;
891 return 0xFF000000 | (r << 16) | (g << 8) | b;
901 if (obj_idx >=
current_room_->GetTileObjectCount())
continue;
904 int x = obj.x() * 16;
905 int y = obj.y() * 16;
906 int w = 16 + (obj.size() * 4);
907 int h = 16 + (obj.size() * 4);
915 for (
int py = y; py < y + h; py++) {
916 for (
int px = x; px < x + w; px++) {
918 (px < x + 2 || px >= x + w - 2 || py < y + 2 || py >= y + h - 2)) {
933 int x = obj.x() * 16;
934 int y = obj.y() * 16;
938 uint32_t tint_color = 0xFF000000;
939 switch (obj.GetLayerValue()) {
946 uint8_t r = (tint_color >> 16) & 0xFF;
947 uint8_t g = (tint_color >> 8) & 0xFF;
948 uint8_t b = tint_color & 0xFF;
951 for (
int py = y; py < y + h && py < canvas.
height(); py++) {
952 for (
int px = x; px < x + w && px < canvas.
width(); px++) {
953 if (px == x || px == x + w - 1 || py == y || py == y + h - 1) {
954 canvas.
SetPixel(px, py, layer_color);
970 if (obj_idx < current_room_->GetTileObjectCount()) {
973 ImGui::Text(
"Object #%zu", obj_idx);
978 if (ImGui::InputInt(
"ID (0x)", &
id, 1, 16, ImGuiInputTextFlags_CharsHexadecimal)) {
979 if (
id >= 0 &&
id <= 0xFFF) {
990 if (ImGui::InputInt(
"X Position", &x, 1, 4)) {
991 if (x >= 0 && x < 64) {
998 if (ImGui::InputInt(
"Y Position", &y, 1, 4)) {
999 if (y >= 0 && y < 64) {
1008 if (obj.id_ < 0x100) {
1009 int size = obj.size();
1010 if (ImGui::SliderInt(
"Size", &size, 0, 15)) {
1019 int layer = obj.GetLayerValue();
1020 if (ImGui::Combo(
"Layer", &layer,
"Layer 0\0Layer 1\0Layer 2\0")) {
1030 if (ImGui::Button(
"Delete Object")) {
1035 if (ImGui::Button(
"Duplicate")) {
1037 duplicate.
set_x(obj.x() + 1);
1047 if (ImGui::Button(
"Delete All Selected")) {
1052 if (ImGui::Button(
"Clear Selection")) {
1062 ImGui::Begin(
"Layer Controls");
1065 ImGui::Text(
"Current Layer:");
1075 static bool layer_visible[3] = {
true,
true,
true};
1076 ImGui::Text(
"Layer Visibility:");
1077 ImGui::Checkbox(
"Show Layer 0", &layer_visible[0]);
1078 ImGui::Checkbox(
"Show Layer 1", &layer_visible[1]);
1079 ImGui::Checkbox(
"Show Layer 2", &layer_visible[2]);
1095 int count0 = 0, count1 = 0, count2 = 0;
1097 switch (obj.GetLayerValue()) {
1098 case 0: count0++;
break;
1099 case 1: count1++;
break;
1100 case 2: count2++;
break;
1103 ImGui::Text(
"Layer 0: %d objects", count0);
1104 ImGui::Text(
"Layer 1: %d objects", count1);
1105 ImGui::Text(
"Layer 2: %d objects", count2);
1113 return absl::OkStatus();
1124 if (grid_dx == 0 && grid_dy == 0) {
1125 return absl::OkStatus();
1130 if (obj_idx >=
current_room_->GetTileObjectCount())
continue;
1133 int new_x = obj.x() + grid_dx;
1134 int new_y = obj.y() + grid_dy;
1137 new_x = std::max(0, std::min(63, new_x));
1138 new_y = std::max(0, std::min(63, new_y));
1152 return absl::OkStatus();
1157 return absl::FailedPreconditionError(
"No room loaded for validation");
1163 for (
size_t i = 0; i < objects.size(); i++) {
1164 for (
size_t j = i + 1; j < objects.size(); j++) {
1166 return absl::FailedPreconditionError(
1167 absl::StrFormat(
"Objects at indices %d and %d collide", i, j));
1173 return absl::OkStatus();
1200 return std::make_unique<DungeonObjectEditor>(rom);
1204namespace ObjectCategories {
1208 {
"Walls", {0x10, 0x11, 0x12, 0x13},
"Basic wall objects"},
1209 {
"Floors", {0x20, 0x21, 0x22, 0x23},
"Floor tile objects"},
1210 {
"Decorations", {0x30, 0x31, 0x32, 0x33},
"Decorative objects"},
1211 {
"Interactive", {0xF9, 0xFA, 0xFB},
"Interactive objects like chests"},
1212 {
"Stairs", {0x13, 0x14, 0x15, 0x16},
"Staircase objects"},
1213 {
"Doors", {0x17, 0x18, 0x19, 0x1A},
"Door objects"},
1214 {
"Special", {0x200, 0x201, 0x202, 0x203},
"Special dungeon objects"}
1221 for (
const auto& category : categories) {
1222 if (category.name == category_name) {
1223 return category.object_ids;
1227 return absl::NotFoundError(
"Category not found");
1233 for (
const auto& category : categories) {
1234 for (
int id : category.object_ids) {
1235 if (
id == object_id) {
1236 return category.name;
1241 return absl::NotFoundError(
"Object category not found");
1246 info.
id = object_id;
1251 if (object_id >= 0x10 && object_id <= 0x1F) {
1258 }
else if (object_id >= 0x20 && object_id <= 0x2F) {
1259 info.
name =
"Floor";
1265 }
else if (object_id == 0xF9) {
1266 info.
name =
"Small Chest";
1273 info.
name =
"Unknown Object";
The Rom class is used to load, save, and modify Rom data.
Represents a bitmap image optimized for SNES ROM hacking.
void SetPixel(int x, int y, const SnesColor &color)
Set a pixel at the given x,y coordinates with SNES color.
static constexpr int kDefaultObjectSize
std::function< void(size_t object_index, const RoomObject &object)> ObjectChangedCallback
std::function< void(const SelectionState &)> SelectionChangedCallback
absl::Status HandleMouseDrag(int start_x, int start_y, int current_x, int current_y)
absl::Status HandleScrollWheel(int delta, int x, int y, bool ctrl_pressed)
std::pair< int, int > ScreenToRoomCoordinates(int screen_x, int screen_y)
absl::Status ValidateRoom()
int GetNextSize(int current_size, int delta)
void SetRoomChangedCallback(RoomChangedCallback callback)
std::pair< int, int > RoomToScreenCoordinates(int room_x, int room_y)
absl::Status InsertObject(int x, int y, int object_type, int size=0x12, int layer=0)
EditingState editing_state_
static constexpr int kMaxLayer
DungeonObjectEditor(Rom *rom)
absl::Status DeleteObject(size_t object_index)
SelectionState selection_state_
absl::Status InitializeEditor()
void SetSelectionChangedCallback(SelectionChangedCallback callback)
absl::Status SelectObject(int screen_x, int screen_y)
void RenderObjectPropertyPanel()
absl::Status CreateUndoPoint()
std::vector< UndoPoint > undo_history_
static constexpr size_t kMaxUndoHistory
bool IsValidSize(int size)
std::optional< size_t > FindObjectAt(int room_x, int room_y)
static constexpr int kMaxObjectSize
absl::Status AddToSelection(size_t object_index)
std::function< void()> RoomChangedCallback
static constexpr int kMinLayer
ObjectChangedCallback object_changed_callback_
std::unique_ptr< Room > current_room_
void RenderLayerVisualization(gfx::Bitmap &canvas)
absl::Status LoadRoom(int room_id)
void SetConfig(const EditorConfig &config)
std::vector< UndoPoint > redo_history_
absl::Status HandleDragOperation(int current_x, int current_y)
absl::Status HandleSizeEdit(int delta, int x, int y)
bool ObjectsCollide(const RoomObject &obj1, const RoomObject &obj2)
absl::Status ClearSelection()
bool IsObjectAtPosition(const RoomObject &object, int x, int y)
void SetCurrentLayer(int layer)
void SetObjectChangedCallback(ObjectChangedCallback callback)
absl::Status HandleMouseRelease(int x, int y)
void UpdatePreviewObject()
absl::Status MoveObject(size_t object_index, int new_x, int new_y)
absl::Status ResizeObject(size_t object_index, int new_size)
std::optional< RoomObject > preview_object_
static constexpr int kMinObjectSize
void SetCurrentObjectType(int object_type)
absl::Status DeleteSelectedObjects()
SelectionChangedCallback selection_changed_callback_
int SnapToGrid(int coordinate)
absl::Status ApplyUndoPoint(const UndoPoint &undo_point)
absl::Status HandleMouseClick(int x, int y, bool left_button, bool right_button, bool shift_pressed)
void RenderSelectionHighlight(gfx::Bitmap &canvas)
RoomChangedCallback room_changed_callback_
void RenderLayerControls()
std::vector< ObjectCategory > GetObjectCategories()
Get all available object categories.
absl::StatusOr< ObjectInfo > GetObjectInfo(int object_id)
absl::StatusOr< std::string > GetObjectCategory(int object_id)
Get category for a specific object.
absl::StatusOr< std::vector< int > > GetObjectsInCategory(const std::string &category_name)
Get objects in a specific category.
constexpr int NumberOfRooms
std::unique_ptr< DungeonObjectEditor > CreateDungeonObjectEditor(Rom *rom)
Factory function to create dungeon object editor.
Main namespace for the application.
bool show_collision_bounds
bool show_selection_highlight
std::vector< size_t > selected_objects
std::vector< RoomObject > objects
std::chrono::steady_clock::time_point timestamp
std::vector< std::pair< int, int > > valid_sizes
std::vector< int > valid_layers