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 if (!object_editor_) {
232 return absl::OkStatus();
233 }
234 if (external_room_ != nullptr) {
235 object_editor_->SetExternalRoom(external_room_);
236 return absl::OkStatus();
237 }
238
240 if (room == nullptr) {
243 }
244 if (room == nullptr) {
245 return absl::NotFoundError("Current room is not available");
246 }
247 object_editor_->SetExternalRoom(room);
248 return absl::OkStatus();
249}
250
252 if (external_room_ != nullptr && external_room_->id() == room_id) {
253 return external_room_;
254 }
255 auto it = rooms_.find(room_id);
256 if (it == rooms_.end()) {
257 return nullptr;
258 }
259 return &it->second;
260}
261
263 return SaveSingleRoomState(rom_, room);
264}
265
267 if (!object_editor_) {
268 object_editor_ = std::make_shared<DungeonObjectEditor>(rom_);
269 }
270 RETURN_IF_ERROR(object_editor_->InitializeEditor());
272}
273
274absl::Status DungeonEditorSystem::LoadRoomData(int room_id) {
275 if (!rom_)
276 return absl::InvalidArgumentError("ROM is null");
277
278 if (room_id < 0 || room_id >= kNumberOfRooms) {
279 return absl::InvalidArgumentError("Invalid room ID");
280 }
281
282 if (external_room_ != nullptr && external_room_->id() == room_id) {
285 }
286
287 rooms_[room_id] = LoadRoomFromRom(rom_, room_id);
288 if (editor_state_.current_room_id == room_id) {
290 }
291 return absl::OkStatus();
292}
293
294absl::Status DungeonEditorSystem::SaveRoomData(int room_id) {
295 if (!rom_)
296 return absl::InvalidArgumentError("ROM is null");
297
298 if (room_id < 0 || room_id >= kNumberOfRooms) {
299 return absl::InvalidArgumentError("Invalid room ID");
300 }
301
302 Room* room = GetManagedRoom(room_id);
303 if (room == nullptr) {
305 room = GetManagedRoom(room_id);
306 }
307 if (room == nullptr) {
308 return absl::NotFoundError("Room is not available");
309 }
310 if (!room->IsLoaded()) {
311 return absl::OkStatus();
312 }
313
314 return SaveManagedRoom(*room);
315}
316
317std::unique_ptr<DungeonEditorSystem> CreateDungeonEditorSystem(
318 Rom* rom, GameData* game_data) {
319 return std::make_unique<DungeonEditorSystem>(rom, game_data);
320}
321
322absl::StatusOr<std::string> ExportRoomLayoutTemplate(const Room& room) {
323 using json = nlohmann::json;
324
325 json tmpl;
326 tmpl["version"] = 1;
327 tmpl["room_id"] = room.id();
328
329 // Room properties
330 json props;
331 props["blockset"] = room.blockset();
332 props["palette"] = room.palette();
333 props["layout"] = room.layout_id();
334 props["spriteset"] = room.spriteset();
335 tmpl["properties"] = props;
336
337 // Tile objects
338 json objects_arr = json::array();
339 for (const auto& obj : room.GetTileObjects()) {
340 json obj_json;
341 obj_json["id"] = obj.id_;
342 obj_json["x"] = obj.x_;
343 obj_json["y"] = obj.y_;
344 obj_json["size"] = obj.size_;
345 obj_json["layer"] = obj.GetLayerValue();
346 objects_arr.push_back(obj_json);
347 }
348 tmpl["objects"] = objects_arr;
349
350 // Sprites
351 json sprites_arr = json::array();
352 for (const auto& sprite : room.GetSprites()) {
353 json spr_json;
354 spr_json["id"] = sprite.id();
355 spr_json["x"] = sprite.x();
356 spr_json["y"] = sprite.y();
357 spr_json["subtype"] = sprite.subtype();
358 spr_json["layer"] = sprite.layer();
359 sprites_arr.push_back(spr_json);
360 }
361 tmpl["sprites"] = sprites_arr;
362
363 // Pot items
364 json items_arr = json::array();
365 for (const auto& item : room.GetPotItems()) {
366 json item_json;
367 item_json["position"] = item.position;
368 item_json["item"] = item.item;
369 items_arr.push_back(item_json);
370 }
371 tmpl["pot_items"] = items_arr;
372
373 return tmpl.dump(2);
374}
375
376absl::Status ApplyRoomLayoutTemplate(Room& room, const std::string& json_str,
377 bool apply_properties) {
378 using json = nlohmann::json;
379
380 json tmpl;
381 try {
382 tmpl = json::parse(json_str);
383 } catch (const json::parse_error& err) {
384 return absl::InvalidArgumentError(
385 absl::StrCat("Invalid JSON: ", err.what()));
386 }
387
388 if (!tmpl.contains("version") || tmpl["version"].get<int>() != 1) {
389 return absl::InvalidArgumentError("Unsupported template version");
390 }
391
392 // Apply room properties if requested
393 if (apply_properties && tmpl.contains("properties")) {
394 const auto& props = tmpl["properties"];
395 if (props.contains("blockset"))
396 room.SetBlockset(props["blockset"]);
397 if (props.contains("palette"))
398 room.SetPalette(props["palette"]);
399 if (props.contains("layout"))
400 room.SetLayoutId(props["layout"]);
401 if (props.contains("spriteset"))
402 room.SetSpriteset(props["spriteset"]);
403 }
404
405 // Apply objects
406 if (tmpl.contains("objects")) {
407 auto& objects = room.GetTileObjects();
408 objects.clear();
409 for (const auto& obj_json : tmpl["objects"]) {
410 int16_t obj_id = obj_json.value("id", static_cast<int16_t>(0));
411 uint8_t obj_x = obj_json.value("x", static_cast<uint8_t>(0));
412 uint8_t obj_y = obj_json.value("y", static_cast<uint8_t>(0));
413 uint8_t obj_size = obj_json.value("size", static_cast<uint8_t>(0));
414 uint8_t obj_layer = obj_json.value("layer", static_cast<uint8_t>(0));
415 objects.emplace_back(obj_id, obj_x, obj_y, obj_size, obj_layer);
416 }
417 }
418
419 // Apply sprites
420 if (tmpl.contains("sprites")) {
421 auto& sprites = room.GetSprites();
422 sprites.clear();
423 for (const auto& spr_json : tmpl["sprites"]) {
424 uint8_t spr_id = spr_json.value("id", static_cast<uint8_t>(0));
425 uint8_t spr_x = spr_json.value("x", static_cast<uint8_t>(0));
426 uint8_t spr_y = spr_json.value("y", static_cast<uint8_t>(0));
427 uint8_t spr_sub = spr_json.value("subtype", static_cast<uint8_t>(0));
428 uint8_t spr_layer = spr_json.value("layer", static_cast<uint8_t>(0));
429 sprites.emplace_back(spr_id, spr_x, spr_y, spr_sub, spr_layer);
430 }
431 }
432
433 // Apply pot items
434 if (tmpl.contains("pot_items")) {
435 auto& items = room.GetPotItems();
436 items.clear();
437 for (const auto& item_json : tmpl["pot_items"]) {
438 PotItem item;
439 item.position = item_json.value("position", static_cast<uint16_t>(0));
440 item.item = item_json.value("item", static_cast<uint8_t>(0));
441 items.push_back(item);
442 }
443 }
444
445 return absl::OkStatus();
446}
447
448} // namespace zelda3
449} // 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
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:902
bool IsLoaded() const
Definition room.h:871
void SetLayoutId(uint8_t id)
Definition room.h:917
uint8_t palette() const
Definition room.h:905
absl::Status SaveObjects(const DungeonStreamLayout *layout=nullptr)
Definition room.cc:2020
absl::Status SaveRoomHeader()
Definition room.cc:2281
absl::Status SaveObjectStreamHeader(const DungeonStreamLayout *layout=nullptr)
Definition room.cc:2110
uint8_t spriteset() const
Definition room.h:904
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:253
absl::Status SaveSprites(const DungeonStreamLayout *layout=nullptr)
Definition room.cc:2208
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:382
void SetSpriteset(uint8_t ss)
Definition room.h:689
void SetBlockset(uint8_t bs)
Definition room.h:681
void SetPalette(uint8_t pal)
Definition room.h:674
uint8_t layout_id() const
Definition room.h:912
const std::vector< PotItem > & GetPotItems() const
Definition room.h:366
int id() const
Definition room.h:899
absl::Status SaveAllChests(Rom *rom, absl::Span< const Room > rooms)
Definition room.cc:3681
nlohmann::json json
absl::Status SaveAllPotItems(Rom *rom, absl::Span< const Room > rooms)
Definition room.cc:3806
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:2902
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:518
constexpr int kNumberOfRooms
absl::Status SaveAllCollision(Rom *rom, absl::Span< Room > rooms)
Definition room.cc:3413
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:108