diff --git a/libespm/include/libespm/ARMO.h b/libespm/include/libespm/ARMO.h index 2aa6c806d7..046eb1f357 100644 --- a/libespm/include/libespm/ARMO.h +++ b/libespm/include/libespm/ARMO.h @@ -16,6 +16,7 @@ class ARMO final : public RecordHeader uint32_t baseValue = 0; float weight = 0; uint32_t enchantmentFormId = 0; + uint32_t equipSlotId = 0; // only for shields }; Data GetData(CompressedFieldsCache& compressedFieldsCache) const; diff --git a/libespm/src/ARMO.cpp b/libespm/src/ARMO.cpp index df50ba660c..d1d59aa9d7 100644 --- a/libespm/src/ARMO.cpp +++ b/libespm/src/ARMO.cpp @@ -19,6 +19,8 @@ ARMO::Data ARMO::GetData(CompressedFieldsCache& compressedFieldsCache) const } else if (!std::memcmp(type, "DNAM", 4)) { hasDNAM = true; result.baseRatingX100 = *reinterpret_cast(data); + } else if (!std::memcmp(type, "ETYP", 4)) { + result.equipSlotId = *reinterpret_cast(data); } }, compressedFieldsCache); diff --git a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp index c0be3b84fa..56cb0b713c 100644 --- a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp +++ b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp @@ -856,9 +856,52 @@ void ActionListener::OnHit(const RawMessageData& rawMsgData_, float healthPercentage = currentActorValues.healthPercentage; - hitData.isHitBlocked = hitData.isHitBlocked || - (targetActor.IsBlockActive() ? ShouldBeBlocked(*aggressor, targetActor) - : false); + if (targetActor.IsBlockActive()) { + if (ShouldBeBlocked(*aggressor, targetActor)) { + bool isRemoteBowAttack = false; + + auto sourceLookupResult = + targetActor.GetParent()->GetEspm().GetBrowser().LookupById( + hitData.source); + if (sourceLookupResult.rec && + sourceLookupResult.rec->GetType() == espm::WEAP::kType) { + auto weapData = + espm::GetData(hitData.source, targetActor.GetParent()); + if (weapData.weapDNAM) { + if (weapData.weapDNAM->animType == espm::WEAP::AnimType::Bow || + weapData.weapDNAM->animType == espm::WEAP::AnimType::Crossbow) { + if (!hitData.isBashAttack) { + isRemoteBowAttack = true; + } + } + } + } + + bool isBlockingByShield = false; + + auto targetActorEquipmentEntries = + targetActor.GetEquipment().inv.entries; + for (auto& entry : targetActorEquipmentEntries) { + if (entry.extra.worn != Inventory::Worn::None) { + auto res = + targetActor.GetParent()->GetEspm().GetBrowser().LookupById( + entry.baseId); + if (res.rec && res.rec->GetType() == espm::ARMO::kType) { + auto data = + espm::GetData(entry.baseId, targetActor.GetParent()); + bool isShield = data.equipSlotId > 0; + if (isShield) { + isBlockingByShield = isShield; + } + } + } + } + + if (!isRemoteBowAttack || isBlockingByShield) { + hitData.isHitBlocked = true; + } + } + } float damage = partOne.CalculateDamage(*aggressor, targetActor, hitData); damage = damage < 0.f ? 0.f : damage;