diff --git a/rsrc/boes/town.xsd b/rsrc/boes/town.xsd
index 5117096c..ae801923 100644
--- a/rsrc/boes/town.xsd
+++ b/rsrc/boes/town.xsd
@@ -38,13 +38,23 @@
+
-
+
+
+
+
+
+
+
+
+
+
@@ -81,7 +91,7 @@
-
+
@@ -91,11 +101,12 @@
-
-
-
+
+
+
+
@@ -127,11 +138,11 @@
-
+
-
-
+
+
@@ -139,28 +150,25 @@
-
+
+
-
+
-
+
-
-
-
-
-
+
@@ -169,14 +177,28 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/rsrc/boes/town/t0.xml b/rsrc/boes/town/t0.xml
index f010714b..f7bfdf46 100644
--- a/rsrc/boes/town/t0.xml
+++ b/rsrc/boes/town/t0.xml
@@ -8,7 +8,7 @@
1
- 0
+ lit
12
@@ -28,20 +28,17 @@
This is a sample sign!
-
-
- 128
- -
- 17
+
-
+ 17
true
3
-
- 12
+
+ 12
0
0
1
diff --git a/src/boe.specials.cpp b/src/boe.specials.cpp
index 1bb5d2d1..0d582460 100644
--- a/src/boe.specials.cpp
+++ b/src/boe.specials.cpp
@@ -3222,8 +3222,8 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
break;
}
i = 0;
- for(j = spec.ex1b; j < std::min(spec.ex2b, univ.town->max_dim()); j++)
- for(k = spec.ex1a; k < std::min(spec.ex2a, univ.town->max_dim()); k++) {
+ for(j = spec.ex1b; j < min(spec.ex2b, univ.town->max_dim()); j++)
+ for(k = spec.ex1a; k < min(spec.ex2a, univ.town->max_dim()); k++) {
switch(eFieldType(spec.m1)) {
// These values are not allowed
case SPECIAL_EXPLORED: case SPECIAL_SPOT: case FIELD_DISPEL: case FIELD_SMASH: break;
diff --git a/src/classes/monster.h b/src/classes/monster.h
index e5cd1569..7ac94792 100644
--- a/src/classes/monster.h
+++ b/src/classes/monster.h
@@ -168,7 +168,7 @@ public:
unsigned long id;
mon_num_t number;
short active, attitude;
- unsigned char start_attitude;
+ unsigned int start_attitude;
location start_loc, cur_loc;
unsigned short mobility;
eMonstTime time_flag;
diff --git a/src/classes/regtown.cpp b/src/classes/regtown.cpp
index 79c0ae7e..52faf681 100644
--- a/src/classes/regtown.cpp
+++ b/src/classes/regtown.cpp
@@ -298,38 +298,38 @@ cTinyTown::cTinyTown(cScenario& scenario, bool init_strings) : cTown(scenario, i
init_start();
}
-short cBigTown::max_dim() const {
+size_t cBigTown::max_dim() const {
return 64;
}
-short cMedTown::max_dim() const {
+size_t cMedTown::max_dim() const {
return 48;
}
-short cTinyTown::max_dim() const {
+size_t cTinyTown::max_dim() const {
return 32;
}
-short cBigTown::max_monst() const {
+size_t cBigTown::max_monst() const {
return 60;
}
-short cMedTown::max_monst() const {
+size_t cMedTown::max_monst() const {
return 40;
}
-short cTinyTown::max_monst() const {
+size_t cTinyTown::max_monst() const {
return 30;
}
-short cBigTown::max_items() const {
+size_t cBigTown::max_items() const {
return 64;
}
-short cMedTown::max_items() const {
+size_t cMedTown::max_items() const {
return 64;
}
-short cTinyTown::max_items() const {
+size_t cTinyTown::max_items() const {
return 64;
}
diff --git a/src/classes/regtown.h b/src/classes/regtown.h
index bb0ba110..96cbcc80 100644
--- a/src/classes/regtown.h
+++ b/src/classes/regtown.h
@@ -32,9 +32,9 @@ public:
ter_num_t& terrain(size_t x, size_t y);
cCreature& creatures(size_t i);
unsigned char& lighting(size_t i, size_t r);
- short max_dim() const;
- short max_monst() const;
- short max_items() const;
+ size_t max_dim() const;
+ size_t max_monst() const;
+ size_t max_items() const;
explicit cBigTown(cScenario& scenario, bool init_strings = false);
void writeTerrainTo(std::ostream& file);
@@ -51,9 +51,9 @@ public:
ter_num_t& terrain(size_t x, size_t y);
cCreature& creatures(size_t i);
unsigned char& lighting(size_t i, size_t r);
- short max_dim() const;
- short max_monst() const;
- short max_items() const;
+ size_t max_dim() const;
+ size_t max_monst() const;
+ size_t max_items() const;
explicit cMedTown(cScenario& scenario, bool init_strings = false);
void writeTerrainTo(std::ostream& file);
@@ -70,9 +70,9 @@ public:
ter_num_t& terrain(size_t x, size_t y);
cCreature& creatures(size_t i);
unsigned char& lighting(size_t i, size_t r);
- short max_dim() const;
- short max_monst() const;
- short max_items() const;
+ size_t max_dim() const;
+ size_t max_monst() const;
+ size_t max_items() const;
explicit cTinyTown(cScenario& scenario, bool init_strings = false);
void writeTerrainTo(std::ostream& file);
diff --git a/src/classes/tmpltown.cpp b/src/classes/tmpltown.cpp
index 994ba995..e175a947 100644
--- a/src/classes/tmpltown.cpp
+++ b/src/classes/tmpltown.cpp
@@ -33,14 +33,14 @@ unsigned char& cBigTemplTown::lighting(size_t i, size_t r){
return _lighting[i][r];
}
-short cBigTemplTown::max_dim() const {
+size_t cBigTemplTown::max_dim() const {
return 0; // not sure what they are yet.
}
-short cBigTemplTown::max_monst() const {
+size_t cBigTemplTown::max_monst() const {
return 30;
}
-short cBigTemplTown::max_items() const {
+size_t cBigTemplTown::max_items() const {
return 64;
}
diff --git a/src/classes/tmpltown.h b/src/classes/tmpltown.h
index dbaceec0..12b0e65f 100644
--- a/src/classes/tmpltown.h
+++ b/src/classes/tmpltown.h
@@ -45,9 +45,9 @@ public:
ter_num_t& terrain(size_t x, size_t y);
cCreature& creatures(size_t i);
unsigned char& lighting(size_t i, size_t r);
- short max_dim() const;
- short max_monst() const;
- short max_items() const;
+ size_t max_dim() const;
+ size_t max_monst() const;
+ size_t max_items() const;
void writeTerrainTo(std::ostream& file);
void readTerrainFrom(std::istream& file);
explicit cBigTemplTown(cScenario& scenario, bool init_strings = false);
@@ -62,9 +62,9 @@ public:
ter_num_t& terrain(size_t x, size_t y);
cCreature& creatures(size_t i);
unsigned char& lighting(size_t i, size_t r);
- short max_dim() const;
- short max_monst() const;
- short max_items() const;
+ size_t max_dim() const;
+ size_t max_monst() const;
+ size_t max_items() const;
void writeTerrainTo(std::ostream& file);
void readTerrainFrom(std::istream& file);
explicit cMedTemplTown(cScenario& scenario, bool init_strings = false);
@@ -79,9 +79,9 @@ public:
ter_num_t& terrain(size_t x, size_t y);
cCreature& creatures(size_t i);
unsigned char& lighting(size_t i, size_t r);
- short max_dim() const;
- short max_monst() const;
- short max_items() const;
+ size_t max_dim() const;
+ size_t max_monst() const;
+ size_t max_items() const;
void writeTerrainTo(std::ostream& file);
void readTerrainFrom(std::istream& file);
explicit cTinyTemplTown(cScenario& scenario, bool init_strings = false);
diff --git a/src/classes/town.cpp b/src/classes/town.cpp
index 9059d25c..505a4e62 100644
--- a/src/classes/town.cpp
+++ b/src/classes/town.cpp
@@ -13,6 +13,7 @@
#include "classes.h"
#include "oldstructs.h"
+#include "mathutil.hpp"
void cTown::append(legacy::big_tr_type&, int){}
void cTown::append(legacy::ave_tr_type&, int){}
@@ -234,8 +235,8 @@ void cTown::set_up_lights() {
l.y = j;
rad = scenario.ter_types[this->terrain(i,j)].light_radius;
if(rad > 0) {
- for(where.x = std::max(0,i - rad); where.x < std::min(this->max_dim(),short(i + rad + 1)); where.x++)
- for(where.y = std::max(0,j - rad); where.y < std::min(this->max_dim(),short(j + rad + 1)); where.y++)
+ for(where.x = std::max(0,i - rad); where.x < min(this->max_dim(),short(i + rad + 1)); where.x++)
+ for(where.y = std::max(0,j - rad); where.y < min(this->max_dim(),short(j + rad + 1)); where.y++)
if(!where_lit[where.x][where.y] && dist(where,l) <= rad && can_see(l,where,get_obscurity) < 5)
where_lit[where.x][where.y] = true;
}
@@ -264,3 +265,23 @@ short cTown::light_obscurity(short x,short y) {
return 1;
return 0;
}
+
+std::ostream& operator<< (std::ostream& out, eLighting light) {
+ switch(light) {
+ case LIGHT_NORMAL: out << "lit"; break;
+ case LIGHT_DARK: out << "dark"; break;
+ case LIGHT_DRAINS: out << "drains"; break;
+ case LIGHT_NONE: out << "none"; break;
+ }
+ return out;
+}
+
+std::istream& operator>> (std::istream& in, eLighting& light) {
+ std::string key;
+ if(key == "lit") light = LIGHT_NORMAL;
+ else if(key == "dark") light = LIGHT_DARK;
+ else if(key == "drains") light = LIGHT_DRAINS;
+ else if(key == "none") light = LIGHT_NONE;
+ else in.fail();
+ return in;
+}
diff --git a/src/classes/town.h b/src/classes/town.h
index 4bd41da7..7d21d484 100644
--- a/src/classes/town.h
+++ b/src/classes/town.h
@@ -53,7 +53,7 @@ public:
public:
location loc;
short code,ability;
- unsigned char charges;
+ unsigned int charges;
bool always_there, property, contained;
void append(legacy::preset_item_type old);
@@ -77,13 +77,13 @@ public:
location exit_locs[4];
short exit_specs[4];
rectangle in_town_rect;
- cItem preset_items[64];
+ std::array preset_items;
short max_num_monst;
std::vector preset_fields;
short spec_on_entry,spec_on_entry_if_dead;
short spec_on_hostile;
- short timer_spec_times[8];
- short timer_specs[8];
+ std::array timer_spec_times;
+ std::array timer_specs;
std::array specials;
bool strong_barriers : 1;
bool defy_mapping : 1;
@@ -107,9 +107,9 @@ public:
virtual ter_num_t& terrain(size_t x, size_t y) = 0;
virtual cCreature& creatures(size_t i) = 0;
virtual unsigned char& lighting(size_t i, size_t r) = 0;
- virtual short max_dim() const = 0;
- virtual short max_monst() const = 0;
- virtual short max_items() const = 0;
+ virtual size_t max_dim() const = 0;
+ virtual size_t max_monst() const = 0;
+ virtual size_t max_items() const = 0;
void init_start();
void set_up_lights();
short light_obscurity(short x,short y); // Obscurity function used for calculating lighting
@@ -120,4 +120,7 @@ public:
virtual void readTerrainFrom(std::istream& file) = 0;
};
+std::ostream& operator<< (std::ostream& out, eLighting light);
+std::istream& operator>> (std::istream& in, eLighting& light);
+
#endif
diff --git a/src/dialogxml/xml-parser/tinyprint.cpp b/src/dialogxml/xml-parser/tinyprint.cpp
index b98b20a2..2e89294e 100644
--- a/src/dialogxml/xml-parser/tinyprint.cpp
+++ b/src/dialogxml/xml-parser/tinyprint.cpp
@@ -24,7 +24,7 @@ void Printer::CloseElement(std::string tagName) {
if(openElements.empty())
throw std::out_of_range("No elements left to close!");
if(tagName != openElements.top()->Value())
- throw std::logic_error("Mismatched closing tag!");
+ throw std::logic_error("Mismatched closing tag! (Expected " + openElements.top()->Value() + ")");
Element* top = openElements.top();
openElements.pop();
PushNode(top);
diff --git a/src/scenedit/scen.fileio.cpp b/src/scenedit/scen.fileio.cpp
index 9172019d..08c04a9e 100644
--- a/src/scenedit/scen.fileio.cpp
+++ b/src/scenedit/scen.fileio.cpp
@@ -61,6 +61,15 @@ template<> void ticpp::Printer::PushElement(std::string tagName, location pos) {
CloseElement(tagName);
}
+template<> void ticpp::Printer::PushElement(std::string tagName, rectangle rect) {
+ OpenElement(tagName);
+ PushAttribute("top", rect.top);
+ PushAttribute("left", rect.left);
+ PushAttribute("bottom", rect.bottom);
+ PushAttribute("right", rect.right);
+ CloseElement(tagName);
+}
+
template<> void ticpp::Printer::PushElement(std::string tagName, cMonster::cAttack attack) {
OpenElement(tagName);
PushAttribute("type", attack.type);
@@ -507,6 +516,143 @@ void writeOutdoorsToXml(ticpp::Printer&& data, cOutdoors& sector) {
data.CloseElement("sector");
}
+void writeTownToXml(ticpp::Printer&& data, cTown& town) {
+ static const char directions[] = {'n', 'w', 's', 'e'};
+ data.OpenElement("town");
+ data.PushAttribute("boes", scenario.format_ed_version());
+ data.PushElement("size", town.max_dim());
+ data.PushElement("name", town.town_name);
+ for(size_t i = 0; i < town.comment.size(); i++) {
+ if(!town.comment[i].empty())
+ data.PushElement("comment", town.comment[i]);
+ }
+ data.PushElement("bounds", town.in_town_rect);
+ data.PushElement("difficulty", town.difficulty);
+ data.PushElement("lighting", town.lighting_type);
+ if(town.spec_on_entry >= 0) {
+ data.OpenElement("onenter");
+ data.PushAttribute("condition", "alive");
+ data.PushText(town.spec_on_entry);
+ data.CloseElement("onenter");
+ }
+ if(town.spec_on_entry_if_dead >= 0) {
+ data.OpenElement("onenter");
+ data.PushAttribute("condition", "dead");
+ data.PushText(town.spec_on_entry_if_dead);
+ data.CloseElement("onenter");
+ }
+ for(int i = 0; i < 4; i++) {
+ if(town.exit_locs[i].x >= 0 && town.exit_locs[i].y >= 0) {
+ data.OpenElement("exit");
+ data.PushAttribute("dir", directions[i]);
+ data.PushAttribute("x", town.exit_locs[i].x);
+ data.PushAttribute("y", town.exit_locs[i].y);
+ data.CloseElement("exit");
+ }
+ }
+ for(int i = 0; i < 4; i++) {
+ if(town.exit_specs[i] >= 0) {
+ data.OpenElement("onexit");
+ data.PushAttribute("dir", directions[i]);
+ data.PushText(town.exit_specs[i]);
+ data.CloseElement("onexit");
+ }
+ }
+ if(town.spec_on_hostile >= 0)
+ data.PushElement("onoffend", town.spec_on_hostile);
+ for(size_t i = 0; i < town.timer_spec_times.size() && i < town.timer_specs.size(); i++) {
+ data.OpenElement("timer");
+ data.PushAttribute("freq", town.timer_spec_times[i]);
+ data.PushText(town.timer_specs[i]);
+ data.CloseElement("timer");
+ }
+ data.OpenElement("flags");
+ if(town.town_chop_time > 0) {
+ data.OpenElement("chop");
+ data.PushAttribute("day", town.town_chop_time);
+ data.PushAttribute("event", town.town_chop_key);
+ data.PushAttribute("kills", town.max_num_monst);
+ data.CloseElement("chop");
+ }
+ if(town.strong_barriers)
+ data.PushElement("strong-barriers", true);
+ if(town.defy_mapping)
+ data.PushElement("defy-mapping", true);
+ if(town.defy_scrying)
+ data.PushElement("defy-scrying", true);
+ if(town.is_hidden)
+ data.PushElement("hidden", true);
+ data.CloseElement("flags");
+ for(int i = 0; i < 4; i++) {
+ if(town.wandering[i].isNull()) continue;
+ data.OpenElement("wandering");
+ for(int j = 0; j < 4; j++) {
+ if(j == 0 || town.wandering[i].monst[j] > 0)
+ data.PushElement("monster", town.wandering[i].monst[j]);
+ }
+ data.CloseElement("wandering");
+ }
+ for(size_t i = 0; i < town.preset_items.size(); i++) {
+ data.OpenElement("item");
+ data.PushAttribute("id", i);
+ data.PushElement("type", town.preset_items[i].code);
+ if(town.preset_items[i].ability >= 0)
+ data.PushElement("mod", town.preset_items[i].ability);
+ if(town.preset_items[i].charges > 0)
+ data.PushElement("charges", town.preset_items[i].charges);
+ if(town.preset_items[i].always_there)
+ data.PushElement("always", true);
+ if(town.preset_items[i].property)
+ data.PushElement("property", true);
+ if(town.preset_items[i].contained)
+ data.PushElement("contained", true);
+ data.CloseElement("item");
+ }
+ for(size_t i = 0; i < town.max_monst(); i++) {
+ data.OpenElement("creature");
+ data.PushAttribute("id", i);
+ cCreature& preset = town.creatures(i);
+ data.PushElement("type", preset.number);
+ data.PushElement("attitude", preset.start_attitude);
+ data.PushElement("mobility", preset.mobility);
+ data.PushElement("personality", preset.personality);
+ data.PushElement("face", preset.facial_pic);
+ if(preset.spec1 >= 0 && preset.spec2 >= 0)
+ data.PushElement("sdf", loc(preset.spec1, preset.spec2));
+ if(preset.spec_enc_code > 0)
+ data.PushElement("encounter", preset.spec_enc_code);
+ if(preset.special_on_kill >= 0)
+ data.PushElement("onkill", preset.special_on_kill);
+ if(preset.time_flag != eMonstTime::ALWAYS) {
+ data.OpenElement("time");
+ data.PushAttribute("type", preset.time_flag);
+ data.PushElement("param", preset.monster_time);
+ data.PushElement("param", preset.time_code);
+ data.CloseElement("time");
+ }
+ data.CloseElement("creature");
+ }
+ for(auto& area : town.room_rect) {
+ if(!area.descr.empty() && area.top < area.bottom && area.left < area.right)
+ data.PushElement("description", area);
+ }
+ for(size_t i = 0; i < town.sign_strs.size(); i++) {
+ if(town.sign_strs[i].empty()) continue;
+ data.OpenElement("sign");
+ data.PushAttribute("id", i);
+ data.PushText(town.sign_strs[i]);
+ data.CloseElement("sign");
+ }
+ for(size_t i = 0; i < town.spec_strs.size(); i++) {
+ if(town.spec_strs[i].empty()) continue;
+ data.OpenElement("string");
+ data.PushAttribute("id", i);
+ data.PushText(town.spec_strs[i]);
+ data.CloseElement("string");
+ }
+ data.CloseElement("town");
+}
+
void save_scenario(fs::path toFile) {
// TODO: I'm not certain 1.0.0 is the correct version here?
scenario.format.prog_make_ver[0] = 1;
@@ -561,6 +707,7 @@ void save_scenario(fs::path toFile) {
std::string file_basename = 't' + std::to_string(i);
// First the main data.
std::ostream& town = scen_file.newFile("scenario/towns/" + file_basename + ".xml");
+ writeTownToXml(ticpp::Printer(file_basename + ".xml", town), *scenario.towns[i]);
// Then the map.
std::ostream& town_map = scen_file.newFile("scenario/towns/" + file_basename + ".map");