7#include <unordered_map>
8#include <unordered_set>
10#include "absl/strings/str_format.h"
29 return patterns.status();
30 if (*patterns && begin >= 0) {
32 (*patterns)->words.lower_bound(
static_cast<uint32_t
>(begin));
33 if (it != (*patterns)->words.end() && it->first < end) {
34 return absl::FailedPreconditionError(
35 "Object tile words are supplied by a pattern source; reopen the "
36 "object tile editor to edit the source file");
39 return absl::OkStatus();
47 if (i < source.
size()) {
58 for (
int i = 0; i < palette.
size(); ++i) {
60 for (
size_t color = 0; color < padded.size(); ++color) {
68 if (palette.
empty()) {
71 if (requested >= 0 && requested <
static_cast<int>(palette.
size())) {
82 size_t word_count()
const {
return static_cast<size_t>(width * height); }
88 if (
id == 0x01 ||
id == 0x02)
90 if (
id == 0x61 ||
id == 0x62)
92 if (
id == 0x03 ||
id == 0x04)
94 if (
id == 0x63 ||
id == 0x64)
96 if (
id >= 0x100 &&
id <= 0x107)
98 if (
id == 0x11F ||
id == 0x120)
111 return active.status();
112 if (!*active || (*active)->source_path != expected.
source_path) {
113 return absl::AbortedError(
"Object pattern source changed with the project");
117 return current.status();
118 if (current->source_text != expected.
source_text) {
119 return absl::AbortedError(
"Object pattern source changed after capture");
121 return absl::OkStatus();
132 return lhs_start < rhs_end && rhs_start < lhs_end;
136 return RangesOverlap(lhs_address, lhs_address + 2, rhs_address,
144 return absl::FailedPreconditionError(
145 "Editable object cell is outside the audited layout");
147 return static_cast<size_t>(shape.
row_major
159 const std::vector<ObjectDrawer::TileTrace>& traces) {
161 if (traces.empty()) {
165 layout.
object_id =
static_cast<int16_t
>(traces[0].object_id);
168 int min_x = traces[0].x_tile;
169 int min_y = traces[0].y_tile;
172 for (
const auto& t : traces) {
173 min_x = std::min(min_x,
static_cast<int>(t.x_tile));
174 min_y = std::min(min_y,
static_cast<int>(t.y_tile));
175 max_x = std::max(max_x,
static_cast<int>(t.x_tile));
176 max_y = std::max(max_y,
static_cast<int>(t.y_tile));
184 std::unordered_map<uint32_t, size_t> last_trace_by_cell;
185 last_trace_by_cell.reserve(traces.size());
186 for (
size_t i = 0; i < traces.size(); ++i) {
187 const auto& t = traces[i];
188 const uint32_t cell_key =
189 (
static_cast<uint32_t
>(
static_cast<uint16_t
>(t.x_tile)) << 16) |
190 static_cast<uint16_t
>(t.y_tile);
191 last_trace_by_cell[cell_key] = i;
194 std::vector<size_t> surviving_trace_indices;
195 surviving_trace_indices.reserve(last_trace_by_cell.size());
196 for (
const auto& [_, trace_index] : last_trace_by_cell) {
197 surviving_trace_indices.push_back(trace_index);
199 std::sort(surviving_trace_indices.begin(), surviving_trace_indices.end());
201 layout.
cells.reserve(surviving_trace_indices.size());
202 for (
size_t trace_index : surviving_trace_indices) {
203 const auto& t = traces[trace_index];
205 cell.
rel_x = t.x_tile - min_x;
206 cell.
rel_y = t.y_tile - min_y;
209 bool h_mirror = (t.flags & 0x1) != 0;
210 bool v_mirror = (t.flags & 0x2) != 0;
211 bool priority = (t.flags & 0x4) != 0;
212 uint8_t palette = (t.flags >> 3) & 0x7;
214 gfx::TileInfo(t.tile_id, palette, v_mirror, h_mirror, priority);
219 layout.
cells.push_back(cell);
226 for (
auto& cell :
cells) {
227 if (cell.rel_x == rel_x && cell.rel_y == rel_y)
235 for (
const auto& cell :
cells) {
236 if (cell.rel_x == rel_x && cell.rel_y == rel_y)
243 for (
const auto& cell :
cells) {
251 for (
auto& cell :
cells) {
254 cell.modified =
false;
266 int16_t object_id,
int subtype) {
267 const int runtime_count =
269 if (runtime_count == 0) {
270 return absl::UnimplementedError(
271 "Custom tile editing supports only registered fixed runtime objects");
273 if (subtype < 0 || subtype >= runtime_count) {
274 return absl::OutOfRangeError(
275 "Custom object subtype is outside the runtime dispatch table");
279 const std::string filename = manager.ResolveFilename(object_id, subtype);
280 if (filename.empty()) {
281 return absl::NotFoundError(
282 "Custom object runtime slot has no mapped asset filename");
305 cell.
rel_x = tile.rel_x;
306 cell.
rel_y = tile.rel_y;
310 layout.
cells.push_back(cell);
323 uint8_t object_size) {
325 return absl::FailedPreconditionError(
"ROM not loaded");
336 auto [anchor_x, anchor_y] =
338 RoomObject obj(object_id, anchor_x, anchor_y, preview_size, 0);
343 bool is_custom =
false;
344 std::string custom_filename;
345 int subtype = obj.
size_ & 0x1F;
349 if (custom_result.ok()) {
360 std::vector<ObjectDrawer::TileTrace> traces;
367 auto status = drawer.
DrawObject(obj, dummy_bg1, dummy_bg2, palette);
374 if (traces.empty()) {
375 return absl::NotFoundError(
"Object produced no tile traces");
393 return ShapeForObject(object_id).has_value();
399 return absl::UnimplementedError(
400 "Standard object tile editing is not supported for this object");
403 return absl::FailedPreconditionError(
"ROM not loaded");
406 const auto shape = *ShapeForObject(object_id);
407 const uint32_t descriptor_pc_address = DescriptorForObject(object_id);
408 auto descriptor_or =
rom_->
ReadWord(
static_cast<int>(descriptor_pc_address));
409 if (!descriptor_or.ok()) {
410 return descriptor_or.status();
412 const uint16_t descriptor_word = *descriptor_or;
413 const int64_t source_address =
415 if (source_address < 0 ||
416 source_address +
static_cast<int64_t
>(shape.word_count() * 2) >
417 static_cast<int64_t
>(
rom_->
size()) ||
418 source_address > std::numeric_limits<int>::max()) {
419 return absl::OutOfRangeError(
420 "Editable object tile source is outside the loaded ROM");
422 const int64_t source_end =
423 source_address +
static_cast<int64_t
>(shape.word_count() * 2);
424 if (RangesOverlap(source_address, source_end, kObjectMetadataStart,
425 kObjectMetadataEnd)) {
426 return absl::FailedPreconditionError(
427 "Editable object tile source overlaps object descriptor or routine "
433 return patterns.status();
434 std::shared_ptr<const DungeonTilePatterns> pattern_source;
437 for (int64_t pc = source_address; pc < source_end; pc += 2) {
438 covered += (*patterns)->words.count(
static_cast<uint32_t
>(pc));
440 if (covered != 0 && covered != shape.word_count()) {
441 return absl::FailedPreconditionError(
442 "Object mixes ROM and source-file tiles; edit its complete source "
446 pattern_source = *patterns;
450 if (!layout_or.ok()) {
451 return layout_or.status();
455 return absl::FailedPreconditionError(
456 "Custom objects do not use standard ROM source provenance");
459 layout.
cells.size() != shape.word_count() ||
462 return absl::FailedPreconditionError(
463 "Object draw does not match the audited source layout");
467 span.
pc_address =
static_cast<uint32_t
>(source_address);
469 for (
size_t word_index = 0; word_index < shape.word_count(); ++word_index) {
473 return word_or.status();
477 ? pattern_source->words.at(span.
pc_address + word_index * 2)
481 std::vector<bool> mapped_words(shape.word_count(),
false);
482 for (
auto& cell : layout.
cells) {
483 auto source_word_index_or = ResolveSourceWordIndex(cell, shape);
484 if (!source_word_index_or.ok()) {
485 return source_word_index_or.status();
487 const size_t source_word_index = *source_word_index_or;
488 if (mapped_words[source_word_index]) {
489 return absl::FailedPreconditionError(
490 "Editable object layout maps multiple cells to one source word");
492 if (cell.original_word != span.
expected_words[source_word_index]) {
493 return absl::FailedPreconditionError(
494 "Draw trace does not match the resolved ROM source word");
496 mapped_words[source_word_index] =
true;
499 if (std::find(mapped_words.begin(), mapped_words.end(),
false) !=
500 mapped_words.end()) {
501 return absl::FailedPreconditionError(
502 "Editable object layout does not cover every source word");
509 provenance.
spans.push_back(std::move(span));
519 if (!room_gfx_buffer) {
520 return absl::FailedPreconditionError(
"No room graphics buffer");
522 if (layout.
cells.empty()) {
523 return absl::OkStatus();
530 std::vector<uint8_t> pixel_data(bmp_w * bmp_h, 255);
531 bitmap.
Create(bmp_w, bmp_h, 8, pixel_data);
535 if (!palette.
empty()) {
536 bitmap.
SetPalette(BuildCombinedPaletteBanks(palette));
539 SDL_SetColorKey(bitmap.
surface(), SDL_TRUE, 255);
540 SDL_SetSurfaceBlendMode(bitmap.
surface(), SDL_BLENDMODE_BLEND);
546 for (
const auto& cell : layout.
cells) {
551 if (runtime_word == 0) {
556 int px = cell.rel_x * 8;
557 int py = cell.rel_y * 8;
563 return absl::OkStatus();
567 gfx::Bitmap& atlas,
const uint8_t* room_gfx_buffer,
569 std::optional<int16_t> custom_object_id, uint16_t retained_attributes) {
570 if (!room_gfx_buffer) {
571 return absl::FailedPreconditionError(
"No room graphics buffer");
575 custom_object_id.has_value() ? 255 : 0);
578 const int resolved_palette = ResolvePaletteIndex(palette, display_palette);
579 if (!palette.
empty()) {
581 BuildPaddedPaletteBank(palette.
palette_ref(resolved_palette)));
584 SDL_SetColorKey(atlas.
surface(), SDL_TRUE, 255);
585 SDL_SetSurfaceBlendMode(atlas.
surface(), SDL_BLENDMODE_BLEND);
600 if (custom_object_id.has_value()) {
604 const uint16_t source_word =
605 static_cast<uint16_t
>(tile_id | ((display_palette & 7) << 10) |
606 (retained_attributes & 0xE000));
607 const uint16_t runtime_word =
609 if (runtime_word == 0) {
612 info.
id_ = runtime_word & 0x03FF;
618 return absl::OkStatus();
623 return absl::OkStatus();
628 return absl::FailedPreconditionError(
629 "Custom object has no filename for write-back");
632 return absl::FailedPreconditionError(
633 "Custom object has no source snapshot for safe write-back");
635 const int runtime_count =
638 return absl::FailedPreconditionError(
639 "Custom object layout no longer identifies a fixed runtime slot");
644 for (
const auto& cell : layout.
cells) {
645 object.tiles.push_back(
652 return absl::AbortedError(
653 "Custom object slot mapping changed after it was opened; edits were "
659 if (!published_or.ok()) {
660 return published_or.status();
665 return absl::OkStatus();
670 return plan_or.status();
678 return absl::InvalidArgumentError(
679 "Cannot build a standard ROM write plan for a custom object");
684 return absl::FailedPreconditionError(
"ROM not loaded");
687 return absl::FailedPreconditionError(
688 "Standard object layout has no editable source provenance");
691 return absl::UnimplementedError(
692 "Standard object tile editing is not supported for this object");
695 const auto shape = *ShapeForObject(layout.
object_id);
697 if (provenance.object_id != layout.
object_id) {
698 return absl::FailedPreconditionError(
699 "Object tile source provenance does not match the layout object");
701 const uint32_t expected_descriptor_address =
703 if (provenance.descriptor_pc_address != expected_descriptor_address) {
704 return absl::FailedPreconditionError(
705 "Object tile source descriptor address does not match the object");
707 if (provenance.spans.size() != 1 ||
708 provenance.spans.front().expected_words.size() != shape.word_count()) {
709 return absl::FailedPreconditionError(
710 "Object tile source provenance does not match the audited source map");
713 const int64_t rom_size =
static_cast<int64_t
>(
rom_->
size());
714 if (provenance.descriptor_pc_address >
715 static_cast<uint32_t
>(std::numeric_limits<int>::max()) ||
716 static_cast<int64_t
>(provenance.descriptor_pc_address) + 2 > rom_size) {
717 return absl::OutOfRangeError(
718 "Object tile source descriptor is outside the loaded ROM");
720 auto current_descriptor_or =
721 rom_->
ReadWord(
static_cast<int>(provenance.descriptor_pc_address));
722 if (!current_descriptor_or.ok()) {
723 return current_descriptor_or.status();
725 if (*current_descriptor_or != provenance.expected_descriptor_word) {
726 return absl::FailedPreconditionError(
727 "Object tile source descriptor changed after capture");
730 const auto& span = provenance.spans.front();
731 const int64_t expected_span_address =
733 provenance.expected_descriptor_word;
734 if (span.pc_address != expected_span_address) {
735 return absl::FailedPreconditionError(
736 "Object tile source span does not match its descriptor");
738 if (span.pc_address >
739 static_cast<uint32_t
>(std::numeric_limits<int>::max()) ||
740 static_cast<int64_t
>(span.pc_address) +
741 static_cast<int64_t
>(span.expected_words.size() * 2) >
743 return absl::OutOfRangeError(
744 "Object tile source span is outside the loaded ROM");
747 const int64_t span_start =
static_cast<int64_t
>(span.pc_address);
748 const int64_t span_end =
749 span_start +
static_cast<int64_t
>(span.expected_words.size() * 2);
750 if (RangesOverlap(span_start, span_end, kObjectMetadataStart,
751 kObjectMetadataEnd)) {
752 return absl::FailedPreconditionError(
753 "Object tile source span overlaps object descriptor or routine "
763 std::vector<int> precondition_addresses;
764 const int descriptor_address =
765 static_cast<int>(provenance.descriptor_pc_address);
766 precondition_addresses.push_back(descriptor_address);
768 {descriptor_address, provenance.expected_descriptor_word});
769 for (
size_t word_index = 0; word_index < span.expected_words.size();
771 const int address =
static_cast<int>(span.pc_address + word_index * 2);
772 if (std::any_of(precondition_addresses.begin(),
773 precondition_addresses.end(), [&](
int existing_address) {
774 return WordRangesOverlap(existing_address, address);
776 return absl::FailedPreconditionError(
777 "Object tile descriptor and source preconditions overlap");
779 precondition_addresses.push_back(address);
784 return absl::FailedPreconditionError(
785 "Object source pattern is incomplete");
787 current_word_or = source_word->second;
789 if (!current_word_or.ok()) {
790 return current_word_or.status();
792 if (*current_word_or != span.expected_words[word_index]) {
793 return absl::FailedPreconditionError(
794 "Object tile source word changed after capture");
796 plan.
preconditions_.push_back({address, span.expected_words[word_index]});
799 if (layout.
cells.size() != shape.word_count() ||
802 return absl::FailedPreconditionError(
803 "Object tile layout does not match the audited shape");
806 std::unordered_set<int> resolved_addresses;
807 for (
const auto& cell : layout.
cells) {
808 if (!cell.source_ref.has_value()) {
809 return absl::FailedPreconditionError(
810 "Object tile cell has no editable source reference");
812 const auto& source_ref = *cell.source_ref;
813 if (source_ref.span_index >= provenance.spans.size()) {
814 return absl::FailedPreconditionError(
815 "Object tile source span reference is out of bounds");
817 const auto& referenced_span = provenance.spans[source_ref.span_index];
818 if (source_ref.word_index >= referenced_span.expected_words.size()) {
819 return absl::FailedPreconditionError(
820 "Object tile source word reference is out of bounds");
823 auto expected_source_index_or = ResolveSourceWordIndex(cell, shape);
824 if (!expected_source_index_or.ok()) {
825 return expected_source_index_or.status();
827 if (source_ref.span_index != 0 ||
828 source_ref.word_index != *expected_source_index_or) {
829 return absl::FailedPreconditionError(
830 "Object tile cell source reference does not match the audited map");
833 const int64_t address64 =
static_cast<int64_t
>(referenced_span.pc_address) +
834 static_cast<int64_t
>(source_ref.word_index) * 2;
835 if (address64 < 0 || address64 + 2 > rom_size ||
836 address64 > std::numeric_limits<int>::max()) {
837 return absl::OutOfRangeError(
838 "Object tile write range is outside the loaded ROM");
840 const int address =
static_cast<int>(address64);
841 if (!resolved_addresses.insert(address).second) {
842 return absl::FailedPreconditionError(
843 "Object tile cells resolve to a duplicate ROM address");
846 const uint16_t expected_word =
847 referenced_span.expected_words[source_ref.word_index];
848 if (cell.original_word != expected_word) {
849 return absl::FailedPreconditionError(
850 "Object tile cell original word does not match its source");
853 if (!cell.modified && current_layout_word != cell.original_word) {
854 return absl::FailedPreconditionError(
855 "Object tile cell changed without being marked modified");
857 if (!cell.modified) {
861 plan.
writes_.push_back({address, expected_word, current_layout_word});
862 plan.
write_ranges_.emplace_back(
static_cast<uint32_t
>(address),
863 static_cast<uint32_t
>(address + 2));
872 return absl::OkStatus();
875 return absl::FailedPreconditionError(
876 "Object tile write plan ranges do not match its writes");
879 return absl::FailedPreconditionError(
880 "Object tile write plan has no source provenance preconditions");
883 return absl::FailedPreconditionError(
"ROM not loaded");
886 const int64_t rom_size =
static_cast<int64_t
>(
rom_->
size());
887 std::unordered_map<int, uint16_t> precondition_words;
891 const auto source_word =
894 if (source_word->second != precondition.expected_word) {
895 return absl::AbortedError(
"Pattern source precondition changed");
900 return word.status();
901 if (*word != precondition.expected_word) {
902 return absl::AbortedError(
"Object descriptor changed after capture");
906 std::map<uint32_t, uint16_t> replacements;
907 for (
const auto& write : plan.
writes_)
908 replacements.emplace(write.address, write.word);
912 return absl::OkStatus();
914 std::vector<int> precondition_addresses;
916 const int64_t address =
static_cast<int64_t
>(precondition.address);
917 if (address < 0 || address + 2 > rom_size) {
918 return absl::OutOfRangeError(
919 "Object tile precondition is outside the loaded ROM");
922 if (std::any_of(precondition_addresses.begin(),
923 precondition_addresses.end(), [&](
int existing_address) {
924 return WordRangesOverlap(existing_address,
925 precondition.address);
927 return absl::FailedPreconditionError(
928 "Object tile plan has overlapping CAS preconditions");
930 precondition_addresses.push_back(precondition.address);
931 if (!precondition_words
932 .emplace(precondition.address, precondition.expected_word)
934 return absl::FailedPreconditionError(
935 "Object tile plan has duplicate CAS preconditions");
939 std::unordered_set<int> write_addresses;
940 std::vector<int> validated_write_addresses;
941 for (
size_t write_index = 0; write_index < plan.
writes_.size();
943 const auto& write = plan.
writes_[write_index];
944 const int64_t address =
static_cast<int64_t
>(write.address);
945 if (address < 0 || address + 2 > rom_size) {
946 return absl::OutOfRangeError(
947 "Object tile write range is outside the loaded ROM");
949 if (std::any_of(validated_write_addresses.begin(),
950 validated_write_addresses.end(), [&](
int existing_address) {
951 return WordRangesOverlap(existing_address, write.address);
953 return absl::FailedPreconditionError(
954 "Object tile plan has overlapping write ranges");
956 validated_write_addresses.push_back(write.address);
957 if (!write_addresses.insert(write.address).second) {
958 return absl::FailedPreconditionError(
959 "Object tile plan has duplicate write addresses");
961 const std::pair<uint32_t, uint32_t> expected_range = {
962 static_cast<uint32_t
>(write.address),
963 static_cast<uint32_t
>(write.address + 2)};
965 return absl::FailedPreconditionError(
966 "Object tile write plan range does not match its write address");
968 const auto precondition = precondition_words.find(write.address);
969 if (precondition == precondition_words.end() ||
970 precondition->second != write.expected_word) {
971 return absl::FailedPreconditionError(
972 "Object tile write has no matching source precondition");
979 auto current_word_or =
rom_->
ReadWord(precondition.address);
980 if (!current_word_or.ok()) {
981 return current_word_or.status();
983 if (*current_word_or != precondition.expected_word) {
984 return absl::FailedPreconditionError(
985 "Object tile source changed after the write plan was built");
988 for (
const auto& write : plan.
writes_) {
990 if (!current_word_or.ok()) {
991 return current_word_or.status();
993 if (*current_word_or != write.expected_word) {
994 return absl::FailedPreconditionError(
995 "Object tile write target changed after the plan was built");
1001 for (
const auto& write : plan.
writes_) {
1002 transaction.
WriteWord(write.address, write.word);
1004 const absl::Status status = transaction.
Commit();
1010 return absl::OkStatus();
1013absl::StatusOr<ObjectTileSourceImpact>
1017 return absl::FailedPreconditionError(
"ROM not loaded");
1020 return absl::InvalidArgumentError(
1021 "Custom objects do not use standard ROM tile sources");
1024 return absl::FailedPreconditionError(
1025 "Standard object layout has no editable source provenance");
1032 if (!plan_or.ok()) {
1033 return plan_or.status();
1036 std::vector<ObjectTileReadRange> target_ranges;
1038 if (span.expected_words.empty()) {
1039 return absl::FailedPreconditionError(
1040 "Editable object tile source span is empty");
1042 const uint64_t end =
static_cast<uint64_t
>(span.pc_address) +
1043 static_cast<uint64_t
>(span.expected_words.size()) * 2;
1044 if (end >
rom_->
size() || end > std::numeric_limits<uint32_t>::max()) {
1045 return absl::OutOfRangeError(
1046 "Editable object tile source span is outside the loaded ROM");
1048 target_ranges.push_back({span.pc_address,
static_cast<uint32_t
>(end)});
1051 const auto overlaps_target =
1053 return std::any_of(target_ranges.begin(), target_ranges.end(),
1055 return candidate.begin < target.end &&
1056 target.begin < candidate.end;
1062 const auto inspect_object = [&](
int object_id) -> absl::Status {
1065 if (!ranges_or.ok()) {
1066 return absl::FailedPreconditionError(absl::StrFormat(
1067 "Unable to resolve tile sources for object 0x%03X: %s", object_id,
1068 ranges_or.status().message()));
1072 entry.
object_id =
static_cast<int16_t
>(object_id);
1073 for (
const auto& range : *ranges_or) {
1074 if (overlaps_target(range)) {
1081 return absl::OkStatus();
1087 for (
int object_id = 0x000; object_id <= 0x0F7; ++object_id) {
1088 const absl::Status status = inspect_object(object_id);
1093 for (
int object_id = 0x100; object_id <= 0x13F; ++object_id) {
1094 const absl::Status status = inspect_object(object_id);
1099 for (
int object_id = 0xF80; object_id <= 0xFFF; ++object_id) {
1100 const absl::Status status = inspect_object(object_id);
1110 constexpr int kLightableTorchTileCount = 4;
1117 for (
const auto consumer :
1121 for (
const auto& range : kLightableTorchSources) {
1122 if (overlaps_target(range)) {
1126 if (!torch_impact.overlapping_ranges.empty()) {
1131 const bool includes_edited_object = std::any_of(
1133 [&](
const auto& entry) { return entry.object_id == layout.object_id; });
1134 if (!includes_edited_object) {
1135 return absl::FailedPreconditionError(
1136 "Edited object is missing from its tile source impact inventory");
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
void set_dirty(bool dirty)
absl::StatusOr< uint16_t > ReadWord(int offset) const
void AdvanceObjectTileRevision()
Transaction & WriteWord(int address, uint16_t value)
Represents a bitmap image optimized for SNES ROM hacking.
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
void UpdateSurfacePixels()
Update SDL surface with current pixel data from data_ vector Call this after modifying pixel data via...
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
SDL_Surface * surface() const
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void AddColor(const SnesColor &color)
SNES 16-bit tile metadata container.
static int RuntimeSubtypeCountForObject(int object_id)
static CustomObjectManager & Get()
absl::StatusOr< std::shared_ptr< CustomObject > > GetObjectInternal(int object_id, int subtype)
absl::StatusOr< std::shared_ptr< const DungeonTilePatterns > > GetTilePatterns()
std::string ResolveFilename(int object_id, int subtype) const
Draws dungeon objects to background buffers using game patterns.
void DrawTileToBitmap(gfx::Bitmap &bitmap, const gfx::TileInfo &tile_info, int pixel_x, int pixel_y, const uint8_t *tiledata)
Draw a single tile directly to bitmap.
void SetRoomFloorGraphics(uint8_t floor1, uint8_t floor2)
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr, gfx::BackgroundBuffer *layout_bg2=nullptr)
Draw a room object to background buffers.
void ClearTraceCollector()
void SetTraceCollector(std::vector< TileTrace > *collector, bool trace_only=false)
std::pair< int, int > ResolveAnchor(int16_t object_id, uint8_t size_byte) const
Resolve the canvas anchor (x, y) for a given object's draw routine.
static ObjectGeometry & Get()
Direct ROM parser for dungeon objects.
absl::StatusOr< std::vector< ObjectTileReadRange > > ResolveTileReadRanges(int16_t object_id)
Resolve every tile-data range consumed by ParseObject.
absl::StatusOr< ObjectTileSourceImpact > AnalyzeStandardTileSourceImpact(const ObjectTileLayout &layout) const
absl::Status ApplyStandardWritePlan(const ObjectTileWritePlan &plan)
static constexpr int kAtlasTilesPerRow
ObjectTileEditor(Rom *rom)
absl::Status RenderLayoutToBitmap(const ObjectTileLayout &layout, gfx::Bitmap &bitmap, const uint8_t *room_gfx_buffer, const gfx::PaletteGroup &palette)
static bool IsEditableStandardObject(int16_t object_id)
absl::Status BuildTile8Atlas(gfx::Bitmap &atlas, const uint8_t *room_gfx_buffer, const gfx::PaletteGroup &palette, int display_palette=2, std::optional< int16_t > custom_object_id=std::nullopt, uint16_t retained_attributes=0)
absl::StatusOr< ObjectTileLayout > CaptureObjectLayout(int16_t object_id, const Room &room, const gfx::PaletteGroup &palette)
absl::Status WriteBack(ObjectTileLayout &layout)
static constexpr int kAtlasWidthPx
static constexpr int kAtlasTileCount
static constexpr int kAtlasHeightPx
absl::StatusOr< ObjectTileLayout > CaptureEditableObjectLayout(int16_t object_id, const Room &room, const gfx::PaletteGroup &palette)
absl::StatusOr< ObjectTileWritePlan > BuildStandardWritePlan(const ObjectTileLayout &layout) const
absl::StatusOr< ObjectTileLayout > LoadCustomObjectLayout(int16_t object_id, int subtype)
Fully resolved standard-object ROM writes.
std::vector< std::pair< uint32_t, uint32_t > > write_ranges_
std::vector< Write > writes_
std::vector< WordPrecondition > preconditions_
std::shared_ptr< const DungeonTilePatterns > pattern_source_
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
#define ASSIGN_OR_RETURN(type_variable_name, expression)
uint16_t TileInfoToWord(TileInfo tile_info)
TileInfo WordToTileInfo(uint16_t word)
gfx::SnesPalette BuildCombinedPaletteBanks(const gfx::PaletteGroup &palette)
absl::StatusOr< size_t > ResolveSourceWordIndex(const ObjectTileLayout::Cell &cell, const EditableShape &shape)
constexpr int64_t kObjectMetadataEnd
absl::Status CheckPatternSourceReadOnly(int64_t begin, int64_t end)
constexpr int64_t kObjectMetadataStart
uint32_t DescriptorForObject(int16_t id)
bool WordRangesOverlap(int64_t lhs_address, int64_t rhs_address)
gfx::SnesPalette BuildPaddedPaletteBank(const gfx::SnesPalette &source)
constexpr int kPaletteBankSize
std::optional< EditableShape > ShapeForObject(int16_t id)
int ResolvePaletteIndex(const gfx::PaletteGroup &palette, int requested)
bool RangesOverlap(int64_t lhs_start, int64_t lhs_end, int64_t rhs_start, int64_t rhs_end)
absl::Status ValidatePatternContext(const DungeonTilePatterns &expected)
uint8_t DefaultRoomObjectSizeForPlacement(int object_id)
constexpr int kRoomObjectSubtype3
uint8_t CanonicalRoomObjectSize(int object_id, uint8_t requested_size)
constexpr int kRoomObjectSubtype1
constexpr int kRoomObjectSubtype2
uint16_t CustomObjectRuntimeTileWord(int object_id, uint16_t source_word)
absl::StatusOr< DungeonTilePatterns > LoadDungeonTilePatterns(const std::string &path)
constexpr int kRoomObjectTileAddress
absl::Status PublishDungeonTilePatterns(const DungeonTilePatterns &expected, const std::map< uint32_t, uint16_t > &replacements)
absl::StatusOr< std::vector< uint8_t > > PublishCustomObjectBinary(const std::string &custom_objects_folder, const std::string &filename, const CustomObject &object, const std::vector< uint8_t > &expected_source_bytes, const fs::path &expected_resolved_path)
absl::StatusOr< CustomObjectAsset > LoadCustomObjectAsset(const std::string &custom_objects_folder, const std::string &filename)
#define RETURN_IF_ERROR(expr)
Represents a group of palettes.
const SnesPalette & palette_ref(int i) const
std::filesystem::path resolved_path
std::vector< uint8_t > source_bytes
Represents a decoded custom object (from binary format)
BoundingBox GetBoundingBox() const
std::vector< TileMapEntry > tiles
Editable tile8 layout captured from an object's draw trace.
bool HasModifications() const
std::shared_ptr< const DungeonTilePatterns > pattern_source
std::vector< uint8_t > custom_source_bytes
std::optional< ObjectTileSourceProvenance > source_provenance
std::filesystem::path custom_resolved_path
Cell * FindCell(int rel_x, int rel_y)
std::vector< Cell > cells
std::string custom_filename
static ObjectTileLayout FromTraces(const std::vector< ObjectDrawer::TileTrace > &traces)
One half-open PC range consumed while parsing an object's tile data.
std::vector< ObjectTileReadRange > overlapping_ranges
One standard object whose parser-visible tile sources overlap an editable layout's authorized source ...
std::vector< ObjectTileReadRange > overlapping_ranges
Fail-closed impact inventory within ObjectParser's source model plus explicitly modeled runtime consu...
std::vector< ObjectTileRuntimeConsumerImpactEntry > runtime_consumers
std::vector< ObjectTileSourceImpactEntry > affected_objects
ROM descriptor and source snapshot authorizing an editable capture.
uint16_t expected_descriptor_word
uint32_t descriptor_pc_address
std::vector< ObjectTileSourceSpan > spans
A cell's exact word within an authorized source span.
One contiguous ROM-backed source span for an object's tile words.
std::vector< uint16_t > expected_words
size_t word_count() const