yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_atlas.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <filesystem>
6#include <fstream>
7#include <map>
8#include <tuple>
9#include <utility>
10
11#include "absl/strings/ascii.h"
12#include "absl/strings/str_format.h"
13#include "absl/strings/str_replace.h"
14#include "core/hack_manifest.h"
15#include "rom/rom.h"
16#include "util/rom_hash.h"
18#include "zelda3/dungeon/room.h"
20
21namespace yaze::zelda3::atlas {
22
23namespace {
24
25constexpr int kRooms = kAtlasRoomCount;
26
27std::string RoomHex(int room) {
28 return absl::StrFormat("0x%02X", room);
29}
30
31uint32_t LoRomPc(uint32_t snes) {
32 return ((snes >> 16) & 0x7F) * 0x8000 + (snes & 0x7FFF);
33}
34
35bool IsHexString(const std::string& text) {
36 return !text.empty() && std::all_of(text.begin(), text.end(), [](char c) {
37 return std::isxdigit(static_cast<unsigned char>(c)) != 0;
38 });
39}
40
41// Display data for one census owner.
43 bool own_area = false; // False: the room falls through to annotation
44 // areas, then INT / OTHER.
45 std::string code;
46 std::string name;
50};
51
52class AreaTable {
53 public:
54 AtlasArea& Get(const std::string& key) {
55 auto it = index_.find(key);
56 if (it != index_.end()) {
57 return areas_[it->second];
58 }
59 index_[key] = areas_.size();
60 areas_.push_back({});
61 areas_.back().key = key;
62 return areas_.back();
63 }
64 bool Has(const std::string& key) const { return index_.contains(key); }
65 std::vector<AtlasArea> Take() { return std::move(areas_); }
66
67 private:
68 std::vector<AtlasArea> areas_;
69 std::map<std::string, size_t> index_;
70};
71
72std::string ExpandLabel(const std::string& templ, const std::string& kind,
73 int n) {
74 const std::string base =
75 templ.empty() ? absl::AsciiStrToUpper(kind) + " {n}" : templ;
76 return absl::StrReplaceAll(base, {{"{n}", std::to_string(n)}});
77}
78
79} // namespace
80
81const char* RoomStatusName(RoomStatus status) {
82 switch (status) {
84 return "in_use";
86 return "free";
88 return "reclaimable";
90 return "orphan";
92 return "reserved";
93 }
94 return "in_use";
95}
96
97const char* LinkKindName(LinkKind kind) {
98 switch (kind) {
100 return "entrance";
101 case LinkKind::kHole:
102 return "hole";
103 case LinkKind::kSpawn:
104 return "spawn";
105 case LinkKind::kStair:
106 return "stair";
107 case LinkKind::kPit:
108 return "pit";
110 return "warp_tiles";
112 return "warp_tag";
114 return "pit_damage";
116 return "tag_pit";
118 return "annotation";
120 return "symbol";
121 }
122 return "?";
123}
124
125absl::StatusOr<std::map<std::string, uint32_t>> ReadWlaSymbols(
126 const std::string& path) {
127 std::ifstream file(path);
128 if (!file) {
129 return absl::NotFoundError(
130 absl::StrFormat("Cannot read symbol file %s", path));
131 }
132 std::map<std::string, uint32_t> symbols;
133 std::string line;
134 while (std::getline(file, line)) {
135 if (!line.empty() && line.back() == '\r') {
136 line.pop_back();
137 }
138 const size_t start = line.find_first_not_of(" \t");
139 if (start == std::string::npos || line.size() < start + 8 ||
140 line[start + 2] != ':') {
141 continue;
142 }
143 const std::string bank = line.substr(start, 2);
144 const std::string addr = line.substr(start + 3, 4);
145 if (!IsHexString(bank) || !IsHexString(addr)) {
146 continue;
147 }
148 const size_t name_start = line.find_first_not_of(" \t", start + 7);
149 if (name_start == std::string::npos || name_start == start + 7) {
150 continue;
151 }
152 const size_t name_end = line.find_first_of(" \t", name_start);
153 const std::string name = line.substr(name_start, name_end - name_start);
154 const uint32_t snes =
155 (std::stoul(bank, nullptr, 16) << 16) | std::stoul(addr, nullptr, 16);
156 symbols.emplace(name, snes);
157 }
158 return symbols;
159}
160
161absl::StatusOr<DungeonAtlasInput> CollectDungeonAtlasInput(
162 Rom* rom, const DungeonAtlasCollectOptions& options) {
163 if (rom == nullptr || !rom->is_loaded()) {
164 return absl::FailedPreconditionError("ROM is not loaded");
165 }
166 DungeonAtlasInput input;
167 input.extras.resize(kRooms);
168
169 RoomCensusOptions census_options;
170 census_options.project = options.registry;
171 census_options.manifest = options.manifest;
172 census_options.vanilla_rom = options.vanilla_rom;
173 census_options.room_visitor = [&input](int room_id, const Room& room) {
174 if (room_id < 0 || room_id >= kRooms) {
175 return;
176 }
177 auto& extras = input.extras[room_id];
178 for (const auto& door : room.GetDoors()) {
179 const auto [tile_x, tile_y] = door.GetTileCoords();
180 AtlasDoorFact fact;
181 fact.direction = static_cast<int>(door.direction);
182 fact.tile_x = tile_x;
183 fact.tile_y = tile_y;
184 fact.type = static_cast<int>(door.type);
185 fact.type_name = std::string(door.GetTypeName());
186 extras.doors.push_back(std::move(fact));
187 }
188 for (const auto& chest : room.GetChests()) {
189 extras.chests.push_back({static_cast<int>(chest.id), chest.size});
190 }
191 for (const auto& pot : room.GetPotItems()) {
192 extras.pot_items.push_back(pot.item);
193 }
194 for (const auto& sprite : room.GetSprites()) {
195 extras.key_drops_small += sprite.key_drop() == 1 ? 1 : 0;
196 extras.key_drops_big += sprite.key_drop() == 2 ? 1 : 0;
197 }
198 };
199 auto census = CollectRoomCensusInput(rom, census_options);
200 if (!census.ok()) {
201 return census.status();
202 }
203 input.census = std::move(census).value();
204
205 if (options.registry != nullptr) {
206 for (const auto& dungeon : options.registry->dungeons) {
207 for (const auto& room : dungeon.rooms) {
208 if (room.id >= 0 && room.id < kRooms && !room.name.empty()) {
209 input.registry_room_names.emplace(room.id, room.name);
210 }
211 }
212 }
213 }
214
215 input.rom.name = std::filesystem::path(rom->filename()).filename().string();
216 input.rom.size = rom->size();
217 input.rom.sha1 = util::ComputeSha1Hex(rom->data(), rom->size());
218
219 input.symbol_reads.resize(options.symbol_links.size());
220 if (!options.symbol_links.empty() && !options.symbols_path.empty()) {
221 auto symbols = ReadWlaSymbols(options.symbols_path);
222 if (!symbols.ok()) {
223 return symbols.status();
224 }
225 input.symbols_path = options.symbols_path;
226 const auto& data = rom->vector();
227 for (size_t i = 0; i < options.symbol_links.size(); ++i) {
228 const auto& link = options.symbol_links[i];
229 auto it = symbols->find(link.symbol);
230 if (it == symbols->end()) {
231 continue;
232 }
233 const size_t pc = LoRomPc(it->second);
234 const size_t bytes = static_cast<size_t>(link.count) * link.width;
235 if (pc + bytes > data.size()) {
236 continue;
237 }
238 auto& read = input.symbol_reads[i];
239 read.found = true;
240 for (int n = 0; n < link.count; ++n) {
241 const size_t at = pc + static_cast<size_t>(n) * link.width;
242 read.values.push_back(link.width == 2 ? (data[at] | (data[at + 1] << 8))
243 : data[at]);
244 }
245 }
246 }
247 return input;
248}
249
251 const AtlasAnnotations& annotations) {
252 DungeonAtlas atlas;
253 const RoomCensus census = BuildRoomCensus(input.census);
254 atlas.rom = input.rom;
255 atlas.title = annotations.title;
256 atlas.title_origin = annotations.title_origin;
259 atlas.symbols = input.symbols_path;
260 atlas.annotation_files = annotations.files;
261 atlas.legend_notes = annotations.legend_notes_dungeon;
262
263 std::vector<RoomCensusRoomFacts> facts = input.census.rooms;
264 facts.resize(kRooms);
265 std::vector<DungeonAtlasRoomExtras> extras = input.extras;
266 extras.resize(kRooms);
267
268 const auto& palette = AtlasAreaPalette();
269 size_t next_color = 0;
270 auto palette_color = [&]() {
271 return palette[next_color++ % palette.size()];
272 };
273
274 // ---- Owner display ----------------------------------------------------
275 auto annotated_by_owner =
276 [&](const std::string& id) -> const DungeonAnnotation* {
277 for (const auto& dungeon : annotations.dungeons) {
278 if (!dungeon.owner.empty() && dungeon.owner == id) {
279 return &dungeon;
280 }
281 }
282 return nullptr;
283 };
284 auto annotated_by_id = [&](int dungeon_id) -> const DungeonAnnotation* {
285 for (const auto& dungeon : annotations.dungeons) {
286 if (dungeon.owner.empty() && dungeon.dungeon_id == dungeon_id) {
287 return &dungeon;
288 }
289 }
290 return nullptr;
291 };
292
293 AreaTable areas;
294 std::vector<OwnerDisplay> owners(census.owners.size());
295 for (size_t i = 0; i < census.owners.size(); ++i) {
296 const auto& owner = census.owners[i];
297 OwnerDisplay& display = owners[i];
298 if (owner.interior) {
299 continue; // Annotation areas first, then INT (below).
300 }
301 const DungeonAnnotation* entry = nullptr;
302 if (owner.from_project) {
303 entry = annotated_by_owner(owner.id);
304 display.own_area = true;
305 display.origin = Origin::Registry();
306 } else {
307 entry = annotated_by_id(owner.dungeon_id);
308 // With a project registry, derived owners are dungeon IDs the project
309 // does not list: OTHER, unless an annotation (not a default) names
310 // them. The vanilla table only fits ROMs that still look vanilla.
311 if (entry != nullptr && entry->origin.kind == Provenance::kDefault &&
312 census.owners_from_project) {
313 entry = nullptr;
314 }
315 display.own_area = !census.owners_from_project || entry != nullptr;
316 display.origin = Origin::Rom();
317 }
318 if (!display.own_area) {
319 continue;
320 }
321 const bool names_apply =
322 entry != nullptr && (entry->origin.kind != Provenance::kDefault ||
323 census.vanilla_owner_names);
324 display.code = names_apply && !entry->code.empty() ? entry->code : owner.id;
325 display.name =
326 names_apply && !entry->name.empty() ? entry->name : owner.name;
327 if (names_apply && (!entry->code.empty() || !entry->name.empty())) {
328 display.origin = entry->origin;
329 }
330 if (entry != nullptr && entry->color.has_value()) {
331 display.color = *entry->color;
332 display.color_origin = entry->color_origin;
333 } else {
334 display.color = palette_color();
335 display.color_origin = Origin::Default();
336 }
337 AtlasArea& area = areas.Get(display.code);
338 area.code = display.code;
339 area.name = display.name;
340 area.color = display.color;
341 area.kind = "dungeon";
342 area.owner = owner.id;
343 area.origin = display.origin;
344 area.color_origin = display.color_origin;
345 }
346
347 std::map<int, const AreaAnnotation*> area_of;
348 for (const auto& area : annotations.areas) {
349 for (int room : area.rooms) {
350 area_of[room] = &area; // Later areas win, as in room_data.py.
351 }
352 AtlasArea& out = areas.Get(area.key);
353 out.code = area.code.empty() ? area.key : area.code;
354 out.name = area.name.empty() ? area.key : area.name;
355 out.kind = area.key == "INT" ? "interior" : "area";
356 out.origin = area.origin;
357 if (area.color.has_value()) {
358 out.color = *area.color;
359 out.color_origin = area.origin;
360 } else if (area.key == "INT") {
363 } else {
364 out.color = palette_color();
366 }
367 }
368 auto ensure_fallback_area = [&](const std::string& key) {
369 if (areas.Has(key)) {
370 return;
371 }
372 AtlasArea& area = areas.Get(key);
373 area.code = key == "OTHER" ? "ID?" : key == "NONE" ? "" : key;
375 area.origin = Origin::Default();
376 if (key == "INT") {
377 const DungeonAnnotation* entry = annotated_by_id(0xFF);
378 area.kind = "interior";
379 area.name =
380 entry != nullptr && !entry->name.empty() ? entry->name : "Interiors";
381 area.color = entry != nullptr && entry->color.has_value()
382 ? *entry->color
384 if (entry != nullptr) {
385 area.origin = entry->origin;
386 area.color_origin = entry->color_origin;
387 }
388 } else if (key == "OTHER") {
389 area.kind = "other";
390 area.name = "Dungeon ID not in project";
391 area.color = kAtlasOtherColor;
392 } else {
393 area.kind = "none";
394 area.name = "No owner";
396 }
397 };
398
399 // ---- Rooms: area, status, name ----------------------------------------
400 atlas.rooms.resize(kRooms);
401 for (int r = 0; r < kRooms; ++r) {
402 const RoomCensusEntry& entry = census.rooms[r];
403 DungeonAtlasRoom& room = atlas.rooms[r];
404 room.room_id = r;
405 room.reached = entry.reached;
406 room.empty = entry.empty;
407 room.interior = entry.interior;
408 room.objects = entry.object_count;
409 room.sprites = entry.sprite_count;
411 room.reasons = entry.reasons;
412 room.tag1 = facts[r].tag1;
413 room.tag2 = facts[r].tag2;
414 room.blockset = facts[r].blockset;
415 room.pit_damage = facts[r].in_pit_damage_table;
416 if (entry.owner_index >= 0) {
417 const auto& owner = census.owners[entry.owner_index];
418 room.owner = owner.id;
419 room.owner_name = owner.name;
420 }
421
422 const OwnerDisplay* display =
423 entry.owner_index >= 0 ? &owners[entry.owner_index] : nullptr;
424 const bool from_project =
425 entry.owner_index >= 0 && census.owners[entry.owner_index].from_project;
426 if (display != nullptr && display->own_area) {
427 room.area = display->code;
428 room.area_origin = from_project ? Origin::Registry() : Origin::Rom();
429 } else if (auto it = area_of.find(r); it != area_of.end()) {
430 room.area = it->second->key;
431 room.area_origin = it->second->origin;
432 } else if (room.interior) {
433 room.area = "INT";
434 room.area_origin = Origin::Rom();
435 ensure_fallback_area("INT");
436 } else if (entry.owner_index >= 0) {
437 room.area = "OTHER";
438 room.area_origin = Origin::Rom();
439 ensure_fallback_area("OTHER");
440 } else {
441 room.area = "NONE";
442 room.area_origin = Origin::Rom();
443 ensure_fallback_area("NONE");
444 }
445
446 switch (entry.status) {
449 break;
452 break;
455 break;
456 }
457 room.status_origin = Origin::Rom();
458
459 if (auto it = annotations.rooms.find(r); it != annotations.rooms.end()) {
460 room.note = it->second.note;
461 if (!it->second.name.empty()) {
462 room.name = it->second.name;
463 room.name_origin = it->second.origin;
464 }
465 }
466 if (room.name.empty()) {
467 if (auto it = input.registry_room_names.find(r);
468 it != input.registry_room_names.end()) {
469 room.name = it->second;
471 }
472 }
473
474 // Doors on the outer wall.
475 room.doors = OuterWallDoors(extras[r].doors);
476
477 // Badges.
478 const auto& items = annotations.items;
479 for (const auto& chest : extras[r].chests) {
480 const bool known = chest.item == items.small_key ||
481 chest.item == items.compass ||
482 chest.item == items.big_key || chest.item == items.map;
483 if (chest.big) {
484 ++room.badges.big_chest;
485 }
486 if (chest.item == items.big_key) {
487 ++room.badges.bk_chest;
488 } else if (chest.item == items.small_key) {
489 ++room.badges.k_chest;
490 } else if (chest.item == items.compass) {
491 ++room.badges.c;
492 } else if (chest.item == items.map) {
493 ++room.badges.m;
494 } else if (!known && !chest.big) {
495 ++room.badges.other;
496 }
497 }
498 room.badges.k_pot = static_cast<int>(std::count(extras[r].pot_items.begin(),
499 extras[r].pot_items.end(),
500 items.pot_small_key));
501 room.badges.k_drop = extras[r].key_drops_small;
502 room.badges.bk_drop = extras[r].key_drops_big;
503 }
504
505 // ---- Links out ---------------------------------------------------------
506 const RoomLinkFactsLookup lookup = [&](int id) -> const RoomLinkFacts* {
507 return IsDungeonRoomId(id) ? &facts[id] : nullptr;
508 };
509 for (int r = 0; r < kRooms; ++r) {
510 const RoomCensusRoomFacts& f = facts[r];
511 DungeonAtlasRoom& room = atlas.rooms[r];
512 std::vector<int> stair_targets;
513 bool has_warp_tag_links = false;
514 for (const auto& source :
515 CollectRoomLinks(r, lookup, input.census.warp_tag_ids)) {
516 AtlasLink link;
517 link.from = r;
518 link.to = source.to;
519 if (source.kind == RoomReferenceKind::kStair ||
520 source.kind == RoomReferenceKind::kWarpTag) {
521 const bool stair = source.kind == RoomReferenceKind::kStair;
522 if (source.to < 0) {
523 atlas.warnings.push_back(absl::StrFormat(
524 "room %s %s slot %d points past $127 (%s); dropped", RoomHex(r),
525 stair ? "stair" : "warp tag", source.slot + 1,
526 RoomHex(source.raw_to)));
527 continue;
528 }
529 if (stair) {
530 if (source.to == r ||
531 std::find(stair_targets.begin(), stair_targets.end(),
532 source.to) != stair_targets.end()) {
533 continue;
534 }
535 stair_targets.push_back(source.to);
536 link.kind = LinkKind::kStair;
537 if (f.stair_slot_estimated[source.slot]) {
538 link.note = "slot by placement order";
539 }
540 } else {
542 has_warp_tag_links = true;
543 }
544 link.index = source.slot + 1;
545 } else if (source.kind == RoomReferenceKind::kHolewarp) {
546 if (source.to < 0) {
547 atlas.warnings.push_back(
548 absl::StrFormat("room %s holewarp points past $127 (%s); dropped",
549 RoomHex(r), RoomHex(source.raw_to)));
550 continue;
551 }
552 if (source.to == r) {
553 continue;
554 }
555 if (!source.strong) {
557 } else {
558 switch (source.holewarp_cause) {
560 // A warp tag replaces the raw holewarp destination with its
561 // quadrant slot. Keep the existing Atlas presentation of those
562 // slots instead of also showing the overwritten raw target.
563 if (has_warp_tag_links) {
564 continue;
565 }
567 break;
569 link.kind = LinkKind::kTagPit;
570 break;
572 link.note = f.hole_sprite;
573 [[fallthrough]];
575 link.kind = LinkKind::kPit;
576 break;
578 continue;
579 }
580 }
581 } else {
582 // Atlas retains its ordinary-door presentation and ignores unused
583 // header bytes; only shared active header/hole links become pills.
584 continue;
585 }
586 room.links_out.push_back(std::move(link));
587 }
588 }
589
590 // Annotation links (runtime-only).
591 for (const auto& note : annotations.links) {
592 if (note.view != AtlasView::kDungeon || note.from < 0 ||
593 note.from >= kRooms) {
594 continue;
595 }
596 DungeonAtlasRoom& room = atlas.rooms[note.from];
597 for (const auto& existing : room.links_out) {
598 if (existing.to == note.to && note.to >= 0 &&
599 ((note.kind == "pit" && existing.kind == LinkKind::kPit) ||
600 (note.kind == "warp" && existing.kind == LinkKind::kWarpTiles) ||
601 (note.kind == "stair" && existing.kind == LinkKind::kStair))) {
602 atlas.warnings.push_back(absl::StrFormat(
603 "%s: %s link %s -> %s repeats a ROM fact", note.origin.ref,
604 note.kind, RoomHex(note.from), RoomHex(note.to)));
605 break;
606 }
607 }
608 AtlasLink link;
610 link.from = note.from;
611 link.to = note.to;
612 link.style = note.kind;
613 link.label = note.label;
614 link.note = note.note;
615 link.origin = note.origin;
616 room.links_out.push_back(std::move(link));
617 }
618 // ---- Links in ----------------------------------------------------------
619 for (const auto& fact : input.census.entrances) {
620 if (fact.room_id < 0 || fact.room_id >= kRooms) {
621 continue;
622 }
623 LinkKind kind;
624 switch (fact.kind) {
626 kind = LinkKind::kEntrance;
627 break;
629 kind = LinkKind::kHole;
630 break;
632 kind = LinkKind::kSpawn;
633 break;
634 default:
635 continue;
636 }
637 auto& links = atlas.rooms[fact.room_id].links_in;
638 const bool duplicate =
639 std::any_of(links.begin(), links.end(), [&](const AtlasLink& seen) {
640 return seen.kind == kind &&
641 (kind == LinkKind::kSpawn
642 ? seen.index == fact.entrance_id
643 : seen.entrance == fact.entrance_id &&
644 seen.ow_map == fact.ow_map);
645 });
646 if (duplicate) {
647 continue;
648 }
649 AtlasLink link;
650 link.kind = kind;
651 link.to = fact.room_id;
652 if (kind == LinkKind::kSpawn) {
653 link.index = fact.entrance_id;
654 } else {
655 link.entrance = fact.entrance_id;
656 link.ow_map = fact.ow_map;
657 }
658 link.origin = Origin::Rom();
659 links.push_back(std::move(link));
660 }
661 for (size_t i = 0; i < annotations.symbol_links.size(); ++i) {
662 const auto& symbol_link = annotations.symbol_links[i];
663 const AtlasSymbolRead* read =
664 i < input.symbol_reads.size() ? &input.symbol_reads[i] : nullptr;
665 if (read == nullptr || !read->found) {
666 atlas.warnings.push_back(
667 input.symbols_path.empty()
668 ? absl::StrFormat("%s: symbol link %s skipped: no .sym file",
669 symbol_link.origin.ref, symbol_link.symbol)
670 : absl::StrFormat("%s: symbol %s not found in %s",
671 symbol_link.origin.ref, symbol_link.symbol,
672 std::filesystem::path(input.symbols_path)
673 .filename()
674 .string()));
675 continue;
676 }
677 for (size_t n = 0; n < read->values.size(); ++n) {
678 const int target = read->values[n];
679 if (target < 0 || target >= kRooms) {
680 atlas.warnings.push_back(absl::StrFormat(
681 "symbol %s entry %d is room %s, past $127; dropped",
682 symbol_link.symbol, static_cast<int>(n + 1), RoomHex(target)));
683 continue;
684 }
685 AtlasLink link;
686 link.kind = LinkKind::kSymbol;
687 link.to = target;
688 link.index = static_cast<int>(n + 1);
689 link.style = symbol_link.kind;
690 link.label = ExpandLabel(symbol_link.label, symbol_link.kind,
691 static_cast<int>(n + 1));
692 link.origin = Origin::Symbol(symbol_link.symbol);
693 atlas.rooms[target].links_in.push_back(std::move(link));
694 }
695 }
696
697 // ---- Cross-area rings, reserved rooms, plans ---------------------------
698 for (auto& room : atlas.rooms) {
699 for (auto& link : room.links_out) {
700 if (link.to >= 0 && link.to < kRooms) {
701 link.cross_area = atlas.rooms[link.to].area != room.area;
702 }
703 }
704 }
705 auto make_reserved = [&](DungeonAtlasRoom& room, const Origin& origin) {
706 if (room.status == RoomStatus::kFree ||
709 room.status_origin = origin;
710 }
711 };
712 for (const auto& [room_id, note] : annotations.rooms) {
713 if (note.reserved && room_id >= 0 && room_id < kRooms) {
714 make_reserved(atlas.rooms[room_id], note.origin);
715 }
716 }
717 for (auto& room : atlas.rooms) {
718 for (const auto& link : room.links_in) {
719 if (link.kind == LinkKind::kSymbol) {
720 make_reserved(room, link.origin);
721 }
722 }
723 for (const auto& link : room.links_out) {
724 if (link.kind == LinkKind::kAnnotation && link.to >= 0 &&
725 link.to < kRooms) {
726 make_reserved(atlas.rooms[link.to], link.origin);
727 }
728 }
729 }
730 for (const auto& plan : annotations.plans) {
731 AtlasPlan out;
732 out.key = plan.key;
733 out.label = plan.label.empty() ? plan.key : plan.label;
734 out.name = plan.name;
735 out.status = plan.status;
736 out.has_color = plan.color.has_value();
737 if (plan.color.has_value()) {
738 out.color = *plan.color;
739 }
740 out.rooms = plan.rooms;
741 out.note = plan.note;
742 out.origin = plan.origin;
743 for (int room : plan.rooms) {
744 if (room >= 0 && room < kRooms) {
745 atlas.rooms[room].plans.push_back(plan.key);
746 }
747 }
748 atlas.plans.push_back(std::move(out));
749 }
750
751 // ---- Edges, areas, key table, counts -----------------------------------
752 std::vector<std::vector<AtlasDoor>> doors_by_room(kRooms);
753 for (int r = 0; r < kRooms; ++r) {
754 doors_by_room[r] = atlas.rooms[r].doors;
755 }
756 atlas.edges = PairDoorEdges(doors_by_room);
757
758 std::map<std::string, AtlasKeyTableRow> key_rows;
759 for (const auto& room : atlas.rooms) {
760 ++areas.Get(room.area).rooms;
761 auto& row = key_rows[room.area];
762 row.area = room.area;
763 row.k_chest += room.badges.k_chest;
764 row.k_pot += room.badges.k_pot;
765 row.k_drop += room.badges.k_drop;
766 row.bk += room.badges.bk_chest + room.badges.bk_drop;
767 row.bc += room.badges.big_chest;
768 row.c += room.badges.c;
769 row.m += room.badges.m;
770 switch (room.status) {
772 ++atlas.counts.free;
773 break;
775 ++atlas.counts.reclaimable;
776 break;
778 ++atlas.counts.in_use;
779 break;
781 ++atlas.counts.orphan;
782 break;
784 ++atlas.counts.reserved;
785 break;
786 }
787 atlas.counts.interior += room.interior ? 1 : 0;
788 }
789 atlas.areas = areas.Take();
790 for (const auto& area : atlas.areas) {
791 const auto it = key_rows.find(area.key);
792 if (it == key_rows.end()) {
793 continue;
794 }
795 const auto& row = it->second;
796 if (row.k_chest + row.k_pot + row.k_drop + row.bk + row.bc + row.c + row.m >
797 0) {
798 atlas.key_table.push_back(row);
799 }
800 }
801 return atlas;
802}
803
804} // namespace yaze::zelda3::atlas
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
auto filename() const
Definition rom.h:207
const auto & vector() const
Definition rom.h:205
auto data() const
Definition rom.h:201
auto size() const
Definition rom.h:200
bool is_loaded() const
Definition rom.h:187
std::string ComputeSha1Hex(const uint8_t *data, size_t size)
Compute SHA-1 hash of data, return lowercase hex string (40 chars).
Definition rom_hash.cc:234
std::string ExpandLabel(const std::string &templ, const std::string &kind, int n)
absl::StatusOr< DungeonAtlasInput > CollectDungeonAtlasInput(Rom *rom, const DungeonAtlasCollectOptions &options)
std::vector< AtlasDoor > OuterWallDoors(const std::vector< AtlasDoorFact > &doors)
absl::StatusOr< std::map< std::string, uint32_t > > ReadWlaSymbols(const std::string &path)
constexpr AtlasColor kAtlasNoOwnerColor
const char * RoomStatusName(RoomStatus status)
DungeonAtlas BuildDungeonAtlas(const DungeonAtlasInput &input, const AtlasAnnotations &annotations)
const std::vector< AtlasColor > & AtlasAreaPalette()
constexpr int kAtlasRoomCount
constexpr AtlasColor kAtlasInteriorColor
constexpr AtlasColor kAtlasOtherColor
std::vector< DoorEdge > PairDoorEdges(const std::vector< std::vector< AtlasDoor > > &doors_by_room)
const char * LinkKindName(LinkKind kind)
absl::StatusOr< RoomCensusInput > CollectRoomCensusInput(Rom *rom, const RoomCensusOptions &options)
bool IsDungeonRoomId(int room_id)
std::vector< RoomLink > CollectRoomLinks(int room_id, const RoomLinkFactsLookup &lookup, const std::set< uint8_t > &warp_tag_ids)
RoomCensus BuildRoomCensus(const RoomCensusInput &input)
std::function< const RoomLinkFacts *(int room_id)> RoomLinkFactsLookup
Definition room_links.h:147
Treasure chest.
Definition zelda.h:425
uint8_t item
Definition zelda.h:428
std::vector< DungeonEntry > dungeons
std::vector< std::string > reasons
std::vector< RoomCensusEntranceFact > entrances
std::set< uint8_t > warp_tag_ids
std::vector< RoomCensusRoomFacts > rooms
std::function< void(int room_id, const Room &room) room_visitor)
const core::ProjectRegistry * project
const core::HackManifest * manifest
std::vector< RoomCensusOwner > owners
std::vector< RoomCensusEntry > rooms
std::array< bool, 4 > stair_slot_estimated
Definition room_links.h:109
std::vector< std::string > legend_notes_dungeon
std::vector< LinkAnnotation > links
std::vector< SymbolLinkAnnotation > symbol_links
std::vector< PlanAnnotation > plans
std::vector< DungeonAnnotation > dungeons
std::vector< AreaAnnotation > areas
std::map< int, RoomAnnotation > rooms
std::vector< SymbolLinkAnnotation > symbol_links
std::map< int, std::string > registry_room_names
std::vector< AtlasSymbolRead > symbol_reads
std::vector< DungeonAtlasRoomExtras > extras
std::vector< std::string > reasons
std::vector< AtlasLink > links_out
std::vector< AtlasLink > links_in
std::vector< AtlasPlan > plans
std::vector< DoorEdge > edges
std::vector< AtlasKeyTableRow > key_table
std::vector< AtlasArea > areas
std::vector< std::string > annotation_files
std::vector< std::string > warnings
std::vector< DungeonAtlasRoom > rooms
std::vector< std::string > legend_notes
static Origin Symbol(std::string label)