yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
project_graphics_import_controller.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstring>
5#include <set>
6
7#include "absl/strings/str_format.h"
10#include "core/project.h"
11#include "util/macro.h"
13
14namespace yaze::editor {
15namespace {
16bool SameDefinition(const gfx::Tile16& a, const gfx::Tile16& b) {
17 for (int q = 0; q < 4; ++q)
20 return false;
21 return true;
22}
23
26 if (a.source_sha256 != b.source_sha256 ||
29 a.stamp.height != b.stamp.height ||
30 a.stamp.tile_ids != b.stamp.tile_ids || a.notes != b.notes ||
31 a.definitions.size() != b.definitions.size() ||
32 a.palette_changes.size() != b.palette_changes.size())
33 return false;
34 for (size_t i = 0; i < a.definitions.size(); ++i)
35 if (a.definitions[i].tile_id != b.definitions[i].tile_id ||
36 !SameDefinition(a.definitions[i].tile_data, b.definitions[i].tile_data))
37 return false;
38 for (size_t i = 0; i < a.palette_changes.size(); ++i) {
39 const auto& x = a.palette_changes[i];
40 const auto& y = b.palette_changes[i];
41 if (x.group_name != y.group_name || x.palette_index != y.palette_index ||
42 x.color_index != y.color_index ||
43 x.original_color.snes() != y.original_color.snes() ||
44 x.new_color.snes() != y.new_color.snes())
45 return false;
46 }
47 return true;
48}
49
50std::pair<int, int> AreaDimensions(const zelda3::OverworldMap& map) {
51 const auto size = map.area_size();
52 return {size == zelda3::AreaSizeEnum::LargeArea ||
54 ? 64
55 : 32,
58 ? 64
59 : 32};
60}
61} // namespace
62
64 const auto* owner = TerrainProject();
65 return owner && !owner->overworld_graphics_preview.empty()
66 ? owner->GetAbsolutePath(owner->overworld_graphics_preview)
67 : std::string{};
68}
69
71 auto preview = graphics_preview_baseline_;
72 if (graphics_source_) {
73 const auto* owner = TerrainProject();
74 if (graphics_source_project_ != owner || !owner ||
75 graphics_source_->portable_catalog() != ProjectGraphicsPath() ||
76 graphics_source_->source_root() !=
77 owner->GetAbsolutePath(owner->git_repository)) {
78 return absl::FailedPreconditionError(
79 "Project graphics draft belongs to a different project binding");
80 }
81 ASSIGN_OR_RETURN(const auto pending,
82 graphics_source_->PendingGraphicsPreview());
83 for (const auto& [parent, characters] : pending)
84 for (const auto& [id, pixels] : characters)
85 preview[parent][id] = pixels;
86 }
87 overworld_.SetGraphicsPreview(std::move(preview));
88 if (overworld_.is_loaded()) {
90 for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
92 maps_bmp_[i].set_modified(true);
93 }
96 }
97 return absl::OkStatus();
98}
99
103
105 if (!graphics_source_)
106 return absl::FailedPreconditionError("Open project graphics first");
107 ASSIGN_OR_RETURN(auto plan, graphics_source_->Preview());
108 ASSIGN_OR_RETURN(auto published, graphics_source_->Publish());
109 return ReloadProjectGraphics();
110}
111
112absl::StatusOr<ProjectGraphicsImportContext>
114 const auto* selected = overworld_.overworld_map(current_map_);
115 if (!graphics_source_ || !selected || is_lyra_preview() ||
117 selected->parent() != graphics_source_->parent_map()) {
118 return absl::FailedPreconditionError(
119 "Select the native area bound to the open graphics source");
120 }
122 const auto* map = overworld_.overworld_map(current_map_);
123 const auto [width, height] = AreaDimensions(*map);
124 const int parent = map->parent();
125 const int origin_x = (parent % 8) * 32;
126 const int origin_y = ((parent % 64) / 8) * 32;
127 auto& tiles = overworld_.GetMapTiles(parent / 64);
128 std::vector<uint16_t> grid;
129 grid.reserve(width * height);
130 for (int y = 0; y < height; ++y) {
131 for (int x = 0; x < width; ++x) {
132 if (origin_x + x >= static_cast<int>(tiles.size()) ||
133 origin_y + y >= static_cast<int>(tiles[origin_x + x].size()))
134 return absl::OutOfRangeError("Area exceeds the loaded world grid");
135 grid.push_back(tiles[origin_x + x][origin_y + y]);
136 }
137 }
139 width, height);
140}
141
142absl::StatusOr<ProjectGraphicsImportPlan>
144 const std::string& pack, const ProjectGraphicsImportBindings& bindings) {
145 if (!graphics_source_ || !graphics_source_->selected_resource())
146 return absl::FailedPreconditionError("Open a project graphics resource");
147 const auto* resource = graphics_source_->selected_resource();
148 if (bindings.target_resource != resource->pending_bytes)
149 return absl::FailedPreconditionError(
150 "Graphics resource changed; preview again");
151 const auto slice = std::find_if(
152 resource->slices.begin(), resource->slices.end(), [&](const auto& s) {
153 return s.profile_id == graphics_source_->profile_id() &&
154 s.source_offset == 0 &&
155 s.byte_count == resource->pending_bytes.size() &&
156 s.first_character() == bindings.resource_first_character;
157 });
158 if (slice == resource->slices.end())
159 return absl::FailedPreconditionError(
160 "Import requires a complete, contiguous resource transfer in this "
161 "area");
164 ASSIGN_OR_RETURN(auto plan,
165 PlanProjectGraphicsImport(asset, bindings, context));
166
167 // Tile16 metadata is global, even when external character graphics are local.
168 // Never replace a live definition outside this area's planned stamp.
169 std::set<int> changed;
170 for (const auto& definition : plan.definitions)
171 if (!SameDefinition(overworld_.tiles16()[definition.tile_id],
172 definition.tile_data))
173 changed.insert(definition.tile_id);
174 const int parent = graphics_source_->parent_map();
175 const int ox = (parent % 8) * 32;
176 const int oy = ((parent % 64) / 8) * 32;
177 for (int world = 0; world < 3; ++world) {
178 const auto& tiles = overworld_.GetMapTiles(world);
179 for (int x = 0; x < static_cast<int>(tiles.size()); ++x) {
180 for (int y = 0; y < static_cast<int>(tiles[x].size()); ++y) {
181 const bool inside = world == parent / 64 &&
182 x >= ox + bindings.anchor_x &&
183 y >= oy + bindings.anchor_y &&
184 x < ox + bindings.anchor_x + asset.width &&
185 y < oy + bindings.anchor_y + asset.height;
186 if (!inside && changed.contains(tiles[x][y]))
187 return absl::FailedPreconditionError(absl::StrFormat(
188 "Tile16 $%03X is placed outside the import at world %d (%d,%d)",
189 tiles[x][y], world, x, y));
190 }
191 }
192 }
193 std::set<int> shared_parents;
194 for (int id = 0; id < zelda3::kNumOverworldMaps; ++id) {
195 auto* other = overworld_.mutable_overworld_map(id);
196 if (!other || other->parent() != id || id == parent)
197 continue;
199 RETURN_IF_ERROR(other->LoadPalette());
200 for (const auto& address : other->palette_addresses()) {
201 if (!address)
202 continue;
203 for (const auto& change : plan.palette_changes) {
204 if (change.group_name ==
205 zelda3::PaletteAddressGroupName(address->group) &&
206 change.palette_index == address->palette_index &&
207 change.color_index == address->color_index)
208 shared_parents.insert(id);
209 }
210 }
211 }
212 if (!shared_parents.empty()) {
213 std::string maps;
214 for (int id : shared_parents)
215 maps += absl::StrFormat("$%02X ", id);
216 plan.notes.push_back("Shared palette records also load in parent maps: " +
217 maps);
218 }
219 return plan;
220}
221
223 const std::string& pack, const ProjectGraphicsImportBindings& bindings) {
225 (graphics_source_->any_dirty() || graphics_source_->can_undo() ||
226 graphics_source_->can_redo()))
227 return absl::FailedPreconditionError(
228 "Publish or undo pixel edits before importing a graphics pack");
229 ASSIGN_OR_RETURN(auto plan, PreviewProjectGraphicsImport(pack, bindings));
231 !SameReviewedPlan(plan, *graphics_import_plan_)) {
233 graphics_import_plan_.reset();
234 return absl::FailedPreconditionError(
235 "The pack or destination changed after review; preview the import "
236 "again");
237 }
238 ASSIGN_OR_RETURN(auto definitions,
239 tile16_editor_.PrepareDefinitionImport(plan.definitions));
240 const auto* resource = graphics_source_->selected_resource();
241 const auto slices = resource->slices;
242 const auto* owner = TerrainProject();
243 const auto source = graphics_source_;
244 const auto resolve = [this, source, owner,
245 slices](const ProjectGraphicsSource& document)
246 -> absl::StatusOr<std::string> {
247 if (graphics_source_ != source || TerrainProject() != owner)
248 return absl::FailedPreconditionError(
249 "Import history belongs to another project");
250 for (const auto& candidate : document.resources()) {
251 if (candidate.slices.size() != slices.size())
252 continue;
253 bool same = true;
254 for (size_t i = 0; i < slices.size(); ++i) {
255 const auto& a = slices[i];
256 const auto& b = candidate.slices[i];
257 same &= a.profile_id == b.profile_id &&
258 a.parent_maps == b.parent_maps &&
259 a.source_offset == b.source_offset &&
260 a.byte_count == b.byte_count &&
261 a.vram_word_address == b.vram_word_address &&
262 a.bg_character_base_word == b.bg_character_base_word;
263 }
264 if (same)
265 return candidate.source_path;
266 }
267 return absl::FailedPreconditionError(
268 "Imported resource consumer binding changed");
269 };
271 auto action,
273 source, resolve, plan, std::move(definitions), game_data_,
274 [this]() { return RefreshPendingProjectGraphics(); }));
276 const bool was_dirty = rom_->dirty();
277 const auto applied = action->Redo();
278 if (!applied.ok()) {
279 rom_->set_dirty(was_dirty || absl::IsInternal(applied));
280 return applied;
281 }
282 undo_manager_.Push(std::move(action));
285 graphics_import_bindings_ = bindings;
287 graphics_import_plan_ = std::move(plan);
288 rom_->set_dirty(true);
289 return absl::OkStatus();
290}
291
293 int world_y) {
295 return absl::FailedPreconditionError("Import a graphics pack first");
296 auto bindings = graphics_import_applied_bindings_;
297 bindings.target_resource =
298 graphics_source_->selected_resource()->pending_bytes;
299 const int parent = graphics_source_->parent_map();
300 bindings.anchor_x = world_x - (parent % 8) * 32;
301 bindings.anchor_y = world_y - ((parent % 64) / 8) * 32;
304 if (plan.resource_after != plan.resource_before ||
305 !plan.palette_changes.empty())
306 return absl::FailedPreconditionError(
307 "This background needs another graphics import with separately owned "
308 "ranges");
309 for (const auto& definition : plan.definitions)
310 if (!SameDefinition(overworld_.tiles16()[definition.tile_id],
311 definition.tile_data))
312 return absl::FailedPreconditionError(
313 "This background needs different Tile16 definitions; preview a "
314 "separate import");
315 return tile_painting_->PlaceBrushAt(plan.stamp, world_x, world_y);
316}
317
318} // namespace yaze::editor
void set_dirty(bool dirty)
Definition rom.h:201
bool dirty() const
Definition rom.h:200
EditorContext context() const
Definition editor.h:376
UndoManager undo_manager_
Definition editor.h:383
ProjectGraphicsImportBindings graphics_import_applied_bindings_
bool IsProjectTerrainMap(int map_id) const
std::array< gfx::Bitmap, zelda3::kExpandedMapCount > maps_bmp_
void ForceRefreshGraphics(int map_index)
absl::Status ApplyProjectGraphicsImport(const std::string &pack, const ProjectGraphicsImportBindings &bindings)
zelda3::OverworldGraphicsPreview graphics_preview_baseline_
absl::Status SyncProjectGraphicsPreview(bool force=false)
absl::StatusOr< ProjectGraphicsImportPlan > PreviewProjectGraphicsImport(const std::string &pack, const ProjectGraphicsImportBindings &bindings)
void RefreshOverworldMapOnDemand(int map_index)
std::shared_ptr< ProjectGraphicsSource > graphics_source_
const project::YazeProject * graphics_source_project_
project::YazeProject * TerrainProject() const
std::optional< ProjectGraphicsImportPlan > graphics_import_plan_
absl::Status PlaceProjectGraphicsImport(int world_x, int world_y)
std::unique_ptr< TilePaintingManager > tile_painting_
ProjectGraphicsImportBindings graphics_import_bindings_
absl::StatusOr< ProjectGraphicsImportContext > GraphicsImportContext()
absl::StatusOr< std::unique_ptr< UndoAction > > PrepareDefinitionImport(const std::vector< Tile16Commit > &definitions)
void Push(std::unique_ptr< UndoAction > action)
Tile composition of four 8x8 tiles.
Definition snes_tile.h:142
Represents a single Overworld map screen.
void SyncAreaProperties(int map_index)
Copy the area parent's render settings (main palette, animated GFX, tile GFX groups,...
const std::vector< gfx::Tile16 > & tiles16() const
Definition overworld.h:784
auto is_loaded() const
Definition overworld.h:836
auto overworld_map(int i) const
Definition overworld.h:770
void SetGraphicsPreview(OverworldGraphicsPreview preview)
auto mutable_overworld_map(int i)
Definition overworld.h:776
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:754
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
bool SameReviewedPlan(const ProjectGraphicsImportPlan &a, const ProjectGraphicsImportPlan &b)
Editors are the view controllers for the application.
absl::StatusOr< ProjectGraphicsImportAsset > LoadProjectGraphicsImport(const std::filesystem::path &logical_pack)
absl::StatusOr< ProjectGraphicsImportContext > CaptureProjectGraphicsImportContext(const zelda3::OverworldMap &map, const std::vector< gfx::Tile16 > &definitions, const std::vector< uint16_t > &grid, int width, int height)
absl::StatusOr< ProjectGraphicsImportPlan > PlanProjectGraphicsImport(const ProjectGraphicsImportAsset &asset, const ProjectGraphicsImportBindings &bindings, const ProjectGraphicsImportContext &context)
absl::StatusOr< std::unique_ptr< UndoAction > > CreateProjectGraphicsImportAction(ProjectGraphicsImportResourceCallbacks resource, const ProjectGraphicsImportPlan &plan, std::unique_ptr< UndoAction > prepared_definitions, const zelda3::GameData *owner, ProjectGraphicsImportRefresh refresh, std::string description)
uint16_t TileInfoToShort(TileInfo tile_info)
Definition snes_tile.cc:394
constexpr int kNumOverworldMaps
Definition common.h:85
const char * PaletteAddressGroupName(PaletteAddressGroup group)
const gfx::TileInfo & Tile16QuadrantInfo(const gfx::Tile16 &tile, int quadrant)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::vector< gfx::PaletteColorChange > palette_changes
std::vector< int > tile_ids
Definition tile_brush.h:15
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:2675