411 census.
rooms.resize(kRooms);
416 std::vector<RoomCensusRoomFacts> facts = input.
rooms;
417 facts.resize(kRooms);
418 for (
int i = 0; i < kRooms; ++i) {
419 facts[i].room_id = i;
430 std::vector<std::vector<RoomLink>> out_edges(kRooms);
431 for (
int room = 0; room < kRooms; ++room) {
434 out_edges[room].push_back(std::move(link));
440 for (
int room = 0; room < kRooms; ++room) {
441 for (
const auto& edge : out_edges[room]) {
442 if (edge.to == room) {
446 ref.
kind = edge.kind;
450 census.
rooms[edge.to].references.push_back(std::move(ref));
453 std::vector<int> roots;
454 for (
const auto& entrance : input.
entrances) {
455 if (entrance.room_id < 0 || entrance.room_id >= kRooms) {
459 ref.
kind = entrance.kind;
462 switch (entrance.kind) {
464 ref.
detail = absl::StrFormat(
"spawn point %d", entrance.entrance_id);
467 ref.
detail = absl::StrFormat(
"overworld hole -> entrance 0x%02X (%s)",
468 entrance.entrance_id, entrance.placement);
472 absl::StrFormat(
"entrance 0x%02X (not placed on the overworld)",
473 entrance.entrance_id);
476 ref.
detail = absl::StrFormat(
"entrance 0x%02X (%s)",
477 entrance.entrance_id, entrance.placement);
481 roots.push_back(entrance.room_id);
483 census.
rooms[entrance.room_id].references.push_back(std::move(ref));
487 auto is_empty = [&](
int room) {
488 return facts[room].object_count == 0 ||
489 (facts[room].object_count <= 1 && facts[room].sprite_count <= 1);
491 std::vector<bool> reached(kRooms,
false);
492 std::deque<int> queue;
493 for (
int root : roots) {
494 if (!reached[root]) {
495 reached[root] =
true;
496 queue.push_back(root);
500 while (!queue.empty()) {
501 const int room = queue.front();
503 for (
const auto& edge : out_edges[room]) {
504 if (edge.strong && !reached[edge.to]) {
505 reached[edge.to] =
true;
506 queue.push_back(edge.to);
518 std::vector<int> map_dungeon(kRooms, -1);
519 std::vector<bool> map_ref_added(kRooms,
false);
520 for (
bool changed =
true; changed;) {
524 const bool active = std::any_of(
525 rooms.begin(), rooms.end(),
526 [&](
int room) { return IsDungeonRoomId(room) && reached[room]; });
527 for (
int room : rooms) {
528 if (room < 0 || room >= kRooms) {
531 if (map_dungeon[room] < 0) {
532 map_dungeon[room] =
static_cast<int>(map) * 2;
534 const bool strong = active && !is_empty(room);
535 if (!map_ref_added[room] || (strong && !reached[room])) {
540 absl::StrFormat(
"on the pause map of dungeon ID 0x%02X%s",
541 static_cast<int>(map) * 2,
542 !active ?
" (no room of that map is reached)"
544 :
" (room is empty)");
545 if (!map_ref_added[room]) {
546 census.
rooms[room].references.push_back(ref);
547 map_ref_added[room] =
true;
549 for (
auto& existing : census.
rooms[room].references) {
557 if (strong && !reached[room]) {
558 reached[room] =
true;
559 queue.push_back(room);
568 std::vector<int> owner_of(kRooms, -1);
569 std::vector<bool> project_listed(kRooms,
false);
574 owner.
name = group.name;
576 const int index =
static_cast<int>(census.
owners.size());
577 census.
owners.push_back(owner);
578 for (
int room : group.rooms) {
579 if (room >= 0 && room < kRooms && owner_of[room] < 0) {
580 owner_of[room] = index;
581 project_listed[room] =
true;
588 int authored_rooms = 0;
589 int vanilla_rooms = 0;
590 for (
const auto& f : facts) {
591 if (f.object_count > 0) {
597 vanilla_rooms * 5 >= authored_rooms * 4;
600 std::map<int, int> derived_owner;
601 auto derived_owner_index = [&](
int dungeon_id) {
602 auto it = derived_owner.find(dungeon_id);
603 if (it != derived_owner.end()) {
610 owner.
name =
"Interiors";
613 owner.
id = absl::StrFormat(
"ID%02X", dungeon_id);
614 const std::string vanilla = VanillaDungeonName(dungeon_id);
617 absl::StrFormat(
"Dungeon ID 0x%02X (not in project)", dungeon_id);
618 }
else if (!vanilla_names || vanilla.empty() || (dungeon_id & 1) != 0) {
619 owner.
name = absl::StrFormat(
"Dungeon ID 0x%02X", dungeon_id);
621 owner.
name = vanilla;
624 const int index =
static_cast<int>(census.
owners.size());
625 census.
owners.push_back(owner);
626 derived_owner[dungeon_id] = index;
631 std::vector<const RoomCensusEntranceFact*> ordered;
632 for (
const auto& entrance : input.
entrances) {
634 entrance.room_id >= 0 && entrance.room_id < kRooms) {
635 ordered.push_back(&entrance);
639 ordered.begin(), ordered.end(), [](
const auto* a,
const auto* b) {
640 const bool a_int = a->dungeon_id == kRoomCensusInteriorDungeonId;
641 const bool b_int = b->dungeon_id == kRoomCensusInteriorDungeonId;
642 if (a_int != b_int) {
645 if (a->dungeon_id != b->dungeon_id) {
646 return a->dungeon_id < b->dungeon_id;
648 return a->entrance_id < b->entrance_id;
650 auto flood = [&](
bool owner_edges_only) {
651 for (
const auto* entrance : ordered) {
652 const int start = entrance->room_id;
653 if (project_listed[start]) {
656 int owner = owner_of[start];
658 owner = derived_owner_index(entrance->dungeon_id);
659 owner_of[start] = owner;
661 std::deque<int> q{start};
662 std::vector<bool> seen(kRooms,
false);
665 const int room = q.front();
667 for (
const auto& edge : out_edges[room]) {
668 if (!edge.strong || (owner_edges_only && !edge.owner_ok) ||
669 seen[edge.to] || project_listed[edge.to]) {
672 seen[edge.to] =
true;
673 if (owner_of[edge.to] < 0) {
674 owner_of[edge.to] = owner;
675 }
else if (owner_of[edge.to] != owner) {
678 q.push_back(edge.to);
685 for (
int room = 0; room <
kRooms; ++room) {
686 if (owner_of[room] < 0 && reached[room] && map_dungeon[room] >= 0) {
687 owner_of[room] = derived_owner_index(map_dungeon[room]);
693 for (
int room = 0; room <
kRooms; ++room) {
694 const auto& f = facts[room];
695 RoomCensusEntry& entry = census.rooms[room];
696 entry.room_id = room;
697 entry.reached = reached[room];
698 entry.owner_index = owner_of[room];
700 entry.owner_index >= 0 && census.owners[entry.owner_index].interior;
701 entry.object_count = f.object_count;
702 entry.sprite_count = f.sprite_count;
703 entry.vanilla_similarity = f.vanilla_similarity;
705 f.object_count == 0 || (f.object_count <= 1 && f.sprite_count <= 1);
706 const std::string content =
707 absl::StrFormat(
"%d object%s, %d sprite%s", f.object_count,
708 f.object_count == 1 ?
"" :
"s", f.sprite_count,
709 f.sprite_count == 1 ?
"" :
"s");
710 const int vanilla_percent =
711 static_cast<int>(f.vanilla_similarity * 100.0f + 0.5f);
714 std::vector<RoomReference> unique_refs;
715 for (
auto& ref : entry.references) {
716 const bool duplicate = std::any_of(
717 unique_refs.begin(), unique_refs.end(), [&](
const auto& seen) {
718 return seen.kind == ref.kind && seen.detail == ref.detail;
721 unique_refs.push_back(std::move(ref));
724 entry.references = std::move(unique_refs);
725 std::vector<std::string> strong_refs;
726 std::vector<std::string> weak_refs;
727 for (
const auto& ref : entry.references) {
728 (ref.strong ? strong_refs : weak_refs).push_back(ref.detail);
730 auto join_capped = [](
const std::vector<std::string>& items) {
731 constexpr size_t kMaxListed = 4;
732 std::vector<std::string> shown(
733 items.begin(), items.begin() + std::min(items.size(), kMaxListed));
734 std::string text = absl::StrJoin(shown,
"; ");
735 if (items.size() > kMaxListed) {
736 text += absl::StrFormat(
"; +%d more",
737 static_cast<int>(items.size() - kMaxListed));
744 entry.reasons.push_back(strong_refs.empty()
745 ?
"reached from an entrance"
746 :
"reached via " + join_capped(strong_refs));
747 }
else if (project_listed[room]) {
750 entry.reasons.push_back(absl::StrFormat(
751 "listed in project dungeon %s, but no entrance, stair, hole, warp "
752 "or door path reaches it",
753 census.owners[owner_of[room]].id));
754 }
else if (entry.empty) {
756 entry.reasons.push_back(
757 "no entrance, stair, hole, warp or door path reaches it");
758 entry.reasons.push_back(
"empty: " + content);
761 entry.reasons.push_back(
762 "no entrance, stair, hole, warp or door path reaches it");
763 entry.reasons.push_back(absl::StrFormat(
764 "vanilla leftover: %d%% of its objects match vanilla (%s)",
765 vanilla_percent, content));
768 entry.reasons.push_back(
769 "no entrance, stair, hole, warp or door path reaches it");
770 entry.reasons.push_back(absl::StrFormat(
771 "partly edited vanilla leftover: %d%% of its objects match vanilla "
772 "(%s); check the edits before reclaiming",
773 vanilla_percent, content));
777 entry.reasons.push_back(
778 "orphan: authored content but no entrance, stair, hole, warp or "
779 "door path reaches it; owner decision needed");
780 entry.reasons.push_back(
781 f.vanilla_similarity < 0.0f
783 : absl::StrFormat(
"%s, %d%% vanilla", content, vanilla_percent));
785 if (!entry.reached && !weak_refs.empty()) {
786 entry.reasons.push_back(
"caveat: " + join_capped(weak_refs));
789 entry.reasons.push_back(
"caveat: listed in RoomsWithPitDamage");
792 switch (entry.status) {
797 ++census.reclaimable_count;
800 ++census.in_use_count;
803 census.orphan_count += entry.orphan ? 1 : 0;
804 census.interior_count += entry.interior ? 1 : 0;
805 if (entry.owner_index >= 0) {
806 ++census.owners[entry.owner_index].room_count;
816 std::vector<int> free_rooms;
817 for (
const auto& entry : census.rooms) {
819 free_rooms.push_back(entry.room_id);
822 auto grid_distance = [](
int a,
int b) {
823 if ((a >> 8) != (b >> 8)) {
826 const int dr = std::abs(((a & 0xFF) >> 4) - ((b & 0xFF) >> 4));
827 const int dc = std::abs((a & 0x0F) - (b & 0x0F));
828 return std::max(dr, dc);
830 auto adjacent = [](
int a,
int b) {
831 if ((a >> 8) != (b >> 8)) {
834 const int dr = std::abs(((a & 0xFF) >> 4) - ((b & 0xFF) >> 4));
835 const int dc = std::abs((a & 0x0F) - (b & 0x0F));
838 std::vector<std::vector<int>> cores;
839 std::vector<bool> assigned(free_rooms.size(),
false);
840 for (
size_t i = 0; i < free_rooms.size(); ++i) {
844 std::vector<int> core;
845 std::deque<size_t> pending{i};
847 while (!pending.empty()) {
848 const size_t at = pending.front();
850 core.push_back(free_rooms[at]);
851 for (
size_t j = 0; j < free_rooms.size(); ++j) {
852 if (!assigned[j] && adjacent(free_rooms[at], free_rooms[j])) {
854 pending.push_back(j);
858 std::sort(core.begin(), core.end());
859 cores.push_back(std::move(core));
861 std::stable_sort(cores.begin(), cores.end(),
862 [](
const auto& a,
const auto& b) {
863 if (a.size() != b.size()) {
864 return a.size() > b.size();
866 return a.front() < b.front();
868 std::vector<bool> absorbed(cores.size(),
false);
869 for (
size_t i = 0; i < cores.size(); ++i) {
874 RoomCensusCluster cluster;
875 cluster.core_rooms = cores[i];
876 cluster.rooms = cores[i];
877 if (
static_cast<int>(cores[i].size()) >= kFreeBlockMinCore) {
878 for (
size_t j = i + 1; j < cores.size(); ++j) {
879 if (absorbed[j] || cores[j].size() >= cores[i].size()) {
883 for (
int a : cores[i]) {
884 for (
int b : cores[j]) {
890 cluster.rooms.insert(cluster.rooms.end(), cores[j].begin(),
895 std::sort(cluster.rooms.begin(), cluster.rooms.end());
897 census.free_clusters.push_back(std::move(cluster));
899 std::stable_sort(census.free_clusters.begin(), census.free_clusters.end(),
900 [](
const auto& a,
const auto& b) {
901 if (a.rooms.size() != b.rooms.size()) {
902 return a.rooms.size() > b.rooms.size();
904 return a.rooms.front() < b.rooms.front();