yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
map_refresh_coordinator.cc
Go to the documentation of this file.
1// Related header
3
4#include <algorithm>
5
6#include "absl/status/status.h"
12#include "rom/rom.h"
13#include "util/log.h"
14#include "util/macro.h"
15#include "zelda3/common.h"
19
20namespace yaze::editor {
21
22namespace {
23
24int WorldForMapIndex(int map_index) {
25 if (map_index >= zelda3::kSpecialWorldMapIdStart) {
26 return 2;
27 }
28 if (map_index >= zelda3::kDarkWorldMapIdStart) {
29 return 1;
30 }
31 return 0;
32}
33
35 int map_index) {
36 return overworld->GetMapTiles(WorldForMapIndex(map_index));
37}
38
39bool IsMapInCurrentWorld(int map_index, int current_world) {
40 return WorldForMapIndex(map_index) == current_world;
41}
42
44 return ctx.game_state ? std::clamp(*ctx.game_state, 0, 2) : 0;
45}
46
49 if (!map) {
50 return;
51 }
53}
54
56 const zelda3::OverworldMap& map) {
57 if (!ctx.current_gfx_bmp) {
58 return;
59 }
60
61 const auto& current_graphics = map.current_graphics();
62 if (current_graphics.empty()) {
63 return;
64 }
65
66 constexpr int kCurrentGfxBitmapWidth = 0x80;
67 if (current_graphics.size() % kCurrentGfxBitmapWidth != 0) {
68 LOG_WARN("MapRefreshCoordinator",
69 "Current graphics size %zu is not divisible by bitmap width %d",
70 current_graphics.size(), kCurrentGfxBitmapWidth);
71 return;
72 }
73
74 const int bitmap_height =
75 static_cast<int>(current_graphics.size() / kCurrentGfxBitmapWidth);
76 const bool recreate_bitmap =
77 !ctx.current_gfx_bmp->is_active() ||
78 ctx.current_gfx_bmp->width() != kCurrentGfxBitmapWidth ||
79 ctx.current_gfx_bmp->height() != bitmap_height ||
80 ctx.current_gfx_bmp->depth() != 0x08;
81
82 if (recreate_bitmap) {
83 ctx.current_gfx_bmp->Create(kCurrentGfxBitmapWidth, bitmap_height, 0x08,
84 current_graphics);
85 } else {
86 ctx.current_gfx_bmp->set_data(current_graphics);
87 }
88
89 const auto command = ctx.current_gfx_bmp->texture()
93}
94
95} // namespace
96
98 if (map_id < 0) {
99 // Invalidate all maps - clear both editor cache and Overworld's tileset
100 // cache
101 ctx_.current_graphics_set->clear();
103 } else {
104 // Invalidate specific map and its siblings in the Overworld's tileset cache
105 ctx_.current_graphics_set->erase(map_id);
107 }
108}
109
111 auto* map = ctx_.overworld->mutable_overworld_map(map_index);
112 if (!map) {
113 return;
114 }
115 PrepareMapForRefresh(ctx_, map);
116 map->LoadAreaGraphics();
117 *ctx_.status = map->BuildTileset();
119 *ctx_.status = map->BuildTiles16Gfx(*ctx_.overworld->mutable_tiles16(),
120 ctx_.overworld->tiles16().size());
122 *ctx_.status =
123 map->BuildBitmap(MapTilesForMapIndex(ctx_.overworld, map_index));
124 (*ctx_.maps_bmp)[map_index].set_data(map->bitmap_data());
125 (*ctx_.maps_bmp)[map_index].set_modified(true);
127}
128
130 // Use the new on-demand refresh system
132}
133
142 if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
143 return;
144 }
145
146 // Check if the map is actually visible or being edited
147 bool is_current_map = (map_index == *ctx_.current_map);
148 bool is_current_world = IsMapInCurrentWorld(map_index, *ctx_.current_world);
149
150 // For non-current maps in non-current worlds, defer the refresh
151 if (!is_current_map && !is_current_world) {
152 // Mark for deferred refresh - will be processed when the map becomes
153 // visible
154 (*ctx_.maps_bmp)[map_index].set_modified(true);
155 return;
156 }
157
158 // For visible maps, do immediate refresh
159 RefreshChildMapOnDemand(map_index);
160}
161
166 auto* map = ctx_.overworld->mutable_overworld_map(map_index);
167 if (!map) {
168 return; // Map not loaded yet (e.g. Overworld not fully initialized)
169 }
170
171 // Check what actually needs to be refreshed
172 bool needs_graphics_rebuild = (*ctx_.maps_bmp)[map_index].modified();
173
174 if (needs_graphics_rebuild) {
175 // Only rebuild what's actually changed
176 PrepareMapForRefresh(ctx_, map);
177 map->LoadAreaGraphics();
178
179 // Rebuild tileset only if graphics changed
180 auto status = map->BuildTileset();
181 if (!status.ok()) {
182 LOG_ERROR("MapRefreshCoordinator",
183 "Failed to build tileset for map %d: %s", map_index,
184 status.message().data());
185 return;
186 }
187
188 // Rebuild tiles16 graphics
189 status = map->BuildTiles16Gfx(*ctx_.overworld->mutable_tiles16(),
190 ctx_.overworld->tiles16().size());
191 if (!status.ok()) {
192 LOG_ERROR("MapRefreshCoordinator",
193 "Failed to build tiles16 graphics for map %d: %s", map_index,
194 status.message().data());
195 return;
196 }
197
198 // Rebuild bitmap
199 status = map->BuildBitmap(MapTilesForMapIndex(ctx_.overworld, map_index));
200 if (!status.ok()) {
201 LOG_ERROR("MapRefreshCoordinator",
202 "Failed to build bitmap for map %d: %s", map_index,
203 status.message().data());
204 return;
205 }
206
207 // Update bitmap data
208 (*ctx_.maps_bmp)[map_index].set_data(map->bitmap_data());
209 (*ctx_.maps_bmp)[map_index].set_modified(false);
210
211 // Validate surface synchronization to help debug crashes
212 if (!(*ctx_.maps_bmp)[map_index].ValidateDataSurfaceSync()) {
213 LOG_WARN("MapRefreshCoordinator",
214 "Warning: Surface synchronization issue detected for map %d",
215 map_index);
216 }
217
218 // Queue texture update to ensure changes are visible
219 if ((*ctx_.maps_bmp)[map_index].texture()) {
222 } else {
225 }
226 }
227
228 // Handle multi-area maps (large, wide, tall) with safe coordination
229 // Use centralized version detection
231 bool use_v3_area_sizes =
233
234 if (use_v3_area_sizes) {
235 // Use v3 multi-area coordination
236 RefreshMultiAreaMapsSafely(map_index, map);
237 } else {
238 // Legacy logic: only handle large maps for vanilla/v2
239 if (map->is_large_map()) {
240 RefreshMultiAreaMapsSafely(map_index, map);
241 }
242 }
243}
244
260 int map_index, zelda3::OverworldMap* map) {
262
263 auto area_size = map->area_size();
264 if (area_size == AreaSizeEnum::SmallArea) {
265 return; // No siblings to coordinate
266 }
267
268 // Always work from parent perspective for consistent coordination
269 int parent_id = map->parent();
270
271 // If we're not the parent, get the parent map to work from
272 auto* parent_map = ctx_.overworld->mutable_overworld_map(parent_id);
273 if (!parent_map) {
274 LOG_WARN("MapRefreshCoordinator",
275 "RefreshMultiAreaMapsSafely: Could not get parent map %d for "
276 "map %d",
277 parent_id, map_index);
278 return;
279 }
280
281 LOG_DEBUG("MapRefreshCoordinator",
282 "RefreshMultiAreaMapsSafely: Processing %s area from parent %d "
283 "(trigger: %d)",
284 (area_size == AreaSizeEnum::LargeArea) ? "large"
285 : (area_size == AreaSizeEnum::WideArea) ? "wide"
286 : "tall",
287 parent_id, map_index);
288
289 // Determine all maps that are part of this multi-area structure
290 // based on the parent's position and area size
291 std::vector<int> sibling_maps;
292
293 switch (area_size) {
294 case AreaSizeEnum::LargeArea:
295 // Large Area: 2x2 grid (4 maps total)
296 sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
297 break;
298
299 case AreaSizeEnum::WideArea:
300 // Wide Area: 2x1 grid (2 maps total, horizontally adjacent)
301 sibling_maps = {parent_id, parent_id + 1};
302 break;
303
304 case AreaSizeEnum::TallArea:
305 // Tall Area: 1x2 grid (2 maps total, vertically adjacent)
306 sibling_maps = {parent_id, parent_id + 8};
307 break;
308
309 default:
310 LOG_WARN("MapRefreshCoordinator",
311 "RefreshMultiAreaMapsSafely: Unknown area size %d for map %d",
312 static_cast<int>(area_size), map_index);
313 return;
314 }
315
316 // Refresh all siblings (including self if different from trigger)
317 // The trigger map (map_index) was already processed by the caller,
318 // so we skip it to avoid double-processing
319 for (int sibling : sibling_maps) {
320 // Skip the trigger map - it was already processed by
321 // RefreshChildMapOnDemand
322 if (sibling == map_index) {
323 continue;
324 }
325
326 // Bounds check
327 if (sibling < 0 || sibling >= zelda3::kNumOverworldMaps) {
328 continue;
329 }
330
331 // Check visibility - only immediately refresh visible maps
332 bool is_current_map = (sibling == *ctx_.current_map);
333 bool is_current_world = IsMapInCurrentWorld(sibling, *ctx_.current_world);
334
335 // Always mark sibling as needing refresh to ensure consistency
336 (*ctx_.maps_bmp)[sibling].set_modified(true);
337
338 if (is_current_map || is_current_world) {
339 LOG_DEBUG("MapRefreshCoordinator",
340 "RefreshMultiAreaMapsSafely: Refreshing sibling map %d",
341 sibling);
342
343 // Direct refresh for visible siblings
344 auto* sibling_map = ctx_.overworld->mutable_overworld_map(sibling);
345 if (!sibling_map)
346 continue;
347
348 PrepareMapForRefresh(ctx_, sibling_map);
349 sibling_map->LoadAreaGraphics();
350
351 auto status = sibling_map->BuildTileset();
352 if (!status.ok()) {
353 LOG_ERROR("MapRefreshCoordinator",
354 "Failed to build tileset for sibling %d: %s", sibling,
355 status.message().data());
356 continue;
357 }
358
359 status = sibling_map->BuildTiles16Gfx(*ctx_.overworld->mutable_tiles16(),
360 ctx_.overworld->tiles16().size());
361 if (!status.ok()) {
362 LOG_ERROR("MapRefreshCoordinator",
363 "Failed to build tiles16 for sibling %d: %s", sibling,
364 status.message().data());
365 continue;
366 }
367
368 status = sibling_map->LoadPalette();
369 if (!status.ok()) {
370 LOG_ERROR("MapRefreshCoordinator",
371 "Failed to load palette for sibling %d: %s", sibling,
372 status.message().data());
373 continue;
374 }
375
376 status = sibling_map->BuildBitmap(
377 MapTilesForMapIndex(ctx_.overworld, sibling));
378 if (!status.ok()) {
379 LOG_ERROR("MapRefreshCoordinator",
380 "Failed to build bitmap for sibling %d: %s", sibling,
381 status.message().data());
382 continue;
383 }
384
385 // Update bitmap data
386 (*ctx_.maps_bmp)[sibling].set_data(sibling_map->bitmap_data());
387
388 // Set palette if bitmap has a valid surface
389 if ((*ctx_.maps_bmp)[sibling].is_active() &&
390 (*ctx_.maps_bmp)[sibling].surface()) {
391 (*ctx_.maps_bmp)[sibling].SetPalette(sibling_map->current_palette());
392 }
393 (*ctx_.maps_bmp)[sibling].set_modified(false);
394
395 // Queue texture update/creation
396 if ((*ctx_.maps_bmp)[sibling].texture()) {
399 } else {
401 ctx_.ensure_map_texture(sibling);
402 }
403 }
404 }
405 // Non-visible siblings remain marked as modified for deferred refresh
406 }
407}
408
410 auto* current_map = ctx_.overworld->mutable_overworld_map(*ctx_.current_map);
411 if (!current_map) {
412 return absl::FailedPreconditionError("Current overworld map not loaded");
413 }
414 PrepareMapForRefresh(ctx_, current_map);
415 RETURN_IF_ERROR(current_map->LoadPalette());
416 const auto current_map_palette = current_map->current_palette();
417 *ctx_.palette = current_map_palette;
418 // Keep tile16 editor in sync with the currently active overworld palette.
419 // Tile16Editor::set_palette owns remapping current_gfx_bmp to the selected
420 // brush row for the Tile8 source view, so do not overwrite it with the raw
421 // area palette here.
422 ctx_.tile16_editor->set_palette(current_map_palette);
423
424 // Use centralized version detection
426 bool use_v3_area_sizes =
428
429 if (use_v3_area_sizes) {
430 // Use v3 area size system
432 auto area_size = current_map->area_size();
433
434 if (area_size != AreaSizeEnum::SmallArea) {
435 // Get all sibling maps that need palette updates
436 std::vector<int> sibling_maps;
437 int parent_id = current_map->parent();
438
439 switch (area_size) {
440 case AreaSizeEnum::LargeArea:
441 // 2x2 grid: parent, parent+1, parent+8, parent+9
442 sibling_maps = {parent_id, parent_id + 1, parent_id + 8,
443 parent_id + 9};
444 break;
445 case AreaSizeEnum::WideArea:
446 // 2x1 grid: parent, parent+1
447 sibling_maps = {parent_id, parent_id + 1};
448 break;
449 case AreaSizeEnum::TallArea:
450 // 1x2 grid: parent, parent+8
451 sibling_maps = {parent_id, parent_id + 8};
452 break;
453 default:
454 break;
455 }
456
457 // Update palette for all siblings - each uses its own loaded palette
458 for (int sibling_index : sibling_maps) {
459 if (sibling_index < 0 || sibling_index >= zelda3::kNumOverworldMaps) {
460 continue;
461 }
462 auto* sibling_map =
463 ctx_.overworld->mutable_overworld_map(sibling_index);
464 if (!sibling_map) {
465 continue;
466 }
467 PrepareMapForRefresh(ctx_, sibling_map);
468 RETURN_IF_ERROR(sibling_map->LoadPalette());
469 (*ctx_.maps_bmp)[sibling_index].SetPalette(
470 sibling_map->current_palette());
471 }
472 } else {
473 // Small area - only update current map
474 (*ctx_.maps_bmp)[*ctx_.current_map].SetPalette(current_map_palette);
475 }
476 } else {
477 // Legacy logic for vanilla and v2 ROMs
478 if (current_map->is_large_map()) {
479 // We need to update the map and its siblings if it's a large map
480 for (int i = 1; i < 4; i++) {
481 int sibling_index = current_map->parent() + i;
482 if (i >= 2)
483 sibling_index += 6;
484 auto* sibling_map =
485 ctx_.overworld->mutable_overworld_map(sibling_index);
486 if (!sibling_map) {
487 continue;
488 }
489 PrepareMapForRefresh(ctx_, sibling_map);
490 RETURN_IF_ERROR(sibling_map->LoadPalette());
491
492 // SAFETY: Only set palette if bitmap has a valid surface
493 // Use sibling map's own loaded palette
494 if ((*ctx_.maps_bmp)[sibling_index].is_active() &&
495 (*ctx_.maps_bmp)[sibling_index].surface()) {
496 (*ctx_.maps_bmp)[sibling_index].SetPalette(
497 sibling_map->current_palette());
498 }
499 }
500 }
501
502 // SAFETY: Only set palette if bitmap has a valid surface
503 if ((*ctx_.maps_bmp)[*ctx_.current_map].is_active() &&
504 (*ctx_.maps_bmp)[*ctx_.current_map].surface()) {
505 (*ctx_.maps_bmp)[*ctx_.current_map].SetPalette(current_map_palette);
506 }
507 }
508
509 return absl::OkStatus();
510}
511
513 // Mark the bitmap as modified to force refresh on next update
514 if (map_index >= 0 && map_index < static_cast<int>(ctx_.maps_bmp->size())) {
515 (*ctx_.maps_bmp)[map_index].set_modified(true);
516
517 // Clear blockset cache
518 *ctx_.current_blockset = 0xFF;
519
520 // Invalidate Overworld's tileset cache for this map and siblings
521 // This ensures stale cached tilesets aren't reused after property changes
523
524 LOG_DEBUG("MapRefreshCoordinator",
525 "ForceRefreshGraphics: Map %d marked for refresh", map_index);
526 }
527}
528
530 bool include_self) {
531 if (map_index < 0 || map_index >= static_cast<int>(ctx_.maps_bmp->size())) {
532 return;
533 }
534
535 auto* map = ctx_.overworld->mutable_overworld_map(map_index);
536 if (map->area_size() == zelda3::AreaSizeEnum::SmallArea) {
537 return; // No siblings for small areas
538 }
539
540 int parent_id = map->parent();
541 std::vector<int> siblings;
542
543 switch (map->area_size()) {
545 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
546 break;
548 siblings = {parent_id, parent_id + 1};
549 break;
551 siblings = {parent_id, parent_id + 8};
552 break;
553 default:
554 return;
555 }
556
557 for (int sibling : siblings) {
558 if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) {
559 // Skip self unless include_self is true
560 if (sibling == map_index && !include_self) {
561 continue;
562 }
563
564 // Mark as modified FIRST before loading
565 (*ctx_.maps_bmp)[sibling].set_modified(true);
566
567 // Load graphics from ROM
568 auto* sibling_map = ctx_.overworld->mutable_overworld_map(sibling);
569 if (!sibling_map) {
570 continue;
571 }
572 PrepareMapForRefresh(ctx_, sibling_map);
573 sibling_map->LoadAreaGraphics();
574
575 // CRITICAL FIX: Bypass visibility check - force immediate refresh
576 // Call RefreshChildMapOnDemand() directly instead of
577 // RefreshOverworldMapOnDemand()
579
580 LOG_DEBUG("MapRefreshCoordinator",
581 "RefreshSiblingMapGraphics: Refreshed sibling map %d", sibling);
582 }
583 }
584}
585
587 const auto& current_ow_map =
589
590 // Use centralized version detection
592 bool use_v3_area_sizes =
594
595 if (use_v3_area_sizes) {
596 // Use v3 area size system
598 auto area_size = current_ow_map.area_size();
599
600 if (area_size != AreaSizeEnum::SmallArea) {
601 // Get all sibling maps that need property updates
602 std::vector<int> sibling_maps;
603 int parent_id = current_ow_map.parent();
604
605 switch (area_size) {
606 case AreaSizeEnum::LargeArea:
607 // 2x2 grid: parent+1, parent+8, parent+9 (skip parent itself)
608 sibling_maps = {parent_id + 1, parent_id + 8, parent_id + 9};
609 break;
610 case AreaSizeEnum::WideArea:
611 // 2x1 grid: parent+1 (skip parent itself)
612 sibling_maps = {parent_id + 1};
613 break;
614 case AreaSizeEnum::TallArea:
615 // 1x2 grid: parent+8 (skip parent itself)
616 sibling_maps = {parent_id + 8};
617 break;
618 default:
619 break;
620 }
621
622 // Copy properties from parent map to all siblings
623 const int game_state = CurrentGameState(ctx_);
624 for (int sibling_index : sibling_maps) {
625 if (sibling_index < 0 || sibling_index >= zelda3::kNumOverworldMaps) {
626 continue;
627 }
628 auto& map = *ctx_.overworld->mutable_overworld_map(sibling_index);
629 map.set_area_graphics(current_ow_map.area_graphics());
630 map.set_area_palette(current_ow_map.area_palette());
631 map.set_sprite_graphics(game_state,
632 current_ow_map.sprite_graphics(game_state));
633 map.set_sprite_palette(game_state,
634 current_ow_map.sprite_palette(game_state));
635 map.set_message_id(current_ow_map.message_id());
636
637 // CRITICAL FIX: Reload graphics after changing properties
638 PrepareMapForRefresh(ctx_, &map);
639 map.LoadAreaGraphics();
640 }
641 }
642 } else {
643 // Legacy logic for vanilla and v2 ROMs
644 if (current_ow_map.is_large_map()) {
645 // We need to copy the properties from the parent map to the children
646 const int game_state = CurrentGameState(ctx_);
647 for (int i = 1; i < 4; i++) {
648 int sibling_index = current_ow_map.parent() + i;
649 if (i >= 2) {
650 sibling_index += 6;
651 }
652 auto& map = *ctx_.overworld->mutable_overworld_map(sibling_index);
653 map.set_area_graphics(current_ow_map.area_graphics());
654 map.set_area_palette(current_ow_map.area_palette());
655 map.set_sprite_graphics(game_state,
656 current_ow_map.sprite_graphics(game_state));
657 map.set_sprite_palette(game_state,
658 current_ow_map.sprite_palette(game_state));
659 map.set_message_id(current_ow_map.message_id());
660
661 // CRITICAL FIX: Reload graphics after changing properties
662 PrepareMapForRefresh(ctx_, &map);
663 map.LoadAreaGraphics();
664 }
665 }
666 }
667}
668
670 LOG_DEBUG("MapRefreshCoordinator", "RefreshTile16Blockset called");
672 return absl::FailedPreconditionError("Current overworld map not loaded");
673 }
675 auto* current_map = ctx_.overworld->mutable_overworld_map(*ctx_.current_map);
676 if (!current_map) {
677 return absl::FailedPreconditionError("Current overworld map not loaded");
678 }
679 // Area graphics ids are reused across worlds; refresh even when the id
680 // matches so DW/SW base sheets and palettes do not inherit LW state.
681 *ctx_.current_blockset = current_map->area_graphics();
682
683 *ctx_.palette = current_map->current_palette();
684 // The Tile16 editor rebuilds selected-tile previews from current_gfx_bmp.
685 // Keep that source data aligned with the current map before set_palette()
686 // reloads Tile8s and remaps the sheet to the selected brush row.
687 SyncCurrentGraphicsBitmapForTile16Editor(ctx_, *current_map);
688 // Tile16Editor::set_palette refreshes the Tile8 source bitmap with the
689 // selected brush palette. Keeping that remap intact makes the source sheet,
690 // held Tile8 preview, and painted Tile16 pixels agree after blockset refresh.
692
693 const auto& tile16_data = current_map->current_tile16_blockset();
694
697
698 // Queue texture update for the atlas
703 } else if (!ctx_.tile16_blockset->atlas.texture() &&
705 // Create texture if it doesn't exist yet
708 }
709
710 return absl::OkStatus();
711}
712
714 // Skip if blockset not loaded or no pending changes
716 return;
717 }
718
720 return;
721 }
722
723 // Validate the atlas bitmap before modifying
725 ctx_.tile16_blockset->atlas.vector().empty() ||
728 return;
729 }
730
731 // Calculate tile positions in the atlas (8 tiles per row, each 16x16)
732 constexpr int kTilesPerRow = 8;
733 constexpr int kTileSize = 16;
734 int atlas_width = ctx_.tile16_blockset->atlas.width();
735 int atlas_height = ctx_.tile16_blockset->atlas.height();
736
737 bool atlas_modified = false;
738
739 // Iterate through all possible tile IDs to check for modifications
740 // Note: This is a brute-force approach; a more efficient method would
741 // maintain a list of modified tile IDs
742 for (int tile_id = 0; tile_id < zelda3::kNumTile16Individual; ++tile_id) {
743 if (!ctx_.tile16_editor->is_tile_modified(tile_id)) {
744 continue;
745 }
746
747 // Get the pending bitmap for this tile
748 const gfx::Bitmap* pending_bmp =
750 if (!pending_bmp || !pending_bmp->is_active() ||
751 pending_bmp->vector().empty()) {
752 continue;
753 }
754
755 // Calculate position in the atlas
756 int tile_x = (tile_id % kTilesPerRow) * kTileSize;
757 int tile_y = (tile_id / kTilesPerRow) * kTileSize;
758
759 // Validate tile position is within atlas bounds
760 if (tile_x + kTileSize > atlas_width || tile_y + kTileSize > atlas_height) {
761 continue;
762 }
763
764 // Copy pending bitmap data into the atlas at the correct position
765 auto& atlas_data = ctx_.tile16_blockset->atlas.mutable_data();
766 const auto& pending_data = pending_bmp->vector();
767
768 for (int y = 0; y < kTileSize && y < pending_bmp->height(); ++y) {
769 for (int x = 0; x < kTileSize && x < pending_bmp->width(); ++x) {
770 int atlas_idx = (tile_y + y) * atlas_width + (tile_x + x);
771 int pending_idx = y * pending_bmp->width() + x;
772
773 if (atlas_idx >= 0 && atlas_idx < static_cast<int>(atlas_data.size()) &&
774 pending_idx >= 0 &&
775 pending_idx < static_cast<int>(pending_data.size())) {
776 atlas_data[atlas_idx] = pending_data[pending_idx];
777 atlas_modified = true;
778 }
779 }
780 }
781 }
782
783 // Only queue texture update if we actually modified something
784 if (atlas_modified && ctx_.tile16_blockset->atlas.texture()) {
788 }
789}
790
791} // namespace yaze::editor
void RefreshMapProperties()
Refresh map properties (copy parent properties to siblings)
void RefreshMultiAreaMapsSafely(int map_index, zelda3::OverworldMap *map)
Safely refresh multi-area maps without recursion.
void RefreshChildMapOnDemand(int map_index)
On-demand child map refresh with selective updates.
void RefreshChildMap(int map_index)
Refresh a child map's graphics pipeline (legacy full rebuild)
absl::Status RefreshMapPalette()
Refresh map palette after palette property changes.
void InvalidateGraphicsCache(int map_id=-1)
Invalidate cached graphics for a specific map or all maps.
void RefreshOverworldMap()
Refresh the current overworld map.
absl::Status RefreshTile16Blockset()
Refresh the tile16 blockset after graphics/palette changes.
void RefreshSiblingMapGraphics(int map_index, bool include_self=false)
Refresh sibling map graphics for multi-area maps.
void UpdateBlocksetWithPendingTileChanges()
Update blockset atlas with pending tile16 editor changes.
void RefreshOverworldMapOnDemand(int map_index)
On-demand map refresh that only updates what's actually needed.
void ForceRefreshGraphics(int map_index)
Force refresh graphics for a specific map.
const gfx::Bitmap * GetPendingTileBitmap(int tile_id) const
Get preview bitmap for a pending tile (nullptr if not modified)
bool has_pending_changes() const
Check if any tiles have uncommitted changes.
bool is_tile_modified(int tile_id) const
Check if a specific tile has pending changes.
void set_palette(const gfx::SnesPalette &palette)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
static Arena & Get()
Definition arena.cc:21
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
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
TextureHandle texture() const
Definition bitmap.h:401
const std::vector< uint8_t > & vector() const
Definition bitmap.h:402
bool is_active() const
Definition bitmap.h:405
void set_modified(bool modified)
Definition bitmap.h:409
int height() const
Definition bitmap.h:395
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:394
int depth() const
Definition bitmap.h:396
std::vector< uint8_t > & mutable_data()
Definition bitmap.h:399
Represents a single Overworld map screen.
const std::vector< uint8_t > & current_graphics() const
void set_game_state(int state)
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
static bool SupportsAreaEnum(OverworldVersion version)
Check if ROM supports area enum system (v3+ only)
Represents the full Overworld data, light and dark world.
Definition overworld.h:389
void ClearGraphicsConfigCache()
Clear entire graphics config cache Call when palette or graphics settings change globally.
Definition overworld.h:446
void InvalidateSiblingMapCaches(int map_index)
Invalidate cached tilesets for a map and all its siblings.
const std::vector< gfx::Tile16 > & tiles16() const
Definition overworld.h:676
auto overworld_map(int i) const
Definition overworld.h:662
auto mutable_overworld_map(int i)
Definition overworld.h:668
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:646
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define PRINT_IF_ERROR(expression)
Definition macro.h:28
void SyncCurrentGraphicsBitmapForTile16Editor(const MapRefreshContext &ctx, const zelda3::OverworldMap &map)
zelda3::OverworldBlockset & MapTilesForMapIndex(zelda3::Overworld *overworld, int map_index)
void PrepareMapForRefresh(const MapRefreshContext &ctx, zelda3::OverworldMap *map)
Editors are the view controllers for the application.
void UpdateTilemap(IRenderer *renderer, Tilemap &tilemap, const std::vector< uint8_t > &data)
Definition tilemap.cc:34
constexpr int kNumTile16Individual
Definition overworld.h:241
constexpr int kSpecialWorldMapIdStart
constexpr int kNumOverworldMaps
Definition common.h:85
std::vector< std::vector< uint16_t > > OverworldBlockset
Represents tile32 data for the overworld.
AreaSizeEnum
Area size enumeration for v3+ ROMs.
constexpr int kDarkWorldMapIdStart
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Context struct holding all data dependencies for map refresh operations. All pointers/references must...
std::function< void(int map_index)> ensure_map_texture
Callback to ensure a map texture is created (stays in OverworldEditor)
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119