diff --git a/core/src/Equipment.cpp b/core/src/Equipment.cpp index 3dfe21b..a75a302 100644 --- a/core/src/Equipment.cpp +++ b/core/src/Equipment.cpp @@ -151,3 +151,162 @@ const char* attributeName(AttributeModifier::Attribute attribute) { return "unknown"; } +float weaponAttackDamage(ItemId item) { + if (!isTool(item)) return unarmedAttackDamage; + const ItemInfo& info = itemInfo(item); + switch (info.toolType) { + case ToolType::Sword: return swordDamage(info.toolTier); + case ToolType::Axe: return axeDamage(info.toolTier); + case ToolType::Pickaxe: return pickaxeDamage(info.toolTier); + case ToolType::Shovel: return pickaxeDamage(info.toolTier) + 0.5f; + case ToolType::Hoe: return unarmedAttackDamage; + case ToolType::None: break; + } + return unarmedAttackDamage; +} + +float weaponAttackSpeed(ItemId item) { + if (!isTool(item)) return unarmedAttackSpeed; + const ItemInfo& info = itemInfo(item); + switch (info.toolType) { + case ToolType::Sword: return 1.6f; + case ToolType::Axe: return axeSpeed(info.toolTier); + case ToolType::Pickaxe: return 1.2f; + case ToolType::Shovel: return 1.0f; + case ToolType::Hoe: return hoeSpeed(info.toolTier); + case ToolType::None: break; + } + return unarmedAttackSpeed; +} + +float weaponAttackReach(ItemId item) { + if (!isTool(item)) return unarmedAttackReach; + switch (itemInfo(item).toolType) { + case ToolType::Sword: return unarmedAttackReach + 0.5f; + case ToolType::Axe: + case ToolType::Hoe: return unarmedAttackReach + 0.25f; + case ToolType::Pickaxe: + case ToolType::Shovel: + case ToolType::None: break; + } + return unarmedAttackReach; +} + +float armorWeightPenalty(int armorPoints) { + if (armorPoints <= 0) return 0.0f; + float penalty = 0.01f * static_cast(armorPoints); + return penalty > 0.35f ? 0.35f : penalty; +} + +void EquipmentStats::applyTo(CombatStats& stats) const { + stats.armor = armor; + stats.baseAttackDamage = attackDamage; + stats.attackSpeed = attackSpeed; +} + +bool Equipment::accepts(EquipSlot slot, const MetaStack& stack) const { + if (stack.empty()) return false; + if (!isHandSlot(slot)) return false; + return true; +} + +bool Equipment::equip(EquipSlot slot, const MetaStack& stack) { + if (!accepts(slot, stack)) return false; + hands[static_cast(handIndex(slot))] = stack; + return true; +} + +bool Equipment::equipArmor(const Armor& piece) { + return equipArmorAt(equipSlotFor(piece.slot), piece); +} + +bool Equipment::equipArmorAt(EquipSlot slot, const Armor& piece) { + if (!isArmorSlot(slot)) return false; + if (piece.material == Armor::Material::None) return false; + if (armorSlotFor(slot) != piece.slot) return false; + worn.at(piece.slot) = piece; + return true; +} + +MetaStack Equipment::unequip(EquipSlot slot) { + if (!isHandSlot(slot)) return MetaStack{}; + MetaStack removed = hands[static_cast(handIndex(slot))]; + hands[static_cast(handIndex(slot))].clear(); + return removed; +} + +Armor Equipment::unequipArmor(EquipSlot slot) { + if (!isArmorSlot(slot)) return Armor{}; + Armor& piece = worn.at(armorSlotFor(slot)); + Armor removed = piece; + piece = Armor{}; + return removed; +} + +const MetaStack& Equipment::hand(EquipSlot slot) const { + if (!isHandSlot(slot)) return noStack(); + return hands[static_cast(handIndex(slot))]; +} + +const Armor& Equipment::armorPiece(EquipSlot slot) const { + Armor::Slot mapped = armorSlotFor(slot); + if (mapped == Armor::Slot::Count) return noArmor(); + return worn.at(mapped); +} + +EquipmentStats Equipment::stats() const { + EquipmentStats stats; + stats.armor = worn; + stats.armorPoints = worn.totalPoints(); + stats.armorToughness = worn.totalToughness(); + + for (int i = 0; i < static_cast(Armor::Slot::Count); ++i) { + Armor::Slot armorSlot = static_cast(i); + const Armor& piece = worn.at(armorSlot); + if (!piece.worn()) continue; + EquipSlot slot = equipSlotFor(armorSlot); + addModifier(stats, AttributeModifier::Attribute::ArmorPoints, + AttributeModifier::Operation::Add, static_cast(armorPoints(piece)), slot); + addModifier(stats, AttributeModifier::Attribute::ArmorToughness, + AttributeModifier::Operation::Add, armorToughness(piece), slot); + } + + float weight = armorWeightPenalty(stats.armorPoints); + stats.movementSpeed = 1.0f - weight; + addModifier(stats, AttributeModifier::Attribute::MovementSpeed, + AttributeModifier::Operation::MultiplyTotal, -weight, EquipSlot::Chest); + + const MetaStack& weapon = hands[0]; + if (!weapon.empty()) { + stats.weaponEnchants = weapon.meta.enchants; + stats.attackDamage = weaponAttackDamage(weapon.item); + stats.attackSpeed = weaponAttackSpeed(weapon.item); + stats.attackReach = weaponAttackReach(weapon.item); + stats.miningSpeed = toolTierSpeedMultiplier(itemInfo(weapon.item).toolTier); + + int efficiency = weapon.meta.enchants.levelOf(EnchantKind::Efficiency); + if (efficiency > 0) { + float bonus = static_cast(efficiency * efficiency + 1); + stats.miningSpeed += bonus; + addModifier(stats, AttributeModifier::Attribute::MiningSpeed, + AttributeModifier::Operation::Add, bonus, EquipSlot::MainHand); + } + + addModifier(stats, AttributeModifier::Attribute::AttackDamage, + AttributeModifier::Operation::Add, stats.attackDamage - unarmedAttackDamage, + EquipSlot::MainHand); + addModifier(stats, AttributeModifier::Attribute::AttackSpeed, + AttributeModifier::Operation::Add, stats.attackSpeed - unarmedAttackSpeed, + EquipSlot::MainHand); + addModifier(stats, AttributeModifier::Attribute::AttackReach, + AttributeModifier::Operation::Add, stats.attackReach - unarmedAttackReach, + EquipSlot::MainHand); + addModifier(stats, AttributeModifier::Attribute::MiningSpeed, + AttributeModifier::Operation::Add, + toolTierSpeedMultiplier(itemInfo(weapon.item).toolTier) - 1.0f, + EquipSlot::MainHand); + } + + return stats; +} +