yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
minecart_track_editor_panel.cc
Go to the documentation of this file.
2#include <fstream>
3#include <iomanip>
4#include <sstream>
5#include "absl/strings/str_format.h"
6#include "absl/strings/str_split.h"
7#include "imgui/imgui.h"
8
9#include <filesystem>
10#include <iostream>
11#include <regex>
12#include "app/gui/core/icons.h"
13#include "app/gui/core/input.h"
14#include "util/log.h"
15
16namespace yaze::editor {
17
18void MinecartTrackEditorPanel::SetProjectRoot(const std::string& root) {
19 if (project_root_ != root) {
20 project_root_ = root;
21 loaded_ = false; // Trigger reload on next draw
22 }
23}
24
26 uint16_t camera_x,
27 uint16_t camera_y) {
29 picking_track_index_ < static_cast<int>(tracks_.size())) {
30 tracks_[picking_track_index_].room_id = room_id;
31 tracks_[picking_track_index_].start_x = camera_x;
32 tracks_[picking_track_index_].start_y = camera_y;
33
34 last_picked_x_ = camera_x;
35 last_picked_y_ = camera_y;
36 has_picked_coords_ = true;
37
39 absl::StrFormat("Track %d: Set to Room $%04X, Pos ($%04X, $%04X)",
40 picking_track_index_, room_id, camera_x, camera_y);
41 show_success_ = true;
42 }
43
44 // Exit picking mode
45 picking_mode_ = false;
47}
48
50 picking_mode_ = true;
51 picking_track_index_ = track_index;
52 status_message_ = absl::StrFormat(
53 "Click on the dungeon canvas to set Track %d position", track_index);
54 show_success_ = false;
55}
56
62
64 if (project_root_.empty()) {
65 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Project root not set.");
66 return;
67 }
68
69 if (!loaded_) {
70 LoadTracks();
71 }
72
73 ImGui::Text("Minecart Track Editor");
74 if (ImGui::Button(ICON_MD_SAVE " Save Tracks")) {
75 SaveTracks();
76 }
77
78 // Show picking mode indicator
79 if (picking_mode_) {
80 ImGui::SameLine();
81 if (ImGui::Button(ICON_MD_CANCEL " Cancel Pick")) {
83 }
84 ImGui::SameLine();
85 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f),
86 ICON_MD_MY_LOCATION " Picking for Track %d...",
88 }
89
90 if (!status_message_.empty() && !picking_mode_) {
91 ImGui::SameLine();
92 ImGui::TextColored(show_success_ ? ImVec4(0, 1, 0, 1) : ImVec4(1, 0, 0, 1),
93 "%s", status_message_.c_str());
94 }
95
96 ImGui::Separator();
97
98 // Coordinate format help
99 ImGui::TextDisabled(
100 "Camera coordinates use $1XXX format (base $1000 + room offset + local "
101 "position)");
102 ImGui::TextDisabled(
103 "Hover over dungeon canvas to see coordinates, or click 'Pick' button.");
104 ImGui::Separator();
105
106 if (ImGui::BeginTable("TracksTable", 6,
107 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
108 ImGuiTableFlags_Resizable)) {
109 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 30.0f);
110 ImGui::TableSetupColumn("Room ID", ImGuiTableColumnFlags_WidthFixed, 80.0f);
111 ImGui::TableSetupColumn("Camera X", ImGuiTableColumnFlags_WidthFixed,
112 80.0f);
113 ImGui::TableSetupColumn("Camera Y", ImGuiTableColumnFlags_WidthFixed,
114 80.0f);
115 ImGui::TableSetupColumn("Pick", ImGuiTableColumnFlags_WidthFixed, 50.0f);
116 ImGui::TableSetupColumn("Go", ImGuiTableColumnFlags_WidthFixed, 40.0f);
117 ImGui::TableHeadersRow();
118
119 for (auto& track : tracks_) {
120 ImGui::TableNextRow();
121
122 // Highlight the row being picked
123 if (picking_mode_ && track.id == picking_track_index_) {
124 ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,
125 IM_COL32(80, 80, 0, 100));
126 }
127
128 ImGui::TableNextColumn();
129 ImGui::Text("%d", track.id);
130
131 ImGui::TableNextColumn();
132 uint16_t room_id = static_cast<uint16_t>(track.room_id);
134 absl::StrFormat("##Room%d", track.id).c_str(), &room_id, 60.0f)) {
135 track.room_id = room_id;
136 }
137
138 ImGui::TableNextColumn();
139 uint16_t start_x = static_cast<uint16_t>(track.start_x);
141 absl::StrFormat("##StartX%d", track.id).c_str(), &start_x,
142 60.0f)) {
143 track.start_x = start_x;
144 }
145
146 ImGui::TableNextColumn();
147 uint16_t start_y = static_cast<uint16_t>(track.start_y);
149 absl::StrFormat("##StartY%d", track.id).c_str(), &start_y,
150 60.0f)) {
151 track.start_y = start_y;
152 }
153
154 // Pick button to select coordinates from canvas
155 ImGui::TableNextColumn();
156 ImGui::PushID(track.id);
157 bool is_picking_this = picking_mode_ && picking_track_index_ == track.id;
158 if (is_picking_this) {
159 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.6f, 0.0f, 1.0f));
160 }
161 if (ImGui::SmallButton(ICON_MD_MY_LOCATION)) {
162 if (is_picking_this) {
164 } else {
165 StartCoordinatePicking(track.id);
166 }
167 }
168 if (is_picking_this) {
169 ImGui::PopStyleColor();
170 }
171 if (ImGui::IsItemHovered()) {
172 ImGui::SetTooltip(is_picking_this ? "Cancel picking"
173 : "Pick coordinates from canvas");
174 }
175 ImGui::PopID();
176
177 // Go to room button
178 ImGui::TableNextColumn();
179 ImGui::PushID(track.id + 1000);
180 if (ImGui::SmallButton(ICON_MD_ARROW_FORWARD)) {
182 room_navigation_callback_(track.room_id);
183 }
184 }
185 if (ImGui::IsItemHovered()) {
186 ImGui::SetTooltip("Navigate to room $%04X", track.room_id);
187 }
188 ImGui::PopID();
189 }
190
191 ImGui::EndTable();
192 }
193}
194
196 std::filesystem::path path = std::filesystem::path(project_root_) /
197 "Sprites/Objects/data/minecart_tracks.asm";
198
199 if (!std::filesystem::exists(path)) {
200 status_message_ = "File not found: " + path.string();
201 show_success_ = false;
202 loaded_ = true; // Prevent retry loop
203 tracks_.clear();
204 return;
205 }
206
207 std::ifstream file(path);
208 std::stringstream buffer;
209 buffer << file.rdbuf();
210 std::string content = buffer.str();
211
212 std::vector<int> rooms;
213 std::vector<int> xs;
214 std::vector<int> ys;
215
216 if (!ParseSection(content, ".TrackStartingRooms", rooms) ||
217 !ParseSection(content, ".TrackStartingX", xs) ||
218 !ParseSection(content, ".TrackStartingY", ys)) {
219 status_message_ = "Error parsing file format.";
220 show_success_ = false;
221 } else {
222 tracks_.clear();
223 size_t count = std::min({rooms.size(), xs.size(), ys.size()});
224 for (size_t i = 0; i < count; ++i) {
225 tracks_.push_back({(int)i, rooms[i], xs[i], ys[i]});
226 }
227 status_message_ = "";
228 show_success_ = true;
229 }
230 loaded_ = true;
231}
232
233bool MinecartTrackEditorPanel::ParseSection(const std::string& content,
234 const std::string& label,
235 std::vector<int>& out_values) {
236 size_t pos = content.find(label);
237 if (pos == std::string::npos)
238 return false;
239
240 // Start searching after the label
241 size_t start = pos + label.length();
242
243 // Find lines starting with 'dw'
244 std::regex dw_regex(R"(dw\s+((?:\$[0-9A-Fa-f]{4}(?:,\s*)?)+))");
245
246 // Create a substring from start to end or next label (simplified: just search until next dot label or end)
247 // Actually, searching line by line is safer.
248 std::stringstream ss(content.substr(start));
249 std::string line;
250 while (std::getline(ss, line)) {
251 // Stop if we hit another label
252 size_t trimmed_start = line.find_first_not_of(" \t");
253 if (trimmed_start != std::string::npos && line[trimmed_start] == '.')
254 break;
255
256 std::smatch match;
257 if (std::regex_search(line, match, dw_regex)) {
258 std::string values_str = match[1];
259 std::stringstream val_ss(values_str);
260 std::string segment;
261 while (std::getline(val_ss, segment, ',')) {
262 // Trim
263 segment.erase(0, segment.find_first_not_of(" \t$"));
264 // Parse hex
265 try {
266 out_values.push_back(std::stoi(segment, nullptr, 16));
267 } catch (...) {}
268 }
269 }
270 }
271 return true;
272}
273
275 std::filesystem::path path = std::filesystem::path(project_root_) /
276 "Sprites/Objects/data/minecart_tracks.asm";
277
278 std::ofstream file(path);
279 if (!file.is_open()) {
280 status_message_ = "Failed to open file for writing.";
281 show_success_ = false;
282 return;
283 }
284
285 std::vector<int> rooms, xs, ys;
286 for (const auto& t : tracks_) {
287 rooms.push_back(t.room_id);
288 xs.push_back(t.start_x);
289 ys.push_back(t.start_y);
290 }
291
292 file << " ; This is which room each track should start in if it hasn't "
293 "already\n";
294 file << " ; been given a track.\n";
295 file << FormatSection(".TrackStartingRooms", rooms);
296 file << "\n";
297
298 file << " ; This is where within the room each track should start in if it "
299 "hasn't\n";
300 file << " ; already been given a position. This is necessary to allow for "
301 "more\n";
302 file << " ; than one stopping point to be in one room.\n";
303 file << FormatSection(".TrackStartingX", xs);
304 file << "\n";
305
306 file << FormatSection(".TrackStartingY", ys);
307
308 status_message_ = "Tracks saved successfully!";
309 show_success_ = true;
310}
311
313 const std::string& label, const std::vector<int>& values) {
314 std::stringstream ss;
315 ss << " " << label << "\n";
316
317 for (size_t i = 0; i < values.size(); i += 8) {
318 ss << " dw ";
319 for (size_t j = 0; j < 8 && i + j < values.size(); ++j) {
320 if (j > 0)
321 ss << ", ";
322 ss << absl::StrFormat("$%04X", values[i + j]);
323 }
324 ss << "\n";
325 }
326 return ss.str();
327}
328
329} // namespace yaze::editor
std::string FormatSection(const std::string &label, const std::vector< int > &values)
void Draw(bool *p_open) override
Draw the panel content.
bool ParseSection(const std::string &content, const std::string &label, std::vector< int > &out_values)
void SetPickedCoordinates(int room_id, uint16_t camera_x, uint16_t camera_y)
#define ICON_MD_MY_LOCATION
Definition icons.h:1270
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_ARROW_FORWARD
Definition icons.h:184
#define ICON_MD_SAVE
Definition icons.h:1644
Editors are the view controllers for the application.
bool InputHexWordCustom(const char *label, uint16_t *data, float input_width)
Definition input.cc:726