#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);