diff --git a/tests/testLootTables.cpp b/tests/testLootTables.cpp index 2005e7a..513226d 100644 --- a/tests/testLootTables.cpp +++ b/tests/testLootTables.cpp @@ -478,3 +478,168 @@ VF_TEST(ironGolemsIgnoreLootingWhileZombiesDoNot) { LootContext looted = plain; addEnchant(looted.toolEnchants, EnchantKind::Looting, 3); + int golem = registry.rollMob(Species::IronGolem, plain).countOf(ItemId::IronIngot); + golemLowest = std::min(golemLowest, golem); + golemHighest = std::max(golemHighest, golem); + golemLootedHighest = std::max(golemLootedHighest, + registry.rollMob(Species::IronGolem, looted) + .countOf(ItemId::IronIngot)); + + zombieHighest = std::max(zombieHighest, + registry.rollMob(Species::Zombie, plain) + .countOf(ItemId::RottenFlesh)); + zombieLootedHighest = std::max(zombieLootedHighest, + registry.rollMob(Species::Zombie, looted) + .countOf(ItemId::RottenFlesh)); + } + CHECK_EQ(golemLowest, 3); + CHECK_EQ(golemHighest, 5); + CHECK_EQ(golemLootedHighest, 5); + CHECK_EQ(zombieHighest, 2); + CHECK_EQ(zombieLootedHighest, 5); +} + +VF_TEST(villagersLeaveNothingBehindAndSayItInTheirRule) { + const LootRegistry& registry = lootRegistry(); + const MobDropRule& villager = registry.mobRule(Species::Villager); + CHECK_EQ(villager.drop, ItemId::None); + CHECK_EQ(villager.rareDrop, ItemId::None); + CHECK(!villager.hostile); + CHECK(mobRuleTable(villager).empty()); + + for (int i = 0; i < 200; ++i) { + LootContext context = enchantedWith(ItemId::DiamondSword, EnchantKind::Looting, 3, + seedFor(i)); + context.playerKill = true; + CHECK(registry.rollMob(Species::Villager, context).empty()); + } +} + +VF_TEST(everyStructureChestFillsInsideItsDeclaredBoundsAndOnlyFromItsOwnPool) { + const LootRegistry& registry = lootRegistry(); + const struct { Structure structure; int lowest; int highest; } bounds[] = { + {Structure::Village, 3, 20}, {Structure::Dungeon, 2, 16}, + {Structure::Mineshaft, 3, 40}, {Structure::Temple, 2, 32}, + {Structure::NetherFortress, 2, 20}, {Structure::EndShip, 2, 18}, + }; + for (const auto& entry : bounds) { + const ChestFill& fill = registry.chestFill(entry.structure); + CHECK_EQ(fill.structure, entry.structure); + CHECK(!fill.pool.entries.empty()); + + int seenLowest = 1000; + int seenHighest = 0; + int strayItems = 0; + for (int i = 0; i < 700; ++i) { + LootRoll roll = registry.rollChest(entry.structure, contextWith(ItemId::None, + seedFor(i))); + seenLowest = std::min(seenLowest, roll.totalCount()); + seenHighest = std::max(seenHighest, roll.totalCount()); + for (const ItemSlot& stack : roll.items) { + if (!poolCanYield(fill.pool, stack.item)) ++strayItems; + } + } + CHECK_EQ(strayItems, 0); + CHECK(seenLowest >= entry.lowest); + CHECK(seenHighest <= entry.highest); + CHECK(seenHighest > seenLowest); + } + CHECK(registry.rollChest(Structure::Count, contextWith(ItemId::None, 1u)).empty()); +} + +VF_TEST(structureChestCountsStayInsideTheRangeTheStructureDeclares) { + const LootRegistry& registry = lootRegistry(); + for (int index = 0; index < static_cast(Structure::Count); ++index) { + Structure structure = static_cast(index); + const ChestFill& fill = registry.chestFill(structure); + CHECK(fill.chestsMin >= 1); + CHECK(fill.chestsMax >= fill.chestsMin); + + int seenLowest = 1000; + int seenHighest = 0; + for (int i = 0; i < 400; ++i) { + int count = registry.chestCountFor(structure, seedFor(i)); + seenLowest = std::min(seenLowest, count); + seenHighest = std::max(seenHighest, count); + } + CHECK_EQ(seenLowest, fill.chestsMin); + CHECK_EQ(seenHighest, fill.chestsMax); + CHECK_EQ(registry.chestCountFor(structure, 77u), registry.chestCountFor(structure, 77u)); + } + CHECK_EQ(registry.chestCountFor(Structure::Count, 5u), 0); +} + +VF_TEST(diamondGearOnlyTurnsUpInTheEndShipAndPickaxesOnlyInMineshafts) { + const LootRegistry& registry = lootRegistry(); + int endShipSwords = 0; + int mineshaftPickaxes = 0; + int elsewhereGear = 0; + + for (int index = 0; index < static_cast(Structure::Count); ++index) { + Structure structure = static_cast(index); + for (int i = 0; i < 600; ++i) { + LootRoll roll = registry.rollChest(structure, contextWith(ItemId::None, seedFor(i))); + int swords = roll.countOf(ItemId::DiamondSword); + int pickaxes = roll.countOf(ItemId::IronPickaxe); + if (structure == Structure::EndShip) { + endShipSwords += swords; + } else { + elsewhereGear += swords; + } + if (structure == Structure::Mineshaft) { + mineshaftPickaxes += pickaxes; + } else { + elsewhereGear += pickaxes; + } + } + } + CHECK(endShipSwords > 0); + CHECK(mineshaftPickaxes > 0); + CHECK_EQ(elsewhereGear, 0); +} + +VF_TEST(fishingSplitsIntoThreePoolsAndLuckShiftsTheWeightsAwayFromJunk) { + const LootRegistry& registry = lootRegistry(); + int plainFish = 0; + int plainTreasure = 0; + int plainJunk = 0; + int luckyTreasure = 0; + int luckyJunk = 0; + + for (int i = 0; i < 4000; ++i) { + FishingLoot plain = registry.rollFishing(contextWith(ItemId::WoodHoe, seedFor(i))); + CHECK_EQ(plain.fishWeight, fishingFishWeight); + CHECK_EQ(plain.treasureWeight, fishingTreasureWeight); + CHECK_EQ(plain.junkWeight, fishingJunkWeight); + if (plain.pool == FishingLoot::Pool::Fish) ++plainFish; + if (plain.pool == FishingLoot::Pool::Treasure) ++plainTreasure; + if (plain.pool == FishingLoot::Pool::Junk) ++plainJunk; + + FishingLoot lucky = registry.rollFishing( + enchantedWith(ItemId::WoodHoe, EnchantKind::Fortune, 3, seedFor(i))); + CHECK_EQ(lucky.treasureWeight, fishingTreasureWeight + 3 * fishingLuckWeightShift); + CHECK_EQ(lucky.junkWeight, fishingJunkWeight - 3 * fishingLuckWeightShift); + if (lucky.pool == FishingLoot::Pool::Treasure) ++luckyTreasure; + if (lucky.pool == FishingLoot::Pool::Junk) ++luckyJunk; + } + CHECK(plainFish > plainTreasure + plainJunk); + CHECK(plainTreasure > 0); + CHECK(plainJunk > 0); + CHECK(luckyTreasure > plainTreasure); + CHECK(luckyJunk < plainJunk); + + // An over-cap Fortune cannot come from addEnchant, but it can be placed on + // a set by hand, and it must clamp the junk weight at nothing rather than + // going negative and eating the treasure band. + int junkAtHighLuck = 0; + for (int i = 0; i < 500; ++i) { + LootContext overCharged = contextWith(ItemId::WoodHoe, seedFor(i)); + overCharged.toolEnchants.entries.push_back({EnchantKind::Fortune, 9}); + FishingLoot outcome = registry.rollFishing(overCharged); + CHECK_EQ(outcome.junkWeight, 0); + CHECK_EQ(outcome.treasureWeight, fishingTreasureWeight + 9 * fishingLuckWeightShift); + if (outcome.pool == FishingLoot::Pool::Junk) ++junkAtHighLuck; + } + CHECK_EQ(junkAtHighLuck, 0); +} +