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