From 87af95b67cb45b72bffd77f7744dc1a10cb3f25a Mon Sep 17 00:00:00 2001 From: Lane Kolbly Date: Sun, 23 Jul 2017 04:46:38 -0500 Subject: Updated armor cover calculation. (#3858) * Updated armor damage calculation. + Added lua docs, added casts from float to int. * Changed verbage in docstring and comment. --- Server/Plugins/APIDump/APIDesc.lua | 36 +++++++ src/Entities/Entity.cpp | 195 ++++++++++++++----------------------- src/Entities/Entity.h | 10 +- src/Entities/Player.cpp | 27 ++--- src/Entities/Player.h | 1 + 5 files changed, 134 insertions(+), 135 deletions(-) diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 58a79fb66..288466303 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -3019,6 +3019,17 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") }, Notes = "Adds the specified amount of speed in the Z axis direction.", }, + ApplyArmorDamage = + { + Params = + { + { + Name = "DamageBlocked", + Type = "number", + }, + }, + Notes = "Lowers armor durability, as if the armor blocked the given amount of damage.", + }, ArmorCoversAgainst = { Params = @@ -3124,6 +3135,31 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") }, Notes = "Returns the entity classname that this class implements. Each descendant overrides this function.", }, + GetEnchantmentCoverAgainst = + { + Params = + { + { + Name = "AttackerEntity", + Type = "cEntity", + }, + { + Name = "DamageType", + Type = "eDamageType", + }, + { + Name = "RawDamage", + Type = "number", + }, + }, + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the number of hitpoints out of RawDamage that the enchantments on the currently equipped armor would cover. See {{TakeDamageInfo}} for more information on attack damage.", + }, GetEntityType = { Returns = diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index f2836de9b..b596bc93d 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -268,7 +268,16 @@ void cEntity::TakeDamage(cEntity & a_Attacker) void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, double a_KnockbackAmount) { - int FinalDamage = a_RawDamage - GetArmorCoverAgainst(a_Attacker, a_DamageType, a_RawDamage); + int ArmorCover = GetArmorCoverAgainst(a_Attacker, a_DamageType, a_RawDamage); + int EnchantmentCover = GetEnchantmentCoverAgainst(a_Attacker, a_DamageType, a_RawDamage); + int FinalDamage = a_RawDamage - ArmorCover - EnchantmentCover; + if ((FinalDamage == 0) && (a_RawDamage > 0)) + { + // Nobody's invincible + FinalDamage = 1; + } + ApplyArmorDamage(ArmorCover); + cEntity::TakeDamage(a_DamageType, a_Attacker, a_RawDamage, FinalDamage, a_KnockbackAmount); } @@ -278,7 +287,7 @@ void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_R void cEntity::TakeDamage(eDamageType a_DamageType, UInt32 a_AttackerID, int a_RawDamage, double a_KnockbackAmount) { - class cNotifyWolves : public cEntityCallback + class cFindEntity : public cEntityCallback { public: @@ -300,8 +309,7 @@ void cEntity::TakeDamage(eDamageType a_DamageType, UInt32 a_AttackerID, int a_Ra } - int FinalDamage = m_RawDamage - m_Entity->GetArmorCoverAgainst(Attacker, m_DamageType, m_RawDamage); - m_Entity->TakeDamage(m_DamageType, Attacker, m_RawDamage, FinalDamage, m_KnockbackAmount); + m_Entity->TakeDamage(m_DamageType, Attacker, m_RawDamage, m_KnockbackAmount); return true; } } Callback; @@ -517,118 +525,8 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI) Player->GetStatManager().AddValue(statDamageDealt, static_cast(floor(a_TDI.FinalDamage * 10 + 0.5))); } - if (IsPlayer()) - { - double TotalEPF = 0.0; - double EPFProtection = 0.00; - double EPFFireProtection = 0.00; - double EPFBlastProtection = 0.00; - double EPFProjectileProtection = 0.00; - double EPFFeatherFalling = 0.00; - - const cItem ArmorItems[] = { GetEquippedHelmet(), GetEquippedChestplate(), GetEquippedLeggings(), GetEquippedBoots() }; - for (size_t i = 0; i < ARRAYCOUNT(ArmorItems); i++) - { - const cItem & Item = ArmorItems[i]; - int Level = static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchProtection)); - if (Level > 0) - { - EPFProtection += (6 + Level * Level) * 0.75 / 3; - } - - Level = static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchFireProtection)); - if (Level > 0) - { - EPFFireProtection += (6 + Level * Level) * 1.25 / 3; - } - - Level = static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchFeatherFalling)); - if (Level > 0) - { - EPFFeatherFalling += (6 + Level * Level) * 2.5 / 3; - } - - Level = static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchBlastProtection)); - if (Level > 0) - { - EPFBlastProtection += (6 + Level * Level) * 1.5 / 3; - } - - Level = static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchProjectileProtection)); - if (Level > 0) - { - EPFProjectileProtection += (6 + Level * Level) * 1.5 / 3; - } - - } - - TotalEPF = EPFProtection + EPFFireProtection + EPFFeatherFalling + EPFBlastProtection + EPFProjectileProtection; - - EPFProtection = EPFProtection / TotalEPF; - EPFFireProtection = EPFFireProtection / TotalEPF; - EPFFeatherFalling = EPFFeatherFalling / TotalEPF; - EPFBlastProtection = EPFBlastProtection / TotalEPF; - EPFProjectileProtection = EPFProjectileProtection / TotalEPF; - - if (TotalEPF > 25) - { - TotalEPF = 25; - } - - float RandomValue = GetRandomProvider().RandReal(0.5f, 1.0f); - - TotalEPF = ceil(TotalEPF * RandomValue); - - if (TotalEPF > 20) - { - TotalEPF = 20; - } - - EPFProtection = TotalEPF * EPFProtection; - EPFFireProtection = TotalEPF * EPFFireProtection; - EPFFeatherFalling = TotalEPF * EPFFeatherFalling; - EPFBlastProtection = TotalEPF * EPFBlastProtection; - EPFProjectileProtection = TotalEPF * EPFProjectileProtection; - - int RemovedDamage = 0; - - if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtAdmin)) - { - RemovedDamage += CeilC(EPFProtection * 0.04 * a_TDI.FinalDamage); - } - - if ((a_TDI.DamageType == dtFalling) || (a_TDI.DamageType == dtEnderPearl)) - { - RemovedDamage += CeilC(EPFFeatherFalling * 0.04 * a_TDI.FinalDamage); - } - - if (a_TDI.DamageType == dtBurning) - { - RemovedDamage += CeilC(EPFFireProtection * 0.04 * a_TDI.FinalDamage); - } - - if (a_TDI.DamageType == dtExplosion) - { - RemovedDamage += CeilC(EPFBlastProtection * 0.04 * a_TDI.FinalDamage); - } - - if (a_TDI.DamageType == dtProjectile) - { - RemovedDamage += CeilC(EPFBlastProtection * 0.04 * a_TDI.FinalDamage); - } - - if (a_TDI.FinalDamage < RemovedDamage) - { - RemovedDamage = 0; - } - - a_TDI.FinalDamage -= RemovedDamage; - } - m_Health -= static_cast(a_TDI.FinalDamage); - // TODO: Apply damage to armor - m_Health = std::max(m_Health, 0); // Add knockback: @@ -708,6 +606,15 @@ int cEntity::GetRawDamageAgainst(const cEntity & a_Receiver) +void cEntity::ApplyArmorDamage(int DamageBlocked) +{ + // cEntities don't necessarily have armor to damage. + return; +} + + + + bool cEntity::ArmorCoversAgainst(eDamageType a_DamageType) { @@ -717,6 +624,7 @@ bool cEntity::ArmorCoversAgainst(eDamageType a_DamageType) case dtOnFire: case dtSuffocating: case dtDrowning: // TODO: This one could be a special case - in various MC versions (PC vs XBox) it is and isn't armor-protected + case dtEnderPearl: case dtStarving: case dtInVoid: case dtPoisoning: @@ -734,7 +642,6 @@ bool cEntity::ArmorCoversAgainst(eDamageType a_DamageType) case dtCactusContact: case dtLavaContact: case dtFireContact: - case dtEnderPearl: case dtExplosion: { return true; @@ -750,6 +657,49 @@ bool cEntity::ArmorCoversAgainst(eDamageType a_DamageType) +int cEntity::GetEnchantmentCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_Damage) +{ + int TotalEPF = 0.0; + + const cItem ArmorItems[] = { GetEquippedHelmet(), GetEquippedChestplate(), GetEquippedLeggings(), GetEquippedBoots() }; + for (size_t i = 0; i < ARRAYCOUNT(ArmorItems); i++) + { + const cItem & Item = ArmorItems[i]; + + if ((a_DamageType != dtInVoid) && (a_DamageType != dtAdmin) && (a_DamageType != dtStarving)) + { + TotalEPF += static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchProtection)) * 1; + } + + if ((a_DamageType == dtBurning) || (a_DamageType == dtFireContact) || (a_DamageType == dtLavaContact)) + { + TotalEPF += static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchFireProtection)) * 2; + } + + if ((a_DamageType == dtFalling) || (a_DamageType == dtEnderPearl)) + { + TotalEPF += static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchFeatherFalling)) * 3; + } + + if (a_DamageType == dtExplosion) + { + TotalEPF += static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchBlastProtection)) * 2; + } + + // Note: Also blocks against fire charges, etc. + if (a_DamageType == dtProjectile) + { + TotalEPF += static_cast(Item.m_Enchantments.GetLevel(cEnchantments::enchProjectileProtection)) * 2; + } + } + int CappedEPF = std::min(20, TotalEPF); + return static_cast(a_Damage * CappedEPF / 25.0); +} + + + + + int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_Damage) { // Returns the hitpoints out of a_RawDamage that the currently equipped armor would cover @@ -761,15 +711,16 @@ int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_Dama } // Add up all armor points: - // Ref.: http://minecraft.gamepedia.com/Armor#Defense_points as of 2012_12_20 + // Ref.: http://minecraft.gamepedia.com/Armor#Defense_points int ArmorValue = 0; + int Toughness = 0; switch (GetEquippedHelmet().m_ItemType) { case E_ITEM_LEATHER_CAP: ArmorValue += 1; break; case E_ITEM_GOLD_HELMET: ArmorValue += 2; break; case E_ITEM_CHAIN_HELMET: ArmorValue += 2; break; case E_ITEM_IRON_HELMET: ArmorValue += 2; break; - case E_ITEM_DIAMOND_HELMET: ArmorValue += 3; break; + case E_ITEM_DIAMOND_HELMET: ArmorValue += 3; Toughness += 2; break; } switch (GetEquippedChestplate().m_ItemType) { @@ -777,7 +728,7 @@ int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_Dama case E_ITEM_GOLD_CHESTPLATE: ArmorValue += 5; break; case E_ITEM_CHAIN_CHESTPLATE: ArmorValue += 5; break; case E_ITEM_IRON_CHESTPLATE: ArmorValue += 6; break; - case E_ITEM_DIAMOND_CHESTPLATE: ArmorValue += 8; break; + case E_ITEM_DIAMOND_CHESTPLATE: ArmorValue += 8; Toughness += 2; break; } switch (GetEquippedLeggings().m_ItemType) { @@ -785,7 +736,7 @@ int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_Dama case E_ITEM_GOLD_LEGGINGS: ArmorValue += 3; break; case E_ITEM_CHAIN_LEGGINGS: ArmorValue += 4; break; case E_ITEM_IRON_LEGGINGS: ArmorValue += 5; break; - case E_ITEM_DIAMOND_LEGGINGS: ArmorValue += 6; break; + case E_ITEM_DIAMOND_LEGGINGS: ArmorValue += 6; Toughness += 2; break; } switch (GetEquippedBoots().m_ItemType) { @@ -793,14 +744,14 @@ int cEntity::GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_Dama case E_ITEM_GOLD_BOOTS: ArmorValue += 1; break; case E_ITEM_CHAIN_BOOTS: ArmorValue += 1; break; case E_ITEM_IRON_BOOTS: ArmorValue += 2; break; - case E_ITEM_DIAMOND_BOOTS: ArmorValue += 3; break; + case E_ITEM_DIAMOND_BOOTS: ArmorValue += 3; Toughness += 2; break; } // TODO: Special armor cases, such as wool, saddles, dog's collar // Ref.: http://minecraft.gamepedia.com/Armor#Mob_armor as of 2012_12_20 - // Now ArmorValue is in [0, 20] range, which corresponds to [0, 80%] protection. Calculate the hitpoints from that: - return a_Damage * (ArmorValue * 4) / 100; + double Reduction = std::max(ArmorValue / 5.0, ArmorValue - a_Damage / (2 + Toughness / 4.0)); + return static_cast(a_Damage * std::min(20.0, Reduction) / 25.0); } diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 4310b1567..d55955b0c 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -141,12 +141,12 @@ public: static const int BURN_TICKS_PER_DAMAGE = 20; ///< Ticks to wait between damaging an entity when it is burning static const int BURN_DAMAGE = 1; ///< Damage to deal when the entity is burning - static const int BURN_TICKS = 200; ///< Ticks to keep an entity burning after it has stood in lava / fire + static const int BURN_TICKS = 160; ///< Ticks to keep an entity burning after it has stood in lava / fire static const int MAX_AIR_LEVEL = 300; ///< Maximum air an entity can have static const int DROWNING_TICKS = 20; ///< Number of ticks per heart of damage - static const int VOID_BOUNDARY = -46; ///< Y position to begin applying void damage + static const int VOID_BOUNDARY = -64; ///< Y position to begin applying void damage static const int FALL_DAMAGE_HEIGHT = 4; ///< Y difference after which fall damage is applied /** Special ID that is considered an "invalid value", signifying no entity. */ @@ -320,6 +320,9 @@ public: /** Returns the hitpoints out of a_RawDamage that the currently equipped armor would cover */ virtual int GetArmorCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_RawDamage); + /** Returns the hitpoints that the currently equipped armor's enchantments would cover */ + virtual int GetEnchantmentCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_Damage); + /** Returns the knockback amount that the currently equipped items would cause to a_Receiver on a hit */ virtual double GetKnockbackAmountAgainst(const cEntity & a_Receiver); @@ -338,6 +341,9 @@ public: /** Returns the currently equipped boots; empty item if none */ virtual cItem GetEquippedBoots(void) const { return cItem(); } + /** Applies damage to the armor after the armor blocked the given amount */ + virtual void ApplyArmorDamage(int DamageBlocked); + // tolua_end /** Called when the health drops below zero. a_TDI's Attacker may be nullptr (environmental damage) */ diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index e4192140a..77ce378da 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -940,6 +940,22 @@ void cPlayer::SetFlying(bool a_IsFlying) +void cPlayer::ApplyArmorDamage(int DamageBlocked) +{ + short ArmorDamage = static_cast(DamageBlocked / 4); + if (ArmorDamage == 0) + { + ArmorDamage = 1; + } + m_Inventory.DamageItem(cInventory::invArmorOffset + 0, ArmorDamage); + m_Inventory.DamageItem(cInventory::invArmorOffset + 1, ArmorDamage); + m_Inventory.DamageItem(cInventory::invArmorOffset + 2, ArmorDamage); + m_Inventory.DamageItem(cInventory::invArmorOffset + 3, ArmorDamage); +} + + + + bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) { @@ -976,17 +992,6 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) AddFoodExhaustion(0.3f); SendHealth(); - // Damage armor - short ArmorDamage = static_cast(a_TDI.RawDamage / 4); - if (ArmorDamage == 0) - { - ArmorDamage = 1; - } - m_Inventory.DamageItem(cInventory::invArmorOffset + 0, ArmorDamage); - m_Inventory.DamageItem(cInventory::invArmorOffset + 1, ArmorDamage); - m_Inventory.DamageItem(cInventory::invArmorOffset + 2, ArmorDamage); - m_Inventory.DamageItem(cInventory::invArmorOffset + 3, ArmorDamage); - // Tell the wolves if (a_TDI.Attacker != nullptr) { diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 95b21ec9a..8ad803998 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -68,6 +68,7 @@ public: /** Returns the currently equipped boots; empty item if none */ virtual cItem GetEquippedBoots(void) const override { return m_Inventory.GetEquippedBoots(); } + virtual void ApplyArmorDamage(int DamageBlocked) override; // tolua_begin -- cgit v1.2.3