254 return absl::InvalidArgumentError(
"Room pointer is null");
270 std::array<bool, kGridSize * kGridSize> occupied{};
279 ResolveTrackObjectDimensions(obj, options, dimension_service);
280 int base_x = obj.x_ + dims.offset_x_tiles;
281 int base_y = obj.y_ + dims.offset_y_tiles;
282 int w = std::max(1, dims.width_tiles);
283 int h = std::max(1, dims.height_tiles);
285 for (
int dy = 0; dy < h; ++dy) {
286 for (
int dx = 0; dx < w; ++dx) {
287 int gx = base_x + dx;
288 int gy = base_y + dy;
289 if (gx >= 0 && gx < kGridSize && gy >= 0 && gy < kGridSize) {
290 occupied[gy * kGridSize + gx] =
true;
297 for (
int y = 0; y < kGridSize; ++y) {
298 for (
int x = 0; x < kGridSize; ++x) {
299 if (!occupied[y * kGridSize + x])
302 bool up = (y > 0) && occupied[(y - 1) * kGridSize + x];
303 bool down = (y < kGridSize - 1) && occupied[(y + 1) * kGridSize + x];
304 bool left = (x > 0) && occupied[y * kGridSize + (x - 1)];
305 bool right = (x < kGridSize - 1) && occupied[y * kGridSize + (x + 1)];
307 uint8_t tile = ClassifyTile(up, down, left, right);
311 if (tile >= 0xB7 && tile <= 0xBA)
313 if (tile >= 0xB2 && tile <= 0xB5)
320 if (sx < 0 || sx >= kGridSize || sy < 0 || sy >= kGridSize)
322 size_t idx = sy * kGridSize + sx;
324 if (IsCornerTile(tile)) {
334 if (ox < 0 || ox >= kGridSize || oy < 0 || oy >= kGridSize)
336 size_t idx = oy * kGridSize + ox;
351 return absl::InvalidArgumentError(
"ROM not loaded");
354 return absl::OutOfRangeError(
"Room ID out of range");
357 const auto& data = rom->
vector();
359 return absl::FailedPreconditionError(
"ROM vector is empty");
364 static_cast<int>(data.size())) {
365 return absl::FailedPreconditionError(
366 "Custom collision pointer table not present in this ROM");
369 return absl::FailedPreconditionError(
370 "Custom collision data region not present in this ROM");
373 return absl::FailedPreconditionError(
374 "Custom collision data region truncated (ROM too small)");
383 "CustomCollisionPointers"));
387 "CustomCollisionData"));
392 std::vector<uint8_t> encoded;
393 encoded.push_back(0xF0);
394 encoded.push_back(0xF0);
396 for (
int y = 0; y < kGridSize; ++y) {
397 for (
int x = 0; x < kGridSize; ++x) {
398 uint8_t tile = map.
tiles[y * kGridSize + x];
401 uint16_t offset =
static_cast<uint16_t
>(y * kGridSize + x);
402 encoded.push_back(offset & 0xFF);
403 encoded.push_back(offset >> 8);
404 encoded.push_back(tile);
407 encoded.push_back(0xFF);
408 encoded.push_back(0xFF);
412 const size_t safe_end =
413 std::min(
static_cast<size_t>(data.size()),
416 std::vector<CollisionBlob> blobs;
420 if (ptr_offset + 2 >=
static_cast<int>(data.size()))
423 uint32_t snes_ptr = data[ptr_offset] | (data[ptr_offset + 1] << 8) |
424 (data[ptr_offset + 2] << 16);
430 return absl::FailedPreconditionError(
431 absl::StrFormat(
"Custom collision pointer for room 0x%02X points "
432 "before data region (pc=0x%06X)",
436 return absl::FailedPreconditionError(
437 absl::StrFormat(
"Custom collision pointer for room 0x%02X overlaps "
438 "WaterFill reserved region (pc=0x%06X)",
441 if (pc >= data.size()) {
442 return absl::OutOfRangeError(
"Custom collision pointer out of ROM range");
445 FindCollisionBlobEnd(data, pc, safe_end, r));
446 blobs.push_back(CollisionBlob{r, pc, end_pc});
447 if (end_pc > max_used_pc) {
448 max_used_pc = end_pc;
455 uint32_t write_pos = max_used_pc;
456 const auto target = std::find_if(
457 blobs.begin(), blobs.end(),
458 [room_id](
const CollisionBlob& blob) { return blob.room_id == room_id; });
459 if (target != blobs.end() && encoded.size() <= target->end - target->start) {
460 const bool overlaps_other = std::any_of(
461 blobs.begin(), blobs.end(), [&](
const CollisionBlob& other) {
462 return other.room_id != room_id && BlobsOverlap(*target, other);
464 if (!overlaps_other) {
465 write_pos = target->start;
471 return absl::ResourceExhaustedError(absl::StrFormat(
472 "Not enough collision data space. Need %d bytes at 0x%06X, "
473 "region ends at 0x%06X",
477 if (write_pos + encoded.size() > data.size()) {
478 return absl::OutOfRangeError(
479 absl::StrFormat(
"ROM too small for custom collision write (need "
480 "end=0x%06X, size=0x%06X)",
481 write_pos + encoded.size(), data.size()));
484 rom->
WriteVector(
static_cast<int>(write_pos), std::move(encoded)));
487 uint32_t snes_addr =
PcToSnes(write_pos);
493 return absl::OkStatus();
498 int min_x = kGridSize, max_x = 0, min_y = kGridSize, max_y = 0;
499 for (
int y = 0; y < kGridSize; ++y) {
500 for (
int x = 0; x < kGridSize; ++x) {
501 if (map.
tiles[y * kGridSize + x] != 0) {
502 min_x = std::min(min_x, x);
503 max_x = std::max(max_x, x);
504 min_y = std::min(min_y, y);
505 max_y = std::max(max_y, y);
514 min_x = std::max(0, min_x - 1);
515 min_y = std::max(0, min_y - 1);
516 max_x = std::min(kGridSize - 1, max_x + 1);
517 max_y = std::min(kGridSize - 1, max_y + 1);
519 std::stringstream ss;
522 for (
int x = min_x; x <= max_x; ++x) {
523 ss << absl::StrFormat(
"%X", x % 16);
527 for (
int y = min_y; y <= max_y; ++y) {
528 ss << absl::StrFormat(
"%02X: ", y);
529 for (
int x = min_x; x <= max_x; ++x) {
530 uint8_t tile = map.
tiles[y * kGridSize + x];
531 ss << TileToChar(tile);