#include "testHarness.h" #include "vf/Equipment.h" #include #include using namespace vf; namespace { const CraftingRegistry& sharedRegistry() { static CraftingRegistry registry; return registry; } EnchantSet withEnchant(EnchantKind kind, int level) { EnchantSet set; addEnchant(set, kind, level); return set; } Equipment fullDiamond() { Equipment gear; gear.equipArmor(makeArmor(Armor::Material::Diamond, Armor::Slot::Helmet)); gear.equipArmor(makeArmor(Armor::Material::Diamond, Armor::Slot::Chestplate)); gear.equipArmor(makeArmor(Armor::Material::Diamond, Armor::Slot::Leggings)); gear.equipArmor(makeArmor(Armor::Material::Diamond, Armor::Slot::Boots)); return gear; } float modifierTotal(const EquipmentStats& stats, AttributeModifier::Attribute attribute) { float total = 0.0f; for (const AttributeModifier& modifier : stats.modifiers) { if (modifier.attribute == attribute) total += modifier.amount; } return total; } CombatStats readyToSwing() { CombatStats stats; stats.attackChargeTicks = 200; return stats; } } // namespace VF_TEST(everyEquipSlotAndAttributeHasAName) { for (int i = 0; i < static_cast(EquipSlot::Count); ++i) { CHECK(std::string(equipSlotName(static_cast(i))) != std::string("unknown")); } for (int i = 0; i < static_cast(AttributeModifier::Attribute::Count); ++i) { CHECK(std::string(attributeName(static_cast(i))) != std::string("unknown")); } CHECK(isArmorSlot(EquipSlot::Head)); CHECK(isArmorSlot(EquipSlot::Feet)); CHECK(!isArmorSlot(EquipSlot::MainHand)); CHECK(isHandSlot(EquipSlot::OffHand)); CHECK(armorSlotFor(EquipSlot::Legs) == Armor::Slot::Leggings); CHECK(armorSlotFor(EquipSlot::MainHand) == Armor::Slot::Count); CHECK(equipSlotFor(Armor::Slot::Boots) == EquipSlot::Feet); CHECK(equipSlotFor(Armor::Slot::Count) == EquipSlot::Count); } VF_TEST(anArmorSlotRefusesAnyItemStackBecauseNoItemIsWearable) { Equipment gear; MetaStack helmetShaped = makeStack(ItemId::Stone, 1); CHECK(!gear.accepts(EquipSlot::Head, helmetShaped)); CHECK(!gear.equip(EquipSlot::Head, helmetShaped)); CHECK(!gear.equip(EquipSlot::Feet, makeTool(ItemId::DiamondSword))); CHECK(gear.armorPiece(EquipSlot::Head).material == Armor::Material::None); CHECK(gear.equip(EquipSlot::MainHand, makeTool(ItemId::DiamondSword))); CHECK(gear.mainHand().item == ItemId::DiamondSword); CHECK(!gear.equip(EquipSlot::MainHand, MetaStack{})); CHECK(gear.mainHand().item == ItemId::DiamondSword); CHECK(gear.armorPiece(EquipSlot::MainHand).material == Armor::Material::None); CHECK(gear.hand(EquipSlot::Head).empty()); } VF_TEST(aPieceOfArmorOnlyGoesOnTheSlotItWasMadeFor) { Equipment gear; Armor helmet = makeArmor(Armor::Material::Iron, Armor::Slot::Helmet); CHECK(!gear.equipArmorAt(EquipSlot::Chest, helmet)); CHECK(!gear.equipArmorAt(EquipSlot::MainHand, helmet)); CHECK(gear.equipArmorAt(EquipSlot::Head, helmet)); CHECK(gear.armorPiece(EquipSlot::Head).material == Armor::Material::Iron); CHECK_EQ(gear.wornPieces(), 1); CHECK(!gear.empty()); CHECK(!gear.equipArmor(Armor{})); Armor removed = gear.unequipArmor(EquipSlot::Head); CHECK(removed.material == Armor::Material::Iron); CHECK_EQ(gear.wornPieces(), 0); CHECK(gear.empty()); CHECK(gear.unequipArmor(EquipSlot::OffHand).material == Armor::Material::None); } VF_TEST(statsSumArmorPointsAndToughnessAcrossTheWholeSet) { Equipment gear = fullDiamond(); EquipmentStats stats = gear.stats(); CHECK_EQ(stats.armorPoints, gear.armor().totalPoints()); CHECK_EQ(stats.armorPoints, 20); CHECK_NEAR(stats.armorToughness, 8.0f, 0.0001f); CHECK_NEAR(modifierTotal(stats, AttributeModifier::Attribute::ArmorPoints), 20.0f, 0.0001f); CHECK_NEAR(modifierTotal(stats, AttributeModifier::Attribute::ArmorToughness), 8.0f, 0.0001f); Equipment bare; EquipmentStats naked = bare.stats(); CHECK_EQ(naked.armorPoints, 0); CHECK_NEAR(naked.armorToughness, 0.0f, 0.0001f); CHECK(naked.modifiers.empty()); } VF_TEST(equipmentStatsFeedTheCombatPipelineDamageAlreadyUnderstands) { CombatStats bare; CombatStats armored; fullDiamond().stats().applyTo(armored); DamageEvent blow; blow.source = meleeSource(1, Vec3{0.0f, 0.0f, 0.0f}, false); blow.amount = 12.0f; EffectSet none; uint32_t rngBare = 42u; uint32_t rngArmored = 42u; CombatResult unprotected = applyDamage(bare, none, blow, rngBare); CombatResult protectedHit = applyDamage(armored, none, blow, rngArmored); CHECK(unprotected.applied); CHECK(protectedHit.applied); CHECK(protectedHit.healthLost < unprotected.healthLost); CHECK(protectedHit.armorDurabilityLost > 0); CHECK_EQ(unprotected.armorDurabilityLost, 0); CHECK(armored.armor.totalPoints() > 0); } VF_TEST(theHeldToolDecidesAttackDamageSpeedAndReach) { CHECK_NEAR(weaponAttackDamage(ItemId::DiamondSword), 7.0f, 0.0001f); CHECK_NEAR(weaponAttackDamage(ItemId::WoodSword), 4.0f, 0.0001f); CHECK_NEAR(weaponAttackDamage(ItemId::DiamondAxe), 9.0f, 0.0001f); CHECK_NEAR(weaponAttackDamage(ItemId::Stone), unarmedAttackDamage, 0.0001f); CHECK_NEAR(weaponAttackSpeed(ItemId::DiamondSword), 1.6f, 0.0001f); CHECK(weaponAttackSpeed(ItemId::DiamondAxe) < weaponAttackSpeed(ItemId::DiamondSword)); CHECK(weaponAttackReach(ItemId::DiamondSword) > weaponAttackReach(ItemId::DiamondPickaxe)); CHECK_NEAR(weaponAttackReach(ItemId::Stone), unarmedAttackReach, 0.0001f); Equipment gear; gear.equip(EquipSlot::MainHand, makeTool(ItemId::DiamondSword)); EquipmentStats stats = gear.stats(); CHECK_NEAR(stats.attackDamage, 7.0f, 0.0001f); CHECK_NEAR(stats.attackSpeed, 1.6f, 0.0001f); CHECK_NEAR(stats.attackReach, unarmedAttackReach + 0.5f, 0.0001f); CHECK_NEAR(modifierTotal(stats, AttributeModifier::Attribute::AttackDamage), 6.0f, 0.0001f);