yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_editor_system.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5
6#include "absl/strings/str_cat.h"
7#include "nlohmann/json.hpp"
9
10namespace yaze {
11namespace zelda3 {
12
13namespace {
14
15absl::Status SaveSingleRoomState(Rom* rom, Room& room) {
20
21 // Room-indexed tables (torches, chests, pot items, custom collision) must
22 // preserve every other room's ROM data when saving a single room. Using the
23 // full dungeon range keeps the save behavior consistent with the editor-level
24 // save path and avoids truncating data for rooms after the edited one.
25 const int room_limit = kNumberOfRooms;
26 auto room_lookup_const = [&room](int room_id) -> const Room* {
27 return room_id == room.id() ? &room : nullptr;
28 };
29 auto room_lookup_mut = [&room](int room_id) -> Room* {
30 return room_id == room.id() ? &room : nullptr;
31 };
32
33 RETURN_IF_ERROR(SaveAllTorches(rom, room_limit, room_lookup_const));
34 RETURN_IF_ERROR(SaveAllCollision(rom, room_limit, room_lookup_mut));
35 RETURN_IF_ERROR(SaveAllChests(rom, room_limit, room_lookup_const));
36 RETURN_IF_ERROR(SaveAllPotItems(rom, room_limit, room_lookup_const));
37 return absl::OkStatus();
38}
39
40} // namespace
41
43 : rom_(rom), game_data_(game_data) {}
44
51
53 if (rom_ == nullptr) {
54 return absl::InvalidArgumentError("ROM is null");
55 }
56
57 // Initialize object editor
58 object_editor_ = std::make_shared<DungeonObjectEditor>(rom_);
59 RETURN_IF_ERROR(object_editor_->InitializeEditor());
60
61 return absl::OkStatus();
62}
63
64absl::Status DungeonEditorSystem::LoadDungeon(int dungeon_id) {
66 editor_state_.is_dirty = false;
67 editor_state_.last_save_time = std::chrono::steady_clock::now();
68 return absl::OkStatus();
69}
70
72 if (!rom_)
73 return absl::InvalidArgumentError("ROM is null");
74
75 std::vector<int> room_ids;
76 room_ids.reserve(rooms_.size() + 1);
77 if (external_room_ != nullptr) {
78 room_ids.push_back(external_room_->id());
79 }
80 for (const auto& [room_id, room] : rooms_) {
81 (void)room;
82 if (std::find(room_ids.begin(), room_ids.end(), room_id) ==
83 room_ids.end()) {
84 room_ids.push_back(room_id);
85 }
86 }
87
88 for (int room_id : room_ids) {
89 Room* room = GetManagedRoom(room_id);
90 if (room == nullptr) {
92 room = GetManagedRoom(room_id);
93 }
94 if (room == nullptr || !room->IsLoaded()) {
95 continue;
96 }
101 }
102
103 auto room_lookup_const = [this](int room_id) -> const Room* {
104 return GetManagedRoom(room_id);
105 };
106 auto room_lookup_mut = [this](int room_id) -> Room* {
107 return GetManagedRoom(room_id);
108 };
109
112 RETURN_IF_ERROR(SaveAllChests(rom_, kNumberOfRooms, room_lookup_const));
114
115 editor_state_.is_dirty = false;
116 editor_state_.last_save_time = std::chrono::steady_clock::now();
117
118 return absl::OkStatus();
119}
120
121absl::Status DungeonEditorSystem::SaveRoom(int room_id) {
122 return SaveRoomData(room_id);
123}
124
125absl::Status DungeonEditorSystem::ReloadRoom(int room_id) {
126 return LoadRoomData(room_id);
127}
128
129absl::Status DungeonEditorSystem::SetCurrentRoom(int room_id) {
130 if (room_id < 0 || room_id >= kNumberOfRooms) {
131 return absl::InvalidArgumentError("Invalid room ID");
132 }
133
135 if (external_room_ == nullptr) {
137 }
139}
140
144
145absl::StatusOr<Room> DungeonEditorSystem::GetRoom(int room_id) {
146 if (room_id < 0 || room_id >= kNumberOfRooms) {
147 return absl::InvalidArgumentError("Invalid room ID");
148 }
149
150 return Room(room_id, rom_, game_data_);
151}
152
153std::shared_ptr<DungeonObjectEditor> DungeonEditorSystem::GetObjectEditor() {
154 if (!object_editor_) {
155 object_editor_ = std::make_shared<DungeonObjectEditor>(rom_);
156 (void)object_editor_->InitializeEditor();
158 }
159 return object_editor_;
160}
161
163 if (object_editor_) {
164 return object_editor_->Undo();
165 }
166
167 if (!CanUndo()) {
168 return absl::FailedPreconditionError("Nothing to undo");
169 }
170
171 return absl::OkStatus();
172}
173
175 if (object_editor_) {
176 return object_editor_->Redo();
177 }
178
179 if (!CanRedo()) {
180 return absl::FailedPreconditionError("Nothing to redo");
181 }
182
183 return absl::OkStatus();
184}
185
187 return !undo_history_.empty();
188}
189
191 return !redo_history_.empty();
192}
193
195 undo_history_.clear();
196 redo_history_.clear();
197}
198
202
206
208 return rom_;
209}
210
214
216 rom_ = rom;
217 if (object_editor_) {
218 object_editor_->SetROM(rom);
220 }
221}
222
229
231 int current_room_id) {
232 // Move the shared object editor away from managed Room storage before
233 // clearing it. Panels retain this object-editor instance across ROM reloads.
234 external_room_ = external_room;
235 if (object_editor_) {
236 (void)object_editor_->ClearSelection();
237 object_editor_->ClearHistory();
238 object_editor_->SetExternalRoom(external_room_);
239 }
240
241 rooms_.clear();
242 ClearHistory();
243 editor_state_.is_dirty = false;
244
245 if (current_room_id < 0 || current_room_id >= kNumberOfRooms) {
246 return absl::OkStatus();
247 }
248 editor_state_.current_room_id = current_room_id;
249 if (external_room_ != nullptr) {
251 }
252 return LoadRoomData(current_room_id);
253}
254
256 if (!object_editor_) {
257 return absl::OkStatus();
258 }
259 if (external_room_ != nullptr) {
260 object_editor_->SetExternalRoom(external_room_);
261 return absl::OkStatus();
262 }
263
265 if (room == nullptr) {
268 }
269 if (room == nullptr) {
270 return absl::NotFoundError("Current room is not available");
271 }
272 object_editor_->SetExternalRoom(room);
273 return absl::OkStatus();
274}
275
277 if (external_room_ != nullptr && external_room_->id() == room_id) {
278 return external_room_;
279 }
280 auto it = rooms_.find(room_id);
281 if (it == rooms_.end()) {
282 return nullptr;
283 }
284 return &it->second;
285}
286
288 return SaveSingleRoomState(rom_, room);
289}
290
292 if (!object_editor_) {
293 object_editor_ = std::make_shared<DungeonObjectEditor>(rom_);
294 }
295 RETURN_IF_ERROR(object_editor_->InitializeEditor());
297}
298
299absl::Status DungeonEditorSystem::LoadRoomData(int room_id) {
300 if (!rom_)
301 return absl::InvalidArgumentError("ROM is null");
302
303 if (room_id < 0 || room_id >= kNumberOfRooms) {
304 return absl::InvalidArgumentError("Invalid room ID");
305 }
306
307 if (external_room_ != nullptr && external_room_->id() == room_id) {
310 }
311
312 rooms_[room_id] = LoadRoomFromRom(rom_, room_id);
313 if (editor_state_.current_room_id == room_id) {
315 }
316 return absl::OkStatus();
317}
318
319absl::Status DungeonEditorSystem::SaveRoomData(int room_id) {
320 if (!rom_)
321 return absl::InvalidArgumentError("ROM is null");
322
323 if (room_id < 0 || room_id >= kNumberOfRooms) {
324 return absl::InvalidArgumentError("Invalid room ID");
325 }
326
327 Room* room = GetManagedRoom(room_id);
328 if (room == nullptr) {
330 room = GetManagedRoom(room_id);
331 }
332 if (room == nullptr) {
333 return absl::NotFoundError("Room is not available");
334 }
335 if (!room->IsLoaded()) {
336 return absl::OkStatus();
337 }
338
339 return SaveManagedRoom(*room);
340}
341
342std::unique_ptr<DungeonEditorSystem> CreateDungeonEditorSystem(
343 Rom* rom, GameData* game_data) {
344 return std::make_unique<DungeonEditorSystem>(rom, game_data);
345}
346
347absl::StatusOr<std::string> ExportRoomLayoutTemplate(const Room& room) {
348 using json = nlohmann::json;
349
350 json tmpl;
351 tmpl["version"] = 1;
352 tmpl["room_id"] = room.id();
353
354 // Room properties
355 json props;
356 props["blockset"] = room.blockset();
357 props["palette"] = room.palette();
358 props["layout"] = room.layout_id();
359 props["spriteset"] = room.spriteset();
360 tmpl["properties"] = props;
361
362 // Tile objects
363 json objects_arr = json::array();
364 for (const auto& obj : room.GetTileObjects()) {
365 json obj_json;
366 obj_json["id"] = obj.id_;
367 obj_json["x"] = obj.x_;
368 obj_json["y"] = obj.y_;
369 obj_json["size"] = obj.size_;
370 obj_json["layer"] = obj.GetLayerValue();
371 objects_arr.push_back(obj_json);
372 }
373 tmpl["objects"] = objects_arr;
374
375 // Sprites
376 json sprites_arr = json::array();
377 for (const auto& sprite : room.GetSprites()) {
378 json spr_json;
379 spr_json["id"] = sprite.id();
380 spr_json["x"] = sprite.x();
381 spr_json["y"] = sprite.y();
382 spr_json["subtype"] = sprite.subtype();
383 spr_json["layer"] = sprite.layer();
384 sprites_arr.push_back(spr_json);
385 }
386 tmpl["sprites"] = sprites_arr;
387
388 // Pot items
389 json items_arr = json::array();
390 for (const auto& item : room.GetPotItems()) {
391 json item_json;
392 item_json["position"] = item.position;
393 item_json["item"] = item.item;
394 items_arr.push_back(item_json);
395 }
396 tmpl["pot_items"] = items_arr;
397
398 return tmpl.dump(2);
399}
400
401absl::Status ApplyRoomLayoutTemplate(Room& room, const std::string& json_str,
402 bool apply_properties) {
403 using json = nlohmann::json;
404
405 json tmpl;
406 try {
407 tmpl = json::parse(json_str);
408 } catch (const json::parse_error& err) {
409 return absl::InvalidArgumentError(
410 absl::StrCat("Invalid JSON: ", err.what()));
411 }
412
413 if (!tmpl.contains("version") || tmpl["version"].get<int>() != 1) {
414 return absl::InvalidArgumentError("Unsupported template version");
415 }
416
417 // Apply room properties if requested
418 if (apply_properties && tmpl.contains("properties")) {
419 const auto& props = tmpl["properties"];
420 if (props.contains("blockset"))
421 room.SetBlockset(props["blockset"]);
422 if (props.contains("palette"))
423 room.SetPalette(props["palette"]);
424 if (props.contains("layout"))
425 room.SetLayoutId(props["layout"]);
426 if (props.contains("spriteset"))
427 room.SetSpriteset(props["spriteset"]);
428 }
429
430 // Apply objects
431 if (tmpl.contains("objects")) {
432 auto& objects = room.GetTileObjects();
433 objects.clear();
434 for (const auto& obj_json : tmpl["objects"]) {
435 int16_t obj_id = obj_json.value("id", static_cast<int16_t>(0));
436 uint8_t obj_x = obj_json.value("x", static_cast<uint8_t>(0));
437 uint8_t obj_y = obj_json.value("y", static_cast<uint8_t>(0));
438 uint8_t obj_size = obj_json.value("size", static_cast<uint8_t>(0));
439 uint8_t obj_layer = obj_json.value("layer", static_cast<uint8_t>(0));
440 objects.emplace_back(obj_id, obj_x, obj_y, obj_size, obj_layer);
441 }
442 }
443
444 // Apply sprites
445 if (tmpl.contains("sprites")) {
446 auto& sprites = room.GetSprites();
447 sprites.clear();
448 for (const auto& spr_json : tmpl["sprites"]) {
449 uint8_t spr_id = spr_json.value("id", static_cast<uint8_t>(0));
450 uint8_t spr_x = spr_json.value("x", static_cast<uint8_t>(0));
451 uint8_t spr_y = spr_json.value("y", static_cast<uint8_t>(0));
452 uint8_t spr_sub = spr_json.value("subtype", static_cast<uint8_t>(0));
453 uint8_t spr_layer = spr_json.value("layer", static_cast<uint8_t>(0));
454 sprites.emplace_back(spr_id, spr_x, spr_y, spr_sub, spr_layer);
455 }
456 }
457
458 // Apply pot items
459 if (tmpl.contains("pot_items")) {
460 auto& items = room.GetPotItems();
461 items.clear();
462 for (const auto& item_json : tmpl["pot_items"]) {
463 PotItem item;
464 item.position = item_json.value("position", static_cast<uint16_t>(0));
465 item.item = item_json.value("item", static_cast<uint8_t>(0));
466 items.push_back(item);
467 }
468 }
469
470 return absl::OkStatus();
471}
472
473} // namespace zelda3
474} // 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:28
absl::Status RefreshRomBackedState(Room *external_room, int current_room_id)
void SetRoomChangedCallback(RoomChangedCallback callback)
std::shared_ptr< DungeonObjectEditor > object_editor_
DungeonEditorSystem(Rom *rom, GameData *game_data=nullptr)
std::shared_ptr< DungeonObjectEditor > GetObjectEditor()
std::unordered_map< int, Room > rooms_
absl::Status LoadDungeon(int dungeon_id)
std::function< void(int room_id)> RoomChangedCallback
absl::StatusOr< Room > GetRoom(int room_id)
uint8_t blockset() const
Definition room.h:903
bool IsLoaded() const
Definition room.h:872
void SetLayoutId(uint8_t id)
Definition room.h:918
uint8_t palette() const
Definition room.h:906
absl::Status SaveObjects(const DungeonStreamLayout *layout=nullptr)
Definition room.cc:2023
absl::Status SaveRoomHeader()
Definition room.cc:2290
absl::Status SaveObjectStreamHeader(const DungeonStreamLayout *layout=nullptr)
Definition room.cc:2119
uint8_t spriteset() const
Definition room.h:905
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:254
absl::Status SaveSprites(const DungeonStreamLayout *layout=nullptr)
Definition room.cc:2217
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:383
void SetSpriteset(uint8_t ss)
Definition room.h:690
void SetBlockset(uint8_t bs)
Definition room.h:682
void SetPalette(uint8_t pal)
Definition room.h:675
uint8_t layout_id() const
Definition room.h:913
const std::vector< PotItem > & GetPotItems() const
Definition room.h:367
int id() const
Definition room.h:900
absl::Status SaveAllChests(Rom *rom, absl::Span< const Room > rooms)
Definition room.cc:3855
nlohmann::json json
absl::Status SaveAllPotItems(Rom *rom, absl::Span< const Room > rooms)
Definition room.cc:3980
absl::Status ApplyRoomLayoutTemplate(Room &room, const std::string &json_str, bool apply_properties)
Apply a previously exported layout template to a room.
absl::Status SaveAllTorches(Rom *rom, absl::Span< const Room > rooms)
Definition room.cc:2911
absl::StatusOr< std::string > ExportRoomLayoutTemplate(const Room &room)
Export a room's layout as a JSON template string.
Room LoadRoomFromRom(Rom *rom, int room_id)
Definition room.cc:521
constexpr int kNumberOfRooms
absl::Status SaveAllCollision(Rom *rom, absl::Span< Room > rooms)
Definition room.cc:3422
std::unique_ptr< DungeonEditorSystem > CreateDungeonEditorSystem(Rom *rom, GameData *game_data)
Factory function to create dungeon editor system.
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::chrono::steady_clock::time_point last_save_time
uint16_t position
Definition room.h:109