Porytiles
Loading...
Searching...
No Matches
project_tileset_artifact_key_provider.cpp
Go to the documentation of this file.
2
3#include <filesystem>
4#include <format>
5#include <set>
6#include <string>
7
12
13namespace {
14
15using namespace porytiles;
16
17// Artifact paths
18const std::filesystem::path src_dir{"src"};
19const std::filesystem::path porytiles_generated_include{"include/porytiles_generated/tilesets"};
20const std::filesystem::path porytiles_src{"porytiles_src"};
21const std::filesystem::path porytiles_bin{"porytiles_bin"};
22const std::filesystem::path anim_dir{"anim"};
23const std::filesystem::path generated_anim_code_header{"generated_anim_code.h"};
24const std::filesystem::path bottom_png{"bottom.png"};
25const std::filesystem::path middle_png{"middle.png"};
26const std::filesystem::path top_png{"top.png"};
27const std::filesystem::path attributes_csv{"attributes.csv"};
28const std::filesystem::path porytiles_pals{"palettes"};
29const std::filesystem::path porymap_pals{"palettes"};
30const std::filesystem::path anim_json{"anim.json"};
31const std::filesystem::path metatiles_bin{"metatiles.bin"};
32const std::filesystem::path attrs_bin{"metatile_attributes.bin"};
33const std::filesystem::path tiles_png{"tiles.png"};
34
48[[nodiscard]] ChainableResult<std::set<std::string>> scan_subdirectories(
49 const std::filesystem::path &dir_path, const std::string &artifact_type, const TextFormatter *format)
50{
51 if (!std::filesystem::exists(dir_path) || !std::filesystem::is_directory(dir_path)) {
52 return std::set<std::string>{};
53 }
54
55 std::set<std::string> result;
56 for (const auto &entry : std::filesystem::directory_iterator{dir_path}) {
57 if (!entry.is_directory()) {
58 continue;
59 }
60 const std::string dir_name = entry.path().filename().string();
61 const auto expected_snake = DynamicCasedName{dir_name}.to_snake_case();
62 if (expected_snake != dir_name) {
64 "{} directory name '{}' must be snake_case (expected '{}')",
65 FormatParam{artifact_type, Style::bold},
66 FormatParam{dir_name, Style::bold},
67 FormatParam{expected_snake, Style::bold})}};
68 }
69 result.insert(dir_name);
70 }
71 return result;
72}
73
88scan_png_files(const std::filesystem::path &dir_path, const std::string &artifact_type, const TextFormatter *format)
89{
90 if (!std::filesystem::exists(dir_path) || !std::filesystem::is_directory(dir_path)) {
91 return std::set<std::string>{};
92 }
93
94 std::set<std::string> result;
95 for (const auto &entry : std::filesystem::directory_iterator{dir_path}) {
96 if (!entry.is_regular_file()) {
97 continue;
98 }
99 const auto &path = entry.path();
100 if (path.extension() != ".png") {
101 continue;
102 }
103 const std::string stem = path.stem().string();
104 const auto expected_snake = DynamicCasedName{stem}.to_snake_case();
105 if (expected_snake != stem) {
107 "{} file name '{}' must be snake_case (expected '{}.png')",
108 FormatParam{artifact_type, Style::bold},
109 FormatParam{path.filename().string(), Style::bold},
110 FormatParam{expected_snake, Style::bold})}};
111 }
112 result.insert(stem);
113 }
114 return result;
115}
116
117} // namespace
118
119namespace porytiles {
120
121/*
122 * Porymap artifacts
123 */
126{
127 // Get primary/secondary status of tileset
129 is_secondary,
130 metadata_provider_->is_secondary(tileset_name),
132 "Failed to determine primary/secondary status.");
133
134 // Get base path from config
135 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
136 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
137 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
138 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
139 std::filesystem::path path =
140 std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_bin / metatiles_bin;
141
142 return ArtifactKey{path.string()};
143}
144
147{
148 // Get primary/secondary status of tileset
150 is_secondary,
151 metadata_provider_->is_secondary(tileset_name),
153 "Failed to determine primary/secondary status.");
154
155 // Get base path from config
156 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
157 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
158 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
159 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
160 std::filesystem::path path =
161 std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_bin / attrs_bin;
162
163 return ArtifactKey{path.string()};
164}
165
167{
168 // Get primary/secondary status of tileset
170 is_secondary,
171 metadata_provider_->is_secondary(tileset_name),
173 "Failed to determine primary/secondary status.");
174
175 // Get base path from config
176 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
177 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
178 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
179 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
180 std::filesystem::path path =
181 std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_bin / tiles_png;
182
183 return ArtifactKey{path.string()};
184}
185
187ProjectTilesetArtifactKeyProvider::key_for_porymap_pal_n(const std::string &tileset_name, std::size_t index) const
188{
189 // Get primary/secondary status of tileset
191 is_secondary,
192 metadata_provider_->is_secondary(tileset_name),
194 "Failed to determine primary/secondary status.");
195
196 // Get base path from config
197 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
198 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
199 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
200 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
201 std::filesystem::path path = std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_bin /
202 porymap_pals / pal_filename(index);
203
204 return ArtifactKey{path.string()};
205}
206
208 const std::string &tileset_name, const std::string &anim_name, const std::string &frame_name) const
209{
210 // Get primary/secondary status of tileset
212 is_secondary,
213 metadata_provider_->is_secondary(tileset_name),
215 "Failed to determine primary/secondary status.");
216
217 // Get base path from config
218 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
219 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
220 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
221 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
222 std::filesystem::path path = std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_bin /
223 anim_dir / anim_name / (frame_name + std::string{".png"});
224
225 return ArtifactKey{path.string()};
226}
227
230{
231 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
232 std::filesystem::path path = porytiles_generated_include / snake_tileset_dir / generated_anim_code_header;
233 return ArtifactKey{path.string()};
234}
235
236/*
237 * Porytiles artifacts
238 */
240ProjectTilesetArtifactKeyProvider::key_for_bottom_png(const std::string &tileset_name) const
241{
242 // Get primary/secondary status of tileset
244 is_secondary,
245 metadata_provider_->is_secondary(tileset_name),
247 "Failed to determine primary/secondary status.");
248
249 // Get base path from config
250 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
251 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
252 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
253 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
254 std::filesystem::path path =
255 std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_src / bottom_png;
256
257 return ArtifactKey{path.string()};
258}
259
261ProjectTilesetArtifactKeyProvider::key_for_middle_png(const std::string &tileset_name) const
262{
263 // Get primary/secondary status of tileset
265 is_secondary,
266 metadata_provider_->is_secondary(tileset_name),
268 "Failed to determine primary/secondary status.");
269
270 // Get base path from config
271 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
272 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
273 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
274 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
275 std::filesystem::path path =
276 std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_src / middle_png;
277
278 return ArtifactKey{path.string()};
279}
280
282{
283 // Get primary/secondary status of tileset
285 is_secondary,
286 metadata_provider_->is_secondary(tileset_name),
288 "Failed to determine primary/secondary status.");
289
290 // Get base path from config
291 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
292 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
293 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
294 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
295 std::filesystem::path path = std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_src / top_png;
296
297 return ArtifactKey{path.string()};
298}
299
302{
303 // Get primary/secondary status of tileset
305 is_secondary,
306 metadata_provider_->is_secondary(tileset_name),
308 "Failed to determine primary/secondary status.");
309
310 // Get base path from config
311 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
312 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
313 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
314 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
315 std::filesystem::path path =
316 std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_src / attributes_csv;
317
318 return ArtifactKey{path.string()};
319}
320
322ProjectTilesetArtifactKeyProvider::key_for_porytiles_pal_n(const std::string &tileset_name, std::size_t index) const
323{
324 // Get primary/secondary status of tileset
326 is_secondary,
327 metadata_provider_->is_secondary(tileset_name),
329 "Failed to determine primary/secondary status.");
330
331 // Get base path from config
332 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
333 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
334 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
335 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
336 std::filesystem::path path = std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_src /
337 porytiles_pals / pal_filename(index);
338
339 return ArtifactKey{path.string()};
340}
341
343 const std::string &tileset_name, const std::string &anim_name, const std::string &frame_name) const
344{
345 // Get primary/secondary status of tileset
347 is_secondary,
348 metadata_provider_->is_secondary(tileset_name),
350 "Failed to determine primary/secondary status.");
351
352 // Get base path from config
353 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
354 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
355 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
356 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
357 std::filesystem::path path = std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_src /
358 anim_dir / anim_name / (frame_name + std::string{".png"});
359
360 return ArtifactKey{path.string()};
361}
362
365{
366 // Get primary/secondary status of tileset
368 is_secondary,
369 metadata_provider_->is_secondary(tileset_name),
371 "Failed to determine primary/secondary status.");
372
373 // Get base path from config
374 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, ArtifactKey);
375 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, ArtifactKey);
376 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
377 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
378 std::filesystem::path path =
379 std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_src / anim_dir / anim_json;
380
381 return ArtifactKey{path.string()};
382}
383
385{
386 // Keys are relative to project_root_, so prepend for filesystem operations
387 const std::filesystem::path artifact = project_root_ / key.key();
388 return std::filesystem::exists(artifact);
389}
390
393{
395 is_secondary,
396 metadata_provider_->is_secondary(tileset_name),
397 std::set<std::string>,
398 "Failed to determine primary/secondary status.");
399 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, std::set<std::string>);
400 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, std::set<std::string>);
401 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
402 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
403
404 const std::filesystem::path anim_path =
405 project_root_ / std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_bin / anim_dir;
406
407 return scan_subdirectories(anim_path, "Porymap animation", format_);
408}
409
411 const std::string &tileset_name, const std::string &anim_name) const
412{
414 is_secondary,
415 metadata_provider_->is_secondary(tileset_name),
416 std::set<std::string>,
417 "Failed to determine primary/secondary status.");
418 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_bin, tileset_name, std::set<std::string>);
419 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_bin, tileset_name, std::set<std::string>);
420 auto base_path = is_secondary ? tileset_paths_secondary_bin : tileset_paths_primary_bin;
421 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
422
423 const std::filesystem::path frame_dir = project_root_ / std::filesystem::path{base_path.value()} /
424 snake_tileset_dir / porytiles_bin / anim_dir / anim_name;
425
426 return scan_png_files(frame_dir, "Porymap animation frame", format_);
427}
428
431{
433 is_secondary,
434 metadata_provider_->is_secondary(tileset_name),
435 std::set<std::string>,
436 "Failed to determine primary/secondary status.");
437 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_src, tileset_name, std::set<std::string>);
438 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_src, tileset_name, std::set<std::string>);
439 auto base_path = is_secondary ? tileset_paths_secondary_src : tileset_paths_primary_src;
440 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
441
442 const std::filesystem::path anim_path =
443 project_root_ / std::filesystem::path{base_path.value()} / snake_tileset_dir / porytiles_src / anim_dir;
444
445 return scan_subdirectories(anim_path, "Porytiles animation", format_);
446}
447
449 const std::string &tileset_name, const std::string &anim_name) const
450{
452 is_secondary,
453 metadata_provider_->is_secondary(tileset_name),
454 std::set<std::string>,
455 "Failed to determine primary/secondary status.");
456 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_secondary_src, tileset_name, std::set<std::string>);
457 PT_UNWRAP_TILESET_CONFIG_PTR(config_, tileset_paths_primary_src, tileset_name, std::set<std::string>);
458 auto base_path = is_secondary ? tileset_paths_secondary_src : tileset_paths_primary_src;
459 const std::string snake_tileset_dir = extract_tileset_cased_name(tileset_name).to_snake_case();
460
461 const std::filesystem::path frame_dir = project_root_ / std::filesystem::path{base_path.value()} /
462 snake_tileset_dir / porytiles_src / anim_dir / anim_name;
463
464 return scan_png_files(frame_dir, "Porytiles animation frame", format_);
465}
466
467} // namespace porytiles
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
A type-safe wrapper for artifact keys.
const std::string & key() const
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.
std::string to_snake_case() const
Outputs all words flattened and joined with underscores.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:63
bool artifact_exists(const ArtifactKey &key) const override
Checks whether an artifact exists in the backing store for the given key.
ChainableResult< ArtifactKey > key_for_porymap_anim_params(const std::string &tileset_name) const override
Returns the key for Porymap animation parameters.
ChainableResult< std::set< std::string > > discover_porymap_anim_frames(const std::string &tileset_name, const std::string &anim_name) const override
Discovers the frame indices for a specific Porymap animation.
ChainableResult< ArtifactKey > key_for_middle_png(const std::string &tileset_name) const override
ChainableResult< ArtifactKey > key_for_metatile_attributes_bin(const std::string &tileset_name) const override
ChainableResult< ArtifactKey > key_for_top_png(const std::string &tileset_name) const override
ChainableResult< std::set< std::string > > discover_porytiles_anims(const std::string &tileset_name) const override
Discovers the names of all Porytiles animations available for a tileset.
ChainableResult< ArtifactKey > key_for_porymap_pal_n(const std::string &tileset_name, std::size_t index) const override
ChainableResult< ArtifactKey > key_for_porytiles_anim_params(const std::string &tileset_name) const override
Returns the key for the anim.json file (Porytiles animation configuration).
ChainableResult< std::set< std::string > > discover_porytiles_anim_frames(const std::string &tileset_name, const std::string &anim_name) const override
Discovers the frame indices for a specific Porytiles animation.
ChainableResult< ArtifactKey > key_for_bottom_png(const std::string &tileset_name) const override
ChainableResult< ArtifactKey > key_for_porytiles_anim_frame(const std::string &tileset_name, const std::string &anim_name, const std::string &frame_name) const override
ChainableResult< ArtifactKey > key_for_tiles_png(const std::string &tileset_name) const override
ChainableResult< ArtifactKey > key_for_attributes_csv(const std::string &tileset_name) const override
ChainableResult< std::set< std::string > > discover_porymap_anims(const std::string &tileset_name) const override
Discovers the names of all Porymap animations available for a tileset.
ChainableResult< ArtifactKey > key_for_metatiles_bin(const std::string &tileset_name) const override
ChainableResult< ArtifactKey > key_for_porymap_anim_frame(const std::string &tileset_name, const std::string &anim_name, const std::string &frame_name) const override
ChainableResult< ArtifactKey > key_for_porytiles_pal_n(const std::string &tileset_name, std::size_t index) const override
static const Style bold
Bold text formatting.
Abstract base class for applying text styling with context-aware formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.
virtual ChainableResult< bool > is_secondary(const std::string &tileset_name) const =0
Determines whether a tileset is a secondary tileset.
std::string pal_filename(std::size_t pal_index)
Constructs a palette filename from a palette index.
DynamicCasedName extract_tileset_cased_name(const std::string &tileset_name)
Extracts the tileset short name and wraps it in a DynamicCasedName.
Utility functions for string manipulation and formatting.
#define PT_UNWRAP_TILESET_CONFIG_PTR(ptr, config, tileset_name, return_type)
Unwraps a tileset-scoped config value via pointer access, returning early if the value is not availab...