Porytiles
Loading...
Searching...
No Matches
project_vanilla_anim_importer.cpp
Go to the documentation of this file.
2
3#include <vector>
4
12
13namespace {
14
15using namespace porytiles;
16
26[[nodiscard]] std::filesystem::path fourBpp_to_png_path(const std::string &path_4bpp)
27{
28 std::filesystem::path p{path_4bpp};
29 p.replace_extension(".png");
30 return p;
31}
32
33} // namespace
34
35namespace porytiles {
36
38ProjectVanillaAnimImporter::import_animations(const std::string &tileset_name) const
39{
40 std::map<std::string, Animation<IndexPixel>> result;
41
42 // Step 1: Get tileset metadata
43 ProjectTilesetMetadataProvider metadata_provider{project_root_, format_, diag_};
44
45 if (!metadata_provider.exists(tileset_name)) {
46 return FormattableError{"Tileset '{}' does not exist.", FormatParam{tileset_name, Style::bold}};
47 }
48
49 auto metadata_result = metadata_provider.metadata_for(tileset_name);
50 if (!metadata_result.has_value()) {
52 FormattableError{"Failed to get tileset metadata."}, metadata_result};
53 }
54 auto metadata = std::move(metadata_result).value();
55
56 if (!metadata.has_animations()) {
57 // No animations - return empty map
58 return result;
59 }
60
61 const std::string callback_func = metadata.callback_func().value();
62 const auto tileset_cased = extract_tileset_cased_name(tileset_name);
63 const std::string pascal_tileset = tileset_cased.to_pascal_case();
64
65 // Step 2: Parse animation parameters from tileset_anims.c
66 const auto tileset_anims_path = project_root_ / "src" / "tileset_anims.c";
67
68 if (!std::filesystem::exists(tileset_anims_path)) {
69 return FormattableError{
70 "'{}': Animation parameters file not found.", FormatParam{tileset_anims_path.string(), Style::bold}};
71 }
72
73 AnimCodeParser anim_parser{format_, diag_};
74 auto anim_params_result =
75 anim_parser.parse_from_callback(tileset_anims_path, callback_func, tileset_cased, /*porytiles_managed=*/false);
76 if (!anim_params_result.has_value()) {
79 "Failed to parse animation parameters for '{}' from '{}'.",
80 FormatParam{tileset_name, Style::bold},
81 FormatParam{tileset_anims_path, Style::bold}},
82 anim_params_result};
83 }
84 std::map<DynamicCasedName, AnimParams> anim_params_map = std::move(anim_params_result).value();
85
86 if (anim_params_map.empty()) {
87 // No animations found in callback chain
88 return result;
89 }
90
91 // Step 3: Parse INCBIN declarations for frame paths
92 CParserFacade c_parser{tileset_anims_path, format_};
93 std::string detected_anim_prefix = "gTilesetAnims_";
94 const std::string g_incbin_prefix = "gTilesetAnims_" + pascal_tileset + "_";
95 auto incbin_decls_result = c_parser.parse_incbin_arrays(g_incbin_prefix);
96 if (!incbin_decls_result.has_value()) {
98 FormattableError{"Failed to parse INCBIN declarations."}, incbin_decls_result};
99 }
100 auto incbin_decls = std::move(incbin_decls_result).value();
101
102 /*
103 * Check if g-prefix results cover the expected animations from callback parsing. In pokefirered-expansion,
104 * BattleFrontier reuses gTilesetAnims_General_* names for its own shared animations, so the g-prefix may match
105 * INCBIN declarations that belong to a different tileset. If the g-prefix results don't cover the expected
106 * animations, try s-prefix.
107 */
108 auto incbins_cover_expected_anims = [&](const std::string &prefix,
109 const std::vector<IncbinDeclaration> &decls) -> bool {
110 for (const auto &params : anim_params_map | std::views::values) {
111 const std::string pascal_anim_name = params.cased_name().to_c_identifier();
112 const std::string first_frame_var = prefix + pascal_tileset + "_" + pascal_anim_name + "_Frame0";
113 bool found = false;
114 for (const auto &decl : decls) {
115 if (decl.variable_name() == first_frame_var) {
116 found = true;
117 break;
118 }
119 }
120 if (!found) {
121 return false;
122 }
123 }
124 return true;
125 };
126
127 if (incbin_decls.empty() || !incbins_cover_expected_anims("gTilesetAnims_", incbin_decls)) {
128 const std::string s_incbin_prefix = "sTilesetAnims_" + pascal_tileset + "_";
129 auto s_incbin_decls_result = c_parser.parse_incbin_arrays(s_incbin_prefix);
130 if (s_incbin_decls_result.has_value()) {
131 auto s_incbin_decls = std::move(s_incbin_decls_result).value();
132 if (!s_incbin_decls.empty()) {
133 incbin_decls = std::move(s_incbin_decls);
134 detected_anim_prefix = "sTilesetAnims_";
135 }
136 }
137 }
138
139 // Build map: frame variable name -> .png file path
140 std::map<std::string, std::filesystem::path> frame_paths;
141 for (const auto &decl : incbin_decls) {
142 if (decl.variable_name().find("_Frame") != std::string::npos && !decl.paths().empty()) {
143 frame_paths[decl.variable_name()] = project_root_ / fourBpp_to_png_path(decl.paths().front());
144 }
145 }
146
147 // Step 4: For each animation, construct Animation<IndexPixel> with frame data
148 for (const auto &[anim_name, params] : anim_params_map) {
149 Animation<IndexPixel> anim{anim_name.to_snake_case()};
150 anim.params(params);
151
152 // Use DynamicCasedName for lossless C identifier reconstruction
153 // This handles names with embedded underscores (e.g., pokefirered's "Water_Current_LandWatersEdge")
154 const std::string pascal_anim_name = params.cased_name().to_c_identifier();
155
156 for (const auto &frame_name : params.frame_names()) {
157 PngIndexedImageLoader png_loader;
158 const std::string frame_name_snake = frame_name.to_snake_case();
159 const std::string frame_var =
160 detected_anim_prefix + pascal_tileset + "_" + pascal_anim_name + "_Frame" + frame_name.to_pascal_case();
161
162 auto it = frame_paths.find(frame_var);
163 if (it == frame_paths.end()) {
164 return FormattableError{
165 "frame variable '{}' not found in INCBIN declarations for frame '{}'",
166 FormatParam{frame_var, Style::bold},
167 FormatParam{frame_name_snake, Style::bold}};
168 }
169
170 const auto &frame_png_path = it->second;
171 if (!std::filesystem::exists(frame_png_path)) {
172 return FormattableError{
173 "frame PNG '{}' not found for frame '{}'",
174 FormatParam{frame_png_path.string(), Style::bold},
175 FormatParam{frame_name_snake, Style::bold}};
176 }
177
178 // Use shared helper to load frame PNG and extract tiles
179 auto frame_load_result =
180 load_animation_frame_from_png<IndexPixel>(frame_png_path, frame_name_snake, png_loader);
181 if (!frame_load_result.has_value()) {
184 "failed to load frame '{}' for animation '{}'",
185 FormatParam{frame_name_snake, Style::bold},
186 FormatParam{anim_name, Style::bold}},
187 frame_load_result};
188 }
189
190 auto &load_result = frame_load_result.value();
191 anim.put_frame(frame_name_snake, std::move(load_result.frame));
192
193 // Update dimensions in params if not already set (first frame determines dimensions)
194 auto animation_params = anim.params();
195 if (animation_params.width_tiles() == 0 && animation_params.height_tiles() == 0) {
196 animation_params.width_tiles(load_result.width_tiles);
197 animation_params.height_tiles(load_result.height_tiles);
198 anim.params(std::move(animation_params));
199 }
200 }
201
202 result[anim_name.to_snake_case()] = std::move(anim);
203 }
204
205 return result;
206}
207
208} // namespace porytiles
Shared helper for loading animation frames from PNG files.
Parses C code to extract tileset animation parameters using callback chain discovery.
const DynamicCasedName & cased_name() const
Returns the structured name for this animation, preserving case format information.
A complete tileset animation with name, configuration, and frame data.
Definition animation.hpp:78
const AnimParams & params() const
Definition animation.hpp:91
High-level facade for parsing C/C++ source files.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
std::string to_c_identifier() const
Outputs PascalCase within each segment, with segments joined by underscores.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:63
An image loader that reads PNG files to create an Image with an index pixel type.
Provides a pokeemerald project filesystem-based implementation for TilesetMetadataProvider.
ChainableResult< std::map< std::string, Animation< IndexPixel > > > import_animations(const std::string &tileset_name) const
Import animations from a vanilla tileset.
static const Style bold
Bold text formatting.
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.