yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_validator.cc
Go to the documentation of this file.
1#include "dungeon_validator.h"
2
3#include "absl/strings/str_format.h"
5
6namespace yaze {
7namespace zelda3 {
8namespace {
9
10constexpr int16_t kBigKeyLockObjectId = 0xF98;
11constexpr int16_t kSmallChestObjectId = 0xF99;
12constexpr int16_t kBigChestObjectId = 0xFB1;
13constexpr size_t kMaxStatefulRoomEventSlots = 6;
14
15bool IsStatefulChest(int16_t object_id) {
16 return object_id == kSmallChestObjectId || object_id == kBigChestObjectId;
17}
18
19} // namespace
20
22 ValidationResult result;
23
24 // Check object count
25 size_t object_count = room.GetTileObjects().size();
26 if (object_count > kMaxTileObjects) {
27 result.warnings.push_back(absl::StrFormat(
28 "High object count (%zu > %d). May cause lag or memory issues.",
29 object_count, kMaxTileObjects));
30 }
31
32 // Check sprite count
33 size_t sprite_count = room.GetSprites().size();
34 if (sprite_count > kMaxTotalSprites) {
35 result.warnings.push_back(
36 absl::StrFormat("Too many sprites (%zu > %d). Game limit is strict.",
37 sprite_count, kMaxTotalSprites));
38 }
39
40 // Check chest count (approximate, based on object ID)
41 int chest_count = 0;
42 for (const auto& obj : room.GetTileObjects()) {
43 // Check for Big Chest (0xE4?? No, let's trust standard ranges)
44 // ZScream logic for chests:
45 // 0xF9 = Small Key Chest
46 // 0xFA = Big Key Chest
47 // 0xFB = Map Chest
48 // 0xFC = Compass Chest
49 // 0xFD = Big Chest
50 // But simple chest objects are also common.
51 // Let's count objects in the 0xF9-0xFD range as chests for now.
52 if (obj.id_ >= 0xF9 && obj.id_ <= 0xFD) {
53 chest_count++;
54 }
55 }
56
57 if (chest_count > kMaxChests) {
58 result.errors.push_back(absl::StrFormat(
59 "Too many chests (%d > %d). Item collection flags will conflict.",
60 chest_count, kMaxChests));
61 result.is_valid = false;
62 }
63
64 // Stateful chests and big-key locks share a six-entry room-event table in
65 // the game engine. Chests use a chest-only index and then synchronize the
66 // shared index, so a chest after a lock reuses an earlier event slot.
67 // Validate the encoded stream order (primary, BG2 overlay, BG1 overlay), not
68 // the editor vector order, which may interleave objects from those lists.
69 size_t stateful_room_event_count = 0;
70 bool saw_big_key_lock = false;
71 int first_chest_after_lock = -1;
72 for (uint8_t list_index = 0; list_index < 3; ++list_index) {
73 for (const auto& obj : room.GetTileObjects()) {
74 if (!UsesRoomObjectStream(obj) || obj.GetLayerValue() != list_index) {
75 continue;
76 }
77
78 if (obj.id_ == kBigKeyLockObjectId) {
79 saw_big_key_lock = true;
80 ++stateful_room_event_count;
81 } else if (IsStatefulChest(obj.id_)) {
82 ++stateful_room_event_count;
83 if (saw_big_key_lock && first_chest_after_lock < 0) {
84 first_chest_after_lock = obj.id_;
85 }
86 }
87 }
88 }
89
90 if (first_chest_after_lock >= 0) {
91 result.warnings.push_back(absl::StrFormat(
92 "Stateful chest 0x%03X appears after big-key lock 0xF98 in "
93 "room-object stream order; move stateful chests before locks to avoid "
94 "room-state slot conflicts.",
95 first_chest_after_lock));
96 }
97 if (stateful_room_event_count > kMaxStatefulRoomEventSlots) {
98 result.warnings.push_back(absl::StrFormat(
99 "Room uses %zu stateful chest/lock slots; the engine supports at most "
100 "%zu.",
101 stateful_room_event_count, kMaxStatefulRoomEventSlots));
102 }
103
104 // Check bounds
105 for (const auto& obj : room.GetTileObjects()) {
106 const int layer = static_cast<int>(obj.GetLayerValue());
107 if (layer < 0 || layer > 2) {
108 result.errors.push_back(absl::StrFormat(
109 "Object 0x%02X has invalid layer %d", obj.id_, layer));
110 result.is_valid = false;
111 }
112 if (UsesSpecialLayerSelector(obj) && layer > 1) {
113 result.errors.push_back(absl::StrFormat(
114 "Special-table object 0x%02X has invalid layer selector %d; "
115 "expected upper/BG1 (0) or lower/BG2 (1)",
116 obj.id_, layer));
117 result.is_valid = false;
118 }
119
120 if (obj.x_ < 0 || obj.x_ >= 64 || obj.y_ < 0 || obj.y_ >= 64) {
121 result.errors.push_back(absl::StrFormat(
122 "Object 0x%02X out of bounds at (%d, %d)", obj.id_, obj.x_, obj.y_));
123 result.is_valid = false;
124 }
125 }
126
127 return result;
128}
129
130} // namespace zelda3
131} // namespace yaze
ValidationResult ValidateRoom(const Room &room)
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:253
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:382
constexpr size_t kMaxTileObjects
bool UsesRoomObjectStream(const RoomObject &object)
bool UsesSpecialLayerSelector(const RoomObject &object)
constexpr size_t kMaxChests
constexpr size_t kMaxTotalSprites
std::vector< std::string > errors
std::vector< std::string > warnings