255 return absl::InvalidArgumentError(
"Room pointer is null");
270 std::array<bool, kGridSize * kGridSize> occupied{};
277 const int subtype = obj.size_ & 0x1F;
283 ResolveTrackObjectDimensions(obj, options, dimension_service);
284 int base_x = obj.x_ + dims.offset_x_tiles;
285 int base_y = obj.y_ + dims.offset_y_tiles;
286 int w = std::max(1, dims.width_tiles);
287 int h = std::max(1, dims.height_tiles);
289 for (
int dy = 0; dy < h; ++dy) {
290 for (
int dx = 0; dx < w; ++dx) {
291 int gx = base_x + dx;
292 int gy = base_y + dy;
293 if (gx >= 0 && gx < kGridSize && gy >= 0 && gy < kGridSize) {
294 occupied[gy * kGridSize + gx] =
true;
301 for (
int y = 0; y < kGridSize; ++y) {
302 for (
int x = 0; x < kGridSize; ++x) {
303 if (!occupied[y * kGridSize + x])
306 bool up = (y > 0) && occupied[(y - 1) * kGridSize + x];
307 bool down = (y < kGridSize - 1) && occupied[(y + 1) * kGridSize + x];
308 bool left = (x > 0) && occupied[y * kGridSize + (x - 1)];
309 bool right = (x < kGridSize - 1) && occupied[y * kGridSize + (x + 1)];
311 uint8_t tile = ClassifyTile(up, down, left, right);
315 if (tile >= 0xB7 && tile <= 0xBA)
317 if (tile >= 0xB2 && tile <= 0xB5)
324 if (sx < 0 || sx >= kGridSize || sy < 0 || sy >= kGridSize)
326 size_t idx = sy * kGridSize + sx;
328 if (IsCornerTile(tile)) {
338 if (ox < 0 || ox >= kGridSize || oy < 0 || oy >= kGridSize)
340 size_t idx = oy * kGridSize + ox;
355 return absl::InvalidArgumentError(
"ROM not loaded");
358 return absl::OutOfRangeError(
"Room ID out of range");
361 const auto& data = rom->
vector();
363 return absl::FailedPreconditionError(
"ROM vector is empty");
368 static_cast<int>(data.size())) {
369 return absl::FailedPreconditionError(
370 "Custom collision pointer table not present in this ROM");
373 return absl::FailedPreconditionError(
374 "Custom collision data region not present in this ROM");
377 return absl::FailedPreconditionError(
378 "Custom collision data region truncated (ROM too small)");
387 "CustomCollisionPointers"));
391 "CustomCollisionData"));
396 std::vector<uint8_t> encoded;
397 encoded.push_back(0xF0);
398 encoded.push_back(0xF0);
400 for (
int y = 0; y < kGridSize; ++y) {
401 for (
int x = 0; x < kGridSize; ++x) {
402 uint8_t tile = map.
tiles[y * kGridSize + x];
405 uint16_t offset =
static_cast<uint16_t
>(y * kGridSize + x);
406 encoded.push_back(offset & 0xFF);
407 encoded.push_back(offset >> 8);
408 encoded.push_back(tile);
411 encoded.push_back(0xFF);
412 encoded.push_back(0xFF);
416 const size_t safe_end =
417 std::min(
static_cast<size_t>(data.size()),
420 std::vector<CollisionBlob> blobs;
424 if (ptr_offset + 2 >=
static_cast<int>(data.size()))
427 uint32_t snes_ptr = data[ptr_offset] | (data[ptr_offset + 1] << 8) |
428 (data[ptr_offset + 2] << 16);
434 return absl::FailedPreconditionError(
435 absl::StrFormat(
"Custom collision pointer for room 0x%02X points "
436 "before data region (pc=0x%06X)",
440 return absl::FailedPreconditionError(
441 absl::StrFormat(
"Custom collision pointer for room 0x%02X overlaps "
442 "WaterFill reserved region (pc=0x%06X)",
445 if (pc >= data.size()) {
446 return absl::OutOfRangeError(
"Custom collision pointer out of ROM range");
449 FindCollisionBlobEnd(data, pc, safe_end, r));
450 blobs.push_back(CollisionBlob{r, pc, end_pc});
451 if (end_pc > max_used_pc) {
452 max_used_pc = end_pc;
459 uint32_t write_pos = max_used_pc;
460 const auto target = std::find_if(
461 blobs.begin(), blobs.end(),
462 [room_id](
const CollisionBlob& blob) { return blob.room_id == room_id; });
463 if (target != blobs.end() && encoded.size() <= target->end - target->start) {
464 const bool overlaps_other = std::any_of(
465 blobs.begin(), blobs.end(), [&](
const CollisionBlob& other) {
466 return other.room_id != room_id && BlobsOverlap(*target, other);
468 if (!overlaps_other) {
469 write_pos = target->start;
475 return absl::ResourceExhaustedError(absl::StrFormat(
476 "Not enough collision data space. Need %d bytes at 0x%06X, "
477 "region ends at 0x%06X",
481 if (write_pos + encoded.size() > data.size()) {
482 return absl::OutOfRangeError(
483 absl::StrFormat(
"ROM too small for custom collision write (need "
484 "end=0x%06X, size=0x%06X)",
485 write_pos + encoded.size(), data.size()));
488 rom->
WriteVector(
static_cast<int>(write_pos), std::move(encoded)));
491 uint32_t snes_addr =
PcToSnes(write_pos);
497 return absl::OkStatus();
502 int min_x = kGridSize, max_x = 0, min_y = kGridSize, max_y = 0;
503 for (
int y = 0; y < kGridSize; ++y) {
504 for (
int x = 0; x < kGridSize; ++x) {
505 if (map.
tiles[y * kGridSize + x] != 0) {
506 min_x = std::min(min_x, x);
507 max_x = std::max(max_x, x);
508 min_y = std::min(min_y, y);
509 max_y = std::max(max_y, y);
518 min_x = std::max(0, min_x - 1);
519 min_y = std::max(0, min_y - 1);
520 max_x = std::min(kGridSize - 1, max_x + 1);
521 max_y = std::min(kGridSize - 1, max_y + 1);
523 std::stringstream ss;
526 for (
int x = min_x; x <= max_x; ++x) {
527 ss << absl::StrFormat(
"%X", x % 16);
531 for (
int y = min_y; y <= max_y; ++y) {
532 ss << absl::StrFormat(
"%02X: ", y);
533 for (
int x = min_x; x <= max_x; ++x) {
534 uint8_t tile = map.
tiles[y * kGridSize + x];
535 ss << TileToChar(tile);