#pragma once #include #include #include #include #include "Block.h" #include "Brewing.h" #include "DataPack.h" #include "Dimension.h" #include "Item.h" #include "Mob.h" #include "Villager.h" namespace vf { // An advancement is addressed the same way as every other loaded resource. using AdvancementId = ResourceId; // One condition inside an advancement, fed by gameplay events. A criterion only // compares the fields its matchMask names, so "kill any mob" and "kill five // creepers" are the same structure with different masks and counts. struct AdvancementCriterion { enum class Trigger : uint8_t { ItemObtained, MobKilled, DimensionEntered, PotionBrewed, VillagerTraded, BlockBroken, Count }; static constexpr uint32_t MatchItem = 1u << 0; static constexpr uint32_t MatchBlock = 1u << 1; static constexpr uint32_t MatchSpecies = 1u << 2; static constexpr uint32_t MatchDimension = 1u << 3; static constexpr uint32_t MatchPotion = 1u << 4; static constexpr uint32_t MatchProfession = 1u << 5; static constexpr uint32_t MatchTool = 1u << 6; // One thing that happened in the world, as the simulation reports it. struct Event { Trigger trigger = Trigger::ItemObtained; ItemId item = ItemId::None; Block block = Block::Air; Species species = Species::Count; DimensionId dimension = DimensionId::Overworld; PotionKind potion = PotionKind::Count; VillagerProfession profession = VillagerProfession::Unemployed; ItemId tool = ItemId::None; // How much this one event contributes to a counted criterion. int amount = 1; static Event itemObtained(ItemId item, int amount = 1); static Event mobKilled(Species species); static Event dimensionEntered(DimensionId dimension); static Event potionBrewed(PotionKind potion); static Event villagerTraded(VillagerProfession profession, ItemId purchased); static Event blockBroken(Block block, ItemId tool); }; std::string name; Trigger trigger = Trigger::ItemObtained; uint32_t matchMask = 0u; ItemId item = ItemId::None; Block block = Block::Air; Species species = Species::Count; DimensionId dimension = DimensionId::Overworld; PotionKind potion = PotionKind::Count; VillagerProfession profession = VillagerProfession::Unemployed; ItemId tool = ItemId::None; int requiredCount = 1; bool accepts(const Event& event) const; }; struct AdvancementReward { int experience = 0; std::vector recipes; std::vector lootTables; bool empty() const { return experience == 0 && recipes.empty() && lootTables.empty(); } }; // One node of the tree. An empty parent path marks a root. // // requirements is a list of groups: the advancement is granted once every group // has at least one completed criterion, which covers both "all of them" (one // group per criterion, or an empty list) and "any of these" (one group holding // several names). An empty requirements list means every criterion is needed. struct Advancement { AdvancementId id; AdvancementId parent; std::string title; std::string description; // Stays out of sight until the parent is granted. bool hidden = false; std::vector criteria; std::vector> requirements; AdvancementReward reward; bool isRoot() const { return parent.path.empty(); } const AdvancementCriterion* findCriterion(const std::string& criterionName) const; bool satisfiedBy(const std::vector& completed) const; // Names used in requirements that no criterion declares. std::vector danglingRequirements() const; }; // Every advancement of a world, indexed by id, plus the parent links between // them. The tree tolerates a missing or looping parent: it reports both rather // than refusing to hold the node. class AdvancementTree { public: bool add(const Advancement& advancement); void clear(); const Advancement* find(const AdvancementId& id) const; std::size_t size() const { return nodes.size(); } std::vector ids() const; std::vector roots() const; std::vector childrenOf(const AdvancementId& id) const; // 0 for a root, -1 when the chain runs into a cycle or a missing parent. int depthOf(const AdvancementId& id) const; bool cycleFrom(const AdvancementId& id) const; bool hasCycle() const; // Ids naming a parent that was never loaded. std::vector missingParents() const; private: std::map nodes; }; // One player's progress against one tree. Time is the caller's logical tick, so // replaying the same events in the same order always yields the same record. class AdvancementProgress { public: explicit AdvancementProgress(const AdvancementTree& tree) : source(&tree) {} // Feeds one event to every criterion that accepts it and returns the // advancements this event completed, in id order. std::vector notify(const AdvancementCriterion::Event& event, long long tick); bool criterionDone(const AdvancementId& id, const std::string& criterion) const; int criterionProgress(const AdvancementId& id, const std::string& criterion) const; // Tick the criterion completed on, -1 while it is still open. long long criterionTick(const AdvancementId& id, const std::string& criterion) const; std::vector completedCriteria(const AdvancementId& id) const; bool granted(const AdvancementId& id) const; long long grantedTick(const AdvancementId& id) const; // Hidden advancements stay invisible until their parent is granted. bool visible(const AdvancementId& id) const; std::vector grantedIds() const; int grantedCount() const { return static_cast(grants.size()); } int totalExperience() const { return experience; } const std::vector& unlockedRecipes() const { return recipes; } const std::vector& awardedLootTables() const { return lootTables; } const AdvancementTree& tree() const { return *source; } private: struct CriterionState { int progress = 0; bool done = false; long long tick = -1; }; const AdvancementTree* source; std::map> states; std::map grants; std::vector recipes; std::vector lootTables; int experience = 0; }; const char* advancementTriggerName(AdvancementCriterion::Trigger trigger); bool parseAdvancementTriggerName(const std::string& text, AdvancementCriterion::Trigger& out); bool parseAdvancementJson(const JsonValue& root, const std::string& defaultSpace, Advancement& out, std::string& problem); // Reads every document the loader kept under data//advancements/. A file // that fails to read costs that advancement and appends one error; the rest of // the tree still loads. AdvancementTree loadAdvancements(const DataPackLoader& loader, std::vector& errors); } // namespace vf