test: extend coverage of testLoot
This commit is contained in:
1 parent
80ef7386dc
commit
a0dca2c904
1 file changed
+208
@@ -313,3 +313,211 @@ VF_TEST(lootContextForCarriesTheKillOverAndRefusesADeadSeed) {
|
|||||||
EnchantSet enchants;
|
EnchantSet enchants;
|
||||||
addEnchant(enchants, EnchantKind::Looting, 2);
|
addEnchant(enchants, EnchantKind::Looting, 2);
|
||||||
|
|
||||||
|
HurtSource blast = environmentSource(DamageType::Explosion);
|
||||||
|
LootContext fromBlast = lootContextFor(blast, ItemId::IronSword, enchants, 0u);
|
||||||
|
CHECK_EQ(fromBlast.killedBy, DamageType::Explosion);
|
||||||
|
CHECK(!fromBlast.fiery);
|
||||||
|
CHECK(!fromBlast.playerKill);
|
||||||
|
CHECK_EQ(fromBlast.tool, ItemId::IronSword);
|
||||||
|
CHECK_EQ(fromBlast.looting(), 2);
|
||||||
|
CHECK(fromBlast.seed != 0u);
|
||||||
|
|
||||||
|
HurtSource lava = environmentSource(DamageType::Lava);
|
||||||
|
LootContext fromLava = lootContextFor(lava, ItemId::None, EnchantSet{}, 4242u);
|
||||||
|
CHECK(fromLava.fiery);
|
||||||
|
CHECK_EQ(fromLava.seed, 4242u);
|
||||||
|
|
||||||
|
HurtSource swing = meleeSource(1, Vec3{}, true);
|
||||||
|
LootContext fromSwing = lootContextFor(swing, ItemId::DiamondSword, EnchantSet{}, 9u);
|
||||||
|
CHECK(fromSwing.playerKill);
|
||||||
|
CHECK(!fromSwing.fiery);
|
||||||
|
CHECK_EQ(fromSwing.looting(), 0);
|
||||||
|
CHECK_EQ(fromSwing.fortune(), 0);
|
||||||
|
CHECK(!fromSwing.silkTouch());
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(smeltedFormOnlyRewritesTheItemsThatHaveACookedCounterpart) {
|
||||||
|
CHECK_EQ(smeltedForm(ItemId::RawPorkchop), ItemId::CookedPorkchop);
|
||||||
|
CHECK_EQ(smeltedForm(ItemId::RawBeef), ItemId::CookedBeef);
|
||||||
|
CHECK_EQ(smeltedForm(ItemId::RawMutton), ItemId::CookedMutton);
|
||||||
|
CHECK_EQ(smeltedForm(ItemId::IronOre), ItemId::IronIngot);
|
||||||
|
CHECK_EQ(smeltedForm(ItemId::GoldOre), ItemId::GoldIngot);
|
||||||
|
|
||||||
|
const ItemId unchanged[] = {ItemId::None, ItemId::Stone, ItemId::CookedBeef,
|
||||||
|
ItemId::DiamondGem, ItemId::RottenFlesh, ItemId::Bread};
|
||||||
|
for (ItemId item : unchanged) CHECK_EQ(smeltedForm(item), item);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(chestPoolsStayInsideTheirDeclaredRollAndCountBounds) {
|
||||||
|
const struct { LootTable::ChestKind kind; int lowest; int highest; } chests[] = {
|
||||||
|
{LootTable::ChestKind::Dungeon, 2, 16},
|
||||||
|
{LootTable::ChestKind::Village, 3, 20},
|
||||||
|
{LootTable::ChestKind::Temple, 2, 32},
|
||||||
|
};
|
||||||
|
for (const auto& chest : chests) {
|
||||||
|
int seenLowest = 1000;
|
||||||
|
int seenHighest = 0;
|
||||||
|
for (int i = 0; i < 800; ++i) {
|
||||||
|
LootRoll roll = rollChestContents(chest.kind, contextWith(ItemId::None, seedFor(i)));
|
||||||
|
seenLowest = std::min(seenLowest, roll.totalCount());
|
||||||
|
seenHighest = std::max(seenHighest, roll.totalCount());
|
||||||
|
}
|
||||||
|
CHECK(seenLowest >= chest.lowest);
|
||||||
|
CHECK(seenHighest <= chest.highest);
|
||||||
|
CHECK(seenHighest > seenLowest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(rareChestEntriesShowUpFarLessOftenThanCommonOnes) {
|
||||||
|
int diamonds = 0;
|
||||||
|
int iron = 0;
|
||||||
|
for (int i = 0; i < 4000; ++i) {
|
||||||
|
LootRoll roll = rollChestContents(LootTable::ChestKind::Dungeon,
|
||||||
|
contextWith(ItemId::None, seedFor(i)));
|
||||||
|
if (roll.countOf(ItemId::DiamondGem) > 0) ++diamonds;
|
||||||
|
if (roll.countOf(ItemId::IronIngot) > 0) ++iron;
|
||||||
|
}
|
||||||
|
CHECK(diamonds > 0);
|
||||||
|
CHECK(iron > diamonds * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(toolsComeOutOfAChestAsSeparateFullyRepairedStacks) {
|
||||||
|
int hoeStacks = 0;
|
||||||
|
int badStacks = 0;
|
||||||
|
for (int i = 0; i < 600; ++i) {
|
||||||
|
LootRoll roll = rollChestContents(LootTable::ChestKind::Village,
|
||||||
|
contextWith(ItemId::None, seedFor(i)));
|
||||||
|
for (const ItemSlot& stack : roll.items) {
|
||||||
|
if (stack.item != ItemId::WoodHoe) continue;
|
||||||
|
++hoeStacks;
|
||||||
|
if (stack.count != 1) ++badStacks;
|
||||||
|
if (stack.durability != toolMaxDurability(ItemId::WoodHoe)) ++badStacks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CHECK(hoeStacks > 0);
|
||||||
|
CHECK_EQ(badStacks, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(stacksOfTheSameMaterialMergeInsteadOfPilingUpAsSeparateEntries) {
|
||||||
|
LootPool pool;
|
||||||
|
pool.minRolls = 4;
|
||||||
|
pool.maxRolls = 4;
|
||||||
|
LootEntry ingot;
|
||||||
|
ingot.item = ItemId::IronIngot;
|
||||||
|
pool.entries.push_back(ingot);
|
||||||
|
|
||||||
|
LootTable table;
|
||||||
|
table.pools.push_back(pool);
|
||||||
|
|
||||||
|
LootRoll roll = rollLootTable(table, contextWith(ItemId::None, 77u));
|
||||||
|
CHECK_EQ(roll.items.size(), std::size_t{1});
|
||||||
|
CHECK_EQ(roll.countOf(ItemId::IronIngot), 4);
|
||||||
|
CHECK_EQ(roll.totalCount(), 4);
|
||||||
|
CHECK_EQ(roll.countOf(ItemId::GoldIngot), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(killConditionsGateEntriesOnHowTheTargetActuallyDied) {
|
||||||
|
LootTable table = singlePool({entryOf(ItemId::Gunpowder, LootEntry::Explosive),
|
||||||
|
entryOf(ItemId::Stick, LootEntry::Ranged)});
|
||||||
|
|
||||||
|
LootContext blasted = contextWith(ItemId::None, 13u);
|
||||||
|
blasted.killedBy = DamageType::Explosion;
|
||||||
|
LootRoll fromBlast = rollLootTable(table, blasted);
|
||||||
|
CHECK_EQ(fromBlast.countOf(ItemId::Gunpowder), 1);
|
||||||
|
CHECK_EQ(fromBlast.countOf(ItemId::Stick), 0);
|
||||||
|
|
||||||
|
LootContext shot = contextWith(ItemId::None, 13u);
|
||||||
|
shot.killedBy = DamageType::Projectile;
|
||||||
|
LootRoll fromArrow = rollLootTable(table, shot);
|
||||||
|
CHECK_EQ(fromArrow.countOf(ItemId::Stick), 1);
|
||||||
|
CHECK_EQ(fromArrow.countOf(ItemId::Gunpowder), 0);
|
||||||
|
|
||||||
|
LootContext punched = contextWith(ItemId::None, 13u);
|
||||||
|
punched.killedBy = DamageType::Melee;
|
||||||
|
CHECK(rollLootTable(table, punched).empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(enchantmentConditionsGateEntriesOnTheToolThatWasUsed) {
|
||||||
|
LootTable table = singlePool({entryOf(ItemId::EmeraldGem, LootEntry::NeedsLooting),
|
||||||
|
entryOf(ItemId::LapisLazuli, LootEntry::NeedsFortune),
|
||||||
|
entryOf(ItemId::Stick, LootEntry::NotSilkTouched)});
|
||||||
|
|
||||||
|
LootContext bare = contextWith(ItemId::None, 21u);
|
||||||
|
LootRoll plain = rollLootTable(table, bare);
|
||||||
|
CHECK_EQ(plain.countOf(ItemId::Stick), 1);
|
||||||
|
CHECK_EQ(plain.totalCount(), 1);
|
||||||
|
|
||||||
|
LootContext silk = enchanted(ItemId::IronPickaxe, EnchantKind::SilkTouch, 1, 21u);
|
||||||
|
CHECK(rollLootTable(table, silk).empty());
|
||||||
|
|
||||||
|
LootContext lucky = enchanted(ItemId::IronPickaxe, EnchantKind::Fortune, 2, 21u);
|
||||||
|
LootRoll withFortune = rollLootTable(table, lucky);
|
||||||
|
CHECK_EQ(withFortune.totalCount(), 1);
|
||||||
|
CHECK(withFortune.countOf(ItemId::LapisLazuli) + withFortune.countOf(ItemId::Stick) == 1);
|
||||||
|
CHECK_EQ(withFortune.countOf(ItemId::EmeraldGem), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(theSmeltIfFieryFunctionRewritesTheItemAtEmitTime) {
|
||||||
|
LootEntry beef;
|
||||||
|
beef.item = ItemId::RawBeef;
|
||||||
|
beef.minCount = 2;
|
||||||
|
beef.maxCount = 2;
|
||||||
|
beef.functions = LootEntry::SmeltIfFiery;
|
||||||
|
LootTable table = singlePool({beef});
|
||||||
|
|
||||||
|
LootContext cold = contextWith(ItemId::None, 5u);
|
||||||
|
LootRoll raw = rollLootTable(table, cold);
|
||||||
|
CHECK_EQ(raw.countOf(ItemId::RawBeef), 2);
|
||||||
|
|
||||||
|
LootContext burning = contextWith(ItemId::None, 5u);
|
||||||
|
burning.fiery = true;
|
||||||
|
LootRoll cooked = rollLootTable(table, burning);
|
||||||
|
CHECK_EQ(cooked.countOf(ItemId::CookedBeef), 2);
|
||||||
|
CHECK_EQ(cooked.countOf(ItemId::RawBeef), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(bonusRollsPerLootingAddWholeExtraPullsToAPool) {
|
||||||
|
LootPool pool;
|
||||||
|
pool.bonusRollsPerLooting = 2;
|
||||||
|
LootEntry gold;
|
||||||
|
gold.item = ItemId::GoldIngot;
|
||||||
|
pool.entries.push_back(gold);
|
||||||
|
LootTable table;
|
||||||
|
table.pools.push_back(pool);
|
||||||
|
|
||||||
|
CHECK_EQ(rollLootTable(table, contextWith(ItemId::None, 3u)).countOf(ItemId::GoldIngot), 1);
|
||||||
|
|
||||||
|
LootContext looted = enchanted(ItemId::IronSword, EnchantKind::Looting, 3, 3u);
|
||||||
|
CHECK_EQ(rollLootTable(table, looted).countOf(ItemId::GoldIngot), 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(anEmptyOrOutOfRangeTableRollsToNothingInsteadOfReadingPastItsEnd) {
|
||||||
|
LootContext context = contextWith(ItemId::DiamondPickaxe, 1u);
|
||||||
|
CHECK(rollLootTable(LootTable{}, context).empty());
|
||||||
|
|
||||||
|
CHECK(blockLootTable(Block::Count).empty());
|
||||||
|
CHECK(mobLootTable(LootTable::MobKind::Count).empty());
|
||||||
|
CHECK(chestLootTable(LootTable::ChestKind::Count).empty());
|
||||||
|
|
||||||
|
CHECK(rollMobDrops(LootTable::MobKind::Count, context).empty());
|
||||||
|
CHECK(rollChestContents(LootTable::ChestKind::Count, context).empty());
|
||||||
|
|
||||||
|
// Air has no item form, so its self-drop table comes out empty rather than
|
||||||
|
// dropping a phantom stack.
|
||||||
|
CHECK(rollBlockDrops(Block::Air, context).empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(countOfAndTotalCountAgreeWithTheStacksTheyWereBuiltFrom) {
|
||||||
|
LootRoll roll;
|
||||||
|
roll.items.push_back({ItemId::IronIngot, 3, 0});
|
||||||
|
roll.items.push_back({ItemId::Bread, 2, 0});
|
||||||
|
roll.items.push_back({ItemId::IronIngot, 4, 0});
|
||||||
|
|
||||||
|
CHECK_EQ(roll.countOf(ItemId::IronIngot), 7);
|
||||||
|
CHECK_EQ(roll.countOf(ItemId::Bread), 2);
|
||||||
|
CHECK_EQ(roll.countOf(ItemId::DiamondGem), 0);
|
||||||
|
CHECK_EQ(roll.totalCount(), 9);
|
||||||
|
CHECK(!roll.empty());
|
||||||
|
CHECK(LootRoll{}.empty());
|
||||||
|
CHECK_EQ(LootRoll{}.totalCount(), 0);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user