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(
33 {.card_id = "screen.dungeon_maps",
34 .display_name = "Dungeon Maps",
35 .window_title = " Dungeon Map Editor",
36 .icon = ICON_MD_MAP,
37 .category = "Screen",
38 .shortcut_hint = "Alt+1",
39 .priority = 10,
40 .enabled_condition = [this]() { return rom()->is_loaded(); },
41 .disabled_tooltip = "Load a ROM first"});
42 panel_manager->RegisterPanel(
43 {.card_id = "screen.inventory_menu",
44 .display_name = "Inventory Menu",
45 .window_title = " Inventory Menu",
46 .icon = ICON_MD_INVENTORY,
47 .category = "Screen",
48 .shortcut_hint = "Alt+2",
49 .priority = 20,
50 .enabled_condition = [this]() { return rom()->is_loaded(); },
51 .disabled_tooltip = "Load a ROM first"});
52 panel_manager->RegisterPanel(
53 {.card_id = "screen.overworld_map",
54 .display_name = "Overworld Map",
55 .window_title = " Overworld Map",
56 .icon = ICON_MD_PUBLIC,
57 .category = "Screen",
58 .shortcut_hint = "Alt+3",
59 .priority = 30,
60 .enabled_condition = [this]() { return rom()->is_loaded(); },
61 .disabled_tooltip = "Load a ROM first"});
62 panel_manager->RegisterPanel(
63 {.card_id = "screen.title_screen",
64 .display_name = "Title Screen",
65 .window_title = " Title Screen",
66 .icon = ICON_MD_TITLE,
67 .category = "Screen",
68 .shortcut_hint = "Alt+4",
69 .priority = 40,
70 .enabled_condition = [this]() { return rom()->is_loaded(); },
71 .disabled_tooltip = "Load a ROM first"});
72 panel_manager->RegisterPanel(
73 {.card_id = "screen.naming_screen",
74 .display_name = "Naming Screen",
75 .window_title = " Naming Screen",
76 .icon = ICON_MD_EDIT,
77 .category = "Screen",
78 .shortcut_hint = "Alt+5",
79 .priority = 50,
80 .enabled_condition = [this]() { return rom()->is_loaded(); },
81 .disabled_tooltip = "Load a ROM first"});
82
83 // Register EditorPanel implementations
84 panel_manager->RegisterEditorPanel(std::make_unique<DungeonMapsPanel>(
85 [this]() { DrawDungeonMapsEditor(); }));
86 panel_manager->RegisterEditorPanel(std::make_unique<InventoryMenuPanel>(
87 [this]() { DrawInventoryMenuEditor(); }));
88 panel_manager->RegisterEditorPanel(std::make_unique<OverworldMapScreenPanel>(
89 [this]() { DrawOverworldMapEditor(); }));
90 panel_manager->RegisterEditorPanel(std::make_unique<TitleScreenPanel>(
91 [this]() { DrawTitleScreenEditor(); }));
92 panel_manager->RegisterEditorPanel(std::make_unique<NamingScreenPanel>(
93 [this]() { DrawNamingScreenEditor(); }));
94
95 // Show title screen by default
96 panel_manager->ShowPanel("screen.title_screen");
97}
98
99absl::Status ScreenEditor::Load() {
100 gfx::ScopedTimer timer("ScreenEditor::Load");
101
106 game_data()->graphics_buffer, false));
107
108 // Load graphics sheets and apply dungeon palette
109 sheets_[0] =
110 std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[212]);
111 sheets_[1] =
112 std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[213]);
113 sheets_[2] =
114 std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[214]);
115 sheets_[3] =
116 std::make_unique<gfx::Bitmap>(gfx::Arena::Get().gfx_sheets()[215]);
117
118 // Apply dungeon palette to all sheets
119 for (int i = 0; i < 4; i++) {
120 sheets_[i]->SetPalette(
121 *game_data()->palette_groups.dungeon_main.mutable_palette(3));
124 }
125
126 // Create a single tilemap for tile8 graphics with on-demand texture creation
127 // Combine all 4 sheets (128x32 each) into one bitmap (128x128)
128 // This gives us 16 tiles per row × 16 rows = 256 tiles total
129 const int tile8_width = 128;
130 const int tile8_height = 128; // 4 sheets × 32 pixels each
131 std::vector<uint8_t> tile8_data(tile8_width * tile8_height);
132
133 // Copy data from all 4 sheets into the combined bitmap
134 for (int sheet_idx = 0; sheet_idx < 4; sheet_idx++) {
135 const auto& sheet = *sheets_[sheet_idx];
136 int dest_y_offset = sheet_idx * 32; // Each sheet is 32 pixels tall
137
138 for (int y = 0; y < 32; y++) {
139 for (int x = 0; x < 128; x++) {
140 int src_index = y * 128 + x;
141 int dest_index = (dest_y_offset + y) * 128 + x;
142
143 if (src_index < sheet.size() && dest_index < tile8_data.size()) {
144 tile8_data[dest_index] = sheet.data()[src_index];
145 }
146 }
147 }
148 }
149
150 // Create tilemap with 8x8 tile size
151 tile8_tilemap_.tile_size = {8, 8};
152 tile8_tilemap_.map_size = {256, 256}; // Logical size for tile count
153 tile8_tilemap_.atlas.Create(tile8_width, tile8_height, 8, tile8_data);
156
157 // Queue single texture creation for the atlas (not individual tiles)
160 return absl::OkStatus();
161}
162
163absl::Status ScreenEditor::Update() {
164 // Panel drawing is handled centrally by PanelManager::DrawAllVisiblePanels()
165 // via the EditorPanel implementations registered in Initialize().
166 // No local drawing needed here - this fixes duplicate panel rendering.
167 return status_;
168}
169
171 // Sidebar is now drawn by EditorManager for card-based editors
172 // This method kept for compatibility but sidebar handles card toggles
173}
174
176 static bool create = false;
177 if (!create && rom()->is_loaded() && game_data()) {
179 if (status_.ok()) {
181 create = true;
182 } else {
183 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading inventory: %s",
184 status_.message().data());
185 return;
186 }
187 }
188
190
191 if (ImGui::BeginTable("InventoryScreen", 4, ImGuiTableFlags_Resizable)) {
192 ImGui::TableSetupColumn("Canvas");
193 ImGui::TableSetupColumn("Tilesheet");
194 ImGui::TableSetupColumn("Item Icons");
195 ImGui::TableSetupColumn("Palette");
196 ImGui::TableHeadersRow();
197
198 ImGui::TableNextColumn();
199 {
200 gui::CanvasFrameOptions frame_opts;
201 frame_opts.draw_grid = true;
202 frame_opts.grid_step = 32.0f;
203 frame_opts.render_popups = true;
204 auto runtime = gui::BeginCanvas(screen_canvas_, frame_opts);
205 gui::DrawBitmap(runtime, inventory_.bitmap(), 2, create ? 1.0f : 0.0f);
206 gui::EndCanvas(screen_canvas_, runtime, frame_opts);
207 }
208
209 ImGui::TableNextColumn();
210 {
211 gui::CanvasFrameOptions frame_opts;
212 frame_opts.canvas_size = ImVec2(128 * 2 + 2, (192 * 2) + 4);
213 frame_opts.draw_grid = true;
214 frame_opts.grid_step = 16.0f;
215 frame_opts.render_popups = true;
216 auto runtime = gui::BeginCanvas(tilesheet_canvas_, frame_opts);
217 gui::DrawBitmap(runtime, inventory_.tilesheet(), 2, create ? 1.0f : 0.0f);
218 gui::EndCanvas(tilesheet_canvas_, runtime, frame_opts);
219 }
220
221 ImGui::TableNextColumn();
223
224 ImGui::TableNextColumn();
226
227 ImGui::EndTable();
228 }
229 ImGui::Separator();
230
231 // TODO(scawful): Future Oracle of Secrets menu editor integration
232 // - Full inventory screen layout editor
233 // - Item slot assignment and positioning
234 // - Heart container and magic meter editor
235 // - Equipment display customization
236 // - A/B button equipment quick-select editor
237}
238
240 if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
241 ImVec2(0, 0))) {
242 ImGui::TableSetupColumn("#drawTool");
243 ImGui::TableSetupColumn("#sep1");
244 ImGui::TableSetupColumn("#zoomOut");
245 ImGui::TableSetupColumn("#zoomIN");
246 ImGui::TableSetupColumn("#sep2");
247 ImGui::TableSetupColumn("#bg2Tool");
248 ImGui::TableSetupColumn("#bg3Tool");
249 ImGui::TableSetupColumn("#itemTool");
250
251 ImGui::TableNextColumn();
252 if (ImGui::Button(ICON_MD_UNDO)) {
253 // status_ = inventory_.Undo();
254 }
255 ImGui::TableNextColumn();
256 if (ImGui::Button(ICON_MD_REDO)) {
257 // status_ = inventory_.Redo();
258 }
259 ImGui::TableNextColumn();
260 ImGui::Text(ICON_MD_MORE_VERT);
261 ImGui::TableNextColumn();
262 if (ImGui::Button(ICON_MD_ZOOM_OUT)) {
264 }
265 ImGui::TableNextColumn();
266 if (ImGui::Button(ICON_MD_ZOOM_IN)) {
268 }
269 ImGui::TableNextColumn();
270 ImGui::Text(ICON_MD_MORE_VERT);
271 ImGui::TableNextColumn();
272 if (ImGui::Button(ICON_MD_DRAW)) {
274 }
275 ImGui::TableNextColumn();
276 if (ImGui::Button(ICON_MD_BUILD)) {
277 // current_mode_ = EditingMode::BUILD;
278 }
279
280 ImGui::EndTable();
281 }
282}
283
285 if (ImGui::BeginChild("##ItemIconsList", ImVec2(0, 0), true,
286 ImGuiWindowFlags_HorizontalScrollbar)) {
287 ImGui::Text("Item Icons (2x2 tiles each)");
288 ImGui::Separator();
289
290 auto& icons = inventory_.item_icons();
291 if (icons.empty()) {
292 ImGui::TextWrapped(
293 "No item icons loaded. Icons will be loaded when the "
294 "inventory is initialized.");
295 ImGui::EndChild();
296 return;
297 }
298
299 // Display icons in a table format
300 if (ImGui::BeginTable("##IconsTable", 2,
301 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
302 ImGui::TableSetupColumn("Icon Name");
303 ImGui::TableSetupColumn("Tile Data");
304 ImGui::TableHeadersRow();
305
306 for (size_t i = 0; i < icons.size(); i++) {
307 const auto& icon = icons[i];
308
309 ImGui::TableNextRow();
310 ImGui::TableNextColumn();
311
312 // Display icon name with selectable row
313 if (ImGui::Selectable(icon.name.c_str(), false,
314 ImGuiSelectableFlags_SpanAllColumns)) {
315 // TODO: Select this icon for editing
316 }
317
318 ImGui::TableNextColumn();
319 // Display tile word data in hex format
320 ImGui::Text("TL:%04X TR:%04X", icon.tile_tl, icon.tile_tr);
321 ImGui::SameLine();
322 ImGui::Text("BL:%04X BR:%04X", icon.tile_bl, icon.tile_br);
323 }
324
325 ImGui::EndTable();
326 }
327
328 ImGui::Separator();
329 ImGui::TextWrapped(
330 "NOTE: Individual icon editing will be implemented in the future "
331 "Oracle of Secrets menu editor. Each icon is composed of 4 tile words "
332 "representing a 2x2 arrangement of 8x8 tiles in SNES tile format "
333 "(vhopppcc cccccccc).");
334 }
335 ImGui::EndChild();
336}
337
339 gfx::ScopedTimer timer("screen_editor_draw_dungeon_map_screen");
340
341 auto& current_dungeon = dungeon_maps_[selected_dungeon];
342
343 floor_number = i;
344 screen_canvas_.DrawBackground(ImVec2(325, 325));
346
347 auto boss_room = current_dungeon.boss_room;
348
349 // Pre-allocate vectors for batch operations
350 std::vector<int> tile_ids_to_render;
351 std::vector<ImVec2> tile_positions;
352 tile_ids_to_render.reserve(zelda3::kNumRooms);
353 tile_positions.reserve(zelda3::kNumRooms);
354
355 for (int j = 0; j < zelda3::kNumRooms; j++) {
356 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
357 int tile16_id = current_dungeon.floor_gfx[floor_number][j];
358 int posX = ((j % 5) * 32);
359 int posY = ((j / 5) * 32);
360
361 // Batch tile rendering
362 tile_ids_to_render.push_back(tile16_id);
363 tile_positions.emplace_back(posX * 2, posY * 2);
364 }
365 }
366
367 // Batch render all tiles
368 for (size_t idx = 0; idx < tile_ids_to_render.size(); ++idx) {
369 int tile16_id = tile_ids_to_render[idx];
370 ImVec2 pos = tile_positions[idx];
371
372 // Extract tile data from the atlas directly
373 const int tiles_per_row = tile16_blockset_.atlas.width() / 16;
374 const int tile_x = (tile16_id % tiles_per_row) * 16;
375 const int tile_y = (tile16_id / tiles_per_row) * 16;
376
377 std::vector<uint8_t> tile_data(16 * 16);
378 int tile_data_offset = 0;
379 tile16_blockset_.atlas.Get16x16Tile(tile_x, tile_y, tile_data,
380 tile_data_offset);
381
382 // Create or update cached tile
383 auto* cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id);
384 if (!cached_tile) {
385 // Create new cached tile
386 gfx::Bitmap new_tile(16, 16, 8, tile_data);
388 tile16_blockset_.tile_cache.CacheTile(tile16_id, std::move(new_tile));
389 cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id);
390 } else {
391 // Update existing cached tile data
392 cached_tile->set_data(tile_data);
393 }
394
395 if (cached_tile && cached_tile->is_active()) {
396 // Ensure the cached tile has a valid texture
397 if (!cached_tile->texture()) {
398 // Queue texture creation via Arena's deferred system
401 }
402 screen_canvas_.DrawBitmap(*cached_tile, pos.x, pos.y, 4.0F, 255);
403 }
404 }
405
406 // Draw overlays and labels
407 for (int j = 0; j < zelda3::kNumRooms; j++) {
408 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
409 int posX = ((j % 5) * 32);
410 int posY = ((j / 5) * 32);
411
412 if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
413 screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64, 64,
414 kRedPen);
415 }
416
417 std::string label =
419 screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
420 std::string gfx_id =
421 util::HexByte(current_dungeon.floor_gfx[floor_number][j]);
422 screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
423 }
424 }
425
426 screen_canvas_.DrawGrid(64.f, 5);
428
429 if (!screen_canvas_.points().empty()) {
430 int x = screen_canvas_.points().front().x / 64;
431 int y = screen_canvas_.points().front().y / 64;
432 selected_room = x + (y * 5);
433 }
434}
435
437 auto& current_dungeon = dungeon_maps_[selected_dungeon];
438 if (ImGui::BeginTabBar("##DungeonMapTabs")) {
439 auto nbr_floors =
440 current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
441 for (int i = 0; i < nbr_floors; i++) {
442 int basement_num = current_dungeon.nbr_of_basement - i;
443 std::string tab_name = absl::StrFormat("Basement %d", basement_num);
444 if (i >= current_dungeon.nbr_of_basement) {
445 tab_name = absl::StrFormat("Floor %d",
446 i - current_dungeon.nbr_of_basement + 1);
447 }
448 if (ImGui::BeginTabItem(tab_name.data())) {
450 ImGui::EndTabItem();
451 }
452 }
453 ImGui::EndTabBar();
454 }
455
457 "Selected Room",
458 &current_dungeon.floor_rooms[floor_number].at(selected_room));
459
460 gui::InputHexWord("Boss Room", &current_dungeon.boss_room);
461
462 const auto button_size = ImVec2(130, 0);
463
464 if (ImGui::Button("Add Floor", button_size) &&
465 current_dungeon.nbr_of_floor < 8) {
466 current_dungeon.nbr_of_floor++;
468 }
469 ImGui::SameLine();
470 if (ImGui::Button("Remove Floor", button_size) &&
471 current_dungeon.nbr_of_floor > 0) {
472 current_dungeon.nbr_of_floor--;
474 }
475
476 if (ImGui::Button("Add Basement", button_size) &&
477 current_dungeon.nbr_of_basement < 8) {
478 current_dungeon.nbr_of_basement++;
480 }
481 ImGui::SameLine();
482 if (ImGui::Button("Remove Basement", button_size) &&
483 current_dungeon.nbr_of_basement > 0) {
484 current_dungeon.nbr_of_basement--;
486 }
487
488 if (ImGui::Button("Copy Floor", button_size)) {
489 copy_button_pressed = true;
490 }
491 ImGui::SameLine();
492 if (ImGui::Button("Paste Floor", button_size)) {
494 }
495}
496
514 gfx::ScopedTimer timer("screen_editor_draw_dungeon_maps_room_gfx");
515
516 if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
517 // Enhanced tilesheet canvas with BeginCanvas/EndCanvas pattern
518 {
519 gui::CanvasFrameOptions tilesheet_opts;
520 tilesheet_opts.canvas_size = ImVec2((256 * 2) + 2, (192 * 2) + 4);
521 tilesheet_opts.draw_grid = true;
522 tilesheet_opts.grid_step = 32.0f;
523 tilesheet_opts.render_popups = true;
524
525 auto tilesheet_rt = gui::BeginCanvas(tilesheet_canvas_, tilesheet_opts);
526
527 // Interactive tile16 selector with grid snapping
528 ImVec2 selected_pos;
529 if (gui::DrawTileSelector(tilesheet_rt, 32, 0, &selected_pos)) {
530 // Double-click detected - handle tile confirmation if needed
531 }
532
533 // Check for single-click selection (legacy compatibility)
535 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
536 if (!tilesheet_canvas_.points().empty()) {
537 selected_tile16_ = static_cast<int>(
538 tilesheet_canvas_.points().front().x / 32 +
539 (tilesheet_canvas_.points().front().y / 32) * 16);
540
541 // Render selected tile16 and cache tile metadata
544 current_tile16_info.begin());
545 }
546 }
547
548 // Use stateless bitmap rendering for tilesheet
549 gui::DrawBitmap(tilesheet_rt, tile16_blockset_.atlas, 1, 1, 2.0F, 255);
550
551 gui::EndCanvas(tilesheet_canvas_, tilesheet_rt, tilesheet_opts);
552 }
553
554 if (!tilesheet_canvas_.points().empty() &&
555 !screen_canvas_.points().empty()) {
559 }
560
561 ImGui::Separator();
562
563 // Current tile canvas with BeginCanvas/EndCanvas pattern
564 {
565 gui::CanvasFrameOptions current_tile_opts;
566 current_tile_opts.draw_grid = true;
567 current_tile_opts.grid_step = 16.0f;
568 current_tile_opts.render_popups = true;
569
570 auto current_tile_rt =
571 gui::BeginCanvas(current_tile_canvas_, current_tile_opts);
572
573 // Get tile8 from cache on-demand (only create texture when needed)
574 if (selected_tile8_ >= 0 && selected_tile8_ < 256) {
575 auto* cached_tile8 = tile8_tilemap_.tile_cache.GetTile(selected_tile8_);
576
577 if (!cached_tile8) {
578 // Extract tile from atlas and cache it
579 const int tiles_per_row =
580 tile8_tilemap_.atlas.width() / 8; // 128 / 8 = 16
581 const int tile_x = (selected_tile8_ % tiles_per_row) * 8;
582 const int tile_y = (selected_tile8_ / tiles_per_row) * 8;
583
584 // Extract 8x8 tile data from atlas
585 std::vector<uint8_t> tile_data(64);
586 for (int py = 0; py < 8; py++) {
587 for (int px = 0; px < 8; px++) {
588 int src_x = tile_x + px;
589 int src_y = tile_y + py;
590 int src_index = src_y * tile8_tilemap_.atlas.width() + src_x;
591 int dst_index = py * 8 + px;
592
593 if (src_index < tile8_tilemap_.atlas.size() && dst_index < 64) {
594 tile_data[dst_index] = tile8_tilemap_.atlas.data()[src_index];
595 }
596 }
597 }
598
599 gfx::Bitmap new_tile8(8, 8, 8, tile_data);
602 std::move(new_tile8));
604 }
605
606 if (cached_tile8 && cached_tile8->is_active()) {
607 // Create texture on-demand only when needed
608 if (!cached_tile8->texture()) {
611 }
612
613 // DrawTilePainter still uses member function (not yet migrated)
614 if (current_tile_canvas_.DrawTilePainter(*cached_tile8, 16)) {
615 // Modify the tile16 based on the selected tile and
616 // current_tile16_info
617 gfx::ModifyTile16(tile16_blockset_, game_data()->graphics_buffer,
620 212, selected_tile16_);
622 }
623 }
624 }
625
626 // Get selected tile from cache and draw with stateless helper
627 auto* selected_tile =
629 if (selected_tile && selected_tile->is_active()) {
630 // Ensure the selected tile has a valid texture
631 if (!selected_tile->texture()) {
634 }
635 gui::DrawBitmap(current_tile_rt, *selected_tile, 2, 2, 4.0f, 255);
636 }
637
638 gui::EndCanvas(current_tile_canvas_, current_tile_rt, current_tile_opts);
639 }
640
642 ImGui::SameLine();
645 ImGui::SameLine();
647
648 if (ImGui::Button("Modify Tile16")) {
649 gfx::ModifyTile16(tile16_blockset_, game_data()->graphics_buffer,
654 }
655 }
656 ImGui::EndChild();
657}
658
676 // Enhanced editing mode controls with visual feedback
677 if (ImGui::Button(ICON_MD_DRAW)) {
679 }
680 ImGui::SameLine();
681 if (ImGui::Button(ICON_MD_EDIT)) {
683 }
684 ImGui::SameLine();
685 if (ImGui::Button(ICON_MD_SAVE)) {
687 }
688
689 static std::vector<std::string> dungeon_names = {
690 "Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
691 "Desert Palace", "Tower of Hera", "Agahnim's Tower",
692 "Palace of Darkness", "Swamp Palace", "Skull Woods",
693 "Thieves' Town", "Ice Palace", "Misery Mire",
694 "Turtle Rock", "Ganon's Tower"};
695
696 if (ImGui::BeginTable("DungeonMapsTable", 4,
697 ImGuiTableFlags_Resizable |
698 ImGuiTableFlags_Reorderable |
699 ImGuiTableFlags_Hideable)) {
700 ImGui::TableSetupColumn("Dungeon");
701 ImGui::TableSetupColumn("Map");
702 ImGui::TableSetupColumn("Rooms Gfx");
703 ImGui::TableSetupColumn("Tiles Gfx");
704 ImGui::TableHeadersRow();
705
706 ImGui::TableNextColumn();
707 for (int i = 0; i < dungeon_names.size(); i++) {
709 selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
710 dungeon_names[i]);
711 if (ImGui::IsItemClicked()) {
713 }
714 }
715
716 ImGui::TableNextColumn();
718
719 ImGui::TableNextColumn();
721
722 ImGui::TableNextColumn();
726 // Get the tile8 ID to use for the tile16 drawing above
728 }
732
733 ImGui::Text("Selected tile8: %d", selected_tile8_);
734 ImGui::Separator();
735 ImGui::Text("For use with custom inserted graphics assembly patches.");
736 if (ImGui::Button("Load GFX from BIN file"))
738
739 ImGui::EndTable();
740 }
741}
742
744 std::string bin_file = util::FileDialogWrapper::ShowOpenFileDialog();
745 if (!bin_file.empty()) {
746 std::ifstream file(bin_file, std::ios::binary);
747 if (file.is_open()) {
748 // Read the gfx data into a buffer
749 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
750 std::istreambuf_iterator<char>());
751 if (auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
753 converted_bin, true)
754 .ok()) {
755 sheets_.clear();
756 std::vector<std::vector<uint8_t>> gfx_sheets;
757 for (int i = 0; i < 4; i++) {
758 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
759 converted_bin.begin() + ((i + 1) * 0x1000));
760 sheets_[i] = std::make_unique<gfx::Bitmap>(128, 32, 8, gfx_sheets[i]);
761 sheets_[i]->SetPalette(
762 *game_data()->palette_groups.dungeon_main.mutable_palette(3));
763 // Queue texture creation via Arena's deferred system
766 }
767 binary_gfx_loaded_ = true;
768 } else {
769 status_ = absl::InternalError("Failed to load dungeon map tile16");
770 }
771 file.close();
772 }
773 }
774}
775
777 // Initialize title screen on first draw
778 if (!title_screen_loaded_ && rom()->is_loaded() && game_data()) {
780 if (!status_.ok()) {
781 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading title screen: %s",
782 status_.message().data());
783 return;
784 }
786 }
787
789 ImGui::Text("Title screen not loaded. Ensure ROM is loaded.");
790 return;
791 }
792
793 // Toolbar with mode controls
794 if (ImGui::Button(ICON_MD_DRAW)) {
796 }
797 ImGui::SameLine();
798 if (ImGui::Button(ICON_MD_SAVE)) {
800 if (status_.ok()) {
801 ImGui::OpenPopup("SaveSuccess");
802 }
803 }
804 ImGui::SameLine();
805 ImGui::Text("Selected Tile: %d", selected_title_tile16_);
806
807 // Save success popup
808 if (ImGui::BeginPopup("SaveSuccess")) {
809 ImGui::Text("Title screen saved successfully!");
810 ImGui::EndPopup();
811 }
812
813 // Layer visibility controls
814 bool prev_bg1 = show_title_bg1_;
815 bool prev_bg2 = show_title_bg2_;
816 ImGui::Checkbox("Show BG1", &show_title_bg1_);
817 ImGui::SameLine();
818 ImGui::Checkbox("Show BG2", &show_title_bg2_);
819
820 // Re-render composite if visibility changed
821 if (prev_bg1 != show_title_bg1_ || prev_bg2 != show_title_bg2_) {
822 status_ =
824 if (status_.ok()) {
828 }
829 }
830
831 // Layout: 2-column table (composite view + tile selector)
832 if (ImGui::BeginTable("TitleScreenTable", 2,
833 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
834 ImGui::TableSetupColumn("Title Screen (Composite)");
835 ImGui::TableSetupColumn("Tile Selector");
836 ImGui::TableHeadersRow();
837
838 // Column 1: Composite Canvas (BG1+BG2 stacked)
839 ImGui::TableNextColumn();
841
842 // Column 2: Blockset Selector
843 ImGui::TableNextColumn();
845
846 ImGui::EndTable();
847 }
848}
849
853
854 // Draw composite tilemap (BG1+BG2 stacked with transparency)
855 auto& composite_bitmap = title_screen_.composite_bitmap();
856 if (composite_bitmap.is_active()) {
857 title_bg1_canvas_.DrawBitmap(composite_bitmap, 0, 0, 2.0f, 255);
858 }
859
860 // Handle tile painting - always paint to BG1 layer
863 if (!title_bg1_canvas_.points().empty()) {
864 auto click_pos = title_bg1_canvas_.points().front();
865 int tile_x = static_cast<int>(click_pos.x) / 8;
866 int tile_y = static_cast<int>(click_pos.y) / 8;
867
868 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
869 int tilemap_index = tile_y * 32 + tile_x;
870
871 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
872 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
873 tile_word |= (title_palette_ & 0x07) << 10;
874 if (title_h_flip_)
875 tile_word |= 0x4000;
876 if (title_v_flip_)
877 tile_word |= 0x8000;
878
879 // Update BG1 buffer and re-render both layers and composite
880 title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word;
882 if (status_.ok()) {
883 // Update BG1 texture
887
888 // Re-render and update composite
891 if (status_.ok()) {
893 gfx::Arena::TextureCommandType::UPDATE, &composite_bitmap);
894 }
895 }
896 }
897 }
898 }
899 }
900
903}
904
908
909 // Draw BG1 tilemap
910 auto& bg1_bitmap = title_screen_.bg1_bitmap();
911 if (bg1_bitmap.is_active()) {
912 title_bg1_canvas_.DrawBitmap(bg1_bitmap, 0, 0, 2.0f, 255);
913 }
914
915 // Handle tile painting
918 if (!title_bg1_canvas_.points().empty()) {
919 auto click_pos = title_bg1_canvas_.points().front();
920 int tile_x = static_cast<int>(click_pos.x) / 8;
921 int tile_y = static_cast<int>(click_pos.y) / 8;
922
923 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
924 int tilemap_index = tile_y * 32 + tile_x;
925
926 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
927 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
928 tile_word |= (title_palette_ & 0x07) << 10;
929 if (title_h_flip_)
930 tile_word |= 0x4000;
931 if (title_v_flip_)
932 tile_word |= 0x8000;
933
934 // Update buffer and re-render
935 title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word;
937 if (status_.ok()) {
940 }
941 }
942 }
943 }
944 }
945
948}
949
953
954 // Draw BG2 tilemap
955 auto& bg2_bitmap = title_screen_.bg2_bitmap();
956 if (bg2_bitmap.is_active()) {
957 title_bg2_canvas_.DrawBitmap(bg2_bitmap, 0, 0, 2.0f, 255);
958 }
959
960 // Handle tile painting
963 if (!title_bg2_canvas_.points().empty()) {
964 auto click_pos = title_bg2_canvas_.points().front();
965 int tile_x = static_cast<int>(click_pos.x) / 8;
966 int tile_y = static_cast<int>(click_pos.y) / 8;
967
968 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
969 int tilemap_index = tile_y * 32 + tile_x;
970
971 // Create tile word: tile_id | (palette << 10) | h_flip | v_flip
972 uint16_t tile_word = selected_title_tile16_ & 0x3FF;
973 tile_word |= (title_palette_ & 0x07) << 10;
974 if (title_h_flip_)
975 tile_word |= 0x4000;
976 if (title_v_flip_)
977 tile_word |= 0x8000;
978
979 // Update buffer and re-render
980 title_screen_.mutable_bg2_buffer()[tilemap_index] = tile_word;
982 if (status_.ok()) {
985 }
986 }
987 }
988 }
989 }
990
993}
994
998
999 // Draw tile8 bitmap (8x8 tiles used to compose tile16)
1000 auto& tiles8_bitmap = title_screen_.tiles8_bitmap();
1001 if (tiles8_bitmap.is_active()) {
1002 title_blockset_canvas_.DrawBitmap(tiles8_bitmap, 0, 0, 2.0f, 255);
1003 }
1004
1005 // Handle tile selection (8x8 tiles)
1007 // Calculate selected tile ID from click position
1008 if (!title_blockset_canvas_.points().empty()) {
1009 auto click_pos = title_blockset_canvas_.points().front();
1010 int tile_x = static_cast<int>(click_pos.x) / 8;
1011 int tile_y = static_cast<int>(click_pos.y) / 8;
1012 int tiles_per_row = 128 / 8; // 16 tiles per row for 8x8 tiles
1013 selected_title_tile16_ = tile_x + (tile_y * tiles_per_row);
1014 }
1015 }
1016
1019
1020 // Show selected tile preview and controls
1021 if (selected_title_tile16_ >= 0) {
1022 ImGui::Text("Selected Tile: %d", selected_title_tile16_);
1023
1024 // Flip controls
1025 ImGui::Checkbox("H Flip", &title_h_flip_);
1026 ImGui::SameLine();
1027 ImGui::Checkbox("V Flip", &title_v_flip_);
1028
1029 // Palette selector (0-7 for 3BPP graphics)
1030 ImGui::SetNextItemWidth(100);
1031 ImGui::SliderInt("Palette", &title_palette_, 0, 7);
1032 }
1033}
1034
1036
1038 // Initialize overworld map on first draw
1039 if (!ow_map_loaded_ && rom()->is_loaded()) {
1041 if (!status_.ok()) {
1042 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading overworld map: %s",
1043 status_.message().data());
1044 return;
1045 }
1046 ow_map_loaded_ = true;
1047 }
1048
1049 if (!ow_map_loaded_) {
1050 ImGui::Text("Overworld map not loaded. Ensure ROM is loaded.");
1051 return;
1052 }
1053
1054 // Toolbar with mode controls
1055 if (ImGui::Button(ICON_MD_DRAW)) {
1057 }
1058 ImGui::SameLine();
1059 if (ImGui::Button(ICON_MD_SAVE)) {
1061 if (status_.ok()) {
1062 ImGui::OpenPopup("OWSaveSuccess");
1063 }
1064 }
1065 ImGui::SameLine();
1066
1067 // World toggle
1068 if (ImGui::Button(ow_show_dark_world_ ? "Dark World" : "Light World")) {
1070 // Re-render map with new world
1072 if (status_.ok()) {
1075 }
1076 }
1077 ImGui::SameLine();
1078
1079 // Custom map load/save buttons
1080 if (ImGui::Button("Load Custom Map...")) {
1082 if (!path.empty()) {
1084 if (!status_.ok()) {
1085 ImGui::OpenPopup("CustomMapLoadError");
1086 }
1087 }
1088 }
1089 ImGui::SameLine();
1090 if (ImGui::Button("Save Custom Map...")) {
1092 if (!path.empty()) {
1094 if (status_.ok()) {
1095 ImGui::OpenPopup("CustomMapSaveSuccess");
1096 }
1097 }
1098 }
1099
1100 ImGui::SameLine();
1101 ImGui::Text("Selected Tile: %d", selected_ow_tile_);
1102
1103 // Custom map error/success popups
1104 if (ImGui::BeginPopup("CustomMapLoadError")) {
1105 ImGui::Text("Error loading custom map: %s", status_.message().data());
1106 ImGui::EndPopup();
1107 }
1108 if (ImGui::BeginPopup("CustomMapSaveSuccess")) {
1109 ImGui::Text("Custom map saved successfully!");
1110 ImGui::EndPopup();
1111 }
1112
1113 // Save success popup
1114 if (ImGui::BeginPopup("OWSaveSuccess")) {
1115 ImGui::Text("Overworld map saved successfully!");
1116 ImGui::EndPopup();
1117 }
1118
1119 // Layout: 3-column table
1120 if (ImGui::BeginTable("OWMapTable", 3,
1121 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
1122 ImGui::TableSetupColumn("Map Canvas");
1123 ImGui::TableSetupColumn("Tileset");
1124 ImGui::TableSetupColumn("Palette");
1125 ImGui::TableHeadersRow();
1126
1127 // Column 1: Map Canvas
1128 ImGui::TableNextColumn();
1131
1132 auto& map_bitmap = ow_map_screen_.map_bitmap();
1133 if (map_bitmap.is_active()) {
1134 ow_map_canvas_.DrawBitmap(map_bitmap, 0, 0, 1.0f, 255);
1135 }
1136
1137 // Handle tile painting
1139 if (ow_map_canvas_.DrawTileSelector(8.0f)) {
1140 if (!ow_map_canvas_.points().empty()) {
1141 auto click_pos = ow_map_canvas_.points().front();
1142 int tile_x = static_cast<int>(click_pos.x) / 8;
1143 int tile_y = static_cast<int>(click_pos.y) / 8;
1144
1145 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1146 int tile_index = tile_x + (tile_y * 64);
1147
1148 // Update appropriate world's tile data
1149 if (ow_show_dark_world_) {
1151 } else {
1153 }
1154
1155 // Re-render map
1157 if (status_.ok()) {
1160 }
1161 }
1162 }
1163 }
1164 }
1165
1168
1169 // Column 2: Tileset Selector
1170 ImGui::TableNextColumn();
1173
1174 auto& tiles8_bitmap = ow_map_screen_.tiles8_bitmap();
1175 if (tiles8_bitmap.is_active()) {
1176 ow_tileset_canvas_.DrawBitmap(tiles8_bitmap, 0, 0, 2.0f, 255);
1177 }
1178
1179 // Handle tile selection
1181 if (!ow_tileset_canvas_.points().empty()) {
1182 auto click_pos = ow_tileset_canvas_.points().front();
1183 int tile_x = static_cast<int>(click_pos.x) / 8;
1184 int tile_y = static_cast<int>(click_pos.y) / 8;
1185 selected_ow_tile_ = tile_x + (tile_y * 16); // 16 tiles per row
1186 }
1187 }
1188
1191
1192 // Column 3: Palette Display
1193 ImGui::TableNextColumn();
1196 // Use inline palette editor for full 128-color palette
1197 gui::InlinePaletteEditor(palette, "Overworld Map Palette");
1198
1199 ImGui::EndTable();
1200 }
1201}
1202
1204 static bool show_bg1 = true;
1205 static bool show_bg2 = true;
1206 static bool show_bg3 = true;
1207
1208 static bool drawing_bg1 = true;
1209 static bool drawing_bg2 = false;
1210 static bool drawing_bg3 = false;
1211
1212 ImGui::Checkbox("Show BG1", &show_bg1);
1213 ImGui::SameLine();
1214 ImGui::Checkbox("Show BG2", &show_bg2);
1215
1216 ImGui::Checkbox("Draw BG1", &drawing_bg1);
1217 ImGui::SameLine();
1218 ImGui::Checkbox("Draw BG2", &drawing_bg2);
1219 ImGui::SameLine();
1220 ImGui::Checkbox("Draw BG3", &drawing_bg3);
1221}
1222
1223} // namespace editor
1224} // 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:35
static Arena & Get()
Definition arena.cc:20
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:201
auto size() const
Definition bitmap.h:376
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:853
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:384
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:692
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:132
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:119
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:236
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:41
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:1310
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89