yaze 0.3.2
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"
14#include "app/gui/core/color.h"
15#include "app/gui/core/icons.h"
16#include "app/gui/core/input.h"
17#include "imgui/imgui.h"
18#include "util/file_util.h"
19#include "util/hex.h"
20#include "util/macro.h"
21
22namespace yaze {
23namespace editor {
24
25constexpr uint32_t kRedPen = 0xFF0000FF;
26
29 return;
30 auto* panel_manager = dependencies_.panel_manager;
31
32 panel_manager->RegisterPanel({.card_id = "screen.dungeon_maps",
33 .display_name = "Dungeon Maps",
34 .window_title = " Dungeon Map Editor",
35 .icon = ICON_MD_MAP,
36 .category = "Screen",
37 .shortcut_hint = "Alt+1",
38 .priority = 10,
39 .enabled_condition = [this]() { return rom()->is_loaded(); },
40 .disabled_tooltip = "Load a ROM first"});
41 panel_manager->RegisterPanel({.card_id = "screen.inventory_menu",
42 .display_name = "Inventory Menu",
43 .window_title = " Inventory Menu",
44 .icon = ICON_MD_INVENTORY,
45 .category = "Screen",
46 .shortcut_hint = "Alt+2",
47 .priority = 20,
48 .enabled_condition = [this]() { return rom()->is_loaded(); },
49 .disabled_tooltip = "Load a ROM first"});
50 panel_manager->RegisterPanel({.card_id = "screen.overworld_map",
51 .display_name = "Overworld Map",
52 .window_title = " Overworld Map",
53 .icon = ICON_MD_PUBLIC,
54 .category = "Screen",
55 .shortcut_hint = "Alt+3",
56 .priority = 30,
57 .enabled_condition = [this]() { return rom()->is_loaded(); },
58 .disabled_tooltip = "Load a ROM first"});
59 panel_manager->RegisterPanel({.card_id = "screen.title_screen",
60 .display_name = "Title Screen",
61 .window_title = " Title Screen",
62 .icon = ICON_MD_TITLE,
63 .category = "Screen",
64 .shortcut_hint = "Alt+4",
65 .priority = 40,
66 .enabled_condition = [this]() { return rom()->is_loaded(); },
67 .disabled_tooltip = "Load a ROM first"});
68 panel_manager->RegisterPanel({.card_id = "screen.naming_screen",
69 .display_name = "Naming Screen",
70 .window_title = " Naming Screen",
71 .icon = ICON_MD_EDIT,
72 .category = "Screen",
73 .shortcut_hint = "Alt+5",
74 .priority = 50,
75 .enabled_condition = [this]() { return rom()->is_loaded(); },
76 .disabled_tooltip = "Load a ROM first"});
77
78 // Register EditorPanel implementations
79 panel_manager->RegisterEditorPanel(std::make_unique<DungeonMapsPanel>(
80 [this]() { DrawDungeonMapsEditor(); }));
81 panel_manager->RegisterEditorPanel(std::make_unique<InventoryMenuPanel>(
82 [this]() { DrawInventoryMenuEditor(); }));
83 panel_manager->RegisterEditorPanel(std::make_unique<OverworldMapScreenPanel>(
84 [this]() { DrawOverworldMapEditor(); }));
85 panel_manager->RegisterEditorPanel(std::make_unique<TitleScreenPanel>(
86 [this]() { DrawTitleScreenEditor(); }));
87 panel_manager->RegisterEditorPanel(std::make_unique<NamingScreenPanel>(
88 [this]() { DrawNamingScreenEditor(); }));
89
90 // Show title screen by default
91 panel_manager->ShowPanel("screen.title_screen");
92}
93
94absl::Status ScreenEditor::Load() {
95 gfx::ScopedTimer timer("ScreenEditor::Load");
96
100 tile16_blockset_, *rom(), game_data(), game_data()->graphics_buffer,
101 false));
102
103 // Load graphics sheets and apply dungeon palette
104 sheets_[0] = std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[212]);
105 sheets_[1] = std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[213]);
106 sheets_[2] = std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[214]);
107 sheets_[3] = std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[215]);
108
109 // Apply dungeon palette to all sheets
110 for (int i = 0; i < 4; i++) {
111 sheets_[i]->SetPalette(*game_data()->palette_groups.dungeon_main.mutable_palette(3));
114 }
115
116 // Create a single tilemap for tile8 graphics with on-demand texture creation
117 // Combine all 4 sheets (128x32 each) into one bitmap (128x128)
118 // This gives us 16 tiles per row × 16 rows = 256 tiles total
119 const int tile8_width = 128;
120 const int tile8_height = 128; // 4 sheets × 32 pixels each
121 std::vector<uint8_t> tile8_data(tile8_width * tile8_height);
122
123 // Copy data from all 4 sheets into the combined bitmap
124 for (int sheet_idx = 0; sheet_idx < 4; sheet_idx++) {
125 const auto& sheet = *sheets_[sheet_idx];
126 int dest_y_offset = sheet_idx * 32; // Each sheet is 32 pixels tall
127
128 for (int y = 0; y < 32; y++) {
129 for (int x = 0; x < 128; x++) {
130 int src_index = y * 128 + x;
131 int dest_index = (dest_y_offset + y) * 128 + x;
132
133 if (src_index < sheet.size() && dest_index < tile8_data.size()) {
134 tile8_data[dest_index] = sheet.data()[src_index];
135 }
136 }
137 }
138 }
139
140 // Create tilemap with 8x8 tile size
141 tile8_tilemap_.tile_size = {8, 8};
142 tile8_tilemap_.map_size = {256, 256}; // Logical size for tile count
143 tile8_tilemap_.atlas.Create(tile8_width, tile8_height, 8, tile8_data);
145
146 // Queue single texture creation for the atlas (not individual tiles)
149 return absl::OkStatus();
150}
151
152absl::Status ScreenEditor::Update() {
153 // Panel drawing is handled centrally by PanelManager::DrawAllVisiblePanels()
154 // via the EditorPanel implementations registered in Initialize().
155 // No local drawing needed here - this fixes duplicate panel rendering.
156 return status_;
157}
158
160 // Sidebar is now drawn by EditorManager for card-based editors
161 // This method kept for compatibility but sidebar handles card toggles
162}
163
165 static bool create = false;
166 if (!create && rom()->is_loaded() && game_data()) {
168 if (status_.ok()) {
170 create = true;
171 } else {
172 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading inventory: %s",
173 status_.message().data());
174 return;
175 }
176 }
177
179
180 if (ImGui::BeginTable("InventoryScreen", 4, ImGuiTableFlags_Resizable)) {
181 ImGui::TableSetupColumn("Canvas");
182 ImGui::TableSetupColumn("Tilesheet");
183 ImGui::TableSetupColumn("Item Icons");
184 ImGui::TableSetupColumn("Palette");
185 ImGui::TableHeadersRow();
186
187 ImGui::TableNextColumn();
188 {
189 gui::CanvasFrameOptions frame_opts;
190 frame_opts.draw_grid = true;
191 frame_opts.grid_step = 32.0f;
192 frame_opts.render_popups = true;
193 auto runtime = gui::BeginCanvas(screen_canvas_, frame_opts);
194 gui::DrawBitmap(runtime, inventory_.bitmap(), 2, create ? 1.0f : 0.0f);
195 gui::EndCanvas(screen_canvas_, runtime, frame_opts);
196 }
197
198 ImGui::TableNextColumn();
199 {
200 gui::CanvasFrameOptions frame_opts;
201 frame_opts.canvas_size = ImVec2(128 * 2 + 2, (192 * 2) + 4);
202 frame_opts.draw_grid = true;
203 frame_opts.grid_step = 16.0f;
204 frame_opts.render_popups = true;
205 auto runtime = gui::BeginCanvas(tilesheet_canvas_, frame_opts);
206 gui::DrawBitmap(runtime, inventory_.tilesheet(), 2, create ? 1.0f : 0.0f);
207 gui::EndCanvas(tilesheet_canvas_, runtime, frame_opts);
208 }
209
210 ImGui::TableNextColumn();
212
213 ImGui::TableNextColumn();
215
216 ImGui::EndTable();
217 }
218 ImGui::Separator();
219
220 // TODO(scawful): Future Oracle of Secrets menu editor integration
221 // - Full inventory screen layout editor
222 // - Item slot assignment and positioning
223 // - Heart container and magic meter editor
224 // - Equipment display customization
225 // - A/B button equipment quick-select editor
226}
227
229 if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
230 ImVec2(0, 0))) {
231 ImGui::TableSetupColumn("#drawTool");
232 ImGui::TableSetupColumn("#sep1");
233 ImGui::TableSetupColumn("#zoomOut");
234 ImGui::TableSetupColumn("#zoomIN");
235 ImGui::TableSetupColumn("#sep2");
236 ImGui::TableSetupColumn("#bg2Tool");
237 ImGui::TableSetupColumn("#bg3Tool");
238 ImGui::TableSetupColumn("#itemTool");
239
240 ImGui::TableNextColumn();
241 if (ImGui::Button(ICON_MD_UNDO)) {
242 // status_ = inventory_.Undo();
243 }
244 ImGui::TableNextColumn();
245 if (ImGui::Button(ICON_MD_REDO)) {
246 // status_ = inventory_.Redo();
247 }
248 ImGui::TableNextColumn();
249 ImGui::Text(ICON_MD_MORE_VERT);
250 ImGui::TableNextColumn();
251 if (ImGui::Button(ICON_MD_ZOOM_OUT)) {
253 }
254 ImGui::TableNextColumn();
255 if (ImGui::Button(ICON_MD_ZOOM_IN)) {
257 }
258 ImGui::TableNextColumn();
259 ImGui::Text(ICON_MD_MORE_VERT);
260 ImGui::TableNextColumn();
261 if (ImGui::Button(ICON_MD_DRAW)) {
263 }
264 ImGui::TableNextColumn();
265 if (ImGui::Button(ICON_MD_BUILD)) {
266 // current_mode_ = EditingMode::BUILD;
267 }
268
269 ImGui::EndTable();
270 }
271}
272
274 if (ImGui::BeginChild("##ItemIconsList", ImVec2(0, 0), true,
275 ImGuiWindowFlags_HorizontalScrollbar)) {
276 ImGui::Text("Item Icons (2x2 tiles each)");
277 ImGui::Separator();
278
279 auto& icons = inventory_.item_icons();
280 if (icons.empty()) {
281 ImGui::TextWrapped(
282 "No item icons loaded. Icons will be loaded when the "
283 "inventory is initialized.");
284 ImGui::EndChild();
285 return;
286 }
287
288 // Display icons in a table format
289 if (ImGui::BeginTable("##IconsTable", 2,
290 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
291 ImGui::TableSetupColumn("Icon Name");
292 ImGui::TableSetupColumn("Tile Data");
293 ImGui::TableHeadersRow();
294
295 for (size_t i = 0; i < icons.size(); i++) {
296 const auto& icon = icons[i];
297
298 ImGui::TableNextRow();
299 ImGui::TableNextColumn();
300
301 // Display icon name with selectable row
302 if (ImGui::Selectable(icon.name.c_str(), false,
303 ImGuiSelectableFlags_SpanAllColumns)) {
304 // TODO: Select this icon for editing
305 }
306
307 ImGui::TableNextColumn();
308 // Display tile word data in hex format
309 ImGui::Text("TL:%04X TR:%04X", icon.tile_tl, icon.tile_tr);
310 ImGui::SameLine();
311 ImGui::Text("BL:%04X BR:%04X", icon.tile_bl, icon.tile_br);
312 }
313
314 ImGui::EndTable();
315 }
316
317 ImGui::Separator();
318 ImGui::TextWrapped(
319 "NOTE: Individual icon editing will be implemented in the future "
320 "Oracle of Secrets menu editor. Each icon is composed of 4 tile words "
321 "representing a 2x2 arrangement of 8x8 tiles in SNES tile format "
322 "(vhopppcc cccccccc).");
323 }
324 ImGui::EndChild();
325}
326
328 gfx::ScopedTimer timer("screen_editor_draw_dungeon_map_screen");
329
330 auto& current_dungeon = dungeon_maps_[selected_dungeon];
331
332 floor_number = i;
333 screen_canvas_.DrawBackground(ImVec2(325, 325));
335
336 auto boss_room = current_dungeon.boss_room;
337
338 // Pre-allocate vectors for batch operations
339 std::vector<int> tile_ids_to_render;
340 std::vector<ImVec2> tile_positions;
341 tile_ids_to_render.reserve(zelda3::kNumRooms);
342 tile_positions.reserve(zelda3::kNumRooms);
343
344 for (int j = 0; j < zelda3::kNumRooms; j++) {
345 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
346 int tile16_id = current_dungeon.floor_gfx[floor_number][j];
347 int posX = ((j % 5) * 32);
348 int posY = ((j / 5) * 32);
349
350 // Batch tile rendering
351 tile_ids_to_render.push_back(tile16_id);
352 tile_positions.emplace_back(posX * 2, posY * 2);
353 }
354 }
355
356 // Batch render all tiles
357 for (size_t idx = 0; idx < tile_ids_to_render.size(); ++idx) {
358 int tile16_id = tile_ids_to_render[idx];
359 ImVec2 pos = tile_positions[idx];
360
361 // Extract tile data from the atlas directly
362 const int tiles_per_row = tile16_blockset_.atlas.width() / 16;
363 const int tile_x = (tile16_id % tiles_per_row) * 16;
364 const int tile_y = (tile16_id / tiles_per_row) * 16;
365
366 std::vector<uint8_t> tile_data(16 * 16);
367 int tile_data_offset = 0;
368 tile16_blockset_.atlas.Get16x16Tile(tile_x, tile_y, tile_data,
369 tile_data_offset);
370
371 // Create or update cached tile
372 auto* cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id);
373 if (!cached_tile) {
374 // Create new cached tile
375 gfx::Bitmap new_tile(16, 16, 8, tile_data);
377 tile16_blockset_.tile_cache.CacheTile(tile16_id, std::move(new_tile));
378 cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id);
379 } else {
380 // Update existing cached tile data
381 cached_tile->set_data(tile_data);
382 }
383
384 if (cached_tile && cached_tile->is_active()) {
385 // Ensure the cached tile has a valid texture
386 if (!cached_tile->texture()) {
387 // Queue texture creation via Arena's deferred system
390 }
391 screen_canvas_.DrawBitmap(*cached_tile, pos.x, pos.y, 4.0F, 255);
392 }
393 }
394
395 // Draw overlays and labels
396 for (int j = 0; j < zelda3::kNumRooms; j++) {
397 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
398 int posX = ((j % 5) * 32);
399 int posY = ((j / 5) * 32);
400
401 if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
402 screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64, 64,
403 kRedPen);
404 }
405
406 std::string label =
408 screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
409 std::string gfx_id =
410 util::HexByte(current_dungeon.floor_gfx[floor_number][j]);
411 screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
412 }
413 }
414
415 screen_canvas_.DrawGrid(64.f, 5);
417
418 if (!screen_canvas_.points().empty()) {
419 int x = screen_canvas_.points().front().x / 64;
420 int y = screen_canvas_.points().front().y / 64;
421 selected_room = x + (y * 5);
422 }
423}
424
426 auto& current_dungeon = dungeon_maps_[selected_dungeon];
427 if (ImGui::BeginTabBar("##DungeonMapTabs")) {
428 auto nbr_floors =
429 current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
430 for (int i = 0; i < nbr_floors; i++) {
431 int basement_num = current_dungeon.nbr_of_basement - i;
432 std::string tab_name = absl::StrFormat("Basement %d", basement_num);
433 if (i >= current_dungeon.nbr_of_basement) {
434 tab_name = absl::StrFormat("Floor %d",
435 i - current_dungeon.nbr_of_basement + 1);
436 }
437 if (ImGui::BeginTabItem(tab_name.data())) {
439 ImGui::EndTabItem();
440 }
441 }
442 ImGui::EndTabBar();
443 }
444
446 "Selected Room",
447 &current_dungeon.floor_rooms[floor_number].at(selected_room));
448
449 gui::InputHexWord("Boss Room", &current_dungeon.boss_room);
450
451 const auto button_size = ImVec2(130, 0);
452
453 if (ImGui::Button("Add Floor", button_size) &&
454 current_dungeon.nbr_of_floor < 8) {
455 current_dungeon.nbr_of_floor++;
457 }
458 ImGui::SameLine();
459 if (ImGui::Button("Remove Floor", button_size) &&
460 current_dungeon.nbr_of_floor > 0) {
461 current_dungeon.nbr_of_floor--;
463 }
464
465 if (ImGui::Button("Add Basement", button_size) &&
466 current_dungeon.nbr_of_basement < 8) {
467 current_dungeon.nbr_of_basement++;
469 }
470 ImGui::SameLine();
471 if (ImGui::Button("Remove Basement", button_size) &&
472 current_dungeon.nbr_of_basement > 0) {
473 current_dungeon.nbr_of_basement--;
475 }
476
477 if (ImGui::Button("Copy Floor", button_size)) {
478 copy_button_pressed = true;
479 }
480 ImGui::SameLine();
481 if (ImGui::Button("Paste Floor", button_size)) {
483 }
484}
485
503 gfx::ScopedTimer timer("screen_editor_draw_dungeon_maps_room_gfx");
504
505 if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
506 // Enhanced tilesheet canvas with BeginCanvas/EndCanvas pattern
507 {
508 gui::CanvasFrameOptions tilesheet_opts;
509 tilesheet_opts.canvas_size = ImVec2((256 * 2) + 2, (192 * 2) + 4);
510 tilesheet_opts.draw_grid = true;
511 tilesheet_opts.grid_step = 32.0f;
512 tilesheet_opts.render_popups = true;
513
514 auto tilesheet_rt = gui::BeginCanvas(tilesheet_canvas_, tilesheet_opts);
515
516 // Interactive tile16 selector with grid snapping
517 ImVec2 selected_pos;
518 if (gui::DrawTileSelector(tilesheet_rt, 32, 0, &selected_pos)) {
519 // Double-click detected - handle tile confirmation if needed
520 }
521
522 // Check for single-click selection (legacy compatibility)
524 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
525 if (!tilesheet_canvas_.points().empty()) {
527 static_cast<int>(tilesheet_canvas_.points().front().x / 32 +
528 (tilesheet_canvas_.points().front().y / 32) * 16);
529
530 // Render selected tile16 and cache tile metadata
533 current_tile16_info.begin());
534 }
535 }
536
537 // Use stateless bitmap rendering for tilesheet
538 gui::DrawBitmap(tilesheet_rt, tile16_blockset_.atlas, 1, 1, 2.0F, 255);
539
540 gui::EndCanvas(tilesheet_canvas_, tilesheet_rt, tilesheet_opts);
541 }
542
543 if (!tilesheet_canvas_.points().empty() &&
544 !screen_canvas_.points().empty()) {
548 }
549
550 ImGui::Separator();
551
552 // Current tile canvas with BeginCanvas/EndCanvas pattern
553 {
554 gui::CanvasFrameOptions current_tile_opts;
555 current_tile_opts.draw_grid = true;
556 current_tile_opts.grid_step = 16.0f;
557 current_tile_opts.render_popups = true;
558
559 auto current_tile_rt =
560 gui::BeginCanvas(current_tile_canvas_, current_tile_opts);
561
562 // Get tile8 from cache on-demand (only create texture when needed)
563 if (selected_tile8_ >= 0 && selected_tile8_ < 256) {
564 auto* cached_tile8 = tile8_tilemap_.tile_cache.GetTile(selected_tile8_);
565
566 if (!cached_tile8) {
567 // Extract tile from atlas and cache it
568 const int tiles_per_row =
569 tile8_tilemap_.atlas.width() / 8; // 128 / 8 = 16
570 const int tile_x = (selected_tile8_ % tiles_per_row) * 8;
571 const int tile_y = (selected_tile8_ / tiles_per_row) * 8;
572
573 // Extract 8x8 tile data from atlas
574 std::vector<uint8_t> tile_data(64);
575 for (int py = 0; py < 8; py++) {
576 for (int px = 0; px < 8; px++) {
577 int src_x = tile_x + px;
578 int src_y = tile_y + py;
579 int src_index = src_y * tile8_tilemap_.atlas.width() + src_x;
580 int dst_index = py * 8 + px;
581
582 if (src_index < tile8_tilemap_.atlas.size() && dst_index < 64) {
583 tile_data[dst_index] = tile8_tilemap_.atlas.data()[src_index];
584 }
585 }
586 }
587
588 gfx::Bitmap new_tile8(8, 8, 8, tile_data);
591 std::move(new_tile8));
593 }
594
595 if (cached_tile8 && cached_tile8->is_active()) {
596 // Create texture on-demand only when needed
597 if (!cached_tile8->texture()) {
600 }
601
602 // DrawTilePainter still uses member function (not yet migrated)
603 if (current_tile_canvas_.DrawTilePainter(*cached_tile8, 16)) {
604 // Modify the tile16 based on the selected tile and
605 // current_tile16_info
606 gfx::ModifyTile16(tile16_blockset_, game_data()->graphics_buffer,
609 212, selected_tile16_);
611 }
612 }
613 }
614
615 // Get selected tile from cache and draw with stateless helper
616 auto* selected_tile =
618 if (selected_tile && selected_tile->is_active()) {
619 // Ensure the selected tile has a valid texture
620 if (!selected_tile->texture()) {
623 }
624 gui::DrawBitmap(current_tile_rt, *selected_tile, 2, 2, 4.0f, 255);
625 }
626
627 gui::EndCanvas(current_tile_canvas_, current_tile_rt, current_tile_opts);
628 }
629
631 ImGui::SameLine();
634 ImGui::SameLine();
636
637 if (ImGui::Button("Modify Tile16")) {
638 gfx::ModifyTile16(tile16_blockset_, game_data()->graphics_buffer,
643 }
644 }
645 ImGui::EndChild();
646}
647
665 // Enhanced editing mode controls with visual feedback
666 if (ImGui::Button(ICON_MD_DRAW)) {
668 }
669 ImGui::SameLine();
670 if (ImGui::Button(ICON_MD_EDIT)) {
672 }
673 ImGui::SameLine();
674 if (ImGui::Button(ICON_MD_SAVE)) {
676 }
677
678 static std::vector<std::string> dungeon_names = {
679 "Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
680 "Desert Palace", "Tower of Hera", "Agahnim's Tower",
681 "Palace of Darkness", "Swamp Palace", "Skull Woods",
682 "Thieves' Town", "Ice Palace", "Misery Mire",
683 "Turtle Rock", "Ganon's Tower"};
684
685 if (ImGui::BeginTable("DungeonMapsTable", 4,
686 ImGuiTableFlags_Resizable |
687 ImGuiTableFlags_Reorderable |
688 ImGuiTableFlags_Hideable)) {
689 ImGui::TableSetupColumn("Dungeon");
690 ImGui::TableSetupColumn("Map");
691 ImGui::TableSetupColumn("Rooms Gfx");
692 ImGui::TableSetupColumn("Tiles Gfx");
693 ImGui::TableHeadersRow();
694
695 ImGui::TableNextColumn();
696 for (int i = 0; i < dungeon_names.size(); i++) {
698 selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
699 dungeon_names[i]);
700 if (ImGui::IsItemClicked()) {
702 }
703 }
704
705 ImGui::TableNextColumn();
707
708 ImGui::TableNextColumn();
710
711 ImGui::TableNextColumn();
715 // Get the tile8 ID to use for the tile16 drawing above
717 }
721
722 ImGui::Text("Selected tile8: %d", selected_tile8_);
723 ImGui::Separator();
724 ImGui::Text("For use with custom inserted graphics assembly patches.");
725 if (ImGui::Button("Load GFX from BIN file"))
727
728 ImGui::EndTable();
729 }
730}
731
733 std::string bin_file = util::FileDialogWrapper::ShowOpenFileDialog();
734 if (!bin_file.empty()) {
735 std::ifstream file(bin_file, std::ios::binary);
736 if (file.is_open()) {
737 // Read the gfx data into a buffer
738 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
739 std::istreambuf_iterator<char>());
740 if (auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
742 converted_bin, true)
743 .ok()) {
744 sheets_.clear();
745 std::vector<std::vector<uint8_t>> gfx_sheets;
746 for (int i = 0; i < 4; i++) {
747 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
748 converted_bin.begin() + ((i + 1) * 0x1000));
749 sheets_[i] = std::make_unique<gfx::Bitmap>(128, 32, 8, gfx_sheets[i]);
750 sheets_[i]->SetPalette(*game_data()->palette_groups.dungeon_main.mutable_palette(3));
751 // Queue texture creation via Arena's deferred system
754 }
755 binary_gfx_loaded_ = true;
756 } else {
757 status_ = absl::InternalError("Failed to load dungeon map tile16");
758 }
759 file.close();
760 }
761 }
762}
763
765 // Initialize title screen on first draw
766 if (!title_screen_loaded_ && rom()->is_loaded() && game_data()) {
768 if (!status_.ok()) {
769 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading title screen: %s",
770 status_.message().data());
771 return;
772 }
774 }
775
777 ImGui::Text("Title screen not loaded. Ensure ROM is loaded.");
778 return;
779 }
780
781 // Toolbar with mode controls
782 if (ImGui::Button(ICON_MD_DRAW)) {
784 }
785 ImGui::SameLine();
786 if (ImGui::Button(ICON_MD_SAVE)) {
788 if (status_.ok()) {
789 ImGui::OpenPopup("SaveSuccess");
790 }
791 }
792 ImGui::SameLine();
793 ImGui::Text("Selected Tile: %d", selected_title_tile16_);
794
795 // Save success popup
796 if (ImGui::BeginPopup("SaveSuccess")) {
797 ImGui::Text("Title screen saved successfully!");
798 ImGui::EndPopup();
799 }
800
801 // Layer visibility controls
802 bool prev_bg1 = show_title_bg1_;
803 bool prev_bg2 = show_title_bg2_;
804 ImGui::Checkbox("Show BG1", &show_title_bg1_);
805 ImGui::SameLine();
806 ImGui::Checkbox("Show BG2", &show_title_bg2_);
807
808 // Re-render composite if visibility changed
809 if (prev_bg1 != show_title_bg1_ || prev_bg2 != show_title_bg2_) {
810 status_ =
812 if (status_.ok()) {
816 }
817 }
818
819 // Layout: 2-column table (composite view + tile selector)
820 if (ImGui::BeginTable("TitleScreenTable", 2,
821 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
822 ImGui::TableSetupColumn("Title Screen (Composite)");
823 ImGui::TableSetupColumn("Tile Selector");
824 ImGui::TableHeadersRow();
825
826 // Column 1: Composite Canvas (BG1+BG2 stacked)
827 ImGui::TableNextColumn();
829
830 // Column 2: Blockset Selector
831 ImGui::TableNextColumn();
833
834 ImGui::EndTable();
835 }
836}
837
841
842 // Draw composite tilemap (BG1+BG2 stacked with transparency)
843 auto& composite_bitmap = title_screen_.composite_bitmap();
844 if (composite_bitmap.is_active()) {
845 title_bg1_canvas_.DrawBitmap(composite_bitmap, 0, 0, 2.0f, 255);
846 }
847
848 // Handle tile painting - always paint to BG1 layer
851 if (!title_bg1_canvas_.points().empty()) {
852 auto click_pos = title_bg1_canvas_.points().front();
853 int tile_x = static_cast<int>(click_pos.x) / 8;
854 int tile_y = static_cast<int>(click_pos.y) / 8;
855
856 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
857 int tilemap_index = tile_y * 32 + tile_x;
858
859 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
860 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
861 tile_word |= (title_palette_ & 0x07) << 10;
862 if (title_h_flip_)
863 tile_word |= 0x4000;
864 if (title_v_flip_)
865 tile_word |= 0x8000;
866
867 // Update BG1 buffer and re-render both layers and composite
868 title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word;
870 if (status_.ok()) {
871 // Update BG1 texture
875
876 // Re-render and update composite
879 if (status_.ok()) {
881 gfx::Arena::TextureCommandType::UPDATE, &composite_bitmap);
882 }
883 }
884 }
885 }
886 }
887 }
888
891}
892
896
897 // Draw BG1 tilemap
898 auto& bg1_bitmap = title_screen_.bg1_bitmap();
899 if (bg1_bitmap.is_active()) {
900 title_bg1_canvas_.DrawBitmap(bg1_bitmap, 0, 0, 2.0f, 255);
901 }
902
903 // Handle tile painting
906 if (!title_bg1_canvas_.points().empty()) {
907 auto click_pos = title_bg1_canvas_.points().front();
908 int tile_x = static_cast<int>(click_pos.x) / 8;
909 int tile_y = static_cast<int>(click_pos.y) / 8;
910
911 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
912 int tilemap_index = tile_y * 32 + tile_x;
913
914 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
915 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
916 tile_word |= (title_palette_ & 0x07) << 10;
917 if (title_h_flip_)
918 tile_word |= 0x4000;
919 if (title_v_flip_)
920 tile_word |= 0x8000;
921
922 // Update buffer and re-render
923 title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word;
925 if (status_.ok()) {
928 }
929 }
930 }
931 }
932 }
933
936}
937
941
942 // Draw BG2 tilemap
943 auto& bg2_bitmap = title_screen_.bg2_bitmap();
944 if (bg2_bitmap.is_active()) {
945 title_bg2_canvas_.DrawBitmap(bg2_bitmap, 0, 0, 2.0f, 255);
946 }
947
948 // Handle tile painting
951 if (!title_bg2_canvas_.points().empty()) {
952 auto click_pos = title_bg2_canvas_.points().front();
953 int tile_x = static_cast<int>(click_pos.x) / 8;
954 int tile_y = static_cast<int>(click_pos.y) / 8;
955
956 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
957 int tilemap_index = tile_y * 32 + tile_x;
958
959 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
960 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
961 tile_word |= (title_palette_ & 0x07) << 10;
962 if (title_h_flip_)
963 tile_word |= 0x4000;
964 if (title_v_flip_)
965 tile_word |= 0x8000;
966
967 // Update buffer and re-render
968 title_screen_.mutable_bg2_buffer()[tilemap_index] = tile_word;
970 if (status_.ok()) {
973 }
974 }
975 }
976 }
977 }
978
981}
982
986
987 // Draw tile8 bitmap (8x8 tiles used to compose tile16)
988 auto& tiles8_bitmap = title_screen_.tiles8_bitmap();
989 if (tiles8_bitmap.is_active()) {
990 title_blockset_canvas_.DrawBitmap(tiles8_bitmap, 0, 0, 2.0f, 255);
991 }
992
993 // Handle tile selection (8x8 tiles)
995 // Calculate selected tile ID from click position
996 if (!title_blockset_canvas_.points().empty()) {
997 auto click_pos = title_blockset_canvas_.points().front();
998 int tile_x = static_cast<int>(click_pos.x) / 8;
999 int tile_y = static_cast<int>(click_pos.y) / 8;
1000 int tiles_per_row = 128 / 8; // 16 tiles per row for 8x8 tiles
1001 selected_title_tile16_ = tile_x + (tile_y * tiles_per_row);
1002 }
1003 }
1004
1007
1008 // Show selected tile preview and controls
1009 if (selected_title_tile16_ >= 0) {
1010 ImGui::Text("Selected Tile: %d", selected_title_tile16_);
1011
1012 // Flip controls
1013 ImGui::Checkbox("H Flip", &title_h_flip_);
1014 ImGui::SameLine();
1015 ImGui::Checkbox("V Flip", &title_v_flip_);
1016
1017 // Palette selector (0-7 for 3BPP graphics)
1018 ImGui::SetNextItemWidth(100);
1019 ImGui::SliderInt("Palette", &title_palette_, 0, 7);
1020 }
1021}
1022
1024
1026 // Initialize overworld map on first draw
1027 if (!ow_map_loaded_ && rom()->is_loaded()) {
1029 if (!status_.ok()) {
1030 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading overworld map: %s",
1031 status_.message().data());
1032 return;
1033 }
1034 ow_map_loaded_ = true;
1035 }
1036
1037 if (!ow_map_loaded_) {
1038 ImGui::Text("Overworld map not loaded. Ensure ROM is loaded.");
1039 return;
1040 }
1041
1042 // Toolbar with mode controls
1043 if (ImGui::Button(ICON_MD_DRAW)) {
1045 }
1046 ImGui::SameLine();
1047 if (ImGui::Button(ICON_MD_SAVE)) {
1049 if (status_.ok()) {
1050 ImGui::OpenPopup("OWSaveSuccess");
1051 }
1052 }
1053 ImGui::SameLine();
1054
1055 // World toggle
1056 if (ImGui::Button(ow_show_dark_world_ ? "Dark World" : "Light World")) {
1058 // Re-render map with new world
1060 if (status_.ok()) {
1063 }
1064 }
1065 ImGui::SameLine();
1066
1067 // Custom map load/save buttons
1068 if (ImGui::Button("Load Custom Map...")) {
1070 if (!path.empty()) {
1072 if (!status_.ok()) {
1073 ImGui::OpenPopup("CustomMapLoadError");
1074 }
1075 }
1076 }
1077 ImGui::SameLine();
1078 if (ImGui::Button("Save Custom Map...")) {
1080 if (!path.empty()) {
1082 if (status_.ok()) {
1083 ImGui::OpenPopup("CustomMapSaveSuccess");
1084 }
1085 }
1086 }
1087
1088 ImGui::SameLine();
1089 ImGui::Text("Selected Tile: %d", selected_ow_tile_);
1090
1091 // Custom map error/success popups
1092 if (ImGui::BeginPopup("CustomMapLoadError")) {
1093 ImGui::Text("Error loading custom map: %s", status_.message().data());
1094 ImGui::EndPopup();
1095 }
1096 if (ImGui::BeginPopup("CustomMapSaveSuccess")) {
1097 ImGui::Text("Custom map saved successfully!");
1098 ImGui::EndPopup();
1099 }
1100
1101 // Save success popup
1102 if (ImGui::BeginPopup("OWSaveSuccess")) {
1103 ImGui::Text("Overworld map saved successfully!");
1104 ImGui::EndPopup();
1105 }
1106
1107 // Layout: 3-column table
1108 if (ImGui::BeginTable("OWMapTable", 3,
1109 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
1110 ImGui::TableSetupColumn("Map Canvas");
1111 ImGui::TableSetupColumn("Tileset");
1112 ImGui::TableSetupColumn("Palette");
1113 ImGui::TableHeadersRow();
1114
1115 // Column 1: Map Canvas
1116 ImGui::TableNextColumn();
1119
1120 auto& map_bitmap = ow_map_screen_.map_bitmap();
1121 if (map_bitmap.is_active()) {
1122 ow_map_canvas_.DrawBitmap(map_bitmap, 0, 0, 1.0f, 255);
1123 }
1124
1125 // Handle tile painting
1127 if (ow_map_canvas_.DrawTileSelector(8.0f)) {
1128 if (!ow_map_canvas_.points().empty()) {
1129 auto click_pos = ow_map_canvas_.points().front();
1130 int tile_x = static_cast<int>(click_pos.x) / 8;
1131 int tile_y = static_cast<int>(click_pos.y) / 8;
1132
1133 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1134 int tile_index = tile_x + (tile_y * 64);
1135
1136 // Update appropriate world's tile data
1137 if (ow_show_dark_world_) {
1139 } else {
1141 }
1142
1143 // Re-render map
1145 if (status_.ok()) {
1148 }
1149 }
1150 }
1151 }
1152 }
1153
1156
1157 // Column 2: Tileset Selector
1158 ImGui::TableNextColumn();
1161
1162 auto& tiles8_bitmap = ow_map_screen_.tiles8_bitmap();
1163 if (tiles8_bitmap.is_active()) {
1164 ow_tileset_canvas_.DrawBitmap(tiles8_bitmap, 0, 0, 2.0f, 255);
1165 }
1166
1167 // Handle tile selection
1169 if (!ow_tileset_canvas_.points().empty()) {
1170 auto click_pos = ow_tileset_canvas_.points().front();
1171 int tile_x = static_cast<int>(click_pos.x) / 8;
1172 int tile_y = static_cast<int>(click_pos.y) / 8;
1173 selected_ow_tile_ = tile_x + (tile_y * 16); // 16 tiles per row
1174 }
1175 }
1176
1179
1180 // Column 3: Palette Display
1181 ImGui::TableNextColumn();
1184 // Use inline palette editor for full 128-color palette
1185 gui::InlinePaletteEditor(palette, "Overworld Map Palette");
1186
1187 ImGui::EndTable();
1188 }
1189}
1190
1192 static bool show_bg1 = true;
1193 static bool show_bg2 = true;
1194 static bool show_bg3 = true;
1195
1196 static bool drawing_bg1 = true;
1197 static bool drawing_bg2 = false;
1198 static bool drawing_bg3 = false;
1199
1200 ImGui::Checkbox("Show BG1", &show_bg1);
1201 ImGui::SameLine();
1202 ImGui::Checkbox("Show BG2", &show_bg2);
1203
1204 ImGui::Checkbox("Draw BG1", &drawing_bg1);
1205 ImGui::SameLine();
1206 ImGui::Checkbox("Draw BG2", &drawing_bg2);
1207 ImGui::SameLine();
1208 ImGui::Checkbox("Draw BG3", &drawing_bg3);
1209}
1210
1211} // namespace editor
1212} // namespace yaze
project::ResourceLabelManager * resource_label()
Definition rom.h:146
bool is_loaded() const
Definition rom.h:128
zelda3::GameData * game_data() const
Definition editor.h:228
EditorDependencies dependencies_
Definition editor.h:237
void RegisterPanel(size_t session_id, const PanelDescriptor &base_info)
void DrawDungeonMapsRoomGfx()
Draw dungeon room graphics editor with enhanced tile16 editing.
std::array< gfx::TileInfo, 4 > current_tile16_info
absl::Status Load() override
absl::Status Update() override
zelda3::OverworldMapScreen ow_map_screen_
void DrawDungeonMapsEditor()
Draw dungeon maps editor with enhanced ROM hacking features.
zelda3::TitleScreen title_screen_
zelda3::Inventory inventory_
zelda3::DungeonMapLabels dungeon_map_labels_
std::vector< zelda3::DungeonMap > dungeon_maps_
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:34
static Arena & Get()
Definition arena.cc:19
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const uint8_t * data() const
Definition bitmap.h:377
const SnesPalette & palette() const
Definition bitmap.h:368
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:199
auto size() const
Definition bitmap.h:376
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:851
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:382
int width() const
Definition bitmap.h:373
void Get16x16Tile(int tile_x, int tile_y, std::vector< uint8_t > &tile_data, int &tile_data_offset)
Extract a 16x16 tile from the bitmap (SNES metatile size)
Definition bitmap.cc:690
RAII timer for automatic timing management.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1075
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1144
ImVector< ImVec2 > * mutable_points()
Definition canvas.h:440
void DrawContextMenu()
Definition canvas.cc:602
int GetTileIdFromMousePos()
Definition canvas.h:412
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1011
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
Definition canvas.cc:852
bool IsMouseHovering() const
Definition canvas.h:433
void DrawBitmapTable(const BitmapTable &gfx_bin)
Definition canvas.cc:1122
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:547
const ImVector< ImVec2 > & points() const
Definition canvas.h:439
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1398
void DrawText(const std::string &text, int x, int y)
Definition canvas.cc:1346
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
absl::Status Create(Rom *rom, GameData *game_data=nullptr)
Initialize and load inventory screen data from ROM.
Definition inventory.cc:14
absl::Status SaveCustomMap(const std::string &file_path, bool use_dark_world)
Save map data to external binary file.
absl::Status LoadCustomMap(const std::string &file_path)
Load custom map from external binary file.
absl::Status Save(Rom *rom)
Save changes back to ROM.
absl::Status RenderMapLayer(bool use_dark_world)
Render map tiles into bitmap.
absl::Status Create(Rom *rom)
Initialize and load overworld map data from ROM.
absl::Status Create(Rom *rom, GameData *game_data=nullptr)
Initialize and load title screen data from ROM.
absl::Status RenderCompositeLayer(bool show_bg1, bool show_bg2)
Render composite layer with BG1 on top of BG2 with transparency.
absl::Status Save(Rom *rom)
absl::Status RenderBG2Layer()
Render BG2 tilemap into bitmap pixels Converts tile IDs from tiles_bg2_buffer_ into pixel data.
absl::Status RenderBG1Layer()
Render BG1 tilemap into bitmap pixels Converts tile IDs from tiles_bg1_buffer_ into pixel data.
#define ICON_MD_TITLE
Definition icons.h:1990
#define ICON_MD_MORE_VERT
Definition icons.h:1243
#define ICON_MD_DRAW
Definition icons.h:625
#define ICON_MD_ZOOM_OUT
Definition icons.h:2196
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_REDO
Definition icons.h:1570
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_PUBLIC
Definition icons.h:1524
#define ICON_MD_INVENTORY
Definition icons.h:1011
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_ZOOM_IN
Definition icons.h:2194
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_UNDO
Definition icons.h:2039
#define PRINT_IF_ERROR(expression)
Definition macro.h:28
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
constexpr uint32_t kRedPen
void RenderTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:76
void ModifyTile16(Tilemap &tilemap, const std::vector< uint8_t > &data, const TileInfo &top_left, const TileInfo &top_right, const TileInfo &bottom_left, const TileInfo &bottom_right, int sheet_offset, int tile_id)
Definition tilemap.cc:223
void UpdateTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:115
std::vector< uint8_t > SnesTo8bppSheet(std::span< uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:131
void EndCanvas(Canvas &canvas)
Definition canvas.cc:1509
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:344
bool DrawTileSelector(const CanvasRuntime &rt, int size, int size_y, ImVec2 *out_selected_pos)
Definition canvas.cc:2272
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:1486
IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, const std::string &title, ImGuiColorEditFlags flags)
Full inline palette editor with color picker and copy options.
Definition color.cc:118
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
Definition input.cc:559
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded)
Definition color.cc:235
void DrawBitmap(const CanvasRuntime &rt, gfx::Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:2087
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:370
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:30
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom, GameData *game_data, const std::vector< uint8_t > &gfx_data, bool bin_mode)
Load the dungeon map tile16 from the ROM.
constexpr int kNumRooms
Definition dungeon_map.h:37
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom)
Save the dungeon map tile16 to the ROM.
absl::StatusOr< std::vector< DungeonMap > > LoadDungeonMaps(Rom &rom, DungeonMapLabels &dungeon_map_labels)
Load the dungeon maps from the ROM.
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
void CacheTile(int tile_id, const Bitmap &bitmap)
Cache a tile bitmap by copying it.
Definition tilemap.h:67
Bitmap * GetTile(int tile_id)
Get a cached tile by ID.
Definition tilemap.h:50
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Definition tilemap.h:123
TileCache tile_cache
Smart tile cache with LRU eviction.
Definition tilemap.h:120
Pair map_size
Size of tilemap in tiles.
Definition tilemap.h:124
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
std::vector< std::array< gfx::TileInfo, 4 > > tile_info
Tile metadata (4 tiles per 16x16)
Definition tilemap.h:122
std::optional< float > grid_step
Definition canvas.h:70
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:1294
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89