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