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) {
406 int new_x =
object.
x() + dx;
407 int new_y =
object.y() + dy;
410 new_x = std::max(0, std::min(63, new_x));
411 new_y = std::max(0, std::min(63, new_y));
425 return absl::OkStatus();
429 const std::vector<size_t>& indices,
int new_layer) {
431 return absl::FailedPreconditionError(
"No room loaded");
434 if (new_layer < kMinLayer || new_layer >
kMaxLayer) {
435 return absl::InvalidArgumentError(
"Invalid layer");
444 for (
size_t index : indices) {
459 return absl::OkStatus();
463 const std::vector<size_t>& indices,
int new_size) {
465 return absl::FailedPreconditionError(
"No room loaded");
469 return absl::InvalidArgumentError(
"Invalid object size");
478 for (
size_t index : indices) {
495 return absl::OkStatus();
513 int new_x =
object.
x() + offset_x;
514 int new_y =
object.y() + offset_y;
517 new_x = std::max(0, std::min(63, new_x));
518 new_y = std::max(0, std::min(63, new_y));
542 for (
size_t index : indices) {
543 if (index < current_room_->GetTileObjectCount()) {
557 std::vector<size_t> new_indices;
569 int new_x = std::min(63, new_obj.
x() + 1);
570 int new_y = std::min(63, new_obj.
y() + 1);
571 new_obj.
set_x(new_x);
572 new_obj.
set_y(new_y);
575 new_indices.push_back(start_index++);
589 return absl::FailedPreconditionError(
"No room loaded");
593 return absl::OutOfRangeError(
"Object index out of range");
597 if (new_type < 0 || new_type > 0xFFF) {
598 return absl::InvalidArgumentError(
"Invalid object type");
608 object.
id_ = new_type;
618 return absl::OkStatus();
624 return absl::FailedPreconditionError(
"No room loaded");
640 std::vector<RoomObject> new_objects =
645 for (
const auto& new_obj : new_objects) {
648 return absl::FailedPreconditionError(
649 "Template placement would cause collision");
656 for (
const auto& obj : new_objects) {
663 size_t added_count = new_objects.size();
664 for (
size_t i = 0; i < added_count; ++i) {
675 return absl::OkStatus();
681 return absl::FailedPreconditionError(
"No objects selected");
684 std::vector<RoomObject> objects;
685 int min_x = 64, min_y = 64;
689 if (index < current_room_->GetTileObjectCount()) {
691 objects.push_back(obj);
692 if (obj.x() < min_x) min_x = obj.
x();
693 if (obj.y() < min_y) min_y = obj.y();
711 return absl::FailedPreconditionError(
"No room loaded");
715 return absl::OkStatus();
738 for (
size_t index : indices) {
744 if (obj.x() < ref_val) ref_val = obj.
x();
747 if (obj.x() > ref_val) ref_val = obj.x();
750 if (obj.y() < ref_val) ref_val = obj.y();
753 if (obj.y() > ref_val) ref_val = obj.y();
767 if (count > 0) ref_val = sum / count;
771 for (
size_t index : indices) {
797 return absl::OkStatus();
803 return absl::FailedPreconditionError(
"No room loaded");
807 return absl::OutOfRangeError(
"Object index out of range");
810 if (new_layer < kMinLayer || new_layer >
kMaxLayer) {
811 return absl::InvalidArgumentError(
"Invalid layer");
831 return absl::OkStatus();
837 return absl::FailedPreconditionError(
"No room loaded");
852 int layer_delta = delta > 0 ? 1 : -1;
860 return absl::OkStatus();
863 return absl::OkStatus();
874 return absl::OkStatus();
881 if (object_index < current_room_->GetTileObjectCount()) {
892 return absl::OkStatus();
895 return absl::OkStatus();
905 if (current_size < 0x40) {
906 return current_size + 0x10;
907 }
else if (current_size < 0x80) {
908 return current_size + 0x08;
910 return current_size + 0x04;
914 if (current_size > 0x80) {
915 return current_size - 0x04;
916 }
else if (current_size > 0x40) {
917 return current_size - 0x08;
919 return current_size - 0x10;
931 bool shift_pressed) {
933 return absl::FailedPreconditionError(
"No room loaded");
945 if (object_index.has_value()) {
964 if (object_index.has_value()) {
986 if (object_index.has_value()) {
997 return absl::OkStatus();
1004 return absl::FailedPreconditionError(
"No room loaded");
1016 if (!undo_status.ok()) {
1027 return absl::FailedPreconditionError(
"No room loaded");
1040 return absl::OkStatus();
1045 return absl::FailedPreconditionError(
"No room loaded");
1054 if (object_index.has_value()) {
1064 return absl::OkStatus();
1081 return absl::OkStatus();
1086 return absl::FailedPreconditionError(
"No room loaded");
1090 return absl::OutOfRangeError(
"Object index out of range");
1107 return absl::OkStatus();
1126 if (object_type >= 0 && object_type <= 0xFFF) {
1135 return std::nullopt;
1142 return static_cast<size_t>(i);
1146 return std::nullopt;
1152 int obj_x =
object.x_ * 16;
1153 int obj_y =
object.y_ * 16;
1160 int obj_height = 16;
1163 if (
object.size_ > 0x80) {
1168 return (x >= obj_x && x < obj_x + obj_width && y >= obj_y &&
1169 y < obj_y + obj_height);
1177 int obj1_x = obj1.
x_ * 16;
1178 int obj1_y = obj1.
y_ * 16;
1182 int obj2_x = obj2.
x_ * 16;
1183 int obj2_y = obj2.
y_ * 16;
1188 if (obj1.
size_ > 0x80) {
1193 if (obj2.
size_ > 0x80) {
1198 return !(obj1_x + obj1_w <= obj2_x || obj2_x + obj2_w <= obj1_x ||
1199 obj1_y + obj1_h <= obj2_y || obj2_y + obj2_h <= obj1_y);
1208 int room_x = screen_x / 16;
1209 int room_y = screen_y / 16;
1211 return {room_x, room_y};
1217 int screen_x = room_x * 16;
1218 int screen_y = room_y * 16;
1220 return {screen_x, screen_y};
1247 return absl::FailedPreconditionError(
"No room loaded");
1255 undo_point.
timestamp = std::chrono::steady_clock::now();
1268 return absl::OkStatus();
1273 return absl::FailedPreconditionError(
"Nothing to undo");
1281 current_state.
timestamp = std::chrono::steady_clock::now();
1294 return absl::FailedPreconditionError(
"Nothing to redo");
1302 current_state.
timestamp = std::chrono::steady_clock::now();
1315 return absl::FailedPreconditionError(
"No room loaded");
1337 return absl::OkStatus();
1358static uint32_t BlendColors(uint32_t base, uint32_t tint) {
1359 uint8_t a_tint = (tint >> 24) & 0xFF;
1363 uint8_t r_base = (base >> 16) & 0xFF;
1364 uint8_t g_base = (base >> 8) & 0xFF;
1365 uint8_t b_base = base & 0xFF;
1367 uint8_t r_tint = (tint >> 16) & 0xFF;
1368 uint8_t g_tint = (tint >> 8) & 0xFF;
1369 uint8_t b_tint = tint & 0xFF;
1371 float alpha = a_tint / 255.0f;
1372 uint8_t r = r_base * (1.0f - alpha) + r_tint * alpha;
1373 uint8_t g = g_base * (1.0f - alpha) + g_tint * alpha;
1374 uint8_t b = b_base * (1.0f - alpha) + b_tint * alpha;
1376 return 0xFF000000 | (r << 16) | (g << 8) | b;
1391 int x = obj.
x() * 16;
1392 int y = obj.y() * 16;
1393 int w = 16 + (obj.size() * 4);
1394 int h = 16 + (obj.size() * 4);
1402 for (
int py = y; py < y + h; py++) {
1403 for (
int px = x; px < x + w; px++) {
1404 if (px < canvas.
width() && py < canvas.
height() &&
1405 (px < x + 2 || px >= x + w - 2 || py < y + 2 || py >= y + h - 2)) {
1406 canvas.
SetPixel(px, py, sel_color);
1421 int x = obj.x() * 16;
1422 int y = obj.y() * 16;
1426 uint32_t tint_color = 0xFF000000;
1427 switch (obj.GetLayerValue()) {
1440 uint8_t r = (tint_color >> 16) & 0xFF;
1441 uint8_t g = (tint_color >> 8) & 0xFF;
1442 uint8_t b = tint_color & 0xFF;
1445 for (
int py = y; py < y + h && py < canvas.
height(); py++) {
1446 for (
int px = x; px < x + w && px < canvas.
width(); px++) {
1447 if (px == x || px == x + w - 1 || py == y || py == y + h - 1) {
1448 canvas.
SetPixel(px, py, layer_color);
1465 if (obj_idx < current_room_->GetTileObjectCount()) {
1475 ImGui::TableNextRow();
1476 ImGui::TableNextColumn();
1478 ImGui::TableNextColumn();
1480 ImGui::Text(
"0x%03X", obj.id_);
1482 ImGui::TextColored(theme.text_secondary_gray,
"(%s)", obj_name.c_str());
1486 ImGui::TableNextRow();
1487 ImGui::TableNextColumn();
1488 ImGui::Text(
"Type");
1489 ImGui::TableNextColumn();
1490 ImGui::Text(
"Subtype %d", subtype);
1501 ImGui::TableNextRow();
1502 ImGui::TableNextColumn();
1504 ImGui::TableNextColumn();
1506 ImGui::SetNextItemWidth(-1);
1507 if (ImGui::InputInt(
"##X", &x, 1, 4)) {
1508 if (x >= 0 && x < 64) {
1517 ImGui::TableNextRow();
1518 ImGui::TableNextColumn();
1520 ImGui::TableNextColumn();
1522 ImGui::SetNextItemWidth(-1);
1523 if (ImGui::InputInt(
"##Y", &y, 1, 4)) {
1524 if (y >= 0 && y < 64) {
1541 if (obj.id_ < 0x100) {
1542 ImGui::TableNextRow();
1543 ImGui::TableNextColumn();
1544 ImGui::Text(
"Size");
1545 ImGui::TableNextColumn();
1546 int size = obj.size();
1547 ImGui::SetNextItemWidth(-1);
1548 if (ImGui::SliderInt(
"##Size", &size, 0, 15,
"0x%02X")) {
1557 ImGui::TableNextRow();
1558 ImGui::TableNextColumn();
1559 ImGui::Text(
"Change ID");
1560 ImGui::TableNextColumn();
1562 ImGui::SetNextItemWidth(-1);
1563 if (ImGui::InputInt(
"##ID", &
id, 1, 16,
1564 ImGuiInputTextFlags_CharsHexadecimal)) {
1565 if (
id >= 0 &&
id <= 0xFFF) {
1581 ImGui::TableNextRow();
1582 ImGui::TableNextColumn();
1583 ImGui::Text(
"Layer");
1584 ImGui::TableNextColumn();
1585 int layer = obj.GetLayerValue();
1586 ImGui::SetNextItemWidth(-1);
1587 if (ImGui::Combo(
"##Layer", &layer,
1588 "BG1 (Floor)\0BG2 (Objects)\0BG3 (Overlay)\0")) {
1603 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
1605 ImGui::PushStyleColor(ImGuiCol_Button,
1606 ImVec4(theme.status_error.x * 0.7f,
1607 theme.status_error.y * 0.7f,
1608 theme.status_error.z * 0.7f, 1.0f));
1609 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, theme.status_error);
1610 if (ImGui::Button(
ICON_MD_DELETE " Delete", ImVec2(button_width, 0))) {
1614 ImGui::PopStyleColor(2);
1619 ImVec2(button_width, 0))) {
1621 duplicate.
set_x(obj.x() + 1);
1628 ImGui::TextColored(theme.text_warning_yellow,
ICON_MD_SELECT_ALL " %zu objects selected",
1635 static int batch_layer = 0;
1636 ImGui::SetNextItemWidth(-1);
1637 if (ImGui::Combo(
"##BatchLayer", &batch_layer,
1638 "BG1 (Floor)\0BG2 (Objects)\0BG3 (Overlay)\0")) {
1646 static int batch_size = 0x12;
1647 ImGui::SetNextItemWidth(-1);
1648 if (ImGui::InputInt(
"##BatchSize", &batch_size, 1, 16,
1649 ImGuiInputTextFlags_CharsHexadecimal)) {
1657 float nudge_btn_size = (ImGui::GetContentRegionAvail().x - 24) / 4;
1679 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
1681 ImGui::PushStyleColor(ImGuiCol_Button,
1682 ImVec4(theme.status_error.x * 0.7f,
1683 theme.status_error.y * 0.7f,
1684 theme.status_error.z * 0.7f, 1.0f));
1685 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, theme.status_error);
1687 ImVec2(button_width, 0))) {
1691 ImGui::PopStyleColor(2);
1696 ImVec2(button_width, 0))) {
1704 ImGui::Begin(
"Layer Controls");
1707 ImGui::Text(
"Current Layer:");
1717 static bool layer_visible[3] = {
true,
true,
true};
1718 ImGui::Text(
"Layer Visibility:");
1719 ImGui::Checkbox(
"Show Layer 0", &layer_visible[0]);
1720 ImGui::Checkbox(
"Show Layer 1", &layer_visible[1]);
1721 ImGui::Checkbox(
"Show Layer 2", &layer_visible[2]);
1737 int count0 = 0, count1 = 0, count2 = 0;
1739 switch (obj.GetLayerValue()) {
1751 ImGui::Text(
"Layer 0: %d objects", count0);
1752 ImGui::Text(
"Layer 1: %d objects", count1);
1753 ImGui::Text(
"Layer 2: %d objects", count2);
1763 return absl::OkStatus();
1774 if (grid_dx == 0 && grid_dy == 0) {
1775 return absl::OkStatus();
1784 int new_x = obj.
x() + grid_dx;
1785 int new_y = obj.y() + grid_dy;
1788 new_x = std::max(0, std::min(63, new_x));
1789 new_y = std::max(0, std::min(63, new_y));
1803 return absl::OkStatus();
1808 return {
false, {}, {
"No room loaded"}};
1817 for (
size_t i = 0; i < objects.size(); i++) {
1818 for (
size_t j = i + 1; j < objects.size(); j++) {
1821 absl::StrFormat(
"Objects at indices %d and %d collide", i, j));
1833 std::vector<std::string> all_issues = result.errors;
1834 all_issues.insert(all_issues.end(), result.warnings.begin(),
1835 result.warnings.end());
1886 return std::make_unique<DungeonObjectEditor>(rom);
1890namespace ObjectCategories {
1894 {
"Walls", {0x10, 0x11, 0x12, 0x13},
"Basic wall objects"},
1895 {
"Floors", {0x20, 0x21, 0x22, 0x23},
"Floor tile objects"},
1896 {
"Decorations", {0x30, 0x31, 0x32, 0x33},
"Decorative objects"},
1897 {
"Interactive", {0xF9, 0xFA, 0xFB},
"Interactive objects like chests"},
1898 {
"Stairs", {0x13, 0x14, 0x15, 0x16},
"Staircase objects"},
1899 {
"Doors", {0x17, 0x18, 0x19, 0x1A},
"Door objects"},
1900 {
"Special", {0xF80, 0xF81, 0xF82, 0xF97},
"Special dungeon objects (Type 3)"}};
1904 const std::string& category_name) {
1907 for (
const auto&
category : categories) {
1908 if (
category.name == category_name) {
1913 return absl::NotFoundError(
"Category not found");
1919 for (
const auto&
category : categories) {
1920 for (
int id :
category.object_ids) {
1921 if (
id == object_id) {
1927 return absl::NotFoundError(
"Object category not found");
1932 info.
id = object_id;
1937 if (object_id >= 0x10 && object_id <= 0x1F) {
1944 }
else if (object_id >= 0x20 && object_id <= 0x2F) {
1945 info.
name =
"Floor";
1951 }
else if (object_id == 0xF9) {
1952 info.
name =
"Small Chest";
1959 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