Porytiles
Loading...
Searching...
No Matches
command_create_tileset.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <memory>
4#include <string>
5#include <unistd.h>
6
7#include "CLI/CLI.hpp"
8#include "fruit/fruit.h"
9
53
54#include "command.hpp"
55#include "option.hpp"
56
57class CreateTilesetCommand final : public Command {
58 public:
59 explicit CreateTilesetCommand(CLI::App &parent_app) : Command{parent_app, kCommandName, kCommandDesc, kCommandGroup}
60 {
61 CLI::App &cmd = get_app();
62 cmd.add_option("<tileset-name>", tileset_name_, "Name of the tileset to create (e.g., gTileset_MyTileset)")
63 ->required();
64 cmd.add_flag("--secondary", secondary_, "Create a secondary tileset instead of a primary.");
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);
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 behavior map provider
152 HeaderBehaviorMapProvider behavior_map_provider{
153 project_root / behaviors_header_root_relative, text_formatter, diag.get()};
154
155 // Setup metadata provider and tileset manager
156 ProjectTilesetMetadataProvider metadata_provider{project_root, text_formatter, diag.get()};
157 ProjectTilesetMetadataWriter metadata_writer{project_root, text_formatter};
158 IncbinDeclarationAppender incbin_appender{project_root, text_formatter};
159 ProjectTilesetAnimsModifier tileset_anims_modifier{project_root, &config, diag.get()};
160 ProjectPorytilesTilesetManager tileset_manager{
161 project_root,
162 &metadata_provider,
163 &metadata_writer,
164 &config,
165 diag.get(),
166 &incbin_appender,
167 &tileset_anims_modifier};
168
169 // Detect base game
170 BaseGameDetector base_game_detector{project_root, text_formatter, diag.get()};
171 auto base_game_result = base_game_detector.detect();
172 if (!base_game_result.has_value()) {
173 diag->fatal(base_game_result);
174 throw CLI::RuntimeError{1};
175 }
176 const BaseGame base_game = base_game_result.value();
177
178 // Conditionally create terrain/encounter providers for FireRed
179 std::unique_ptr<HeaderTerrainTypeMapProvider> terrain_provider;
180 std::unique_ptr<HeaderEncounterTypeMapProvider> encounter_provider;
181 if (base_game == BaseGame::pokefirered) {
182 terrain_provider = std::make_unique<HeaderTerrainTypeMapProvider>(
183 project_root / global_fieldmap_header_root_relative, text_formatter, diag.get());
184 encounter_provider = std::make_unique<HeaderEncounterTypeMapProvider>(
185 project_root / global_fieldmap_header_root_relative, text_formatter, diag.get());
186 }
187
188 // Setup attributes CSV loader (after base game detection for format validation)
189 AttributesCsvLoader attributes_csv_loader{
190 text_formatter, &behavior_map_provider, base_game, terrain_provider.get(), encounter_provider.get()};
191
192 // Setup the tileset repository
194 project_root, &config, &metadata_provider, text_formatter, diag.get()};
195 ProjectTilesetArtifactReader artifact_reader{
196 project_root,
197 metatile_attr_size,
198 &png_rgba_loader,
199 &png_indexed_loader,
200 &jasc_loader,
201 &attributes_csv_loader,
202 &anim_json_parser,
203 &anim_code_parser,
204 &metadata_provider};
205 ProjectTilesetArtifactWriter artifact_writer{
206 &config,
207 &config,
208 project_root,
209 base_game,
210 metatile_attr_size,
211 text_formatter,
212 diag.get(),
213 &png_rgba_saver,
214 &png_indexed_saver,
215 &jasc_saver,
216 &anim_json_parser,
217 &anim_code_generator,
218 &behavior_map_provider,
219 terrain_provider.get(),
220 encounter_provider.get()};
221 ProjectArtifactChecksumProvider checksum_provider{project_root};
222 TilesetRepo repo{
223 &checksum_provider, &metadata_provider, &key_provider, &artifact_reader, &artifact_writer, diag.get()};
224
225 // Setup creator and compiler
226 TilesetCreator creator{&config, &behavior_map_provider};
227 TilesetCompiler compiler{&config, text_formatter, diag.get(), tile_printer.get(), pal_printer.get()};
228
229 // Setup layout metadata provider (needed for secondary tileset primary pairing)
230 ProjectLayoutMetadataProvider layout_metadata_provider{project_root, text_formatter, diag.get()};
231
232 // Create and run the appropriate use case based on --secondary flag
233 ChainableResult<void> create_result;
234 if (secondary_) {
235 CreateSecondaryTileset create_use_case{
236 &creator,
237 &compiler,
238 &repo,
239 &metadata_provider,
240 &layout_metadata_provider,
241 &tileset_manager,
242 &config,
243 &config,
244 diag.get()};
245 create_result = create_use_case.create(tileset_name_);
246 }
247 else {
248 CreatePrimaryTileset create_use_case{
249 &creator, &compiler, &repo, &metadata_provider, &tileset_manager, &config, &config, diag.get()};
250 create_result = create_use_case.create(tileset_name_);
251 }
252 if (!create_result.has_value()) {
253 const auto fail_result = ChainableResult<void>{
254 FormattableError{"Failed to create tileset '{}'.", FormatParam{tileset_name_, Style::bold}},
255 create_result};
256 diag->fatal(fail_result);
257 throw CLI::RuntimeError{1};
258 }
259 }
260
261 private:
262 static constexpr auto kCommandName = "create-tileset";
263 static constexpr auto kCommandDesc = "Create a new Porytiles-managed tileset from scratch.";
264 static constexpr auto kCommandGroup = "COMMANDS";
265 std::string tileset_name_;
266 bool secondary_ = false;
267 OptProjectRoot project_root_opt_;
268 porytiles::CliOptionStorage cli_storage_;
269};
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
CreateTilesetCommand(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 creating a new primary Tileset from scratch.
Use case for creating a new secondary Tileset from scratch.
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).
Domain service for creating new tilesets from scratch.
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.