yaze 0.2.0
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
screen_editor.cc
Go to the documentation of this file.
1#include "screen_editor.h"
2
3#include <fstream>
4#include <iostream>
5#include <string>
6
7#include "absl/strings/str_format.h"
8#include "absl/strings/string_view.h"
12#include "app/gfx/bitmap.h"
13#include "app/gfx/snes_tile.h"
14#include "app/gfx/tilesheet.h"
15#include "app/gui/canvas.h"
16#include "app/gui/icons.h"
17#include "app/gui/input.h"
18#include "imgui/imgui.h"
19
20namespace yaze {
21namespace app {
22namespace editor {
23
24using core::Renderer;
25
26constexpr uint32_t kRedPen = 0xFF0000FF;
27
28absl::Status ScreenEditor::Update() {
29 if (ImGui::BeginTabBar("##ScreenEditorTabBar")) {
30 if (ImGui::BeginTabItem("Dungeon Maps")) {
31 if (rom()->is_loaded()) {
33 }
34 ImGui::EndTabItem();
35 }
40 ImGui::EndTabBar();
41 }
42 return status_;
43}
44
46 TAB_ITEM("Inventory Menu")
47
48 static bool create = false;
49 if (!create && rom()->is_loaded()) {
52 create = true;
53 }
54
56
57 if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
58 ImGui::TableSetupColumn("Canvas");
59 ImGui::TableSetupColumn("Tiles");
60 ImGui::TableSetupColumn("Palette");
61 ImGui::TableHeadersRow();
62
63 ImGui::TableNextColumn();
69
70 ImGui::TableNextColumn();
71 tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
76
77 ImGui::TableNextColumn();
79
80 ImGui::EndTable();
81 }
82 ImGui::Separator();
84}
85
87 if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
88 ImVec2(0, 0))) {
89 ImGui::TableSetupColumn("#drawTool");
90 ImGui::TableSetupColumn("#sep1");
91 ImGui::TableSetupColumn("#zoomOut");
92 ImGui::TableSetupColumn("#zoomIN");
93 ImGui::TableSetupColumn("#sep2");
94 ImGui::TableSetupColumn("#bg2Tool");
95 ImGui::TableSetupColumn("#bg3Tool");
96 ImGui::TableSetupColumn("#itemTool");
97
106
107 ImGui::EndTable();
108 }
109}
110
112 std::vector<std::array<uint8_t, 25>> current_floor_rooms_d;
113 std::vector<std::array<uint8_t, 25>> current_floor_gfx_d;
114 int total_floors_d;
115 uint8_t nbr_floor_d;
116 uint8_t nbr_basement_d;
117
118 for (int d = 0; d < 14; d++) {
119 current_floor_rooms_d.clear();
120 current_floor_gfx_d.clear();
122 int ptr,
123 rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2)));
125 int ptr_gfx,
126 rom()->ReadWord(zelda3::screen::kDungeonMapGfxPtr + (d * 2)));
127 ptr |= 0x0A0000; // Add bank to the short ptr
128 ptr_gfx |= 0x0A0000; // Add bank to the short ptr
129 int pc_ptr = core::SnesToPc(ptr); // Contains data for the next 25 rooms
130 int pc_ptr_gfx =
131 core::SnesToPc(ptr_gfx); // Contains data for the next 25 rooms
132
134 ushort boss_room_d,
135 rom()->ReadWord(zelda3::screen::kDungeonMapBossRooms + (d * 2)));
136
138 nbr_basement_d,
139 rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
140 nbr_basement_d &= 0x0F;
141
143 nbr_floor_d,
144 rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
145 nbr_floor_d &= 0xF0;
146 nbr_floor_d = nbr_floor_d >> 4;
147
148 total_floors_d = nbr_basement_d + nbr_floor_d;
149
150 dungeon_map_labels_.emplace_back();
151
152 // for each floor in the dungeon
153 for (int i = 0; i < total_floors_d; i++) {
154 dungeon_map_labels_[d].emplace_back();
155
156 std::array<uint8_t, 25> rdata;
157 std::array<uint8_t, 25> gdata;
158
159 // for each room on the floor
160 for (int j = 0; j < 25; j++) {
161 gdata[j] = 0xFF;
162 rdata[j] = rom()->data()[pc_ptr + j + (i * 25)]; // Set the rooms
163
164 if (rdata[j] == 0x0F) {
165 gdata[j] = 0xFF;
166 } else {
167 gdata[j] = rom()->data()[pc_ptr_gfx++];
168 }
169
170 std::string label = core::UppercaseHexByte(rdata[j]);
171 dungeon_map_labels_[d][i][j] = label;
172 }
173
174 current_floor_gfx_d.push_back(gdata); // Add new floor gfx data
175 current_floor_rooms_d.push_back(rdata); // Add new floor data
176 }
177
178 dungeon_maps_.emplace_back(boss_room_d, nbr_floor_d, nbr_basement_d,
179 current_floor_rooms_d, current_floor_gfx_d);
180 }
181
182 return absl::OkStatus();
183}
184
186 for (int d = 0; d < 14; d++) {
187 int ptr = zelda3::screen::kDungeonMapRoomsPtr + (d * 2);
188 int ptr_gfx = zelda3::screen::kDungeonMapGfxPtr + (d * 2);
189 int pc_ptr = core::SnesToPc(ptr);
190 int pc_ptr_gfx = core::SnesToPc(ptr_gfx);
191
192 const int nbr_floors = dungeon_maps_[d].nbr_of_floor;
193 const int nbr_basements = dungeon_maps_[d].nbr_of_basement;
194 for (int i = 0; i < nbr_floors + nbr_basements; i++) {
195 for (int j = 0; j < 25; j++) {
196 RETURN_IF_ERROR(rom()->WriteByte(pc_ptr + j + (i * 25),
197 dungeon_maps_[d].floor_rooms[i][j]));
198 RETURN_IF_ERROR(rom()->WriteByte(pc_ptr_gfx + j + (i * 25),
199 dungeon_maps_[d].floor_gfx[i][j]));
200 pc_ptr_gfx++;
201 }
202 }
203 }
204
205 return absl::OkStatus();
206}
207
209 const std::vector<uint8_t>& gfx_data, bool bin_mode) {
211
212 for (int i = 0; i < 186; i++) {
214 if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
216 }
217
218 ASSIGN_OR_RETURN(auto tl, rom()->ReadWord(addr + (i * 8)));
219 gfx::TileInfo t1 = gfx::WordToTileInfo(tl); // Top left
220
221 ASSIGN_OR_RETURN(auto tr, rom()->ReadWord(addr + 2 + (i * 8)));
222 gfx::TileInfo t2 = gfx::WordToTileInfo(tr); // Top right
223
224 ASSIGN_OR_RETURN(auto bl, rom()->ReadWord(addr + 4 + (i * 8)));
225 gfx::TileInfo t3 = gfx::WordToTileInfo(bl); // Bottom left
226
227 ASSIGN_OR_RETURN(auto br, rom()->ReadWord(addr + 6 + (i * 8)));
228 gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right
229
230 int sheet_offset = 212;
231 if (bin_mode) {
232 sheet_offset = 0;
233 }
234 tile16_sheet_.ComposeTile16(gfx_data, t1, t2, t3, t4, sheet_offset);
235 }
236
238 *rom()->mutable_dungeon_palette(3)));
240
241 for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) {
242 auto tile = tile16_sheet_.GetTile16(i);
243 tile16_individual_[i] = tile;
245 tile16_individual_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3)));
247 }
248
249 return absl::OkStatus();
250}
251
253 for (int i = 0; i < 186; i++) {
255 if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
257 }
258
259 gfx::TileInfo t1 = tile16_sheet_.tile_info()[i].tiles[0];
260 gfx::TileInfo t2 = tile16_sheet_.tile_info()[i].tiles[1];
261 gfx::TileInfo t3 = tile16_sheet_.tile_info()[i].tiles[2];
262 gfx::TileInfo t4 = tile16_sheet_.tile_info()[i].tiles[3];
263
264 auto tl = gfx::TileInfoToWord(t1);
265 RETURN_IF_ERROR(rom()->WriteWord(addr + (i * 8), tl));
266
267 auto tr = gfx::TileInfoToWord(t2);
268 RETURN_IF_ERROR(rom()->WriteWord(addr + 2 + (i * 8), tr));
269
270 auto bl = gfx::TileInfoToWord(t3);
271 RETURN_IF_ERROR(rom()->WriteWord(addr + 4 + (i * 8), bl));
272
273 auto br = gfx::TileInfoToWord(t4);
274 RETURN_IF_ERROR(rom()->WriteWord(addr + 6 + (i * 8), br));
275 }
276 return absl::OkStatus();
277}
278
280 auto& current_dungeon = dungeon_maps_[selected_dungeon];
281 if (ImGui::BeginTabBar("##DungeonMapTabs")) {
282 auto nbr_floors =
283 current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
284 for (int i = 0; i < nbr_floors; i++) {
285 int basement_num = current_dungeon.nbr_of_basement - i;
286 std::string tab_name = absl::StrFormat("Basement %d", basement_num);
287 if (i >= current_dungeon.nbr_of_basement) {
288 tab_name = absl::StrFormat("Floor %d",
289 i - current_dungeon.nbr_of_basement + 1);
290 }
291
292 if (ImGui::BeginTabItem(tab_name.c_str())) {
293 floor_number = i;
294 screen_canvas_.DrawBackground(ImVec2(325, 325));
296
297 auto boss_room = current_dungeon.boss_room;
298 for (int j = 0; j < 25; j++) {
299 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
300 int tile16_id = current_dungeon.floor_gfx[floor_number][j];
301 int posX = ((j % 5) * 32);
302 int posY = ((j / 5) * 32);
303
304 if (tile16_individual_.count(tile16_id) == 0) {
305 tile16_individual_[tile16_id] =
306 tile16_sheet_.GetTile16(tile16_id);
308 &tile16_individual_[tile16_id]);
309 }
310 screen_canvas_.DrawBitmap(tile16_individual_[tile16_id], (posX * 2),
311 (posY * 2), 4.0f);
312
313 if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
314 screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64,
315 64, kRedPen);
316 }
317
318 std::string label =
320 screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
321 std::string gfx_id = core::UppercaseHexByte(tile16_id);
322 screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
323 }
324 }
325
326 screen_canvas_.DrawGrid(64.f, 5);
328
329 if (!screen_canvas_.points().empty()) {
330 int x = screen_canvas_.points().front().x / 64;
331 int y = screen_canvas_.points().front().y / 64;
332 selected_room = x + (y * 5);
333 }
334 ImGui::EndTabItem();
335 }
336 }
337 ImGui::EndTabBar();
338 }
339
341 "Selected Room",
342 &current_dungeon.floor_rooms[floor_number].at(selected_room));
343
344 gui::InputHexWord("Boss Room", &current_dungeon.boss_room);
345
346 const ImVec2 button_size = ImVec2(130, 0);
347
348 // Add Floor Button
349 if (ImGui::Button("Add Floor", button_size) &&
350 current_dungeon.nbr_of_floor < 8) {
351 current_dungeon.nbr_of_floor++;
353 }
354 ImGui::SameLine();
355 if (ImGui::Button("Remove Floor", button_size) &&
356 current_dungeon.nbr_of_floor > 0) {
357 current_dungeon.nbr_of_floor--;
359 }
360
361 // Add Basement Button
362 if (ImGui::Button("Add Basement", button_size) &&
363 current_dungeon.nbr_of_basement < 8) {
364 current_dungeon.nbr_of_basement++;
366 }
367 ImGui::SameLine();
368 if (ImGui::Button("Remove Basement", button_size) &&
369 current_dungeon.nbr_of_basement > 0) {
370 current_dungeon.nbr_of_basement--;
372 }
373
374 if (ImGui::Button("Copy Floor", button_size)) {
375 copy_button_pressed = true;
376 }
377 ImGui::SameLine();
378 if (ImGui::Button("Paste Floor", button_size)) {
380 }
381}
382
385 if (!LoadDungeonMaps().ok()) {
386 ImGui::Text("Failed to load dungeon maps");
387 }
388
389 if (LoadDungeonMapTile16(rom()->graphics_buffer()).ok()) {
390 // TODO: Load roomset gfx based on dungeon ID
391 sheets_.emplace(0, rom()->gfx_sheets()[212]);
392 sheets_.emplace(1, rom()->gfx_sheets()[213]);
393 sheets_.emplace(2, rom()->gfx_sheets()[214]);
394 sheets_.emplace(3, rom()->gfx_sheets()[215]);
396 } else {
397 ImGui::Text("Failed to load dungeon map tile16");
398 }
399 }
400
401 if (ImGui::BeginTable("##DungeonMapToolset", 2, ImGuiTableFlags_SizingFixedFit)) {
402 ImGui::TableSetupColumn("Draw Mode");
403 ImGui::TableSetupColumn("Edit Mode");
404
405 ImGui::TableNextColumn();
406 if (ImGui::Button(ICON_MD_DRAW)) {
408 }
409
410 ImGui::TableNextColumn();
411 if (ImGui::Button(ICON_MD_EDIT)) {
413 }
414
415 ImGui::EndTable();
416 }
417
418 static std::vector<std::string> dungeon_names = {
419 "Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
420 "Desert Palace", "Tower of Hera", "Agahnim's Tower",
421 "Palace of Darkness", "Swamp Palace", "Skull Woods",
422 "Thieves' Town", "Ice Palace", "Misery Mire",
423 "Turtle Rock", "Ganon's Tower"};
424
425 if (ImGui::BeginTable("DungeonMapsTable", 4,
426 ImGuiTableFlags_Resizable |
427 ImGuiTableFlags_Reorderable |
428 ImGuiTableFlags_Hideable)) {
429 ImGui::TableSetupColumn("Dungeon");
430 ImGui::TableSetupColumn("Map");
431 ImGui::TableSetupColumn("Rooms Gfx");
432 ImGui::TableSetupColumn("Tiles Gfx");
433 ImGui::TableHeadersRow();
434
435 // Dungeon column
436 ImGui::TableNextColumn();
437 for (int i = 0; i < dungeon_names.size(); i++) {
438 rom()->resource_label()->SelectableLabelWithNameEdit(
439 selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
440 dungeon_names[i]);
441 if (ImGui::IsItemClicked()) {
443 }
444 }
445
446 // Map column
447 ImGui::TableNextColumn();
449
450 ImGui::TableNextColumn();
451 if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
452 tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4));
458
459 if (!tilesheet_canvas_.points().empty()) {
460 selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
461 (tilesheet_canvas_.points().front().y / 32) * 16;
463
464 // Draw the selected tile
465 if (!screen_canvas_.points().empty()) {
469 }
470 }
471
472 ImGui::Separator();
473 current_tile_canvas_.DrawBackground(ImVec2(64 * 2 + 2, 64 * 2 + 4));
476 4.0f);
479
481 ImGui::SameLine();
484 ImGui::SameLine();
486
487 if (ImGui::Button("Modify Tile16")) {
489 rom()->graphics_buffer(), current_tile16_info.tiles[0],
495 *rom()->mutable_dungeon_palette(3)));
498 }
499 }
500 ImGui::EndChild();
501
502 ImGui::TableNextColumn();
506 // Get the tile8 ID to use for the tile16 drawing above
508 }
512
513 ImGui::Text("Selected tile8: %d", selected_tile8_);
514
515 ImGui::Separator();
516 ImGui::Text("For use with custom inserted graphics assembly patches.");
517 if (ImGui::Button("Load GFX from BIN file")) LoadBinaryGfx();
518
519 ImGui::EndTable();
520 }
521}
522
524 std::string bin_file = core::FileDialogWrapper::ShowOpenFileDialog();
525 if (!bin_file.empty()) {
526 std::ifstream file(bin_file, std::ios::binary);
527 if (file.is_open()) {
528 // Read the gfx data into a buffer
529 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
530 std::istreambuf_iterator<char>());
531 auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
532 gfx_bin_data_ = converted_bin;
534 if (LoadDungeonMapTile16(converted_bin, true).ok()) {
535 sheets_.clear();
536 std::vector<std::vector<uint8_t>> gfx_sheets;
537 for (int i = 0; i < 4; i++) {
538 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
539 converted_bin.begin() + ((i + 1) * 0x1000));
540 sheets_.emplace(i, gfx::Bitmap(128, 32, 8, gfx_sheets[i]));
541 sheets_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3));
543 }
544 binary_gfx_loaded_ = true;
545 } else {
546 status_ = absl::InternalError("Failed to load dungeon map tile16");
547 }
548 file.close();
549 }
550 }
551}
552
554 if (ImGui::BeginTabItem("Title Screen")) {
555 ImGui::EndTabItem();
556 }
557}
558
560 if (ImGui::BeginTabItem("Naming Screen")) {
561 ImGui::EndTabItem();
562 }
563}
564
566 if (ImGui::BeginTabItem("Overworld Map")) {
567 ImGui::EndTabItem();
568 }
569}
570
572 static bool show_bg1 = true;
573 static bool show_bg2 = true;
574 static bool show_bg3 = true;
575
576 static bool drawing_bg1 = true;
577 static bool drawing_bg2 = false;
578 static bool drawing_bg3 = false;
579
580 ImGui::Checkbox("Show BG1", &show_bg1);
581 ImGui::SameLine();
582 ImGui::Checkbox("Show BG2", &show_bg2);
583
584 ImGui::Checkbox("Draw BG1", &drawing_bg1);
585 ImGui::SameLine();
586 ImGui::Checkbox("Draw BG2", &drawing_bg2);
587 ImGui::SameLine();
588 ImGui::Checkbox("Draw BG3", &drawing_bg3);
589}
590
591} // namespace editor
592} // namespace app
593} // namespace yaze
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath.
static Renderer & GetInstance()
Definition renderer.h:27
void RenderBitmap(gfx::Bitmap *bitmap)
Used to render a bitmap to the screen.
Definition renderer.h:49
std::vector< std::vector< std::array< std::string, 25 > > > dungeon_map_labels_
absl::Status LoadDungeonMapTile16(const std::vector< uint8_t > &gfx_data, bool bin_mode=false)
absl::Status Update() override
std::vector< uint8_t > gfx_bin_data_
zelda3::screen::Inventory inventory_
gfx::InternalTile16 current_tile16_info
std::vector< zelda3::screen::DungeonMap > dungeon_maps_
std::unordered_map< int, gfx::Bitmap > tile16_individual_
Represents a bitmap image.
Definition bitmap.h:67
SNES 16-bit tile metadata container.
Definition snes_tile.h:50
void Init(int width, int height, TileType tile_type)
Definition tilesheet.cc:49
Bitmap GetTile16(int tile_id)
Definition tilesheet.h:63
void ModifyTile16(const std::vector< uint8_t > &graphics_buffer, const TileInfo &top_left, const TileInfo &top_right, const TileInfo &bottom_left, const TileInfo &bottom_right, int tile_id, int sheet_offset=0)
Definition tilesheet.cc:87
void ComposeTile16(const std::vector< uint8_t > &graphics_buffer, const TileInfo &top_left, const TileInfo &top_right, const TileInfo &bottom_left, const TileInfo &bottom_right, int sheet_offset=0)
Definition tilesheet.cc:62
bool DrawTileSelector(int size)
Definition canvas.cc:340
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0), bool drag=false)
Definition canvas.cc:66
int GetTileIdFromMousePos()
Definition canvas.h:133
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:686
void DrawText(std::string text, int x, int y)
Definition canvas.cc:615
void DrawBitmap(const Bitmap &bitmap, int border_offset=0, bool ready=true)
Definition canvas.cc:461
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:519
auto points() const
Definition canvas.h:153
void DrawBitmapTable(const BitmapTable &gfx_bin)
Definition canvas.cc:498
void DrawContextMenu(gfx::Bitmap *bitmap=nullptr)
Definition canvas.cc:101
#define RETURN_IF_ERROR(expression)
Definition constants.h:62
#define END_TAB_ITEM()
Definition constants.h:5
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition constants.h:70
#define TEXT_COLUMN(w)
Definition constants.h:16
#define TAB_ITEM(w)
Definition constants.h:4
#define RETURN_VOID_IF_ERROR(expression)
Definition constants.h:53
unsigned short ushort
Definition constants.h:112
#define BUTTON_COLUMN(w)
Definition constants.h:12
#define ICON_MD_MORE_VERT
Definition icons.h:1241
#define ICON_MD_DRAW
Definition icons.h:623
#define ICON_MD_ZOOM_OUT
Definition icons.h:2191
#define ICON_MD_REDO
Definition icons.h:1568
#define ICON_MD_EDIT
Definition icons.h:643
#define ICON_MD_BUILD
Definition icons.h:326
#define ICON_MD_ZOOM_IN
Definition icons.h:2189
#define ICON_MD_UNDO
Definition icons.h:2034
uint32_t SnesToPc(uint32_t addr) noexcept
Definition common.h:226
std::string UppercaseHexByte(uint8_t byte, bool leading)
Definition common.cc:103
constexpr uint32_t kRedPen
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:331
std::vector< uint8_t > SnesTo8bppSheet(const std::vector< uint8_t > &sheet, int bpp, int num_sheets)
Definition snes_tile.cc:152
uint16_t TileInfoToWord(TileInfo tile_info)
Definition snes_tile.cc:314
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:175
absl::Status DisplayPalette(app::gfx::SnesPalette &palette, bool loaded)
Definition color.cc:56
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
Definition input.cc:250
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:161
constexpr int kDungeonMapTile16Expanded
Definition dungeon_map.h:23
constexpr int kDungeonMapGfxPtr
Definition dungeon_map.h:15
constexpr int kDungeonMapBossRooms
Definition dungeon_map.h:26
constexpr int kDungeonMapTile16
Definition dungeon_map.h:22
constexpr int kDungeonMapFloors
Definition dungeon_map.h:13
constexpr int kDungeonMapRoomsPtr
Definition dungeon_map.h:12
constexpr int kDungeonMapExpCheck
Definition dungeon_map.h:21
Definition common.cc:22
std::array< TileInfo, 4 > tiles
Definition tilesheet.h:19