1 #include "combat.hpp"
2 
3 #include <components/misc/rng.hpp>
4 #include <components/settings/settings.hpp>
5 
6 #include <components/sceneutil/positionattitudetransform.hpp>
7 
8 #include "../mwbase/environment.hpp"
9 #include "../mwbase/world.hpp"
10 #include "../mwbase/mechanicsmanager.hpp"
11 #include "../mwbase/soundmanager.hpp"
12 #include "../mwbase/windowmanager.hpp"
13 
14 #include "../mwworld/class.hpp"
15 #include "../mwworld/inventorystore.hpp"
16 #include "../mwworld/esmstore.hpp"
17 
18 #include "npcstats.hpp"
19 #include "movement.hpp"
20 #include "spellcasting.hpp"
21 #include "spellresistance.hpp"
22 #include "difficultyscaling.hpp"
23 #include "actorutil.hpp"
24 #include "pathfinding.hpp"
25 
26 namespace
27 {
28 
signedAngleRadians(const osg::Vec3f & v1,const osg::Vec3f & v2,const osg::Vec3f & normal)29 float signedAngleRadians (const osg::Vec3f& v1, const osg::Vec3f& v2, const osg::Vec3f& normal)
30 {
31     return std::atan2((normal * (v1 ^ v2)), (v1 * v2));
32 }
33 
34 }
35 
36 namespace MWMechanics
37 {
38 
applyOnStrikeEnchantment(const MWWorld::Ptr & attacker,const MWWorld::Ptr & victim,const MWWorld::Ptr & object,const osg::Vec3f & hitPosition,const bool fromProjectile)39     bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition, const bool fromProjectile)
40     {
41         std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : "";
42         if (!enchantmentName.empty())
43         {
44             const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
45                         enchantmentName);
46             if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
47             {
48                 MWMechanics::CastSpell cast(attacker, victim, fromProjectile);
49                 cast.mHitPosition = hitPosition;
50                 cast.cast(object, false);
51                 return true;
52             }
53         }
54         return false;
55     }
56 
blockMeleeAttack(const MWWorld::Ptr & attacker,const MWWorld::Ptr & blocker,const MWWorld::Ptr & weapon,float damage,float attackStrength)57     bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage, float attackStrength)
58     {
59         if (!blocker.getClass().hasInventoryStore(blocker))
60             return false;
61 
62         MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker);
63 
64         if (blockerStats.getKnockedDown() // Used for both knockout or knockdown
65                 || blockerStats.getHitRecovery()
66                 || blockerStats.isParalyzed())
67             return false;
68 
69         if (!MWBase::Environment::get().getMechanicsManager()->isReadyToBlock(blocker))
70             return false;
71 
72         MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker);
73         MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
74         if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name())
75             return false;
76 
77         if (!blocker.getRefData().getBaseNode())
78             return false; // shouldn't happen
79 
80         float angleDegrees = osg::RadiansToDegrees(
81                     signedAngleRadians (
82                     (attacker.getRefData().getPosition().asVec3() - blocker.getRefData().getPosition().asVec3()),
83                     blocker.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0),
84                     osg::Vec3f(0,0,1)));
85 
86         const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
87         if (angleDegrees < gmst.find("fCombatBlockLeftAngle")->mValue.getFloat())
88             return false;
89         if (angleDegrees > gmst.find("fCombatBlockRightAngle")->mValue.getFloat())
90             return false;
91 
92         MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker);
93 
94         float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2f * blockerStats.getAttribute(ESM::Attribute::Agility).getModified()
95             + 0.1f * blockerStats.getAttribute(ESM::Attribute::Luck).getModified();
96         float enemySwing = attackStrength;
97         float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->mValue.getFloat() + gmst.find("fSwingBlockBase")->mValue.getFloat();
98 
99         float blockerTerm = blockTerm * swingTerm;
100         if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0)
101             blockerTerm *= gmst.find("fBlockStillBonus")->mValue.getFloat();
102         blockerTerm *= blockerStats.getFatigueTerm();
103 
104         float attackerSkill = 0;
105         if (weapon.isEmpty())
106             attackerSkill = attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand);
107         else
108             attackerSkill = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon));
109         float attackerTerm = attackerSkill + 0.2f * attackerStats.getAttribute(ESM::Attribute::Agility).getModified()
110                 + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified();
111         attackerTerm *= attackerStats.getFatigueTerm();
112 
113         int x = int(blockerTerm - attackerTerm);
114         int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger();
115         int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger();
116         x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x));
117 
118         if (Misc::Rng::roll0to99() < x)
119         {
120             // Reduce shield durability by incoming damage
121             int shieldhealth = shield->getClass().getItemHealth(*shield);
122 
123             shieldhealth -= std::min(shieldhealth, int(damage));
124             shield->getCellRef().setCharge(shieldhealth);
125             if (shieldhealth == 0)
126                 inv.unequipItem(*shield, blocker);
127             // Reduce blocker fatigue
128             const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat();
129             const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat();
130             const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->mValue.getFloat();
131             MWMechanics::DynamicStat<float> fatigue = blockerStats.getFatigue();
132             float normalizedEncumbrance = blocker.getClass().getNormalizedEncumbrance(blocker);
133             normalizedEncumbrance = std::min(1.f, normalizedEncumbrance);
134             float fatigueLoss = fFatigueBlockBase + normalizedEncumbrance * fFatigueBlockMult;
135             if (!weapon.isEmpty())
136                 fatigueLoss += weapon.getClass().getWeight(weapon) * attackStrength * fWeaponFatigueBlockMult;
137             fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
138             blockerStats.setFatigue(fatigue);
139 
140             blockerStats.setBlock(true);
141 
142             if (blocker == getPlayer())
143                 blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0);
144 
145             return true;
146         }
147         return false;
148     }
149 
isNormalWeapon(const MWWorld::Ptr & weapon)150     bool isNormalWeapon(const MWWorld::Ptr &weapon)
151     {
152         if (weapon.isEmpty())
153             return false;
154 
155         const int flags = weapon.get<ESM::Weapon>()->mBase->mData.mFlags;
156         bool isSilver = flags & ESM::Weapon::Silver;
157         bool isMagical = flags & ESM::Weapon::Magical;
158         bool isEnchanted = !weapon.getClass().getEnchantment(weapon).empty();
159 
160         return !isSilver && !isMagical && (!isEnchanted || !Settings::Manager::getBool("enchanted weapons are magical", "Game"));
161     }
162 
resistNormalWeapon(const MWWorld::Ptr & actor,const MWWorld::Ptr & attacker,const MWWorld::Ptr & weapon,float & damage)163     void resistNormalWeapon(const MWWorld::Ptr &actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr &weapon, float &damage)
164     {
165         if (damage == 0 || weapon.isEmpty() || !isNormalWeapon(weapon))
166             return;
167 
168         const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects();
169         const float resistance = effects.get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() / 100.f;
170         const float weakness = effects.get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude() / 100.f;
171 
172         damage *= 1.f - std::min(1.f, resistance-weakness);
173 
174         if (damage == 0 && attacker == getPlayer())
175             MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}");
176     }
177 
applyWerewolfDamageMult(const MWWorld::Ptr & actor,const MWWorld::Ptr & weapon,float & damage)178     void applyWerewolfDamageMult(const MWWorld::Ptr &actor, const MWWorld::Ptr &weapon, float &damage)
179     {
180         if (damage == 0 || weapon.isEmpty() || !actor.getClass().isNpc())
181             return;
182 
183         const int flags = weapon.get<ESM::Weapon>()->mBase->mData.mFlags;
184         bool isSilver = flags & ESM::Weapon::Silver;
185 
186         if (isSilver && actor.getClass().getNpcStats(actor).isWerewolf())
187         {
188             const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
189             damage *= store.get<ESM::GameSetting>().find("fWereWolfSilverWeaponDamageMult")->mValue.getFloat();
190         }
191     }
192 
projectileHit(const MWWorld::Ptr & attacker,const MWWorld::Ptr & victim,MWWorld::Ptr weapon,const MWWorld::Ptr & projectile,const osg::Vec3f & hitPosition,float attackStrength)193     void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile,
194                        const osg::Vec3f& hitPosition, float attackStrength)
195     {
196         MWBase::World *world = MWBase::Environment::get().getWorld();
197         const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
198 
199         bool validVictim = !victim.isEmpty() && victim.getClass().isActor();
200 
201         float damage = 0.f;
202         if (validVictim)
203         {
204             if (attacker == getPlayer())
205                 MWBase::Environment::get().getWindowManager()->setEnemy(victim);
206 
207             int weaponSkill = ESM::Skill::Marksman;
208             if (!weapon.isEmpty())
209                 weaponSkill = weapon.getClass().getEquipmentSkill(weapon);
210 
211             int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon));
212 
213             if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue))
214             {
215                 victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false);
216                 MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker);
217                 return;
218             }
219 
220             const unsigned char* attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
221             damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage
222 
223             // Arrow/bolt damage
224             // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon
225             attack = projectile.get<ESM::Weapon>()->mBase->mData.mChop;
226             damage += attack[0] + ((attack[1] - attack[0]) * attackStrength);
227 
228             adjustWeaponDamage(damage, weapon, attacker);
229             if (weapon == projectile || Settings::Manager::getBool("only appropriate ammunition bypasses resistance", "Game") || isNormalWeapon(weapon))
230                 resistNormalWeapon(victim, attacker, projectile, damage);
231             applyWerewolfDamageMult(victim, projectile, damage);
232 
233             if (attacker == getPlayer())
234                 attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
235 
236             const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence();
237             bool unaware = attacker == getPlayer() && !sequence.isInCombat()
238                 && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim);
239             bool knockedDown = victim.getClass().getCreatureStats(victim).getKnockedDown();
240             if (knockedDown || unaware)
241             {
242                 damage *= gmst.find("fCombatKODamageMult")->mValue.getFloat();
243                 if (!knockedDown)
244                     MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
245             }
246         }
247 
248         reduceWeaponCondition(damage, validVictim, weapon, attacker);
249 
250         // Apply "On hit" effect of the projectile
251         bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition, true);
252 
253         if (validVictim)
254         {
255             // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory
256             if (victim != getPlayer() && !appliedEnchantment)
257             {
258                 float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat();
259                 if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f)
260                     victim.getClass().getContainerStore(victim).add(projectile, 1, victim);
261             }
262 
263             victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true);
264         }
265     }
266 
getHitChance(const MWWorld::Ptr & attacker,const MWWorld::Ptr & victim,int skillValue)267     float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue)
268     {
269         MWMechanics::CreatureStats &stats = attacker.getClass().getCreatureStats(attacker);
270         const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
271 
272         MWBase::World *world = MWBase::Environment::get().getWorld();
273         const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
274 
275         float defenseTerm = 0;
276         MWMechanics::CreatureStats& victimStats = victim.getClass().getCreatureStats(victim);
277         if (victimStats.getFatigue().getCurrent() >= 0)
278         {
279             // Maybe we should keep an aware state for actors updated every so often instead of testing every time
280             bool unaware = (!victimStats.getAiSequence().isInCombat())
281                     && (attacker == getPlayer())
282                     && (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim));
283             if (!(victimStats.getKnockedDown() ||
284                     victimStats.isParalyzed()
285                     || unaware ))
286             {
287                 defenseTerm = victimStats.getEvasion();
288             }
289             defenseTerm += std::min(100.f,
290                                     gmst.find("fCombatInvisoMult")->mValue.getFloat() *
291                                     victimStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude());
292             defenseTerm += std::min(100.f,
293                                     gmst.find("fCombatInvisoMult")->mValue.getFloat() *
294                                     victimStats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude());
295         }
296         float attackTerm = skillValue +
297                           (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
298                           (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
299         attackTerm *= stats.getFatigueTerm();
300         attackTerm += mageffects.get(ESM::MagicEffect::FortifyAttack).getMagnitude() -
301                      mageffects.get(ESM::MagicEffect::Blind).getMagnitude();
302 
303         return round(attackTerm - defenseTerm);
304     }
305 
applyElementalShields(const MWWorld::Ptr & attacker,const MWWorld::Ptr & victim)306     void applyElementalShields(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim)
307     {
308         // Don't let elemental shields harm the player in god mode.
309         bool godmode = attacker == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
310         if (godmode)
311             return;
312         for (int i=0; i<3; ++i)
313         {
314             float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).getMagnitude();
315 
316             if (!magnitude)
317                 continue;
318 
319             CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker);
320             float saveTerm = attacker.getClass().getSkill(attacker, ESM::Skill::Destruction)
321                     + 0.2f * attackerStats.getAttribute(ESM::Attribute::Willpower).getModified()
322                     + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified();
323 
324             float fatigueMax = attackerStats.getFatigue().getModified();
325             float fatigueCurrent = attackerStats.getFatigue().getCurrent();
326 
327             float normalisedFatigue = floor(fatigueMax)==0 ? 1 : std::max (0.0f, (fatigueCurrent/fatigueMax));
328 
329             saveTerm *= 1.25f * normalisedFatigue;
330 
331             float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99());
332 
333             int element = ESM::MagicEffect::FireDamage;
334             if (i == 1)
335                 element = ESM::MagicEffect::ShockDamage;
336             if (i == 2)
337                 element = ESM::MagicEffect::FrostDamage;
338 
339             float elementResistance = MWMechanics::getEffectResistanceAttribute(element, &attackerStats.getMagicEffects());
340 
341             x = std::min(100.f, x + elementResistance);
342 
343             static const float fElementalShieldMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fElementalShieldMult")->mValue.getFloat();
344             x = fElementalShieldMult * magnitude * (1.f - 0.01f * x);
345 
346             // Note swapped victim and attacker, since the attacker takes the damage here.
347             x = scaleDamage(x, victim, attacker);
348 
349             MWMechanics::DynamicStat<float> health = attackerStats.getHealth();
350             health.setCurrent(health.getCurrent() - x);
351             attackerStats.setHealth(health);
352 
353             MWBase::Environment::get().getSoundManager()->playSound3D(attacker, "Health Damage", 1.0f, 1.0f);
354         }
355     }
356 
reduceWeaponCondition(float damage,bool hit,MWWorld::Ptr & weapon,const MWWorld::Ptr & attacker)357     void reduceWeaponCondition(float damage, bool hit, MWWorld::Ptr &weapon, const MWWorld::Ptr &attacker)
358     {
359         if (weapon.isEmpty())
360             return;
361 
362         if (!hit)
363             damage = 0.f;
364 
365         const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
366         if(weaphashealth)
367         {
368             int weaphealth = weapon.getClass().getItemHealth(weapon);
369 
370             bool godmode = attacker == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
371 
372             // weapon condition does not degrade when godmode is on
373             if (!godmode)
374             {
375                 const float fWeaponDamageMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWeaponDamageMult")->mValue.getFloat();
376                 float x = std::max(1.f, fWeaponDamageMult * damage);
377 
378                 weaphealth -= std::min(int(x), weaphealth);
379                 weapon.getCellRef().setCharge(weaphealth);
380             }
381 
382             // Weapon broken? unequip it
383             if (weaphealth == 0)
384                 weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon, attacker);
385         }
386     }
387 
adjustWeaponDamage(float & damage,const MWWorld::Ptr & weapon,const MWWorld::Ptr & attacker)388     void adjustWeaponDamage(float &damage, const MWWorld::Ptr &weapon, const MWWorld::Ptr& attacker)
389     {
390         if (weapon.isEmpty())
391             return;
392 
393         const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
394         if (weaphashealth)
395         {
396             damage *= weapon.getClass().getItemNormalizedHealth(weapon);
397         }
398 
399         static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
400                 .find("fDamageStrengthBase")->mValue.getFloat();
401         static const float fDamageStrengthMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
402                 .find("fDamageStrengthMult")->mValue.getFloat();
403         damage *= fDamageStrengthBase +
404                 (attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1f);
405     }
406 
getHandToHandDamage(const MWWorld::Ptr & attacker,const MWWorld::Ptr & victim,float & damage,bool & healthdmg,float attackStrength)407     void getHandToHandDamage(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, float &damage, bool &healthdmg, float attackStrength)
408     {
409         const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
410         float minstrike = store.get<ESM::GameSetting>().find("fMinHandToHandMult")->mValue.getFloat();
411         float maxstrike = store.get<ESM::GameSetting>().find("fMaxHandToHandMult")->mValue.getFloat();
412         damage  = static_cast<float>(attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand));
413         damage *= minstrike + ((maxstrike-minstrike)*attackStrength);
414 
415         MWMechanics::CreatureStats& otherstats = victim.getClass().getCreatureStats(victim);
416         healthdmg = otherstats.isParalyzed()
417                 || otherstats.getKnockedDown();
418         bool isWerewolf = (attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf());
419 
420         // Options in the launcher's combo box: unarmedFactorsStrengthComboBox
421         // 0 = Do not factor strength into hand-to-hand combat.
422         // 1 = Factor into werewolf hand-to-hand combat.
423         // 2 = Ignore werewolves.
424         int factorStrength = Settings::Manager::getInt("strength influences hand to hand", "Game");
425         if (factorStrength == 1 || (factorStrength == 2 && !isWerewolf)) {
426             damage *= attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() / 40.0f;
427         }
428 
429         if(isWerewolf)
430         {
431             healthdmg = true;
432             // GLOB instead of GMST because it gets updated during a quest
433             damage *= MWBase::Environment::get().getWorld()->getGlobalFloat("werewolfclawmult");
434         }
435         if(healthdmg)
436             damage *= store.get<ESM::GameSetting>().find("fHandtoHandHealthPer")->mValue.getFloat();
437 
438         MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
439         if(isWerewolf)
440         {
441             const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfHit");
442             if(sound)
443                 sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f);
444         }
445         else if (!healthdmg)
446             sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f);
447     }
448 
applyFatigueLoss(const MWWorld::Ptr & attacker,const MWWorld::Ptr & weapon,float attackStrength)449     void applyFatigueLoss(const MWWorld::Ptr &attacker, const MWWorld::Ptr &weapon, float attackStrength)
450     {
451         // somewhat of a guess, but using the weapon weight makes sense
452         const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
453         const float fFatigueAttackBase = store.find("fFatigueAttackBase")->mValue.getFloat();
454         const float fFatigueAttackMult = store.find("fFatigueAttackMult")->mValue.getFloat();
455         const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->mValue.getFloat();
456         CreatureStats& stats = attacker.getClass().getCreatureStats(attacker);
457         MWMechanics::DynamicStat<float> fatigue = stats.getFatigue();
458         const float normalizedEncumbrance = attacker.getClass().getNormalizedEncumbrance(attacker);
459 
460         bool godmode = attacker == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
461 
462         if (!godmode)
463         {
464             float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult;
465             if (!weapon.isEmpty())
466                 fatigueLoss += weapon.getClass().getWeight(weapon) * attackStrength * fWeaponFatigueMult;
467             fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
468             stats.setFatigue(fatigue);
469         }
470     }
471 
getFightDistanceBias(const MWWorld::Ptr & actor1,const MWWorld::Ptr & actor2)472     float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2)
473     {
474         osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3());
475         osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3());
476 
477         float d = getAggroDistance(actor1, pos1, pos2);
478 
479         static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
480                     "iFightDistanceBase")->mValue.getInteger();
481         static const float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
482                     "fFightDistanceMultiplier")->mValue.getFloat();
483 
484         return (iFightDistanceBase - fFightDistanceMultiplier * d);
485     }
486 
isTargetMagicallyHidden(const MWWorld::Ptr & target)487     bool isTargetMagicallyHidden(const MWWorld::Ptr& target)
488     {
489         const MagicEffects& magicEffects = target.getClass().getCreatureStats(target).getMagicEffects();
490         return (magicEffects.get(ESM::MagicEffect::Invisibility).getMagnitude() > 0)
491             || (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75);
492     }
493 
getAggroDistance(const MWWorld::Ptr & actor,const osg::Vec3f & lhs,const osg::Vec3f & rhs)494     float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs)
495     {
496         if (canActorMoveByZAxis(actor))
497             return distanceIgnoreZ(lhs, rhs);
498         return distance(lhs, rhs);
499     }
500 }
501