yaze 0.2.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_editor.cc
Go to the documentation of this file.
1#include "overworld_editor.h"
2
3#include <cmath>
4#include <future>
5#include <unordered_map>
6#include <vector>
7
8#include "absl/status/status.h"
9#include "absl/strings/str_format.h"
10#include "app/core/features.h"
15#include "app/gfx/bitmap.h"
17#include "app/gui/canvas.h"
18#include "app/gui/icons.h"
19#include "app/gui/input.h"
20#include "app/gui/style.h"
21#include "app/gui/zeml.h"
22#include "app/rom.h"
23#include "app/zelda3/common.h"
25#include "imgui/imgui.h"
26#include "imgui_memory_editor.h"
27#include "util/hex.h"
28#include "util/log.h"
29#include "util/macro.h"
30
31namespace yaze {
32namespace editor {
33
34using core::Renderer;
35using namespace ImGui;
36
37constexpr int kTile16Size = 0x10;
38
40 // Load zeml string from layouts/overworld.zeml
41 std::string layout = gui::zeml::LoadFile("overworld.zeml");
42 // Parse the zeml string into a Node object
44
45 gui::zeml::Bind(&*layout_node_.GetNode("OverworldCanvas"),
46 [this]() { DrawOverworldCanvas(); });
47 gui::zeml::Bind(&*layout_node_.GetNode("OverworldTileSelector"),
48 [this]() { status_ = DrawTileSelector(); });
49 gui::zeml::Bind(&*layout_node_.GetNode("OwUsageStats"), [this]() {
50 if (rom_->is_loaded()) {
51 status_ = UpdateUsageStats();
52 }
53 });
54 gui::zeml::Bind(&*layout_node_.GetNode("owToolset"),
55 [this]() { DrawToolset(); });
56 gui::zeml::Bind(&*layout_node_.GetNode("OwTile16Editor"), [this]() {
57 if (rom_->is_loaded()) {
58 status_ = tile16_editor_.Update();
59 }
60 });
61 gui::zeml::Bind(&*layout_node_.GetNode("OwGfxGroupEditor"), [this]() {
62 if (rom_->is_loaded()) {
63 status_ = gfx_group_editor_.Update();
64 }
65 });
66}
67
68absl::Status OverworldEditor::Load() {
72 *overworld_.mutable_all_tiles_types()));
74 all_gfx_loaded_ = true;
75 return absl::OkStatus();
76}
77
79 status_ = absl::OkStatus();
80
83 }
84
85 // Draw the overworld editor layout from the ZEML file
87 return status_;
88}
89
91 static bool use_work_area = true;
92 static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration |
93 ImGuiWindowFlags_NoMove |
94 ImGuiWindowFlags_NoSavedSettings;
95 const ImGuiViewport *viewport = ImGui::GetMainViewport();
96 ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos);
97 ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size);
98 if (ImGui::Begin("Fullscreen Overworld Editor", &overworld_canvas_fullscreen_,
99 flags)) {
100 // Draws the toolset for editing the Overworld.
101 DrawToolset();
103 }
104 ImGui::End();
105}
106
108 static bool show_gfx_group = false;
109 static bool show_properties = false;
110
111 if (toolset_table_.column_contents.empty()) {
112 gui::AddTableColumn(toolset_table_, "##Undo", [&]() {
113 if (Button(ICON_MD_UNDO)) status_ = Undo();
114 });
115 gui::AddTableColumn(toolset_table_, "##Redo", [&]() {
116 if (Button(ICON_MD_REDO)) status_ = Redo();
117 });
119 gui::AddTableColumn(toolset_table_, "##ZoomOut", [&]() {
120 if (Button(ICON_MD_ZOOM_OUT)) ow_map_canvas_.ZoomOut();
121 });
122 gui::AddTableColumn(toolset_table_, "##ZoomIn", [&]() {
123 if (Button(ICON_MD_ZOOM_IN)) ow_map_canvas_.ZoomIn();
124 });
125 gui::AddTableColumn(toolset_table_, "##Fullscreen", [&]() {
126 if (Button(ICON_MD_OPEN_IN_FULL))
128 HOVER_HINT("Fullscreen Canvas")
129 });
131 gui::AddTableColumn(toolset_table_, "##Pan", [&]() {
134 ow_map_canvas_.set_draggable(true);
135 }
136 HOVER_HINT("Pan (Right click and drag)");
137 });
138 gui::AddTableColumn(toolset_table_, "##DrawTile", [&]() {
141 }
142 HOVER_HINT("Draw Tile");
143 });
144 gui::AddTableColumn(toolset_table_, "##Entrances", [&]() {
145 if (Selectable(ICON_MD_DOOR_FRONT,
148 HOVER_HINT("Entrances");
149 });
150 gui::AddTableColumn(toolset_table_, "##Exits", [&]() {
153 HOVER_HINT("Exits");
154 });
155 gui::AddTableColumn(toolset_table_, "##Items", [&]() {
158 HOVER_HINT("Items");
159 });
160 gui::AddTableColumn(toolset_table_, "##Sprites", [&]() {
161 if (Selectable(ICON_MD_PEST_CONTROL_RODENT,
164 HOVER_HINT("Sprites");
165 });
166 gui::AddTableColumn(toolset_table_, "##Transports", [&]() {
167 if (Selectable(ICON_MD_ADD_LOCATION,
170 HOVER_HINT("Transports");
171 });
172 gui::AddTableColumn(toolset_table_, "##Music", [&]() {
175 HOVER_HINT("Music");
176 });
177 gui::AddTableColumn(toolset_table_, "##Tile16Editor", [&]() {
179 HOVER_HINT("Tile16 Editor");
180 });
181 gui::AddTableColumn(toolset_table_, "##GfxGroupEditor", [&]() {
182 if (Button(ICON_MD_TABLE_CHART)) show_gfx_group = !show_gfx_group;
183 HOVER_HINT("Gfx Group Editor");
184 });
186 gui::AddTableColumn(toolset_table_, "##Properties", [&]() {
187 if (Button(ICON_MD_CONTENT_COPY)) {
188 std::vector<uint8_t> png_data;
189 png_data = maps_bmp_[current_map_].GetPngData();
190 if (png_data.size() > 0) {
192 } else {
193 status_ = absl::InternalError(
194 "Failed to convert overworld map surface to PNG");
195 }
196 }
197 HOVER_HINT("Copy Map to Clipboard");
198 });
199 gui::AddTableColumn(toolset_table_, "##Palette", [&]() {
201 });
203 gui::AddTableColumn(toolset_table_, "##Properties",
204 [&]() { Checkbox("Properties", &show_properties); });
205
206 } else {
208 }
209
211 ImGui::Begin("Tile16 Editor", &show_tile16_editor_,
212 ImGuiWindowFlags_MenuBar);
213 status_ = tile16_editor_.Update();
214 ImGui::End();
215 }
216
217 if (show_gfx_group) {
218 gui::BeginWindowWithDisplaySettings("Gfx Group Editor", &show_gfx_group);
219 status_ = gfx_group_editor_.Update();
221 }
222
223 if (show_properties) {
224 ImGui::Begin("Properties", &show_properties);
226 ImGui::End();
227 }
228
229 // TODO: Customizable shortcuts for the Overworld Editor
230 if (!ImGui::IsAnyItemActive()) {
231 if (ImGui::IsKeyDown(ImGuiKey_1)) {
233 } else if (ImGui::IsKeyDown(ImGuiKey_2)) {
235 } else if (ImGui::IsKeyDown(ImGuiKey_3)) {
237 } else if (ImGui::IsKeyDown(ImGuiKey_4)) {
239 } else if (ImGui::IsKeyDown(ImGuiKey_5)) {
241 } else if (ImGui::IsKeyDown(ImGuiKey_6)) {
243 } else if (ImGui::IsKeyDown(ImGuiKey_7)) {
245 } else if (ImGui::IsKeyDown(ImGuiKey_8)) {
247 }
248 }
249}
250
251constexpr std::array<const char *, 8> kMapSettingsColumnNames = {
252 "##WorldId", "##GfxId", "##PalId", "##SprGfxId",
253 "##5thCol", "##6thCol", "##7thCol", "##8thCol"};
254
256 if (BeginTable(kOWMapTable.data(), 8, kOWMapFlags, ImVec2(0, 0), -1)) {
257 for (const auto &name : kMapSettingsColumnNames)
258 ImGui::TableSetupColumn(name);
259
260 TableNextColumn();
261 ImGui::SetNextItemWidth(120.f);
262 ImGui::Combo("##world", &current_world_, kWorldList.data(), 3);
263
264 TableNextColumn();
265 ImGui::BeginGroup();
266 if (gui::InputHexByte("Gfx",
267 overworld_.mutable_overworld_map(current_map_)
268 ->mutable_area_graphics(),
272 }
273 ImGui::EndGroup();
274
275 TableNextColumn();
276 ImGui::BeginGroup();
277 if (gui::InputHexByte("Palette",
278 overworld_.mutable_overworld_map(current_map_)
279 ->mutable_area_palette(),
284 }
285 ImGui::EndGroup();
286
287 TableNextColumn();
288 ImGui::BeginGroup();
289 gui::InputHexByte("Spr Gfx",
290 overworld_.mutable_overworld_map(current_map_)
291 ->mutable_sprite_graphics(game_state_),
293 ImGui::EndGroup();
294
295 TableNextColumn();
296 ImGui::BeginGroup();
297 gui::InputHexByte("Spr Palette",
298 overworld_.mutable_overworld_map(current_map_)
299 ->mutable_sprite_palette(game_state_),
301 ImGui::EndGroup();
302
303 TableNextColumn();
304 ImGui::BeginGroup();
306 "Msg Id",
307 overworld_.mutable_overworld_map(current_map_)->mutable_message_id(),
308 kInputFieldSize + 20);
309 ImGui::EndGroup();
310
311 TableNextColumn();
312 ImGui::SetNextItemWidth(100.f);
313 ImGui::Combo("##World", &game_state_, kGamePartComboString.data(), 3);
314
315 TableNextColumn();
316 ImGui::Checkbox(
317 "##mosaic",
318 overworld_.mutable_overworld_map(current_map_)->mutable_mosaic());
319 HOVER_HINT("Enable Mosaic effect for the current map");
320
321 ImGui::EndTable();
322 }
323}
324
326 if (BeginTable(kOWMapTable.data(), 15, kOWMapFlags, ImVec2(0, 0), -1)) {
327 for (const auto &name : kMapSettingsColumnNames)
328 ImGui::TableSetupColumn(name);
329
330 TableNextColumn();
331 ImGui::SetNextItemWidth(120.f);
332 ImGui::Combo("##world", &current_world_, kWorldList.data(), 3);
333
334 static const std::array<std::string, 8> kCustomMapSettingsColumnNames = {
335 "TileGfx0", "TileGfx1", "TileGfx2", "TileGfx3",
336 "TileGfx4", "TileGfx5", "TileGfx6", "TileGfx7"};
337 for (int i = 0; i < 8; ++i) {
338 TableNextColumn();
339 ImGui::BeginGroup();
340 if (gui::InputHexByte(kCustomMapSettingsColumnNames[i].data(),
341 overworld_.mutable_overworld_map(current_map_)
342 ->mutable_custom_tileset(i),
346 }
347 ImGui::EndGroup();
348 }
349
350 TableNextColumn();
351 ImGui::BeginGroup();
352 if (gui::InputHexByte("Palette",
353 overworld_.mutable_overworld_map(current_map_)
354 ->mutable_area_palette(),
359 }
360 ImGui::EndGroup();
361
362 TableNextColumn();
363 ImGui::BeginGroup();
364 gui::InputHexByte("Spr Gfx",
365 overworld_.mutable_overworld_map(current_map_)
366 ->mutable_sprite_graphics(game_state_),
368 ImGui::EndGroup();
369
370 TableNextColumn();
371 ImGui::BeginGroup();
372 gui::InputHexByte("Spr Palette",
373 overworld_.mutable_overworld_map(current_map_)
374 ->mutable_sprite_palette(game_state_),
376 ImGui::EndGroup();
377
378 TableNextColumn();
379 ImGui::BeginGroup();
381 "Msg Id",
382 overworld_.mutable_overworld_map(current_map_)->mutable_message_id(),
383 kInputFieldSize + 20);
384 ImGui::EndGroup();
385
386 TableNextColumn();
387 ImGui::SetNextItemWidth(100.f);
388 ImGui::Combo("##World", &game_state_, kGamePartComboString.data(), 3);
389
390 TableNextColumn();
391 ImGui::Checkbox(
392 "##mosaic",
393 overworld_.mutable_overworld_map(current_map_)->mutable_mosaic());
394 HOVER_HINT("Enable Mosaic effect for the current map");
395
396 ImGui::EndTable();
397 }
398}
399
401 int xx = 0;
402 int yy = 0;
403 for (int i = 0; i < 0x40; i++) {
404 int world_index = i + (current_world_ * 0x40);
405 int map_x = (xx * kOverworldMapSize * ow_map_canvas_.global_scale());
406 int map_y = (yy * kOverworldMapSize * ow_map_canvas_.global_scale());
407 ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y,
408 ow_map_canvas_.global_scale());
409 xx++;
410 if (xx >= 8) {
411 yy++;
412 xx = 0;
413 }
414 }
415}
416
418 // Determine which overworld map the user is currently editing.
419 auto mouse_position = ow_map_canvas_.drawn_tile_position();
420 int map_x = mouse_position.x / kOverworldMapSize;
421 int map_y = mouse_position.y / kOverworldMapSize;
422 current_map_ = map_x + map_y * 8;
423 if (current_world_ == 1) {
424 current_map_ += 0x40;
425 } else if (current_world_ == 2) {
426 current_map_ += 0x80;
427 }
428
429 // Render the updated map bitmap.
430 RenderUpdatedMapBitmap(mouse_position,
432
433 // Calculate the correct superX and superY values
434 int superY = current_map_ / 8;
435 int superX = current_map_ % 8;
436 int mouse_x = mouse_position.x;
437 int mouse_y = mouse_position.y;
438 // Calculate the correct tile16_x and tile16_y positions
439 int tile16_x = (mouse_x % kOverworldMapSize) / (kOverworldMapSize / 32);
440 int tile16_y = (mouse_y % kOverworldMapSize) / (kOverworldMapSize / 32);
441
442 // Update the overworld_.map_tiles() based on tile16 ID and current world
443 auto &selected_world =
444 (current_world_ == 0) ? overworld_.mutable_map_tiles()->light_world
445 : (current_world_ == 1) ? overworld_.mutable_map_tiles()->dark_world
446 : overworld_.mutable_map_tiles()->special_world;
447
448 int index_x = superX * 32 + tile16_x;
449 int index_y = superY * 32 + tile16_y;
450
451 selected_world[index_x][index_y] = current_tile16_;
452}
453
455 const ImVec2 &click_position, const std::vector<uint8_t> &tile_data) {
456 // Calculate the tile index for x and y based on the click_position
457 int tile_index_x =
458 (static_cast<int>(click_position.x) % kOverworldMapSize) / kTile16Size;
459 int tile_index_y =
460 (static_cast<int>(click_position.y) % kOverworldMapSize) / kTile16Size;
461
462 // Calculate the pixel start position based on tile index and tile size
463 ImVec2 start_position;
464 start_position.x = tile_index_x * kTile16Size;
465 start_position.y = tile_index_y * kTile16Size;
466
467 // Update the bitmap's pixel data based on the start_position and tile_data
468 gfx::Bitmap &current_bitmap = maps_bmp_[current_map_];
469 for (int y = 0; y < kTile16Size; ++y) {
470 for (int x = 0; x < kTile16Size; ++x) {
471 int pixel_index =
472 (start_position.y + y) * kOverworldMapSize + (start_position.x + x);
473 current_bitmap.WriteToPixel(pixel_index, tile_data[y * kTile16Size + x]);
474 }
475 }
476
477 current_bitmap.set_modified(true);
478}
479
482
483 // User has selected a tile they want to draw from the blockset.
484 if (!blockset_canvas_.points().empty() &&
485 !ow_map_canvas_.select_rect_active()) {
486 // Left click is pressed
488 kTile16Size)) {
490 }
491 }
492
493 if (ow_map_canvas_.select_rect_active()) {
494 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
495 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
496 auto &selected_world =
497 (current_world_ == 0) ? overworld_.mutable_map_tiles()->light_world
498 : (current_world_ == 1)
499 ? overworld_.mutable_map_tiles()->dark_world
500 : overworld_.mutable_map_tiles()->special_world;
501 // new_start_pos and new_end_pos
502 auto start = ow_map_canvas_.selected_points()[0];
503 auto end = ow_map_canvas_.selected_points()[1];
504
505 // Calculate the bounds of the rectangle in terms of 16x16 tile indices
506 int start_x = std::floor(start.x / kTile16Size) * kTile16Size;
507 int start_y = std::floor(start.y / kTile16Size) * kTile16Size;
508 int end_x = std::floor(end.x / kTile16Size) * kTile16Size;
509 int end_y = std::floor(end.y / kTile16Size) * kTile16Size;
510
511 if (start_x > end_x) std::swap(start_x, end_x);
512 if (start_y > end_y) std::swap(start_y, end_y);
513
514 constexpr int local_map_size = 512; // Size of each local map
515 // Number of tiles per local map (since each tile is 16x16)
516 constexpr int tiles_per_local_map = local_map_size / kTile16Size;
517
518 for (int y = start_y, i = 0; y <= end_y; y += kTile16Size) {
519 for (int x = start_x; x <= end_x; x += kTile16Size, ++i) {
520 // Determine which local map (512x512) the tile is in
521 int local_map_x = x / local_map_size;
522 int local_map_y = y / local_map_size;
523
524 // Calculate the tile's position within its local map
525 int tile16_x = (x % local_map_size) / kTile16Size;
526 int tile16_y = (y % local_map_size) / kTile16Size;
527
528 // Calculate the index within the overall map structure
529 int index_x = local_map_x * tiles_per_local_map + tile16_x;
530 int index_y = local_map_y * tiles_per_local_map + tile16_y;
531 int tile16_id = overworld_.GetTileFromPosition(
532 ow_map_canvas_.selected_tiles()[i]);
533 selected_world[index_x][index_y] = tile16_id;
534 }
535 }
536
538 }
539 }
540}
541
543 ow_map_canvas_.DrawSelectRect(current_map_);
544
545 // Single tile case
546 if (ow_map_canvas_.selected_tile_pos().x != -1) {
548 overworld_.GetTileFromPosition(ow_map_canvas_.selected_tile_pos());
549 ow_map_canvas_.set_selected_tile_pos(ImVec2(-1, -1));
550 }
551
552 static std::vector<int> tile16_ids;
553 if (ow_map_canvas_.select_rect_active()) {
554 // Get the tile16 IDs from the selected tile ID positions
555 if (tile16_ids.size() != 0) {
556 tile16_ids.clear();
557 }
558
559 if (ow_map_canvas_.selected_tiles().size() > 0) {
560 for (auto &each : ow_map_canvas_.selected_tiles()) {
561 tile16_ids.push_back(overworld_.GetTileFromPosition(each));
562 }
563 }
564 }
565 // Create a composite image of all the tile16s selected
566 ow_map_canvas_.DrawBitmapGroup(tile16_ids, tile16_individual_, 0x10);
567}
568
570 // 4096x4096, 512x512 maps and some are larges maps 1024x1024
571 const auto mouse_position = ImGui::GetIO().MousePos;
572 const int large_map_size = 1024;
573 const auto canvas_zero_point = ow_map_canvas_.zero_point();
574
575 // Calculate which small map the mouse is currently over
576 int map_x = (mouse_position.x - canvas_zero_point.x) / kOverworldMapSize;
577 int map_y = (mouse_position.y - canvas_zero_point.y) / kOverworldMapSize;
578
579 // Calculate the index of the map in the `maps_bmp_` vector
580 current_map_ = map_x + map_y * 8;
581 const int current_highlighted_map = current_map_;
582 if (current_world_ == 1) {
583 current_map_ += 0x40;
584 } else if (current_world_ == 2) {
585 current_map_ += 0x80;
586 }
587
588 current_parent_ = overworld_.overworld_map(current_map_)->parent();
589
590 if (overworld_.overworld_map(current_map_)->is_large_map() ||
591 overworld_.overworld_map(current_map_)->large_index() != 0) {
592 const int highlight_parent =
593 overworld_.overworld_map(current_highlighted_map)->parent();
594 const int parent_map_x = highlight_parent % 8;
595 const int parent_map_y = highlight_parent / 8;
596 ow_map_canvas_.DrawOutline(parent_map_x * kOverworldMapSize,
597 parent_map_y * kOverworldMapSize, large_map_size,
598 large_map_size);
599 } else {
600 const int current_map_x = current_highlighted_map % 8;
601 const int current_map_y = current_highlighted_map / 8;
602 ow_map_canvas_.DrawOutline(current_map_x * kOverworldMapSize,
603 current_map_y * kOverworldMapSize,
605 }
606
607 if (maps_bmp_[current_map_].modified() ||
608 ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
612 maps_bmp_[current_map_].set_modified(false);
613 }
614
615 return absl::OkStatus();
616}
617
619 if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle)) {
622 ow_map_canvas_.set_draggable(true);
624 }
625 if (ImGui::IsMouseReleased(ImGuiMouseButton_Middle) &&
628 ow_map_canvas_.set_draggable(false);
630 }
631}
632
634 if (all_gfx_loaded_) {
635 if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) {
637 } else {
639 }
640 Separator();
641 }
642
645 ow_map_canvas_.DrawBackground();
647
650 ow_map_canvas_.DrawContextMenu();
651 } else {
652 ow_map_canvas_.set_draggable(false);
653 }
654
655 if (overworld_.is_loaded()) {
657 DrawOverworldExits(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
659 ow_map_canvas_.scrolling());
664 }
665 if (IsItemHovered()) status_ = CheckForCurrentMap();
666 }
667
668 ow_map_canvas_.DrawGrid();
669 ow_map_canvas_.DrawOverlay();
670 EndChild();
671
672 // Handle mouse wheel activity
673 if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) &&
674 ImGui::IsMouseDragging(ImGuiMouseButton_Middle)) {
675 ImGui::SetScrollX(ImGui::GetScrollX() + ImGui::GetIO().MouseWheelH * 16.0f);
676 ImGui::SetScrollY(ImGui::GetScrollY() + ImGui::GetIO().MouseWheel * 16.0f);
677 }
678}
679
682 ImGui::BeginGroup();
683 gui::BeginChildWithScrollbar("##Tile16SelectorScrollRegion");
684 blockset_canvas_.DrawBackground();
686 {
687 blockset_canvas_.DrawContextMenu();
688 blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, /*border_offset=*/2,
690
691 if (blockset_canvas_.DrawTileSelector(32.0f)) {
692 // Open the tile16 editor to the tile
693 auto tile_pos = blockset_canvas_.points().front();
694 int grid_x = static_cast<int>(tile_pos.x / 32);
695 int grid_y = static_cast<int>(tile_pos.y / 32);
696 int id = grid_x + grid_y * 8;
697 RETURN_IF_ERROR(tile16_editor_.SetCurrentTile(id));
698 show_tile16_editor_ = true;
699 }
700
701 if (ImGui::IsItemClicked() && !blockset_canvas_.points().empty()) {
702 int x = blockset_canvas_.points().front().x / 32;
703 int y = blockset_canvas_.points().front().y / 32;
704 current_tile16_ = x + (y * 8);
705 }
706
707 blockset_canvas_.DrawGrid();
708 blockset_canvas_.DrawOverlay();
709 }
710 EndChild();
711 ImGui::EndGroup();
712 return absl::OkStatus();
713}
714
716 graphics_bin_canvas_.DrawBackground();
717 graphics_bin_canvas_.DrawContextMenu();
718 if (all_gfx_loaded_) {
719 int key = 0;
720 for (auto &value : GraphicsSheetManager::GetInstance().gfx_sheets()) {
721 int offset = 0x40 * (key + 1);
722 int top_left_y = graphics_bin_canvas_.zero_point().y + 2;
723 if (key >= 1) {
724 top_left_y = graphics_bin_canvas_.zero_point().y + 0x40 * key;
725 }
726 auto texture = value.texture();
727 graphics_bin_canvas_.draw_list()->AddImage(
728 (ImTextureID)(intptr_t)texture,
729 ImVec2(graphics_bin_canvas_.zero_point().x + 2, top_left_y),
730 ImVec2(graphics_bin_canvas_.zero_point().x + 0x100,
731 graphics_bin_canvas_.zero_point().y + offset));
732 key++;
733 }
734 }
735 graphics_bin_canvas_.DrawGrid();
736 graphics_bin_canvas_.DrawOverlay();
737}
738
740 if (overworld_.is_loaded() &&
742 overworld_.set_current_map(current_map_);
743 palette_ = overworld_.current_area_palette();
744 gfx::Bitmap bmp;
745 Renderer::GetInstance().CreateAndRenderBitmap(0x80, kOverworldMapSize, 0x08,
746 overworld_.current_graphics(),
747 bmp, palette_);
749 }
750
752 ImGui::BeginGroup();
753 gui::BeginChildWithScrollbar("##AreaGraphicsScrollRegion");
754 current_gfx_canvas_.DrawBackground();
756 {
757 current_gfx_canvas_.DrawContextMenu();
759 /*border_offset=*/2, overworld_.is_loaded());
760 current_gfx_canvas_.DrawTileSelector(32.0f);
761 current_gfx_canvas_.DrawGrid();
762 current_gfx_canvas_.DrawOverlay();
763 }
764 EndChild();
765 ImGui::EndGroup();
766 return absl::OkStatus();
767}
768
770 if (BeginTabBar(kTileSelectorTab.data(),
771 ImGuiTabBarFlags_FittingPolicyScroll)) {
772 if (BeginTabItem("Tile16")) {
774 EndTabItem();
775 }
776 if (BeginTabItem("Tile8")) {
778 gui::BeginChildWithScrollbar("##Tile8SelectorScrollRegion");
780 EndChild();
782 EndTabItem();
783 }
784 if (BeginTabItem("Area Graphics")) {
786 EndTabItem();
787 }
788 EndTabBar();
789 }
790 return absl::OkStatus();
791}
792
793void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling,
794 bool holes) {
795 int i = 0;
796 for (auto &each : overworld_.entrances()) {
797 if (each.map_id_ < 0x40 + (current_world_ * 0x40) &&
798 each.map_id_ >= (current_world_ * 0x40) && !each.deleted) {
799 auto color = ImVec4(255, 255, 0, 100);
800 if (each.is_hole_) {
801 color = ImVec4(255, 255, 255, 200);
802 }
803 ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, color);
804 std::string str = util::HexByte(each.entrance_id_);
805
807 HandleEntityDragging(&each, canvas_p0, scrolling, is_dragging_entity_,
809
810 if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
811 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
812 jump_to_tab_ = each.entrance_id_;
813 }
814
815 if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
816 ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
818 current_entrance_ = each;
819 }
820 }
821
822 ow_map_canvas_.DrawText(str, each.x_, each.y_);
823 }
824 i++;
825 }
826
828 // Get the deleted entrance ID and insert it at the mouse position
829 auto deleted_entrance_id = overworld_.deleted_entrances().back();
830 overworld_.deleted_entrances().pop_back();
831 auto &entrance = overworld_.entrances()[deleted_entrance_id];
832 entrance.map_id_ = current_map_;
833 entrance.entrance_id_ = deleted_entrance_id;
834 entrance.x_ = ow_map_canvas_.hover_mouse_pos().x;
835 entrance.y_ = ow_map_canvas_.hover_mouse_pos().y;
836 entrance.deleted = false;
837 }
838
840 const auto is_hovering =
841 IsMouseHoveringOverEntity(current_entrance_, canvas_p0, scrolling);
842
843 if (!is_hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
844 ImGui::OpenPopup("Entrance Inserter");
845 } else {
847 overworld_.entrances()[current_entrance_id_])) {
849 }
850
851 if (overworld_.entrances()[current_entrance_id_].deleted) {
852 overworld_.mutable_deleted_entrances()->emplace_back(
854 }
855 }
856 }
857}
858
859void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) {
860 int i = 0;
861 for (auto &each : *overworld_.mutable_exits()) {
862 if (each.map_id_ < 0x40 + (current_world_ * 0x40) &&
863 each.map_id_ >= (current_world_ * 0x40) && !each.deleted_) {
864 ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16,
865 ImVec4(255, 255, 255, 150));
867 each.entity_id_ = i;
868 HandleEntityDragging(&each, ow_map_canvas_.zero_point(),
871
872 if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
873 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
874 jump_to_tab_ = each.room_id_;
875 }
876
877 if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
878 ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
880 current_exit_ = each;
881 current_entity_ = &each;
882 current_entity_->entity_id_ = i;
883 ImGui::OpenPopup("Exit editor");
884 }
885 }
886
887 std::string str = util::HexByte(i);
888 ow_map_canvas_.DrawText(str, each.x_, each.y_);
889 }
890 i++;
891 }
892
895 const auto hovering = IsMouseHoveringOverEntity(
896 overworld_.mutable_exits()->at(current_exit_id_),
897 ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
898
899 if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
900 ImGui::OpenPopup("Exit Inserter");
901 } else {
903 overworld_.mutable_exits()->at(current_exit_id_))) {
904 overworld_.mutable_exits()->at(current_exit_id_) = current_exit_;
905 }
906 }
907 }
908}
909
911 int i = 0;
912 for (auto &item : *overworld_.mutable_all_items()) {
913 // Get the item's bitmap and real X and Y positions
914 if (item.room_map_id_ < 0x40 + (current_world_ * 0x40) &&
915 item.room_map_id_ >= (current_world_ * 0x40) && !item.deleted) {
916 ow_map_canvas_.DrawRect(item.x_, item.y_, 16, 16, ImVec4(255, 0, 0, 150));
917
919 // Check if this item is being clicked and dragged
920 HandleEntityDragging(&item, ow_map_canvas_.zero_point(),
923
924 const auto hovering = IsMouseHoveringOverEntity(
925 item, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
926 if (hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
928 current_item_ = item;
929 current_entity_ = &item;
930 }
931 }
932 std::string item_name = "";
933 if (item.id_ < zelda3::kSecretItemNames.size()) {
934 item_name = zelda3::kSecretItemNames[item.id_];
935 } else {
936 item_name = absl::StrFormat("0x%02X", item.id_);
937 }
938 ow_map_canvas_.DrawText(item_name, item.x_, item.y_);
939 }
940 i++;
941 }
942
945 const auto hovering = IsMouseHoveringOverEntity(
946 overworld_.mutable_all_items()->at(current_item_id_),
947 ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
948
949 if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
950 ImGui::OpenPopup("Item Inserter");
951 } else {
953 overworld_.mutable_all_items()->at(current_item_id_))) {
954 overworld_.mutable_all_items()->at(current_item_id_) = current_item_;
955 }
956 }
957 }
958}
959
961 int i = 0;
962 for (auto &sprite : *overworld_.mutable_sprites(game_state_)) {
963 if (!sprite.deleted()) {
964 int map_x = sprite.map_x();
965 int map_y = sprite.map_y();
966 ow_map_canvas_.DrawRect(map_x, map_y, kTile16Size, kTile16Size,
967 /*magenta=*/ImVec4(255, 0, 255, 150));
969 HandleEntityDragging(&sprite, ow_map_canvas_.zero_point(),
972 if (IsMouseHoveringOverEntity(sprite, ow_map_canvas_.zero_point(),
973 ow_map_canvas_.scrolling()) &&
974 ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
976 current_sprite_ = sprite;
977 }
978 }
979 if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
980 if (sprite_previews_[sprite.id()].is_active()) {
981 ow_map_canvas_.DrawBitmap(sprite_previews_[sprite.id()], map_x, map_y,
982 2.0f);
983 }
984 }
985
986 ow_map_canvas_.DrawText(absl::StrFormat("%s", sprite.name()), map_x,
987 map_y);
988 }
989 i++;
990 }
991
994 const auto hovering = IsMouseHoveringOverEntity(
995 overworld_.mutable_sprites(game_state_)->at(current_sprite_id_),
996 ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
997
998 if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
999 ImGui::OpenPopup("Sprite Inserter");
1000 } else {
1001 if (DrawSpriteEditorPopup(overworld_.mutable_sprites(game_state_)
1002 ->at(current_sprite_id_))) {
1003 overworld_.mutable_sprites(game_state_)->at(current_sprite_id_) =
1005 }
1006 }
1007 }
1008}
1009
1011 if (core::FeatureFlags::get().overworld.kSaveOverworldMaps) {
1012 RETURN_IF_ERROR(overworld_.CreateTile32Tilemap());
1013 RETURN_IF_ERROR(overworld_.SaveMap32Tiles());
1014 RETURN_IF_ERROR(overworld_.SaveMap16Tiles());
1015 RETURN_IF_ERROR(overworld_.SaveOverworldMaps());
1016 }
1017 if (core::FeatureFlags::get().overworld.kSaveOverworldEntrances) {
1018 RETURN_IF_ERROR(overworld_.SaveEntrances());
1019 }
1020 if (core::FeatureFlags::get().overworld.kSaveOverworldExits) {
1021 RETURN_IF_ERROR(overworld_.SaveExits());
1022 }
1023 if (core::FeatureFlags::get().overworld.kSaveOverworldItems) {
1024 RETURN_IF_ERROR(overworld_.SaveItems());
1025 }
1026 if (core::FeatureFlags::get().overworld.kSaveOverworldProperties) {
1027 RETURN_IF_ERROR(overworld_.SaveMapProperties());
1028 }
1029 return absl::OkStatus();
1030}
1031
1033 util::logf("Loading overworld.");
1034 // Load the Link to the Past overworld.
1036 palette_ = overworld_.current_area_palette();
1037
1038 util::logf("Loading overworld graphics.");
1039 // Create the area graphics image
1040 Renderer::GetInstance().CreateAndRenderBitmap(0x80, kOverworldMapSize, 0x40,
1041 overworld_.current_graphics(),
1043
1044 util::logf("Loading overworld tileset.");
1045 // Create the tile16 blockset image
1046 Renderer::GetInstance().CreateAndRenderBitmap(
1047 0x80, 0x2000, 0x08, overworld_.tile16_blockset_data(),
1049 map_blockset_loaded_ = true;
1050
1051 // Copy the tile16 data into individual tiles.
1052 auto tile16_blockset_data = overworld_.tile16_blockset_data();
1053
1054 util::logf("Loading overworld tile16 graphics.");
1055 // Loop through the tiles and copy their pixel data into separate vectors
1056 for (unsigned int i = 0; i < zelda3::kNumTile16Individual; i++) {
1057 std::vector<uint8_t> tile16_data(kTile16Size * kTile16Size);
1058 tile16_individual_[i].Create(kTile16Size, kTile16Size, 0x08, tile16_data);
1059
1060 // Copy the pixel data for the current tile into the vector
1061 for (int ty = 0; ty < kTile16Size; ty++) {
1062 for (int tx = 0; tx < kTile16Size; tx++) {
1063 int position = tx + (ty * kTile16Size);
1064 uint8_t value = tile16_blockset_data[(i % 8 * kTile16Size) +
1065 (i / 8 * kTile16Size * 0x80) +
1066 (ty * 0x80) + tx];
1067 tile16_individual_[i].mutable_data()[position] = value;
1068 }
1069 }
1070
1071 tile16_individual_[i].SetPalette(palette_);
1072 Renderer::GetInstance().RenderBitmap(&tile16_individual_[i]);
1073 }
1074
1075 util::logf("Loading overworld maps.");
1076 // Render the overworld maps loaded from the ROM.
1077 for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
1078 overworld_.set_current_map(i);
1079 auto palette = overworld_.current_area_palette();
1080 try {
1081 Renderer::GetInstance().CreateAndRenderBitmap(
1083 overworld_.current_map_bitmap_data(), maps_bmp_[i], palette);
1084 } catch (const std::bad_alloc &e) {
1085 std::cout << "Error: " << e.what() << std::endl;
1086 continue;
1087 }
1088 }
1089
1090 if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
1092 }
1093
1094 return absl::OkStatus();
1095}
1096
1098 // Render the sprites for each Overworld map
1099 const int depth = 0x10;
1100 for (int i = 0; i < 3; i++)
1101 for (auto const &sprite : *overworld_.mutable_sprites(i)) {
1102 int width = sprite.width();
1103 int height = sprite.height();
1104 if (width == 0 || height == 0) {
1105 continue;
1106 }
1107 if (sprite_previews_.size() < sprite.id()) {
1108 sprite_previews_.resize(sprite.id() + 1);
1109 }
1110 sprite_previews_[sprite.id()].Create(width, height, depth,
1111 *sprite.preview_graphics());
1112 sprite_previews_[sprite.id()].SetPalette(palette_);
1113 Renderer::GetInstance().RenderBitmap(&(sprite_previews_[sprite.id()]));
1114 }
1115 return absl::OkStatus();
1116}
1117
1119 overworld_.mutable_overworld_map(map_index)->LoadAreaGraphics();
1120 status_ = overworld_.mutable_overworld_map(map_index)->BuildTileset();
1122 status_ = overworld_.mutable_overworld_map(map_index)->BuildTiles16Gfx(
1123 *overworld_.mutable_tiles16(), overworld_.tiles16().size());
1125 status_ = overworld_.mutable_overworld_map(map_index)->BuildBitmap(
1126 overworld_.GetMapTiles(current_world_));
1127 maps_bmp_[map_index].set_data(
1128 overworld_.mutable_overworld_map(map_index)->bitmap_data());
1129 maps_bmp_[map_index].set_modified(true);
1131}
1132
1134 std::vector<std::future<void>> futures;
1135 int indices[4];
1136
1137 auto refresh_map_async = [this](int map_index) {
1138 RefreshChildMap(map_index);
1139 };
1140
1141 int source_map_id = current_map_;
1142 bool is_large = overworld_.overworld_map(current_map_)->is_large_map();
1143 if (is_large) {
1144 source_map_id = current_parent_;
1145 // We need to update the map and its siblings if it's a large map
1146 for (int i = 1; i < 4; i++) {
1147 int sibling_index = overworld_.overworld_map(source_map_id)->parent() + i;
1148 if (i >= 2) sibling_index += 6;
1149 futures.push_back(
1150 std::async(std::launch::async, refresh_map_async, sibling_index));
1151 indices[i] = sibling_index;
1152 }
1153 }
1154 indices[0] = source_map_id;
1155 futures.push_back(
1156 std::async(std::launch::async, refresh_map_async, source_map_id));
1157
1158 for (auto &each : futures) {
1159 each.wait();
1160 each.get();
1161 }
1162 int n = is_large ? 4 : 1;
1163 // We do texture updating on the main thread
1164 for (int i = 0; i < n; ++i) {
1165 Renderer::GetInstance().UpdateBitmap(&maps_bmp_[indices[i]]);
1166 }
1167}
1168
1171 overworld_.mutable_overworld_map(current_map_)->LoadPalette());
1172 const auto current_map_palette = overworld_.current_area_palette();
1173
1174 if (overworld_.overworld_map(current_map_)->is_large_map()) {
1175 // We need to update the map and its siblings if it's a large map
1176 for (int i = 1; i < 4; i++) {
1177 int sibling_index = overworld_.overworld_map(current_map_)->parent() + i;
1178 if (i >= 2) sibling_index += 6;
1180 overworld_.mutable_overworld_map(sibling_index)->LoadPalette());
1181 maps_bmp_[sibling_index].SetPalette(current_map_palette);
1182 }
1183 }
1184
1185 maps_bmp_[current_map_].SetPalette(current_map_palette);
1186 return absl::OkStatus();
1187}
1188
1190 auto &current_ow_map = *overworld_.mutable_overworld_map(current_map_);
1191 if (current_ow_map.is_large_map()) {
1192 // We need to copy the properties from the parent map to the children
1193 for (int i = 1; i < 4; i++) {
1194 int sibling_index = current_ow_map.parent() + i;
1195 if (i >= 2) {
1196 sibling_index += 6;
1197 }
1198 auto &map = *overworld_.mutable_overworld_map(sibling_index);
1199 map.set_area_graphics(current_ow_map.area_graphics());
1200 map.set_area_palette(current_ow_map.area_palette());
1201 map.set_sprite_graphics(game_state_,
1202 current_ow_map.sprite_graphics(game_state_));
1203 map.set_sprite_palette(game_state_,
1204 current_ow_map.sprite_palette(game_state_));
1205 map.set_message_id(current_ow_map.message_id());
1206 }
1207 }
1208}
1209
1211 if (current_blockset_ ==
1212 overworld_.overworld_map(current_map_)->area_graphics()) {
1213 return absl::OkStatus();
1214 }
1215 current_blockset_ = overworld_.overworld_map(current_map_)->area_graphics();
1216
1217 overworld_.set_current_map(current_map_);
1218 palette_ = overworld_.current_area_palette();
1219 // Create the tile16 blockset image
1221 tile16_blockset_bmp_.SetPalette(palette_);
1222
1223 // Copy the tile16 data into individual tiles.
1224 const auto tile16_data = overworld_.tile16_blockset_data();
1225
1226 // Loop through the tiles and copy their pixel data into separate vectors
1227 std::vector<std::future<absl::Status>> futures;
1228 for (unsigned int i = 0; i < zelda3::kNumTile16Individual; i++) {
1229 futures.push_back(std::async(
1230 std::launch::async,
1231 [&](int index) -> absl::Status {
1232 std::vector<uint8_t> tile_data(16 * 16, 0x00);
1233 for (int ty = 0; ty < 16; ty++) {
1234 for (int tx = 0; tx < 16; tx++) {
1235 int position = tx + (ty * 0x10);
1236 uint8_t value =
1237 tile16_data[(index % 8 * 16) + (index / 8 * 16 * 0x80) +
1238 (ty * 0x80) + tx];
1239 tile_data[position] = value;
1240 }
1241 }
1242 tile16_individual_[index].set_data(tile_data);
1243 tile16_individual_[index].SetPalette(palette_);
1244 return absl::OkStatus();
1245 },
1246 i));
1247 }
1248
1249 for (auto &future : futures) {
1250 future.wait();
1251 RETURN_IF_ERROR(future.get());
1252 }
1253
1254 // Render the bitmaps of each tile.
1255 for (unsigned int id = 0; id < zelda3::kNumTile16Individual; id++) {
1256 Renderer::GetInstance().UpdateBitmap(&tile16_individual_[id]);
1257 }
1258
1259 return absl::OkStatus();
1260}
1261
1263 static bool init_properties = false;
1264
1265 if (!init_properties) {
1266 for (int i = 0; i < 0x40; i++) {
1267 std::string area_graphics_str = absl::StrFormat(
1268 "%02hX", overworld_.overworld_map(i)->area_graphics());
1270 ->push_back(area_graphics_str);
1271
1272 area_graphics_str = absl::StrFormat(
1273 "%02hX", overworld_.overworld_map(i + 0x40)->area_graphics());
1275 ->push_back(area_graphics_str);
1276
1277 std::string area_palette_str =
1278 absl::StrFormat("%02hX", overworld_.overworld_map(i)->area_palette());
1280 ->push_back(area_palette_str);
1281
1282 area_palette_str = absl::StrFormat(
1283 "%02hX", overworld_.overworld_map(i + 0x40)->area_palette());
1285 ->push_back(area_palette_str);
1286 std::string sprite_gfx_str = absl::StrFormat(
1287 "%02hX", overworld_.overworld_map(i)->sprite_graphics(1));
1289 ->push_back(sprite_gfx_str);
1290
1291 sprite_gfx_str = absl::StrFormat(
1292 "%02hX", overworld_.overworld_map(i)->sprite_graphics(2));
1294 ->push_back(sprite_gfx_str);
1295
1296 sprite_gfx_str = absl::StrFormat(
1297 "%02hX", overworld_.overworld_map(i + 0x40)->sprite_graphics(1));
1299 ->push_back(sprite_gfx_str);
1300
1301 sprite_gfx_str = absl::StrFormat(
1302 "%02hX", overworld_.overworld_map(i + 0x40)->sprite_graphics(2));
1304 ->push_back(sprite_gfx_str);
1305
1306 std::string sprite_palette_str = absl::StrFormat(
1307 "%02hX", overworld_.overworld_map(i)->sprite_palette(1));
1309 ->push_back(sprite_palette_str);
1310
1311 sprite_palette_str = absl::StrFormat(
1312 "%02hX", overworld_.overworld_map(i)->sprite_palette(2));
1314 ->push_back(sprite_palette_str);
1315
1316 sprite_palette_str = absl::StrFormat(
1317 "%02hX", overworld_.overworld_map(i + 0x40)->sprite_palette(1));
1319 ->push_back(sprite_palette_str);
1320
1321 sprite_palette_str = absl::StrFormat(
1322 "%02hX", overworld_.overworld_map(i + 0x40)->sprite_palette(2));
1324 ->push_back(sprite_palette_str);
1325 }
1326 init_properties = true;
1327 }
1328
1329 Text("Area Gfx LW/DW");
1330 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 2.0f, 32,
1332 SameLine();
1333 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 2.0f, 32,
1335 ImGui::Separator();
1336
1337 Text("Sprite Gfx LW/DW");
1338 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 2.0f, 32,
1340 SameLine();
1341 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 2.0f, 32,
1343 SameLine();
1344 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 2.0f, 32,
1346 SameLine();
1347 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 2.0f, 32,
1349 ImGui::Separator();
1350
1351 Text("Area Pal LW/DW");
1352 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 2.0f, 32,
1354 SameLine();
1355 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 2.0f, 32,
1357
1358 static bool show_gfx_group = false;
1359 Checkbox("Show Gfx Group Editor", &show_gfx_group);
1360 if (show_gfx_group) {
1361 gui::BeginWindowWithDisplaySettings("Gfx Group Editor", &show_gfx_group);
1362 status_ = gfx_group_editor_.Update();
1364 }
1365}
1366
1368 if (BeginTable("UsageStatsTable", 3, kOWEditFlags, ImVec2(0, 0))) {
1369 TableSetupColumn("Entrances");
1370 TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch,
1371 ImGui::GetContentRegionAvail().x);
1372 TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 256);
1373 TableHeadersRow();
1374 TableNextRow();
1375
1376 TableNextColumn();
1377 if (BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
1378 ImGuiWindowFlags_HorizontalScrollbar)) {
1379 for (int i = 0; i < 0x81; i++) {
1380 auto entrance_name = rom_->resource_label()->CreateOrGetLabel(
1381 "Dungeon Entrance Names", util::HexByte(i),
1382 zelda3::kEntranceNames[i].data());
1383 std::string str = absl::StrFormat("%#x - %s", i, entrance_name);
1384 if (Selectable(str.c_str(), selected_entrance_ == i,
1385 overworld_.entrances().at(i).deleted
1386 ? ImGuiSelectableFlags_Disabled
1387 : 0)) {
1389 selected_usage_map_ = overworld_.entrances().at(i).map_id_;
1390 properties_canvas_.set_highlight_tile_id(selected_usage_map_);
1391 }
1392 if (IsItemHovered()) {
1393 BeginTooltip();
1394 Text("Entrance ID: %d", i);
1395 Text("Map ID: %d", overworld_.entrances().at(i).map_id_);
1396 Text("Entrance ID: %d", overworld_.entrances().at(i).entrance_id_);
1397 Text("X: %d", overworld_.entrances().at(i).x_);
1398 Text("Y: %d", overworld_.entrances().at(i).y_);
1399 Text("Deleted? %s",
1400 overworld_.entrances().at(i).deleted ? "Yes" : "No");
1401 EndTooltip();
1402 }
1403 }
1404 EndChild();
1405 }
1406
1407 TableNextColumn();
1408 DrawUsageGrid();
1409
1410 TableNextColumn();
1412
1413 EndTable();
1414 }
1415 return absl::OkStatus();
1416}
1417
1419 // Create a grid of 8x8 squares
1420 int total_squares = 128;
1421 int squares_wide = 8;
1422 int squares_tall = (total_squares + squares_wide - 1) /
1423 squares_wide; // Ceiling of total_squares/squares_wide
1424
1425 // Loop through each row
1426 for (int row = 0; row < squares_tall; ++row) {
1427 NewLine();
1428
1429 for (int col = 0; col < squares_wide; ++col) {
1430 if (row * squares_wide + col >= total_squares) {
1431 break;
1432 }
1433 // Determine if this square should be highlighted
1434 bool highlight = selected_usage_map_ == (row * squares_wide + col);
1435
1436 // Set highlight color if needed
1437 if (highlight) {
1438 PushStyleColor(ImGuiCol_Button,
1439 ImVec4(1.0f, 0.5f, 0.0f,
1440 1.0f)); // Or any highlight color
1441 }
1442
1443 // Create a button or selectable for each square
1444 if (Button("##square", ImVec2(20, 20))) {
1445 // Switch over to the room editor tab
1446 // and add a room tab by the ID of the square
1447 // that was clicked
1448 }
1449
1450 // Reset style if it was highlighted
1451 if (highlight) {
1452 PopStyleColor();
1453 }
1454
1455 // Check if the square is hovered
1456 if (IsItemHovered()) {
1457 // Display a tooltip with all the room properties
1458 }
1459
1460 // Keep squares in the same line
1461 SameLine();
1462 }
1463 }
1464}
1465
1467 Text("Current Map: %d", current_map_);
1468 Text("Current Tile16: %d", current_tile16_);
1469 int relative_x = (int)ow_map_canvas_.drawn_tile_position().x % 512;
1470 int relative_y = (int)ow_map_canvas_.drawn_tile_position().y % 512;
1471 Text("Current Tile16 Drawn Position (Relative): %d, %d", relative_x,
1472 relative_y);
1473
1474 // Print the size of the overworld map_tiles per world
1475 Text("Light World Map Tiles: %d",
1476 (int)overworld_.mutable_map_tiles()->light_world.size());
1477 Text("Dark World Map Tiles: %d",
1478 (int)overworld_.mutable_map_tiles()->dark_world.size());
1479 Text("Special World Map Tiles: %d",
1480 (int)overworld_.mutable_map_tiles()->special_world.size());
1481
1482 static bool view_lw_map_tiles = false;
1483 static MemoryEditor mem_edit;
1484 // Let's create buttons which let me view containers in the memory editor
1485 if (Button("View Light World Map Tiles")) {
1486 view_lw_map_tiles = !view_lw_map_tiles;
1487 }
1488
1489 if (view_lw_map_tiles) {
1490 mem_edit.DrawContents(
1491 overworld_.mutable_map_tiles()->light_world[current_map_].data(),
1492 overworld_.mutable_map_tiles()->light_world[current_map_].size());
1493 }
1494}
1495
1497 overworld_.Destroy();
1498 current_graphics_set_.clear();
1499 for (auto &bmp : tile16_individual_) {
1500 bmp.Clear();
1501 }
1502 for (auto &bmp : maps_bmp_) {
1503 bmp.Clear();
1504 }
1505 for (auto &bmp : sprite_previews_) {
1506 bmp.Clear();
1507 }
1508 tile16_blockset_bmp_.Clear();
1509 current_gfx_bmp_.Clear();
1510 all_gfx_loaded_ = false;
1511 map_blockset_loaded_ = false;
1512 return absl::OkStatus();
1513}
1514
1516 uint64_t timeout) {
1517 for (auto &bmp : tile16_individual_) {
1518 bmp.CleanupUnusedTexture(current_time, timeout);
1519 }
1520 for (auto &bmp : maps_bmp_) {
1521 bmp.CleanupUnusedTexture(current_time, timeout);
1522 }
1523}
1524
1525} // namespace editor
1526} // namespace yaze
static GraphicsSheetManager & GetInstance()
Definition rom.h:272
static Flags & get()
Definition features.h:69
absl::Status Clear() override
zelda3::OverworldItem current_item_
zelda3::OverworldEntranceTileTypes entrance_tiletypes_
zelda3::OverworldEntrance current_entrance_
absl::Status CheckForCurrentMap()
Check for changes to the overworld map. Calls RefreshOverworldMap and RefreshTile16Blockset on the cu...
void DrawOverworldExits(ImVec2 zero, ImVec2 scrolling)
absl::Status Undo() override
zelda3::GameEntity * dragged_entity_
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > maps_bmp_
void CheckForOverworldEdits()
Check for changes to the overworld map.
zelda3::OverworldExit current_exit_
void RenderUpdatedMapBitmap(const ImVec2 &click_position, const std::vector< uint8_t > &tile_data)
absl::Status Redo() override
void CheckForSelectRectangle()
Draw and create the tile16 IDs that are currently selected.
absl::Status Load() override
std::vector< gfx::Bitmap > sprite_previews_
absl::Status LoadGraphics()
Load the Bitmap objects for each OverworldMap.
void CleanupUnusedTextures(uint64_t current_time, uint64_t timeout) override
std::array< gfx::Bitmap, zelda3::kNumTile16Individual > tile16_individual_
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling, bool holes=false)
zelda3::GameEntity * current_entity_
static Renderer & GetInstance()
Definition renderer.h:26
Represents a bitmap image.
Definition bitmap.h:66
void WriteToPixel(int position, uint8_t value)
Write a value to a pixel at the given position.
Definition bitmap.cc:501
void set_modified(bool modified)
Definition bitmap.h:210
#define ICON_MD_GRID_VIEW
Definition icons.h:895
#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_OPEN_IN_FULL
Definition icons.h:1351
#define ICON_MD_TABLE_CHART
Definition icons.h:1928
#define ICON_MD_REDO
Definition icons.h:1568
#define ICON_MD_GRASS
Definition icons.h:889
#define ICON_MD_DOOR_BACK
Definition icons.h:610
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1262
#define ICON_MD_DOOR_FRONT
Definition icons.h:611
#define ICON_MD_ADD_LOCATION
Definition icons.h:98
#define ICON_MD_ZOOM_IN
Definition icons.h:2189
#define ICON_MD_PEST_CONTROL_RODENT
Definition icons.h:1428
#define ICON_MD_CONTENT_COPY
Definition icons.h:463
#define ICON_MD_UNDO
Definition icons.h:2034
#define ICON_MD_PAN_TOOL_ALT
Definition icons.h:1370
#define PRINT_IF_ERROR(expression)
Definition macro.h:25
#define RETURN_IF_ERROR(expression)
Definition macro.h:51
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:59
#define HOVER_HINT(string)
Definition macro.h:22
void CopyImageToClipboard(const std::vector< uint8_t > &data)
Definition clipboard.cc:9
Editors are the view controllers for the application.
void DrawExitInserterPopup()
Definition entity.cc:162
constexpr absl::string_view kOWMapTable
constexpr ImGuiTableFlags kOWMapFlags
constexpr unsigned int kOverworldMapSize
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite)
Definition entity.cc:452
void DrawItemInsertPopup()
Definition entity.cc:312
bool DrawEntranceInserterPopup()
Definition entity.cc:105
void DrawSpriteInserterPopup()
Definition entity.cc:428
constexpr absl::string_view kWorldList
constexpr absl::string_view kGamePartComboString
void HandleEntityDragging(zelda3::GameEntity *entity, ImVec2 canvas_p0, ImVec2 scrolling, bool &is_dragging_entity, zelda3::GameEntity *&dragged_entity, zelda3::GameEntity *&current_entity, bool free_movement)
Definition entity.cc:56
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance)
Definition entity.cc:130
bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity, ImVec2 canvas_p0, ImVec2 scrolling)
Definition entity.cc:21
absl::Status DisplayPalette(gfx::SnesPalette &palette, bool loaded)
bool DrawItemEditorPopup(zelda3::OverworldItem &item)
Definition entity.cc:342
constexpr int kTile16Size
constexpr ImGuiTableFlags kOWEditFlags
constexpr float kInputFieldSize
Definition entity.cc:19
constexpr absl::string_view kTileSelectorTab
constexpr std::array< const char *, 8 > kMapSettingsColumnNames
bool DrawExitEditorPopup(zelda3::OverworldExit &exit)
Definition entity.cc:180
void Render(Node &node)
Render a zeml tree.
Definition zeml.cc:380
std::string LoadFile(const std::string &filename)
Definition zeml.cc:589
void Bind(Node *node, std::function< void()> callback)
Bind a callback to a node.
Definition zeml.cc:566
Node Parse(const std::string &yazon_input, const std::map< std::string, void * > &data_bindings)
Parse a zeml string.
Definition zeml.cc:359
void BeginPadding(int i)
Definition style.cc:372
void BeginChildBothScrollbars(int id)
Definition style.cc:389
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:161
void AddTableColumn(Table &table, const std::string &label, GuiElement element)
Definition input.cc:370
void EndNoPadding()
Definition style.cc:382
void DrawTable(Table &params)
Definition input.cc:376
void EndPadding()
Definition style.cc:376
void BeginNoPadding()
Definition style.cc:378
void EndWindowWithDisplaySettings()
Definition style.cc:367
void BeginWindowWithDisplaySettings(const char *id, bool *active, const ImVec2 &size, ImGuiWindowFlags flags)
Definition style.cc:347
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:175
void BeginChildWithScrollbar(const char *str_id)
Definition style.cc:384
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:33
constexpr std::string_view kEntranceNames[]
Definition common.h:48
constexpr int kNumTile16Individual
Definition overworld.h:97
constexpr int kNumOverworldMaps
Definition overworld.h:96
const std::vector< std::string > kSecretItemNames
absl::StatusOr< OverworldEntranceTileTypes > LoadEntranceTileTypes(Rom *rom)
Main namespace for the application.
Definition controller.cc:18