Porytiles
Loading...
Searching...
No Matches
command_compile_tileset.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <filesystem>
4#include <memory>
5#include <string>
6#include <unistd.h>
7
8#include "CLI/CLI.hpp"
9#include "fruit/fruit.h"
10
54
55#include "command.hpp"
56#include "option.hpp"
57
58class CompileTilesetCommand final : public Command {
59 public:
60 explicit CompileTilesetCommand(CLI::App &parent_app)
61 : Command{parent_app, kCommandName, kCommandDesc, kCommandGroup}
62 {
63 CLI::App &cmd = get_app();
64 cmd.add_option("<tileset-name>", tileset_name_, "Name of the tileset to compile")->required();
65 project_root_opt_.RegisterOpt(cmd);
66 porytiles::register_config_options(cmd, cli_storage_);
67 }
68
69 void Run() override
70 {
71 using namespace porytiles;
72
73 // Use Fruit DI to inject TextFormatter based on no_color flag
74 const bool no_color = !isatty(STDERR_FILENO); // Disable color when stderr is not a terminal
75 fruit::Injector injector{di::get_formatter_component, no_color};
76 auto text_formatter = injector.get<TextFormatter *>();
77
78 // Create unfiltered diag for config bootstrapping (so config-loading warnings always show)
79 auto stderr_diag = std::make_unique<StderrStyledUserDiagnostics>(text_formatter);
80
81 std::filesystem::path project_root = project_root_opt_.project_root();
82 std::filesystem::path fieldmap_header_root_relative{"include/fieldmap.h"};
83 std::filesystem::path behaviors_header_root_relative{"include/constants/metatile_behaviors.h"};
84 std::filesystem::path global_fieldmap_header_root_relative{"include/global.fieldmap.h"};
85
86 // Setup layered configuration (CLI options have highest priority)
87 std::vector<std::unique_ptr<ConfigProvider>> providers{};
88 providers.push_back(std::make_unique<CliOptionProvider>(cli_storage_));
89 auto yaml_provider = std::make_unique<YamlFileProvider>(text_formatter, stderr_diag.get(), project_root);
90 auto *yaml_provider_ptr = yaml_provider.get();
91 providers.push_back(std::move(yaml_provider));
92 providers.push_back(
93 std::make_unique<HeaderDefineProvider>(project_root, fieldmap_header_root_relative, text_formatter));
94 providers.push_back(std::make_unique<MetatilesHeaderProvider>(project_root, text_formatter));
95 providers.push_back(std::make_unique<DefaultProvider>());
96 LazyLayeredConfig config{text_formatter, std::move(providers)};
97
98 // Eagerly validate all YAML config files for unknown keys
99 if (yaml_provider_ptr->preload_and_validate(ConfigScopeType::tileset, tileset_name_)) {
100 const auto validation_err = ChainableResult<void>{FormattableError{
101 "Configuration validation failed for tileset '{}'.", FormatParam{tileset_name_, Style::bold}}};
102 stderr_diag->fatal(validation_err);
103 throw CLI::RuntimeError{1};
104 }
105
106 // Eagerly validate metatile-attr-size to fail fast before any file I/O
107 auto attr_size_check = config.metatile_attr_size(ConfigScopeType::tileset, tileset_name_);
108 if (!attr_size_check.has_value()) {
109 stderr_diag->fatal(attr_size_check);
110 throw CLI::RuntimeError{1};
111 }
112 const std::size_t metatile_attr_size = attr_size_check.value().value();
113
114 // Helper to safely extract filter patterns from config, falling back to empty on error
115 auto get_filter_patterns =
116 [&](ChainableResult<ConfigValue<std::vector<std::string>>> result) -> std::vector<std::string> {
117 if (result.has_value()) {
118 return std::move(result).value().value();
119 }
120 stderr_diag->fatal(result);
121 return {};
122 };
123
124 // Build diagnostic filters from config values
125 DiagnosticTagFilter warning_filter{
126 get_filter_patterns(config.diagnostic_warnings_exclude(ConfigScopeType::tileset, tileset_name_)),
127 get_filter_patterns(config.diagnostic_warnings_include(ConfigScopeType::tileset, tileset_name_))};
128
129 DiagnosticTagFilter remark_filter{
130 get_filter_patterns(config.diagnostic_remarks_exclude(ConfigScopeType::tileset, tileset_name_)),
131 get_filter_patterns(config.diagnostic_remarks_include(ConfigScopeType::tileset, tileset_name_))};
132
133 // Wrap with filter decorator for all subsequent operations
134 auto diag = std::make_unique<FilteredUserDiagnostics>(
135 text_formatter, stderr_diag.get(), std::move(warning_filter), std::move(remark_filter));
136
137 std::unique_ptr<TilePrinter> tile_printer = std::make_unique<AsciiTilePrinter>(text_formatter);
138 std::unique_ptr<PalettePrinter> pal_printer = std::make_unique<ColorPalettePrinter>(text_formatter);
139
140 // Initialize stateless services
141 PngRgbaImageLoader png_rgba_loader{};
142 PngIndexedImageLoader png_indexed_loader{};
143 PngRgbaImageSaver png_rgba_saver{};
144 PngIndexedImageSaver png_indexed_saver{};
145 JascPalLoader jasc_loader{text_formatter};
146 JascPalSaver jasc_saver{text_formatter};
147 AnimJsonParser anim_json_parser{text_formatter};
148 AnimCodeParser anim_code_parser{text_formatter, diag.get()};
149 AnimCodeGenerator anim_code_generator{};
150
151 // Setup primary compiler
152 TilesetCompiler compiler{&config, text_formatter, diag.get(), tile_printer.get(), pal_printer.get()};
153
154 // Setup behavior map provider
155 HeaderBehaviorMapProvider behavior_map_provider{
156 project_root / behaviors_header_root_relative, text_formatter, diag.get()};
157
158 // Setup metadata provider (needed by artifact reader for animation param loading)
159 ProjectTilesetMetadataProvider metadata_provider{project_root, text_formatter, diag.get()};
160 ProjectLayoutMetadataProvider layout_metadata_provider{project_root, text_formatter, diag.get()};
161
162 // Setup Porytiles tileset manager and its dependencies
163 ProjectTilesetMetadataWriter metadata_writer{project_root, text_formatter};
164 IncbinDeclarationAppender incbin_appender{project_root, text_formatter};
165 ProjectTilesetAnimsModifier tileset_anims_modifier{project_root, &config, diag.get()};
166 ProjectPorytilesTilesetManager tileset_manager{
167 project_root,
168 &metadata_provider,
169 &metadata_writer,
170 &config,
171 diag.get(),
172 &incbin_appender,
173 &tileset_anims_modifier};
174
175 // Detect base game
176 BaseGameDetector base_game_detector{project_root, text_formatter, diag.get()};
177 auto base_game_result = base_game_detector.detect();
178 if (!base_game_result.has_value()) {
179 diag->fatal(base_game_result);
180 throw CLI::RuntimeError{1};
181 }
182 const BaseGame base_game = base_game_result.value();
183
184 // Conditionally create terrain/encounter providers for FireRed
185 std::unique_ptr<HeaderTerrainTypeMapProvider> terrain_provider;
186 std::unique_ptr<HeaderEncounterTypeMapProvider> encounter_provider;
187 if (base_game == BaseGame::pokefirered) {
188 terrain_provider = std::make_unique<HeaderTerrainTypeMapProvider>(
189 project_root / global_fieldmap_header_root_relative, text_formatter, diag.get());
190 encounter_provider = std::make_unique<HeaderEncounterTypeMapProvider>(
191 project_root / global_fieldmap_header_root_relative, text_formatter, diag.get());
192 }
193
194 // Setup attributes CSV loader (after base game detection for format validation)
195 AttributesCsvLoader attributes_csv_loader{
196 text_formatter, &behavior_map_provider, base_game, terrain_provider.get(), encounter_provider.get()};
197
198 // Setup the tileset repository
200 project_root, &config, &metadata_provider, text_formatter, diag.get()};
201 ProjectTilesetArtifactReader artifact_reader{
202 project_root,
203 metatile_attr_size,
204 &png_rgba_loader,
205 &png_indexed_loader,
206 &jasc_loader,
207 &attributes_csv_loader,
208 &anim_json_parser,
209 &anim_code_parser,
210 &metadata_provider};
211 ProjectTilesetArtifactWriter artifact_writer{
212 &config,
213 &config,
214 project_root,
215 base_game,
216 metatile_attr_size,
217 text_formatter,
218 diag.get(),
219 &png_rgba_saver,
220 &png_indexed_saver,
221 &jasc_saver,
222 &anim_json_parser,
223 &anim_code_generator,
224 &behavior_map_provider,
225 terrain_provider.get(),
226 encounter_provider.get()};
227 ProjectArtifactChecksumProvider checksum_provider{project_root};
228 TilesetRepo repo{
229 &checksum_provider, &metadata_provider, &key_provider, &artifact_reader, &artifact_writer, diag.get()};
230
231 // Verify the tileset exists in the project before proceeding
232 if (!metadata_provider.exists(tileset_name_)) {
233 const auto not_found_err = ChainableResult<void>{FormattableError{
234 "Tileset '{}' does not exist. Create or import it first.", FormatParam{tileset_name_, Style::bold}}};
235 diag->fatal(not_found_err);
236 throw CLI::RuntimeError{1};
237 }
238
239 // Detect primary vs secondary and dispatch to the correct use case
240 auto is_secondary_result = metadata_provider.is_secondary(tileset_name_);
241 if (!is_secondary_result.has_value()) {
242 diag->fatal(is_secondary_result);
243 throw CLI::RuntimeError{1};
244 }
245
246 ChainableResult<void> compile_result;
247 if (is_secondary_result.value()) {
248 CompileSecondaryTileset compile_use_case{
249 &repo,
250 &compiler,
251 &metadata_provider,
252 &layout_metadata_provider,
253 &tileset_manager,
254 &config,
255 &config,
256 diag.get()};
257 compile_result = compile_use_case.compile(tileset_name_);
258 }
259 else {
260 CompilePrimaryTileset compile_use_case{
261 &repo, &compiler, &metadata_provider, &tileset_manager, &config, &config, diag.get()};
262 compile_result = compile_use_case.compile(tileset_name_);
263 }
264 if (!compile_result.has_value()) {
265 const auto fail_result = ChainableResult<std::unique_ptr<Tileset>>{
266 FormattableError{"Failed to compile tileset '{}'.", FormatParam{tileset_name_, Style::bold}},
267 compile_result};
268 diag->fatal(fail_result);
269 throw CLI::RuntimeError{1};
270 }
271 }
272
273 private:
274 static constexpr auto kCommandName = "compile-tileset";
275 static constexpr auto kCommandDesc =
276 "Compile a tileset -- update the Porymap assets to match the Porytiles assets.";
277 static constexpr auto kCommandGroup = "COMMANDS";
278 std::string tileset_name_;
279 OptProjectRoot project_root_opt_;
280 porytiles::CliOptionStorage cli_storage_;
281};
Command is an abstract class that provides basic command functionality for the Porytiles CLI driver.
Definition command.hpp:18
CLI::App & get_app() const
Definition command.hpp:44
CompileTilesetCommand(CLI::App &parent_app)
std::filesystem::path project_root() const
Definition option.hpp:58
void RegisterOpt(CLI::App &app) override
Definition option.hpp:46
Generates C header code for tileset animations.
Parses C code to extract tileset animation parameters using callback chain discovery.
Parses and writes animation configuration JSON files (anim.json).
A service that loads metatile attributes from a CSV file.
Detects the base game of a decompilation project.
ChainableResult< BaseGame > detect() const
Detects the base game for the project.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
bool has_value() const
Checks whether the result contains a success value.
Use case for compiling a primary Tileset.
Use case for compiling a secondary Tileset.
Regex-based include/exclude filter for diagnostic tags.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:63
Loads behavior mappings from a C/C++ header file containing behavior definitions.
Appends INCBIN declarations for Porytiles-managed tileset assets.
An implementation of FilePalLoader that loads palettes from JASC-PAL (Paintshop Pro) pal files.
An implementation of FilePalSaver that saves palettes to JASC-PAL (Paintshop Pro) pal files.
A Config implementation that lazily pulls a config value by consulting multiple priority-ordered back...
An image loader that reads PNG files to create an Image with an index pixel type.
An image saver that saves PNG files from an Image with an index pixel type.
An image loader that reads PNG files to create an Image with an Rgba32 pixel type.
An image saver that saves PNG files from an Image with an Rgba32 pixel type.
Pokeemerald project filesystem-based implementation of ArtifactChecksumProvider.
Provides a pokeemerald project filesystem-based implementation for LayoutMetadataProvider.
Manages Porytiles-owned tilesets via TilesetManifest JSON files.
Modifies tileset_anims.c to include Porytiles-managed animation code headers.
Provides a pokeemerald project filesystem-based implementation for TilesetArtifactKeyProvider.
Provides a filesystem-based implementation for TilesetArtifactReader.
Provides a filesystem-based implementation for TilesetArtifactWriter.
Provides a pokeemerald project filesystem-based implementation for TilesetMetadataProvider.
Provides surgical update capability for tileset metadata in headers.h files.
Abstract base class for applying text styling with context-aware formatting.
Service that compiles a Tileset (primary or secondary).
Repository interface for the Tileset aggregate root.
BaseGame
Identifies the target base game for a decompilation project.
Definition base_game.hpp:20
void register_config_options(CLI::App &app, CliOptionStorage &storage)
Registers all config options with a CLI11 App.
Storage struct for CLI option values.