Porytiles
Loading...
Searching...
No Matches
yaml_file_provider_impl.ipp
Go to the documentation of this file.
1#pragma once
2
3#include <cstdint>
4#include <filesystem>
5#include <fstream>
6#include <map>
7#include <sstream>
8#include <string>
9
10#include "yaml-cpp/yaml.h"
11
32
33// The anonymous namespace ensures internal linkage per translation unit
34// This file is intentionally included only in yaml_file_provider.cpp
35namespace {
36
37using namespace porytiles;
38
39// Static caches shared across all YamlFileProvider instances
40std::map<std::filesystem::path, YAML::Node> yaml_cache;
41std::map<std::filesystem::path, std::vector<std::string>> file_lines_cache;
42
54std::string get_line_content(const std::filesystem::path &path, std::size_t line_num)
55{
56 const auto it = file_lines_cache.find(path);
57 if (it != file_lines_cache.end() && line_num < it->second.size()) {
58 return it->second[line_num];
59 }
60 return "";
61}
62
76std::string make_source_string(const TextFormatter *format, const std::string &file_path, const YAML::Mark &mark)
77{
78 return format->format("{}:{}", FormatParam{file_path}, FormatParam{mark.line + 1});
79}
80
105std::vector<std::string>
106make_source_details(const TextFormatter *format, const std::string &file_path, const YAML::Mark &mark)
107{
108 const std::filesystem::path path{file_path};
109 const auto it = file_lines_cache.find(path);
110 if (it == file_lines_cache.end()) {
111 return {};
112 }
113
114 const auto &lines = it->second;
115 const std::size_t line_num = mark.line; // 0-indexed
116
117 if (lines.empty() || line_num >= lines.size()) {
118 return {};
119 }
120
121 // Use FileHighlightPrinter (line_num is already 0-indexed)
122 const FileHighlightPrinter printer{format};
123 return printer.print(lines, std::vector{line_num});
124}
125
137void collect_yaml_paths(
138 const YAML::Node &node, const std::string &prefix, std::vector<std::pair<std::string, YAML::Mark>> &paths)
139{
140 if (!node.IsMap()) {
141 return;
142 }
143
144 for (const auto &kv : node) {
145 const auto key = kv.first.as<std::string>();
146 const auto full_path = prefix.empty() ? key : prefix + "." + key;
147 paths.emplace_back(full_path, kv.first.Mark());
148
149 // Recurse into nested maps
150 if (kv.second.IsMap()) {
151 collect_yaml_paths(kv.second, full_path, paths);
152 }
153 }
154}
155
170[[nodiscard]] bool validate_yaml_paths(
171 const TextFormatter *format,
172 const UserDiagnostics *diagnostics,
173 const std::filesystem::path &file_path,
174 const YAML::Node &node)
175{
176 if (diagnostics == nullptr) {
177 return false;
178 }
179
180 bool found_unknown = false;
181 std::vector<std::pair<std::string, YAML::Mark>> paths;
182 collect_yaml_paths(node, "", paths);
183
184 for (const auto &[path, mark] : paths) {
185 if (!valid_yaml_paths.contains(path)) {
186 // Skip children of map-type config values (dynamic keys like animation names)
187 bool is_map_child = false;
188 for (const auto &prefix : valid_yaml_map_prefixes) {
189 if (path.starts_with(prefix + ".")) {
190 is_map_child = true;
191 break;
192 }
193 }
194 if (is_map_child) {
195 continue;
196 }
197
198 const auto source = make_source_string(format, file_path.string(), mark);
199 auto details = make_source_details(format, file_path.string(), mark);
200
201 std::vector<std::string> error_lines;
202 error_lines.push_back(format->format("Unknown configuration key '{}'.", FormatParam{path, Style::bold}));
203 error_lines.emplace_back();
204 error_lines.push_back(format->format("Source: {}", FormatParam{source, Style::italic}));
205 error_lines.emplace_back();
206 for (auto &detail : details) {
207 error_lines.push_back(std::move(detail));
208 }
209
210 diagnostics->error("unknown-config-key", error_lines);
211 found_unknown = true;
212 }
213 }
214
215 return found_unknown;
216}
217
228parse_size_t(const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
229{
230 if (!node.IsDefined()) {
232 }
233
234 try {
235 const auto value = node.as<std::size_t>();
236 const auto mark = node.Mark();
237 const auto source = make_source_string(format, file_path, mark);
238 const auto details = make_source_details(format, file_path, mark);
239 return LayerValue<std::size_t>::valid(value, key, source, details);
240 }
241 catch (const YAML::Exception &e) {
242 const auto mark = node.Mark();
243 const auto error =
244 format->format("Failed to parse '{}' as integer: {}", FormatParam{key, Style::bold}, e.what());
245 const auto source = make_source_string(format, file_path, mark);
246 const auto details = make_source_details(format, file_path, mark);
247 return LayerValue<std::size_t>::invalid(error, source, details);
248 }
249}
250
261parse_bool(const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
262{
263 if (!node.IsDefined()) {
265 }
266
267 try {
268 const auto value = node.as<bool>();
269 const auto mark = node.Mark();
270 const auto source = make_source_string(format, file_path, mark);
271 const auto details = make_source_details(format, file_path, mark);
272 return LayerValue<bool>::valid(value, key, source, details);
273 }
274 catch (const YAML::Exception &e) {
275 const auto mark = node.Mark();
276 const auto error =
277 format->format("Failed to parse '{}' as boolean: {}", FormatParam{key, Style::bold}, e.what());
278 const auto source = make_source_string(format, file_path, mark);
279 const auto details = make_source_details(format, file_path, mark);
280 return LayerValue<bool>::invalid(error, source, details);
281 }
282}
283
294parse_string(const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
295{
296 if (!node.IsDefined()) {
298 }
299
300 try {
301 const auto value = node.as<std::string>();
302 const auto mark = node.Mark();
303 const auto source = make_source_string(format, file_path, mark);
304 const auto details = make_source_details(format, file_path, mark);
305 return LayerValue<std::string>::valid(value, key, source, details);
306 }
307 catch (const YAML::Exception &e) {
308 const auto mark = node.Mark();
309 const auto error =
310 format->format("Failed to parse '{}' as string: {}", FormatParam{key, Style::bold}, e.what());
311 const auto source = make_source_string(format, file_path, mark);
312 const auto details = make_source_details(format, file_path, mark);
313 return LayerValue<std::string>::invalid(error, source, details);
314 }
315}
316
331parse_rgba32(const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
332{
333 if (!node.IsDefined()) {
335 }
336
337 try {
338 const auto mark = node.Mark();
339 const auto source = make_source_string(format, file_path, mark);
340 const auto details = make_source_details(format, file_path, mark);
341
342 if (!node.IsSequence()) {
343 const auto error =
344 format->format("'{}' must be a sequence [r, g, b] or [r, g, b, a]", FormatParam{key, Style::bold});
345 return LayerValue<Rgba32>::invalid(error, source, details);
346 }
347
348 if (node.size() < 3 || node.size() > 4) {
349 const auto error = format->format(
350 "'{}' must have 3 or 4 elements [r, g, b] or [r, g, b, a], got {}",
352 FormatParam{node.size(), Style::bold});
353 return LayerValue<Rgba32>::invalid(error, source, details);
354 }
355
356 const auto r = node[0].as<std::uint8_t>();
357 const auto g = node[1].as<std::uint8_t>();
358 const auto b = node[2].as<std::uint8_t>();
359 const auto a = (node.size() == 4) ? node[3].as<std::uint8_t>() : Rgba32::alpha_opaque;
360
361 const Rgba32 color{r, g, b, a};
362 return LayerValue<Rgba32>::valid(color, key, source, details);
363 }
364 catch (const YAML::Exception &e) {
365 const auto mark = node.Mark();
366 const auto error = format->format("Failed to parse '{}' as rgba: {}", FormatParam{key, Style::bold}, e.what());
367 const auto source = make_source_string(format, file_path, mark);
368 const auto details = make_source_details(format, file_path, mark);
369 return LayerValue<Rgba32>::invalid(error, source, details);
370 }
371}
372
374 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
375{
376 if (!node.IsDefined()) {
378 }
379
380 try {
381 const auto mark = node.Mark();
382 const auto details = make_source_details(format, file_path, mark);
383
384 if (!node.IsSequence()) {
385 const auto error =
386 format->format("'{}' must be a sequence of palette hints", FormatParam{key, Style::bold});
387 const auto source = make_source_string(format, file_path, mark);
388 return LayerValue<std::vector<PaletteHint>>::invalid(error, source, details);
389 }
390
391 std::vector<PaletteHint> hints;
392 for (std::size_t i = 0; i < node.size(); ++i) {
393 const auto &hint_node = node[i];
394
395 if (!hint_node.IsMap()) {
396 const auto hint_mark = hint_node.Mark();
397 const auto error = format->format(
398 "'{}[{}]' must be a map with 'name' and 'colors' keys",
401 const auto source = make_source_string(format, file_path, hint_mark);
402 const auto hint_details = make_source_details(format, file_path, hint_mark);
403 return LayerValue<std::vector<PaletteHint>>::invalid(error, source, hint_details);
404 }
405
406 // Parse name field
407 const auto name_node = hint_node["name"];
408 if (!name_node.IsDefined()) {
409 const auto hint_mark = hint_node.Mark();
410 const auto error = format->format(
411 "'{}[{}]' is missing required 'name' field", FormatParam{key, Style::bold}, FormatParam{i});
412 const auto source = make_source_string(format, file_path, hint_mark);
413 const auto hint_details = make_source_details(format, file_path, hint_mark);
414 return LayerValue<std::vector<PaletteHint>>::invalid(error, source, hint_details);
415 }
416 const auto name = name_node.as<std::string>();
417
418 // Parse colors field
419 const auto colors_node = hint_node["colors"];
420 if (!colors_node.IsDefined()) {
421 const auto hint_mark = hint_node.Mark();
422 const auto error = format->format(
423 "'{}[{}]' is missing required 'colors' field", FormatParam{key, Style::bold}, FormatParam{i});
424 const auto source = make_source_string(format, file_path, hint_mark);
425 const auto hint_details = make_source_details(format, file_path, hint_mark);
426 return LayerValue<std::vector<PaletteHint>>::invalid(error, source, hint_details);
427 }
428
429 if (!colors_node.IsSequence()) {
430 const auto colors_mark = colors_node.Mark();
431 const auto error = format->format(
432 "'{}[{}].colors' must be a sequence of colors", FormatParam{key, Style::bold}, FormatParam{i});
433 const auto source = make_source_string(format, file_path, colors_mark);
434 const auto colors_details = make_source_details(format, file_path, colors_mark);
435 return LayerValue<std::vector<PaletteHint>>::invalid(error, source, colors_details);
436 }
437
438 // Parse each color
439 std::vector<Rgba32> colors;
440 for (std::size_t j = 0; j < colors_node.size(); ++j) {
441 const auto &color_node = colors_node[j];
442
443 if (!color_node.IsSequence() || color_node.size() != 3) {
444 const auto color_mark = color_node.Mark();
445 const auto error = format->format(
446 "'{}[{}].colors[{}]' must be [r, g, b]",
448 FormatParam{i},
449 FormatParam{j});
450 const auto source = make_source_string(format, file_path, color_mark);
451 const auto color_details = make_source_details(format, file_path, color_mark);
452 return LayerValue<std::vector<PaletteHint>>::invalid(error, source, color_details);
453 }
454
455 const auto r = color_node[0].as<std::uint8_t>();
456 const auto g = color_node[1].as<std::uint8_t>();
457 const auto b = color_node[2].as<std::uint8_t>();
458 const auto a = (color_node.size() == 4) ? color_node[3].as<std::uint8_t>() : Rgba32::alpha_opaque;
459
460 colors.emplace_back(r, g, b, a);
461 }
462
463 hints.emplace_back(name, Palette{std::move(colors)});
464 }
465
466 const auto source = make_source_string(format, file_path, mark);
467 return LayerValue<std::vector<PaletteHint>>::valid(std::move(hints), key, source, details);
468 }
469 catch (const YAML::Exception &e) {
470 const auto mark = node.Mark();
471 const auto error =
472 format->format("Failed to parse '{}' as palette hints: {}", FormatParam{key, Style::bold}, e.what());
473 const auto source = make_source_string(format, file_path, mark);
474 const auto details = make_source_details(format, file_path, mark);
475 return LayerValue<std::vector<PaletteHint>>::invalid(error, source, details);
476 }
477}
478
492LayerValue<std::vector<std::string>> parse_string_vector(
493 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
494{
495 if (!node.IsDefined()) {
497 }
498
499 try {
500 const auto mark = node.Mark();
501 const auto source = make_source_string(format, file_path, mark);
502 const auto details = make_source_details(format, file_path, mark);
503
504 if (!node.IsSequence()) {
505 const auto error = format->format("'{}' must be a sequence of strings.", FormatParam{key, Style::bold});
506 return LayerValue<std::vector<std::string>>::invalid(error, source, details);
507 }
508
509 std::vector<std::string> result;
510 for (std::size_t i = 0; i < node.size(); ++i) {
511 result.push_back(node[i].as<std::string>());
512 }
513 return LayerValue<std::vector<std::string>>::valid(std::move(result), key, source, details);
514 }
515 catch (const YAML::Exception &e) {
516 const auto mark = node.Mark();
517 const auto error =
518 format->format("Failed to parse '{}' as string list: {}.", FormatParam{key, Style::bold}, e.what());
519 const auto source = make_source_string(format, file_path, mark);
520 const auto details = make_source_details(format, file_path, mark);
521 return LayerValue<std::vector<std::string>>::invalid(error, source, details);
522 }
523}
524
537LayerValue<TilesPalMode> parse_tiles_pal_mode(
538 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
539{
540 if (!node.IsDefined()) {
542 }
543
544 try {
545 const auto mark = node.Mark();
546 const auto source = make_source_string(format, file_path, mark);
547 const auto details = make_source_details(format, file_path, mark);
548 const auto node_value = node.as<std::string>();
549 const auto mode_opt = tiles_pal_mode_from_str(node_value);
550
551 if (!mode_opt.has_value()) {
552 const auto error = format->format(
553 "'{}' has invalid value '{}'", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
554 return LayerValue<TilesPalMode>::invalid(error, source, details);
555 }
556
557 return LayerValue<TilesPalMode>::valid(mode_opt.value(), key, source, details);
558 }
559 catch (const YAML::Exception &e) {
560 const auto mark = node.Mark();
561 const auto error =
562 format->format("Failed to parse '{}' as TilesPalMode: {}", FormatParam{key, Style::bold}, e.what());
563 const auto source = make_source_string(format, file_path, mark);
564 const auto details = make_source_details(format, file_path, mark);
565 return LayerValue<TilesPalMode>::invalid(error, source, details);
566 }
567}
568
569LayerValue<ArtifactEditMode> parse_artifact_edit_mode(
570 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
571{
572 if (!node.IsDefined()) {
574 }
575
576 try {
577 const auto mark = node.Mark();
578 const auto source = make_source_string(format, file_path, mark);
579 const auto details = make_source_details(format, file_path, mark);
580 const auto node_value = node.as<std::string>();
581 const auto mode_opt = artifact_edit_mode_from_str(node_value);
582
583 if (!mode_opt.has_value()) {
584 const auto error = format->format(
585 "'{}' has invalid value '{}'", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
586 return LayerValue<ArtifactEditMode>::invalid(error, source, details);
587 }
588
589 return LayerValue<ArtifactEditMode>::valid(mode_opt.value(), key, source, details);
590 }
591 catch (const YAML::Exception &e) {
592 const auto mark = node.Mark();
593 const auto error =
594 format->format("Failed to parse '{}' as ArtifactEditMode: {}", FormatParam{key, Style::bold}, e.what());
595 const auto source = make_source_string(format, file_path, mark);
596 const auto details = make_source_details(format, file_path, mark);
597 return LayerValue<ArtifactEditMode>::invalid(error, source, details);
598 }
599}
600
601LayerValue<AnimPalResolutionStrategy> parse_anim_pal_resolution_strategy(
602 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
603{
604 if (!node.IsDefined()) {
606 }
607
608 try {
609 const auto mark = node.Mark();
610 const auto source = make_source_string(format, file_path, mark);
611 const auto details = make_source_details(format, file_path, mark);
612 const auto node_value = node.as<std::string>();
613 const auto mode_opt = anim_pal_resolution_strategy_from_str(node_value);
614
615 if (!mode_opt.has_value()) {
616 const auto error = format->format(
617 "'{}' has invalid value '{}'", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
619 }
620
621 return LayerValue<AnimPalResolutionStrategy>::valid(mode_opt.value(), key, source, details);
622 }
623 catch (const YAML::Exception &e) {
624 const auto mark = node.Mark();
625 const auto error = format->format(
626 "Failed to parse '{}' as AnimPalResolutionStrategy: {}", FormatParam{key, Style::bold}, e.what());
627 const auto source = make_source_string(format, file_path, mark);
628 const auto details = make_source_details(format, file_path, mark);
630 }
631}
632
633LayerValue<PerAnimOverrides> parse_per_anim_overrides(
634 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
635{
636 if (!node.IsDefined()) {
638 }
639
640 try {
641 const auto mark = node.Mark();
642 const auto source = make_source_string(format, file_path, mark);
643 const auto details = make_source_details(format, file_path, mark);
644
645 if (!node.IsMap()) {
646 const auto error = format->format(
647 "'{}' must be a map of animation names to config objects.", FormatParam{key, Style::bold});
648 return LayerValue<PerAnimOverrides>::invalid(error, source, details);
649 }
650
651 PerAnimOverrides configs;
652 for (const auto &kv : node) {
653 const auto anim_name = kv.first.as<std::string>();
654 const auto &anim_node = kv.second;
655
656 PerAnimOverride anim_config;
657 anim_config.anim_name = anim_name;
658
659 if (!anim_node.IsMap()) {
660 const auto anim_mark = kv.first.Mark();
661 const auto anim_source = make_source_string(format, file_path, anim_mark);
662 const auto anim_details = make_source_details(format, file_path, anim_mark);
663 const auto error = format->format(
664 "'{}' animation '{}' must be a map.",
666 FormatParam{anim_name, Style::bold});
667 return LayerValue<PerAnimOverrides>::invalid(error, anim_source, anim_details);
668 }
669
670 // Parse frame_linking (optional)
671 if (anim_node["frame_linking"].IsDefined()) {
672 const auto linking_str = anim_node["frame_linking"].as<std::string>();
673 const auto linking_opt = frame_linking_from_str(linking_str);
674 if (!linking_opt.has_value()) {
675 const auto linking_mark = anim_node["frame_linking"].Mark();
676 const auto linking_source = make_source_string(format, file_path, linking_mark);
677 const auto linking_details = make_source_details(format, file_path, linking_mark);
678 const auto error = format->format(
679 "'{}' animation '{}' has invalid frame_linking value '{}'.",
681 FormatParam{anim_name, Style::bold},
682 FormatParam{linking_str, Style::bold});
683 return LayerValue<PerAnimOverrides>::invalid(error, linking_source, linking_details);
684 }
685 const auto fl_mark = anim_node["frame_linking"].Mark();
686 anim_config.linking = ConfigPODField{
687 linking_opt.value(),
688 key + "." + anim_name + ".frame_linking",
689 "Animation Config (" + anim_name + ") frame_linking",
690 make_source_string(format, file_path, fl_mark),
691 make_source_details(format, file_path, fl_mark)};
692 }
693
694 // Parse palette_resolution_strategy (optional scalar — per-anim middle tier)
695 if (anim_node["palette_resolution_strategy"].IsDefined()) {
696 const auto &strategy_node = anim_node["palette_resolution_strategy"];
697 const auto strategy_str = strategy_node.as<std::string>();
698 const auto strategy_opt = anim_pal_resolution_strategy_from_str(strategy_str);
699 if (!strategy_opt.has_value()) {
700 const auto strategy_mark = strategy_node.Mark();
701 const auto strategy_source = make_source_string(format, file_path, strategy_mark);
702 const auto strategy_details = make_source_details(format, file_path, strategy_mark);
703 const auto error = format->format(
704 "'{}' animation '{}' palette_resolution_strategy has invalid value '{}'.",
706 FormatParam{anim_name, Style::bold},
707 FormatParam{strategy_str, Style::bold});
708 return LayerValue<PerAnimOverrides>::invalid(error, strategy_source, strategy_details);
709 }
710 const auto pal_mark = strategy_node.Mark();
712 strategy_opt.value(),
713 key + "." + anim_name + ".palette_resolution_strategy",
714 "Animation Config (" + anim_name + ") per-anim strategy",
715 make_source_string(format, file_path, pal_mark),
716 make_source_details(format, file_path, pal_mark)};
717 }
718
719 // Parse key_frame_resolution_strategy (optional scalar — per-anim override)
720 if (anim_node["key_frame_resolution_strategy"].IsDefined()) {
721 const auto &strategy_node = anim_node["key_frame_resolution_strategy"];
722 const auto strategy_str = strategy_node.as<std::string>();
723 const auto strategy_opt = anim_key_frame_resolution_strategy_from_str(strategy_str);
724 if (!strategy_opt.has_value()) {
725 const auto strategy_mark = strategy_node.Mark();
726 const auto strategy_source = make_source_string(format, file_path, strategy_mark);
727 const auto strategy_details = make_source_details(format, file_path, strategy_mark);
728 const auto error = format->format(
729 "'{}' animation '{}' key_frame_resolution_strategy has invalid value '{}'.",
731 FormatParam{anim_name, Style::bold},
732 FormatParam{strategy_str, Style::bold});
733 return LayerValue<PerAnimOverrides>::invalid(error, strategy_source, strategy_details);
734 }
735 const auto kf_mark = strategy_node.Mark();
737 strategy_opt.value(),
738 key + "." + anim_name + ".key_frame_resolution_strategy",
739 "Animation Config (" + anim_name + ") key_frame_resolution_strategy",
740 make_source_string(format, file_path, kf_mark),
741 make_source_details(format, file_path, kf_mark)};
742 }
743
744 // Parse multi_palette_subtile_resolution_strategy (optional scalar — per-anim override)
745 if (anim_node["multi_palette_subtile_resolution_strategy"].IsDefined()) {
746 const auto &strategy_node = anim_node["multi_palette_subtile_resolution_strategy"];
747 const auto strategy_str = strategy_node.as<std::string>();
748 const auto strategy_opt = anim_multi_pal_subtile_resolution_strategy_from_str(strategy_str);
749 if (!strategy_opt.has_value()) {
750 const auto strategy_mark = strategy_node.Mark();
751 const auto strategy_source = make_source_string(format, file_path, strategy_mark);
752 const auto strategy_details = make_source_details(format, file_path, strategy_mark);
753 const auto error = format->format(
754 "'{}' animation '{}' multi_palette_subtile_resolution_strategy has invalid value '{}'.",
756 FormatParam{anim_name, Style::bold},
757 FormatParam{strategy_str, Style::bold});
758 return LayerValue<PerAnimOverrides>::invalid(error, strategy_source, strategy_details);
759 }
760 const auto mps_mark = strategy_node.Mark();
762 strategy_opt.value(),
763 key + "." + anim_name + ".multi_palette_subtile_resolution_strategy",
764 "Animation Config (" + anim_name + ") multi_palette_subtile_resolution_strategy",
765 make_source_string(format, file_path, mps_mark),
766 make_source_details(format, file_path, mps_mark)};
767 }
768
769 // Parse per_tile_palette_resolution_strategies (optional sequence — per-tile most specific tier)
770 if (anim_node["per_tile_palette_resolution_strategies"].IsDefined()) {
771 const auto &strategies_node = anim_node["per_tile_palette_resolution_strategies"];
772 if (!strategies_node.IsSequence()) {
773 const auto strategies_mark = strategies_node.Mark();
774 const auto strategies_source = make_source_string(format, file_path, strategies_mark);
775 const auto strategies_details = make_source_details(format, file_path, strategies_mark);
776 const auto error = format->format(
777 "'{}' animation '{}' per_tile_palette_resolution_strategies must be a sequence.",
779 FormatParam{anim_name, Style::bold});
780 return LayerValue<PerAnimOverrides>::invalid(error, strategies_source, strategies_details);
781 }
782
783 for (std::size_t i = 0; i < strategies_node.size(); ++i) {
784 const auto strategy_str = strategies_node[i].as<std::string>();
785 if (strategy_str == "_") {
786 anim_config.per_tile_pal_resolution_strategies.emplace_back();
787 }
788 else {
789 const auto strategy_opt = anim_pal_resolution_strategy_from_str(strategy_str);
790 if (!strategy_opt.has_value()) {
791 const auto strategy_mark = strategies_node[i].Mark();
792 const auto strategy_source = make_source_string(format, file_path, strategy_mark);
793 const auto strategy_details = make_source_details(format, file_path, strategy_mark);
794 const auto error = format->format(
795 "'{}' animation '{}' per_tile_palette_resolution_strategies[{}] has invalid value "
796 "'{}'.",
798 FormatParam{anim_name, Style::bold},
800 FormatParam{strategy_str, Style::bold});
801 return LayerValue<PerAnimOverrides>::invalid(error, strategy_source, strategy_details);
802 }
803 const auto tile_mark = strategies_node[i].Mark();
804 anim_config.per_tile_pal_resolution_strategies.push_back(
806 strategy_opt.value(),
807 key + "." + anim_name + ".per_tile_palette_resolution_strategies[" + std::to_string(i) +
808 "]",
809 "Animation Config (" + anim_name + ") subtile " + std::to_string(i),
810 make_source_string(format, file_path, tile_mark),
811 make_source_details(format, file_path, tile_mark)});
812 }
813 }
814 }
815
816 configs[anim_name] = std::move(anim_config);
817 }
818
819 return LayerValue<PerAnimOverrides>::valid(std::move(configs), key, source, details);
820 }
821 catch (const YAML::Exception &e) {
822 const auto mark = node.Mark();
823 const auto error =
824 format->format("Failed to parse '{}' as animation configs: {}.", FormatParam{key, Style::bold}, e.what());
825 const auto source = make_source_string(format, file_path, mark);
826 const auto details = make_source_details(format, file_path, mark);
827 return LayerValue<PerAnimOverrides>::invalid(error, source, details);
828 }
829}
830
831LayerValue<AnimKeyFrameResolutionStrategy> parse_anim_key_frame_resolution_strategy(
832 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
833{
834 if (!node.IsDefined()) {
836 }
837
838 try {
839 const auto mark = node.Mark();
840 const auto source = make_source_string(format, file_path, mark);
841 const auto details = make_source_details(format, file_path, mark);
842 const auto node_value = node.as<std::string>();
843 const auto mode_opt = anim_key_frame_resolution_strategy_from_str(node_value);
844
845 if (!mode_opt.has_value()) {
846 const auto error = format->format(
847 "'{}' has invalid value '{}'.", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
849 }
850
851 return LayerValue<AnimKeyFrameResolutionStrategy>::valid(mode_opt.value(), key, source, details);
852 }
853 catch (const YAML::Exception &e) {
854 const auto mark = node.Mark();
855 const auto error = format->format(
856 "Failed to parse '{}' as AnimKeyFrameResolutionStrategy: {}.", FormatParam{key, Style::bold}, e.what());
857 const auto source = make_source_string(format, file_path, mark);
858 const auto details = make_source_details(format, file_path, mark);
860 }
861}
862
863LayerValue<AnimMultiPalSubtileResolutionStrategy> parse_anim_multi_pal_subtile_resolution_strategy(
864 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
865{
866 if (!node.IsDefined()) {
868 }
869
870 try {
871 const auto mark = node.Mark();
872 const auto source = make_source_string(format, file_path, mark);
873 const auto details = make_source_details(format, file_path, mark);
874 const auto node_value = node.as<std::string>();
875 const auto mode_opt = anim_multi_pal_subtile_resolution_strategy_from_str(node_value);
876
877 if (!mode_opt.has_value()) {
878 const auto error = format->format(
879 "'{}' has invalid value '{}'.", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
881 }
882
883 return LayerValue<AnimMultiPalSubtileResolutionStrategy>::valid(mode_opt.value(), key, source, details);
884 }
885 catch (const YAML::Exception &e) {
886 const auto mark = node.Mark();
887 const auto error = format->format(
888 "Failed to parse '{}' as AnimMultiPalSubtileResolutionStrategy: {}.",
890 e.what());
891 const auto source = make_source_string(format, file_path, mark);
892 const auto details = make_source_details(format, file_path, mark);
894 }
895}
896
897LayerValue<FrameLinking> parse_frame_linking(
898 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
899{
900 if (!node.IsDefined()) {
902 }
903
904 try {
905 const auto mark = node.Mark();
906 const auto source = make_source_string(format, file_path, mark);
907 const auto details = make_source_details(format, file_path, mark);
908 const auto node_value = node.as<std::string>();
909 const auto mode_opt = frame_linking_from_str(node_value);
910
911 if (!mode_opt.has_value()) {
912 const auto error = format->format(
913 "'{}' has invalid value '{}'.", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
914 return LayerValue<FrameLinking>::invalid(error, source, details);
915 }
916
917 return LayerValue<FrameLinking>::valid(mode_opt.value(), key, source, details);
918 }
919 catch (const YAML::Exception &e) {
920 const auto mark = node.Mark();
921 const auto error =
922 format->format("Failed to parse '{}' as FrameLinking: {}.", FormatParam{key, Style::bold}, e.what());
923 const auto source = make_source_string(format, file_path, mark);
924 const auto details = make_source_details(format, file_path, mark);
925 return LayerValue<FrameLinking>::invalid(error, source, details);
926 }
927}
928
929LayerValue<TileSharingPacking> parse_tile_sharing_packing(
930 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
931{
932 if (!node.IsDefined()) {
934 }
935
936 try {
937 const auto mark = node.Mark();
938 const auto source = make_source_string(format, file_path, mark);
939 const auto details = make_source_details(format, file_path, mark);
940 const auto node_value = node.as<std::string>();
941 const auto mode_opt = tile_sharing_packing_from_str(node_value);
942
943 if (!mode_opt.has_value()) {
944 const auto error = format->format(
945 "'{}' has invalid value '{}'.", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
946 return LayerValue<TileSharingPacking>::invalid(error, source, details);
947 }
948
949 return LayerValue<TileSharingPacking>::valid(mode_opt.value(), key, source, details);
950 }
951 catch (const YAML::Exception &e) {
952 const auto mark = node.Mark();
953 const auto error =
954 format->format("Failed to parse '{}' as TileSharingPacking: {}.", FormatParam{key, Style::bold}, e.what());
955 const auto source = make_source_string(format, file_path, mark);
956 const auto details = make_source_details(format, file_path, mark);
957 return LayerValue<TileSharingPacking>::invalid(error, source, details);
958 }
959}
960
961LayerValue<TileSharingAlignment> parse_tile_sharing_alignment(
962 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
963{
964 if (!node.IsDefined()) {
966 }
967
968 try {
969 const auto mark = node.Mark();
970 const auto source = make_source_string(format, file_path, mark);
971 const auto details = make_source_details(format, file_path, mark);
972 const auto node_value = node.as<std::string>();
973 const auto mode_opt = tile_sharing_alignment_from_str(node_value);
974
975 if (!mode_opt.has_value()) {
976 const auto error = format->format(
977 "'{}' has invalid value '{}'.", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
978 return LayerValue<TileSharingAlignment>::invalid(error, source, details);
979 }
980
981 return LayerValue<TileSharingAlignment>::valid(mode_opt.value(), key, source, details);
982 }
983 catch (const YAML::Exception &e) {
984 const auto mark = node.Mark();
985 const auto error = format->format(
986 "Failed to parse '{}' as TileSharingAlignment: {}.", FormatParam{key, Style::bold}, e.what());
987 const auto source = make_source_string(format, file_path, mark);
988 const auto details = make_source_details(format, file_path, mark);
989 return LayerValue<TileSharingAlignment>::invalid(error, source, details);
990 }
991}
992
993LayerValue<PackingStrategyType> parse_packing_strategy_type(
994 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
995{
996 if (!node.IsDefined()) {
998 }
999
1000 try {
1001 const auto mark = node.Mark();
1002 const auto source = make_source_string(format, file_path, mark);
1003 const auto details = make_source_details(format, file_path, mark);
1004 const auto node_value = node.as<std::string>();
1005 const auto mode_opt = packing_strategy_type_from_str(node_value);
1006
1007 if (!mode_opt.has_value()) {
1008 const auto error = format->format(
1009 "'{}' has invalid value '{}'.", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
1010 return LayerValue<PackingStrategyType>::invalid(error, source, details);
1011 }
1012
1013 return LayerValue<PackingStrategyType>::valid(mode_opt.value(), key, source, details);
1014 }
1015 catch (const YAML::Exception &e) {
1016 const auto mark = node.Mark();
1017 const auto error =
1018 format->format("Failed to parse '{}' as PackingStrategyType: {}.", FormatParam{key, Style::bold}, e.what());
1019 const auto source = make_source_string(format, file_path, mark);
1020 const auto details = make_source_details(format, file_path, mark);
1021 return LayerValue<PackingStrategyType>::invalid(error, source, details);
1022 }
1023}
1024
1025LayerValue<PrimaryPairingMode> parse_primary_pairing_mode(
1026 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
1027{
1028 if (!node.IsDefined()) {
1030 }
1031
1032 try {
1033 const auto mark = node.Mark();
1034 const auto source = make_source_string(format, file_path, mark);
1035 const auto details = make_source_details(format, file_path, mark);
1036 const auto node_value = node.as<std::string>();
1037 const auto mode_opt = primary_pairing_mode_from_str(node_value);
1038
1039 if (!mode_opt.has_value()) {
1040 const auto error = format->format(
1041 "'{}' has invalid value '{}'.", FormatParam{key, Style::bold}, FormatParam{node_value, Style::bold});
1042 return LayerValue<PrimaryPairingMode>::invalid(error, source, details);
1043 }
1044
1045 return LayerValue<PrimaryPairingMode>::valid(mode_opt.value(), key, source, details);
1046 }
1047 catch (const YAML::Exception &e) {
1048 const auto mark = node.Mark();
1049 const auto error =
1050 format->format("Failed to parse '{}' as PrimaryPairingMode: {}.", FormatParam{key, Style::bold}, e.what());
1051 const auto source = make_source_string(format, file_path, mark);
1052 const auto details = make_source_details(format, file_path, mark);
1053 return LayerValue<PrimaryPairingMode>::invalid(error, source, details);
1054 }
1055}
1056
1057LayerValue<PackingStrategyParams> parse_packing_strategy_params(
1058 const TextFormatter *format, const YAML::Node &node, const std::string &key, const std::string &file_path)
1059{
1060 if (!node.IsDefined()) {
1062 }
1063
1064 try {
1065 const auto mark = node.Mark();
1066 const auto source = make_source_string(format, file_path, mark);
1067 const auto details = make_source_details(format, file_path, mark);
1068
1069 if (!node.IsMap()) {
1070 const auto error = format->format(
1071 "'{}' must be a map of strategy names to parameter objects.", FormatParam{key, Style::bold});
1072 return LayerValue<PackingStrategyParams>::invalid(error, source, details);
1073 }
1074
1075 PackingStrategyParams params;
1076
1077 // Parse backtracking sub-map
1078 if (node["backtracking"].IsDefined()) {
1079 const auto &bt_node = node["backtracking"];
1080 if (!bt_node.IsMap()) {
1081 const auto bt_mark = bt_node.Mark();
1082 const auto bt_source = make_source_string(format, file_path, bt_mark);
1083 const auto bt_details = make_source_details(format, file_path, bt_mark);
1084 const auto error = format->format("'{}' backtracking must be a map.", FormatParam{key, Style::bold});
1085 return LayerValue<PackingStrategyParams>::invalid(error, bt_source, bt_details);
1086 }
1087
1088 if (bt_node["search_algorithm"].IsDefined()) {
1089 const auto &sa_node = bt_node["search_algorithm"];
1090 const auto sa_str = sa_node.as<std::string>();
1091 const auto sa_opt = search_algorithm_from_str(sa_str);
1092 if (!sa_opt.has_value()) {
1093 const auto sa_mark = sa_node.Mark();
1094 const auto sa_source = make_source_string(format, file_path, sa_mark);
1095 const auto sa_details = make_source_details(format, file_path, sa_mark);
1096 const auto error = format->format(
1097 "'{}' backtracking search_algorithm has invalid value '{}'.",
1099 FormatParam{sa_str, Style::bold});
1100 return LayerValue<PackingStrategyParams>::invalid(error, sa_source, sa_details);
1101 }
1102 const auto sa_mark = sa_node.Mark();
1104 sa_opt.value(),
1105 key + ".backtracking.search_algorithm",
1106 "Packing Strategy Params (backtracking) search_algorithm",
1107 make_source_string(format, file_path, sa_mark),
1108 make_source_details(format, file_path, sa_mark)};
1109 }
1110
1111 if (bt_node["node_cutoff"].IsDefined()) {
1112 const auto &nc_node = bt_node["node_cutoff"];
1113 const auto nc_val = nc_node.as<std::size_t>();
1114 const auto nc_mark = nc_node.Mark();
1116 nc_val,
1117 key + ".backtracking.node_cutoff",
1118 "Packing Strategy Params (backtracking) node_cutoff",
1119 make_source_string(format, file_path, nc_mark),
1120 make_source_details(format, file_path, nc_mark)};
1121 }
1122
1123 if (bt_node["best_branches"].IsDefined()) {
1124 const auto &bb_node = bt_node["best_branches"];
1125 const auto bb_val = bb_node.as<std::size_t>();
1126 const auto bb_mark = bb_node.Mark();
1128 bb_val,
1129 key + ".backtracking.best_branches",
1130 "Packing Strategy Params (backtracking) best_branches",
1131 make_source_string(format, file_path, bb_mark),
1132 make_source_details(format, file_path, bb_mark)};
1133 }
1134
1135 if (bt_node["smart_prune"].IsDefined()) {
1136 const auto &sp_node = bt_node["smart_prune"];
1137 const auto sp_val = sp_node.as<bool>();
1138 const auto sp_mark = sp_node.Mark();
1140 sp_val,
1141 key + ".backtracking.smart_prune",
1142 "Packing Strategy Params (backtracking) smart_prune",
1143 make_source_string(format, file_path, sp_mark),
1144 make_source_details(format, file_path, sp_mark)};
1145 }
1146 }
1147
1148 // Parse overload_and_remove sub-map
1149 if (node["overload_and_remove"].IsDefined()) {
1150 const auto &oar_node = node["overload_and_remove"];
1151 if (!oar_node.IsMap()) {
1152 const auto oar_mark = oar_node.Mark();
1153 const auto oar_source = make_source_string(format, file_path, oar_mark);
1154 const auto oar_details = make_source_details(format, file_path, oar_mark);
1155 const auto error =
1156 format->format("'{}' overload_and_remove must be a map.", FormatParam{key, Style::bold});
1157 return LayerValue<PackingStrategyParams>::invalid(error, oar_source, oar_details);
1158 }
1159
1160 if (oar_node["max_attempts"].IsDefined()) {
1161 const auto &ma_node = oar_node["max_attempts"];
1162 const auto ma_val = ma_node.as<std::size_t>();
1163 const auto ma_mark = ma_node.Mark();
1165 ma_val,
1166 key + ".overload_and_remove.max_attempts",
1167 "Packing Strategy Params (overload_and_remove) max_attempts",
1168 make_source_string(format, file_path, ma_mark),
1169 make_source_details(format, file_path, ma_mark)};
1170 }
1171
1172 if (oar_node["seed"].IsDefined()) {
1173 const auto &seed_node = oar_node["seed"];
1174 const auto seed_val = seed_node.as<std::uint64_t>();
1175 const auto seed_mark = seed_node.Mark();
1177 seed_val,
1178 key + ".overload_and_remove.seed",
1179 "Packing Strategy Params (overload_and_remove) seed",
1180 make_source_string(format, file_path, seed_mark),
1181 make_source_details(format, file_path, seed_mark)};
1182 }
1183
1184 if (oar_node["shuffle_strategy"].IsDefined()) {
1185 const auto &ss_node = oar_node["shuffle_strategy"];
1186 const auto ss_str = ss_node.as<std::string>();
1187 const auto ss_opt = shuffle_strategy_from_str(ss_str);
1188 if (!ss_opt.has_value()) {
1189 const auto ss_mark = ss_node.Mark();
1190 const auto ss_source = make_source_string(format, file_path, ss_mark);
1191 const auto ss_details = make_source_details(format, file_path, ss_mark);
1192 const auto error = format->format(
1193 "'{}' overload_and_remove shuffle_strategy has invalid value '{}'.",
1195 FormatParam{ss_str, Style::bold});
1196 return LayerValue<PackingStrategyParams>::invalid(error, ss_source, ss_details);
1197 }
1198 const auto ss_mark = ss_node.Mark();
1200 ss_opt.value(),
1201 key + ".overload_and_remove.shuffle_strategy",
1202 "Packing Strategy Params (overload_and_remove) shuffle_strategy",
1203 make_source_string(format, file_path, ss_mark),
1204 make_source_details(format, file_path, ss_mark)};
1205 }
1206 }
1207
1208 return LayerValue<PackingStrategyParams>::valid(std::move(params), key, source, details);
1209 }
1210 catch (const YAML::Exception &e) {
1211 const auto mark = node.Mark();
1212 const auto error = format->format(
1213 "Failed to parse '{}' as packing strategy params: {}.", FormatParam{key, Style::bold}, e.what());
1214 const auto source = make_source_string(format, file_path, mark);
1215 const auto details = make_source_details(format, file_path, mark);
1216 return LayerValue<PackingStrategyParams>::invalid(error, source, details);
1217 }
1218}
1219
1234std::optional<YAML::Node> load_yaml_file(
1235 const std::filesystem::path &path,
1236 const TextFormatter *format = nullptr,
1237 const UserDiagnostics *diagnostics = nullptr,
1238 bool *out_had_unknown_keys = nullptr)
1239{
1240 // Check cache first
1241 const auto cache_it = yaml_cache.find(path);
1242 if (cache_it != yaml_cache.end()) {
1243 return cache_it->second;
1244 }
1245
1246 // File doesn't exist, return nullopt
1247 if (!std::filesystem::exists(path)) {
1248 return std::nullopt;
1249 }
1250
1251 // Try to load and cache the file
1252 try {
1253 auto node = YAML::LoadFile(path.string());
1254 yaml_cache[path] = node;
1255
1256 // Also cache the file contents line-by-line for source info
1257 std::ifstream file{path};
1258 std::vector<std::string> lines;
1259 std::string line;
1260 while (std::getline(file, line)) {
1261 lines.push_back(line);
1262 }
1263 file_lines_cache[path] = std::move(lines);
1264
1265 // Validate paths if diagnostics is provided
1266 if (format != nullptr && diagnostics != nullptr) {
1267 if (validate_yaml_paths(format, diagnostics, path, node) && out_had_unknown_keys != nullptr) {
1268 *out_had_unknown_keys = true;
1269 }
1270 }
1271
1272 return node;
1273 }
1274 catch (const YAML::Exception &) {
1275 // Failed to parse YAML, return nullopt
1276 return std::nullopt;
1277 }
1278}
1279
1297std::vector<std::filesystem::path>
1298get_tileset_config_path_chain(const std::filesystem::path &project_root, const std::string &tileset)
1299{
1300 std::vector<std::filesystem::path> paths;
1301
1302 // Porytiles utility directory root
1303 const auto porytiles_dir = project_root / "porytiles";
1304
1305 // Priority order (highest to lowest):
1306 // 1. porytiles/tilesets/{tileset_name}/config.local.yaml
1307 paths.push_back(porytiles_dir / "tilesets" / tileset / "config.local.yaml");
1308
1309 // 2. porytiles/tilesets/{tileset_name}/config.yaml
1310 paths.push_back(porytiles_dir / "tilesets" / tileset / "config.yaml");
1311
1312 // 3. porytiles/config.local.yaml
1313 paths.push_back(porytiles_dir / "config.local.yaml");
1314
1315 // 4. porytiles/config.yaml
1316 paths.push_back(porytiles_dir / "config.yaml");
1317
1318 return paths;
1319}
1320
1334get_config_path_chain(const std::filesystem::path &project_root, ConfigScopeType type, const std::string &scope)
1335{
1336 switch (type) {
1337 case ConfigScopeType::tileset:
1338 return get_tileset_config_path_chain(project_root, scope);
1339 case ConfigScopeType::layout:
1340 panic("Layout config path chain resolution is not yet implemented.");
1341 }
1342 // Should never reach here
1343 panic("Invalid ConfigScopeType");
1344}
1345
1361[[nodiscard]] bool preload_and_validate_yaml_files(
1362 const TextFormatter *format,
1363 const UserDiagnostics *diagnostics,
1364 const std::filesystem::path &project_root,
1365 ConfigScopeType type,
1366 const std::string &scope)
1367{
1368 auto paths_result = get_config_path_chain(project_root, type, scope);
1369 if (!paths_result.has_value()) {
1370 return false;
1371 }
1372
1373 bool had_unknown_keys = false;
1374 for (const auto &path : paths_result.value()) {
1375 load_yaml_file(path, format, diagnostics, &had_unknown_keys);
1376 }
1377
1378 return had_unknown_keys;
1379}
1380
1406template <typename T, typename LoadFunc, typename NodeExtractFunc, typename ParseFunc>
1407LayerValue<T> search_config_files(
1408 const TextFormatter *format,
1409 const std::vector<std::filesystem::path> &paths,
1410 LoadFunc load_func,
1411 NodeExtractFunc extract_node_func,
1412 ParseFunc parse_func,
1413 const std::string &key,
1414 const std::string &provider_name)
1415{
1416 for (const auto &path : paths) {
1417 const auto yaml_doc = load_func(path);
1418 if (!yaml_doc.has_value()) {
1419 // File doesn't exist or couldn't be loaded, try next file
1420 continue;
1421 }
1422
1423 try {
1424 const auto node = extract_node_func(yaml_doc.value());
1425 auto result = parse_func(format, node, key, path.string());
1426
1427 // If we got a valid value or an error, return it immediately
1428 if (result.state == ValidationState::valid || result.state == ValidationState::invalid) {
1429 result.source_key = provider_name;
1430 return result;
1431 }
1432
1433 // If not_provided, continue to next file
1434 }
1435 catch (const YAML::Exception &) {
1436 // Node extraction or parsing threw an exception, treat as not_provided for this file
1437 // and continue to the next file
1438 continue;
1439 }
1440 }
1441
1442 // Not found in any file
1444}
1445
1446} // namespace
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A service for printing file lines with highlighted lines and line numbers.
A text parameter with associated styling for formatted output.
A generic palette container for colors that support transparency checking.
Definition palette.hpp:47
Represents a 32-bit RGBA color.
Definition rgba32.hpp:23
static constexpr std::uint8_t alpha_opaque
Definition rgba32.hpp:26
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.
Abstract class for structured error reporting and diagnostic output.
virtual void error(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged error message.
const std::unordered_set< std::string > valid_yaml_paths
Set of valid YAML configuration paths.
std::optional< TilesPalMode > tiles_pal_mode_from_str(const std::string &str)
Parses a string into a TilesPalMode with fuzzy matching.
std::optional< ArtifactEditMode > artifact_edit_mode_from_str(const std::string &str)
Parses a string into a ArtifactEditMode with fuzzy matching.
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.cpp:43
std::optional< PackingStrategyType > packing_strategy_type_from_str(const std::string &str)
Parses a string into a PackingStrategyType with fuzzy matching.
std::optional< FrameLinking > frame_linking_from_str(const std::string &str)
Parses a string into a FrameLinking with fuzzy matching.
std::optional< PrimaryPairingMode > primary_pairing_mode_from_str(const std::string &str)
Parses a string into a PrimaryPairingMode with fuzzy matching.
std::optional< AnimPalResolutionStrategy > anim_pal_resolution_strategy_from_str(const std::string &str)
Parses a string into a AnimPalResolutionStrategy with fuzzy matching.
const std::unordered_set< std::string > valid_yaml_map_prefixes
Set of YAML path prefixes that represent map-type configuration values.
std::optional< ShuffleStrategy > shuffle_strategy_from_str(const std::string &str)
Parses a string into a ShuffleStrategy with fuzzy matching.
std::unordered_map< std::string, PerAnimOverride > PerAnimOverrides
Per-animation configuration map.
@ error
Emit a formatted error and fail decompilation.
std::optional< TileSharingPacking > tile_sharing_packing_from_str(const std::string &str)
Parses a string into a TileSharingPacking with fuzzy matching.
std::optional< TileSharingAlignment > tile_sharing_alignment_from_str(const std::string &str)
Parses a string into a TileSharingAlignment with fuzzy matching.
ConfigScopeType
Specifies the scope type for configuration value lookups.
@ tileset
Configuration scoped to a specific tileset.
std::optional< AnimMultiPalSubtileResolutionStrategy > anim_multi_pal_subtile_resolution_strategy_from_str(const std::string &str)
Parses a string into a AnimMultiPalSubtileResolutionStrategy with fuzzy matching.
std::optional< SearchAlgorithm > search_algorithm_from_str(const std::string &str)
Parses a string into a SearchAlgorithm with fuzzy matching.
std::optional< AnimKeyFrameResolutionStrategy > anim_key_frame_resolution_strategy_from_str(const std::string &str)
Parses a string into a AnimKeyFrameResolutionStrategy with fuzzy matching.
ConfigPODField< std::size_t > best_branches
ConfigPODField< std::size_t > node_cutoff
ConfigPODField< SearchAlgorithm > search_algorithm
A lightweight wrapper for per-field configuration values with source metadata.
A small container that holds an optional-wrapped value, validation state, and metadata about the valu...
static LayerValue valid(T val, std::string source_key, std::string source_info)
Creates a LayerValue representing a valid configuration value.
static LayerValue not_provided()
Creates a LayerValue representing that the provider does not supply this configuration.
static LayerValue invalid(std::string error, std::string source_info)
Creates a LayerValue representing an invalid configuration value.
ConfigPODField< ShuffleStrategy > shuffle_strategy
ConfigPODField< std::uint64_t > seed
ConfigPODField< std::size_t > max_attempts
Container for per-strategy packing parameters.
Per-animation configuration override for animation decompilation.
std::vector< ConfigPODField< AnimPalResolutionStrategy > > per_tile_pal_resolution_strategies
ConfigPODField< AnimMultiPalSubtileResolutionStrategy > multi_pal_subtile_resolution_strategy
ConfigPODField< AnimKeyFrameResolutionStrategy > key_frame_resolution_strategy
ConfigPODField< FrameLinking > linking
ConfigPODField< AnimPalResolutionStrategy > pal_resolution_strategy