7#include "absl/strings/str_format.h"
14#include "imgui/imgui.h"
22 if (
rom_ ==
nullptr) {
23 return absl::InvalidArgumentError(
"ROM is null");
50 return absl::OkStatus();
54 if (
rom_ ==
nullptr) {
55 return absl::InvalidArgumentError(
"ROM is null");
59 return absl::InvalidArgumentError(
"Invalid room ID");
85 return absl::OkStatus();
90 return absl::FailedPreconditionError(
"No room loaded");
96 if (!validation_result.is_valid) {
97 std::string error_msg =
"Validation failed";
98 if (!validation_result.errors.empty()) {
99 error_msg +=
": " + validation_result.errors[0];
101 return absl::FailedPreconditionError(error_msg);
111 return absl::FailedPreconditionError(
"No room loaded");
131 return absl::OkStatus();
135 int size,
int layer) {
137 return absl::FailedPreconditionError(
"No room loaded");
142 if (object_type < 0 || object_type > 0xFFF) {
143 return absl::InvalidArgumentError(
"Invalid object type");
147 return absl::InvalidArgumentError(
"Invalid object size");
150 if (layer < kMinLayer || layer >
kMaxLayer) {
151 return absl::InvalidArgumentError(
"Invalid layer");
167 RoomObject new_object(object_type, x, y, size, layer);
175 return absl::FailedPreconditionError(
176 "Object placement would cause collision");
183 if (!add_status.ok()) {
206 return absl::OkStatus();
211 return absl::FailedPreconditionError(
"No room loaded");
215 return absl::OutOfRangeError(
"Object index out of range");
226 if (!remove_status.ok()) {
227 return remove_status;
232 if (selected_index > object_index) {
234 }
else if (selected_index == object_index) {
252 return absl::OkStatus();
257 return absl::FailedPreconditionError(
"No room loaded");
261 return absl::FailedPreconditionError(
"No objects selected");
272 std::sort(sorted_selection.begin(), sorted_selection.end(),
273 std::greater<size_t>());
276 for (
size_t index : sorted_selection) {
277 if (index < current_room_->GetTileObjectCount()) {
290 return absl::OkStatus();
296 return absl::FailedPreconditionError(
"No room loaded");
300 return absl::OutOfRangeError(
"Object index out of range");
321 test_object.
set_x(new_x);
322 test_object.
set_y(new_y);
325 if (i != object_index &&
327 return absl::FailedPreconditionError(
328 "Object move would cause collision");
346 return absl::OkStatus();
352 return absl::FailedPreconditionError(
"No room loaded");
356 return absl::OutOfRangeError(
"Object index out of range");
360 return absl::InvalidArgumentError(
"Invalid object size");
382 return absl::OkStatus();
386 const std::vector<size_t>& indices,
int dx,
int dy) {
388 return absl::FailedPreconditionError(
"No room loaded");
391 if (indices.empty()) {
392 return absl::OkStatus();
402 for (
size_t index : indices) {
407 int new_x =
object.
x() + dx;
408 int new_y =
object.y() + dy;
411 new_x = std::max(0, std::min(63, new_x));
412 new_y = std::max(0, std::min(63, new_y));
426 return absl::OkStatus();
430 const std::vector<size_t>& indices,
int new_layer) {
432 return absl::FailedPreconditionError(
"No room loaded");
435 if (new_layer < kMinLayer || new_layer >
kMaxLayer) {
436 return absl::InvalidArgumentError(
"Invalid layer");
445 for (
size_t index : indices) {
461 return absl::OkStatus();
465 const std::vector<size_t>& indices,
int new_size) {
467 return absl::FailedPreconditionError(
"No room loaded");
471 return absl::InvalidArgumentError(
"Invalid object size");
480 for (
size_t index : indices) {
498 return absl::OkStatus();
518 int new_x =
object.
x() + offset_x;
519 int new_y =
object.y() + offset_y;
522 new_x = std::max(0, std::min(63, new_x));
523 new_y = std::max(0, std::min(63, new_y));
543 const std::vector<size_t>& indices) {
549 for (
size_t index : indices) {
550 if (index < current_room_->GetTileObjectCount()) {
564 std::vector<size_t> new_indices;
576 int new_x = std::min(63, new_obj.
x() + 1);
577 int new_y = std::min(63, new_obj.
y() + 1);
578 new_obj.
set_x(new_x);
579 new_obj.
set_y(new_y);
582 new_indices.push_back(start_index++);
596 return absl::FailedPreconditionError(
"No room loaded");
600 return absl::OutOfRangeError(
"Object index out of range");
604 if (new_type < 0 || new_type > 0xFFF) {
605 return absl::InvalidArgumentError(
"Invalid object type");
615 object.
id_ = new_type;
625 return absl::OkStatus();
631 return absl::FailedPreconditionError(
"No room loaded");
647 std::vector<RoomObject> new_objects =
652 for (
const auto& new_obj : new_objects) {
655 return absl::FailedPreconditionError(
656 "Template placement would cause collision");
663 for (
const auto& obj : new_objects) {
670 size_t added_count = new_objects.size();
671 for (
size_t i = 0; i < added_count; ++i) {
682 return absl::OkStatus();
688 return absl::FailedPreconditionError(
"No objects selected");
691 std::vector<RoomObject> objects;
692 int min_x = 64, min_y = 64;
696 if (index < current_room_->GetTileObjectCount()) {
698 objects.push_back(obj);
720 return absl::FailedPreconditionError(
"No room loaded");
724 return absl::OkStatus();
747 for (
size_t index : indices) {
754 if (obj.x() < ref_val)
758 if (obj.x() > ref_val)
762 if (obj.y() < ref_val)
766 if (obj.y() > ref_val)
782 ref_val = sum / count;
786 for (
size_t index : indices) {
813 return absl::OkStatus();
819 return absl::FailedPreconditionError(
"No room loaded");
823 return absl::OutOfRangeError(
"Object index out of range");
826 if (new_layer < kMinLayer || new_layer >
kMaxLayer) {
827 return absl::InvalidArgumentError(
"Invalid layer");
847 return absl::OkStatus();
853 return absl::FailedPreconditionError(
"No room loaded");
868 int layer_delta = delta > 0 ? 1 : -1;
876 return absl::OkStatus();
879 return absl::OkStatus();
890 return absl::OkStatus();
897 if (object_index < current_room_->GetTileObjectCount()) {
908 return absl::OkStatus();
911 return absl::OkStatus();
921 if (current_size < 0x40) {
922 return current_size + 0x10;
923 }
else if (current_size < 0x80) {
924 return current_size + 0x08;
926 return current_size + 0x04;
930 if (current_size > 0x80) {
931 return current_size - 0x04;
932 }
else if (current_size > 0x40) {
933 return current_size - 0x08;
935 return current_size - 0x10;
947 bool shift_pressed) {
949 return absl::FailedPreconditionError(
"No room loaded");
961 if (object_index.has_value()) {
980 if (object_index.has_value()) {
1002 if (object_index.has_value()) {
1013 return absl::OkStatus();
1020 return absl::FailedPreconditionError(
"No room loaded");
1032 if (!undo_status.ok()) {
1043 return absl::FailedPreconditionError(
"No room loaded");
1056 return absl::OkStatus();
1061 return absl::FailedPreconditionError(
"No room loaded");
1070 if (object_index.has_value()) {
1080 return absl::OkStatus();
1097 return absl::OkStatus();
1102 return absl::FailedPreconditionError(
"No room loaded");
1106 return absl::OutOfRangeError(
"Object index out of range");
1123 return absl::OkStatus();
1142 if (object_type >= 0 && object_type <= 0xFFF) {
1151 return std::nullopt;
1158 return static_cast<size_t>(i);
1162 return std::nullopt;
1168 int obj_x =
object.x_;
1169 int obj_y =
object.y_;
1174 if (
object.size_ > 0x80) {
1179 return (x >= obj_x && x < obj_x + obj_width && y >= obj_y &&
1180 y < obj_y + obj_height);
1188 int obj1_x = obj1.
x_ * 16;
1189 int obj1_y = obj1.
y_ * 16;
1193 int obj2_x = obj2.
x_ * 16;
1194 int obj2_y = obj2.
y_ * 16;
1199 if (obj1.
size_ > 0x80) {
1204 if (obj2.
size_ > 0x80) {
1209 return !(obj1_x + obj1_w <= obj2_x || obj2_x + obj2_w <= obj1_x ||
1210 obj1_y + obj1_h <= obj2_y || obj2_y + obj2_h <= obj1_y);
1219 int room_x = screen_x / 16;
1220 int room_y = screen_y / 16;
1222 return {room_x, room_y};
1228 int screen_x = room_x * 16;
1229 int screen_y = room_y * 16;
1231 return {screen_x, screen_y};
1240 if (grid_size <= 0) {
1245 int tile_step = std::max(1, grid_size / 16);
1246 return (coordinate / tile_step) * tile_step;
1265 return absl::FailedPreconditionError(
"No room loaded");
1273 undo_point.
timestamp = std::chrono::steady_clock::now();
1286 return absl::OkStatus();
1291 return absl::FailedPreconditionError(
"Nothing to undo");
1299 current_state.
timestamp = std::chrono::steady_clock::now();
1312 return absl::FailedPreconditionError(
"Nothing to redo");
1320 current_state.
timestamp = std::chrono::steady_clock::now();
1333 return absl::FailedPreconditionError(
"No room loaded");
1355 return absl::OkStatus();
1376static uint32_t BlendColors(uint32_t base, uint32_t tint) {
1377 uint8_t a_tint = (tint >> 24) & 0xFF;
1381 uint8_t r_base = (base >> 16) & 0xFF;
1382 uint8_t g_base = (base >> 8) & 0xFF;
1383 uint8_t b_base = base & 0xFF;
1385 uint8_t r_tint = (tint >> 16) & 0xFF;
1386 uint8_t g_tint = (tint >> 8) & 0xFF;
1387 uint8_t b_tint = tint & 0xFF;
1389 float alpha = a_tint / 255.0f;
1390 uint8_t r = r_base * (1.0f - alpha) + r_tint * alpha;
1391 uint8_t g = g_base * (1.0f - alpha) + g_tint * alpha;
1392 uint8_t b = b_base * (1.0f - alpha) + b_tint * alpha;
1394 return 0xFF000000 | (r << 16) | (g << 8) | b;
1409 int x = obj.
x() * 16;
1410 int y = obj.y() * 16;
1411 int w = 16 + (obj.size() * 4);
1412 int h = 16 + (obj.size() * 4);
1420 for (
int py = y; py < y + h; py++) {
1421 for (
int px = x; px < x + w; px++) {
1422 if (px < canvas.
width() && py < canvas.
height() &&
1423 (px < x + 2 || px >= x + w - 2 || py < y + 2 || py >= y + h - 2)) {
1424 canvas.
SetPixel(px, py, sel_color);
1439 int x = obj.x() * 16;
1440 int y = obj.y() * 16;
1444 uint32_t tint_color = 0xFF000000;
1445 switch (obj.GetLayerValue()) {
1458 uint8_t r = (tint_color >> 16) & 0xFF;
1459 uint8_t g = (tint_color >> 8) & 0xFF;
1460 uint8_t b = tint_color & 0xFF;
1463 for (
int py = y; py < y + h && py < canvas.
height(); py++) {
1464 for (
int px = x; px < x + w && px < canvas.
width(); px++) {
1465 if (px == x || px == x + w - 1 || py == y || py == y + h - 1) {
1466 canvas.
SetPixel(px, py, layer_color);
1483 if (obj_idx < current_room_->GetTileObjectCount()) {
1493 ImGui::TableNextRow();
1494 ImGui::TableNextColumn();
1496 ImGui::TableNextColumn();
1498 ImGui::Text(
"0x%03X", obj.id_);
1500 ImGui::TextColored(theme.text_secondary_gray,
"(%s)", obj_name.c_str());
1504 ImGui::TableNextRow();
1505 ImGui::TableNextColumn();
1506 ImGui::Text(
"Type");
1507 ImGui::TableNextColumn();
1508 ImGui::Text(
"Subtype %d", subtype);
1519 ImGui::TableNextRow();
1520 ImGui::TableNextColumn();
1522 ImGui::TableNextColumn();
1524 ImGui::SetNextItemWidth(-1);
1525 if (ImGui::InputInt(
"##X", &x, 1, 4)) {
1526 if (x >= 0 && x < 64) {
1535 ImGui::TableNextRow();
1536 ImGui::TableNextColumn();
1538 ImGui::TableNextColumn();
1540 ImGui::SetNextItemWidth(-1);
1541 if (ImGui::InputInt(
"##Y", &y, 1, 4)) {
1542 if (y >= 0 && y < 64) {
1559 if (obj.id_ < 0x100) {
1560 ImGui::TableNextRow();
1561 ImGui::TableNextColumn();
1562 ImGui::Text(
"Size");
1563 ImGui::TableNextColumn();
1564 int size = obj.size();
1565 ImGui::SetNextItemWidth(-1);
1566 if (ImGui::SliderInt(
"##Size", &size, 0, 15,
"0x%02X")) {
1575 ImGui::TableNextRow();
1576 ImGui::TableNextColumn();
1577 ImGui::Text(
"Change ID");
1578 ImGui::TableNextColumn();
1580 ImGui::SetNextItemWidth(-1);
1581 if (ImGui::InputInt(
"##ID", &
id, 1, 16,
1582 ImGuiInputTextFlags_CharsHexadecimal)) {
1583 if (
id >= 0 &&
id <= 0xFFF) {
1599 ImGui::TableNextRow();
1600 ImGui::TableNextColumn();
1601 ImGui::Text(
"Layer");
1602 ImGui::TableNextColumn();
1603 int layer = obj.GetLayerValue();
1604 ImGui::SetNextItemWidth(-1);
1605 if (ImGui::Combo(
"##Layer", &layer,
1606 "BG1 (Floor)\0BG2 (Objects)\0BG3 (Overlay)\0")) {
1621 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
1623 ImGui::PushStyleColor(
1625 ImVec4(theme.status_error.x * 0.7f, theme.status_error.y * 0.7f,
1626 theme.status_error.z * 0.7f, 1.0f));
1627 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, theme.status_error);
1628 if (ImGui::Button(
ICON_MD_DELETE " Delete", ImVec2(button_width, 0))) {
1632 ImGui::PopStyleColor(2);
1637 ImVec2(button_width, 0))) {
1639 duplicate.
set_x(obj.x() + 1);
1646 ImGui::TextColored(theme.text_warning_yellow,
1654 static int batch_layer = 0;
1655 ImGui::SetNextItemWidth(-1);
1656 if (ImGui::Combo(
"##BatchLayer", &batch_layer,
1657 "BG1 (Floor)\0BG2 (Objects)\0BG3 (Overlay)\0")) {
1665 static int batch_size = 0x12;
1666 ImGui::SetNextItemWidth(-1);
1667 if (ImGui::InputInt(
"##BatchSize", &batch_size, 1, 16,
1668 ImGuiInputTextFlags_CharsHexadecimal)) {
1676 float nudge_btn_size = (ImGui::GetContentRegionAvail().x - 24) / 4;
1698 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
1700 ImGui::PushStyleColor(
1702 ImVec4(theme.status_error.x * 0.7f, theme.status_error.y * 0.7f,
1703 theme.status_error.z * 0.7f, 1.0f));
1704 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, theme.status_error);
1706 ImVec2(button_width, 0))) {
1710 ImGui::PopStyleColor(2);
1715 ImVec2(button_width, 0))) {
1723 ImGui::Begin(
"Layer Controls");
1726 ImGui::Text(
"Current Layer:");
1736 static bool layer_visible[3] = {
true,
true,
true};
1737 ImGui::Text(
"Layer Visibility:");
1738 ImGui::Checkbox(
"Show Layer 0", &layer_visible[0]);
1739 ImGui::Checkbox(
"Show Layer 1", &layer_visible[1]);
1740 ImGui::Checkbox(
"Show Layer 2", &layer_visible[2]);
1756 int count0 = 0, count1 = 0, count2 = 0;
1758 switch (obj.GetLayerValue()) {
1770 ImGui::Text(
"Layer 0: %d objects", count0);
1771 ImGui::Text(
"Layer 1: %d objects", count1);
1772 ImGui::Text(
"Layer 2: %d objects", count2);
1782 return absl::OkStatus();
1793 if (grid_dx == 0 && grid_dy == 0) {
1794 return absl::OkStatus();
1803 int new_x = obj.
x() + grid_dx;
1804 int new_y = obj.y() + grid_dy;
1807 new_x = std::max(0, std::min(63, new_x));
1808 new_y = std::max(0, std::min(63, new_y));
1822 return absl::OkStatus();
1827 return {
false, {}, {
"No room loaded"}};
1836 for (
size_t i = 0; i < objects.size(); i++) {
1837 for (
size_t j = i + 1; j < objects.size(); j++) {
1840 absl::StrFormat(
"Objects at indices %d and %d collide", i, j));
1852 std::vector<std::string> all_issues = result.errors;
1853 all_issues.insert(all_issues.end(), result.warnings.begin(),
1854 result.warnings.end());
1905 return std::make_unique<DungeonObjectEditor>(rom);
1909namespace ObjectCategories {
1913 {
"Walls", {0x10, 0x11, 0x12, 0x13},
"Basic wall objects"},
1914 {
"Floors", {0x20, 0x21, 0x22, 0x23},
"Floor tile objects"},
1915 {
"Decorations", {0x30, 0x31, 0x32, 0x33},
"Decorative objects"},
1916 {
"Interactive", {0xF9, 0xFA, 0xFB},
"Interactive objects like chests"},
1917 {
"Stairs", {0x13, 0x14, 0x15, 0x16},
"Staircase objects"},
1918 {
"Doors", {0x17, 0x18, 0x19, 0x1A},
"Door objects"},
1920 {0xF80, 0xF81, 0xF82, 0xF97},
1921 "Special dungeon objects (Type 3)"}};
1925 const std::string& category_name) {
1928 for (
const auto&
category : categories) {
1929 if (
category.name == category_name) {
1934 return absl::NotFoundError(
"Category not found");
1940 for (
const auto&
category : categories) {
1941 for (
int id :
category.object_ids) {
1942 if (
id == object_id) {
1948 return absl::NotFoundError(
"Object category not found");
1953 info.
id = object_id;
1958 if (object_id >= 0x10 && object_id <= 0x1F) {
1965 }
else if (object_id >= 0x20 && object_id <= 0x2F) {
1966 info.
name =
"Floor";
1972 }
else if (object_id == 0xF9) {
1973 info.
name =
"Small Chest";
1980 info.
name =
"Unknown Object";
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
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.
ValidationResult ValidateRoom()
static constexpr int kDefaultObjectSize
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)
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
ObjectTemplateManager template_manager_
const std::vector< ObjectTemplate > & GetTemplates() const
absl::Status BatchMoveObjects(const std::vector< size_t > &indices, int dx, int dy)
DungeonObjectEditor(Rom *rom)
absl::Status ChangeObjectLayer(size_t object_index, int new_layer)
absl::Status BatchChangeObjectLayer(const std::vector< size_t > &indices, int new_layer)
absl::Status DeleteObject(size_t object_index)
void CopySelectedObjects(const std::vector< size_t > &indices)
SelectionState selection_state_
absl::Status InitializeEditor()
void SetSelectionChangedCallback(SelectionChangedCallback callback)
absl::Status SelectObject(int screen_x, int screen_y)
std::optional< size_t > DuplicateObject(size_t object_index, int offset_x=1, int offset_y=1)
absl::Status AlignSelectedObjects(Alignment alignment)
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
std::vector< RoomObject > clipboard_
static constexpr int kMinLayer
ObjectChangedCallback object_changed_callback_
DungeonValidator validator_
void RenderLayerVisualization(gfx::Bitmap &canvas)
std::vector< std::string > GetValidationErrors()
absl::Status LoadRoom(int room_id)
std::unique_ptr< Room > owned_room_
void SetConfig(const EditorConfig &config)
std::function< void(size_t object_index, const RoomObject &object)> ObjectChangedCallback
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)
void SetExternalRoom(Room *room)
absl::Status ClearSelection()
bool IsObjectAtPosition(const RoomObject &object, int x, int y)
void SetCurrentLayer(int layer)
void SetObjectChangedCallback(ObjectChangedCallback callback)
std::vector< size_t > PasteObjects()
absl::Status HandleMouseRelease(int x, int y)
void UpdatePreviewObject()
absl::Status InsertTemplate(const ObjectTemplate &tmpl, int x, int y)
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_
absl::Status BatchResizeObjects(const std::vector< size_t > &indices, int new_size)
static constexpr int kMinObjectSize
absl::Status ChangeObjectType(size_t object_index, int new_type)
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)
absl::Status CreateTemplateFromSelection(const std::string &name, const std::string &description)
RoomChangedCallback room_changed_callback_
void RenderLayerControls()
ValidationResult ValidateRoom(const Room &room)
static ObjectTemplate CreateFromObjects(const std::string &name, const std::string &description, const std::vector< RoomObject > &objects, int origin_x, int origin_y)
const std::vector< ObjectTemplate > & GetTemplates() const
std::vector< RoomObject > InstantiateTemplate(const ObjectTemplate &tmpl, int x, int y, Rom *rom)
absl::Status LoadTemplates(const std::string &directory_path)
absl::Status SaveTemplate(const ObjectTemplate &tmpl, const std::string &directory_path)
void set_size(uint8_t size)
absl::Status RemoveObject(size_t index)
size_t GetTileObjectCount() const
absl::Status SaveObjects()
RoomObject & GetTileObject(size_t index)
const std::vector< RoomObject > & GetTileObjects() const
void SetTileObjects(const std::vector< RoomObject > &objects)
absl::Status AddObject(const RoomObject &object)
void RemoveTileObject(size_t index)
#define ICON_MD_ARROW_FORWARD
#define ICON_MD_OPEN_WITH
#define ICON_MD_ARROW_DOWNWARD
#define ICON_MD_ASPECT_RATIO
#define ICON_MD_ARROW_UPWARD
#define ICON_MD_ARROW_BACK
#define ICON_MD_SELECT_ALL
#define ICON_MD_CONTENT_COPY
#define ICON_MD_DELETE_SWEEP
const AgentUITheme & GetTheme()
void PropertyRow(const char *label, const char *value)
void SectionHeader(const char *icon, const char *label, const ImVec4 &color)
bool BeginPropertyTable(const char *id, int columns, ImGuiTableFlags extra_flags)
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.
int GetObjectSubtype(int object_id)
std::string GetObjectName(int object_id)
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
std::vector< std::string > errors