yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
game_tilemap_comparison.cc
Go to the documentation of this file.
2
3#include <vector>
4
5#include "absl/status/status.h"
6#include "absl/strings/str_cat.h"
7#include "absl/strings/str_join.h"
10#include "rom/rom.h"
12#include "zelda3/dungeon/room.h"
13
14namespace yaze::zelda3 {
15namespace {
16
17constexpr uint16_t kTileNumberBits = 0x03FF;
18constexpr uint16_t kPaletteBits = 0x1C00;
19constexpr uint16_t kPriorityBit = 0x2000;
20constexpr uint16_t kHorizontalFlipBit = 0x4000;
21constexpr uint16_t kVerticalFlipBit = 0x8000;
22
23// Rebuilds the tile word ObjectDrawer::PushTrace recorded.
24uint16_t TraceWord(const ObjectDrawer::TileTrace& trace) {
25 uint16_t word = trace.tile_id & kTileNumberBits;
26 word |= static_cast<uint16_t>((trace.flags >> 3) & 0x7) << 10;
27 if (trace.flags & 0x4)
28 word |= kPriorityBit;
29 if (trace.flags & 0x1)
30 word |= kHorizontalFlipBit;
31 if (trace.flags & 0x2)
32 word |= kVerticalFlipBit;
33 return word;
34}
35
36bool IsSpecialTableObject(const RoomObject& object) {
37 return (object.options() & ObjectOption::Torch) != ObjectOption::Nothing ||
38 (object.options() & ObjectOption::Block) != ObjectOption::Nothing;
39}
40
41} // namespace
42
43absl::StatusOr<RoomTilemaps> ParseGameRoomTilemaps(
44 const std::vector<uint8_t>& bytes) {
45 if (bytes.size() != kGameRoomTilemapBytes) {
46 return absl::InvalidArgumentError(
47 absl::StrCat("Room tilemap capture must be ", kGameRoomTilemapBytes,
48 " bytes, got ", bytes.size()));
49 }
50 RoomTilemaps maps;
51 maps.bg1.resize(kRoomTilemapWords);
52 maps.bg2.resize(kRoomTilemapWords);
53 for (size_t i = 0; i < kRoomTilemapWords; ++i) {
54 maps.bg1[i] = static_cast<uint16_t>(bytes[i * 2] | (bytes[i * 2 + 1] << 8));
55 const size_t j = kRoomTilemapWords * 2 + i * 2;
56 maps.bg2[i] = static_cast<uint16_t>(bytes[j] | (bytes[j + 1] << 8));
57 }
58 return maps;
59}
60
62 auto compose = [](const gfx::BackgroundBuffer& layout,
63 const gfx::BackgroundBuffer& objects) {
64 std::vector<uint16_t> out(kRoomTilemapWords, 0);
65 const auto& layout_words = layout.buffer();
66 const auto& object_words = objects.buffer();
67 for (size_t i = 0; i < kRoomTilemapWords; ++i) {
68 const uint16_t object_word =
69 i < object_words.size() ? object_words[i] : 0;
70 const uint16_t layout_word =
71 i < layout_words.size() ? layout_words[i] : 0;
72 out[i] = object_word != 0 ? object_word : layout_word;
73 }
74 return out;
75 };
76 RoomTilemaps maps;
77 maps.bg1 = compose(room.bg1_buffer(), room.object_bg1_buffer());
78 maps.bg2 = compose(room.bg2_buffer(), room.object_bg2_buffer());
79 return maps;
80}
81
82bool GameHidesObjectOnRoomLoad(int tag1, int tag2, const RoomObject& object) {
83 if (object.id_ != 0xF99) {
84 return false;
85 }
86 auto hides = [](int tag) {
87 return tag == 0x27 || tag == 0x3C || tag == 0x3E ||
88 (tag >= 0x29 && tag <= 0x32);
89 };
90 return hides(tag1) || hides(tag2);
91}
92
94 const std::vector<RoomObject>& objects,
95 const RoomTilemaps& yaze,
96 const std::vector<bool>& hidden) {
97 TileOwners owners;
98 owners.bg1.assign(kRoomTilemapWords, -1);
99 owners.bg2.assign(kRoomTilemapWords, -1);
100 if (yaze.bg1.size() != kRoomTilemapWords ||
101 yaze.bg2.size() != kRoomTilemapWords) {
102 return owners;
103 }
104
105 std::vector<ObjectDrawer::TileTrace> trace;
106 ObjectDrawer drawer(rom, room_id, nullptr);
107 drawer.SetTraceCollector(&trace, /*trace_only=*/true);
108 gfx::BackgroundBuffer bg1(512, 512);
109 gfx::BackgroundBuffer bg2(512, 512);
110 gfx::PaletteGroup palette_group;
111 std::vector<uint16_t> owner_words_bg1(kRoomTilemapWords, 0);
112 std::vector<uint16_t> owner_words_bg2(kRoomTilemapWords, 0);
113
114 // Same order as Room's object pass: the three room object lists in list
115 // order. Torches and pushable blocks are drawn from their own tables and
116 // are left without owners.
117 for (int list = 0; list < 3; ++list) {
118 for (size_t index = 0; index < objects.size(); ++index) {
119 const RoomObject& source = objects[index];
120 if (IsSpecialTableObject(source) ||
121 (index < hidden.size() && hidden[index])) {
122 continue;
123 }
124 int list_index = source.GetLayerValue();
125 if (list_index > 2) {
126 list_index = 2;
127 }
128 if (list_index != list) {
129 continue;
130 }
131 RoomObject object = source;
132 object.SetRom(rom);
133 object.layer_ =
134 MapRoomObjectListIndexToDrawLayer(static_cast<uint8_t>(list_index));
135 trace.clear();
136 if (!drawer.DrawObject(object, bg1, bg2, palette_group).ok()) {
137 continue;
138 }
139 for (const auto& tile : trace) {
140 if (tile.x_tile < 0 || tile.y_tile < 0 ||
141 tile.x_tile >= kRoomTilemapSize ||
142 tile.y_tile >= kRoomTilemapSize) {
143 continue;
144 }
145 const size_t position =
146 static_cast<size_t>(tile.y_tile) * kRoomTilemapSize + tile.x_tile;
147 const bool bg2_tile = tile.layer == RoomObject::BG2;
148 (bg2_tile ? owners.bg2 : owners.bg1)[position] =
149 static_cast<int>(index);
150 (bg2_tile ? owner_words_bg2 : owner_words_bg1)[position] =
151 TraceWord(tile);
152 }
153 }
154 }
155
156 // Keep an owner only where its own word is what yaze shows; otherwise a
157 // later draw (a door, another object) produced the final tile.
158 for (size_t position = 0; position < kRoomTilemapWords; ++position) {
159 if (owners.bg1[position] >= 0 &&
160 owner_words_bg1[position] != yaze.bg1[position]) {
161 owners.bg1[position] = -1;
162 }
163 if (owners.bg2[position] >= 0 &&
164 owner_words_bg2[position] != yaze.bg2[position]) {
165 owners.bg2[position] = -1;
166 }
167 }
168 return owners;
169}
170
172 const RoomTilemaps& yaze,
173 const TileOwners& owners,
174 const std::vector<RoomObject>& objects) {
175 RoomTilemapCheck check;
176 check.room_id = room_id;
177 check.placements.resize(objects.size());
178 for (size_t i = 0; i < objects.size(); ++i) {
179 check.placements[i].object_index = i;
180 check.placements[i].object_id = objects[i].id_;
181 }
182
183 auto compare_layer = [&](int layer, const std::vector<uint16_t>& game_words,
184 const std::vector<uint16_t>& yaze_words,
185 const std::vector<int>& layer_owners,
186 int* matching) {
187 for (size_t position = 0; position < kRoomTilemapWords; ++position) {
188 const int owner =
189 position < layer_owners.size() ? layer_owners[position] : -1;
190 const bool same = game_words[position] == yaze_words[position];
191 if (owner >= 0 && static_cast<size_t>(owner) < objects.size()) {
192 auto& placement = check.placements[static_cast<size_t>(owner)];
193 ++placement.tiles_owned;
194 if (!same) {
195 ++placement.tiles_different;
196 placement.difference_bits |= static_cast<uint16_t>(
197 game_words[position] ^ yaze_words[position]);
198 }
199 }
200 if (same) {
201 ++*matching;
202 continue;
203 }
204 TileDifference difference;
205 difference.layer = layer;
206 difference.x = static_cast<int>(position % kRoomTilemapSize);
207 difference.y = static_cast<int>(position / kRoomTilemapSize);
208 difference.game = game_words[position];
209 difference.yaze = yaze_words[position];
210 difference.owner = owner;
211 if (owner < 0) {
212 ++check.unowned_differences;
213 }
214 check.differences.push_back(difference);
215 }
216 };
217 if (game.bg1.size() == kRoomTilemapWords &&
218 yaze.bg1.size() == kRoomTilemapWords) {
219 compare_layer(1, game.bg1, yaze.bg1, owners.bg1, &check.bg1_matching);
220 }
221 if (game.bg2.size() == kRoomTilemapWords &&
222 yaze.bg2.size() == kRoomTilemapWords) {
223 compare_layer(2, game.bg2, yaze.bg2, owners.bg2, &check.bg2_matching);
224 }
225 return check;
226}
227
228std::string DescribeTileWordDifference(uint16_t xor_bits) {
229 std::vector<std::string> parts;
230 if (xor_bits & kTileNumberBits)
231 parts.push_back("tile");
232 if (xor_bits & kPaletteBits)
233 parts.push_back("palette");
234 if (xor_bits & kPriorityBit)
235 parts.push_back("priority");
236 if (xor_bits & (kHorizontalFlipBit | kVerticalFlipBit)) {
237 parts.push_back("flip");
238 }
239 return parts.empty() ? "none" : absl::StrJoin(parts, ", ");
240}
241
242} // namespace yaze::zelda3
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
std::vector< uint16_t > & buffer()
Draws dungeon objects to background buffers using game patterns.
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr, gfx::BackgroundBuffer *layout_bg2=nullptr)
Draw a room object to background buffers.
void SetTraceCollector(std::vector< TileTrace > *collector, bool trace_only=false)
void SetRom(Rom *rom)
Definition room_object.h:78
uint8_t GetLayerValue() const
auto & object_bg2_buffer()
Definition room.h:1151
auto & bg1_buffer()
Definition room.h:1145
auto & object_bg1_buffer()
Definition room.h:1149
auto & bg2_buffer()
Definition room.h:1146
Zelda 3 specific classes and functions.
RoomTilemaps ComposeYazeRoomTilemaps(const Room &room)
constexpr int kRoomTilemapSize
constexpr size_t kGameRoomTilemapBytes
RoomObject::LayerType MapRoomObjectListIndexToDrawLayer(uint8_t list_index)
std::string DescribeTileWordDifference(uint16_t xor_bits)
absl::StatusOr< RoomTilemaps > ParseGameRoomTilemaps(const std::vector< uint8_t > &bytes)
constexpr size_t kRoomTilemapWords
TileOwners ComputeObjectTileOwners(Rom *rom, int room_id, const std::vector< RoomObject > &objects, const RoomTilemaps &yaze, const std::vector< bool > &hidden)
RoomTilemapCheck CompareRoomTilemaps(int room_id, const RoomTilemaps &game, const RoomTilemaps &yaze, const TileOwners &owners, const std::vector< RoomObject > &objects)
bool GameHidesObjectOnRoomLoad(int tag1, int tag2, const RoomObject &object)
Represents a group of palettes.
std::vector< ObjectPlacementCheck > placements
std::vector< TileDifference > differences