Complete implementation of saving new-format scenarios and write version-check and loading stub for new-format scenarios

This commit is contained in:
2015-02-04 19:43:12 -05:00
parent 233a19175d
commit 8d4cea7d0b
8 changed files with 376 additions and 254 deletions

View File

@@ -34,6 +34,7 @@
<xs:element name="personality">
<xs:complexType>
<xs:sequence>
<xs:element name='title' type='xs:string'/>
<xs:element name="look" type="xs:string"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="job" type="xs:string"/>
@@ -46,250 +47,9 @@
<xs:complexType>
<xs:sequence>
<xs:element name="keyword" minOccurs="1" maxOccurs="2" type="xs:string"/>
<xs:choice minOccurs="1" maxOccurs="1">
<xs:element name="talk">
<xs:complexType>
<xs:sequence>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="if-flag">
<xs:complexType>
<xs:sequence>
<xs:element name="sdf" type="sdf"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="record">
<xs:complexType>
<xs:sequence>
<xs:element name="sdf" type="sdf"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
<xs:attribute name="value" type="xs:integer" default="1"/>
</xs:complexType>
</xs:element>
<xs:element name="inn">
<xs:complexType>
<xs:sequence>
<xs:element name="cost" type="xs:integer"/>
<xs:element name="quality">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<!-- Map location, not SDF, but same data structure -->
<xs:element name="bed" type="sdf"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="if-day">
<xs:complexType>
<xs:sequence>
<xs:element name="day" type="xs:integer"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="if-event">
<xs:complexType>
<xs:sequence>
<xs:element name="event" type="xs:integer"/>
<xs:element name="day" type="xs:integer"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="if-town">
<xs:complexType>
<xs:sequence>
<xs:element name="town" type="xs:integer"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="shop">
<xs:complexType>
<xs:sequence>
<xs:element name="cost" type="costAdjust"/>
<xs:element name="first" type="xs:integer"/>
<xs:element name="count" type="xs:integer"/>
<xs:element name="title" type="xs:string"/>
</xs:sequence>
<xs:attribute name="type" use="required">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="items"/>
<xs:enumeration value="mage"/>
<xs:enumeration value="priest"/>
<xs:enumeration value="recipe"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="train">
<xs:complexType/>
</xs:element>
<xs:element name="heal">
<xs:complexType>
<xs:sequence>
<xs:element name="cost" type="costAdjust"/>
<xs:element name="title" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="sell">
<xs:complexType>
<xs:sequence>
<!-- Cost currently unused -->
<xs:element name="cost" type="costAdjust"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
<xs:attribute name="type" use="required">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="weapons"/>
<xs:enumeration value="armour"/>
<xs:enumeration value="armor"/>
<xs:enumeration value="any"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="identify">
<xs:complexType>
<xs:sequence>
<xs:element name="cost" type="xs:integer"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="enchant">
<xs:complexType>
<xs:sequence>
<!-- Cost currently unused -->
<xs:element name="cost" type="costAdjust"/>
<xs:element name="type">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="+1"/>
<xs:enumeration value="+2"/>
<xs:enumeration value="+3"/>
<xs:enumeration value="+5"/>
<xs:enumeration value="shoot-flame"/>
<xs:enumeration value="flaming"/>
<xs:enumeration value="blessed"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="buy">
<xs:complexType>
<xs:sequence>
<xs:element name="cost" type="xs:integer"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="buy-record">
<xs:complexType>
<xs:sequence>
<xs:element name="cost" type="xs:integer"/>
<xs:element name="sdf" type="sdf"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="vehicle">
<xs:complexType>
<xs:sequence>
<xs:element name="cost" type="xs:integer"/>
<xs:element name="first" type="xs:integer"/>
<xs:element name="count" type="xs:integer"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
<xs:attribute name="type">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="horse"/>
<xs:enumeration value="boat"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="special">
<xs:complexType>
<xs:sequence>
<xs:element name="item" type="xs:integer"/>
<xs:element name="cost" type="xs:integer"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="junk">
<xs:complexType>
<xs:sequence>
<xs:element name="cost" type="costAdjust"/>
<xs:element name="list">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="4"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="title" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="reveal-town">
<xs:complexType>
<xs:sequence>
<xs:element name="cost" type="xs:integer"/>
<xs:element name="town" type="xs:integer"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="call">
<xs:complexType>
<xs:sequence>
<xs:element name="special" type="xs:integer"/>
<xs:element ref="text" minOccurs="1" maxOccurs="2"/>
</xs:sequence>
<xs:attribute name="type" use="required">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="local"/>
<xs:enumeration value="global"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:choice>
<xs:element name="end" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="force"/>
<xs:enumeration value="hostile"/>
<xs:enumeration value="crime"/>
<xs:enumeration value="die"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="type" type="xs:integer"/>
<xs:element name="param" minOccurs="0" maxOccurs="4" type="xs:integer"/>
<xs:element name="text" minOccurs="1" maxOccurs="2" type="xs:string"/>
</xs:sequence>
<xs:attribute name="for" type="xs:integer" use="required"/>
</xs:complexType><!--

View File

@@ -138,6 +138,19 @@
<name>Sample Quest</name>
<description>Your mission, if you choose to accept it, is...!!??</description>
</quest>
<!-- And shops -->
<shop>
<name>A Few Spells</name>
<type>live</type>
<prompt>spell</prompt>
<face>43</face>
<entries>
<mage-spell>30</mage-spell>
<mage-spell>31</mage-spell>
<priest-spell>30</priest-spell>
<priest-spell>31</priest-spell>
</entries>
</shop>
</game>
<editor>

View File

@@ -6,6 +6,15 @@
<xs:enumeration value="false"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name='shop-amount'>
<xs:union memberTypes='xs:integer'>
<xs:simpleType>
<xs:restriction base='xs:string'>
<xs:enumeration value='infinite'/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
<xs:attributeGroup name="rect">
<xs:attribute name="top" use="required" type="xs:integer"/>
<xs:attribute name="left" use="required" type="xs:integer"/>
@@ -84,6 +93,88 @@
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name='entries'>
<xs:complexType>
<xs:choice minOccurs='0' maxOccurs='30'>
<xs:element name='item'>
<xs:complexType><xs:simpleContent>
<xs:extension base='xs:integer'>
<xs:attribute name='quantity' type='shop-amount' default='0'/>
<xs:attribute name='chance' type='xs:integer' default='100'/>
</xs:extension>
</xs:simpleContent></xs:complexType>
</xs:element>
<xs:element name='mage-spell'>
<xs:simpleType>
<xs:restriction base='xs:integer'>
<xs:minInclusive value='0'/>
<xs:maxInclusive value='61'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='priest-spell'>
<xs:simpleType>
<xs:restriction base='xs:integer'>
<xs:minInclusive value='0'/>
<xs:maxInclusive value='61'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='recipe'>
<xs:simpleType>
<xs:restriction base='xs:integer'>
<xs:minInclusive value='0'/>
<xs:maxInclusive value='19'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='skill'>
<xs:simpleType>
<xs:restriction base='xs:integer'>
<xs:minInclusive value='0'/>
<xs:maxInclusive value='20'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='treasure'>
<xs:simpleType>
<xs:restriction base='xs:integer'>
<xs:minInclusive value='0'/>
<xs:maxInclusive value='4'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='class'>
<xs:simpleType>
<xs:restriction base='xs:integer'>
<xs:minInclusive value='1'/>
<xs:maxInclusive value='100'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='heal'>
<xs:simpleType>
<xs:restriction base='xs:integer'>
<xs:minInclusive value='0'/>
<xs:maxInclusive value='9'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='special'>
<xs:complexType>
<xs:all>
<xs:element name='name' type='xs:string'/>
<xs:element name='description' type='xs:string'/>
<xs:element name='node' type='xs:integer'/>
<xs:element name='quantity' type='shop-amount'/>
<xs:element name='cost' type='xs:integer'/>
<xs:element name='icon' type='xs:integer'/>
</xs:all>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="game">
<xs:complexType>
<xs:sequence>
@@ -136,7 +227,7 @@
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="quest" maxOccurs="unbounded">
<xs:element name="quest" minOccurs='0' maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name='deadline' minOccurs='0'>
@@ -162,6 +253,37 @@
<xs:attribute name="start-with" type="bool"/>
</xs:complexType>
</xs:element>
<xs:element name="shop" minOccurs='0' maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name='name' type='xs:string'/>
<xs:element name='type'>
<xs:simpleType>
<xs:restriction base='xs:string'>
<xs:enumeration value='live'/>
<xs:enumeration value='dead'/>
<xs:enumeration value='rand'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='prompt'>
<xs:simpleType>
<xs:restriction base='xs:string'>
<xs:enumeration value='shop'/>
<xs:enumeration value='heal'/>
<xs:enumeration value='mage'/>
<xs:enumeration value='priest'/>
<xs:enumeration value='spell'/>
<xs:enumeration value='alch'/>
<xs:enumeration value='train'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='face' type='xs:integer'/>
<xs:element ref='entries'/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="timer" minOccurs="0" maxOccurs="20">
<xs:complexType>
<xs:simpleContent>

View File

@@ -8,6 +8,7 @@
#include "shop.hpp"
#include <numeric>
#include <iostream>
#include "mathutil.hpp"
#include "graphtool.hpp" // for get_str
@@ -305,3 +306,69 @@ int cShopItem::getCost(int adj) {
cost /= 10;
return cost;
}
std::istream& operator>>(std::istream& in, eShopType& type) {
std::string name;
in >> name;
if(name == "live") type = eShopType::NORMAL;
else if(name == "dead") type = eShopType::ALLOW_DEAD;
else if(name == "rand") type = eShopType::RANDOM;
else in.setstate(std::ios_base::failbit);
return in;
}
std::ostream& operator<<(std::ostream& out, eShopType type) {
switch(type) {
case eShopType::NORMAL:
out << "live";
break;
case eShopType::ALLOW_DEAD:
out << "dead";
break;
case eShopType::RANDOM:
out << "rand";
break;
}
return out;
}
std::istream& operator>>(std::istream& in, eShopPrompt& type) {
std::string name;
in >> name;
if(name == "shop") type = eShopPrompt::SHOPPING;
else if(name == "heal") type = eShopPrompt::HEALING;
else if(name == "mage") type = eShopPrompt::MAGE;
else if(name == "priest") type = eShopPrompt::PRIEST;
else if(name == "spell") type = eShopPrompt::SPELLS;
else if(name == "alch") type = eShopPrompt::ALCHEMY;
else if(name == "train") type = eShopPrompt::TRAINING;
else in.setstate(std::ios_base::failbit);
return in;
}
std::ostream& operator<<(std::ostream& out, eShopPrompt type) {
switch(type) {
case eShopPrompt::SHOPPING:
out << "shop";
break;
case eShopPrompt::HEALING:
out << "heal";
break;
case eShopPrompt::MAGE:
out << "mage";
break;
case eShopPrompt::PRIEST:
out << "priest";
break;
case eShopPrompt::SPELLS:
out << "spell";
break;
case eShopPrompt::ALCHEMY:
out << "alch";
break;
case eShopPrompt::TRAINING:
out << "train";
break;
}
return out;
}

View File

@@ -11,6 +11,7 @@
#include <array>
#include <string>
#include <iosfwd>
#include "item.h"
#include "pictypes.hpp" // for pic_num_t
@@ -89,4 +90,9 @@ public:
void clear();
};
std::istream& operator>>(std::istream& in, eShopType& type);
std::ostream& operator<<(std::ostream& out, eShopType type);
std::istream& operator>>(std::istream& in, eShopPrompt& type);
std::ostream& operator<<(std::ostream& out, eShopPrompt type);
#endif

View File

@@ -47,6 +47,10 @@ template<> inline void ticpp::Printer::PushAttribute(std::string attrName, bool
PushAttribute(attrName, attrVal ? "true" : "false");
}
template<> inline void ticpp::Printer::PushElement(std::string attrName, bool attrVal) {
PushAttribute(attrName, attrVal ? "true" : "false");
}
template<> inline void ticpp::Printer::PushText(bool textVal) {
PushText(textVal ? "true" : "false");
}

View File

@@ -217,6 +217,72 @@ static void writeScenarioToXml(ticpp::Printer&& data) {
data.PushElement("description", quest.descr);
data.CloseElement("quest");
}
for(size_t i = 0; i < scenario.shops.size(); i++) {
cShop& shop = scenario.shops[i];
data.OpenElement("shop");
data.PushElement("name", shop.getName());
data.PushElement("type", shop.getType());
data.PushElement("prompt", shop.getPrompt());
data.PushElement("face", shop.getFace());
size_t num_entries = shop.size();
data.OpenElement("entries");
for(size_t j = 0; j < num_entries; j++) {
cShopItem entry = shop.getItem(j);
int quantity = entry.quantity, chance = 100;
switch(entry.type) {
case eShopItemType::EMPTY: break;
case eShopItemType::OPTIONAL:
quantity %= 1000;
chance = entry.quantity / 1000;
case eShopItemType::ITEM:
data.OpenElement("item");
if(quantity == 0)
data.PushAttribute("quantity", "infinite");
else data.PushAttribute("quantity", quantity);
if(chance < 100)
data.PushAttribute("chance", chance);
data.PushText(entry.index);
data.CloseElement("item");
break;
case eShopItemType::CALL_SPECIAL:
data.OpenElement("special");
data.PushElement("name", entry.item.full_name);
data.PushElement("description", entry.item.desc);
data.PushElement("node", entry.item.item_level);
data.PushElement("quantity", entry.quantity);
data.PushElement("cost", entry.item.value);
data.PushElement("icon", entry.item.graphic_num);
data.CloseElement("special");
break;
case eShopItemType::MAGE_SPELL:
data.PushElement("mage-spell", entry.index);
break;
case eShopItemType::PRIEST_SPELL:
data.PushElement("priest-spell", entry.index);
break;
case eShopItemType::ALCHEMY:
data.PushElement("recipe", entry.index);
break;
case eShopItemType::SKILL:
data.PushElement("skill", entry.index);
break;
case eShopItemType::TREASURE:
data.PushElement("treasure", entry.index);
break;
case eShopItemType::CLASS:
data.PushElement("class", entry.index);
break;
case eShopItemType::CURE_ACID: case eShopItemType::CURE_DISEASE: case eShopItemType::CURE_DUMBFOUNDING:
case eShopItemType::CURE_PARALYSIS: case eShopItemType::CURE_POISON: case eShopItemType::DESTONE:
case eShopItemType::HEAL_WOUNDS: case eShopItemType::RAISE_DEAD: case eShopItemType::REMOVE_CURSE:
case eShopItemType::RESURRECT:
data.PushElement("heal", int(entry.type) - int(eShopItemType::HEAL_WOUNDS));
break;
}
}
data.CloseElement("entries");
data.CloseElement("shop");
}
for(int i = 0; i < 20; i++) {
if(scenario.scenario_timer_times[i] > 0) {
data.OpenElement("timer");
@@ -676,7 +742,51 @@ static void writeTownToXml(ticpp::Printer&& data, cTown& town) {
data.CloseElement("town");
}
map_data buildOutMapData(location which) {
static void writeDialogueToXml(ticpp::Printer&& data, cSpeech& talk) {
data.OpenElement("dialogue");
data.PushAttribute("boes", scenario.format_ed_version());
for(size_t i = 0; i < 10; i++) {
cPersonality& who = talk.people[i];
data.OpenElement("personality");
data.PushAttribute("id", i);
data.PushElement("title", who.title);
data.PushElement("look", who.look);
data.PushElement("name", who.name);
data.PushElement("job", who.job);
data.CloseElement("personality");
}
for(size_t i = 0; i < 60; i++) {
cSpeech::cNode& node = talk.talk_nodes[i];
if(node.personality == -1) continue;
// TODO: Is it safe to assume the two links run together like this?
if(std::string(node.link1, 8) == "xxxxxxxx")
continue;
data.OpenElement("node");
data.PushAttribute("for", node.personality);
if(std::string(node.link1, 4) != "xxxx")
data.PushElement("keyword", std::string(node.link1, 4));
if(std::string(node.link2, 4) != "xxxx")
data.PushElement("keyword", std::string(node.link2, 4));
data.PushElement("type", int(node.type));
if(node.extras[0] >= 0 || node.extras[1] >= 0 || node.extras[2] >= 0 || node.extras[3] >= 0)
data.PushElement("param", node.extras[0]);
if(node.extras[1] >= 0 || node.extras[2] >= 0 || node.extras[3] >= 0)
data.PushElement("param", node.extras[1]);
if(node.extras[2] >= 0 || node.extras[3] >= 0)
data.PushElement("param", node.extras[2]);
if(node.extras[3] >= 0)
data.PushElement("param", node.extras[3]);
if(!node.str1.empty())
data.PushElement("text", node.str1);
else data.PushElement("text");
if(!node.str2.empty())
data.PushElement("text", node.str2);
data.CloseElement("node");
}
data.CloseElement("dialogue");
}
static map_data buildOutMapData(location which) {
cOutdoors& sector = *scenario.outdoors[which.x][which.y];
map_data terrain;
for(size_t x = 0; x < 48; x++) {
@@ -718,7 +828,7 @@ map_data buildOutMapData(location which) {
return terrain;
}
map_data buildTownMapData(size_t which) {
static map_data buildTownMapData(size_t which) {
cTown& town = *scenario.towns[which];
map_data terrain;
for(size_t x = 0; x < town.max_dim(); x++) {
@@ -774,11 +884,14 @@ void save_scenario(fs::path toFile) {
scenario.format.prog_make_ver[0] = 2;
scenario.format.prog_make_ver[1] = 0;
scenario.format.prog_make_ver[2] = 0;
scenario.format.flag1 = 'O'; scenario.format.flag2 = 'B';
scenario.format.flag3 = 'O'; scenario.format.flag4 = 'E';
// TODO: This is just a skeletal outline of what needs to be done to save the scenario
tarball scen_file;
{
// First, write out the scenario header data. This is in a binary format identical to older scenarios.
std::ostream& header = scen_file.newFile("scenario/header.exs");
header.write(reinterpret_cast<char*>(&scenario.format), sizeof(scenario_header_flags));
// Next, the bulk scenario data.
std::ostream& scen_data = scen_file.newFile("scenario/scenario.xml");
@@ -835,16 +948,18 @@ void save_scenario(fs::path toFile) {
writeSpecialNodes(town_spec, scenario.towns[i]->specials);
// Don't forget the dialogue nodes.
std::ostream& town_talk = scen_file.newFile("scenario/towns/" + file_basename + "talk.xml");
std::ostream& town_talk = scen_file.newFile("scenario/towns/talk" + std::to_string(i) + ".xml");
writeDialogueToXml(ticpp::Printer("talk.xml", town_talk), scenario.towns[i]->talking);
}
giveError("Sorry, scenario saving is currently disabled.");
return;
// Make sure it has the proper file extension
std::string fname = toFile.filename().string();
size_t dot = fname.find_last_of('.');
if(dot == std::string::npos || fname.substr(dot) != ".boes")
fname += ".boes";
if(dot == std::string::npos || fname.substr(dot) != ".boes") {
if(fname.substr(dot) == ".exs")
fname.replace(dot,4,".boes");
else fname += ".boes";
}
toFile = toFile.parent_path()/fname;
// Now write to zip file.

View File

@@ -18,6 +18,8 @@
#include "map_parse.hpp"
#include "graphtool.hpp"
#include "mathutil.hpp"
#include "gzstream.h"
#include "tarball.hpp"
#include "porting.hpp"
#include "restypes.hpp"
@@ -35,14 +37,21 @@ static bool load_scenario_v1(fs::path file_to_load, cScenario& scenario);
static bool load_outdoors_v1(fs::path scen_file, location which_out,cOutdoors& the_out, legacy::scenario_data_type& scenario);
static bool load_town_v1(fs::path scen_file,short which_town,cTown& the_town,legacy::scenario_data_type& scenario,std::vector<shop_info_t>& shops);
// Load new scenarios
static bool load_scenario_v2(fs::path file_to_load, cScenario& scenario);
static bool load_outdoors(fs::path out_base, location which_out, cOutdoors& the_out);
static bool load_town(fs::path town_base, short which_town, cTown*& the_town);
static bool load_town_talk(fs::path town_base, short which_town, cSpeech& the_talk);
bool load_scenario(fs::path file_to_load, cScenario& scenario) {
scenario = cScenario();
// TODO: Implement checking to determine whether it's old or new
return load_scenario_v1(file_to_load, scenario);
std::string fname = file_to_load.filename().string();
size_t dot = fname.find_last_of('.');
if(fname.substr(dot) == ".boes")
return load_scenario_v2(file_to_load, scenario);
else if(fname.substr(dot) == ".exs")
return load_scenario_v1(file_to_load, scenario);
giveError("That is not a Blades of Exile scenario.");
return false;
}
template<typename Container> static void port_shop_spec_node(cSpecial& spec, std::vector<shop_info_t>& shops, Container strs) {
@@ -92,6 +101,9 @@ bool load_scenario_v1(fs::path file_to_load, cScenario& scenario){
(scenario.format.flag3 == 60) && (scenario.format.flag4 == 80)) {
cur_scen_is_mac = false;
file_ok = true;
} else if(scenario.format.flag1 == 'O' && scenario.format.flag2 == 'B' && scenario.format.flag3 == 'O' && scenario.format.flag4 == 'E') {
// This means we're looking at the scenario header file of an unpacked new-format scenario.
return load_scenario_v2(file_to_load.parent_path(), scenario);
}
if(!file_ok) {
fclose(file_id);
@@ -235,6 +247,29 @@ bool load_scenario_v1(fs::path file_to_load, cScenario& scenario){
return true;
}
bool load_scenario_v2(fs::path file_to_load, cScenario& scenario) {
// First determine whether we're dealing with a packed or unpacked scenario.
bool is_packed;
tarball pack;
std::ifstream fin;
if(fs::is_directory(file_to_load)) { // Unpacked
is_packed = false;
} else { // Packed
igzstream gzin(file_to_load.string().c_str());
pack.readFrom(gzin);
}
auto getFile = [&](std::string relpath) -> std::istream& {
if(is_packed) return pack.getFile(relpath);
if(fin.is_open()) fin.close();
fin.open((file_to_load/relpath).string().c_str());
// TODO: Suppress the warning about returning reference to local
return fin;
};
// From here on, we don't have to care about whether it's packed or unpacked.
return false;
}
static long get_town_offset(short which_town, legacy::scenario_data_type& scenario){
int i,j;
long len_to_jump,store;