From 18bb16df9cf6ded0565abc6c75075c107b4c1017 Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Sun, 27 Sep 2015 00:58:10 -0400 Subject: [PATCH] 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 --- rsrc/schemas/monsters.xsd | 27 +- src/classes/estreams.cpp | 18 + src/classes/monster.hpp | 3 + src/scenedit/scen.fileio.cpp | 8 +- src/tools/fileio_scen.cpp | 23 +- test/files/monsters/abil_gen/bad_damage.xml | 22 ++ test/files/monsters/abil_gen/bad_extra.xml | 20 ++ test/files/monsters/abil_gen/bad_field.xml | 22 ++ test/files/monsters/abil_gen/bad_node.xml | 20 ++ test/files/monsters/abil_gen/bad_status.xml | 22 ++ test/files/monsters/abil_gen/bad_type.xml | 19 ++ .../monsters/abil_gen/minimal_damage.xml | 25 ++ .../files/monsters/abil_gen/minimal_field.xml | 25 ++ .../files/monsters/abil_gen/minimal_range.xml | 24 ++ .../monsters/abil_gen/minimal_status.xml | 25 ++ .../files/monsters/abil_gen/minimal_touch.xml | 22 ++ test/files/monsters/abil_gen/missing_elem.xml | 19 ++ test/files/monsters/abil_missile/bad_node.xml | 20 ++ test/files/monsters/abil_missile/bad_type.xml | 19 ++ test/files/monsters/abil_missile/minimal.xml | 25 ++ .../monsters/abil_missile/missing_elem.xml | 19 ++ test/files/monsters/abil_radiate/bad_node.xml | 20 ++ test/files/monsters/abil_radiate/bad_type.xml | 19 ++ test/files/monsters/abil_radiate/minimal.xml | 21 ++ .../monsters/abil_radiate/missing_elem.xml | 19 ++ test/files/monsters/abil_radiate/pattern.xml | 22 ++ test/files/monsters/abil_spec/bad_node.xml | 20 ++ test/files/monsters/abil_spec/bad_type.xml | 19 ++ test/files/monsters/abil_spec/minimal.xml | 22 ++ .../monsters/abil_spec/too_many_params.xml | 23 ++ test/files/monsters/abil_summon/bad_node.xml | 20 ++ test/files/monsters/abil_summon/bad_type.xml | 19 ++ .../monsters/abil_summon/minimal_lvl.xml | 24 ++ .../monsters/abil_summon/minimal_race.xml | 24 ++ .../monsters/abil_summon/minimal_type.xml | 24 ++ .../monsters/abil_summon/missing_elem.xml | 19 ++ test/files/monsters/bad_abil_bad_attr.xml | 19 ++ test/files/monsters/bad_abil_no_type.xml | 19 ++ test/files/monsters/bad_abil_type_attr.xml | 19 ++ test/files/monsters/bad_abil_type_none.xml | 19 ++ test/files/monsters/bad_abil_type_tag.xml | 18 + test/files/monsters/duplicate_attr.xml | 27 ++ test/files/monsters/immunity.xml | 27 ++ test/files/monsters/optional.xml | 27 ++ test/monst_read.cpp | 316 ++++++++++++++++++ test/monst_write.cpp | 295 ++++++++++++++++ 46 files changed, 1511 insertions(+), 17 deletions(-) create mode 100644 test/files/monsters/abil_gen/bad_damage.xml create mode 100644 test/files/monsters/abil_gen/bad_extra.xml create mode 100644 test/files/monsters/abil_gen/bad_field.xml create mode 100644 test/files/monsters/abil_gen/bad_node.xml create mode 100644 test/files/monsters/abil_gen/bad_status.xml create mode 100644 test/files/monsters/abil_gen/bad_type.xml create mode 100644 test/files/monsters/abil_gen/minimal_damage.xml create mode 100644 test/files/monsters/abil_gen/minimal_field.xml create mode 100644 test/files/monsters/abil_gen/minimal_range.xml create mode 100644 test/files/monsters/abil_gen/minimal_status.xml create mode 100644 test/files/monsters/abil_gen/minimal_touch.xml create mode 100644 test/files/monsters/abil_gen/missing_elem.xml create mode 100644 test/files/monsters/abil_missile/bad_node.xml create mode 100644 test/files/monsters/abil_missile/bad_type.xml create mode 100644 test/files/monsters/abil_missile/minimal.xml create mode 100644 test/files/monsters/abil_missile/missing_elem.xml create mode 100644 test/files/monsters/abil_radiate/bad_node.xml create mode 100644 test/files/monsters/abil_radiate/bad_type.xml create mode 100644 test/files/monsters/abil_radiate/minimal.xml create mode 100644 test/files/monsters/abil_radiate/missing_elem.xml create mode 100644 test/files/monsters/abil_radiate/pattern.xml create mode 100644 test/files/monsters/abil_spec/bad_node.xml create mode 100644 test/files/monsters/abil_spec/bad_type.xml create mode 100644 test/files/monsters/abil_spec/minimal.xml create mode 100644 test/files/monsters/abil_spec/too_many_params.xml create mode 100644 test/files/monsters/abil_summon/bad_node.xml create mode 100644 test/files/monsters/abil_summon/bad_type.xml create mode 100644 test/files/monsters/abil_summon/minimal_lvl.xml create mode 100644 test/files/monsters/abil_summon/minimal_race.xml create mode 100644 test/files/monsters/abil_summon/minimal_type.xml create mode 100644 test/files/monsters/abil_summon/missing_elem.xml create mode 100644 test/files/monsters/bad_abil_bad_attr.xml create mode 100644 test/files/monsters/bad_abil_no_type.xml create mode 100644 test/files/monsters/bad_abil_type_attr.xml create mode 100644 test/files/monsters/bad_abil_type_none.xml create mode 100644 test/files/monsters/bad_abil_type_tag.xml create mode 100644 test/files/monsters/duplicate_attr.xml create mode 100644 test/files/monsters/immunity.xml create mode 100644 test/files/monsters/optional.xml diff --git a/rsrc/schemas/monsters.xsd b/rsrc/schemas/monsters.xsd index 89734fd0..67098506 100644 --- a/rsrc/schemas/monsters.xsd +++ b/rsrc/schemas/monsters.xsd @@ -40,6 +40,18 @@ + + + + + + + + + + + + @@ -182,15 +194,11 @@ - - - - - - - - - + + + + + @@ -239,6 +247,7 @@ + diff --git a/src/classes/estreams.cpp b/src/classes/estreams.cpp index 7a7e3b32..4a7b7821 100644 --- a/src/classes/estreams.cpp +++ b/src/classes/estreams.cpp @@ -14,6 +14,7 @@ #include #include #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; +} diff --git a/src/classes/monster.hpp b/src/classes/monster.hpp index fc7016b9..1be8f53a 100644 --- a/src/classes/monster.hpp +++ b/src/classes/monster.hpp @@ -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 diff --git a/src/scenedit/scen.fileio.cpp b/src/scenedit/scen.fileio.cpp index e6ad059f..e50943e4 100644 --- a/src/scenedit/scen.fileio.cpp +++ b/src/scenedit/scen.fileio.cpp @@ -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(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; diff --git a/src/tools/fileio_scen.cpp b/src/tools/fileio_scen.cpp index 4d2a508f..5b3f6479 100644 --- a/src/tools/fileio_scen.cpp +++ b/src/tools/fileio_scen.cpp @@ -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 reqs = {"type", "min", "max", "duration", "chance"}; + std::set 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 throw xBadNode(type, elem->Row(), elem->Column(), fname); + } 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(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 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); } } diff --git a/test/files/monsters/abil_gen/bad_damage.xml b/test/files/monsters/abil_gen/bad_damage.xml new file mode 100644 index 00000000..e0c22fcd --- /dev/null +++ b/test/files/monsters/abil_gen/bad_damage.xml @@ -0,0 +1,22 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + touch + 8 + 60 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/bad_extra.xml b/test/files/monsters/abil_gen/bad_extra.xml new file mode 100644 index 00000000..0dacbb01 --- /dev/null +++ b/test/files/monsters/abil_gen/bad_extra.xml @@ -0,0 +1,20 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + 5 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/bad_field.xml b/test/files/monsters/abil_gen/bad_field.xml new file mode 100644 index 00000000..0559d8b0 --- /dev/null +++ b/test/files/monsters/abil_gen/bad_field.xml @@ -0,0 +1,22 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + touch + 8 + 60 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/bad_node.xml b/test/files/monsters/abil_gen/bad_node.xml new file mode 100644 index 00000000..322a9529 --- /dev/null +++ b/test/files/monsters/abil_gen/bad_node.xml @@ -0,0 +1,20 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/bad_status.xml b/test/files/monsters/abil_gen/bad_status.xml new file mode 100644 index 00000000..61a33a44 --- /dev/null +++ b/test/files/monsters/abil_gen/bad_status.xml @@ -0,0 +1,22 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + touch + 8 + 60 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/bad_type.xml b/test/files/monsters/abil_gen/bad_type.xml new file mode 100644 index 00000000..522e3d7b --- /dev/null +++ b/test/files/monsters/abil_gen/bad_type.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/minimal_damage.xml b/test/files/monsters/abil_gen/minimal_damage.xml new file mode 100644 index 00000000..d3e8e802 --- /dev/null +++ b/test/files/monsters/abil_gen/minimal_damage.xml @@ -0,0 +1,25 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + ray + 8 + 60 + 2 + 10 + fire + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/minimal_field.xml b/test/files/monsters/abil_gen/minimal_field.xml new file mode 100644 index 00000000..a73509ee --- /dev/null +++ b/test/files/monsters/abil_gen/minimal_field.xml @@ -0,0 +1,25 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + ray + 8 + 60 + 2 + 10 + cloud-sleep + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/minimal_range.xml b/test/files/monsters/abil_gen/minimal_range.xml new file mode 100644 index 00000000..da34ce71 --- /dev/null +++ b/test/files/monsters/abil_gen/minimal_range.xml @@ -0,0 +1,24 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + ray + 8 + 60 + 2 + 10 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/minimal_status.xml b/test/files/monsters/abil_gen/minimal_status.xml new file mode 100644 index 00000000..2d8c0768 --- /dev/null +++ b/test/files/monsters/abil_gen/minimal_status.xml @@ -0,0 +1,25 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + ray + 8 + 60 + 2 + 10 + poison + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/minimal_touch.xml b/test/files/monsters/abil_gen/minimal_touch.xml new file mode 100644 index 00000000..a43dc29c --- /dev/null +++ b/test/files/monsters/abil_gen/minimal_touch.xml @@ -0,0 +1,22 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + touch + 8 + 60 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_gen/missing_elem.xml b/test/files/monsters/abil_gen/missing_elem.xml new file mode 100644 index 00000000..ed6b4137 --- /dev/null +++ b/test/files/monsters/abil_gen/missing_elem.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_missile/bad_node.xml b/test/files/monsters/abil_missile/bad_node.xml new file mode 100644 index 00000000..cc3e35e3 --- /dev/null +++ b/test/files/monsters/abil_missile/bad_node.xml @@ -0,0 +1,20 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_missile/bad_type.xml b/test/files/monsters/abil_missile/bad_type.xml new file mode 100644 index 00000000..db8e174e --- /dev/null +++ b/test/files/monsters/abil_missile/bad_type.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_missile/minimal.xml b/test/files/monsters/abil_missile/minimal.xml new file mode 100644 index 00000000..347b7f24 --- /dev/null +++ b/test/files/monsters/abil_missile/minimal.xml @@ -0,0 +1,25 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + arrow + 3 + 2d6 + 8 + 10 + 80 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_missile/missing_elem.xml b/test/files/monsters/abil_missile/missing_elem.xml new file mode 100644 index 00000000..fd8d8794 --- /dev/null +++ b/test/files/monsters/abil_missile/missing_elem.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_radiate/bad_node.xml b/test/files/monsters/abil_radiate/bad_node.xml new file mode 100644 index 00000000..ad151538 --- /dev/null +++ b/test/files/monsters/abil_radiate/bad_node.xml @@ -0,0 +1,20 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_radiate/bad_type.xml b/test/files/monsters/abil_radiate/bad_type.xml new file mode 100644 index 00000000..00141457 --- /dev/null +++ b/test/files/monsters/abil_radiate/bad_type.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_radiate/minimal.xml b/test/files/monsters/abil_radiate/minimal.xml new file mode 100644 index 00000000..7f7a6aca --- /dev/null +++ b/test/files/monsters/abil_radiate/minimal.xml @@ -0,0 +1,21 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + cloud-sleep + 75 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_radiate/missing_elem.xml b/test/files/monsters/abil_radiate/missing_elem.xml new file mode 100644 index 00000000..1c39aa1e --- /dev/null +++ b/test/files/monsters/abil_radiate/missing_elem.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_radiate/pattern.xml b/test/files/monsters/abil_radiate/pattern.xml new file mode 100644 index 00000000..44e08cff --- /dev/null +++ b/test/files/monsters/abil_radiate/pattern.xml @@ -0,0 +1,22 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + cloud-sleep + plus + 75 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_spec/bad_node.xml b/test/files/monsters/abil_spec/bad_node.xml new file mode 100644 index 00000000..249eb12d --- /dev/null +++ b/test/files/monsters/abil_spec/bad_node.xml @@ -0,0 +1,20 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_spec/bad_type.xml b/test/files/monsters/abil_spec/bad_type.xml new file mode 100644 index 00000000..c8f78a1d --- /dev/null +++ b/test/files/monsters/abil_spec/bad_type.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_spec/minimal.xml b/test/files/monsters/abil_spec/minimal.xml new file mode 100644 index 00000000..5e1eb4da --- /dev/null +++ b/test/files/monsters/abil_spec/minimal.xml @@ -0,0 +1,22 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + 1 + 2 + 3 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_spec/too_many_params.xml b/test/files/monsters/abil_spec/too_many_params.xml new file mode 100644 index 00000000..d661f9b3 --- /dev/null +++ b/test/files/monsters/abil_spec/too_many_params.xml @@ -0,0 +1,23 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + 1 + 2 + 3 + 4 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_summon/bad_node.xml b/test/files/monsters/abil_summon/bad_node.xml new file mode 100644 index 00000000..049a311a --- /dev/null +++ b/test/files/monsters/abil_summon/bad_node.xml @@ -0,0 +1,20 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_summon/bad_type.xml b/test/files/monsters/abil_summon/bad_type.xml new file mode 100644 index 00000000..62d00a00 --- /dev/null +++ b/test/files/monsters/abil_summon/bad_type.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_summon/minimal_lvl.xml b/test/files/monsters/abil_summon/minimal_lvl.xml new file mode 100644 index 00000000..c068561f --- /dev/null +++ b/test/files/monsters/abil_summon/minimal_lvl.xml @@ -0,0 +1,24 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + 3 + 1 + 5 + 15 + 50 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_summon/minimal_race.xml b/test/files/monsters/abil_summon/minimal_race.xml new file mode 100644 index 00000000..00aab748 --- /dev/null +++ b/test/files/monsters/abil_summon/minimal_race.xml @@ -0,0 +1,24 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + reptile + 1 + 5 + 15 + 50 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_summon/minimal_type.xml b/test/files/monsters/abil_summon/minimal_type.xml new file mode 100644 index 00000000..11f96981 --- /dev/null +++ b/test/files/monsters/abil_summon/minimal_type.xml @@ -0,0 +1,24 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + 32 + 1 + 5 + 15 + 50 + + + + \ No newline at end of file diff --git a/test/files/monsters/abil_summon/missing_elem.xml b/test/files/monsters/abil_summon/missing_elem.xml new file mode 100644 index 00000000..441800ca --- /dev/null +++ b/test/files/monsters/abil_summon/missing_elem.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/bad_abil_bad_attr.xml b/test/files/monsters/bad_abil_bad_attr.xml new file mode 100644 index 00000000..e7c1f9e0 --- /dev/null +++ b/test/files/monsters/bad_abil_bad_attr.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/bad_abil_no_type.xml b/test/files/monsters/bad_abil_no_type.xml new file mode 100644 index 00000000..6d877ad1 --- /dev/null +++ b/test/files/monsters/bad_abil_no_type.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/bad_abil_type_attr.xml b/test/files/monsters/bad_abil_type_attr.xml new file mode 100644 index 00000000..8fe5f15f --- /dev/null +++ b/test/files/monsters/bad_abil_type_attr.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/bad_abil_type_none.xml b/test/files/monsters/bad_abil_type_none.xml new file mode 100644 index 00000000..4d253e8b --- /dev/null +++ b/test/files/monsters/bad_abil_type_none.xml @@ -0,0 +1,19 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + + \ No newline at end of file diff --git a/test/files/monsters/bad_abil_type_tag.xml b/test/files/monsters/bad_abil_type_tag.xml new file mode 100644 index 00000000..82abf59c --- /dev/null +++ b/test/files/monsters/bad_abil_type_tag.xml @@ -0,0 +1,18 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + + + \ No newline at end of file diff --git a/test/files/monsters/duplicate_attr.xml b/test/files/monsters/duplicate_attr.xml new file mode 100644 index 00000000..2156511f --- /dev/null +++ b/test/files/monsters/duplicate_attr.xml @@ -0,0 +1,27 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + + + touch + 8 + 60 + + + touch + 8 + 60 + + + + \ No newline at end of file diff --git a/test/files/monsters/immunity.xml b/test/files/monsters/immunity.xml new file mode 100644 index 00000000..cf4b68e5 --- /dev/null +++ b/test/files/monsters/immunity.xml @@ -0,0 +1,27 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + true + true + 10 + 20 + 30 + 40 + 50 + 60 + 70 + 80 + 90 + + + \ No newline at end of file diff --git a/test/files/monsters/optional.xml b/test/files/monsters/optional.xml new file mode 100644 index 00000000..33f1ee72 --- /dev/null +++ b/test/files/monsters/optional.xml @@ -0,0 +1,27 @@ + + + Test Monster + 1 + 0 + 2 + 10 + 4 + humanoid + + 5 + hostile-a + + 12 + 3 + 3 + 4 + 1 + 2 + 42 + 91 + + 128 + 35 + + + \ No newline at end of file diff --git a/test/monst_read.cpp b/test/monst_read.cpp index ea690a8a..fc919848 100644 --- a/test/monst_read.cpp +++ b/test/monst_read.cpp @@ -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); + } + } } diff --git a/test/monst_write.cpp b/test/monst_write.cpp index a7dc50fe..4e89af57 100644 --- a/test/monst_write.cpp +++ b/test/monst_write.cpp @@ -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; }