From 2492610ec74d451e343acb95bb1d64391fd848dc Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Wed, 18 Jan 2023 00:39:12 -0500 Subject: [PATCH] Reading and writing saved games now uses the new tagfile system. This should avoid any format inconsistencies, like missing newlines and the like. Although a basic save and load works, there may still be some issues with the format. This probably renders older saved games incompatible. The format is mostly the same, but there are a few small changes in the name of making the format more uniform. --- src/fileio/fileio_party.cpp | 67 ++- src/fileio/tagfile.cpp | 23 +- src/fileio/tagfile.hpp | 39 +- src/scenario/item.cpp | 135 +++--- src/scenario/item.hpp | 6 +- src/scenario/monster.cpp | 314 +++++-------- src/scenario/monster.hpp | 5 +- src/scenario/monster_abilities.hpp | 5 + src/scenario/outdoors.cpp | 76 +-- src/scenario/outdoors.hpp | 7 +- src/scenario/scenario.cpp | 63 ++- src/scenario/scenario.hpp | 6 +- src/scenario/town.cpp | 8 - src/scenario/town.hpp | 2 - src/scenario/vehicle.cpp | 31 +- src/scenario/vehicle.hpp | 6 +- src/universe/creature.cpp | 132 ++---- src/universe/creature.hpp | 6 +- src/universe/party.cpp | 732 ++++++++++++++--------------- src/universe/party.hpp | 10 +- src/universe/pc.cpp | 267 +++++------ src/universe/pc.hpp | 5 +- src/universe/population.cpp | 5 - src/universe/population.hpp | 1 - src/universe/universe.cpp | 124 +++-- src/universe/universe.hpp | 6 +- test/pc_read.cpp | 11 +- 27 files changed, 994 insertions(+), 1098 deletions(-) diff --git a/src/fileio/fileio_party.cpp b/src/fileio/fileio_party.cpp index 6cf46b07..e22fa337 100644 --- a/src/fileio/fileio_party.cpp +++ b/src/fileio/fileio_party.cpp @@ -18,6 +18,7 @@ #include "gfx/gfxsheets.hpp" #include "porting.hpp" +#include "fileio/tagfile.hpp" #include "fileio/tarball.hpp" extern bool mac_is_intel; @@ -279,6 +280,7 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){ zin.close(); cUniverse univ; + cTagFile file; { // Load main party data first std::istream& fin = partyIn.getFile("save/party.txt"); @@ -286,7 +288,8 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){ showError("Loading Blades of Exile save file failed."); return false; } - univ.party.readFrom(fin); + file.readFrom(fin); + univ.party.readFrom(file); } // Next load the PCs @@ -298,7 +301,8 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){ showError("Loading Blades of Exile save file failed."); return false; } - univ.party[i].readFrom(fin); + file.readFrom(fin); + univ.party[i].readFrom(file); } // Including stored PCs @@ -308,7 +312,8 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){ while(fin >> next_uid) { std::string fname = "save/pc~" + std::to_string(next_uid) + ".txt"; cPlayer* stored_pc = new cPlayer(no_party); - stored_pc->readFrom(partyIn.getFile(fname)); + file.readFrom(partyIn.getFile(fname)); + stored_pc->readFrom(file); } } @@ -332,7 +337,8 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){ showError("Loading Blades of Exile save file failed."); return false; } - univ.scenario.readFrom(fin); + file.readFrom(fin); + univ.scenario.readFrom(file); } { // Then the "setup" array @@ -344,11 +350,15 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){ uint16_t magic; fin.read((char*)&magic, 2); fin.read((char*)&univ.party.setup, sizeof(univ.party.setup)); - if(magic == 0x0E0B) // should be 0x0B0E! - for(auto& i : univ.party.setup) - for(auto& j : i) - for(auto& k : j) - flip_short(reinterpret_cast(&k)); + if(magic == 0x0E0B) { // should be 0x0B0E! + for(auto& i : univ.party.setup) { + for(auto& j : i) { + for(auto& k : j) { + flip_short(reinterpret_cast(&k)); + } + } + } + } } if(partyIn.hasFile("save/town.txt")) { @@ -358,15 +368,18 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){ showError("Loading Blades of Exile save file failed."); return false; } - univ.town.readFrom(fin); + file.readFrom(fin); + univ.town.readFrom(file); } else univ.party.town_num = 200; if (partyIn.hasFile("save/townmaps.dat")) { // Read town maps std::istream& fin2 = partyIn.getFile("save/townmaps.dat"); - for(int i = 0; i < univ.scenario.towns.size(); i++) - for(int j = 0; j < univ.scenario.towns[i]->max_dim; j++) + for(int i = 0; i < univ.scenario.towns.size(); i++) { + for(int j = 0; j < univ.scenario.towns[i]->max_dim; j++) { fin2 >> univ.scenario.towns[i]->maps[j]; + } + } } // Load outdoors data std::istream& fin = partyIn.getFile("save/out.txt"); @@ -378,10 +391,13 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){ // Read outdoor maps std::istream& fin2 = partyIn.getFile("save/outmaps.dat"); - for(int i = 0; i < univ.scenario.outdoors.height(); i++) - for(int j = 0; j < 48; j++) - for(int k = 0; k < univ.scenario.outdoors.width(); k++) + for(int i = 0; i < univ.scenario.outdoors.height(); i++) { + for(int j = 0; j < 48; j++) { + for(int k = 0; k < univ.scenario.outdoors.width(); k++) { fin2 >> univ.scenario.outdoors[k][i]->maps[j]; + } + } + } } else univ.party.scen_name = ""; if(partyIn.hasFile("save/export.png")) { @@ -410,15 +426,20 @@ bool save_party(fs::path dest_file, const cUniverse& univ) { dest_file = dest_file.parent_path()/fname; tarball partyOut; + cTagFile file; // First, write the main party data - univ.party.writeTo(partyOut.newFile("save/party.txt")); + univ.party.writeTo(file); + file.writeTo(partyOut.newFile("save/party.txt")); + file.clear(); // Then write the data for each of the party members for(int i = 0; i < 6; i++) { static char fname[] = "save/pc1.txt"; fname[7] = i + '1'; - univ.party[i].writeTo(partyOut.newFile(fname)); + univ.party[i].writeTo(file); + file.writeTo(partyOut.newFile(fname)); + file.clear(); } // And stored PCs @@ -426,13 +447,17 @@ bool save_party(fs::path dest_file, const cUniverse& univ) { std::ostream& fout = partyOut.newFile("save/stored_pcs.txt"); for(const auto& p : univ.stored_pcs) { std::string fname = "save/pc~" + std::to_string(p.first) + ".txt"; - p.second->writeTo(partyOut.newFile(fname)); + p.second->writeTo(file); + file.writeTo(partyOut.newFile(fname)); + file.clear(); fout << p.first << '\n'; } } if(!univ.party.scen_name.empty()) { - univ.scenario.writeTo(partyOut.newFile("save/scenario.txt")); + univ.scenario.writeTo(file); + file.writeTo(partyOut.newFile("save/scenario.txt")); + file.clear(); { std::ostream& fout = partyOut.newFile("save/setup.dat"); @@ -443,7 +468,9 @@ bool save_party(fs::path dest_file, const cUniverse& univ) { if(univ.party.town_num < 200) { // Write the current town data - univ.town.writeTo(partyOut.newFile("save/town.txt")); + univ.town.writeTo(file); + file.writeTo(partyOut.newFile("save/town.txt")); + file.clear(); } { diff --git a/src/fileio/tagfile.cpp b/src/fileio/tagfile.cpp index 04e73da9..1fe44659 100644 --- a/src/fileio/tagfile.cpp +++ b/src/fileio/tagfile.cpp @@ -29,9 +29,12 @@ void cTagFile::writeTo(std::ostream& file) const { } } +void cTagFile::clear() { + pages.clear(); +} cTagFile_Page& cTagFile::add() { - pages.emplace_back(); + pages.emplace_back(pages.size()); return pages.back(); } @@ -43,6 +46,8 @@ const cTagFile_Page& cTagFile::operator[](unsigned long i) const { return pages.at(i); } +cTagFile_Page::cTagFile_Page(size_t i) : i(i) {} + void cTagFile_Page::internal_add_page(const std::string &key) { tag_map.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(std::ref(*this), key)); } @@ -72,6 +77,14 @@ std::string cTagFile_Page::getFirstKey() const { return tag_list.front().get().key; } +size_t cTagFile_Page::index() const { + return i; +} + +void cTagFile_Page::clear() { + tag_list.clear(); + tag_map.clear(); +} cTagFile_Tag& cTagFile_Page::add(const std::string& key) { internal_add_page(key); @@ -86,7 +99,9 @@ cTagFile_TagList& cTagFile_Page::operator[](const std::string& key) { } const cTagFile_TagList& cTagFile_Page::operator[](const std::string& key) const { - return tag_map.at(key); + if(tag_map.count(key) > 0) return tag_map.at(key); + static const cTagFile_TagList empty{}; + return empty; } cTagFile_Tag& cTagFile_TagList::add() { @@ -106,6 +121,10 @@ bool cTagFile_TagList::empty() const { return tags.empty(); } +size_t cTagFile_TagList::size() const { + return tags.size(); +} + void cTagFile_TagList::internal_add_tag() { tags.emplace_back(key); parent->tag_list.push_back(tags.back()); diff --git a/src/fileio/tagfile.hpp b/src/fileio/tagfile.hpp index af7db073..ef622c28 100644 --- a/src/fileio/tagfile.hpp +++ b/src/fileio/tagfile.hpp @@ -221,7 +221,7 @@ class cTagFile_TagExtractProxy { public: cTagFile_TagExtractProxy() : tag(nullptr) {} cTagFile_TagExtractProxy(const cTagFile_Tag& tag) : tag(&tag) {} - operator bool() const { + explicit operator bool() const { return tag; } template @@ -231,6 +231,17 @@ public: j += tag->extract(i, value); return cTagFile_TagExtractProxy(*tag, j); } + template + cTagFile_TagExtractProxy operator>>(T&& value) { + if(tag == nullptr) return *this; + size_t j = i; + T check = value; + j += tag->extract(i, value); + if(check != value) { + return cTagFile_TagExtractProxy(); + } + return cTagFile_TagExtractProxy(*tag, j); + } }; class cTagFile_TagEncodeProxy { @@ -249,11 +260,13 @@ public: }; class cTagFile_TagList { - cTagFile_Page* parent; + friend class cTagFile_Page; + cTagFile_Page* parent = nullptr; std::deque tags; mutable size_t i = 0; cTagFile_TagList(const cTagFile_TagList&) = delete; void internal_add_tag(); + cTagFile_TagList() = default; public: const std::string key; cTagFile_TagList(cTagFile_Page& parent, const std::string& key); @@ -261,24 +274,26 @@ public: cTagFile_Tag& operator[](size_t n); const cTagFile_Tag& operator[](size_t n) const; bool empty() const; + size_t size() const; template cTagFile_TagEncodeProxy operator<<(const T& value) { internal_add_tag(); return cTagFile_TagEncodeProxy(tags.back()) << value; } template - cTagFile_TagExtractProxy operator>>(T& value) const { + cTagFile_TagExtractProxy operator>>(T&& value) const { if(i >= tags.size()) { i = 0; return cTagFile_TagExtractProxy() >> value; } else { - return cTagFile_TagExtractProxy(tags[i++]) >> value; + return cTagFile_TagExtractProxy(tags[i++]) >> std::forward(value); } } template void extract(Container& values) const { using T = typename Container::value_type; values.clear(); + i = 0; for(size_t n = 0; n < tags.size(); n++) { T value; *this >> value; @@ -296,6 +311,7 @@ public: extractSparse(Container& values, typename Container::value_type def = typename Container::value_type()) const { using T = typename Container::value_type; values.clear(); + i = 0; for(size_t n = 0; n < tags.size(); n++) { size_t i = 0; T value; @@ -323,6 +339,7 @@ public: typename Container::value_type::second_type >; values.clear(); + i = 0; for(size_t n = 0; n < tags.size(); n++) { T value; *this >> value.first >> value.second; @@ -341,6 +358,7 @@ public: template void extract(vector2d& values) const { values.resize(0, tags.size()); + i = 0; for(size_t n = 0; n < tags.size(); n++) { std::vector row; tags[n].extract(0, row); @@ -365,6 +383,7 @@ public: template void extractSparse(vector2d& values, T def = T()) const { values.resize(0, 0); + i = 0; for(size_t n = 0; n < tags.size(); n++) { size_t x = 0, y = 0; T value; @@ -396,14 +415,16 @@ class cTagFile_Page { std::deque> tag_list; cTagFile_Page(const cTagFile_Page&) = delete; void internal_add_page(const std::string& key); -protected: - std::string getFirstKey() const; + const size_t i; public: - cTagFile_Page() = default; + std::string getFirstKey() const; + cTagFile_Page(size_t index); void readFrom(std::istream& file); void writeTo(std::ostream& file) const; + void clear(); cTagFile_Tag& add(const std::string& key); bool contains(const std::string& key) const; + size_t index() const; cTagFile_TagList& operator[](const std::string& key); const cTagFile_TagList& operator[](const std::string& key) const; }; @@ -415,6 +436,7 @@ class cTagFile { public: void readFrom(std::istream& file); void writeTo(std::ostream& file) const; + void clear(); cTagFile_Page& add(); cTagFile_Page& operator[](size_t n); const cTagFile_Page& operator[](size_t n) const; @@ -428,7 +450,8 @@ public: template::value>::type> struct as_hex { Int value; - as_hex(Int value) : value(value) {} + as_hex(Int value = Int()) : value(value) {} + as_hex& operator= (Int newval) { value = newval; return *this; } operator Int() { return value; } }; diff --git a/src/scenario/item.cpp b/src/scenario/item.cpp index b6451290..726600da 100644 --- a/src/scenario/item.cpp +++ b/src/scenario/item.cpp @@ -18,6 +18,7 @@ #include "oldstructs.hpp" #include "utility.hpp" #include "fileio/fileio.hpp" +#include "fileio/tagfile.hpp" #include "damage.hpp" #include "spell.hpp" @@ -1290,78 +1291,72 @@ std::string cItem::getAbilName() const { return sout.str(); } -void cItem::writeTo(std::ostream& file, std::string prefix) const { - file << prefix << "VARIETY " << variety << '\n'; - file << prefix << "LEVEL " << item_level << '\n'; - file << prefix << "AWKWARD " << awkward << '\n'; - file << prefix << "BONUS " << bonus << '\n'; - file << prefix << "PROT " << protection << '\n'; - file << prefix << "CHARGES " << charges << '\n'; - file << prefix << "WEAPON " << weap_type << '\n'; - file << prefix << "USE " << magic_use_type << '\n'; - file << prefix << "ICON " << graphic_num << '\n'; - file << prefix << "ABILITY " << ability << '\n'; - file << prefix << "ABILSTR " << abil_strength << '\t' << abil_data.value << '\n'; - file << prefix << "TYPE " << type_flag << '\n'; - file << prefix << "ISSPEC " << is_special << '\n'; - file << prefix << "VALUE " << value << '\n'; - file << prefix << "WEIGHT " << weight << '\n'; - file << prefix << "SPEC " << special_class << '\n'; - file << prefix << "MISSILE " << missile << '\n'; - file << prefix << "AT " << item_loc.x << ' ' << item_loc.y << '\n'; - file << prefix << "FULLNAME " << maybe_quote_string(full_name) << '\n'; - file << prefix << "NAME " << maybe_quote_string(name) << '\n'; - file << prefix << "DESCR " << maybe_quote_string(desc) << '\n'; - file << prefix << "TREASURE " << treas_class << '\n'; - if(ident) file << prefix << "IDENTIFIED\n"; - if(property) file << prefix << "PROPERTY\n"; - if(magic) file << prefix << "MAGIC\n"; - if(contained) file << prefix << "CONTAINED\n"; - if(held) file << prefix << "HELD\n"; - if(cursed) file << prefix << "CURSED\n"; - if(concealed) file << prefix << "CONCEALED\n"; - if(enchanted) file << prefix << "ENCHANTED\n"; - if(unsellable) file << prefix << "UNSELLABLE\n"; +void cItem::writeTo(cTagFile_Page& page) const { + page["VARIETY"] << variety; + page["LEVEL"] << item_level; + page["AWKWARD"] << awkward; + page["BONUS"] << bonus; + page["PROT"] << protection; + page["CHARGES"] << charges; + page["WEAPON"] << weap_type; + page["USE"] << magic_use_type; + page["ICON"] << graphic_num; + page["ABILITY"] << ability; + page["ABILSTR"] << abil_strength << abil_data.value; + page["TYPE"] << type_flag; + page["ISSPEC"] << is_special; + page["VALUE"] << value; + page["WEIGHT"] << weight; + page["SPEC"] << special_class; + page["MISSILE"] << missile; + page["AT"] << item_loc.x << item_loc.y; + page["FULLNAME"] << full_name; + page["NAME"] << name; + page["DESCR"] << desc; + page["TREASURE"] << treas_class; + if(ident) page.add("IDENTIFIED"); + if(property) page.add("PROPERTY"); + if(magic) page.add("MAGIC"); + if(contained) page.add("CONTAINED"); + if(held) page.add("HELD"); + if(cursed) page.add("CURSED"); + if(concealed) page.add("CONCEALED"); + if(enchanted) page.add("ENCHANTED"); + if(unsellable) page.add("UNSELLABLE"); } -void cItem::readFrom(std::istream& sin){ - while(sin) { - std::string cur; - getline(sin, cur); - std::istringstream sin(cur); - sin >> cur; - if(cur == "VARIETY") sin >> variety; - else if(cur == "LEVEL") sin >> item_level; - else if(cur == "AWKWARD") sin >> awkward; - else if(cur == "BONUS") sin >> bonus; - else if(cur == "PROT") sin >> protection; - else if(cur == "CHARGES") sin >> charges; - else if(cur == "WEAPON") sin >> weap_type; - else if(cur == "USE") sin >> magic_use_type; - else if(cur == "ICON") sin >> graphic_num; - else if(cur == "ABILITY") sin >> ability; - else if(cur == "ABILSTR") sin >> abil_strength >> abil_data.value; - else if(cur == "TYPE") sin >> type_flag; - else if(cur == "ISSPEC") sin >> is_special; - else if(cur == "VALUE") sin >> value; - else if(cur == "WEIGHT") sin >> weight; - else if(cur == "SPEC") sin >> special_class; - else if(cur == "MISSILE") sin >> missile; - else if(cur == "AT") sin >> item_loc.x >> item_loc.y; - else if(cur == "FULLNAME") full_name = read_maybe_quoted_string(sin); - else if(cur == "NAME") name = read_maybe_quoted_string(sin); - else if(cur == "DESCR") desc = read_maybe_quoted_string(sin); - else if(cur == "TREASURE") sin >> treas_class; - else if(cur == "IDENTIFIED") ident = true; - else if(cur == "PROPERTY") property = true; - else if(cur == "MAGIC") magic = true; - else if(cur == "CONTAINED") contained = true; - else if(cur == "HELD") held = true; - else if(cur == "CURSED") cursed = true; - else if(cur == "CONCEALED") concealed = true; - else if(cur == "ENCHANTED") enchanted = true; - else if(cur == "UNSELLABLE") unsellable = true; - } +void cItem::readFrom(const cTagFile_Page& page){ + page["VARIETY"] >> variety; + page["LEVEL"] >> item_level; + page["AWKWARD"] >> awkward; + page["BONUS"] >> bonus; + page["PROT"] >> protection; + page["CHARGES"] >> charges; + page["WEAPON"] >> weap_type; + page["USE"] >> magic_use_type; + page["ICON"] >> graphic_num; + page["ABILITY"] >> ability; + page["ABILSTR"] >> abil_strength >> abil_data.value; + page["TYPE"] >> type_flag; + page["ISSPEC"] >> is_special; + page["VALUE"] >> value; + page["WEIGHT"] >> weight; + page["SPEC"] >> special_class; + page["MISSILE"] >> missile; + page["AT"] >> item_loc.x >> item_loc.y; + page["FULLNAME"] >> full_name; + page["NAME"] >> name; + page["DESCR"] >> desc; + page["TREASURE"] >> treas_class; + if(page.contains("IDENTIFIED")) ident = true; + else if(page.contains("PROPERTY")) property = true; + else if(page.contains("MAGIC")) magic = true; + else if(page.contains("CONTAINED")) contained = true; + else if(page.contains("HELD")) held = true; + else if(page.contains("CURSED")) cursed = true; + else if(page.contains("CONCEALED")) concealed = true; + else if(page.contains("ENCHANTED")) enchanted = true; + else if(page.contains("UNSELLABLE")) unsellable = true; } enum {USE_COMBAT = 1, USE_TOWN = 2, USE_OUTDOORS = 4, USE_MAGIC = 8}; diff --git a/src/scenario/item.hpp b/src/scenario/item.hpp index fc1c566c..da56333f 100644 --- a/src/scenario/item.hpp +++ b/src/scenario/item.hpp @@ -23,6 +23,8 @@ namespace legacy { struct item_record_type; }; +class cTagFile_Page; + enum eItemPreset { ITEM_KNIFE, ITEM_BUCKLER, @@ -91,8 +93,8 @@ public: explicit cItem(eItemPreset preset); explicit cItem(eAlchemy recipe); void import_legacy(legacy::item_record_type& old); - void writeTo(std::ostream& file, std::string prefix = "") const; - void readFrom(std::istream& sin); + void writeTo(cTagFile_Page& file) const; + void readFrom(const cTagFile_Page& sin); }; class cSpecItem { diff --git a/src/scenario/monster.cpp b/src/scenario/monster.cpp index d4508875..b1332955 100644 --- a/src/scenario/monster.cpp +++ b/src/scenario/monster.cpp @@ -18,6 +18,7 @@ #include "oldstructs.hpp" #include "fileio/fileio.hpp" +#include "fileio/tagfile.hpp" #include "spell.hpp" #include "gfx/gfxsheets.hpp" // for NO_PIC @@ -775,197 +776,140 @@ int uAbility::get_ap_cost(eMonstAbil key) const { return -256; } -void cMonster::writeTo(std::ostream& file) const { - file << "MONSTER " << maybe_quote_string(m_name) << '\n'; - file << "LEVEL " << int(level) << '\n'; - file << "ARMOR " << int(armor) << '\n'; - file << "SKILL " << int(skill) << '\n'; - for(int i = 0; i < a.size(); i++) - file << "ATTACK " << i + 1 << ' ' << a[i].dice << ' ' << a[i].sides << ' ' << a[i].type << '\n'; - file << "HEALTH " << int(m_health) << '\n'; - file << "SPEED " << int(speed) << '\n'; - file << "MAGE " << int(mu) << '\n'; - file << "PRIEST " << int(cl) << '\n'; - file << "RACE " << m_type << '\n'; - file << "TREASURE" << int(treasure) << '\n'; - file << "CORPSEITEM " << corpse_item << ' ' << corpse_item_chance << '\n'; - for(int i = 0; i < 8; i++) { - eDamageType dmg = eDamageType(i); - if(resist.at(dmg) != 100) - file << "IMMUNE " << dmg << '\t' << resist.at(dmg) << '\n'; - } - file << "SIZE " << int(x_width) << ' ' << int(y_width) << '\n'; - file << "ATTITUDE " << int(default_attitude) << '\n'; - file << "SUMMON " << int(summon_type) << '\n'; - file << "PORTRAIT " << default_facial_pic << '\n'; - file << "PICTURE " << picture_num << '\n'; - file << "SOUND " << ambient_sound << '\n'; - file << '\f'; - for(auto& abil_element : abil) { - if(!abil_element.second.active || abil_element.first == eMonstAbil::NO_ABIL) continue; - file << "ABIL " << abil_element.first << '\n'; - switch(getMonstAbilCategory(abil_element.first)) { - case eMonstAbilCat::INVALID: continue; - case eMonstAbilCat::MISSILE: - file << "TYPE " << abil_element.second.missile.type << ' ' << abil_element.second.missile.pic << '\n'; - file << "DAMAGE " << abil_element.second.missile.dice << ' ' << abil_element.second.missile.sides << '\n'; - file << "SKILL " << abil_element.second.missile.skill << '\n'; - file << "RANGE " << abil_element.second.missile.range << '\n'; - file << "CHANCE " << abil_element.second.missile.odds << '\n'; - break; - case eMonstAbilCat::GENERAL: - file << "TYPE " << abil_element.second.gen.type << ' ' << abil_element.second.gen.pic << '\n'; - file << "DAMAGE " << abil_element.second.gen.strength << '\n'; - file << "RANGE " << abil_element.second.gen.range << '\n'; - file << "CHANCE " << abil_element.second.gen.odds << '\n'; - if(abil_element.first == eMonstAbil::DAMAGE || abil_element.first == eMonstAbil::DAMAGE2) - file << "EXTRA " << abil_element.second.gen.dmg << '\n'; - else if(abil_element.first == eMonstAbil::FIELD) - file << "EXTRA " << abil_element.second.gen.fld << '\n'; - else if(abil_element.first == eMonstAbil::STATUS || abil_element.first == eMonstAbil::STATUS2 || abil_element.first == eMonstAbil::STUN) - file << "EXTRA " << abil_element.second.gen.stat; - break; - case eMonstAbilCat::RADIATE: - file << "TYPE " << abil_element.second.radiate.type << '\n'; - file << "CHANCE " << abil_element.second.radiate.chance << '\n'; - break; - case eMonstAbilCat::SUMMON: - file << "TYPE " << abil_element.second.summon.type << '\t' << abil_element.second.summon.what << '\n'; - file << "HOWMANY " << abil_element.second.summon.min << ' ' << abil_element.second.summon.max << '\n'; - file << "CHANCE " << abil_element.second.summon.chance << '\n'; - break; - case eMonstAbilCat::SPECIAL: - file << "EXTRA " << abil_element.second.special.extra1 << ' ' << abil_element.second.special.extra2 << '\n'; - break; - } - file << '\f'; +void cMonster::writeTo(cTagFile_Page& page) const { + page["MONSTER"] << m_name; + page["LEVEL"] << level; + page["ARMOR"] << armor; + page["SKILL"] << skill; + for(int i = 0; i < a.size(); i++) { + page["ATTACK"] << i + 1 << a[i].dice << a[i].sides << a[i].type; } + page["HEALTH"] << m_health; + page["SPEED"] << speed; + page["MAGE"] << mu; + page["PRIEST"] << cl; + page["RACE"] << m_type; + page["TREASURE"] << treasure; + page["CORPSEITEM"] << corpse_item << corpse_item_chance; + page["IMMUNE"].encodeSparse(resist, 100); + page["SIZE"] << x_width << y_width; + page["ATTITUDE"] << default_attitude; + page["SUMMON"] << summon_type; + page["PORTRAIT"] << default_facial_pic; + page["PICTURE"] << picture_num; + page["SOUND"] << ambient_sound; } -void cMonster::readFrom(std::istream& file) { +bool uAbility::writeTo(eMonstAbil key, cTagFile_Page &page) const { + if(key == eMonstAbil::NO_ABIL || !active) return false; + page["ABIL"] << key; + switch(getMonstAbilCategory(key)) { + case eMonstAbilCat::INVALID: return false; + case eMonstAbilCat::MISSILE: + page["TYPE"] << missile.type << missile.pic; + page["DAMAGE"] << missile.dice << missile.sides; + page["SKILL"] << missile.skill; + page["RANGE"] << missile.range; + page["CHANCE"] << missile.odds; + break; + case eMonstAbilCat::GENERAL: + page["TYPE"] << gen.type << gen.pic; + page["DAMAGE"] << gen.strength; + page["RANGE"] << gen.range; + page["CHANCE"] << gen.odds; + if(key == eMonstAbil::DAMAGE || key == eMonstAbil::DAMAGE2) + page["EXTRA"] << gen.dmg; + else if(key == eMonstAbil::FIELD) + page["EXTRA"] << gen.fld; + else if(key == eMonstAbil::STATUS || key == eMonstAbil::STATUS2 || key == eMonstAbil::STUN) + page["EXTRA"] << gen.stat; + break; + case eMonstAbilCat::RADIATE: + page["TYPE"] << radiate.type; + page["CHANCE"] << radiate.chance; + break; + case eMonstAbilCat::SUMMON: + page["TYPE"] << summon.type << summon.what; + page["HOWMANY"] << summon.min << summon.max; + page["CHANCE"] << summon.chance; + break; + case eMonstAbilCat::SPECIAL: + page["EXTRA"] << special.extra1 << special.extra2; + break; + } + return true; +} + +void cMonster::readFrom(const cTagFile_Page& page) { // On-see event is not exported, so make sure the field ise not filled with garbage data see_spec = -1; - std::istringstream bin; - std::string cur; - getline(file, cur, '\f'); - bin.str(cur); - while(bin) { - int temp1, temp2; - getline(bin, cur); - std::istringstream line(cur); - line >> cur; - if(cur == "MONSTER") { - line >> std::ws; - m_name = read_maybe_quoted_string(line); - } else if(cur == "ATTACK") { - int which; - line >> which; - which--; - if(which < 0 || which >= a.size()) continue; - line >> a[which].dice >> a[which].sides >> a[which].type; - } else if(cur == "SIZE") { - line >> temp1 >> temp2; - x_width = temp1; - y_width = temp2; - } else if(cur == "IMMUNE") { - eDamageType dmg; - line >> dmg >> resist[dmg]; - } else if(cur == "RACE") - line >> m_type; - else if(cur == "CORPSEITEM") - line >> corpse_item >> corpse_item_chance; - else if(cur == "PORTRAIT") - line >> default_facial_pic; - else if(cur == "PICTURE") - line >> picture_num; - else if(cur == "SOUND") - line >> ambient_sound; - else if(cur == "ATTITUDE") - line >> default_attitude; - else { - line >> temp1; - if(cur == "LEVEL") - level = temp1; - else if (cur == "ARMOR") - armor = temp1; - else if(cur == "SKILL") - skill = temp1; - else if(cur == "HEALTH") - m_health = temp1; - else if(cur == "SPEED") - speed = temp1; - else if(cur == "MAGE") - mu = temp1; - else if(cur == "PRIEST") - cl = temp1; - else if(cur == "TREASURE") - treasure = temp1; - else if(cur == "SUMMON") - summon_type = temp1; - } - } - while(file) { - getline(file, cur, '\f'); - bin.str(cur); - getline(bin, cur); - std::istringstream line(cur); - line >> cur; - if(cur == "ABIL") { - eMonstAbil key; - uAbility abil; - line >> key; - eMonstAbilCat cat = getMonstAbilCategory(key); - if(cat == eMonstAbilCat::INVALID) continue; - while(bin) { - getline(bin, cur); - line.str(cur); - line >> cur; - if(cur == "TYPE") { - if(cat == eMonstAbilCat::MISSILE) - line >> abil.missile.type >> abil.missile.pic; - else if(cat == eMonstAbilCat::GENERAL) - line >> abil.gen.type >> abil.gen.pic; - else if(cat == eMonstAbilCat::RADIATE) - line >> abil.radiate.type; - else if(cat == eMonstAbilCat::SUMMON) - line >> abil.summon.type >> abil.summon.what; - } else if(cur == "DAMAGE") { - if(cat == eMonstAbilCat::MISSILE) - line >> abil.missile.dice >> abil.missile.sides; - else if(cat == eMonstAbilCat::GENERAL) - line >> abil.gen.strength; - } else if(cur == "SKILL") { - if(cat == eMonstAbilCat::MISSILE) - line >> abil.missile.skill; - } else if(cur == "RANGE") { - if(cat == eMonstAbilCat::MISSILE) - line >> abil.missile.range; - else if(cat == eMonstAbilCat::GENERAL) - line >> abil.gen.range; - } else if(cur == "CHANCE") { - if(cat == eMonstAbilCat::MISSILE) - line >> abil.missile.odds; - else if(cat == eMonstAbilCat::GENERAL) - line >> abil.gen.odds; - else if(cat == eMonstAbilCat::RADIATE) - line >> abil.radiate.chance; - else if(cat == eMonstAbilCat::SUMMON) - line >> abil.summon.chance; - } else if(cur == "EXTRA") { - if(key == eMonstAbil::DAMAGE || key == eMonstAbil::DAMAGE2) - line >> abil.gen.dmg; - else if(key == eMonstAbil::FIELD) - line >> abil.gen.fld; - else if(key == eMonstAbil::STATUS || key == eMonstAbil::STATUS2 || key == eMonstAbil::STUN) - line >> abil.gen.stat; - else if(cat == eMonstAbilCat::SPECIAL) - line >> abil.special.extra1 >> abil.special.extra2; - } else if(cur == "HOWMANY") { - if(cat == eMonstAbilCat::SUMMON) - line >> abil.summon.min >> abil.summon.max; - } - } + page["MONSTER"] >> m_name; + page["SIZE"] >> x_width >> y_width; + // TODO: Isn't the default immunity supposed to be 100? Is that handled? + page["IMMUNE"].extractSparse(resist); + page["RACE"] >> m_type; + page["CORPSEITEM"] >> corpse_item >> corpse_item_chance; + page["PORTRAIT"] >> default_facial_pic; + page["PICTURE"] >> picture_num; + page["SOUND"] >> ambient_sound; + page["ATTITUDE"] >> default_attitude; + page["LEVEL"] >> level; + page["ARMOR"] >> armor; + page["SKILL"] >> skill; + page["HEALTH"] >> m_health; + page["SPEED"] >> speed; + page["MAGE"] >> mu; + page["PRIEST"] >> cl; + page["TREASURE"] >> treasure; + page["SUMMON"] >> summon_type; + for(int i = 0; i < page["ATTACK"].size(); i++) { + size_t which_atk; + auto tmp = page["ATTACK"] >> which_atk; + if(which_atk > 0 && which_atk <= a.size()) { + which_atk--; + tmp >> a[which_atk].dice >> a[which_atk].sides >> a[which_atk].type; } } } +eMonstAbil uAbility::readFrom(const cTagFile_Page& page) { + eMonstAbil key; + page["ABIL"] >> key; + eMonstAbilCat cat = getMonstAbilCategory(key); + switch(cat) { + case eMonstAbilCat::INVALID: return eMonstAbil::NO_ABIL; + case eMonstAbilCat::MISSILE: + page["TYPE"] >> missile.type >> missile.pic; + page["DAMAGE"] >> missile.dice >> missile.sides; + page["SKILL"] >> missile.skill; + page["RANGE"] >> missile.range; + page["CHANCE"] >> missile.odds; + break; + case eMonstAbilCat::GENERAL: + page["TYPE"] >> gen.type >> gen.pic; + page["DAMAGE"] >> gen.strength; + page["RANGE"] >> gen.range; + page["CHANCE"] >> gen.odds; + if(key == eMonstAbil::DAMAGE || key == eMonstAbil::DAMAGE2) + page["EXTRA"] >> gen.dmg; + else if(key == eMonstAbil::FIELD) + page["EXTRA"] >> gen.fld; + else if(key == eMonstAbil::STATUS || key == eMonstAbil::STATUS2 || key == eMonstAbil::STUN) + page["EXTRA"] >> gen.stat; + break; + case eMonstAbilCat::RADIATE: + page["TYPE"] >> radiate.type; + page["CHANCE"] >> radiate.chance; + break; + case eMonstAbilCat::SUMMON: + page["TYPE"] >> summon.type >> summon.what; + page["HOWMANY"] >> summon.min >> summon.max; + page["CHANCE"] >> summon.chance; + break; + case eMonstAbilCat::SPECIAL: + page["EXTRA"] >> special.extra1 >> special.extra2; + break; + } + return key; +} + diff --git a/src/scenario/monster.hpp b/src/scenario/monster.hpp index 431f7bb0..102adea9 100644 --- a/src/scenario/monster.hpp +++ b/src/scenario/monster.hpp @@ -26,6 +26,7 @@ namespace legacy { class cScenario; class cUniverse; +class cTagFile_Page; enum class eAttitude { DOCILE, HOSTILE_A, FRIENDLY, HOSTILE_B @@ -75,8 +76,8 @@ public: void import_legacy(legacy::monster_record_type& old); cMonster(); - void writeTo(std::ostream& file) const; - void readFrom(std::istream& file); + void writeTo(cTagFile_Page& page) const; + void readFrom(const cTagFile_Page& page); }; class cTownperson { diff --git a/src/scenario/monster_abilities.hpp b/src/scenario/monster_abilities.hpp index bd2fbf9d..9ca06e3f 100644 --- a/src/scenario/monster_abilities.hpp +++ b/src/scenario/monster_abilities.hpp @@ -13,6 +13,8 @@ #include "fields.hpp" #include "spell.hpp" +class cTagFile_Page; + enum class eMonstAbil { NO_ABIL, MISSILE, @@ -113,8 +115,11 @@ union uAbility { } special; std::string to_string(eMonstAbil myKey) const; int get_ap_cost(eMonstAbil key) const; + bool writeTo(eMonstAbil key, cTagFile_Page& page) const; + eMonstAbil readFrom(const cTagFile_Page& page); }; + std::ostream& operator << (std::ostream& out, eMonstAbil e); std::istream& operator >> (std::istream& in, eMonstAbil& e); std::ostream& operator << (std::ostream& out, eMonstMissile e); diff --git a/src/scenario/outdoors.cpp b/src/scenario/outdoors.cpp index 9af5a126..999f55b5 100644 --- a/src/scenario/outdoors.cpp +++ b/src/scenario/outdoors.cpp @@ -14,6 +14,7 @@ #include #include "dialogxml/dialogs/strdlog.hpp" +#include "fileio/tagfile.hpp" #include "oldstructs.hpp" #include "scenario/scenario.hpp" @@ -150,43 +151,48 @@ void cOutdoors::cCreature::import_legacy(legacy::outdoor_creature_type old){ m_loc.y = old.m_loc.y; } -void cOutdoors::cWandering::writeTo(std::ostream& file, std::string prefix) const { - for(int i = 0; i < 7; i++) - file << prefix << "HOSTILE " << i << ' ' << monst[i] << '\n'; - for(int i = 0; i < 3; i++) - file << prefix << "FRIEND " << i << ' ' << friendly[i] << '\n'; - file << prefix << "MEET " << spec_on_meet << '\n'; - file << prefix << "WIN " << spec_on_win << '\n'; - file << prefix << "FLEE " << spec_on_flee << '\n'; - file << prefix << "FLAGS " << cant_flee << ' ' << forced << '\n'; - file << prefix << "SDF " << end_spec1 << ' ' << end_spec2 << '\n'; +void cOutdoors::cCreature::writeTo(cTagFile_Page& page) const { + page["DIRECTION"] << direction; + page["SECTOR"] << which_sector.x << which_sector.y; + page["LOCINSECTOR"] << m_loc.x << m_loc.y; + page["HOME"] << home_sector.x << home_sector.y; + page.add("-"); // Not strictly needed, but adds a nice dividing line + what_monst.writeTo(page); } -void cOutdoors::cWandering::readFrom(std::istream& file){ - while(file) { - std::string cur; - getline(file, cur); - std::istringstream sin(cur); - sin >> cur; - if(cur == "HOSTILE"){ - int i; - sin >> i; - sin >> monst[i]; - }else if(cur == "FRIEND"){ - int i; - sin >> i; - sin >> friendly[i]; - }else if(cur == "MEET") - sin >> spec_on_meet; - else if(cur == "WIN") - sin >> spec_on_win; - else if(cur == "FLEE") - sin >> spec_on_flee; - else if(cur == "FLAGS") - sin >> cant_flee >> forced; - else if(cur == "SDF") - sin >> end_spec1 >> end_spec2; - } +void cOutdoors::cCreature::readFrom(const cTagFile_Page& page) { + page["DIRECTION"] >> direction; + page["SECTOR"] >> which_sector.x >> which_sector.y; + page["LOCINSECTOR"] >> m_loc.x >> m_loc.y; + page["HOME"] >> home_sector.x >> home_sector.y; + what_monst.readFrom(page); +} + +void cOutdoors::cWandering::writeTo(cTagFile_Page& page) const { + page["MEET"] << spec_on_meet; + page["WIN"] << spec_on_win; + page["FLEE"] << spec_on_flee; + page["FLAGS"] << cant_flee << forced; + page["SDF"] << end_spec1 << end_spec2; + for(int i = 0; i < 7; i++) + page["HOSTILE"] << i << monst[i]; + for(int i = 0; i < 3; i++) + page["FRIEND"] << i << friendly[i]; +} + +void cOutdoors::cWandering::readFrom(const cTagFile_Page& page){ + page["MEET"] >> spec_on_meet; + page["WIN"] >> spec_on_win; + page["FLEE"] >> spec_on_flee; + page["FLAGS"] >> cant_flee >> forced; + page["SDF"] >> end_spec1 >> end_spec2; + std::vector temp; + page["HOSTILE"].extractSparse(temp); + temp.resize(monst.size()); + std::copy_n(temp.begin(), monst.size(), monst.begin()); + page["FRIEND"].extractSparse(temp); + temp.resize(friendly.size()); + std::copy_n(temp.begin(), friendly.size(), friendly.begin()); } bool cOutdoors::cWandering::isNull(){ diff --git a/src/scenario/outdoors.hpp b/src/scenario/outdoors.hpp index cfa79d4d..897aefe6 100644 --- a/src/scenario/outdoors.hpp +++ b/src/scenario/outdoors.hpp @@ -25,6 +25,7 @@ namespace legacy { }; class cScenario; +class cTagFile_Page; enum eAmbientSound { AMBIENT_NONE, @@ -46,8 +47,8 @@ public: bool isNull(); void import_legacy(legacy::out_wandering_type old); - void writeTo(std::ostream& file, std::string prefix = "") const; - void readFrom(std::istream& sin); + void writeTo(cTagFile_Page& page) const; + void readFrom(const cTagFile_Page& page); cWandering(); }; class cCreature { // formerly outdoor_creature_type @@ -58,6 +59,8 @@ public: location which_sector,m_loc,home_sector; // home_sector is the sector it was spawned in void import_legacy(legacy::outdoor_creature_type old); + void writeTo(cTagFile_Page& page) const; + void readFrom(const cTagFile_Page& page); }; short x,y; // Used while loading legacy scenarios. std::vector city_locs; diff --git a/src/scenario/scenario.cpp b/src/scenario/scenario.cpp index b821bd2c..edfc79df 100644 --- a/src/scenario/scenario.cpp +++ b/src/scenario/scenario.cpp @@ -15,6 +15,7 @@ #include "oldstructs.hpp" #include "mathutil.hpp" +#include "fileio/tagfile.hpp" void cScenario::reset_version() { format.prog_make_ver[0] = 2; @@ -538,49 +539,39 @@ bool cScenario::is_town_entrance_valid(spec_loc_t loc) const { return loc.spec >= 0 && loc.spec < towns_in_scenario; } -void cScenario::writeTo(std::ostream& file) const { +void cScenario::writeTo(cTagFile& file) const { + auto& page = file.add(); for(int i = 0; i < towns.size(); i++) { if(towns[i]->item_taken.any()) - file << "ITEMTAKEN " << i << ' ' << towns[i]->item_taken << '\n'; + page["ITEMTAKEN"] << i << towns[i]->item_taken; if(towns[i]->can_find) - file << "TOWNVISIBLE " << i << '\n'; - else file << "TOWNHIDDEN " << i << '\n'; + page["TOWNVISIBLE"] << i; + else page["TOWNHIDDEN"] << i; if(towns[i]->m_killed > 0) - file << "TOWNSLAUGHTER " << i << ' ' << towns[i]->m_killed << '\n'; + page["TOWNSLAUGHTER"] << i << towns[i]->m_killed; } } -void cScenario::readFrom(std::istream& file){ - // TODO: Error-check input - // TODO: Don't call this sin, it's a trig function - std::istringstream bin; - std::string cur; - getline(file, cur, '\f'); - bin.str(cur); - while(bin) { // continue as long as no error, such as eof, occurs - getline(bin, cur); - std::istringstream sin(cur); - sin >> cur; - if(cur == "ITEMTAKEN"){ - int i; - sin >> i; - if(i >= 0 && i < towns.size()) - sin >> towns[i]->item_taken; - } else if(cur == "TOWNVISIBLE") { - int i; - sin >> i; - if(i >= 0 && i < towns.size()) - towns[i]->can_find = true; - } else if(cur == "TOWNHIDDEN") { - int i; - sin >> i; - if(i >= 0 && i < towns.size()) - towns[i]->can_find = false; - } else if(cur == "TOWNSLAUGHTER"){ - int i; - sin >> i; - if(i >= 0 && i < towns.size()) - sin >> towns[i]->m_killed; +void cScenario::readFrom(const cTagFile& file){ + std::deque> taken; + std::vector visible, hidden, slaughter; + auto& page = file[0]; + page["ITEMTAKEN"].extract(taken); + page["TOWNVISIBLE"].extract(visible); + page["TOWNHIDDEN"].extract(hidden); + page["TOWNSLAUGHTER"].extractSparse(slaughter); + std::sort(visible.begin(), visible.end()); + std::sort(hidden.begin(), hidden.end()); + for(size_t i = 0; i < towns.size(); i++) { + if(i < taken.size()) towns[i]->item_taken = taken[i]; + else towns[i]->item_taken.clear(); + if(i < slaughter.size()) towns[i]->m_killed = slaughter[i]; + else towns[i]->m_killed = 0; + if(std::binary_search(visible.begin(), visible.end(), i)) { + towns[i]->can_find = true; + } + if(std::binary_search(hidden.begin(), hidden.end(), i)) { + towns[i]->can_find = false; } } } diff --git a/src/scenario/scenario.hpp b/src/scenario/scenario.hpp index ebdc81d7..329c7407 100644 --- a/src/scenario/scenario.hpp +++ b/src/scenario/scenario.hpp @@ -24,6 +24,8 @@ #include "shop.hpp" #include "quest.hpp" +class cTagFile; + namespace legacy{ struct scenario_data_type; struct item_storage_shortcut_type; @@ -96,8 +98,8 @@ public: void import_legacy(legacy::scenario_data_type& old); void import_legacy(legacy::scen_item_data_type& old); - void writeTo(std::ostream& file) const; - void readFrom(std::istream& file); + void writeTo(cTagFile& file) const; + void readFrom(const cTagFile& file); std::string format_scen_version(); std::string format_ed_version(); diff --git a/src/scenario/town.cpp b/src/scenario/town.cpp index c768f30d..bdfee2c6 100644 --- a/src/scenario/town.cpp +++ b/src/scenario/town.cpp @@ -238,14 +238,6 @@ void cTown::reattach(cScenario& scen) { scenario = &scen; } -void cTown::writeTerrainTo(std::ostream& file) { - writeArray(file, terrain, max_dim, max_dim); -} - -void cTown::readTerrainFrom(std::istream& file) { - readArray(file, terrain, max_dim, max_dim); -} - bool cTown::is_item_taken(size_t i) const { if(i >= item_taken.size()) return false; return item_taken[i]; diff --git a/src/scenario/town.hpp b/src/scenario/town.hpp index cdcd9970..f8e6acf0 100644 --- a/src/scenario/town.hpp +++ b/src/scenario/town.hpp @@ -113,8 +113,6 @@ public: explicit cTown(cScenario& scenario, size_t dim); void import_legacy(legacy::town_record_type& old); void reattach(cScenario& to); - void writeTerrainTo(std::ostream& file); - void readTerrainFrom(std::istream& file); // Work with the item_taken bitset bool is_item_taken(size_t i) const; void clear_items_taken(); diff --git a/src/scenario/vehicle.cpp b/src/scenario/vehicle.cpp index fd4b0887..fe4c2182 100644 --- a/src/scenario/vehicle.cpp +++ b/src/scenario/vehicle.cpp @@ -13,6 +13,7 @@ #include #include +#include "fileio/tagfile.hpp" #include "oldstructs.hpp" cVehicle::cVehicle() : @@ -52,28 +53,18 @@ void cVehicle::import_legacy(legacy::boat_record_type& old){ sector.y = old.boat_sector.y; } -void cVehicle::writeTo(std::ostream& file) const { - file << "LOCATION " << loc.x << ' ' << loc.y << '\n'; - file << "SECTOR " << sector.x << ' ' << sector.y << '\n'; - file << "IN " << which_town << '\n'; - if(property) file << "OWNED\n"; +void cVehicle::writeTo(cTagFile_Page& page) const { + page["LOCATION"] << loc.x << loc.y; + page["SECTOR"] << sector.x << sector.y; + page["IN"] << which_town; + if(property) page.add("OWNED"); } -void cVehicle::readFrom(std::istream& file) { - while(file) { - std::string cur; - getline(file, cur); - std::istringstream lineIn(cur); - lineIn >> cur; - if(cur == "LOCATION") - lineIn >> loc.x >> loc.y; - else if(cur == "SECTOR") - lineIn >> sector.x >> sector.y; - else if(cur == "IN") - lineIn >> which_town; - else if(cur == "OWNED") - property = true; - } +void cVehicle::readFrom(const cTagFile_Page& page) { + page["LOCATION"] >> loc.x >> loc.y; + page["SECTOR"] >> sector.x >> sector.y; + page["IN"] >> which_town; + property = page.contains("OWNED"); } bool operator==(const cVehicle& a, const cVehicle& b) { diff --git a/src/scenario/vehicle.hpp b/src/scenario/vehicle.hpp index a28e911e..ad0b5237 100644 --- a/src/scenario/vehicle.hpp +++ b/src/scenario/vehicle.hpp @@ -17,6 +17,8 @@ namespace legacy { struct boat_record_type; }; +class cTagFile_Page; + class cVehicle { public: // Both boats and horses use this type. @@ -30,8 +32,8 @@ public: cVehicle(); void import_legacy(legacy::horse_record_type& old); void import_legacy(legacy::boat_record_type& old); - void writeTo(std::ostream& file) const; - void readFrom(std::istream& file); + void writeTo(cTagFile_Page& page) const; + void readFrom(const cTagFile_Page& page); }; bool operator==(const cVehicle& a, const cVehicle& b); diff --git a/src/universe/creature.cpp b/src/universe/creature.cpp index 01f583d1..109ef337 100644 --- a/src/universe/creature.cpp +++ b/src/universe/creature.cpp @@ -14,6 +14,7 @@ #include "mathutil.hpp" #include "pc.hpp" #include "spell.hpp" +#include "fileio/tagfile.hpp" const short cCreature::charm_odds[21] = {90,90,85,80,78, 75,73,60,40,30, 20,10,4,1,0, 0,0,0,0,0, 0}; @@ -331,94 +332,55 @@ bool cCreature::on_space(location loc) const { return false; } -void cCreature::writeTo(std::ostream& file) const { - file << "MONSTER " << number << '\n'; - file << "ATTITUDE " << attitude << '\n'; - file << "STARTATT " << start_attitude << '\n'; - file << "STARTLOC " << start_loc.x << ' ' << start_loc.y << '\n'; - file << "LOCATION " << cur_loc.x << ' ' << cur_loc.y << '\n'; - file << "MOBILITY " << mobility << '\n'; - file << "TIMEFLAG " << time_flag << '\n'; - file << "SUMMONED " << summon_time << ' ' << party_summoned << '\n'; - file << "SPEC " << spec1 << ' ' << spec2 << '\n'; - file << "SPECCODE " << spec_enc_code << '\n'; - file << "TIMECODE " << time_code << '\n'; - file << "TIME " << monster_time << '\n'; - file << "TALK " << personality << '\n'; - file << "DEATH " << special_on_kill << '\n'; - file << "FACE " << facial_pic << '\n'; - file << "TARGET " << target << '\n'; - file << "TARGLOC " << targ_loc.x << ' ' << targ_loc.y << '\n'; - for(auto stat : status) { - if(stat.second != 0) - file << "STATUS " << stat.first << ' ' << stat.second << '\n'; - } - file << "CURHP " << health << '\n'; - file << "CURSP " << mp << '\n'; - file << "MORALE " << morale << '\n'; - file << "DIRECTION " << direction << '\n'; +void cCreature::writeTo(cTagFile_Page& page) const { + page["MONSTER"] << number; + page["ATTITUDE"] << attitude; + page["STARTATT"] << start_attitude; + page["STARTLOC"] << start_loc.x << start_loc.y; + page["LOCATION"] << cur_loc.x << cur_loc.y; + page["MOBILITY"] << mobility; + page["TIMEFLAG"] << time_flag; + page["SUMMONED"] << summon_time << party_summoned; + page["SPEC"] << spec1 << spec2; + page["SPECCODE"] << spec_enc_code; + page["TIMECODE"] << time_code; + page["TIME"] << monster_time; + page["TALK"] << personality; + page["DEATH"] << special_on_kill; + page["FACE"] << facial_pic; + page["TARGET"] << target; + page["TARGLOC"] << targ_loc.x << targ_loc.y; + page["STATUS"].encodeSparse(status); + page["CURHP"] << health << '\n'; + page["CURSP"] << mp << '\n'; + page["MORALE"] << morale << '\n'; + page["DIRECTION"] << direction << '\n'; // TODO: Should we be saving "max_mp" and/or "m_morale"? } -void cCreature::readFrom(std::istream& file) { - while(file) { - std::string cur; - getline(file, cur); - std::istringstream line(cur); - line >> cur; - if(cur == "MONSTER") - line >> number; - else if(cur == "ATTITUDE") - line >> attitude; - else if(cur == "STARTATT") { - line >> start_attitude; - } else if(cur == "STARTLOC") - line >> start_loc.x >> start_loc.y; - else if(cur == "LOCATION") - line >> cur_loc.x >> cur_loc.y; - else if(cur == "MOBILITY") { - unsigned int i; - line >> i; - mobility = i; - } else if(cur == "TIMEFLAG") { - line >> time_flag; - } else if(cur == "SUMMONED") - line >> summon_time >> party_summoned; - else if(cur == "SPEC") - line >> spec1 >> spec2; - else if(cur == "SPECCODE") { - int i; - line >> i; - spec_enc_code = i; - } else if(cur == "TIMECODE") { - int i; - line >> i; - time_code = i; - } else if(cur == "TIME") - line >> monster_time; - else if(cur == "TALK") - line >> personality; - else if(cur == "DEATH") - line >> special_on_kill; - else if(cur == "FACE") - line >> facial_pic; - else if(cur == "TARGET") - line >> target; - else if(cur == "TARGLOC") - line >> targ_loc.x >> targ_loc.y; - else if(cur == "CURHP") - line >> health; - else if(cur == "CURSP") - line >> mp; - else if(cur == "MORALE") - line >> morale; - else if(cur == "DIRECTION") - line >> direction; - else if(cur == "STATUS") { - eStatus i; - line >> i >> status[i]; - } - } +void cCreature::readFrom(const cTagFile_Page& page) { + page["MONSTER"] >> number; + page["ATTITUDE"] >> attitude; + page["STARTATT"] >> start_attitude; + page["STARTLOC"] >> start_loc.x >> start_loc.y; + page["LOCATION"] >> cur_loc.x >> cur_loc.y; + page["MOBILITY"] >> mobility; + page["TIMEFLAG"] >> time_flag; + page["SUMMONED"] >> summon_time >> party_summoned; + page["SPEC"] >> spec1 >> spec2; + page["SPECCODE"] >> spec_enc_code; + page["TIMECODE"] >> time_code; + page["TIME"] >> monster_time; + page["TALK"] >> personality; + page["DEATH"] >> special_on_kill; + page["FACE"] >> facial_pic; + page["TARGET"] >> target; + page["TARGLOC"] >> targ_loc.x >> targ_loc.y; + page["STATUS"].extractSparse(status); + page["CURHP"] >> health; + page["CURSP"] >> mp; + page["MORALE"] >> morale; + page["DIRECTION"] >> direction; } void cCreature::print_attacks(iLiving* target) { diff --git a/src/universe/creature.hpp b/src/universe/creature.hpp index 593a1acd..b2fd4c27 100644 --- a/src/universe/creature.hpp +++ b/src/universe/creature.hpp @@ -14,6 +14,8 @@ #include "scenario/monster.hpp" #include "living.hpp" +class cTagFile_Page; + class cCreature : public cMonster, public cTownperson, public iLiving { public: static const short charm_odds[21]; @@ -69,8 +71,8 @@ public: bool on_space(location loc) const; void import_legacy(legacy::creature_data_type old); - void writeTo(std::ostream& file) const; - void readFrom(std::istream& file); + void writeTo(cTagFile_Page& file) const; + void readFrom(const cTagFile_Page& file); }; #endif diff --git a/src/universe/party.cpp b/src/universe/party.cpp index 05e60666..bad4a811 100644 --- a/src/universe/party.cpp +++ b/src/universe/party.cpp @@ -19,6 +19,7 @@ #include "dialogxml/dialogs/strdlog.hpp" #include "oldstructs.hpp" #include "fileio/fileio.hpp" +#include "fileio/tagfile.hpp" #include "mathutil.hpp" cParty::cParty(ePartyPreset party_preset) { @@ -662,447 +663,406 @@ bool cParty::start_timer(short time, spec_num_t node, eSpecCtxType type){ return(true); } -void cParty::writeTo(std::ostream& file) const { - file << "CREATEVERSION " << std::hex << OBOE_CURRENT_VERSION << std::dec << '\n'; - file << "AGE " << age << '\n'; - file << "GOLD " << gold << '\n'; - file << "FOOD " << food << '\n'; - file << "NEXTID " << next_pc_id << '\n'; - file << "HOSTILES " << int(hostiles_present) << '\n'; - file << "EASY " << int(easy_mode) << '\n'; - file << "LESSWM " << int(less_wm) << '\n'; - for(int i = 0; i < 310; i++) - for(int j = 0; j < 50; j++) - if(stuff_done[i][j] > 0) - file << "SDF " << i << ' ' << j << ' ' << unsigned(stuff_done[i][j]) << '\n'; - for(auto iter = pointers.begin(); iter != pointers.end(); iter++) - file << "POINTER " << iter->first << ' ' << iter->second.first << ' ' << iter->second.second << '\n'; - for(int i = 0; i < magic_ptrs.size(); i++) - file << "POINTER " << i+10 << ' ' << int(magic_ptrs[i]) << '\n'; - file << "LIGHT " << light_level << '\n'; - file << "OUTCORNER " << outdoor_corner.x << ' ' << outdoor_corner.y << '\n'; - file << "INWHICHCORNER " << i_w_c.x << ' ' << i_w_c.y << '\n'; - file << "SECTOR " << out_loc.x << ' ' << out_loc.y << '\n'; - file << "LOCINSECTOR " << loc_in_sec.x << ' ' << loc_in_sec.y << '\n'; - file << "IN " << in_boat << ' ' << in_horse << '\n'; - for(auto& kv : status) { - if(kv.second > 0) - file << "STATUS " << kv.first << ' ' << kv.second << '\n'; +void cParty::writeTo(cTagFile& file) const { + auto& main_page = file.add(); + main_page["CREATEVERSION"] << as_hex(OBOE_CURRENT_VERSION); + main_page["AGE"] << age; + main_page["GOLD"] << gold; + main_page["FOOD"] << food; + main_page["NEXTID"] << next_pc_id; + main_page["HOSTILES"] << hostiles_present; + main_page["EASY"] << easy_mode; + main_page["LESSWM"] << less_wm; + for(int i = 0; i < 310; i++) { + for(int j = 0; j < 50; j++) { + if(stuff_done[i][j] > 0) { + main_page["SDF"] << i << j << stuff_done[i][j]; + } + } } + for(auto iter = pointers.begin(); iter != pointers.end(); iter++) { + main_page["POINTER"] << iter->first << iter->second.first << iter->second.second; + } + for(int i = 0; i < magic_ptrs.size(); i++) { + main_page["POINTER"] << i + 10 << magic_ptrs[i]; + } + main_page["LIGHT"] << light_level; + main_page["OUTCORNER"] << outdoor_corner.x << outdoor_corner.y; + main_page["INWHICHCORNER"] << i_w_c.x << i_w_c.y; + main_page["SECTOR"] << out_loc.x << out_loc.y; + main_page["LOCINSECTOR"] << loc_in_sec.x << loc_in_sec.y; + main_page["IN"] << in_boat << in_horse; + main_page["STATUS"].encodeSparse(status); if(is_split()) { - file << "SPLIT_LEFT_IN " << left_in << '\n'; - file << "SPLIT_LEFT_AT " << left_at.x << ' ' << left_at.y << '\n'; + main_page["SPLIT_LEFT_IN"] << left_in; + main_page["SPLIT_LEFT_AT"] << left_at.x << left_at.y; } - for(int i : m_noted) - file << "ROSTER " << i << '\n'; - for(int i : m_seen) - file << "SEEN " << i << '\n'; - for(int i = 0; i < 4; i++) - if(imprisoned_monst[i] > 0) - file << "SOULCRYSTAL " << i << ' ' << imprisoned_monst[i] << '\n'; - file << "DIRECTION " << direction << '\n'; - file << "WHICHSLOT " << at_which_save_slot << '\n'; + for(int i : m_noted) { + main_page["ROSTER"] << i; + } + for(int i : m_seen) { + main_page["SEEN"] << i; + } + for(int i = 0; i < 4; i++) { + if(imprisoned_monst[i] > 0) { + main_page["SOULCRYSTAL"] << i << imprisoned_monst[i]; + } + } + main_page["DIRECTION"] << direction; + main_page["WHICHSLOT"] << at_which_save_slot; for(int i = 0; i < creature_save.size(); i++) { - file << "TOWNSAVE " << i << ' ' << creature_save[i].which_town; - if(creature_save[i].hostile) file << " HOSTILE"; - file << '\n'; + auto tmp = main_page["TOWNSAVE"] << i << creature_save[i].which_town; + if(creature_save[i].hostile) tmp << "HOSTILE"; + } + for(int i = 0; i < alchemy.size(); i++) { + if(alchemy[i]) { + main_page["ALCHEMY"] << i; + } + } + main_page["EVENT"].encodeSparse(key_times); + for(int i : spec_items) { + main_page["ITEM"] << i; + } + main_page["KILLS"] << total_m_killed; + main_page["DAMAGE"] << total_dam_done; + main_page["WOUNDS"] << total_dam_taken; + main_page["EXPERIENCE"] << total_xp_gained; + main_page["SCENARIO"] << scen_name; + main_page["WON"] << scen_won; + main_page["PLAYED"] << scen_played; + for(auto p : active_quests) { + main_page["QUEST"] << p.first << p.second.status << p.second.start << p.second.source; } - for(int i = 0; i < alchemy.size(); i++) - if(alchemy[i]) - file << "ALCHEMY " << i << '\n'; - for(auto key : key_times) - file << "EVENT " << key.first << ' ' << key.second << '\n'; - for(int i : spec_items) - file << "ITEM " << i << '\n'; - file << "KILLS " << total_m_killed << '\n'; - file << "DAMAGE " << total_dam_done << '\n'; - file << "WOUNDS " << total_dam_taken << '\n'; - file << "EXPERIENCE " << total_xp_gained << '\n'; - file << "SCENARIO " << scen_name << '\n'; - file << "WON " << scen_won << '\n'; - file << "PLAYED " << scen_played << '\n'; - for(auto p : active_quests) - file << "QUEST " << p.first << ' ' << p.second.status << ' ' << p.second.start << ' ' << p.second.source << '\n'; for(auto p : store_limited_stock) { for(auto p2 : p.second) { - file << "SHOPSTOCK " << p.first << ' ' << p2.first << ' ' << p2.second << '\n'; + main_page["SHOPSTOCK"] << p.first << p2.first << p2.second; } } for(auto iter = campaign_flags.begin(); iter != campaign_flags.end(); iter++){ - std::string campaign_id = maybe_quote_string(iter->first); // Okay, we have the campaign ID in a state such that reading it back in will restore the original ID. // Now output any flags that are set for this campaign. - for(unsigned int i = 0; i < 25; i++) - for(unsigned int j = 0; j < 20; j++) - if(iter->second.idx[i][j] > 0) - file << "CAMPAIGN " << campaign_id << ' ' << i << ' ' << j << ' ' << unsigned(iter->second.idx[i][j]) << '\n'; + for(unsigned int i = 0; i < campaign_flag_type::x_max; i++) { + for(unsigned int j = 0; j < campaign_flag_type::y_max; j++) { + if(iter->second.idx[i][j] > 0) { + main_page["CAMPAIGN"] << iter->first << i << j << iter->second.idx[i][j]; + } + } + } } - file << '\f'; for(int i = 0; i < boats.size(); i++){ if(boats[i].exists) { - file << "BOAT " << i << '\n'; - boats[i].writeTo(file); - file << '\f'; + auto& boat_page = file.add(); + boat_page["BOAT"] << i; + boats[i].writeTo(boat_page); } } - file << '\f'; for(int i = 0; i < horses.size(); i++){ if(horses[i].exists) { - file << "HORSE " << i << '\n'; - horses[i].writeTo(file); - file << '\f'; + auto& horse_page = file.add(); + horse_page["HORSE"] << i; + horses[i].writeTo(horse_page); } } - file << '\f'; for(auto& p : magic_store_items) { - for(auto& p2 : p.second) - if(p2.second.variety != eItemType::NO_ITEM){ - file << "MAGICSTORE " << p.first << ' ' << p2.first << '\n'; - p2.second.writeTo(file); - file << '\f'; + for(auto& p2 : p.second) { + if(p2.second.variety != eItemType::NO_ITEM) { + auto& store_page = file.add(); + store_page["MAGICSTORE"] << p.first << p2.first; + p2.second.writeTo(store_page); } - } - file << '\f'; - for(int i = 0; i < job_banks.size(); i++) { - file << "JOBBANK " << i << ' ' << job_banks[i].anger << '\n'; - if(!job_banks[i].inited) continue; - for(int j = 0; j < 6; j++) - file << "JOB " << j << ' ' << job_banks[i].jobs[j] << '\n'; - } - file << '\f'; - for(int i = 0; i < out_c.size(); i++) - if(out_c[i].exists){ - file << "ENCOUNTER " << i << "\n"; - file << "DIRECTION " << out_c[i].direction << '\n'; - file << "SECTOR " << out_c[i].which_sector.x << ' ' << out_c[i].which_sector.y << '\n'; - file << "LOCINSECTOR " << out_c[i].m_loc.x << ' ' << out_c[i].m_loc.y << '\n'; - file << "HOME " << out_c[i].home_sector.x << ' ' << out_c[i].home_sector.y << '\n'; - file << "-\n"; - out_c[i].what_monst.writeTo(file); - file << '\f'; } - file << '\f'; - file << '\f'; - for(unsigned int i = 0; i < party_event_timers.size(); i++) - file << "TIMER " << ' ' << party_event_timers[i].time << ' ' << int(party_event_timers[i].node_type) - << ' ' << party_event_timers[i].node << '\f'; - file << '\f'; - for(int i = 0; i < creature_save.size(); i++) + } + for(int i = 0; i < job_banks.size(); i++) { + auto& job_page = file.add(); + job_page["JOBBANK"] << i << job_banks[i].anger; + if(!job_banks[i].inited) continue; + for(int j = 0; j < 6; j++){ + job_page["JOB"] << j << job_banks[i].jobs[j]; + } + } + for(int i = 0; i < out_c.size(); i++) { + if(out_c[i].exists) { + auto& enc_page = file.add(); + enc_page["ENCOUNTER"] << i; + out_c[i].writeTo(enc_page); + } + } + // TODO: Why is each timer on its own page, anyway? It clearly doesn't need to be! + for(unsigned int i = 0; i < party_event_timers.size(); i++) { + auto& timer_page = file.add(); + timer_page["TIMER"] << party_event_timers[i].time << int(party_event_timers[i].node_type) << party_event_timers[i].node; + } + for(int i = 0; i < creature_save.size(); i++) { for(int j = 0; j < creature_save[i].size(); j++) { if(creature_save[i][j].active > 0) { - file << "CREATURE " << i << ' ' << j << '\n'; - creature_save[i][j].writeTo(file); - file << '\f'; + auto& creature_page = file.add(); + creature_page["CREATURE"] << i << j; + creature_save[i][j].writeTo(creature_page); } } - file << '\f'; - for(int i = 0; i < stored_items.size(); i++) - for(size_t j = 0; j < stored_items[i].size(); j++) - if(stored_items[i][j].variety != eItemType::NO_ITEM){ - file << "STORED " << i << ' ' << j << '\n'; - stored_items[i][j].writeTo(file); - file << '\f'; + } + for(int i = 0; i < stored_items.size(); i++) { + for(size_t j = 0; j < stored_items[i].size(); j++) { + if(stored_items[i][j].variety != eItemType::NO_ITEM) { + auto& item_page = file.add(); + item_page["STORED"] << i << j; + stored_items[i][j].writeTo(item_page); } + } + } if(summons.size() > 0) { - file << '\f'; int i = 0; for(const cMonster& monst : summons) { - file << "SUMMON " << i++ << '\n'; - monst.writeTo(file); - file << '\f'; + auto& monst_page = file.add(); + monst_page["SUMMON"] << i++; + monst.writeTo(monst_page); + cTagFile_Page* abil_page = nullptr; + for(const auto& abil_elem : monst.abil) { + if(abil_page == nullptr) abil_page = &file.add(); + if(abil_elem.second.writeTo(abil_elem.first, *abil_page)) { + abil_page = nullptr; + } + } } } if(journal.size() > 0) { - file << '\f'; for(const cJournal& entry : journal) { - file << "JOURNAL " << entry.day << ' ' << maybe_quote_string(entry.in_scen) << '\n' << entry.the_str << '\n'; - file << '\f'; + auto& journal_page = file.add(); + journal_page["JOURNAL"] << entry.day << entry.in_scen; + journal_page["STRING"] << entry.the_str; } } if(special_notes.size() > 0) { - file << '\f'; for(const cEncNote& note : special_notes) { - file << "ENCNOTE " << note.type << ' ' << maybe_quote_string(note.where) << '\n' << note.the_str << '\n'; - file << '\f'; + auto& note_page = file.add(); + note_page["ENCNOTE"] << note.type << note.where; + note_page["STRING"] << note.the_str; } } if(talk_save.size() > 0) { - file << '\f'; for(const cConvers& note : talk_save) { - file << "TALKNOTE\n"; - file << "WHO " << maybe_quote_string(note.who_said) << '\n'; - file << "WHERE " << maybe_quote_string(note.in_town) << ' ' << maybe_quote_string(note.in_scen) << '\n'; - file << "-\n" << note.the_str1 << '\n' << note.the_str2 << '\n'; - file << '\f'; + auto& note_page = file.add(); + note_page.add("TALKNOTE"); + note_page["WHO"] << note.who_said; + note_page["WHERE"] << note.in_town << note.in_scen; + note_page["STRING"] << note.the_str1; + note_page["STRING"] << note.the_str2; } } } -void cParty::readFrom(std::istream& file){ - // TODO: Error-check input - // TODO: Don't call this sin, it's a trig function - std::istringstream bin; - std::string cur; - getline(file, cur, '\f'); - bin.str(cur); - while(bin) { // continue as long as no error, such as eof, occurs - getline(bin, cur); - std::istringstream sin(cur); - sin >> cur; - if(cur == "AGE") - sin >> age; - else if(cur == "GOLD") - sin >> gold; - else if(cur == "FOOD") - sin >> food; - else if(cur == "NEXTID") - sin >> next_pc_id; - else if(cur == "SDF"){ - int i,j; - unsigned int n; - sin >> i >> j >> n; - stuff_done[i][j] = n; - } else if(cur == "HOSTILES") { - int n; - sin >> n; - hostiles_present = n; - } else if(cur == "EASY") { - int n; - sin >> n; - easy_mode = n; - } else if(cur == "LESSWM") { - int n; - sin >> n; - less_wm = n; - } else if(cur == "CREATEVERSION") { - unsigned long long version; - sin >> std::hex >> version >> std::dec; +void cParty::readFrom(const cTagFile& file) { + size_t monst_i = 0; + for(const auto& page : file) { + if(page.index() == 0) { + as_hex version{0}; + page["CREATEVERSION"] >> version; if(version > OBOE_CURRENT_VERSION) { showWarning("This game appears to have been created with a newer version of Blades of Exile than you are running. Exile will do its best to load the saved game anyway, but there may be loss of information."); } - } else if(cur == "POINTER") { - int i,j,k; - sin >> i >> j; - if(i >= 10 && i < 100) { - magic_ptrs[i-10] = j; - } else if(i >= 100 && i < 200) { - sin >> k; - pointers[i] = std::make_pair(j,k); + + page["AGE"] >> age; + page["GOLD"] >> gold; + page["FOOD"] >> food; + page["NEXTID"] >> next_pc_id; + page["HOSTILES"] >> hostiles_present; + page["EASY"] >> easy_mode; + page["LESSWM"] >> less_wm; + page["LIGHT"] >> light_level; + page["OUTCORNER"] >> outdoor_corner.x >> outdoor_corner.y; + page["INWHICHCORNER"] >> i_w_c.x >> i_w_c.y; + page["SECTOR"] >> out_loc.x >> out_loc.y; + page["LOCINSECTOR"] >> loc_in_sec.x >> loc_in_sec.y; + page["IN"] >> in_boat >> in_horse; + page["DIRECTION"] >> direction; + page["WHICHSLOT"] >> at_which_save_slot; + page["KILLS"] >> total_m_killed; + page["DAMAGE"] >> total_dam_done; + page["WOUNDS"] >> total_dam_taken; + page["EXPERIENCE"] >> total_xp_gained; + page["SCENARIO"] >> scen_name; + page["WON"] >> scen_won; + page["PLAYED"] >> scen_played; + + page["STATUS"].extractSparse(status); + page["EVENT"].extractSparse(key_times); + + if(page.contains("SPLIT_LEFT_IN")) { + page["SPLIT_LEFT_IN"] >> left_in; + page["SPLIT_LEFT_AT"] >> left_at.x >> left_at.y; + } else { + left_in = -1; } - } else if(cur == "STATUS") { - ePartyStatus stat; - int n; - sin >> stat >> n; - status[stat] = n; - }else if(cur == "LIGHT") - sin >> light_level; - else if(cur == "OUTCORNER") - sin >> outdoor_corner.x >> outdoor_corner.y; - else if(cur == "INWHICHCORNER") - sin >> i_w_c.x >> i_w_c.y; - else if(cur == "SECTOR") - sin >> out_loc.x >> out_loc.y; - else if(cur == "LOCINSECTOR") - sin >> loc_in_sec.x >> loc_in_sec.y; - else if(cur == "SPLIT_LEFT_IN") - sin >> left_in; - else if(cur == "SPLIT_LEFT_AT") - sin >> left_at.x >> left_at.y; - else if(cur == "IN") - sin >> in_boat >> in_horse; - else if(cur == "ROSTER"){ - int i; - sin >> i; - m_noted.insert(i); - }else if(cur == "SEEN"){ - int i; - sin >> i; - m_seen.insert(i); - }else if(cur == "SOULCRYSTAL"){ - int i; - sin >> i; - if(i < 0 || i >= imprisoned_monst.size()) continue; - sin >> imprisoned_monst[i]; - }else if(cur == "DIRECTION") - sin >> direction; - else if(cur == "WHICHSLOT") - sin >> at_which_save_slot; - else if(cur == "ALCHEMY"){ - int i; - sin >> i; - if(i >= 0 && i < alchemy.size()) - alchemy[i] = true; - } else if(cur == "TOWNSAVE") { - int i; - std::string str; - sin >> i; - if(i < 0 || i >= creature_save.size()) continue; - sin >> creature_save[i].which_town >> str; - creature_save[i].hostile = str == "HOSTILE"; - }else if(cur == "EVENT"){ - int i; - sin >> i; - sin >> key_times[i]; - }else if(cur == "ITEM"){ - int i; - sin >> i; - spec_items.insert(i); - } else if(cur == "QUEST") { - int i; - sin >> i; - sin >> active_quests[i].status >> active_quests[i].start >> active_quests[i].source; - } else if(cur == "SHOPSTOCK") { - int i, j; - sin >> i >> j >> store_limited_stock[i][j]; - }else if(cur == "KILLS") - sin >> total_m_killed; - else if(cur == "DAMAGE") - sin >> total_dam_done; - else if(cur == "WOUNDS") - sin >> total_dam_taken; - else if(cur == "EXPERIENCE") - sin >> total_xp_gained; - else if(cur == "SCENARIO") - sin >> scen_name; - else if(cur == "WON") - sin >> scen_won; - else if(cur == "PLAYED") - sin >> scen_played; - sin.clear(); - } - bin.clear(); - while(file) { - getline(file, cur, '\f'); - bin.str(cur); - bin >> cur; - if(cur == "BOAT") { - int i; - bin >> i; - if(i >= boats.size()) - boats.resize(i + 1); - boats[i].exists = true; - boats[i].readFrom(bin); - } else if(cur == "HORSE") { - int i; - bin >> i; - if(i >= horses.size()) - horses.resize(i + 1); - horses[i].exists = true; - horses[i].readFrom(bin); - } else if(cur == "MAGICSTORE") { - int i,j; - bin >> i >> j; - magic_store_items[i][j].readFrom(bin); - } else if(cur == "ENCOUNTER") { - int i; - bin >> i; - while(bin) { - getline(bin, cur); - std::istringstream sin(cur); - sin >> cur; - if(cur == "DIRECTION") - sin >> out_c[i].direction; - else if(cur == "SECTOR") - sin >> out_c[i].which_sector.x >> out_c[i].which_sector.y; - else if(cur == "LOCINSECTOR") - sin >> out_c[i].m_loc.x >> out_c[i].m_loc.y; - else if(cur == "HOME") - sin >> out_c[i].home_sector.x >> out_c[i].home_sector.y; - else if(cur == "-") break; - } - out_c[i].what_monst.readFrom(bin); - out_c[i].exists = true; - }else if(cur == "CAMPAIGN") { - unsigned int i, j; - int val; - cur = read_maybe_quoted_string(bin); - bin >> i >> j >> val; - if(i < 25 && j < 25) - campaign_flags[cur].idx[i][j] = val; - } else if(cur == "TIMER") { - int i, j; - bin >> i; - cTimer timer; - bin >> timer.time >> j >> timer.node; - timer.node_type = eSpecCtxType(j); - party_event_timers.push_back(timer); - } else if(cur == "CREATURE") { - int i, j; - bin >> i >> j; - if(i < 0 || i >= creature_save.size()) continue; - creature_save[i].init(j); - creature_save[i][j].readFrom(bin); - } else if(cur == "STORED") { - int i, j; - bin >> i >> j; - if(i < 0 || i >= 3 || j < 0) continue; - if(j >= stored_items[i].size()) - stored_items[i].resize(j + 1); - stored_items[i][j].readFrom(bin); - } else if(cur == "SUMMON") { - size_t i; - bin >> i; - cMonster monst; - bin >> std::ws; - monst.readFrom(bin); - if(i >= summons.size()) - summons.resize(i + 1); - summons[i] = monst; - } else if(cur == "JOURNAL") { - cJournal entry; - bin >> entry.day; - entry.in_scen = read_maybe_quoted_string(bin); - bin >> std::ws; - getline(bin, entry.the_str); - journal.push_back(entry); - } else if(cur == "ENCNOTE") { - cEncNote note; - bin >> note.type; - note.where = read_maybe_quoted_string(bin); - bin >> std::ws; - getline(bin, note.the_str); - special_notes.push_back(note); - } else if(cur == "TALKNOTE") { - cConvers note; - while(bin) { - getline(bin, cur); - std::istringstream sin(cur); - sin >> cur; - if(cur == "WHO") - note.who_said = read_maybe_quoted_string(sin); - else if(cur == "WHERE") { - note.in_town = read_maybe_quoted_string(sin); - note.in_scen = read_maybe_quoted_string(sin); - } else if(cur == "-") break; - } - bin >> std::ws; - getline(bin, note.the_str1); - getline(bin, note.the_str2); - note.filled = true; - talk_save.push_back(note); - } else if(cur == "JOB_BANK") { - int i; - bin >> i; - if(i < 0) continue; - if(i >= job_banks.size()) - job_banks.resize(i + 1); - bin >> job_banks[i].anger; - job_banks[i].inited = false; - while(bin) { - getline(bin, cur); - std::istringstream sin(cur); - sin >> cur; - if(cur == "JOB") { - job_banks[i].inited = true; - int j; - sin >> j; - if(j < 0 || j >= 6) - continue; - sin >> job_banks[i].jobs[j]; + + memset(stuff_done, 0, sizeof(stuff_done)); + for(size_t i = 0; i < page["SDF"].size(); i++) { + size_t x, y, val; + page["SDF"] >> x >> y >> val; + if(x <= sdx_max && y <= sdy_max) { + stuff_done[x][y] = val; } } + + pointers.clear(); + magic_ptrs.fill(0); + for(size_t n = 0; n < page["POINTER"].size(); n++) { + int i, j, k; + auto tmp = page["POINTER"] >> i >> j; + if(i >= 10 && i < 100) { + magic_ptrs[i - 10] = j; + } else { + tmp >> k; + pointers[i] = std::make_pair(j, k); + } + } + + m_noted.clear(); m_seen.clear(); + std::vector roster; + page["ROSTER"].extract(roster); + std::copy(roster.begin(), roster.end(), std::inserter(m_noted, m_noted.begin())); + page["SEEN"].extract(roster); + std::copy(roster.begin(), roster.end(), std::inserter(m_seen, m_seen.begin())); + + imprisoned_monst.fill(0); + std::vector soul_crystal; + page["SOULCRYSTAL"].extract(soul_crystal); + std::copy_n(soul_crystal.begin(), std::min(soul_crystal.size(), imprisoned_monst.size()), imprisoned_monst.begin()); + + alchemy.reset(); + std::deque alch; // deque instead of vector to avoid the bitwise specialization + page["ALCHEMY"].extract(alch); + alch.resize(alchemy.size()); + for(size_t i = 0; i < alchemy.size(); i++) { + alchemy[i] = alch[i]; + } + + for(size_t n = 0; n < page["TOWNSAVE"].size(); n++) { + size_t i; + auto tmp = page["TOWNSAVE"] >> i; + if(i >= creature_save.size()) continue; + std::string hostile; + tmp >> creature_save[i].which_town >> hostile; + creature_save[i].hostile = hostile == "HOSTILE"; + } + + spec_items.clear(); + std::vector items; + page["ITEM"].extract(items); + std::copy(items.begin(), items.end(), std::inserter(spec_items, spec_items.begin())); + + active_quests.clear(); + std::map> quests; + page["QUEST"].extractSparse(quests); + for(const auto& p : quests) { + cJob job(std::get<1>(p.second), std::get<2>(p.second)); + job.status = std::get<0>(p.second); + active_quests.emplace(p.first, job); + } + + vector2d shop_stock; + page["SHOPSTOCK"].extractSparse(shop_stock); + for(size_t x = 0; x < shop_stock.width(); x++) { + for(size_t y = 0; y < shop_stock.height(); y++) { + store_limited_stock[x][y] = shop_stock[x][y]; + } + } + + std::string cur; + unsigned int i, j; + int val; + while(page["CAMPAIGN"] >> cur >> i >> j >> val) { + if(i <= campaign_flag_type::x_max && j <= campaign_flag_type::y_max) { + campaign_flags[cur].idx[i][j] = val; + } + } + } else if(page.getFirstKey() == "BOAT") { + size_t i; + page["BOAT"] >> i; + if(i >= boats.size()) boats.resize(i + 1); + boats[i].exists = true; + boats[i].readFrom(page); + } else if(page.getFirstKey() == "HORSE") { + size_t i; + page["HORSE"] >> i; + if(i >= horses.size()) horses.resize(i + 1); + horses[i].exists = true; + horses[i].readFrom(page); + } else if(page.getFirstKey() == "MAGICSTORE") { + size_t i, j; + page["MAGICSTORE"] >> i >> j; + magic_store_items[i][j].readFrom(page); + } else if(page.getFirstKey() == "ENCOUNTER") { + int i; + page["ENCOUNTER"] >> i; + out_c[i].exists = true; + out_c[i].readFrom(page); + } else if(page.getFirstKey() == "TIMER") { + size_t i, j; + cTimer timer; + while(page["TIMER"] >> i >> timer.time >> j >> timer.node) { + timer.node_type = eSpecCtxType(j); + if(i < party_event_timers.size()) party_event_timers.resize(i + 1); + party_event_timers[i] = timer; + } + } else if(page.getFirstKey() == "CREATURE") { + size_t i, j; + page["CREATURE"] >> i >> j; + if(i < 0 || i >= creature_save.size()) continue; + creature_save[i].init(j); + creature_save[i][j].readFrom(page); + } else if(page.getFirstKey() == "STORED") { + size_t i, j; + page["STORED"] >> i >> j; + if(i >= 3) continue; + if(j >= stored_items[i].size()) { + stored_items[i].resize(j + 1); + } + stored_items[i][j].readFrom(page); + } else if(page.getFirstKey() == "SUMMON") { + page["SUMMON"] >> monst_i; + if(monst_i >= summons.size()) { + summons.resize(monst_i + 1); + } + summons[monst_i].readFrom(page); + } else if(page.getFirstKey() == "ABIL") { + if(monst_i >= summons.size()) continue; + uAbility abil; + eMonstAbil key = abil.readFrom(page); + if(key != eMonstAbil::NO_ABIL) { + summons[monst_i].abil[key] = abil; + } + } else if(page.getFirstKey() == "JOURNAL") { + cJournal entry; + page["JOURNAL"] >> entry.day >> entry.in_scen; + page["STRING"] >> entry.the_str; + journal.push_back(entry); + } else if(page.getFirstKey() == "ENCNOTE") { + cEncNote note; + page["ENCNOTE"] >> note.type >> note.where; + page["STRING"] >> note.the_str; + special_notes.push_back(note); + } else if(page.getFirstKey() == "TALKNOTE") { + // TALKNOTE is an empty tag with no value that only serves to identify the page + cConvers note; + page["WHO"] >> note.who_said; + page["WHERE"] >> note.in_town >> note.in_scen; + page["STRING"] >> note.the_str1; + page["STRING"] >> note.the_str2; + note.filled = true; + talk_save.push_back(note); + } else if(page.getFirstKey() == "JOBBANK") { + size_t i; + job_bank_t bank; + page["JOBBANK"] >> i >> bank.anger; + if(i >= job_banks.size()) { + job_banks.resize(i + 1); + } + std::vector jobs; + page["JOB"].extractSparse(jobs); + bank.inited = !jobs.empty(); + std::copy_n(jobs.begin(), std::min(jobs.size(), bank.jobs.size()), bank.jobs.begin()); + job_banks[i] = bank; } - bin.clear(); } } diff --git a/src/universe/party.hpp b/src/universe/party.hpp index a07679d1..5c7d8a45 100644 --- a/src/universe/party.hpp +++ b/src/universe/party.hpp @@ -38,6 +38,11 @@ namespace legacy { struct campaign_flag_type{ unsigned char idx[25][25]; +private: + using idx_array = decltype(idx); +public: + static const int x_max = std::extent::value - 1; + static const int y_max = std::extent::value - 1; }; struct job_bank_t { @@ -54,6 +59,7 @@ enum eEncNoteType { class cUniverse; class cItem; +class cTagFile; class cParty : public iLiving { public: @@ -190,8 +196,8 @@ public: bool start_timer(short time, spec_num_t node, eSpecCtxType type); cPlayer& operator[](unsigned short n); const cPlayer& operator[](unsigned short n) const; - void writeTo(std::ostream& file) const; - void readFrom(std::istream& file); + void writeTo(cTagFile& file) const; + void readFrom(const cTagFile& file); bool give_item(cItem item,int flags); bool forced_give(cItem item,eItemAbil abil,short dat = -1); diff --git a/src/universe/pc.cpp b/src/universe/pc.cpp index 003877a8..a29775ef 100644 --- a/src/universe/pc.cpp +++ b/src/universe/pc.cpp @@ -19,6 +19,7 @@ #include "oldstructs.hpp" #include "mathutil.hpp" #include "fileio/fileio.hpp" +#include "fileio/tagfile.hpp" #include "sounds.hpp" extern short skill_bonus[21]; @@ -1194,164 +1195,136 @@ void operator -= (eMainStatus& stat, eMainStatus othr){ stat = (eMainStatus) (-10 + (int)othr); } -void cPlayer::writeTo(std::ostream& file) const { - file << "UID " << unique_id << '\n'; - file << "STATUS -1 " << main_status << '\n'; - file << "NAME " << maybe_quote_string(name) << '\n'; - file << "SKILL " << eSkill::MAX_HP << ' ' << max_health << '\n'; - if(max_sp > 0) - file << "SKILL " << eSkill::MAX_SP << ' ' << max_sp << '\n'; - for(auto p : skills) { - if(p.second > 0) - file << "SKILL " << int(p.first) << ' ' << p.second << '\n'; +void cPlayer::writeTo(cTagFile& file) const { + auto& page = file.add(); + page["UID"] << unique_id; + page["STATUS"] << -1 << main_status; + page["NAME"] << name; + page["SKILL"] << eSkill::MAX_HP << max_health; + if(max_sp > 0) { + page["SKILL"] << eSkill::MAX_SP << max_sp; } - file << "HEALTH " << cur_health << '\n'; - file << "MANA " << cur_sp << '\n'; - file << "EXPERIENCE " << experience << '\n'; - file << "SKILLPTS " << skill_pts << '\n'; - file << "LEVEL " << level << '\n'; - auto status = this->status; - for(int i = 0; i < 15; i++) { - eStatus stat = (eStatus) i; - if(status[stat] != 0) - file << "STATUS " << i << ' ' << status.at(stat) << '\n'; + page["SKILL"].encodeSparse(skills); + page["HEALTH"] << cur_health; + page["MANA"] << cur_sp; + page["EXPERIENCE"] << experience; + page["SKILLPTS"] << skill_pts; + page["LEVEL"] << level; + page["STATUS"].encodeSparse(status); + for(int i = 0; i < equip.size(); i++) { + if(equip[i]) { + page["EQUIP"] << i; + } + } + for(int i = 0; i < mage_spells.size(); i++) { + if(mage_spells[i]) { + page["MAGE"] << i; + } + } + for(int i = 0; i < priest_spells.size(); i++) { + if(priest_spells[i]) { + page["PRIEST"] << i; + } } - for(int i = 0; i < equip.size(); i++) - if(equip[i]) - file << "EQUIP " << i << '\n'; - for(int i = 0; i < 62; i++) - if(mage_spells[i]) - file << "MAGE " << i << '\n'; - for(int i = 0; i < 62; i++) - if(priest_spells[i]) - file << "PRIEST " << i << '\n'; auto traits = this->traits; for(int i = 0; i < 62; i++) { eTrait trait = eTrait(i); - if(traits[trait]) - file << "TRAIT " << i << '\n'; - } - file << "ICON " << which_graphic << '\n'; - file << "RACE " << race << '\n'; - file << "DIRECTION " << direction << '\n'; - if(weap_poisoned) - file << "POISON " << weap_poisoned.slot << '\n'; - file << '\f'; - for(int i = 0; i < items.size(); i++) - if(items[i].variety != eItemType::NO_ITEM){ - file << "ITEM " << i << '\n'; - items[i].writeTo(file); - file << '\f'; + if(traits[trait]) { + page["TRAIT"] << i; } + } + page["ICON"] << which_graphic; + page["RACE"] << race; + page["DIRECTION"] << direction; + if(weap_poisoned) { + page["POISON"] << weap_poisoned.slot; + } + for(int i = 0; i < items.size(); i++) { + if(items[i].variety != eItemType::NO_ITEM) { + auto& item_page = file.add(); + item_page["ITEM"] << i; + items[i].writeTo(item_page); + } + } } -void cPlayer::readFrom(std::istream& file){ - std::istringstream bin, sin; - std::string cur; - getline(file, cur, '\f'); - bin.str(cur); - - // Clear some data that is not always present - equip.reset(); - mage_spells.reset(); - priest_spells.reset(); - weap_poisoned.clear(); - status.clear(); - traits.clear(); - skills.clear(); - cur_sp = max_sp = ap = 0; - - while(getline(bin, cur)) { // continue as long as no error, such as eof, occurs - sin.str(cur); - sin >> cur; - if(cur == "STATUS"){ - eStatus i; - sin >> i; - if(i == eStatus::MAIN) sin >> main_status; - else sin >> status[i]; - }else if(cur == "NAME") - name = read_maybe_quoted_string(sin); - else if(cur == "SKILL"){ - eSkill skill; - sin >> skill; - switch(skill) { - case eSkill::MAX_HP: - sin >> max_health; - break; - case eSkill::MAX_SP: - sin >> max_sp; - break; - case eSkill::CUR_HP: - sin >> cur_health; - break; - case eSkill::CUR_SP: - sin >> cur_sp; - break; - case eSkill::CUR_XP: - sin >> experience; - break; - case eSkill::CUR_SKILL: - break; - case eSkill::CUR_LEVEL: - sin >> level; - break; - default: - sin >> skills[skill]; +void cPlayer::readFrom(const cTagFile& file) { + for(const auto& page : file) { + if(page.index() == 0) { + status.clear(); + page["STATUS"] >> eStatus::MAIN >> main_status; + page["STATUS"].extractSparse(status); + status.erase(eStatus::MAIN); + + cur_sp = max_sp = ap = 0; + page["NAME"] >> name; + page["UID"] >> unique_id; + page["HEALTH"] >> cur_health; + page["MANA"] >> cur_sp; + page["EXPERIENCE"] >> experience; + page["SKILLPTS"] >> skill_pts; + page["LEVEL"] >> level; + page["ICON"] >> which_graphic; + page["DIRECTION"] >> direction; + page["RACE"] >> race; + + skills.clear(); + page["SKILL"].extractSparse(skills); + max_health = skills[eSkill::MAX_HP]; + max_sp = skills[eSkill::MAX_SP]; + skills.erase(eSkill::MAX_HP); + skills.erase(eSkill::MAX_SP); + skills.erase(eSkill::CUR_HP); + skills.erase(eSkill::CUR_SP); + skills.erase(eSkill::CUR_XP); + skills.erase(eSkill::CUR_LEVEL); + skills.erase(eSkill::CUR_SKILL); + skills.erase(eSkill::INVALID); + + + equip.reset(); + for(size_t n = 0; n < page["EQUIP"].size(); n++) { + size_t i; + page["EQUIP"] >> i; + equip[i] = true; } - }else if(cur == "HEALTH") - sin >> cur_health; - else if(cur == "MANA") - sin >> cur_sp; - else if(cur == "EXPERIENCE") - sin >> experience; - else if(cur == "SKILLPTS") - sin >> skill_pts; - else if(cur == "LEVEL") - sin >> level; - else if(cur == "STATUS"){ - eStatus i; - sin >> i; - sin >> status[i]; - } else if(cur == "UID") { - sin >> unique_id; - if(party) + + mage_spells.reset(); + for(size_t n = 0; n < page["MAGE"].size(); n++) { + size_t i; + page["MAGE"] >> i; + mage_spells[i] = true; + } + + priest_spells.reset(); + for(size_t n = 0; n < page["PRIEST"].size(); n++) { + size_t i; + page["PRIEST"] >> i; + priest_spells[i] = true; + } + + traits.clear(); + for(size_t n = 0; n < page["TRAIT"].size(); n++) { + eTrait trait; + page["TRAIT"] >> trait; + traits[trait] = true; + } + + weap_poisoned.clear(); + if(page.contains("POISON")) { + page["POISON"] >> weap_poisoned.slot; + } + + if(party) { + // TODO: This probably belongs somewhere other than here party->next_pc_id = max(unique_id + 1, party->next_pc_id); - }else if(cur == "EQUIP"){ - int i; - sin >> i; - equip[i] = true; - }else if(cur == "MAGE"){ - int i; - sin >> i; - mage_spells[i] = true; - }else if(cur == "PRIEST"){ - int i; - sin >> i; - priest_spells[i] = true; - }else if(cur == "TRAIT"){ - eTrait trait; - sin >> trait; - traits[trait] = true; - }else if(cur == "ICON") - sin >> which_graphic; - else if(cur == "DIRECTION") - sin >> direction; - else if(cur == "RACE") - sin >> race; - else if(cur == "POISON") - sin >> weap_poisoned.slot; - sin.clear(); - } - bin.clear(); - while(getline(file, cur, '\f')) { - bin.str(cur); - bin >> cur; - if(cur == "ITEM") { - int i; - bin >> i; - items[i].readFrom(bin); + } + } else if(page.getFirstKey() == "ITEM") { + size_t i; + page["ITEM"] >> i; + if(i >= items.size()) continue; + items[i].readFrom(page); } - bin.clear(); } } diff --git a/src/universe/pc.hpp b/src/universe/pc.hpp index 59ea7cc8..8d65286e 100644 --- a/src/universe/pc.hpp +++ b/src/universe/pc.hpp @@ -43,6 +43,7 @@ enum { class cParty; class cPlayer; +class cTagFile; struct cInvenSlot { unsigned int slot; @@ -179,8 +180,8 @@ public: cPlayer(no_party_t, const cPlayer& other); cPlayer(cParty& party, const cPlayer& other); short get_tnl(); - void writeTo(std::ostream& file) const; - void readFrom(std::istream& file); + void writeTo(cTagFile& file) const; + void readFrom(const cTagFile& file); virtual ~cPlayer() = default; // Copy-and-swap void swap(cPlayer& other); diff --git a/src/universe/population.cpp b/src/universe/population.cpp index 7017c6d8..5e8683da 100644 --- a/src/universe/population.cpp +++ b/src/universe/population.cpp @@ -72,8 +72,3 @@ void cPopulation::swap(cPopulation& other) { std::swap(which_town, other.which_town); std::swap(hostile, other.hostile); } - -void cPopulation::readFrom(std::istream& in, size_t n) { - if(n >= dudes.size()) dudes.resize(n + 1); - dudes[n].readFrom(in); -} diff --git a/src/universe/population.hpp b/src/universe/population.hpp index 1dabe32a..18fbbd8b 100644 --- a/src/universe/population.hpp +++ b/src/universe/population.hpp @@ -27,7 +27,6 @@ public: void import_legacy(legacy::creature_list_type old); void init(size_t n); void assign(size_t n, const cTownperson& other, const cMonster& base, bool easy, int difficulty_adjust); - void readFrom(std::istream& in, size_t n); size_t size() const {return dudes.size();} void clear() {dudes.clear();} cCreature& operator[](size_t n); diff --git a/src/universe/universe.cpp b/src/universe/universe.cpp index feab380a..dab9681d 100644 --- a/src/universe/universe.cpp +++ b/src/universe/universe.cpp @@ -18,6 +18,7 @@ #include "oldstructs.hpp" #include "mathutil.hpp" #include "fileio/fileio.hpp" +#include "fileio/tagfile.hpp" #include "gfx/gfxsheets.hpp" void cCurOut::import_legacy(legacy::out_info_type& old){ @@ -795,79 +796,68 @@ void cCurOut::readFrom(std::istream& file) { readArray(file, out_e, 96, 96); } -void cCurTown::writeTo(std::ostream& file) const { - file << "TOWN " << univ.party.town_num << '\n'; - file << "DIFFICULTY " << difficulty << '\n'; - if(monst.hostile) file << "HOSTILE" << '\n'; - file << "AT " << univ.party.town_loc.x << ' ' << univ.party.town_loc.y << '\n'; - file << '\f'; - for(size_t i = 0; i < items.size(); i++) - if(items[i].variety != eItemType::NO_ITEM){ - file << "ITEM " << i << '\n'; - items[i].writeTo(file); - file << '\f'; - } - file << '\f'; - for(int i = 0; i < monst.size(); i++) { - if(monst[i].active > 0) { - file << "CREATURE " << i << '\n'; - monst[i].writeTo(file); - file << '\f'; +void cCurTown::writeTo(cTagFile& file) const { + auto& page = file.add(); + page["TOWN"] << univ.party.town_num; + page["DIFFICULTY"] << difficulty; + if(monst.hostile) page.add("HOSTILE"); + page["AT"] << univ.party.town_loc.x << univ.party.town_loc.y; + for(size_t i = 0; i < items.size(); i++) { + if(items[i].variety != eItemType::NO_ITEM) { + auto& item_page = file.add(); + item_page["ITEM"] << i; + items[i].writeTo(item_page); } } - file << '\f'; - file << "FIELDS\n"; - file << std::hex; - writeArray(file, fields, record()->max_dim, record()->max_dim); - file << std::dec; - file << "TERRAIN\n"; - record()->writeTerrainTo(file); + for(int i = 0; i < monst.size(); i++) { + if(monst[i].active > 0) { + auto& monst_page = file.add(); + monst_page["CREATURE"] << i; + monst[i].writeTo(monst_page); + } + } + auto& fields_page = file.add(); + vector2d> fields_tmp; + fields_tmp.resize(64, 64); + for(size_t x = 0; x < 64; x++) { + for(size_t y = 0; y < 64; y++) { + fields_tmp[x][y] = fields[x][y]; + } + } + fields_page["FIELDS"].encode(fields_tmp); + fields_page["TERRAIN"].encode(record()->terrain); // TODO: Do we need to save special_spot? } -void cCurTown::readFrom(std::istream& file){ - std::istringstream bin, sin; - std::string cur; - getline(file, cur, '\f'); - bin.str(cur); - while(bin){ - getline(bin, cur); - sin.str(cur); - sin >> cur; - if(cur == "TOWN") - sin >> univ.party.town_num; - else if(cur == "DIFFICULTY") - sin >> difficulty; - else if(cur == "HOSTILE") - monst.hostile = true; - else if(cur == "AT") - sin >> univ.party.town_loc.x >> univ.party.town_loc.y; - sin.clear(); - } - bin.clear(); - while(file) { - getline(file, cur, '\f'); - bin.str(cur); - bin >> cur; - if(cur == "FIELDS") { - int num = univ.party.town_num; - bin >> std::hex; - readArray(bin, fields, univ.scenario.towns[num]->max_dim, univ.scenario.towns[num]->max_dim); - bin >> std::dec; - } else if(cur == "ITEM") { - int i; - bin >> i; - if(i >= items.size()) - items.resize(i + 1); - items[i].readFrom(bin); - } else if(cur == "CREATURE") { - int i; - bin >> i; - monst.readFrom(bin, i); +void cCurTown::readFrom(const cTagFile& file){ + for(const auto& page : file) { + if(page.index() == 0) { + page["TOWN"] >> univ.party.town_num; + page["DIFFICULTY"] >> difficulty; + monst.hostile = page.contains("HOSTILE"); + page["AT"] >> univ.party.town_loc.x >> univ.party.town_loc.y; + } else if(page.getFirstKey() == "FIELDS" || page.getFirstKey() == "TERRAIN") { + vector2d> fields_tmp; + page["FIELDS"].extract(fields_tmp); + page["TERRAIN"].extract(record()->terrain); + fields_tmp.resize(64, 64); + for(size_t x = 0; x < 64; x++) { + for(size_t y = 0; y < 64; y++) { + fields[x][y] = fields_tmp[x][y]; + } + } + } else if(page.getFirstKey() == "ITEM") { + size_t i; + page["ITEM"] >> i; + if(i >= items.size()) items.resize(i + 1); + items[i].readFrom(page); + } else if(page.getFirstKey() == "CREATURE") { + size_t i; + page["CREATURE"] >> i; + monst.init(i); + monst[i].readFrom(page); monst[i].active = true; - } else if(cur == "TERRAIN") - univ.scenario.towns[univ.party.town_num]->readTerrainFrom(bin); - bin.clear(); + } } } diff --git a/src/universe/universe.hpp b/src/universe/universe.hpp index 15de3941..77bdbb18 100644 --- a/src/universe/universe.hpp +++ b/src/universe/universe.hpp @@ -22,6 +22,8 @@ #include "scenario/scenario.hpp" #include "dialogxml/widgets/pictypes.hpp" +class cTagFile; + namespace legacy { struct out_info_type; struct current_town_type; @@ -115,8 +117,8 @@ public: bool set_force_cage(short x, short y, bool b); bool set_road(short x, short y, bool b); bool is_impassable(short x, short y); - void writeTo(std::ostream& file) const; - void readFrom(std::istream& file); + void writeTo(cTagFile& file) const; + void readFrom(const cTagFile& file); ~cCurTown(); // It's not directly copyable due to the cUniverse reference, which must always point to the cUniverse that contains it. diff --git a/test/pc_read.cpp b/test/pc_read.cpp index 6576a49c..34c197f9 100644 --- a/test/pc_read.cpp +++ b/test/pc_read.cpp @@ -10,12 +10,14 @@ #include "catch.hpp" #include "universe/pc.hpp" #include "universe/party.hpp" +#include "fileio/tagfile.hpp" using namespace std; TEST_CASE("Loading player character from file") { ifstream fin; fin.exceptions(ios::badbit); + cTagFile file; cPlayer pc(no_party); // Fill in some junk data pc.cur_sp = 27; @@ -30,7 +32,8 @@ TEST_CASE("Loading player character from file") { SECTION("Basic Info") { fin.open("files/player/basic.txt"); - pc.readFrom(fin); + file.readFrom(fin); + pc.readFrom(file); CHECK(pc.unique_id == 3); CHECK(pc.main_status == eMainStatus::ALIVE); CHECK(pc.name == "Freddy O'Hara"); @@ -58,7 +61,8 @@ TEST_CASE("Loading player character from file") { } SECTION("Skills") { fin.open("files/player/skills.txt"); - pc.readFrom(fin); + file.readFrom(fin); + pc.readFrom(file); CHECK(pc.skills.size() == 8); CHECK(pc.skills[eSkill::STRENGTH] == 5); CHECK(pc.skills[eSkill::DEXTERITY] == 6); @@ -72,7 +76,8 @@ TEST_CASE("Loading player character from file") { } SECTION("Spells") { fin.open("files/player/spells.txt"); - pc.readFrom(fin); + file.readFrom(fin); + pc.readFrom(file); // This has bits 3, 7, and 20 set CHECK(pc.mage_spells == 0x100088); // This has bits 2, 8, and 60 set