From 468a60e9d2eb16a2e52aa3c490638e3c3613bf2c Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 6 Sep 2026 23:13:21 +0200 Subject: [PATCH] feat: add armorToughness and armorEnchantability to Damage --- core/src/Damage.cpp | 159 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/core/src/Damage.cpp b/core/src/Damage.cpp index ccfeb80..1abb4d3 100644 --- a/core/src/Damage.cpp +++ b/core/src/Damage.cpp @@ -175,3 +175,162 @@ int armorPoints(const Armor& piece) { } } +float armorToughness(const Armor& piece) { + if (!piece.worn()) return 0.0f; + return piece.material == Armor::Material::Diamond ? 2.0f : 0.0f; +} + +int armorEnchantability(Armor::Material material) { + switch (material) { + case Armor::Material::Leather: return 15; + case Armor::Material::Chain: return 12; + case Armor::Material::Iron: return 9; + case Armor::Material::Gold: return 25; + case Armor::Material::Diamond: return 10; + default: return 0; + } +} + +Enchantment::Target armorTarget(Armor::Slot slot) { + switch (slot) { + case Armor::Slot::Helmet: return Enchantment::Target::Helmet; + case Armor::Slot::Chestplate: return Enchantment::Target::Chestplate; + case Armor::Slot::Leggings: return Enchantment::Target::Leggings; + default: return Enchantment::Target::Boots; + } +} + +int ArmorSet::totalPoints() const { + int total = 0; + for (const Armor& piece : pieces) total += armorPoints(piece); + return total; +} + +float ArmorSet::totalToughness() const { + float total = 0.0f; + for (const Armor& piece : pieces) total += armorToughness(piece); + return total; +} + +int ArmorSet::protectionPoints(DamageType type) const { + int total = 0; + for (const Armor& piece : pieces) { + if (!piece.worn()) continue; + total += protectionPointsFor(piece.enchants, type); + } + return total; +} + +int ArmorSet::levelOf(EnchantKind kind) const { + int best = 0; + for (const Armor& piece : pieces) { + if (!piece.worn()) continue; + best = std::max(best, piece.enchants.levelOf(kind)); + } + return best; +} + +float armorReduction(int points, float toughness, float damage) { + if (points <= 0) return damage; + float pointsF = static_cast(points); + float defence = std::max(pointsF / 5.0f, pointsF - damage / (2.0f + toughness / 4.0f)); + defence = std::min(20.0f, defence); + return damage * (1.0f - defence / 25.0f); +} + +float protectionFactor(int points) { + int capped = std::min(points, 20); + if (capped <= 0) return 1.0f; + return 1.0f - static_cast(capped) * 0.04f; +} + +float effectiveMaxHealth(const CombatStats& stats, const EffectSet& effects) { + return stats.baseMaxHealth + effectModifiers(effects).maxHealthBonus; +} + +int attackCooldownTicks(float attackSpeed) { + if (attackSpeed <= 0.0f) return ticksPerSecond; + int ticks = static_cast(std::lround(static_cast(ticksPerSecond) / attackSpeed)); + return std::max(1, ticks); +} + +float attackCharge(const CombatStats& stats) { + int cooldown = attackCooldownTicks(stats.attackSpeed); + float charge = static_cast(stats.attackChargeTicks) / static_cast(cooldown); + return std::min(1.0f, std::max(0.0f, charge)); +} + +void registerSwing(CombatStats& stats) { stats.attackChargeTicks = 0; } + +EffectSet::Pending tickCombat(CombatStats& stats, EffectSet& effects) { + if (stats.invulnerableTicks > 0) --stats.invulnerableTicks; + if (stats.invulnerableTicks == 0) stats.lastDamageTaken = 0.0f; + + int cooldown = attackCooldownTicks(stats.attackSpeed); + if (stats.attackChargeTicks < cooldown) ++stats.attackChargeTicks; + + EffectSet::Pending pending = tickEffects(effects); + EffectModifier modifier = effectModifiers(effects); + stats.absorption = std::max(stats.absorption, modifier.absorptionHearts); + + float maxHealth = stats.baseMaxHealth + modifier.maxHealthBonus; + applyEffectTick(pending, stats.health, maxHealth); + if (stats.health <= 0.0f) { + stats.health = 0.0f; + stats.alive = false; + } + return pending; +} + +float meleeAttackDamage(const CombatStats& attacker, const EnchantSet& weapon, + const EffectSet& attackerEffects, bool undeadTarget, bool arthropodTarget, + bool critical) { + float charge = attackCharge(attacker); + float damage = attacker.baseAttackDamage * (0.2f + charge * charge * 0.8f); + damage += effectModifiers(attackerEffects).attackDamageBonus; + damage = std::max(0.0f, damage); + + float enchantBonus = 0.0f; + int sharpness = weapon.levelOf(EnchantKind::Sharpness); + if (sharpness > 0) enchantBonus += 1.0f + 0.5f * static_cast(sharpness - 1); + if (undeadTarget) enchantBonus += 2.5f * static_cast(weapon.levelOf(EnchantKind::Smite)); + if (arthropodTarget) { + enchantBonus += 2.5f * static_cast(weapon.levelOf(EnchantKind::BaneOfArthropods)); + } + // A partially charged swing only lands a fraction of the enchantment bonus. + damage += enchantBonus * charge; + + if (critical && charge >= 0.9f) damage *= criticalMultiplier; + return damage; +} + +float fallDamage(float fallDistance, const EffectSet& effects) { + float safeDistance = 3.0f + static_cast(effectLevel(effects, EffectKind::JumpBoost)); + float excess = fallDistance - safeDistance; + if (excess <= 0.0f) return 0.0f; + return std::floor(excess); +} + +Vec3 knockbackVector(const Vec3& attackerPos, const Vec3& targetPos, float strength) { + return knockbackAlong(targetPos - attackerPos, strength); +} + +int wearArmor(ArmorSet& armor, float incomingDamage, uint32_t& rng) { + int wear = std::max(1, static_cast(std::floor(incomingDamage / 4.0f))); + int spent = 0; + for (Armor& piece : armor.pieces) { + if (!piece.worn()) continue; + int unbreaking = piece.enchants.levelOf(EnchantKind::Unbreaking); + int applied = 0; + for (int i = 0; i < wear; ++i) { + if (unbreakingBlocksWear(unbreaking, rng)) continue; + ++applied; + } + applied = std::min(applied, piece.durability); + piece.durability -= applied; + spent += applied; + if (piece.durability <= 0) piece = Armor{}; + } + return spent; +} +