From 1892e1d612c036a89d66a6eb0ad0bef379e02cef Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 2 Sep 2026 20:15:06 +0200 Subject: [PATCH] feat(vf): add BlockDropRule and MobDropRule --- core/include/vf/LootTables.h | 187 +++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 core/include/vf/LootTables.h diff --git a/core/include/vf/LootTables.h b/core/include/vf/LootTables.h new file mode 100644 index 0000000..9d53148 --- /dev/null +++ b/core/include/vf/LootTables.h @@ -0,0 +1,187 @@ +#pragma once + +#include +#include + +#include "Loot.h" + +namespace vf { + +// The item palette carries no shears, and correctToolFor already treats leaves +// as a hoe block, so the hoe is the shear class tool throughout the registry. +constexpr ToolType shearToolType = ToolType::Hoe; + +constexpr int fishingBaseWaitTicks = 600; +constexpr int fishingMinWaitTicks = 100; +constexpr int fishingLureTicksPerLevel = 100; +constexpr int fishingFishWeight = 85; +constexpr int fishingTreasureWeight = 5; +constexpr int fishingJunkWeight = 10; +constexpr int fishingLuckWeightShift = 2; + +// Harvest level a tier reaches. Gold sits at the wooden level, which is why a +// golden pickaxe cannot take a diamond. +int toolHarvestLevel(ToolTier tier); + +// One authored row per block in the palette. `harvestTool` always names the tool +// class the block is broken with, whether or not a drop depends on it; +// `toolRequiredForDrop` is what actually gates the drop. +struct BlockDropRule { + Block block = Block::Air; + // ItemId::None means the block yields nothing. For the blocks added after + // the item palette froze at ItemId::Ladder that is a decision, not a gap: + // there is no item to hand out and none is invented. + ItemId drop = ItemId::None; + int minCount = 1; + int maxCount = 1; + ToolType harvestTool = ToolType::None; + ToolTier minimumTier = ToolTier::None; + bool toolRequiredForDrop = false; + // What Silk Touch takes instead; ItemId::None falls back to `drop`. + ItemId silkDrop = ItemId::None; + // Shear class tools take the silk drop too, without the enchantment. + bool shearHarvest = false; + // What comes out when the block breaks inside fire; ItemId::None means fire + // changes nothing. + ItemId firedDrop = ItemId::None; + // Ore style: Fortune multiplies the whole count. + bool fortuneMultiplies = false; + // Leaf style: Fortune improves the odds instead of the count. + float dropChance = 1.0f; + float chancePerFortune = 0.0f; + ItemId bonusDrop = ItemId::None; + float bonusChance = 0.0f; + int bonusMin = 1; + int bonusMax = 1; + + ItemId silkTouchDrop() const { return silkDrop != ItemId::None ? silkDrop : drop; } + // Ice yields nothing when it is smashed but something when it is taken + // whole, so the silk branch has to count here too. + bool yieldsNothing() const { + return silkTouchDrop() == ItemId::None && bonusDrop == ItemId::None; + } +}; + +struct MobDropRule { + enum class Species : uint8_t { + Zombie, Husk, Drowned, ZombiePigman, Skeleton, Stray, Creeper, Witch, Spider, CaveSpider, + Blaze, Ghast, IronGolem, Villager, Sheep, Cow, Pig, + Count + }; + + Species species = Species::Zombie; + const char* name = ""; + ItemId drop = ItemId::None; + int minCount = 0; + int maxCount = 0; + bool lootingRaisesCount = true; + bool dropNeedsPlayerKill = false; + // Cooked counterpart when the kill was fiery; ItemId::None means fire + // changes nothing. + ItemId firedDrop = ItemId::None; + ItemId rareDrop = ItemId::None; + float rareChance = 0.0f; + float rareChancePerLooting = 0.0f; + bool rareNeedsPlayerKill = true; + int experience = 0; + bool undead = false; + bool arthropod = false; + bool hostile = true; +}; + +struct ChestFill { + enum class Structure : uint8_t { + Village, Dungeon, Mineshaft, Temple, NetherFortress, EndShip, + Count + }; + + Structure structure = Structure::Village; + const char* name = ""; + LootPool pool; + // How many chests one generated structure of this kind holds. + int chestsMin = 1; + int chestsMax = 1; +}; + +struct FishingLoot { + // EnchantKind has no Luck of the Sea and no Lure, so Fortune on the rod + // shifts the pool weights and Efficiency shortens the wait. The palette has + // no fish either: the catch pool hands back the raw meats it does have. + enum class Pool : uint8_t { Fish, Treasure, Junk, Count }; + + Pool pool = Pool::Fish; + LootRoll caught; + int waitTicks = 0; + // The weights the roll actually used, after the luck shift. + int fishWeight = 0; + int treasureWeight = 0; + int junkWeight = 0; +}; + +// A handle to any table in the registry, so a caller can hold on to one without +// caring which family it came from. +struct LootTableId { + enum class Domain : uint8_t { Block, Mob, Chest, Fishing, Count }; + + Domain domain = Domain::Block; + uint16_t index = 0; + + static LootTableId ofBlock(Block block); + static LootTableId ofMob(MobDropRule::Species species); + static LootTableId ofChest(ChestFill::Structure structure); + static LootTableId ofFishing(FishingLoot::Pool pool); + + bool valid() const; + bool operator==(const LootTableId& other) const { + return domain == other.domain && index == other.index; + } + bool operator!=(const LootTableId& other) const { return !(*this == other); } +}; + +// Whether the tool in hand clears the rule's tool class and harvest level. +bool toolAllows(const BlockDropRule& rule, ItemId tool); + +// Rules expressed as the pools the shared roller in Loot.cpp understands. +LootTable blockRuleTable(const BlockDropRule& rule); +LootTable mobRuleTable(const MobDropRule& rule); + +// Every block, mob, structure chest and fishing pool as data, authored one row +// per enum value and in enum order. +class LootRegistry { +public: + LootRegistry(); + + const BlockDropRule& blockRule(Block block) const; + const MobDropRule& mobRule(MobDropRule::Species species) const; + const ChestFill& chestFill(ChestFill::Structure structure) const; + + const std::vector& blockRules() const { return blocks; } + const std::vector& mobRules() const { return mobs; } + const std::vector& chestFills() const { return chests; } + + // Each of these checks the authored table is complete and in enum order. + bool coversEveryBlock() const; + bool coversEveryMob() const; + bool coversEveryStructure() const; + + const LootTable& tableFor(LootTableId id) const; + + LootRoll rollBlock(Block block, const LootContext& context) const; + LootRoll rollMob(MobDropRule::Species species, const LootContext& context) const; + LootRoll rollChest(ChestFill::Structure structure, const LootContext& context) const; + int chestCountFor(ChestFill::Structure structure, uint32_t seed) const; + FishingLoot rollFishing(const LootContext& context) const; + +private: + std::vector blocks; + std::vector mobs; + std::vector chests; + std::vector blockTables; + std::vector mobTables; + std::vector chestTables; + std::vector fishingTables; +}; + +const LootRegistry& lootRegistry(); + +} // namespace vf