5#include <unordered_map>
15enum class CsvFormat { emerald, firered };
18 std::size_t metatile_id;
20 std::string terrain_type;
21 std::string encounter_type;
25 const std::string &line,
26 std::size_t line_index,
27 const std::filesystem::path &path,
28 const std::vector<std::string> &all_lines,
32 auto columns =
split(line,
",");
34 if (columns.size() < 2) {
35 std::vector<std::string> err_lines{};
36 err_lines.push_back(format.
format(
37 "{}:{}: expected at least 2 columns (id,behavior), found {}",
41 err_lines.emplace_back();
42 err_lines.append_range(file_printer.
print(all_lines, std::vector{line_index}));
49 auto id_result = parse_int<int>(columns[0], 0);
50 if (!id_result.has_value()) {
51 std::vector<std::string> err_lines{};
52 err_lines.push_back(format.
format(
53 "{}:{}: invalid metatile id '{}': {}",
58 err_lines.emplace_back();
59 err_lines.append_range(file_printer.
print(all_lines, std::vector{line_index}));
63 if (id_result.value() < 0) {
64 std::vector<std::string> err_lines{};
65 err_lines.push_back(format.
format(
66 "{}:{}: metatile id '{}' cannot be negative",
70 err_lines.emplace_back();
71 err_lines.append_range(file_printer.
print(all_lines, std::vector{line_index}));
75 return CsvRow{
static_cast<std::size_t
>(id_result.value()), columns[1],
"",
""};
79 const std::string &line,
80 std::size_t line_index,
81 const std::filesystem::path &path,
82 const std::vector<std::string> &all_lines,
86 auto columns =
split(line,
",");
88 if (columns.size() < 4) {
89 std::vector<std::string> err_lines{};
90 err_lines.push_back(format.
format(
91 "{}:{}: expected 4 columns (id,behavior,terrainType,encounterType), found {}",
95 err_lines.emplace_back();
96 err_lines.append_range(file_printer.
print(all_lines, std::vector{line_index}));
105 auto id_result = parse_int<int>(columns[0], 0);
106 if (!id_result.has_value()) {
107 std::vector<std::string> err_lines{};
108 err_lines.push_back(format.
format(
109 "{}:{}: invalid metatile id '{}': {}",
114 err_lines.emplace_back();
115 err_lines.append_range(file_printer.
print(all_lines, std::vector{line_index}));
119 if (id_result.value() < 0) {
120 std::vector<std::string> err_lines{};
121 err_lines.push_back(format.
format(
122 "{}:{}: metatile id '{}' cannot be negative",
126 err_lines.emplace_back();
127 err_lines.append_range(file_printer.
print(all_lines, std::vector{line_index}));
131 return CsvRow{
static_cast<std::size_t
>(id_result.value()), columns[1], columns[2], columns[3]};
135 const std::filesystem::path &path,
137 std::optional<BaseGame> base_game,
148 std::vector<std::string> lines{};
150 std::ifstream stream{path};
151 std::string line_buf{};
152 while (std::getline(stream, line_buf)) {
154 lines.push_back(line_buf);
160 "{}: file is empty, expected header 'id,behavior' or 'id,behavior,terrainType,encounterType'",
165 auto header_columns =
split(lines[0],
",");
166 for (
auto &col : header_columns) {
170 CsvFormat csv_format{};
172 if (header_columns.size() >= 4 && header_columns[0] ==
"id" && header_columns[1] ==
"behavior" &&
173 header_columns[2] ==
"terrainType" && header_columns[3] ==
"encounterType") {
174 csv_format = CsvFormat::firered;
176 else if (header_columns.size() >= 2 && header_columns[0] ==
"id" && header_columns[1] ==
"behavior") {
177 csv_format = CsvFormat::emerald;
180 std::vector<std::string> err_lines{};
181 err_lines.push_back(format.
format(
182 "{}:{}: invalid header, expected 'id,behavior' or 'id,behavior,terrainType,encounterType' but found '{}'",
186 err_lines.emplace_back();
187 err_lines.append_range(file_printer.
print(lines, std::vector<std::size_t>{0}));
192 if (base_game.has_value()) {
193 if (csv_format == CsvFormat::firered && base_game.value() != BaseGame::pokefirered) {
194 std::vector<std::string> err_lines{};
195 err_lines.push_back(format.
format(
196 "{}:{}: CSV has FireRed format (id,behavior,terrainType,encounterType) but project base game is '{}'",
199 FormatParam{to_string(base_game.value()), Style::bold}));
200 err_lines.emplace_back();
201 err_lines.append_range(file_printer.
print(lines, std::vector<std::size_t>{0}));
204 if (csv_format == CsvFormat::emerald && base_game.value() == BaseGame::pokefirered) {
205 std::vector<std::string> err_lines{};
206 err_lines.push_back(format.
format(
207 "{}:{}: CSV has Emerald format (id,behavior) but project base game is '{}'",
210 FormatParam{to_string(base_game.value()), Style::bold}));
211 err_lines.emplace_back();
212 err_lines.append_range(file_printer.
print(lines, std::vector<std::size_t>{0}));
218 if (csv_format == CsvFormat::firered) {
219 if (terrain_map ==
nullptr) {
220 panic(
"FireRed CSV format requires a terrain type provider but none was provided.");
222 if (encounter_map ==
nullptr) {
223 panic(
"FireRed CSV format requires an encounter type provider but none was provided.");
228 std::map<std::size_t, MetatileAttribute> result{};
229 std::unordered_map<std::size_t, std::size_t> id_to_line_index{};
231 for (std::size_t line_index = 1; line_index < lines.size(); ++line_index) {
232 const auto &line = lines[line_index];
239 csv_format == CsvFormat::firered
240 ? parse_firered_csv_row(line, line_index, path, lines, format, file_printer)
241 : parse_emerald_csv_row(line, line_index, path, lines, format, file_printer);
248 const auto &row = row_result.
value();
250 if (id_to_line_index.contains(row.metatile_id)) {
251 const std::size_t original_line_index = id_to_line_index.at(row.metatile_id);
252 std::vector<std::string> err_lines{};
255 err_lines.push_back(format.
format(
256 "{}:{}: duplicate metatile id '{}'",
260 err_lines.emplace_back();
263 err_lines.append_range(file_printer.
print(lines, std::vector{line_index}));
264 err_lines.emplace_back();
267 err_lines.push_back(format.
format(
268 "{} originally defined at line {}:",
273 err_lines.append_range(file_printer.
print(lines, std::vector{original_line_index}));
277 id_to_line_index.emplace(row.metatile_id, line_index);
279 auto behavior_value = behavior_map.
lookup(row.behavior);
280 if (!behavior_value.has_value()) {
281 std::vector<std::string> err_lines{};
282 err_lines.push_back(format.
format(
283 "{}:{}: unknown metatile behavior '{}'",
287 err_lines.emplace_back();
288 err_lines.append_range(file_printer.
print(lines, std::vector{line_index}));
293 if (csv_format == CsvFormat::firered) {
295 auto terrain_value = terrain_map->
lookup(row.terrain_type);
296 if (!terrain_value.has_value()) {
297 std::vector<std::string> err_lines{};
298 err_lines.push_back(format.
format(
299 "{}:{}: unknown terrain type '{}'",
303 err_lines.emplace_back();
304 err_lines.append_range(file_printer.
print(lines, std::vector{line_index}));
310 auto encounter_value = encounter_map->
lookup(row.encounter_type);
311 if (!encounter_value.has_value()) {
312 std::vector<std::string> err_lines{};
313 err_lines.push_back(format.
format(
314 "{}:{}: unknown encounter type '{}'",
318 err_lines.emplace_back();
319 err_lines.append_range(file_printer.
print(lines, std::vector{line_index}));
328 behavior_value.value(),
329 terrain_value.value(),
330 encounter_value.value(),
337 result.emplace(row.metatile_id,
MetatileAttribute{LayerType::normal, behavior_value.value()});
351 return parse_attributes_csv(
352 path, *behavior_map_, base_game_, terrain_map_, encounter_map_, *format_, *file_printer_);
ChainableResult< std::map< std::size_t, MetatileAttribute > > load(const std::filesystem::path &path) const
Loads metatile attributes from a CSV file.
Abstract interface for providing two-way metatile behavior mappings.
virtual ChainableResult< std::uint16_t > lookup(const std::string &behavior_name) const =0
Looks up the numeric value for a behavior constant name.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
T & value() &
Returns a reference to the contained success value.
bool has_value() const
Checks whether the result contains a success value.
Abstract interface for providing two-way metatile encounter type mappings.
virtual ChainableResult< std::uint8_t > lookup(const std::string &encounter_name) const =0
Looks up the numeric value for an encounter type constant name.
A service for printing file lines with highlighted lines and line numbers.
std::vector< std::string > print(const std::vector< std::string > &lines, const std::vector< std::size_t > &line_indices_to_highlight, std::size_t window_size=9) const
Prints lines with specified lines highlighted and line numbers shown.
static const Style bold
Bold text formatting.
Abstract interface for providing two-way metatile terrain type mappings.
virtual ChainableResult< std::uint8_t > lookup(const std::string &terrain_name) const =0
Looks up the numeric value for a terrain type constant name.
Abstract base class for applying text styling with context-aware formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > ¶ms) const
Formats a string with styled parameters using fmtlib syntax.
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
std::string & trim_line_ending(std::string &line)
Removes line ending characters from a string in-place.
@ split
Split the animation into separate animations for each palette variant (not yet implemented).
void trim(std::string &string)
Removes leading and trailing whitespace from a string in-place.
Utility functions for string manipulation and formatting.