More read/write test cases for monsters, especially monster abilities

Fixes:
- For radiate abilities, the pattern was not saved and defaulted to single instead of square on load
- For summon abilities, what to summon was not saved and chance was incorrectly saved
This commit is contained in:
2015-09-27 00:58:10 -04:00
parent ab232bb31a
commit 18bb16df9c
46 changed files with 1511 additions and 17 deletions

View File

@@ -40,6 +40,18 @@
<xs:enumeration value="hostile-b"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="spellPattern">
<xs:restriction base="xs:token">
<xs:enumeration value="single"/>
<xs:enumeration value="square"/>
<xs:enumeration value="small-square"/>
<xs:enumeration value="open-square"/>
<xs:enumeration value="radius-2"/>
<xs:enumeration value="radius-3"/>
<xs:enumeration value="plus"/>
<xs:enumeration value="wall"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="permille">
<xs:restriction base="xs:decimal">
<xs:fractionDigits value="1"/>
@@ -182,15 +194,11 @@
<xs:element name="summon">
<xs:complexType>
<xs:all>
<xs:element name="type">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="type"/>
<xs:enumeration value="lvl"/>
<xs:enumeration value="race"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:choice minOccurs="1" maxOccurs="1">
<xs:element name="type" type="xs:integer"/>
<xs:element name="lvl" type="xs:integer"/>
<xs:element name="race" type="species"/>
</xs:choice>
<xs:element name="min" type="xs:integer"/>
<xs:element name="max" type="xs:integer"/>
<xs:element name="duration" type="xs:integer"/>
@@ -239,6 +247,7 @@
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="pattern" type="spellPattern" minOccurs="0"/>
<xs:element name="chance" type="permille"/>
</xs:all>
<xs:attribute name="type" use="required">

View File

@@ -14,6 +14,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/ptr_container/ptr_set.hpp>
#include "simpletypes.hpp"
#include "spell.hpp"
std::string oboeVersionString() {
unsigned short M, m, f;
@@ -646,3 +647,20 @@ std::istream& operator>> (std::istream& in, eContentRating& rating) {
in.setstate(std::ios::failbit);
return in;
}
// MARK: eSpellPat
cEnumLookup spell_pat_strs = {
"single", "square", "small-square", "open-square", "radius-2", "radius-3", "plus", "wall",
};
std::ostream& operator<< (std::ostream& out, eSpellPat pat) {
writeEnum(out, pat, spell_pat_strs, "single");
return out;
}
std::istream& operator>> (std::istream& in, eSpellPat& pat) {
if(!readEnum(in, pat, spell_pat_strs, PAT_SINGLE))
in.setstate(std::ios::failbit);
return in;
}

View File

@@ -170,4 +170,7 @@ std::istream& operator >> (std::istream& in, eMonstTime& e);
std::ostream& operator<< (std::ostream& out, eAttitude node);
std::istream& operator>> (std::istream& in, eAttitude& node);
std::ostream& operator<<(std::ostream& out, const cMonster::cAttack& att);
std::ostream& operator<< (std::ostream& out, eSpellPat pat);
std::istream& operator>> (std::istream& in, eSpellPat& pat);
#endif

View File

@@ -572,17 +572,21 @@ void writeMonstersToXml(ticpp::Printer&& data, cScenario& scenario) {
case eMonstAbilCat::SUMMON:
data.OpenElement("summon");
data.PushAttribute("type", abil);
data.PushElement("type", param.summon.type);
if(param.summon.type == eMonstSummon::SPECIES)
data.PushElement("race", eRace(param.summon.what));
else data.PushElement(boost::lexical_cast<std::string>(param.summon.type), param.summon.what);
data.PushElement("min", param.summon.min);
data.PushElement("max", param.summon.max);
data.PushElement("duration", param.summon.len);
data.PushElement("chance", param.summon.chance);
str << std::fixed << std::setprecision(1) << float(param.summon.chance)/10;
data.PushElement("chance", str.str());
data.CloseElement("summon");
break;
case eMonstAbilCat::RADIATE:
data.OpenElement("radiate");
data.PushAttribute("type", abil);
data.PushElement("type", param.radiate.type);
data.PushElement("pattern", param.radiate.pat);
data.PushElement("chance", param.radiate.chance);
data.CloseElement("radiate");
break;

View File

@@ -1261,14 +1261,12 @@ static void readMonstAbilFromXml(ticpp::Element& data, cMonster& monst) {
} else if(type == "summon") {
if(getMonstAbilCategory(abil_type) != eMonstAbilCat::SUMMON)
throw xBadVal(type, "type", val, data.Row(), data.Column(), fname);
std::set<std::string> reqs = {"type", "min", "max", "duration", "chance"};
std::set<std::string> reqs = {"type+what", "min", "max", "duration", "chance"};
auto& summon = abil.summon;
for(elem = elem.begin(&data); elem != elem.end(); elem++) {
elem->GetValue(&type);
reqs.erase(type);
if(type == "type") {
elem->GetText(&summon.type);
} else if(type == "min") {
if(type == "min") {
elem->GetText(&summon.min);
} else if(type == "max") {
elem->GetText(&summon.max);
@@ -1278,7 +1276,17 @@ static void readMonstAbilFromXml(ticpp::Element& data, cMonster& monst) {
long double percent;
elem->GetText(&percent);
summon.chance = percent * 10;
} else {
if(type == "type" || type == "lvl") {
elem->GetText(&summon.what);
} else if(type == "race") {
eRace race;
elem->GetText(&race);
summon.what = (mon_num_t) race;
} else throw xBadNode(type, elem->Row(), elem->Column(), fname);
reqs.erase("type+what");
summon.type = boost::lexical_cast<eMonstSummon>(type);
}
}
if(!reqs.empty())
throw xMissingElem("summon", *reqs.begin(), data.Row(), data.Column(), fname);
@@ -1287,11 +1295,14 @@ static void readMonstAbilFromXml(ticpp::Element& data, cMonster& monst) {
throw xBadVal(type, "type", val, data.Row(), data.Column(), fname);
std::set<std::string> reqs = {"type", "chance"};
auto& radiate = abil.radiate;
radiate.pat = PAT_SQ; // Default radiate pattern is 3x3 square
for(elem = elem.begin(&data); elem != elem.end(); elem++) {
elem->GetValue(&type);
reqs.erase(type);
if(type == "type") {
elem->GetText(&radiate.type);
} else if(type == "pattern") {
elem->GetText(&radiate.pat);
} else if(type == "chance") {
elem->GetText(&radiate.chance);
} else throw xBadNode(type, elem->Row(), elem->Column(), fname);
@@ -1315,7 +1326,7 @@ static void readMonstAbilFromXml(ticpp::Element& data, cMonster& monst) {
elem->GetText(&special.extra3);
num_params++;
}
}
} else throw xBadNode(type, data.Row(), data.Column(), fname);
}
}

View File

@@ -0,0 +1,22 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='dmg'>
<type>touch</type>
<strength>8</strength>
<chance>60</chance>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,20 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='drain-sp'>
<extra>5</extra>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,22 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='field'>
<type>touch</type>
<strength>8</strength>
<chance>60</chance>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,20 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='dmg'>
<bad></bad>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,22 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='status'>
<type>touch</type>
<strength>8</strength>
<chance>60</chance>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='missile'>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,25 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='dmg'>
<type>ray</type>
<strength>8</strength>
<chance>60</chance>
<missile>2</missile>
<range>10</range>
<extra>fire</extra>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,25 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='field'>
<type>ray</type>
<strength>8</strength>
<chance>60</chance>
<missile>2</missile>
<range>10</range>
<extra>cloud-sleep</extra>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,24 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='drain-sp'>
<type>ray</type>
<strength>8</strength>
<chance>60</chance>
<missile>2</missile>
<range>10</range>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,25 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='status'>
<type>ray</type>
<strength>8</strength>
<chance>60</chance>
<missile>2</missile>
<range>10</range>
<extra>poison</extra>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,22 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='drain-sp'>
<type>touch</type>
<strength>8</strength>
<chance>60</chance>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='dmg'>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,20 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<missile type='missile'>
<bad></bad>
</missile>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<missile type='dmg'>
</missile>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,25 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<missile type='missile'>
<type>arrow</type>
<missile>3</missile>
<strength>2d6</strength>
<skill>8</skill>
<range>10</range>
<chance>80</chance>
</missile>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<missile type='missile'>
</missile>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,20 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<radiate type='radiate'>
<bad></bad>
</radiate>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<radiate type='dmg'>
</radiate>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,21 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<radiate type='radiate'>
<type>cloud-sleep</type>
<chance>75</chance>
</radiate>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<radiate type='radiate'>
</radiate>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,22 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<radiate type='radiate'>
<type>cloud-sleep</type>
<pattern>plus</pattern>
<chance>75</chance>
</radiate>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,20 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<special type='old-heat'>
<bad></bad>
</special>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<special type='dmg'>
</special>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,22 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<special type='old-heat'>
<param>1</param>
<param>2</param>
<param>3</param>
</special>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,23 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<special type='old-heat'>
<param>1</param>
<param>2</param>
<param>3</param>
<param>4</param>
</special>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,20 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<summon type='summon'>
<bad></bad>
</summon>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<summon type='dmg'>
</summon>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,24 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<summon type='summon'>
<lvl>3</lvl>
<min>1</min>
<max>5</max>
<duration>15</duration>
<chance>50</chance>
</summon>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,24 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<summon type='summon'>
<race>reptile</race>
<min>1</min>
<max>5</max>
<duration>15</duration>
<chance>50</chance>
</summon>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,24 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<summon type='summon'>
<type>32</type>
<min>1</min>
<max>5</max>
<duration>15</duration>
<chance>50</chance>
</summon>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<summon type='summon'>
</summon>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general thing='stuff'>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='bad'>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,19 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='none'>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,18 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<bad type="dmg"></bad>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,27 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<abilities>
<general type='drain-sp'>
<type>touch</type>
<strength>8</strength>
<chance>60</chance>
</general>
<general type='drain-sp'>
<type>touch</type>
<strength>8</strength>
<chance>60</chance>
</general>
</abilities>
</monster>
</monsters>

View File

@@ -0,0 +1,27 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity>
<all>true</all>
<fear>true</fear>
<fire>10</fire>
<cold>20</cold>
<magic>30</magic>
<poison>40</poison>
<undead>50</undead>
<demon>60</demon>
<weird>70</weird>
<weap>80</weap>
<spec>90</spec>
</immunity>
</monster>
</monsters>

View File

@@ -0,0 +1,27 @@
<monsters boes="2.0.0">
<monster id='1'>
<name>Test Monster</name>
<level>1</level>
<armor>0</armor>
<skill>2</skill>
<hp>10</hp>
<speed>4</speed>
<race>humanoid</race>
<attacks/>
<pic h='1' w='1'>5</pic>
<attitude>hostile-a</attitude>
<immunity/>
<default-face>12</default-face>
<speed>3</speed>
<summon>3</summon>
<treasure>4</treasure>
<mage>1</mage>
<priest>2</priest>
<voice>42</voice>
<onsight>91</onsight>
<loot>
<type>128</type>
<chance>35</chance>
</loot>
</monster>
</monsters>

View File

@@ -151,4 +151,320 @@ TEST_CASE("Loading a monster type definition") {
CHECK(scen.scen_monsters[1].a[2].dice == 1);
CHECK(scen.scen_monsters[1].a[2].sides == 8);
}
SECTION("With some immunities") {
fin.open("files/monsters/immunity.xml");
doc = xmlDocFromStream(fin, "immunity.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].mindless);
CHECK(scen.scen_monsters[1].invuln);
CHECK(scen.scen_monsters[1].resist[eDamageType::FIRE] == 10);
CHECK(scen.scen_monsters[1].resist[eDamageType::COLD] == 20);
CHECK(scen.scen_monsters[1].resist[eDamageType::MAGIC] == 30);
CHECK(scen.scen_monsters[1].resist[eDamageType::POISON] == 40);
CHECK(scen.scen_monsters[1].resist[eDamageType::UNDEAD] == 50);
CHECK(scen.scen_monsters[1].resist[eDamageType::DEMON] == 60);
CHECK(scen.scen_monsters[1].resist[eDamageType::UNBLOCKABLE] == 70);
CHECK(scen.scen_monsters[1].resist[eDamageType::WEAPON] == 80);
CHECK(scen.scen_monsters[1].resist[eDamageType::SPECIAL] == 90);
// TODO: Setting special damage resistance shouldn't be allowed though
}
SECTION("With some misc optional data") {
fin.open("files/monsters/optional.xml");
doc = xmlDocFromStream(fin, "optional.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].speed == 3);
CHECK(scen.scen_monsters[1].mu == 1);
CHECK(scen.scen_monsters[1].cl == 2);
CHECK(scen.scen_monsters[1].treasure == 4);
CHECK(scen.scen_monsters[1].corpse_item == 128);
CHECK(scen.scen_monsters[1].corpse_item_chance == 35);
CHECK(scen.scen_monsters[1].summon_type == 3);
CHECK(scen.scen_monsters[1].default_facial_pic == 12);
CHECK(scen.scen_monsters[1].ambient_sound == 42);
CHECK(scen.scen_monsters[1].see_spec == 91);
}
}
TEST_CASE("Loading monster abilities") {
ifstream fin;
cScenario scen;
Document doc;
fin.exceptions(ios::badbit);
SECTION("With an invalid category") {
fin.open("files/monsters/bad_abil_type_tag.xml");
doc = xmlDocFromStream(fin, "bad_abil_type_tag.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadNode);
}
SECTION("With missing type attribute") {
fin.open("files/monsters/bad_abil_no_type.xml");
doc = xmlDocFromStream(fin, "bad_abil_no_type.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xMissingAttr);
}
SECTION("With an invalid attribute") {
fin.open("files/monsters/bad_abil_bad_attr.xml");
doc = xmlDocFromStream(fin, "bad_abil_bad_attr.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadAttr);
}
SECTION("With an invalid type") {
fin.open("files/monsters/bad_abil_type_attr.xml");
doc = xmlDocFromStream(fin, "bad_abil_type_attr.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), Exception);
}
SECTION("With a type of none") {
fin.open("files/monsters/bad_abil_type_none.xml");
doc = xmlDocFromStream(fin, "bad_abil_type_none.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadVal);
}
SECTION("General Abilities") {
SECTION("With a bad ability type") {
fin.open("files/monsters/abil_gen/bad_type.xml");
doc = xmlDocFromStream(fin, "bad_type.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadVal);
}
SECTION("With an invalid subtag") {
fin.open("files/monsters/abil_gen/bad_node.xml");
doc = xmlDocFromStream(fin, "bad_node.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadNode);
}
SECTION("Missing a required subtag") {
fin.open("files/monsters/abil_gen/missing_elem.xml");
doc = xmlDocFromStream(fin, "missing_elem.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xMissingElem);
}
SECTION("Minimal touch ability") {
fin.open("files/monsters/abil_gen/minimal_touch.xml");
doc = xmlDocFromStream(fin, "minimal_touch.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen.type == eMonstGen::TOUCH);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen.strength == 8);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen.odds == 600);
}
SECTION("Minimal ranged ability") {
fin.open("files/monsters/abil_gen/minimal_range.xml");
doc = xmlDocFromStream(fin, "minimal_range.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen.type == eMonstGen::RAY);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen.strength == 8);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen.odds == 600);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen.pic == 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen.range == 10);
}
SECTION("With an extra value when not needed") {
fin.open("files/monsters/abil_gen/bad_extra.xml");
doc = xmlDocFromStream(fin, "bad_extra.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadNode);
}
SECTION("Damage ability without type") {
fin.open("files/monsters/abil_gen/bad_damage.xml");
doc = xmlDocFromStream(fin, "bad_damage.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xMissingElem);
}
SECTION("Field ability without type") {
fin.open("files/monsters/abil_gen/bad_field.xml");
doc = xmlDocFromStream(fin, "bad_field.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xMissingElem);
}
SECTION("Status ability without type") {
fin.open("files/monsters/abil_gen/bad_status.xml");
doc = xmlDocFromStream(fin, "bad_status.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xMissingElem);
}
SECTION("Ranged damage ability") {
fin.open("files/monsters/abil_gen/minimal_damage.xml");
doc = xmlDocFromStream(fin, "minimal_damage.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DAMAGE].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DAMAGE].gen.type == eMonstGen::RAY);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DAMAGE].gen.strength == 8);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DAMAGE].gen.odds == 600);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DAMAGE].gen.pic == 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DAMAGE].gen.range == 10);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DAMAGE].gen.dmg == eDamageType::FIRE);
}
SECTION("Ranged field ability") {
fin.open("files/monsters/abil_gen/minimal_field.xml");
doc = xmlDocFromStream(fin, "minimal_field.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::FIELD].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::FIELD].gen.type == eMonstGen::RAY);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::FIELD].gen.strength == 8);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::FIELD].gen.odds == 600);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::FIELD].gen.pic == 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::FIELD].gen.range == 10);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::FIELD].gen.fld == CLOUD_SLEEP);
}
SECTION("Ranged status ability") {
fin.open("files/monsters/abil_gen/minimal_status.xml");
doc = xmlDocFromStream(fin, "minimal_status.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STATUS].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STATUS].gen.type == eMonstGen::RAY);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STATUS].gen.strength == 8);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STATUS].gen.odds == 600);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STATUS].gen.pic == 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STATUS].gen.range == 10);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STATUS].gen.stat == eStatus::POISON);
}
}
SECTION("Missile Abilities") {
SECTION("With a bad ability type") {
fin.open("files/monsters/abil_missile/bad_type.xml");
doc = xmlDocFromStream(fin, "bad_type.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadVal);
}
SECTION("With an invalid subtag") {
fin.open("files/monsters/abil_missile/bad_node.xml");
doc = xmlDocFromStream(fin, "bad_node.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadNode);
}
SECTION("Missing a required subtag") {
fin.open("files/monsters/abil_missile/missing_elem.xml");
doc = xmlDocFromStream(fin, "missing_elem.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xMissingElem);
}
SECTION("Minimal ability") {
fin.open("files/monsters/abil_missile/minimal.xml");
doc = xmlDocFromStream(fin, "minimal.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile.type == eMonstMissile::ARROW);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile.pic == 3);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile.dice == 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile.sides == 6);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile.skill == 8);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile.range == 10);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile.odds == 800);
}
}
SECTION("Radiate Abilities") {
SECTION("With a bad ability type") {
fin.open("files/monsters/abil_radiate/bad_type.xml");
doc = xmlDocFromStream(fin, "bad_type.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadVal);
}
SECTION("With an invalid subtag") {
fin.open("files/monsters/abil_radiate/bad_node.xml");
doc = xmlDocFromStream(fin, "bad_node.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadNode);
}
SECTION("Missing a required subtag") {
fin.open("files/monsters/abil_radiate/missing_elem.xml");
doc = xmlDocFromStream(fin, "missing_elem.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xMissingElem);
}
SECTION("Minimal ability") {
fin.open("files/monsters/abil_radiate/minimal.xml");
doc = xmlDocFromStream(fin, "minimal.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RADIATE].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RADIATE].radiate.type == CLOUD_SLEEP);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RADIATE].radiate.pat == PAT_SQ);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RADIATE].radiate.chance == 75);
}
SECTION("With non-default spell pattern") {
fin.open("files/monsters/abil_radiate/pattern.xml");
doc = xmlDocFromStream(fin, "pattern.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RADIATE].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RADIATE].radiate.type == CLOUD_SLEEP);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RADIATE].radiate.pat == PAT_PLUS);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RADIATE].radiate.chance == 75);
}
}
SECTION("Summon Abilities") {
SECTION("With a bad ability type") {
fin.open("files/monsters/abil_summon/bad_type.xml");
doc = xmlDocFromStream(fin, "bad_type.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadVal);
}
SECTION("With an invalid subtag") {
fin.open("files/monsters/abil_summon/bad_node.xml");
doc = xmlDocFromStream(fin, "bad_node.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadNode);
}
SECTION("Missing a required subtag") {
fin.open("files/monsters/abil_summon/missing_elem.xml");
doc = xmlDocFromStream(fin, "missing_elem.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xMissingElem);
}
SECTION("Minimal type ability") {
fin.open("files/monsters/abil_summon/minimal_type.xml");
doc = xmlDocFromStream(fin, "minimal_type.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.type == eMonstSummon::TYPE);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.what == 32);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.min == 1);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.max == 5);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.len == 15);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.chance == 500);
}
SECTION("Minimal level ability") {
fin.open("files/monsters/abil_summon/minimal_lvl.xml");
doc = xmlDocFromStream(fin, "minimal_lvl.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.type == eMonstSummon::LEVEL);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.what == 3);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.min == 1);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.max == 5);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.len == 15);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.chance == 500);
}
SECTION("Minimal race ability") {
fin.open("files/monsters/abil_summon/minimal_race.xml");
doc = xmlDocFromStream(fin, "minimal_race.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.type == eMonstSummon::SPECIES);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.what == int(eRace::REPTILE));
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.min == 1);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.max == 5);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.len == 15);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon.chance == 500);
}
}
SECTION("Special Abilities") {
SECTION("With a bad ability type") {
fin.open("files/monsters/abil_spec/bad_type.xml");
doc = xmlDocFromStream(fin, "bad_type.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadVal);
}
SECTION("With an invalid subtag") {
fin.open("files/monsters/abil_spec/bad_node.xml");
doc = xmlDocFromStream(fin, "bad_node.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadNode);
}
SECTION("With too many parameters") {
fin.open("files/monsters/abil_spec/too_many_params.xml");
doc = xmlDocFromStream(fin, "too_many_params.xml");
REQUIRE_THROWS_AS(readMonstersFromXml(move(doc), scen), xBadNode);
}
SECTION("With all parameters") {
fin.open("files/monsters/abil_spec/minimal.xml");
doc = xmlDocFromStream(fin, "minimal.xml");
REQUIRE_NOTHROW(readMonstersFromXml(move(doc), scen));
REQUIRE(scen.scen_monsters.size() >= 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RAY_HEAT].active);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RAY_HEAT].special.extra1 == 1);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RAY_HEAT].special.extra2 == 2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RAY_HEAT].special.extra3 == 3);
}
}
}

View File

@@ -18,6 +18,24 @@ extern Document xmlDocFromStream(istream& stream, string name);
extern void readMonstersFromXml(Document&& data, cScenario& scenario);
extern void writeMonstersToXml(Printer&& data, cScenario& scenario);
// Some setup to make monster abilities printable and comparable
using MissileAbil = decltype(uAbility::missile);
using GeneralAbil = decltype(uAbility::gen);
using SummonAbil = decltype(uAbility::summon);
using RadiateAbil = decltype(uAbility::radiate);
using SpecialAbil = decltype(uAbility::special);
static ostream& operator<< (ostream& out, const MissileAbil& abil);
static ostream& operator<< (ostream& out, const GeneralAbil& abil);
static ostream& operator<< (ostream& out, const SummonAbil& abil);
static ostream& operator<< (ostream& out, const RadiateAbil& abil);
static ostream& operator<< (ostream& out, const SpecialAbil& abil);
static bool operator == (const MissileAbil& lhs, const MissileAbil& rhs);
static bool operator == (const GeneralAbil& lhs, const GeneralAbil& rhs);
static bool operator == (const SummonAbil& lhs, const SummonAbil& rhs);
static bool operator == (const RadiateAbil& lhs, const RadiateAbil& rhs);
static bool operator == (const SpecialAbil& lhs, const SpecialAbil& rhs);
static void in_and_out(string name, cScenario& scen) {
string fpath = "junk/monst_";
fpath += name;
@@ -62,10 +80,287 @@ TEST_CASE("Saving monster types") {
CHECK(scen.scen_monsters[1].y_width == 2);
CHECK(scen.scen_monsters[1].picture_num == 17);
CHECK(scen.scen_monsters[1].default_attitude == eAttitude::HOSTILE_B);
CHECK_FALSE(scen.scen_monsters[1].mindless);
CHECK_FALSE(scen.scen_monsters[1].invuln);
CHECK_FALSE(scen.scen_monsters[1].invisible);
CHECK_FALSE(scen.scen_monsters[1].guard);
for(int i = 0; i <= 8; i++) {
eDamageType dmg = eDamageType(i);
CAPTURE(dmg);
CHECK(scen.scen_monsters[1].resist[dmg] == 100);
}
}
SECTION("With most optional information") {
scen.scen_monsters[1].speed = 3;
scen.scen_monsters[1].mu = 1;
scen.scen_monsters[1].cl = 2;
scen.scen_monsters[1].treasure = 4;
scen.scen_monsters[1].corpse_item = 128;
scen.scen_monsters[1].corpse_item_chance = 35;
scen.scen_monsters[1].mindless = true;
scen.scen_monsters[1].invuln = true;
scen.scen_monsters[1].invisible = true;
scen.scen_monsters[1].guard = true;
scen.scen_monsters[1].summon_type = 3;
scen.scen_monsters[1].default_facial_pic = 12;
scen.scen_monsters[1].ambient_sound = 42;
scen.scen_monsters[1].see_spec = 91;
in_and_out("full", scen);
CHECK(scen.scen_monsters[1].speed == 3);
CHECK(scen.scen_monsters[1].mu == 1);
CHECK(scen.scen_monsters[1].cl == 2);
CHECK(scen.scen_monsters[1].treasure == 4);
CHECK(scen.scen_monsters[1].corpse_item == 128);
CHECK(scen.scen_monsters[1].corpse_item_chance == 35);
CHECK(scen.scen_monsters[1].mindless);
CHECK(scen.scen_monsters[1].invuln);
CHECK(scen.scen_monsters[1].invisible);
CHECK(scen.scen_monsters[1].guard);
CHECK(scen.scen_monsters[1].summon_type == 3);
CHECK(scen.scen_monsters[1].default_facial_pic == 12);
CHECK(scen.scen_monsters[1].ambient_sound == 42);
CHECK(scen.scen_monsters[1].see_spec == 91);
}
SECTION("With resistances") {
scen.scen_monsters[1].resist[eDamageType::WEAPON] = 5;
scen.scen_monsters[1].resist[eDamageType::FIRE] = 10;
scen.scen_monsters[1].resist[eDamageType::POISON] = 15;
scen.scen_monsters[1].resist[eDamageType::MAGIC] = 20;
scen.scen_monsters[1].resist[eDamageType::UNBLOCKABLE] = 25;
scen.scen_monsters[1].resist[eDamageType::COLD] = 30;
scen.scen_monsters[1].resist[eDamageType::UNDEAD] = 35;
scen.scen_monsters[1].resist[eDamageType::DEMON] = 40;
scen.scen_monsters[1].resist[eDamageType::SPECIAL] = 45;
in_and_out("resistance", scen);
CHECK(scen.scen_monsters[1].resist[eDamageType::WEAPON] == 5);
CHECK(scen.scen_monsters[1].resist[eDamageType::FIRE] == 10);
CHECK(scen.scen_monsters[1].resist[eDamageType::POISON] == 15);
CHECK(scen.scen_monsters[1].resist[eDamageType::MAGIC] == 20);
CHECK(scen.scen_monsters[1].resist[eDamageType::UNBLOCKABLE] == 25);
CHECK(scen.scen_monsters[1].resist[eDamageType::COLD] == 30);
CHECK(scen.scen_monsters[1].resist[eDamageType::UNDEAD] == 35);
CHECK(scen.scen_monsters[1].resist[eDamageType::DEMON] == 40);
// This one should not be saved, so we expect it to revert to default
CHECK(scen.scen_monsters[1].resist[eDamageType::SPECIAL] == 100);
}
SECTION("With attacks") {
scen.scen_monsters[1].a[0].dice = 1;
scen.scen_monsters[1].a[0].sides = 4;
scen.scen_monsters[1].a[1].dice = 2;
scen.scen_monsters[1].a[1].sides = 6;
scen.scen_monsters[1].a[1].type = eMonstMelee::STAB;
scen.scen_monsters[1].a[2].sides = 3;
scen.scen_monsters[1].a[2].dice = 8;
scen.scen_monsters[1].a[2].type = eMonstMelee::SLIME;
in_and_out("attacks", scen);
CHECK(scen.scen_monsters[1].a[0].dice == 1);
CHECK(scen.scen_monsters[1].a[0].sides == 4);
CHECK(scen.scen_monsters[1].a[0].type == eMonstMelee::SWING);
CHECK(scen.scen_monsters[1].a[1].dice == 2);
CHECK(scen.scen_monsters[1].a[1].sides == 6);
CHECK(scen.scen_monsters[1].a[1].type == eMonstMelee::STAB);
CHECK(scen.scen_monsters[1].a[2].sides == 3);
CHECK(scen.scen_monsters[1].a[2].dice == 8);
CHECK(scen.scen_monsters[1].a[2].type == eMonstMelee::SLIME);
}
SECTION("With an ability of every type") {
MissileAbil missile = {true, eMonstMissile::SPINE, 5, 1, 8, 7, 10, 500};
RadiateAbil radiate = {true, WALL_FIRE, 450, PAT_RAD3};
SummonAbil summon = {true, eMonstSummon::TYPE, 128, 1, 8, 50, 580};
SpecialAbil
split = {true, 1, 2, 3},
martyr = {true, 4, 5, 6},
absorb = {true, 7, 8, 9},
web = {true, 10, 11, 12},
heat = {true, 13, 14, 15},
special = {true, 16, 17, 18},
hit = {true, 19, 20, 21},
death = {true, 22, 23, 24};
GeneralAbil
damage = {true, eMonstGen::RAY, 3, 6, 12, 450},
damage2 = {true, eMonstGen::TOUCH, -1, 4, 0, 250},
status = {true, eMonstGen::GAZE, 2, 9, 20, 400},
status2 = {true, eMonstGen::TOUCH, -1, 13, 0, 600},
field = {true, eMonstGen::SPIT, 11, 14, 16, 150},
petrify = {true, eMonstGen::BREATH, 15, 18, 22, 350},
sp = {true, eMonstGen::RAY, 17, 24, 26, 100},
xp = {true, eMonstGen::GAZE, 19, 27, 28, 120},
kill = {true, eMonstGen::BREATH, 21, 32, 30, 300},
food = {true, eMonstGen::SPIT, 23, 35, 40, 240},
gold = {true, eMonstGen::GAZE, 29, 38, 36, 750},
stun = {true, eMonstGen::RAY, 31, 33, 34, 800};
damage.dmg = eDamageType::FIRE;
damage2.dmg = eDamageType::COLD;
status.stat = eStatus::PARALYZED;
status2.stat = eStatus::POISON;
field.fld = FIELD_ANTIMAGIC;
stun.stat = eStatus::HASTE_SLOW;
scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile = missile;
scen.scen_monsters[1].abil[eMonstAbil::DAMAGE].gen = damage;
scen.scen_monsters[1].abil[eMonstAbil::DAMAGE2].gen = damage2;
scen.scen_monsters[1].abil[eMonstAbil::STATUS].gen = status;
scen.scen_monsters[1].abil[eMonstAbil::STATUS2].gen = status2;
scen.scen_monsters[1].abil[eMonstAbil::FIELD].gen = field;
scen.scen_monsters[1].abil[eMonstAbil::PETRIFY].gen = petrify;
scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen = sp;
scen.scen_monsters[1].abil[eMonstAbil::DRAIN_XP].gen = xp;
scen.scen_monsters[1].abil[eMonstAbil::KILL].gen = kill;
scen.scen_monsters[1].abil[eMonstAbil::STEAL_FOOD].gen = food;
scen.scen_monsters[1].abil[eMonstAbil::STEAL_GOLD].gen = gold;
scen.scen_monsters[1].abil[eMonstAbil::STUN].gen = stun;
scen.scen_monsters[1].abil[eMonstAbil::SPLITS].special = split;
scen.scen_monsters[1].abil[eMonstAbil::MARTYRS_SHIELD].special = martyr;
scen.scen_monsters[1].abil[eMonstAbil::ABSORB_SPELLS].special = absorb;
scen.scen_monsters[1].abil[eMonstAbil::MISSILE_WEB].special = web;
scen.scen_monsters[1].abil[eMonstAbil::RAY_HEAT].special = heat;
scen.scen_monsters[1].abil[eMonstAbil::SPECIAL].special = special;
scen.scen_monsters[1].abil[eMonstAbil::HIT_TRIGGER].special = hit;
scen.scen_monsters[1].abil[eMonstAbil::DEATH_TRIGGER].special = death;
scen.scen_monsters[1].abil[eMonstAbil::RADIATE].radiate = radiate;
scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon = summon;
in_and_out("abilities", scen);
// Data stored in irrelevant slots should not be saved.
split.extra2 = split.extra3 = 0;
martyr.extra3 = absorb.extra3 = 0;
web.extra3 = special.extra3 = 0;
hit.extra3 = death.extra2 = death.extra3 = 0;
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile == missile);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DAMAGE].gen == damage);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DAMAGE2].gen == damage2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STATUS].gen == status);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STATUS2].gen == status2);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::FIELD].gen == field);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::PETRIFY].gen == petrify);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_SP].gen == sp);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DRAIN_XP].gen == xp);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::KILL].gen == kill);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STEAL_FOOD].gen == food);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STEAL_GOLD].gen == gold);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::STUN].gen == stun);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SPLITS].special == split);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MARTYRS_SHIELD].special == martyr);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::ABSORB_SPELLS].special == absorb);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE_WEB].special == web);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RAY_HEAT].special == heat);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SPECIAL].special == special);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::HIT_TRIGGER].special == hit);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::DEATH_TRIGGER].special == death);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::RADIATE].radiate == radiate);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::SUMMON].summon == summon);
}
SECTION("Test granularity of permille chances") {
MissileAbil missile = {true, eMonstMissile::SPINE, 5, 1, 8, 7, 10};
for(int i = 0; i <= 1000; i++) {
missile.odds = i;
scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile = missile;
in_and_out("permille granularity", scen);
CAPTURE(i);
CHECK(scen.scen_monsters[1].abil[eMonstAbil::MISSILE].missile.odds == i);
}
}
}
ostream& operator<< (ostream& out, const MissileAbil& abil) {
out << "Missile {";
if(!abil.active) return out << "inactive}";
out << "type = " << abil.type << ", ";
out << "missile = " << abil.pic << ", ";
out << "strength = " << abil.dice << 'd' << abil.sides << ", ";
out << "skill = " << abil.skill << ", ";
out << "range = " << abil.range << ", ";
out << "chance = " << abil.odds << "}";
return out;
}
bool operator == (const MissileAbil& lhs, const MissileAbil& rhs) {
if(lhs.active != rhs.active) return false;
if(lhs.type != rhs.type) return false;
if(lhs.pic != rhs.pic) return false;
if(lhs.dice != rhs.dice) return false;
if(lhs.sides != rhs.sides) return false;
if(lhs.skill != rhs.skill) return false;
if(lhs.range != rhs.range) return false;
if(lhs.odds != rhs.odds) return false;
return true;
}
ostream& operator<< (ostream& out, const GeneralAbil& abil) {
out << "General {";
if(!abil.active) return out << "inactive}";
out << "type = " << abil.type << ", ";
if(abil.type != eMonstGen::TOUCH)
out << "missile = " << abil.pic << ", ";
out << "strength = " << abil.strength << ", ";
if(abil.type != eMonstGen::TOUCH)
out << "range = " << abil.range << ", ";
out << "chance = " << abil.odds << ", ";
out << "extra = " << abil.dmg << " | " << abil.stat << " | " << abil.fld << "}";
return out;
}
bool operator == (const GeneralAbil& lhs, const GeneralAbil& rhs) {
if(lhs.active != rhs.active) return false;
if(lhs.type != rhs.type) return false;
if(lhs.strength != rhs.strength) return false;
if(lhs.odds != rhs.odds) return false;
if(lhs.dmg != rhs.dmg) return false;
if(lhs.stat != rhs.stat) return false;
if(lhs.fld != rhs.fld) return false;
if(lhs.type == eMonstGen::TOUCH) return true;
if(lhs.pic != rhs.pic) return false;
if(lhs.range != rhs.range) return false;
return true;
}
ostream& operator<< (ostream& out, const SummonAbil& abil) {
out << "Summon {";
if(!abil.active) return out << "inactive}";
out << "type = " << abil.type << ", ";
out << "what = " << abil.what << ", ";
out << "count = " << abil.min << '-' << abil.max << ", ";
out << "duration = " << abil.len << ", ";
out << "chance = " << abil.chance << "}";
return out;
}
bool operator == (const SummonAbil& lhs, const SummonAbil& rhs) {
if(lhs.active != rhs.active) return false;
if(lhs.type != rhs.type) return false;
if(lhs.what != rhs.what) return false;
if(lhs.min != rhs.min) return false;
if(lhs.max != rhs.max) return false;
if(lhs.len != rhs.len) return false;
if(lhs.chance != rhs.chance) return false;
return true;
}
ostream& operator<< (ostream& out, const RadiateAbil& abil) {
out << "Radiate {";
if(!abil.active) return out << "inactive}";
out << "type = " << abil.type << ", ";
out << "pattern = " << abil.pat << ", ";
out << "chance = " << abil.chance << "}";
return out;
}
bool operator == (const RadiateAbil& lhs, const RadiateAbil& rhs) {
if(lhs.active != rhs.active) return false;
if(lhs.type != rhs.type) return false;
if(lhs.pat != rhs.pat) return false;
if(lhs.chance != rhs.chance) return false;
return true;
}
ostream& operator<< (ostream& out, const SpecialAbil& abil) {
if(!abil.active) return out << "Special {inactive}";
out << "Special {" << abil.extra1 << ", " << abil.extra2 << ", " << abil.extra3 << "}";
return out;
}
bool operator == (const SpecialAbil& lhs, const SpecialAbil& rhs) {
if(lhs.active != rhs.active) return false;
if(lhs.extra1 != rhs.extra1) return false;
if(lhs.extra2 != rhs.extra2) return false;
if(lhs.extra3 != rhs.extra3) return false;
return true;
}