254 return absl::InvalidArgumentError(
"Room pointer is null");
269 std::array<bool, kGridSize * kGridSize> occupied{};
276 const int subtype = obj.size_ & 0x1F;
282 ResolveTrackObjectDimensions(obj, options, dimension_service);
283 int base_x = obj.x_ + dims.offset_x_tiles;
284 int base_y = obj.y_ + dims.offset_y_tiles;
285 int w = std::max(1, dims.width_tiles);
286 int h = std::max(1, dims.height_tiles);
288 for (
int dy = 0; dy < h; ++dy) {
289 for (
int dx = 0; dx < w; ++dx) {
290 int gx = base_x + dx;
291 int gy = base_y + dy;
292 if (gx >= 0 && gx < kGridSize && gy >= 0 && gy < kGridSize) {
293 occupied[gy * kGridSize + gx] =
true;
300 for (
int y = 0; y < kGridSize; ++y) {
301 for (
int x = 0; x < kGridSize; ++x) {
302 if (!occupied[y * kGridSize + x])
305 bool up = (y > 0) && occupied[(y - 1) * kGridSize + x];
306 bool down = (y < kGridSize - 1) && occupied[(y + 1) * kGridSize + x];
307 bool left = (x > 0) && occupied[y * kGridSize + (x - 1)];
308 bool right = (x < kGridSize - 1) && occupied[y * kGridSize + (x + 1)];
310 uint8_t tile = ClassifyTile(up, down, left, right);
314 if (tile >= 0xB7 && tile <= 0xBA)
316 if (tile >= 0xB2 && tile <= 0xB5)
323 if (sx < 0 || sx >= kGridSize || sy < 0 || sy >= kGridSize)
325 size_t idx = sy * kGridSize + sx;
327 if (IsCornerTile(tile)) {
337 if (ox < 0 || ox >= kGridSize || oy < 0 || oy >= kGridSize)
339 size_t idx = oy * kGridSize + ox;
354 return absl::InvalidArgumentError(
"ROM not loaded");
357 return absl::OutOfRangeError(
"Room ID out of range");
360 const auto& data = rom->
vector();
362 return absl::FailedPreconditionError(
"ROM vector is empty");
367 static_cast<int>(data.size())) {
368 return absl::FailedPreconditionError(
369 "Custom collision pointer table not present in this ROM");
372 return absl::FailedPreconditionError(
373 "Custom collision data region not present in this ROM");
376 return absl::FailedPreconditionError(
377 "Custom collision data region truncated (ROM too small)");
386 "CustomCollisionPointers"));
390 "CustomCollisionData"));
395 std::vector<uint8_t> encoded;
396 encoded.push_back(0xF0);
397 encoded.push_back(0xF0);
399 for (
int y = 0; y < kGridSize; ++y) {
400 for (
int x = 0; x < kGridSize; ++x) {
401 uint8_t tile = map.
tiles[y * kGridSize + x];
404 uint16_t offset =
static_cast<uint16_t
>(y * kGridSize + x);
405 encoded.push_back(offset & 0xFF);
406 encoded.push_back(offset >> 8);
407 encoded.push_back(tile);
410 encoded.push_back(0xFF);
411 encoded.push_back(0xFF);
415 const size_t safe_end =
416 std::min(
static_cast<size_t>(data.size()),
419 std::vector<CollisionBlob> blobs;
423 if (ptr_offset + 2 >=
static_cast<int>(data.size()))
426 uint32_t snes_ptr = data[ptr_offset] | (data[ptr_offset + 1] << 8) |
427 (data[ptr_offset + 2] << 16);
433 return absl::FailedPreconditionError(
434 absl::StrFormat(
"Custom collision pointer for room 0x%02X points "
435 "before data region (pc=0x%06X)",
439 return absl::FailedPreconditionError(
440 absl::StrFormat(
"Custom collision pointer for room 0x%02X overlaps "
441 "WaterFill reserved region (pc=0x%06X)",
444 if (pc >= data.size()) {
445 return absl::OutOfRangeError(
"Custom collision pointer out of ROM range");
448 FindCollisionBlobEnd(data, pc, safe_end, r));
449 blobs.push_back(CollisionBlob{r, pc, end_pc});
450 if (end_pc > max_used_pc) {
451 max_used_pc = end_pc;
458 uint32_t write_pos = max_used_pc;
459 const auto target = std::find_if(
460 blobs.begin(), blobs.end(),
461 [room_id](
const CollisionBlob& blob) { return blob.room_id == room_id; });
462 if (target != blobs.end() && encoded.size() <= target->end - target->start) {
463 const bool overlaps_other = std::any_of(
464 blobs.begin(), blobs.end(), [&](
const CollisionBlob& other) {
465 return other.room_id != room_id && BlobsOverlap(*target, other);
467 if (!overlaps_other) {
468 write_pos = target->start;
474 return absl::ResourceExhaustedError(absl::StrFormat(
475 "Not enough collision data space. Need %d bytes at 0x%06X, "
476 "region ends at 0x%06X",
480 if (write_pos + encoded.size() > data.size()) {
481 return absl::OutOfRangeError(
482 absl::StrFormat(
"ROM too small for custom collision write (need "
483 "end=0x%06X, size=0x%06X)",
484 write_pos + encoded.size(), data.size()));
487 rom->
WriteVector(
static_cast<int>(write_pos), std::move(encoded)));
490 uint32_t snes_addr =
PcToSnes(write_pos);
496 return absl::OkStatus();
501 int min_x = kGridSize, max_x = 0, min_y = kGridSize, max_y = 0;
502 for (
int y = 0; y < kGridSize; ++y) {
503 for (
int x = 0; x < kGridSize; ++x) {
504 if (map.
tiles[y * kGridSize + x] != 0) {
505 min_x = std::min(min_x, x);
506 max_x = std::max(max_x, x);
507 min_y = std::min(min_y, y);
508 max_y = std::max(max_y, y);
517 min_x = std::max(0, min_x - 1);
518 min_y = std::max(0, min_y - 1);
519 max_x = std::min(kGridSize - 1, max_x + 1);
520 max_y = std::min(kGridSize - 1, max_y + 1);
522 std::stringstream ss;
525 for (
int x = min_x; x <= max_x; ++x) {
526 ss << absl::StrFormat(
"%X", x % 16);
530 for (
int y = min_y; y <= max_y; ++y) {
531 ss << absl::StrFormat(
"%02X: ", y);
532 for (
int x = min_x; x <= max_x; ++x) {
533 uint8_t tile = map.
tiles[y * kGridSize + x];
534 ss << TileToChar(tile);