From ad735881903253355dae2d87d81db736eb0d9e40 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 22:44:39 +0200 Subject: [PATCH] test: extend coverage of seedFor and contextWith --- tests/testLootTables.cpp | 162 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 tests/testLootTables.cpp diff --git a/tests/testLootTables.cpp b/tests/testLootTables.cpp new file mode 100644 index 0000000..8caf423 --- /dev/null +++ b/tests/testLootTables.cpp @@ -0,0 +1,162 @@ +#include +#include + +#include "testHarness.h" +#include "vf/LootTables.h" + +using namespace vf; + +namespace { + +using Species = MobDropRule::Species; +using Structure = ChestFill::Structure; + +uint32_t seedFor(int index) { + uint32_t mixed = static_cast(index) + 1u; + mixed ^= mixed >> 16; + mixed *= 0x7feb352du; + mixed ^= mixed >> 15; + mixed *= 0x846ca68bu; + mixed ^= mixed >> 16; + return mixed == 0u ? 1u : mixed; +} + +LootContext contextWith(ItemId tool, uint32_t seed) { + LootContext context; + context.tool = tool; + context.seed = seed; + return context; +} + +LootContext enchantedWith(ItemId tool, EnchantKind kind, int level, uint32_t seed) { + LootContext context = contextWith(tool, seed); + addEnchant(context.toolEnchants, kind, level); + return context; +} + +bool sameRoll(const LootRoll& left, const LootRoll& right) { + if (left.items.size() != right.items.size()) return false; + for (std::size_t i = 0; i < left.items.size(); ++i) { + if (left.items[i].item != right.items[i].item) return false; + if (left.items[i].count != right.items[i].count) return false; + if (left.items[i].durability != right.items[i].durability) return false; + } + return true; +} + +ItemId bestToolFor(ToolType type) { + switch (type) { + case ToolType::Pickaxe: return ItemId::DiamondPickaxe; + case ToolType::Axe: return ItemId::DiamondAxe; + case ToolType::Shovel: return ItemId::DiamondShovel; + case ToolType::Sword: return ItemId::DiamondSword; + case ToolType::Hoe: return ItemId::DiamondHoe; + case ToolType::None: return ItemId::None; + } + return ItemId::None; +} + +const std::vector& toolPalette() { + static const std::vector tools = { + ItemId::None, ItemId::WoodPickaxe, ItemId::WoodAxe, ItemId::WoodShovel, + ItemId::WoodSword, ItemId::WoodHoe, ItemId::StonePickaxe, ItemId::StoneAxe, + ItemId::StoneShovel, ItemId::StoneSword, ItemId::StoneHoe, ItemId::IronPickaxe, + ItemId::IronAxe, ItemId::IronShovel, ItemId::IronSword, ItemId::IronHoe, + ItemId::GoldPickaxe, ItemId::GoldAxe, ItemId::GoldShovel, ItemId::GoldSword, + ItemId::GoldHoe, ItemId::DiamondPickaxe, ItemId::DiamondAxe, ItemId::DiamondShovel, + ItemId::DiamondSword, ItemId::DiamondHoe, + }; + return tools; +} + +bool ruleCanYield(const BlockDropRule& rule, ItemId item) { + return item == rule.drop || item == rule.silkTouchDrop() || item == rule.firedDrop || + item == rule.bonusDrop; +} + +bool poolCanYield(const LootPool& pool, ItemId item) { + for (const LootEntry& entry : pool.entries) { + if (entry.item == item) return true; + } + return false; +} + +ItemId firstEntryOfPool(const LootTable& table, std::size_t poolIndex) { + if (poolIndex >= table.pools.size()) return ItemId::None; + if (table.pools[poolIndex].entries.empty()) return ItemId::None; + return table.pools[poolIndex].entries[0].item; +} + +} // namespace + +VF_TEST(theRegistrysPublishedConstantsAreTheOnesTheBoundsWereDerivedFrom) { + CHECK_EQ(fishingBaseWaitTicks, 600); + CHECK_EQ(fishingMinWaitTicks, 100); + CHECK_EQ(fishingLureTicksPerLevel, 100); + CHECK_EQ(fishingFishWeight, 85); + CHECK_EQ(fishingTreasureWeight, 5); + CHECK_EQ(fishingJunkWeight, 10); + CHECK_EQ(fishingLuckWeightShift, 2); + CHECK_EQ(fishingFishWeight + fishingTreasureWeight + fishingJunkWeight, 100); + CHECK_EQ(shearToolType, ToolType::Hoe); + + CHECK_EQ(static_cast(Block::Count), 130); + CHECK_EQ(static_cast(Species::Count), 17); + CHECK_EQ(static_cast(Structure::Count), 6); + CHECK_EQ(static_cast(FishingLoot::Pool::Count), 3); +} + +VF_TEST(everyBlockInThePaletteCarriesAnAuthoredRuleInEnumOrder) { + const LootRegistry& registry = lootRegistry(); + CHECK(registry.coversEveryBlock()); + CHECK_EQ(registry.blockRules().size(), static_cast(Block::Count)); + + int outOfOrder = 0; + for (int index = 0; index < static_cast(Block::Count); ++index) { + Block block = static_cast(index); + const BlockDropRule& rule = registry.blockRule(block); + if (rule.block != block) ++outOfOrder; + CHECK(rule.minCount >= 0); + CHECK(rule.maxCount >= rule.minCount); + CHECK(rule.dropChance > 0.0f); + CHECK(rule.dropChance <= 1.0f); + } + CHECK_EQ(outOfOrder, 0); + + CHECK(registry.coversEveryMob()); + CHECK_EQ(registry.mobRules().size(), static_cast(Species::Count)); + CHECK(registry.coversEveryStructure()); + CHECK_EQ(registry.chestFills().size(), static_cast(Structure::Count)); +} + +VF_TEST(everyRuleNamesTheSameToolClassTheItemModuleAlreadyDeclares) { + const LootRegistry& registry = lootRegistry(); + int mismatches = 0; + int constrained = 0; + for (int index = 0; index < static_cast(Block::Count); ++index) { + Block block = static_cast(index); + ToolType declared = correctToolFor(block); + if (declared == ToolType::None) continue; + ++constrained; + if (registry.blockRule(block).harvestTool != declared) ++mismatches; + } + CHECK_EQ(mismatches, 0); + CHECK_EQ(constrained, 28); +} + +VF_TEST(theRegistryIsNeverMorePermissiveThanTheSalvagedToolCheck) { + const LootRegistry& registry = lootRegistry(); + int leaks = 0; + int gatedCases = 0; + for (int index = 0; index < static_cast(Block::Count); ++index) { + Block block = static_cast(index); + for (ItemId tool : toolPalette()) { + if (toolQualifiesFor(block, tool)) continue; + ++gatedCases; + if (!registry.rollBlock(block, contextWith(tool, 1234u)).empty()) ++leaks; + } + } + CHECK_EQ(leaks, 0); + CHECK(gatedCases > 100); +} +