From 374e98d665f303f2a981b4d2dfcc5601e073c48d Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 30 Aug 2018 23:04:02 +0300 Subject: [PATCH 1/9] Make rateWeapon more sensible and account for weapon speed --- apps/openmw/mwmechanics/weaponpriority.cpp | 71 +++++++++------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index ac01a0714..8d09bd8d1 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -21,13 +21,19 @@ namespace MWMechanics float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type, float arrowRating, float boltRating) { - if (item.getTypeName() != typeid(ESM::Weapon).name()) + if (enemy.isEmpty() || item.getTypeName() != typeid(ESM::Weapon).name()) + return 0.f; + + if (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) == 0) return 0.f; const ESM::Weapon* weapon = item.get()->mBase; if (type != -1 && weapon->mData.mType != type) return 0.f; + + const MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& gmst = world->getStore().get(); if (type == -1 && (weapon->mData.mType == ESM::Weapon::Arrow || weapon->mData.mType == ESM::Weapon::Bolt)) return 0.f; @@ -37,54 +43,34 @@ namespace MWMechanics if (weapon->mData.mType >= ESM::Weapon::MarksmanBow && weapon->mData.mType <= ESM::Weapon::MarksmanThrown) { - // Range weapon is useless under water - if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.75f)) - return 0.f; - - if (enemy.isEmpty()) - return 0.f; - - if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) + // Underwater ranged combat is impossible + if (world->isUnderwater(MWWorld::ConstPtr(actor), 0.75f) + || world->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) return 0.f; if (getDistanceMinusHalfExtents(actor, enemy) >= getMaxAttackDistance(enemy)) - rangedMult = 1.5f; + { + static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); + static const float fAIRangeMeleeWeaponMult = gmst.find("fAIRangeMeleeWeaponMult")->mValue.getFloat(); + rangedMult = fAIRangeMeleeWeaponMult / fAIMeleeWeaponMult; + } } + const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) - { - float rangedDamage = weapon->mData.mChop[0] + weapon->mData.mChop[1]; - MWMechanics::adjustWeaponDamage(rangedDamage, item, actor); - - rating = rangedDamage / 2.f; - - if (weapon->mData.mType >= ESM::Weapon::MarksmanThrown) - MWMechanics::resistNormalWeapon(enemy, actor, item, rating); - } + rating = chop; else { - float meleeDamage = 0.f; - - for (int i=0; i<2; ++i) - { - meleeDamage += weapon->mData.mSlash[i]; - meleeDamage += weapon->mData.mThrust[i]; - meleeDamage += weapon->mData.mChop[i]; - } - - MWMechanics::adjustWeaponDamage(meleeDamage, item, actor); - rating = meleeDamage / 6.f; - - MWMechanics::resistNormalWeapon(enemy, actor, item, rating); + const float slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2.f; + const float thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1]) / 2.f; + rating = (slash * slash + thrust * thrust + chop * chop) / (slash + thrust + chop); } - if (item.getClass().hasItemHealth(item)) - { - if (item.getClass().getItemHealth(item) == 0) - return 0.f; - } + adjustWeaponDamage(rating, item, actor); - if (weapon->mData.mType == ESM::Weapon::MarksmanBow) + if (weapon->mData.mType != ESM::Weapon::MarksmanBow && weapon->mData.mType != ESM::Weapon::MarksmanCrossbow) + resistNormalWeapon(enemy, actor, item, rating); + else if (weapon->mData.mType == ESM::Weapon::MarksmanBow) { if (arrowRating <= 0.f) rating = 0.f; @@ -101,7 +87,7 @@ namespace MWMechanics if (!weapon->mEnchant.empty()) { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(weapon->mEnchant); + const ESM::Enchantment* enchantment = world->getStore().get().find(weapon->mEnchant); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); @@ -111,19 +97,22 @@ namespace MWMechanics } } + if (weapon->mData.mType <= ESM::Weapon::MarksmanThrown) + rating *= weapon->mData.mSpeed; + if (actor.getClass().isNpc()) { int skill = item.getClass().getEquipmentSkill(item); if (skill != -1) { int value = actor.getClass().getSkill(actor, skill); - rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.f; + rating *= getHitChance(actor, enemy, value) / 100.f; } } else { MWWorld::LiveCellRef *ref = actor.get(); - rating *= MWMechanics::getHitChance(actor, enemy, ref->mBase->mData.mCombat) / 100.f; + rating *= getHitChance(actor, enemy, ref->mBase->mData.mCombat) / 100.f; } return rating * rangedMult; From 00c847db195e41036510681796d0279db0cbdf02 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 30 Aug 2018 23:36:47 +0300 Subject: [PATCH 2/9] Make AI Blind, Sound and Silence effect rating more logical --- apps/openmw/mwmechanics/spellpriority.cpp | 54 ++++++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index a4bf46769..b43756162 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -232,20 +232,59 @@ namespace MWMechanics case ESM::MagicEffect::CommandHumanoid: return 0.f; + case ESM::MagicEffect::Blind: + { + if (enemy.isEmpty()) + return 0.f; + + const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + + // Enemy can't attack + if (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + return 0.f; + + // Enemy doesn't attack + if (stats.getDrawState() != MWMechanics::DrawState_Weapon) + return 0.f; + + break; + } + case ESM::MagicEffect::Sound: { if (enemy.isEmpty()) return 0.f; - // there is no need to cast sound if enemy is not able to cast spells - CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + // Enemy can't cast spells if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() > 0) return 0.f; if (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) return 0.f; + // Enemy doesn't cast spells + if (stats.getDrawState() != MWMechanics::DrawState_Spell) + return 0.f; + + break; + } + + case ESM::MagicEffect::Silence: + { + if (enemy.isEmpty()) + return 0.f; + + const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + + // Enemy can't cast spells + if (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + return 0.f; + + // Enemy doesn't cast spells + if (stats.getDrawState() != MWMechanics::DrawState_Spell) + return 0.f; break; } @@ -353,9 +392,10 @@ namespace MWMechanics int priority = 1; if (effect.mEffectID == ESM::MagicEffect::RestoreHealth) priority = 10; - const DynamicStat& current = actor.getClass().getCreatureStats(actor). - getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); - float toHeal = (effect.mMagnMin + effect.mMagnMax)/2.f * effect.mDuration; + const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const DynamicStat& current = stats.getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); + const float magnitude = (effect.mMagnMin + effect.mMagnMax)/2.f; + const float toHeal = magnitude * effect.mDuration; // Effect doesn't heal more than we need, *or* we are below 1/2 health if (current.getModified() - current.getCurrent() > toHeal || current.getCurrent() < current.getModified()*0.5) @@ -363,8 +403,8 @@ namespace MWMechanics return 10000.f * priority - (toHeal - (current.getModified()-current.getCurrent())); // prefer the most fitting potion } - else - return -10000.f * priority; // Save for later + + return 0.f; } break; From 533b72eff609dc9cdc3af1de9591dcfc410981eb Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 31 Aug 2018 00:36:30 +0300 Subject: [PATCH 3/9] Cache weapon type strings --- apps/openmw/mwclass/weapon.cpp | 39 ++++++++++++---------- apps/openmw/mwmechanics/spellpriority.cpp | 4 +-- apps/openmw/mwmechanics/weaponpriority.cpp | 2 +- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 294aebd94..466ae4716 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -268,24 +268,27 @@ namespace MWClass { text += "\n#{sType} "; - std::map > mapping; - mapping[ESM::Weapon::ShortBladeOneHand] = std::make_pair("sSkillShortblade", "sOneHanded"); - mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded"); - mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded"); - mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded"); - mapping[ESM::Weapon::BluntTwoClose] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); - mapping[ESM::Weapon::BluntTwoWide] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); - mapping[ESM::Weapon::SpearTwoWide] = std::make_pair("sSkillSpear", "sTwoHanded"); - mapping[ESM::Weapon::AxeOneHand] = std::make_pair("sSkillAxe", "sOneHanded"); - mapping[ESM::Weapon::AxeTwoHand] = std::make_pair("sSkillAxe", "sTwoHanded"); - mapping[ESM::Weapon::MarksmanBow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::MarksmanCrossbow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::MarksmanThrown] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::Arrow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::Bolt] = std::make_pair("sSkillMarksman", ""); - - std::string type = mapping[ref->mBase->mData.mType].first; - std::string oneOrTwoHanded = mapping[ref->mBase->mData.mType].second; + static std::map > mapping; + if (mapping.empty()) + { + mapping[ESM::Weapon::ShortBladeOneHand] = std::make_pair("sSkillShortblade", "sOneHanded"); + mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded"); + mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded"); + mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded"); + mapping[ESM::Weapon::BluntTwoClose] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); + mapping[ESM::Weapon::BluntTwoWide] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); + mapping[ESM::Weapon::SpearTwoWide] = std::make_pair("sSkillSpear", "sTwoHanded"); + mapping[ESM::Weapon::AxeOneHand] = std::make_pair("sSkillAxe", "sOneHanded"); + mapping[ESM::Weapon::AxeTwoHand] = std::make_pair("sSkillAxe", "sTwoHanded"); + mapping[ESM::Weapon::MarksmanBow] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::MarksmanCrossbow] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::MarksmanThrown] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::Arrow] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::Bolt] = std::make_pair("sSkillMarksman", ""); + } + + const std::string type = mapping[ref->mBase->mData.mType].first; + const std::string oneOrTwoHanded = mapping[ref->mBase->mData.mType].second; text += store.get().find(type)->mValue.getString() + ((oneOrTwoHanded != "") ? ", " + store.get().find(oneOrTwoHanded)->mValue.getString() : ""); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index b43756162..f55b30d7c 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -403,8 +403,8 @@ namespace MWMechanics return 10000.f * priority - (toHeal - (current.getModified()-current.getCurrent())); // prefer the most fitting potion } - - return 0.f; + else + return -10000.f * priority; // Save for later } break; diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 8d09bd8d1..61d348e14 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -31,7 +31,7 @@ namespace MWMechanics if (type != -1 && weapon->mData.mType != type) return 0.f; - + const MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& gmst = world->getStore().get(); From fa3e45fa7dfb4a1f2733e8b02b73c6c6aa596623 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 31 Aug 2018 01:54:59 +0300 Subject: [PATCH 4/9] Slight cleanup --- apps/openmw/mwmechanics/weaponpriority.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 61d348e14..6b9616359 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -100,21 +100,21 @@ namespace MWMechanics if (weapon->mData.mType <= ESM::Weapon::MarksmanThrown) rating *= weapon->mData.mSpeed; + int value = 50.f; if (actor.getClass().isNpc()) { int skill = item.getClass().getEquipmentSkill(item); if (skill != -1) - { - int value = actor.getClass().getSkill(actor, skill); - rating *= getHitChance(actor, enemy, value) / 100.f; - } + value = actor.getClass().getSkill(actor, skill); } else { MWWorld::LiveCellRef *ref = actor.get(); - rating *= getHitChance(actor, enemy, ref->mBase->mData.mCombat) / 100.f; + value = ref->mBase->mData.mCombat; } + rating *= getHitChance(actor, enemy, value) / 100.f; + return rating * rangedMult; } From e66be02e2e2f4a6ebd8fb883e58402aa0bc17cdf Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 31 Aug 2018 02:06:29 +0300 Subject: [PATCH 5/9] Account for enemy armor rating in weapon rating --- apps/openmw/mwmechanics/weaponpriority.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 6b9616359..6861a1b77 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -97,8 +97,11 @@ namespace MWMechanics } } - if (weapon->mData.mType <= ESM::Weapon::MarksmanThrown) - rating *= weapon->mData.mSpeed; + if (enemy.getClass().isNpc()) + { + static const float fCombatArmorMinMult = gmst.find("fCombatArmorMinMult")->mValue.getFloat(); + rating *= std::max(fCombatArmorMinMult, rating / (rating + enemy.getClass().getArmorRating(enemy))); + } int value = 50.f; if (actor.getClass().isNpc()) @@ -115,6 +118,9 @@ namespace MWMechanics rating *= getHitChance(actor, enemy, value) / 100.f; + if (weapon->mData.mType <= ESM::Weapon::MarksmanThrown) + rating *= weapon->mData.mSpeed; + return rating * rangedMult; } From 58bd35c70de83a46ffabacf28d7a0cc7c30be142 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 1 Sep 2018 01:00:18 +0300 Subject: [PATCH 6/9] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab7d88d3..f84ccf050 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -136,6 +136,9 @@ Feature #4550: Weapon priority: make ranged weapon bonus more sensible Feature #4579: Add option for applying Strength into hand to hand damage Feature #4581: Use proper logging system + Feature #4624: Spell priority: don't cast hit chance-affecting spells if the enemy is not in respective stance at the moment + Feature #4625: Weapon priority: use weighted mean for melee damage rating + Feature #4626: Weapon priority: account for weapon speed Task #2490: Don't open command prompt window on Release-mode builds automatically Task #4545: Enable is_pod string test Task #4605: Optimize skinning From 2965373ed69c31be87aa3674c5dd035d3d82f53d Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 1 Sep 2018 01:24:13 +0300 Subject: [PATCH 7/9] Avoid potential zero division --- apps/openmw/mwmechanics/weaponpriority.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 6861a1b77..b2a9c8ded 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -52,7 +52,10 @@ namespace MWMechanics { static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); static const float fAIRangeMeleeWeaponMult = gmst.find("fAIRangeMeleeWeaponMult")->mValue.getFloat(); - rangedMult = fAIRangeMeleeWeaponMult / fAIMeleeWeaponMult; + if (fAIMeleeWeaponMult != 0) + rangedMult = fAIRangeMeleeWeaponMult / fAIMeleeWeaponMult; + else + rangedMult = fAIRangeMeleeWeaponMult; } } From ceb6121b33536b371f7edfb07d6425761f7c0139 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 1 Sep 2018 01:44:29 +0300 Subject: [PATCH 8/9] Better checks for enemy incapacitation --- apps/openmw/mwmechanics/spellpriority.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index f55b30d7c..f9d19ca28 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -240,7 +240,7 @@ namespace MWMechanics const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); // Enemy can't attack - if (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + if (stats.isParalyzed() || stats.getKnockedDown()) return 0.f; // Enemy doesn't attack @@ -261,7 +261,7 @@ namespace MWMechanics if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() > 0) return 0.f; - if (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + if (stats.isParalyzed() || stats.getKnockedDown()) return 0.f; // Enemy doesn't cast spells @@ -279,7 +279,7 @@ namespace MWMechanics const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); // Enemy can't cast spells - if (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + if (stats.isParalyzed() || stats.getKnockedDown()) return 0.f; // Enemy doesn't cast spells From d758b573e2c5adfd25f5b46935944c19736346de Mon Sep 17 00:00:00 2001 From: Capostrophic <21265616+Capostrophic@users.noreply.github.com> Date: Sat, 1 Sep 2018 12:07:26 +0300 Subject: [PATCH 9/9] Fix erroneous assumption that ranged weaponry has speed Restrict speed mult to melee weaponry --- apps/openmw/mwmechanics/weaponpriority.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index b2a9c8ded..6ce064fe4 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -121,7 +121,7 @@ namespace MWMechanics rating *= getHitChance(actor, enemy, value) / 100.f; - if (weapon->mData.mType <= ESM::Weapon::MarksmanThrown) + if (weapon->mData.mType < ESM::Weapon::MarksmanBow) rating *= weapon->mData.mSpeed; return rating * rangedMult;