Porytiles
Loading...
Searching...
No Matches
project_tileset_artifact_reader.cpp
Go to the documentation of this file.
2
3#include <expected>
4#include <filesystem>
5#include <format>
6#include <fstream>
7#include <functional>
8#include <iterator>
9#include <optional>
10
22
23#include <iostream>
24
25namespace {
26
27using namespace porytiles;
28
29ChainableResult<void> import_layer_png(
30 Tileset &dest,
31 const ArtifactKey &src_key,
32 const std::filesystem::path &project_root,
33 const PngRgbaImageLoader &loader,
34 const std::function<void(PorytilesTilesetComponent &, const Image<Rgba32> &)> &layer_img_setter)
35{
36 // Keys are relative to project_root, so prepend for file I/O
37 auto image_result = loader.load_from_file((project_root / src_key.key()).string());
38 if (!image_result.has_value()) {
39 switch (image_result.error().type()) {
40 case ImageLoadError::Type::file_not_found:
41 case ImageLoadError::Type::unsupported_channel_count:
42 case ImageLoadError::Type::other_load_error:
44 FormattableError{"Failed to load layer image '{}'.", FormatParam{src_key.key(), Style::bold}},
45 image_result};
46 default:
47 panic("unhandled ImageLoadError type");
48 }
49 }
50 layer_img_setter(dest.porytiles_component(), *image_result.value());
51 return {};
52}
53
54ChainableResult<void> import_porytiles_palette(
55 Tileset &dest,
56 const ArtifactKey &src_key,
57 std::size_t index,
58 const std::filesystem::path &project_root,
59 const FilePalLoader &loader)
60{
61 if (index >= pal::num_pals) {
62 panic(std::format("invalid pal index {}: out of range", index));
63 }
64
65 // Keys are relative to project_root, so prepend for file I/O
67 palette,
68 loader.load_with_wildcards((project_root / src_key.key()).string()),
69 void,
70 "Failed to load palette file.");
71 dest.porytiles_component().set_pal(index, palette);
72
73 return {};
74}
75
97template <SupportsTransparency PixelType, typename LoaderType, typename ComponentGetter>
98ChainableResult<void> import_anim_frame_impl(
99 Tileset &dest,
100 const ArtifactKey &src_key,
101 const std::filesystem::path &project_root,
102 const std::string &anim_name,
103 const std::string &frame_name,
104 const LoaderType &loader,
105 ComponentGetter component_getter,
106 std::string_view error_context)
107{
108 // Use shared helper to load PNG and extract tiles
109 const auto png_path = project_root / src_key.key();
111 load_result,
112 load_animation_frame_from_png<PixelType>(png_path, frame_name, loader),
113 void,
114 "{}: failed to load {}",
115 FormatParam(src_key.key(), Style::bold),
116 FormatParam(std::string(error_context)));
117
118 // Get or create the animation in the component
119 auto &component = component_getter(dest);
120 if (!component.has_anim(anim_name)) {
121 Animation<PixelType> anim{anim_name};
122 component.add_anim(std::move(anim));
123 }
124
125 auto &anim = component.anims().at(anim_name);
126
127 if (frame_name == "key") {
128 // Key frame
129 anim.key_frame(std::move(load_result.frame));
130 }
131 else {
132 // Regular frame - use string-based map storage
133 anim.put_frame(frame_name, std::move(load_result.frame));
134 }
135
136 // Update dimensions in params if not already set
137 auto params = anim.params();
138 if (params.width_tiles() == 0 && params.height_tiles() == 0) {
139 params.width_tiles(load_result.width_tiles);
140 params.height_tiles(load_result.height_tiles);
141 anim.params(std::move(params));
142 }
143
144 return {};
145}
146
147} // namespace
148
149namespace porytiles {
150
151/*
152 * Porymap artifacts
153 */
155{
156 // Keys are relative to project_root_, so prepend for file I/O
157 PT_TRY_ASSIGN_PASS_ERR(entries, parse_metatiles_bin(project_root_ / src_key.key()), void);
158 for (auto &entry : entries) {
159 dest.porymap_component().push_back_tilemap_entry(std::move(entry));
160 }
161 return {};
162}
163
166{
167 // Keys are relative to project_root_, so prepend for file I/O
168 const auto path = project_root_ / src_key.key();
170 attributes,
173 void,
174 "Failed to read metatile_attributes.bin.");
175 for (auto &attr : attributes) {
176 dest.porymap_component().push_back_attribute(std::move(attr));
177 }
178 return {};
179}
180
182{
183 // Keys are relative to project_root_, so prepend for file I/O
184 PT_TRY_ASSIGN_PASS_ERR(image, load_indexed_png(project_root_ / src_key.key(), *png_indexed_loader_), void);
185 dest.porymap_component().tiles_png(*image);
186 return {};
187}
188
190ProjectTilesetArtifactReader::read_porymap_pal_n(Tileset &dest, const ArtifactKey &src_key, std::size_t index) const
191{
192 if (index >= pal::num_pals) {
193 panic(std::format("invalid pal index {}: out of range", index));
194 }
195 // Keys are relative to project_root_, so prepend for file I/O
196 PT_TRY_ASSIGN_PASS_ERR(palette, load_porymap_palette(project_root_ / src_key.key(), *pal_loader_), void);
197 dest.porymap_component().set_pal(index, std::move(palette));
198 return {};
199}
200
202 Tileset &dest,
203 const std::string &anim_name,
204 const ArtifactKey &params_key,
205 const std::vector<std::pair<std::string, ArtifactKey>> &frame_keys) const
206{
207 // Load each frame using the unified template helper
208 // The first call creates the animation in the component, subsequent calls add frames to it
209 for (const auto &[frame_name, frame_key] : frame_keys) {
211 (import_anim_frame_impl<IndexPixel>(
212 dest,
213 frame_key,
214 project_root_,
215 anim_name,
216 frame_name,
217 *png_indexed_loader_,
218 [](Tileset &t) -> PorymapTilesetComponent & { return t.porymap_component(); },
219 "Porymap animation frame")),
220 void);
221 }
222
223 // Skip param loading if there is no generated header file present
224 std::filesystem::path params_path = project_root_ / params_key.key();
225 if (!std::filesystem::exists(params_path)) {
226 return {};
227 }
228
229 // Setup callback info, use Porytiles-managed callback regardless of metadata
230 const auto tileset_cased = extract_tileset_cased_name(dest.name());
231 const std::string callback_func = "InitTilesetAnim_PorytilesManaged_" + tileset_cased.to_pascal_case();
232
233 // Parse C code for animation params
235 parsed_params,
236 anim_code_parser_->parse_from_callback(params_path, callback_func, tileset_cased, true),
237 void,
238 "{}: Failed to parse animation parameters.",
239 FormatParam(params_path, Style::bold));
240
241 // Apply params to the specific animation if found
242 if (parsed_params.contains(DynamicCasedName{anim_name})) {
243 auto &anim = dest.porymap_component().anims().at(anim_name);
244 auto existing_params = anim.params();
245 auto new_params = parsed_params.at(DynamicCasedName{anim_name});
246
247 // Preserve dimensions from frame import (C code doesn't have this info)
248 new_params.width_tiles(existing_params.width_tiles());
249 new_params.height_tiles(existing_params.height_tiles());
250
251 anim.params(std::move(new_params));
252 }
253
254 return {};
255}
256
257/*
258 * Porytiles artifacts
259 */
261{
263 import_layer_png(
264 dest,
265 src_key,
266 project_root_,
267 *png_rgba_loader_,
268 [](PorytilesTilesetComponent &comp, const Image<Rgba32> &img) { comp.bottom(img); }),
269 void);
270 return {};
271}
272
274{
276 import_layer_png(
277 dest,
278 src_key,
279 project_root_,
280 *png_rgba_loader_,
281 [](PorytilesTilesetComponent &comp, const Image<Rgba32> &img) { comp.middle(img); }),
282 void);
283 return {};
284}
285
287{
289 import_layer_png(
290 dest,
291 src_key,
292 project_root_,
293 *png_rgba_loader_,
294 [](PorytilesTilesetComponent &comp, const Image<Rgba32> &img) { comp.top(img); }),
295 void);
296 return {};
297}
298
300{
301 // Keys are relative to project_root_, so prepend for file I/O
302 PT_TRY_ASSIGN_PASS_ERR(attributes, attributes_csv_loader_->load((project_root_ / src_key.key()).string()), void);
303 for (const auto &[metatile_id, attribute] : attributes) {
304 dest.porytiles_component().insert_attribute(metatile_id, attribute);
305 }
306 return {};
307}
308
310ProjectTilesetArtifactReader::read_porytiles_pal_n(Tileset &dest, const ArtifactKey &src_key, std::size_t index) const
311{
312 PT_TRY_CALL_PASS_ERR(import_porytiles_palette(dest, src_key, index, project_root_, *pal_loader_), void);
313 return {};
314}
315
317 Tileset &dest,
318 const std::string &anim_name,
319 const ArtifactKey &params_key,
320 const std::optional<ArtifactKey> &key_frame_key,
321 const std::vector<std::pair<std::string, ArtifactKey>> &frame_keys) const
322{
323 // Parse anim.json to get params for this animation
324 // Keys are relative to project_root_, so prepend for file I/O
326 parsed_anim_params,
327 anim_json_parser_->parse((project_root_ / params_key.key()).string()),
328 void,
329 "{}: Failed to parse animation parameters.",
330 FormatParam(project_root_ / params_key.key(), Style::bold));
331
332 // Find params for this specific animation
333 auto it = parsed_anim_params.find(DynamicCasedName{anim_name});
334 if (it == parsed_anim_params.end()) {
336 "{}: Animation '{}' not found in animation parameters file.",
337 FormatParam{project_root_ / params_key.key(), Style::bold},
338 FormatParam{anim_name, Style::bold}}};
339 }
340
341 const auto &params = it->second;
342
343 // Create the animation with params FIRST, before loading frames
344 // This ensures YAML-specified width_tiles/height_tiles take precedence over auto-detected dimensions
345 Animation<Rgba32> anim{anim_name, params};
346 dest.porytiles_component().add_anim(std::move(anim));
347
348 // Load key frame using the unified template helper (only present for automatic/hybrid frame linking)
349 if (key_frame_key.has_value()) {
351 (import_anim_frame_impl<Rgba32>(
352 dest,
353 key_frame_key.value(),
354 project_root_,
355 anim_name,
356 "key",
357 *png_rgba_loader_,
358 [](Tileset &t) -> PorytilesTilesetComponent & { return t.porytiles_component(); },
359 "Porytiles animation frame")),
360 void);
361 }
362
363 // Load remaining frames using the unified template helper
364 for (const auto &[frame_name, frame_key] : frame_keys) {
366 (import_anim_frame_impl<Rgba32>(
367 dest,
368 frame_key,
369 project_root_,
370 anim_name,
371 frame_name,
372 *png_rgba_loader_,
373 [](Tileset &t) -> PorytilesTilesetComponent & { return t.porytiles_component(); },
374 "Porytiles animation frame")),
375 void);
376 }
377
378 return {};
379}
380
381[[nodiscard]] ChainableResult<void>
383{
384 const auto json_path = project_root_ / params_key.key();
385
387 parsed_refs,
388 anim_json_parser_->parse_primary_references(json_path),
389 void,
390 "{}: Failed to parse primary animation references.",
391 FormatParam(json_path, Style::bold));
392
393 if (parsed_refs.empty()) {
394 return {};
395 }
396
397 std::map<std::string, std::vector<AnimOverrideEntry>> converted;
398 for (auto &[cased_name, entries] : parsed_refs) {
399 converted[cased_name.to_snake_case()] = std::move(entries);
400 }
401
402 dest.porytiles_component().primary_anim_overrides(std::move(converted));
403 return {};
404}
405
406} // namespace porytiles
Shared helper for loading animation frames from PNG files.
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
#define PT_TRY_CALL_PASS_ERR(expr, return_type)
Unwraps a void ChainableResult, passing through the error chain with an empty FormattableError when t...
#define PT_TRY_ASSIGN_PASS_ERR(var, expr, return_type)
Unwraps a ChainableResult, passing through the error chain with an empty FormattableError when types ...
ChainableResult< std::map< DynamicCasedName, AnimParams > > parse_from_callback(const std::filesystem::path &c_file_path, const std::string &callback_func_name, const DynamicCasedName &tileset_cased_name, bool porytiles_managed) const
Parses animation parameters by following the callback chain.
ChainableResult< std::map< DynamicCasedName, std::vector< AnimOverrideEntry > > > parse_primary_references(const std::filesystem::path &json_path) const
Parses the primary_references section from an anim.json file.
ChainableResult< std::map< DynamicCasedName, AnimParams > > parse(const std::filesystem::path &json_path) const
Parses an anim.json file into a map of animation parameters.
A complete tileset animation with name, configuration, and frame data.
Definition animation.hpp:78
const AnimFrame< PixelType > & key_frame() const
Returns the key frame of this animation.
A type-safe wrapper for artifact keys.
const std::string & key() const
ChainableResult< std::map< std::size_t, MetatileAttribute > > load(const std::filesystem::path &path) const
Loads metatile attributes from a CSV file.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A smart string wrapper that preserves word structure for lossless case format conversion.
A service interface that loads a fixed-length Palette from a given file.
virtual ChainableResult< Palette< Rgba32, pal::max_size > > load_with_wildcards(const std::filesystem::path &path) const =0
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:63
A template for two-dimensional images with arbitrarily typed pixel values.
Definition image.hpp:23
An image loader that reads PNG files to create an Image with an Rgba32 pixel type.
ChainableResult< std::unique_ptr< Image< Rgba32 > >, ImageLoadError > load_from_file(const std::filesystem::path &path) const
const std::map< std::string, Animation< IndexPixel > > & anims() const
void push_back_tilemap_entry(TilemapEntry entry)
Add a TilemapEntry to the end of the entry vector.
void set_pal(std::size_t pal_index, Palette< Rgba32, pal::max_size > pal)
void push_back_attribute(MetatileAttribute attribute)
Add a MetatileAttribute to the end of the attribute vector.
const Image< IndexPixel > & tiles_png() const
void set_pal(std::size_t pal_index, Palette< Rgba32, pal::max_size > pal)
void insert_attribute(std::size_t metatile_id, MetatileAttribute attribute)
Insert a MetatileAttribute to the end of the attribute vector.
const std::map< std::string, std::vector< AnimOverrideEntry > > & primary_anim_overrides() const
ChainableResult< void > read_metatile_attributes_bin(Tileset &dest, const ArtifactKey &src_key) const override
ChainableResult< void > read_top_png(Tileset &dest, const ArtifactKey &src_key) const override
ChainableResult< void > read_porytiles_pal_n(Tileset &dest, const ArtifactKey &src_key, std::size_t index) const override
ChainableResult< void > read_porymap_pal_n(Tileset &dest, const ArtifactKey &src_key, std::size_t index) const override
ChainableResult< void > read_attributes_csv(Tileset &dest, const ArtifactKey &src_key) const override
ChainableResult< void > read_middle_png(Tileset &dest, const ArtifactKey &src_key) const override
ChainableResult< void > read_porytiles_anim(Tileset &dest, const std::string &anim_name, const ArtifactKey &params_key, const std::optional< ArtifactKey > &key_frame_key, const std::vector< std::pair< std::string, ArtifactKey > > &frame_keys) const override
ChainableResult< void > read_bottom_png(Tileset &dest, const ArtifactKey &src_key) const override
ChainableResult< void > read_porymap_anim(Tileset &dest, const std::string &anim_name, const ArtifactKey &params_key, const std::vector< std::pair< std::string, ArtifactKey > > &frame_keys) const override
ChainableResult< void > read_porytiles_primary_anim_references(Tileset &dest, const ArtifactKey &params_key) const override
ChainableResult< void > read_tiles_png(Tileset &dest, const ArtifactKey &src_key) const override
ChainableResult< void > read_metatiles_bin(Tileset &dest, const ArtifactKey &src_key) const override
static const Style bold
Bold text formatting.
A complete tileset containing both Porytiles and Porymap components.
Definition tileset.hpp:14
const PorytilesTilesetComponent & porytiles_component() const
Definition tileset.hpp:37
const PorymapTilesetComponent & porymap_component() const
Definition tileset.hpp:47
const std::string & name() const
Definition tileset.hpp:32
constexpr std::size_t bytes_per_attr_firered
constexpr std::size_t num_pals
Definition palette.hpp:21
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.cpp:43
ChainableResult< std::vector< MetatileAttribute > > parse_firered_metatile_attributes(const std::filesystem::path &path)
Parses a metatile_attributes.bin file for FireRed format.
ChainableResult< Palette< Rgba32, pal::max_size > > load_porymap_palette(const std::filesystem::path &path, const FilePalLoader &loader)
Loads a Porymap palette file (e.g., 00.pal).
ChainableResult< std::vector< MetatileAttribute > > parse_emerald_metatile_attributes(const std::filesystem::path &path)
Parses a metatile_attributes.bin file for Emerald format.
DynamicCasedName extract_tileset_cased_name(const std::string &tileset_name)
Extracts the tileset short name and wraps it in a DynamicCasedName.
ChainableResult< std::vector< TilemapEntry > > parse_metatiles_bin(const std::filesystem::path &path)
Parses a metatiles.bin file into TilemapEntry objects.
ChainableResult< std::unique_ptr< Image< IndexPixel > > > load_indexed_png(const std::filesystem::path &path, const PngIndexedImageLoader &loader)
Loads an indexed PNG file (e.g., tiles.png).
Utility functions for string manipulation and formatting.