From 0096ea1af553aea9268f14a37e5fd61e92aa751d Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 2 Sep 2026 14:36:45 +0200 Subject: [PATCH] feat(vf): add HurtSource and DamageEvent --- core/include/vf/Damage.h | 148 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 core/include/vf/Damage.h diff --git a/core/include/vf/Damage.h b/core/include/vf/Damage.h new file mode 100644 index 0000000..5b16e6c --- /dev/null +++ b/core/include/vf/Damage.h @@ -0,0 +1,148 @@ +#pragma once + +#include +#include +#include "Effect.h" +#include "Enchant.h" +#include "Math.h" + +namespace vf { + +enum class DamageType : uint8_t { + Melee, Projectile, Fall, Fire, Lava, Drowning, Explosion, Magic, Void, Starve, Cactus, + Suffocation, + Count +}; + +// Armour points do nothing against these; the hit lands on health directly. +bool bypassesArmor(DamageType type); +// Ignores the post-hit invulnerability window, so it lands every tick. +bool bypassesInvulnerability(DamageType type); +// Neither Resistance nor Protection help against these. +bool bypassesMitigation(DamageType type); +bool isFireDamage(DamageType type); +bool isProjectileDamage(DamageType type); + +struct HurtSource { + DamageType type = DamageType::Melee; + Vec3 origin; + int attackerId = -1; + int ownerId = -1; + bool byPlayer = false; + bool fiery = false; +}; + +HurtSource meleeSource(int attackerId, const Vec3& origin, bool byPlayer); +HurtSource projectileSource(int projectileId, int ownerId, const Vec3& origin, bool byPlayer); +HurtSource environmentSource(DamageType type, const Vec3& origin = {}); + +struct DamageEvent { + HurtSource source; + float amount = 0.0f; + bool critical = false; + float knockbackStrength = 0.0f; + Vec3 direction; // attacker toward target, drives knockback +}; + +struct Armor { + enum class Slot : uint8_t { Helmet, Chestplate, Leggings, Boots, Count }; + enum class Material : uint8_t { None, Leather, Chain, Iron, Gold, Diamond }; + + Material material = Material::None; + Slot slot = Slot::Chestplate; + int durability = 0; + int maxDurability = 0; + EnchantSet enchants; + + bool worn() const { return material != Material::None && durability > 0; } +}; + +Armor makeArmor(Armor::Material material, Armor::Slot slot); +int armorPoints(const Armor& piece); +float armorToughness(const Armor& piece); +int armorEnchantability(Armor::Material material); +Enchantment::Target armorTarget(Armor::Slot slot); + +struct ArmorSet { + std::array pieces{}; + + Armor& at(Armor::Slot slot) { return pieces[static_cast(slot)]; } + const Armor& at(Armor::Slot slot) const { return pieces[static_cast(slot)]; } + void equip(const Armor& piece) { at(piece.slot) = piece; } + + int totalPoints() const; + float totalToughness() const; + // Summed enchantment protection factor for a damage type, before the cap. + int protectionPoints(DamageType type) const; + int levelOf(EnchantKind kind) const; +}; + +// Vanilla armour curve: points bite harder against small hits, toughness keeps +// them relevant against large ones. +float armorReduction(int points, float toughness, float damage); + +// Protection family reduction factor, 1.0 for none down to 0.2 at the cap. +float protectionFactor(int protectionPoints); + +struct CombatStats { + float baseMaxHealth = 20.0f; + float health = 20.0f; + float absorption = 0.0f; + float baseAttackDamage = 1.0f; + float attackSpeed = 4.0f; + int attackChargeTicks = 0; + int invulnerableTicks = 0; + float lastDamageTaken = 0.0f; + ArmorSet armor; + bool alive = true; +}; + +constexpr int invulnerabilityTicks = 10; +constexpr float criticalMultiplier = 1.5f; + +float effectiveMaxHealth(const CombatStats& stats, const EffectSet& effects); +int attackCooldownTicks(float attackSpeed); +float attackCharge(const CombatStats& stats); +void registerSwing(CombatStats& stats); + +// One combat tick: recharges the attack, counts down invulnerability and +// resolves the periodic half of the entity's effects onto its health. +EffectSet::Pending tickCombat(CombatStats& stats, EffectSet& effects); + +struct CombatResult { + bool applied = false; + bool blockedByInvulnerability = false; + float incoming = 0.0f; + float afterArmor = 0.0f; + float afterResistance = 0.0f; + float afterProtection = 0.0f; + float absorbedByAbsorption = 0.0f; + float healthLost = 0.0f; + float thornsDamage = 0.0f; + int armorDurabilityLost = 0; + bool fatal = false; + Vec3 knockback; +}; + +// Full swing damage: base attack scaled by the charge bar, Strength/Weakness, +// the Sharpness family against the right target, then the critical multiplier. +float meleeAttackDamage(const CombatStats& attacker, const EnchantSet& weapon, + const EffectSet& attackerEffects, bool undeadTarget, bool arthropodTarget, + bool critical); + +// Raw fall damage before armour and Feather Falling, reduced by Jump Boost the +// way vanilla does. +float fallDamage(float fallDistance, const EffectSet& effects); + +Vec3 knockbackVector(const Vec3& attackerPos, const Vec3& targetPos, float strength); + +// Wears every worn piece by one point per four damage, honouring Unbreaking. +// Returns the durability actually spent. +int wearArmor(ArmorSet& armor, float incomingDamage, uint32_t& rng); + +// Runs the whole pipeline: invulnerability window, armour, Resistance, +// Protection, absorption, health, knockback, thorns and armour wear. +CombatResult applyDamage(CombatStats& target, const EffectSet& targetEffects, + const DamageEvent& event, uint32_t& rng); + +} // namespace vf