Porytiles
Loading...
Searching...
No Matches
primary_tileset_decompiler.cpp
Go to the documentation of this file.
2
3#include <array>
4#include <format>
5#include <iostream>
6#include <memory>
7#include <ranges>
8#include <set>
9#include <vector>
10
25
26namespace porytiles {
28{
29 // Unwrap config values
30 PT_UNWRAP_TILESET_CONFIG_PTR(config_, extrinsic_transparency, tileset.name(), std::unique_ptr<Tileset>);
31 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_palettes_in_primary, tileset.name(), std::unique_ptr<Tileset>);
32 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_palettes_total, tileset.name(), std::unique_ptr<Tileset>);
33 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_metatiles_in_primary, tileset.name(), std::unique_ptr<Tileset>);
34 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_tiles_in_primary, tileset.name(), std::unique_ptr<Tileset>);
35 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_tiles_per_metatile, tileset.name(), std::unique_ptr<Tileset>);
36
37 LayerModeConverter layer_mode_converter{format_, diag_, tile_printer_, extrinsic_transparency};
38 MetatileDecompiler metatile_decompiler{format_, diag_, tile_printer_, extrinsic_transparency};
39
40 // Decompile Porymap tilemap entries
42 tilemap_entries,
43 layer_mode_converter.triple_layerize(tileset.porymap_component()),
44 std::unique_ptr<Tileset>,
45 std::format("Failed to triple-layerize Porymap component for tileset '{}'.", tileset.name()));
46
47 // Create the new Porymap component early so we can pass it for potential backporting during animation
48 // decompilation. If mangle strategy is used, duplicate key frame tiles will be modified and backported to
49 // tiles.png.
50 //
51 // IMPORTANT: This must happen BEFORE metatile decompilation so that mangled tiles are used when decompiling
52 // metatiles.
53 auto new_porymap_component = std::make_unique<PorymapTilesetComponent>(tileset.porymap_component());
54
55 auto new_porytiles_component = std::make_unique<PorytilesTilesetComponent>();
56 std::size_t i = 0;
57 for (const auto &palette : tileset.porytiles_component().palettes()) {
58 // Copy over Porytiles palettes
59 if (palette.has_value()) {
60 new_porytiles_component->set_palette(i, palette.value());
61 }
62 i++;
63 }
64
65 // Copy metatile attributes from Porymap component to Porytiles component, applying the layer_type pin round-trip.
66 // Each bin-decoded attribute is unpinned. Whether it gets pinned (and thus survives as an explicit CSV value) is
67 // decided from the prior attributes.csv state recorded on the loaded component. On a fresh import (no
68 // CSV) the state defaults to no_csv, so every row is pinned from the bin.
69 const PriorPinColumnState prior_pin_state =
70 tileset.porytiles_component().prior_pin_column_state(FieldRole::layer_type);
71 const auto &porymap_attributes = tileset.porymap_component().metatile_attributes_bin();
72 for (std::size_t metatile_id = 0; metatile_id < porymap_attributes.size(); metatile_id++) {
73 new_porytiles_component->insert_attribute(
74 metatile_id,
76 porymap_attributes[metatile_id],
77 prior_pin_state,
78 tileset.porytiles_component().get_attribute(metatile_id)));
79 }
80
81 // Decompile animations from Porymap component to Porytiles component.
82 //
83 // IMPORTANT: This must happen BEFORE metatile decompilation so that any mangled key frame tiles are present in
84 // new_porymap_component->tiles_png() when metatiles are decompiled
85 // Triple-layerize the metatiles for the AnimDecompiler (it uses triple-layer addressing for override extraction)
86 new_porymap_component->metatiles_bin(tilemap_entries);
87
88 if (const auto &porymap_animations = tileset.porymap_component().anims(); !porymap_animations.empty()) {
89 // Save canonical resolved RGBA forms of previously-processed animation key frame tiles for inter-animation
90 // duplicate detection
91 std::set<PixelTile<Rgba32>> inter_anim_canonical_tiles;
92
93 AnimDecompiler anim_decompiler{config_, diag_, tile_printer_, palette_printer_};
94
95 for (const auto &index_pixel_anim : porymap_animations | std::views::values) {
96 // Run animation decompilation
98 rgba_anim,
99 anim_decompiler.decompile_animation(
100 tileset.name(), index_pixel_anim, inter_anim_canonical_tiles, *new_porymap_component),
101 std::unique_ptr<Tileset>,
102 diag_->formatter().format(
103 "Failed to decompile animation '{}'.", FormatParam{index_pixel_anim.name(), Style::bold}));
104
105 // After successful decompilation, record this animation's key frame tiles (post-mangle: the decompiler
106 // resolves them after any mangling) for inter-animation duplicate detection in later animations.
107 // Manual-linked animations aren't handled here, they have no key frames. Their tiles.png range is
108 // still covered by the cross-range duplicate check inside decompile_animation.
109 if (rgba_anim.has_key_frame()) {
110 for (const auto &t : rgba_anim.key_frame().tiles()) {
112 const PixelTile<Rgba32> &base = canonical;
113 inter_anim_canonical_tiles.insert(base);
114 }
115 }
116
117 new_porytiles_component->add_anim(std::move(rgba_anim));
118 }
119
120 // Warn about per anim overrides that reference non-existent animations (after all animations processed)
121 PT_UNWRAP_TILESET_CONFIG_PTR(config_, per_anim_overrides, tileset.name(), std::unique_ptr<Tileset>);
122 for (const auto &key : per_anim_overrides.value() | std::views::keys) {
123 if (!porymap_animations.contains(key)) {
124 diag_->warning(
125 "unused-animation-config",
126 {diag_->formatter().format(
127 "Animation config for '{}' does not match any animation in tileset '{}'.",
129 FormatParam{tileset.name(), Style::bold})});
130 }
131 }
132 }
133
134 // Restore original metatiles_bin (line 81 set triple-layer format for AnimDecompiler, but output must match input)
135 new_porymap_component->metatiles_bin(tileset.porymap_component().metatiles_bin());
136
137 // Decompile metatiles AFTER animation processing so that any mangled key frame tiles are used
139 metatiles,
140 metatile_decompiler.decompile_metatiles(
141 tilemap_entries, new_porymap_component->tiles_png(), tileset.porymap_component().palettes()),
142 std::unique_ptr<Tileset>,
143 std::format("Failed to decompile Porymap component for tileset '{}'.", tileset.name()));
144
145 // Convert metatiles into three layer images
146 LayerImageMetatileizer<Rgba32> metatileizer{};
147 constexpr std::size_t metatiles_per_row = 8; // Standard width for layer images (128 pixels)
148
150 layer_images,
151 metatileizer.demetatileize(metatiles, metatiles_per_row),
152 std::unique_ptr<Tileset>,
153 format_->format(
154 "Failed to demetatileize metatiles for tileset '{}'.", FormatParam{tileset.name(), Style::bold}));
155
156 auto &[bottom_image, middle_image, top_image] = layer_images;
157
158 // Set each porytiles_component layer to the new image
159 new_porytiles_component->bottom(bottom_image);
160 new_porytiles_component->middle(middle_image);
161 new_porytiles_component->top(top_image);
162
163 auto new_tileset =
164 std::make_unique<Tileset>(tileset.name(), std::move(new_porytiles_component), std::move(new_porymap_component));
165
166 return new_tileset;
167}
168
169} // namespace porytiles
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
Decompiles indexed animation tiles to RGBA format.
A PixelTile representation that stores the canonical (lexicographically minimal) orientation among al...
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A text parameter with associated styling for formatted output.
Service for converting layer images into collections of metatiles.
An 8x8 tile backed by literal-array-based per-pixel storage of an arbitrary pixel type.
ChainableResult< std::unique_ptr< Tileset > > decompile(const Tileset &tileset) const
static const Style bold
Bold text 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.
A complete tileset containing both Porytiles and Porymap components.
Definition tileset.hpp:12
const TextFormatter & formatter() const
virtual void warning(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged warning message.
@ canonical
Export tiles in canonical form without applying flip transformations.
MetatileAttribute merge_prior_layer_type_pin(MetatileAttribute fresh_from_bin, PriorPinColumnState prior_state, const std::optional< MetatileAttribute > &prior_attribute)
Applies the decompile/import round-trip rules to one metatile's layer_type pin.
PriorPinColumnState
What an attributes.csv (if any) said about a role's pin column when this component was loaded.
@ tileset
Configuration scoped to a specific tileset.
#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...