yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_render_commands.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <optional>
6#include <sstream>
7#include <string>
8
9#include "absl/strings/numbers.h"
10#include "absl/strings/str_format.h"
12#include "cli/util/hex_util.h"
13#include "rom/rom.h"
14#include "util/macro.h"
15#include "zelda3/game_data.h"
16
17namespace yaze {
18namespace cli {
19namespace handlers {
20
21namespace {
22
23namespace ow = app::service::OverworldOverlay;
24
25absl::StatusOr<uint32_t> ParseOverworldOverlays(const std::string& s) {
26 uint32_t flags = ow::kNone;
27 std::istringstream ss(s);
28 std::string tok;
29 while (std::getline(ss, tok, ',')) {
30 tok.erase(0, tok.find_first_not_of(" \t"));
31 if (!tok.empty())
32 tok.erase(tok.find_last_not_of(" \t") + 1);
33 if (tok.empty())
34 continue;
35 if (tok == "sprites")
36 flags |= ow::kSprites;
37 else if (tok == "entrances")
38 flags |= ow::kEntrances;
39 else if (tok == "exits")
40 flags |= ow::kExits;
41 else if (tok == "holes")
42 flags |= ow::kHoles;
43 else if (tok == "items")
44 flags |= ow::kItems;
45 else if (tok == "grid")
46 flags |= ow::kGrid;
47 else if (tok == "all")
48 flags = ow::kAll & ~ow::kGrid; // grid stays opt-in
49 else
50 return absl::InvalidArgumentError(
51 absl::StrFormat("Unknown overlay '%s' (expected sprites, entrances, "
52 "exits, holes, items, grid, all)",
53 tok));
54 }
55 return flags;
56}
57
58absl::StatusOr<bool> ParseAreaOverlay(const std::string& value) {
59 if (value == "on" || value == "true" || value == "1")
60 return true;
61 if (value == "off" || value == "false" || value == "0")
62 return false;
63 return absl::InvalidArgumentError("--area-overlay must be on or off");
64}
65
66std::optional<std::string> OutputArg(const resources::ArgumentParser& parser) {
67 if (auto out = parser.GetString("out"); out.has_value())
68 return out;
69 return parser.GetString("output");
70}
71
72} // namespace
73
75 const resources::ArgumentParser& parser) {
76 RETURN_IF_ERROR(parser.RequireArgs({"screen"}));
77 const auto out = OutputArg(parser);
78 if (!out.has_value() || out->empty()) {
79 return absl::InvalidArgumentError("--out <path.png> is required");
80 }
81 if (const auto scale = parser.GetString("scale"); scale.has_value()) {
83 }
84 if (const auto ov = parser.GetString("overlays"); ov.has_value()) {
85 RETURN_IF_ERROR(ParseOverworldOverlays(*ov).status());
86 }
87 if (const auto ao = parser.GetString("area-overlay"); ao.has_value()) {
88 RETURN_IF_ERROR(ParseAreaOverlay(*ao).status());
89 }
90 if (const auto phase = parser.GetString("phase"); phase.has_value()) {
91 int value = -1;
92 if (!absl::SimpleAtoi(*phase, &value) || value < 0 || value > 2) {
93 return absl::InvalidArgumentError("--phase must be 0, 1, or 2");
94 }
95 }
96 return absl::OkStatus();
97}
98
100 Rom* rom, const resources::ArgumentParser& parser,
101 resources::OutputFormatter& formatter) {
102 resources::CommandInvocationContext invocation_context;
103 if (rom != nullptr && !rom->filename().empty()) {
104 invocation_context.source_rom_path = std::filesystem::path(rom->filename());
105 invocation_context.active_rom_path = std::filesystem::path(rom->filename());
106 }
107 return ExecuteWithContext(rom, parser, formatter, invocation_context);
108}
109
111 Rom* rom, const resources::ArgumentParser& parser,
113 const resources::CommandInvocationContext& invocation_context) {
115
116 int screen = 0;
117 if (!util::ParseHexString(*parser.GetString("screen"), &screen)) {
118 return absl::InvalidArgumentError("--screen must be hex (e.g. 0x40)");
119 }
120
121 const std::string output_path = *OutputArg(parser);
122 ASSIGN_OR_RETURN(const auto resolved_output,
125 invocation_context));
126
128 req.screen_id = screen;
129 if (auto ov = parser.GetString("overlays"); ov.has_value()) {
130 ASSIGN_OR_RETURN(req.overlay_flags, ParseOverworldOverlays(*ov));
131 }
132 if (auto phase = parser.GetString("phase"); phase.has_value()) {
133 absl::SimpleAtoi(*phase, &req.phase);
134 }
135 if (auto sc = parser.GetString("scale"); sc.has_value()) {
137 }
138 if (auto ao = parser.GetString("area-overlay"); ao.has_value()) {
139 ASSIGN_OR_RETURN(req.area_overlay, ParseAreaOverlay(*ao));
140 }
141
142 if (!rom || !rom->is_loaded()) {
143 return absl::FailedPreconditionError("ROM not loaded");
144 }
145
146 zelda3::GameData game_data;
147 auto gd_status = zelda3::LoadGameData(*rom, game_data);
148 if (!gd_status.ok()) {
149 return absl::InternalError(
150 absl::StrFormat("Failed to load game data: %s", gd_status.message()));
151 }
152
153 app::service::RenderService render_service(rom, &game_data);
154 ASSIGN_OR_RETURN(auto result, render_service.RenderOverworldArea(req));
155
157 invocation_context));
158 std::ofstream out(resolved_output, std::ios::binary);
159 if (!out) {
160 return absl::InternalError(
161 absl::StrFormat("Cannot open output file: %s", output_path));
162 }
163 out.write(reinterpret_cast<const char*>(result.png_data.data()),
164 static_cast<std::streamsize>(result.png_data.size()));
165 out.close();
166 if (!out) {
167 return absl::InternalError(
168 absl::StrFormat("Write failed for: %s", output_path));
169 }
170
171 formatter.AddField("requested_screen",
172 absl::StrFormat("0x%02X", result.requested_screen));
173 formatter.AddField("parent_screen",
174 absl::StrFormat("0x%02X", result.parent_screen));
175 formatter.AddField("area_size", result.area_size);
176 formatter.AddField("subscreen_overlay",
177 result.subscreen_overlay < 0
178 ? std::string("none")
179 : absl::StrFormat("0x%02X", result.subscreen_overlay));
180 formatter.BeginArray("screens");
181 for (int s : result.screens)
182 formatter.AddArrayItem(absl::StrFormat("0x%02X", s));
183 formatter.EndArray();
184 formatter.AddField("sprite_phase", req.phase < 0
185 ? std::string("all")
186 : absl::StrFormat("%d", req.phase));
187 formatter.AddField("output", output_path);
188 formatter.AddField("width", result.width);
189 formatter.AddField("height", result.height);
190 formatter.AddField("scale", absl::StrFormat("%.2f", req.scale));
191 formatter.AddField("size_bytes", static_cast<int>(result.png_data.size()));
192 formatter.AddField(
193 "coordinates",
194 "marker x/y are area-local pixels at scale 1; tile = x/16, y/16");
195 formatter.BeginArray("markers");
196 for (const auto& m : result.markers) {
197 formatter.BeginObject();
198 formatter.AddField("kind", m.kind);
199 formatter.AddField("id", absl::StrFormat("0x%02X", m.id));
200 if (m.phase >= 0) {
201 formatter.AddField("phase", m.phase);
202 formatter.AddField("list_index", m.list_index);
203 }
204 formatter.AddField("x", m.x);
205 formatter.AddField("y", m.y);
206 formatter.AddField("tile", absl::StrFormat("(%d,%d)", m.x / 16, m.y / 16));
207 formatter.EndObject();
208 }
209 formatter.EndArray();
210 return absl::OkStatus();
211}
212
213} // namespace handlers
214} // namespace cli
215} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
auto filename() const
Definition rom.h:207
bool is_loaded() const
Definition rom.h:187
absl::StatusOr< OverworldRenderResult > RenderOverworldArea(const OverworldRenderRequest &req)
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status ExecuteWithContext(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter, const resources::CommandInvocationContext &invocation_context) override
Execute with immutable, invocation-scoped ROM path identity.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
absl::Status RequireArgs(const std::vector< std::string > &required) const
Validate that required arguments are present.
Utility for consistent output formatting across commands.
void BeginArray(const std::string &key)
Begin an array.
void AddArrayItem(const std::string &item)
Add an item to current array.
void BeginObject(const std::string &title="")
Start a JSON object or text section.
void EndObject()
End a JSON object or text section.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
absl::StatusOr< float > ParseRenderScale(absl::string_view value)
std::optional< std::string > OutputArg(const resources::ArgumentParser &parser)
absl::StatusOr< std::filesystem::path > ResolveStableArtifactPath(const std::filesystem::path &path)
absl::Status RejectArtifactRomAliases(absl::string_view option_name, const std::filesystem::path &artifact_path, const CommandInvocationContext &invocation_context)
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
Definition game_data.cc:123
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::optional< std::filesystem::path > active_rom_path
std::optional< std::filesystem::path > source_rom_path