diff --git a/tests/testAdvancement.cpp b/tests/testAdvancement.cpp new file mode 100644 index 0000000..8c86406 --- /dev/null +++ b/tests/testAdvancement.cpp @@ -0,0 +1,156 @@ +#include "testHarness.h" +#include "vf/Advancement.h" +#include "vf/Scoreboard.h" + +#include + +using namespace vf; + +namespace { + +using Event = AdvancementCriterion::Event; +using Trigger = AdvancementCriterion::Trigger; + +ResourceId id(const std::string& text) { + ResourceId parsed; + ResourceId::parse(text, "voxelforge", parsed); + return parsed; +} + +const char* const rootAdvancement = R"json({ + "display": {"title": "Getting started", "description": "Chop something down"}, + "criteria": { + "anyWood": {"trigger": "itemObtained", "item": "voxelforge:wood"} + }, + "rewards": {"experience": 5} +})json"; + +const char* const toolsAdvancement = R"json({ + "parent": "voxelforge:root", + "display": {"title": "A workbench of your own"}, + "criteria": { + "sticks": {"trigger": "itemObtained", "item": "voxelforge:stick", "count": 4}, + "table": {"trigger": "itemObtained", "item": "voxelforge:crafting_table"} + }, + "rewards": {"experience": 10, "recipes": ["voxelforge:redstone_torch"]} +})json"; + +const char* const hunterAdvancement = R"json({ + "parent": "voxelforge:tools", + "display": {"title": "Monster hunter", "hidden": true}, + "criteria": { + "zombies": {"trigger": "mobKilled", "species": "zombie", "count": 3}, + "creeper": {"trigger": "mobKilled", "species": "creeper"} + }, + "requirements": [["zombies", "creeper"]], + "rewards": {"experience": 20, "loot": ["voxelforge:chests/starter"]} +})json"; + +const char* const travellerAdvancement = R"json({ + "parent": "voxelforge:root", + "display": {"title": "Into the fire"}, + "criteria": { + "portal": {"trigger": "dimensionEntered", "dimension": "nether"}, + "brew": {"trigger": "potionBrewed", "potion": "healing"}, + "trade": {"trigger": "villagerTraded", "profession": "librarian", + "item": "voxelforge:emerald_gem"}, + "mine": {"trigger": "blockBroken", "block": "voxelforge:obsidian", + "tool": "voxelforge:diamond_pickaxe"} + }, + "requirements": [["portal"], ["brew", "trade", "mine"]] +})json"; + +DataPack contentPack() { + DataPackMeta meta; + meta.name = "story"; + DataPack pack(meta); + pack.addFile("data/voxelforge/advancements/root.json", rootAdvancement); + pack.addFile("data/voxelforge/advancements/tools.json", toolsAdvancement); + pack.addFile("data/voxelforge/advancements/hunter.json", hunterAdvancement); + pack.addFile("data/voxelforge/advancements/traveller.json", travellerAdvancement); + return pack; +} + +AdvancementTree loadTree(const DataPack& pack, std::vector& errors) { + DataPackLoader loader; + loader.addPack(pack); + loader.load(); + return loadAdvancements(loader, errors); +} + +Advancement makeAdvancement(const std::string& self, const std::string& parent) { + Advancement node; + node.id = id(self); + if (!parent.empty()) node.parent = id(parent); + AdvancementCriterion criterion; + criterion.name = "tick"; + criterion.trigger = Trigger::ItemObtained; + node.criteria.push_back(criterion); + return node; +} + +} // namespace + +VF_TEST(advancementsLoadOutOfAPackAndKeepTheirTreeShape) { + std::vector errors; + AdvancementTree tree = loadTree(contentPack(), errors); + CHECK(errors.empty()); + CHECK_EQ(tree.size(), static_cast(4)); + + const Advancement* root = tree.find(id("voxelforge:root")); + CHECK(root != nullptr); + CHECK(root->isRoot()); + CHECK_EQ(root->title, std::string("Getting started")); + CHECK_EQ(root->description, std::string("Chop something down")); + CHECK_EQ(root->criteria.size(), static_cast(1)); + CHECK(root->criteria[0].item == ItemId::Wood); + CHECK_EQ(root->criteria[0].matchMask, AdvancementCriterion::MatchItem); + CHECK_EQ(root->reward.experience, 5); + + const Advancement* tools = tree.find(id("voxelforge:tools")); + CHECK(tools != nullptr); + CHECK(!tools->isRoot()); + CHECK(tools->parent == id("voxelforge:root")); + CHECK_EQ(tools->criteria.size(), static_cast(2)); + CHECK_EQ(tools->findCriterion("sticks")->requiredCount, 4); + CHECK(tools->findCriterion("absent") == nullptr); + CHECK_EQ(tools->reward.recipes.size(), static_cast(1)); + CHECK(tools->reward.recipes[0] == id("voxelforge:redstone_torch")); + + const Advancement* traveller = tree.find(id("voxelforge:traveller")); + CHECK(traveller->findCriterion("portal")->dimension == DimensionId::Nether); + CHECK(traveller->findCriterion("brew")->potion == PotionKind::Healing); + CHECK(traveller->findCriterion("trade")->profession == VillagerProfession::Librarian); + CHECK(traveller->findCriterion("trade")->item == ItemId::EmeraldGem); + CHECK(traveller->findCriterion("mine")->block == Block::Obsidian); + CHECK(traveller->findCriterion("mine")->tool == ItemId::DiamondPickaxe); + CHECK_EQ(traveller->requirements.size(), static_cast(2)); + + CHECK_EQ(tree.roots().size(), static_cast(1)); + CHECK(tree.roots()[0] == id("voxelforge:root")); + CHECK_EQ(tree.childrenOf(id("voxelforge:root")).size(), static_cast(2)); + CHECK_EQ(tree.childrenOf(id("voxelforge:tools")).size(), static_cast(1)); + CHECK_EQ(tree.depthOf(id("voxelforge:root")), 0); + CHECK_EQ(tree.depthOf(id("voxelforge:tools")), 1); + CHECK_EQ(tree.depthOf(id("voxelforge:hunter")), 2); + CHECK_EQ(tree.depthOf(id("voxelforge:absent")), -1); + CHECK(!tree.hasCycle()); + CHECK(tree.missingParents().empty()); + CHECK_EQ(tree.ids().size(), static_cast(4)); +} + +VF_TEST(anAdvancementIsGrantedOnlyWhenItsLastCriterionCompletes) { + std::vector errors; + AdvancementTree tree = loadTree(contentPack(), errors); + AdvancementProgress progress(tree); + const AdvancementId tools = id("voxelforge:tools"); + + CHECK(!progress.granted(tools)); + CHECK_EQ(progress.grantedCount(), 0); + + std::vector granted = progress.notify(Event::itemObtained(ItemId::Stick, 2), 100); + CHECK(granted.empty()); + CHECK(!progress.criterionDone(tools, "sticks")); + CHECK_EQ(progress.criterionProgress(tools, "sticks"), 2); + CHECK_EQ(progress.criterionTick(tools, "sticks"), -1); +