2022-04-12 18:48:27 +03:00
|
|
|
|
2014-01-21 01:01:21 +01:00
|
|
|
#include "combat.hpp"
|
|
|
|
|
2015-04-22 17:58:55 +02:00
|
|
|
#include <components/misc/rng.hpp>
|
2018-03-31 02:38:36 +03:00
|
|
|
#include <components/settings/settings.hpp>
|
2015-03-15 14:07:47 +13:00
|
|
|
|
2015-11-20 21:57:04 +01:00
|
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
|
|
|
2022-09-05 19:35:15 +02:00
|
|
|
#include <components/esm3/loadench.hpp>
|
|
|
|
#include <components/esm3/loadmgef.hpp>
|
|
|
|
#include <components/esm3/loadsoun.hpp>
|
|
|
|
|
2014-01-21 01:01:21 +01:00
|
|
|
#include "../mwbase/environment.hpp"
|
2014-03-08 05:51:47 +01:00
|
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
|
|
#include "../mwbase/soundmanager.hpp"
|
2016-06-17 23:07:16 +09:00
|
|
|
#include "../mwbase/windowmanager.hpp"
|
2014-01-21 01:01:21 +01:00
|
|
|
#include "../mwbase/world.hpp"
|
|
|
|
|
|
|
|
#include "../mwworld/class.hpp"
|
2014-02-23 20:11:05 +01:00
|
|
|
#include "../mwworld/esmstore.hpp"
|
2023-02-07 00:37:55 +01:00
|
|
|
#include "../mwworld/globals.hpp"
|
2014-01-21 01:01:21 +01:00
|
|
|
#include "../mwworld/inventorystore.hpp"
|
|
|
|
|
2016-06-17 23:07:16 +09:00
|
|
|
#include "actorutil.hpp"
|
|
|
|
#include "difficultyscaling.hpp"
|
|
|
|
#include "movement.hpp"
|
2020-04-04 18:28:53 +03:00
|
|
|
#include "npcstats.hpp"
|
2019-10-19 12:40:34 +02:00
|
|
|
#include "pathfinding.hpp"
|
2016-06-17 23:07:16 +09:00
|
|
|
#include "spellcasting.hpp"
|
2020-04-04 18:28:53 +03:00
|
|
|
#include "spellresistance.hpp"
|
2014-01-22 12:09:44 +01:00
|
|
|
|
2014-01-21 01:01:21 +01:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
2015-05-30 01:00:24 +02:00
|
|
|
float signedAngleRadians(const osg::Vec3f& v1, const osg::Vec3f& v2, const osg::Vec3f& normal)
|
2014-01-21 01:01:21 +01:00
|
|
|
{
|
2015-05-30 01:00:24 +02:00
|
|
|
return std::atan2((normal * (v1 ^ v2)), (v1 * v2));
|
2014-01-21 01:01:21 +01:00
|
|
|
}
|
|
|
|
|
2016-02-22 19:37:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace MWMechanics
|
2014-03-08 05:51:47 +01:00
|
|
|
{
|
2016-02-22 19:37:19 +01:00
|
|
|
|
2016-12-19 10:15:19 +01:00
|
|
|
bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object,
|
|
|
|
const osg::Vec3f& hitPosition, const bool fromProjectile)
|
2014-03-08 05:51:47 +01:00
|
|
|
{
|
2022-10-18 09:26:55 +02:00
|
|
|
const ESM::RefId& enchantmentName
|
|
|
|
= !object.isEmpty() ? object.getClass().getEnchantment(object) : ESM::RefId::sEmpty;
|
2016-02-22 19:37:19 +01:00
|
|
|
if (!enchantmentName.empty())
|
2014-03-08 05:51:47 +01:00
|
|
|
{
|
2016-02-22 19:37:19 +01:00
|
|
|
const ESM::Enchantment* enchantment
|
|
|
|
= MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
|
|
|
|
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
|
|
|
{
|
2016-12-19 10:15:19 +01:00
|
|
|
MWMechanics::CastSpell cast(attacker, victim, fromProjectile);
|
2016-02-22 19:37:19 +01:00
|
|
|
cast.mHitPosition = hitPosition;
|
2021-10-21 17:22:15 +02:00
|
|
|
cast.cast(object, 0, false);
|
2021-10-11 21:46:15 +02:00
|
|
|
// Apply magic effects directly instead of waiting a frame to allow soul trap to work on one-hit kills
|
2021-10-14 17:40:03 +02:00
|
|
|
if (!victim.isEmpty() && victim.getClass().isActor())
|
2021-10-14 16:49:30 +02:00
|
|
|
MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(victim);
|
2016-02-22 19:37:19 +01:00
|
|
|
return true;
|
|
|
|
}
|
2014-03-08 05:51:47 +01:00
|
|
|
}
|
2016-02-22 19:37:19 +01:00
|
|
|
return false;
|
2014-03-08 05:51:47 +01:00
|
|
|
}
|
2014-01-21 01:01:21 +01:00
|
|
|
|
2015-06-26 05:15:07 +02:00
|
|
|
bool blockMeleeAttack(const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon,
|
|
|
|
float damage, float attackStrength)
|
2014-01-21 01:01:21 +01:00
|
|
|
{
|
|
|
|
if (!blocker.getClass().hasInventoryStore(blocker))
|
|
|
|
return false;
|
|
|
|
|
2014-07-20 16:52:57 +02:00
|
|
|
MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker);
|
|
|
|
|
|
|
|
if (blockerStats.getKnockedDown() // Used for both knockout or knockdown
|
|
|
|
|| blockerStats.getHitRecovery() || blockerStats.isParalyzed())
|
|
|
|
return false;
|
|
|
|
|
2014-12-12 16:49:22 +01:00
|
|
|
if (!MWBase::Environment::get().getMechanicsManager()->isReadyToBlock(blocker))
|
2014-01-21 01:01:21 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker);
|
|
|
|
MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
2021-10-11 11:46:21 +00:00
|
|
|
if (shield == inv.end() || shield->getType() != ESM::Armor::sRecordId)
|
2014-01-21 01:01:21 +01:00
|
|
|
return false;
|
|
|
|
|
2015-05-30 01:00:24 +02:00
|
|
|
if (!blocker.getRefData().getBaseNode())
|
|
|
|
return false; // shouldn't happen
|
|
|
|
|
|
|
|
float angleDegrees = osg::RadiansToDegrees(signedAngleRadians(
|
|
|
|
(attacker.getRefData().getPosition().asVec3() - blocker.getRefData().getPosition().asVec3()),
|
|
|
|
blocker.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0, 1, 0), osg::Vec3f(0, 0, 1)));
|
2014-01-21 01:01:21 +01:00
|
|
|
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst
|
|
|
|
= MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
2022-04-12 18:48:27 +03:00
|
|
|
static const float fCombatBlockLeftAngle = gmst.find("fCombatBlockLeftAngle")->mValue.getFloat();
|
|
|
|
if (angleDegrees < fCombatBlockLeftAngle)
|
2014-01-21 01:01:21 +01:00
|
|
|
return false;
|
2022-04-12 18:48:27 +03:00
|
|
|
static const float fCombatBlockRightAngle = gmst.find("fCombatBlockRightAngle")->mValue.getFloat();
|
|
|
|
if (angleDegrees > fCombatBlockRightAngle)
|
2014-01-21 01:01:21 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker);
|
|
|
|
|
2015-03-08 17:42:07 +13:00
|
|
|
float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block)
|
|
|
|
+ 0.2f * blockerStats.getAttribute(ESM::Attribute::Agility).getModified()
|
|
|
|
+ 0.1f * blockerStats.getAttribute(ESM::Attribute::Luck).getModified();
|
2015-06-26 05:15:07 +02:00
|
|
|
float enemySwing = attackStrength;
|
2022-04-12 18:48:27 +03:00
|
|
|
static const float fSwingBlockMult = gmst.find("fSwingBlockMult")->mValue.getFloat();
|
|
|
|
static const float fSwingBlockBase = gmst.find("fSwingBlockBase")->mValue.getFloat();
|
|
|
|
float swingTerm = enemySwing * fSwingBlockMult + fSwingBlockBase;
|
2014-01-21 01:01:21 +01:00
|
|
|
|
|
|
|
float blockerTerm = blockTerm * swingTerm;
|
|
|
|
if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0)
|
2022-04-12 18:48:27 +03:00
|
|
|
{
|
|
|
|
static const float fBlockStillBonus = gmst.find("fBlockStillBonus")->mValue.getFloat();
|
|
|
|
blockerTerm *= fBlockStillBonus;
|
|
|
|
}
|
2014-01-21 01:01:21 +01:00
|
|
|
blockerTerm *= blockerStats.getFatigueTerm();
|
|
|
|
|
2018-12-23 15:18:33 +04:00
|
|
|
float attackerSkill = 0;
|
2014-12-17 16:07:50 +01:00
|
|
|
if (weapon.isEmpty())
|
|
|
|
attackerSkill = attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand);
|
|
|
|
else
|
|
|
|
attackerSkill = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon));
|
2015-03-08 17:42:07 +13:00
|
|
|
float attackerTerm = attackerSkill + 0.2f * attackerStats.getAttribute(ESM::Attribute::Agility).getModified()
|
|
|
|
+ 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified();
|
2014-01-21 01:01:21 +01:00
|
|
|
attackerTerm *= attackerStats.getFatigueTerm();
|
|
|
|
|
2022-04-12 18:48:27 +03:00
|
|
|
static const int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger();
|
|
|
|
static const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger();
|
2021-11-06 07:30:28 +03:00
|
|
|
int x = std::clamp<int>(blockerTerm - attackerTerm, iBlockMinChance, iBlockMaxChance);
|
2014-01-21 01:01:21 +01:00
|
|
|
|
2022-03-06 21:56:02 +02:00
|
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
|
|
if (Misc::Rng::roll0to99(prng) < x)
|
2014-01-21 01:01:21 +01:00
|
|
|
{
|
2019-06-20 20:45:52 +03:00
|
|
|
// Reduce shield durability by incoming damage
|
|
|
|
int shieldhealth = shield->getClass().getItemHealth(*shield);
|
2014-01-21 01:01:21 +01:00
|
|
|
|
2019-06-20 20:45:52 +03:00
|
|
|
shieldhealth -= std::min(shieldhealth, int(damage));
|
|
|
|
shield->getCellRef().setCharge(shieldhealth);
|
|
|
|
if (shieldhealth == 0)
|
2023-01-16 23:51:04 +01:00
|
|
|
inv.unequipItem(*shield);
|
2014-01-21 01:01:21 +01:00
|
|
|
// Reduce blocker fatigue
|
2022-04-12 18:48:27 +03:00
|
|
|
static const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat();
|
|
|
|
static const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat();
|
|
|
|
static const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->mValue.getFloat();
|
2014-01-21 01:01:21 +01:00
|
|
|
MWMechanics::DynamicStat<float> fatigue = blockerStats.getFatigue();
|
2014-10-05 15:50:01 +02:00
|
|
|
float normalizedEncumbrance = blocker.getClass().getNormalizedEncumbrance(blocker);
|
2014-01-21 01:01:21 +01:00
|
|
|
normalizedEncumbrance = std::min(1.f, normalizedEncumbrance);
|
|
|
|
float fatigueLoss = fFatigueBlockBase + normalizedEncumbrance * fFatigueBlockMult;
|
2014-12-17 16:07:50 +01:00
|
|
|
if (!weapon.isEmpty())
|
2015-06-26 05:15:07 +02:00
|
|
|
fatigueLoss += weapon.getClass().getWeight(weapon) * attackStrength * fWeaponFatigueBlockMult;
|
2014-01-21 01:01:21 +01:00
|
|
|
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
|
|
|
|
blockerStats.setFatigue(fatigue);
|
|
|
|
|
|
|
|
blockerStats.setBlock(true);
|
|
|
|
|
2015-08-21 21:12:39 +12:00
|
|
|
if (blocker == getPlayer())
|
2014-01-21 01:01:21 +01:00
|
|
|
blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-08 21:17:15 +03:00
|
|
|
bool isNormalWeapon(const MWWorld::Ptr& weapon)
|
2014-01-22 12:09:44 +01:00
|
|
|
{
|
2018-12-08 21:17:15 +03:00
|
|
|
if (weapon.isEmpty())
|
|
|
|
return false;
|
2014-01-22 12:09:44 +01:00
|
|
|
|
2018-12-08 21:55:08 +03:00
|
|
|
const int flags = weapon.get<ESM::Weapon>()->mBase->mData.mFlags;
|
2018-12-08 21:17:15 +03:00
|
|
|
bool isSilver = flags & ESM::Weapon::Silver;
|
|
|
|
bool isMagical = flags & ESM::Weapon::Magical;
|
|
|
|
bool isEnchanted = !weapon.getClass().getEnchantment(weapon).empty();
|
2014-01-22 12:09:44 +01:00
|
|
|
|
2018-12-08 21:17:15 +03:00
|
|
|
return !isSilver && !isMagical
|
|
|
|
&& (!isEnchanted || !Settings::Manager::getBool("enchanted weapons are magical", "Game"));
|
|
|
|
}
|
|
|
|
|
2018-12-08 21:55:08 +03:00
|
|
|
void resistNormalWeapon(
|
|
|
|
const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage)
|
2018-12-08 21:17:15 +03:00
|
|
|
{
|
2018-12-08 21:47:39 +03:00
|
|
|
if (damage == 0 || weapon.isEmpty() || !isNormalWeapon(weapon))
|
2018-12-08 21:55:08 +03:00
|
|
|
return;
|
2018-12-08 21:17:15 +03:00
|
|
|
|
2018-12-08 21:47:39 +03:00
|
|
|
const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects();
|
|
|
|
const float resistance = effects.get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() / 100.f;
|
|
|
|
const float weakness = effects.get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude() / 100.f;
|
2014-01-22 12:09:44 +01:00
|
|
|
|
2018-12-08 21:47:39 +03:00
|
|
|
damage *= 1.f - std::min(1.f, resistance - weakness);
|
2014-01-22 12:09:44 +01:00
|
|
|
|
2018-12-08 21:55:08 +03:00
|
|
|
if (damage == 0 && attacker == getPlayer())
|
|
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}");
|
2014-01-22 12:09:44 +01:00
|
|
|
}
|
|
|
|
|
2018-12-08 21:47:39 +03:00
|
|
|
void applyWerewolfDamageMult(const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon, float& damage)
|
|
|
|
{
|
2018-12-08 21:55:08 +03:00
|
|
|
if (damage == 0 || weapon.isEmpty() || !actor.getClass().isNpc())
|
2018-12-08 21:47:39 +03:00
|
|
|
return;
|
|
|
|
|
2018-12-08 21:55:08 +03:00
|
|
|
const int flags = weapon.get<ESM::Weapon>()->mBase->mData.mFlags;
|
2018-12-08 21:47:39 +03:00
|
|
|
bool isSilver = flags & ESM::Weapon::Silver;
|
|
|
|
|
2018-12-08 21:55:08 +03:00
|
|
|
if (isSilver && actor.getClass().getNpcStats(actor).isWerewolf())
|
|
|
|
{
|
|
|
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
|
|
|
damage *= store.get<ESM::GameSetting>().find("fWereWolfSilverWeaponDamageMult")->mValue.getFloat();
|
|
|
|
}
|
2018-12-08 21:47:39 +03:00
|
|
|
}
|
|
|
|
|
2016-12-19 10:15:19 +01:00
|
|
|
void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon,
|
2015-06-26 02:32:41 +02:00
|
|
|
const MWWorld::Ptr& projectile, const osg::Vec3f& hitPosition, float attackStrength)
|
2014-03-08 05:51:47 +01:00
|
|
|
{
|
|
|
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst = world->getStore().get<ESM::GameSetting>();
|
|
|
|
|
2016-12-19 10:15:19 +01:00
|
|
|
bool validVictim = !victim.isEmpty() && victim.getClass().isActor();
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2022-02-23 10:10:50 +03:00
|
|
|
int weaponSkill = ESM::Skill::Marksman;
|
|
|
|
if (!weapon.isEmpty())
|
|
|
|
weaponSkill = weapon.getClass().getEquipmentSkill(weapon);
|
|
|
|
|
2016-12-19 10:15:19 +01:00
|
|
|
float damage = 0.f;
|
|
|
|
if (validVictim)
|
|
|
|
{
|
|
|
|
if (attacker == getPlayer())
|
|
|
|
MWBase::Environment::get().getWindowManager()->setEnemy(victim);
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2022-02-23 10:10:50 +03:00
|
|
|
int skillValue = attacker.getClass().getSkill(attacker, weaponSkill);
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2022-03-06 21:56:02 +02:00
|
|
|
if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue))
|
2016-12-19 10:15:19 +01:00
|
|
|
{
|
|
|
|
victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false);
|
|
|
|
MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker);
|
|
|
|
return;
|
|
|
|
}
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2016-12-19 10:15:19 +01:00
|
|
|
const unsigned char* attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
|
|
|
|
damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2016-12-19 10:15:19 +01:00
|
|
|
// Arrow/bolt damage
|
|
|
|
// NB in case of thrown weapons, we are applying the damage twice since projectile == weapon
|
|
|
|
attack = projectile.get<ESM::Weapon>()->mBase->mData.mChop;
|
|
|
|
damage += attack[0] + ((attack[1] - attack[0]) * attackStrength);
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2016-12-19 10:15:19 +01:00
|
|
|
adjustWeaponDamage(damage, weapon, attacker);
|
2022-02-23 10:10:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
reduceWeaponCondition(damage, validVictim, weapon, attacker);
|
|
|
|
|
|
|
|
if (validVictim)
|
|
|
|
{
|
2019-02-22 17:18:23 +03:00
|
|
|
if (weapon == projectile
|
|
|
|
|| Settings::Manager::getBool("only appropriate ammunition bypasses resistance", "Game")
|
|
|
|
|| isNormalWeapon(weapon))
|
2018-12-08 21:55:08 +03:00
|
|
|
resistNormalWeapon(victim, attacker, projectile, damage);
|
2018-12-08 21:47:39 +03:00
|
|
|
applyWerewolfDamageMult(victim, projectile, damage);
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2018-07-29 19:27:13 +03:00
|
|
|
if (attacker == getPlayer())
|
2016-12-19 10:15:19 +01:00
|
|
|
attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
|
2018-07-29 19:27:13 +03:00
|
|
|
|
2019-07-31 13:54:27 +03:00
|
|
|
const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence();
|
|
|
|
bool unaware = attacker == getPlayer() && !sequence.isInCombat()
|
|
|
|
&& !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim);
|
|
|
|
bool knockedDown = victim.getClass().getCreatureStats(victim).getKnockedDown();
|
|
|
|
if (knockedDown || unaware)
|
|
|
|
{
|
2022-04-12 18:48:27 +03:00
|
|
|
static const float fCombatKODamageMult = gmst.find("fCombatKODamageMult")->mValue.getFloat();
|
|
|
|
damage *= fCombatKODamageMult;
|
2019-07-31 13:54:27 +03:00
|
|
|
if (!knockedDown)
|
2022-10-18 09:26:55 +02:00
|
|
|
MWBase::Environment::get().getSoundManager()->playSound3D(
|
|
|
|
victim, ESM::RefId::stringRefId("critical damage"), 1.0f, 1.0f);
|
2018-07-29 19:27:13 +03:00
|
|
|
}
|
2016-12-19 10:15:19 +01:00
|
|
|
}
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2019-10-20 13:30:52 +03:00
|
|
|
// Apply "On hit" effect of the projectile
|
|
|
|
bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition, true);
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2016-12-19 10:15:19 +01:00
|
|
|
if (validVictim)
|
2014-06-15 16:00:29 +02:00
|
|
|
{
|
2016-12-19 10:15:19 +01:00
|
|
|
// Non-enchanted arrows shot at enemies have a chance to turn up in their inventory
|
|
|
|
if (victim != getPlayer() && !appliedEnchantment)
|
|
|
|
{
|
2022-04-12 18:48:27 +03:00
|
|
|
static const float fProjectileThrownStoreChance
|
|
|
|
= gmst.find("fProjectileThrownStoreChance")->mValue.getFloat();
|
2022-03-06 21:56:02 +02:00
|
|
|
if (Misc::Rng::rollProbability(world->getPrng()) < fProjectileThrownStoreChance / 100.f)
|
2023-01-16 23:51:04 +01:00
|
|
|
victim.getClass().getContainerStore(victim).add(projectile, 1);
|
2016-12-19 10:15:19 +01:00
|
|
|
}
|
2014-03-08 05:51:47 +01:00
|
|
|
|
2016-12-19 10:15:19 +01:00
|
|
|
victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true);
|
|
|
|
}
|
2014-03-08 05:51:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
float getHitChance(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue)
|
|
|
|
{
|
|
|
|
MWMechanics::CreatureStats& stats = attacker.getClass().getCreatureStats(attacker);
|
|
|
|
const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects();
|
2015-03-12 02:44:41 +01:00
|
|
|
|
|
|
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst = world->getStore().get<ESM::GameSetting>();
|
|
|
|
|
|
|
|
float defenseTerm = 0;
|
2015-08-20 18:17:02 +12:00
|
|
|
MWMechanics::CreatureStats& victimStats = victim.getClass().getCreatureStats(victim);
|
|
|
|
if (victimStats.getFatigue().getCurrent() >= 0)
|
2015-03-12 02:44:41 +01:00
|
|
|
{
|
|
|
|
// Maybe we should keep an aware state for actors updated every so often instead of testing every time
|
|
|
|
bool unaware = (!victimStats.getAiSequence().isInCombat()) && (attacker == getPlayer())
|
|
|
|
&& (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim));
|
|
|
|
if (!(victimStats.getKnockedDown() || victimStats.isParalyzed() || unaware))
|
|
|
|
{
|
|
|
|
defenseTerm = victimStats.getEvasion();
|
|
|
|
}
|
2022-04-12 18:48:27 +03:00
|
|
|
static const float fCombatInvisoMult = gmst.find("fCombatInvisoMult")->mValue.getFloat();
|
2015-03-12 02:44:41 +01:00
|
|
|
defenseTerm += std::min(100.f,
|
|
|
|
fCombatInvisoMult * victimStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude());
|
|
|
|
defenseTerm += std::min(100.f,
|
|
|
|
fCombatInvisoMult * victimStats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude());
|
|
|
|
}
|
2014-03-08 05:51:47 +01:00
|
|
|
float attackTerm = skillValue + (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f)
|
|
|
|
+ (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
|
2015-03-12 02:44:41 +01:00
|
|
|
attackTerm *= stats.getFatigueTerm();
|
|
|
|
attackTerm += mageffects.get(ESM::MagicEffect::FortifyAttack).getMagnitude()
|
2014-08-16 22:38:22 +02:00
|
|
|
- mageffects.get(ESM::MagicEffect::Blind).getMagnitude();
|
2015-03-12 02:44:41 +01:00
|
|
|
|
2015-03-15 08:49:03 +13:00
|
|
|
return round(attackTerm - defenseTerm);
|
2014-03-08 05:51:47 +01:00
|
|
|
}
|
|
|
|
|
2014-07-15 21:53:11 +02:00
|
|
|
void applyElementalShields(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim)
|
|
|
|
{
|
2021-03-01 21:37:30 +00:00
|
|
|
// Don't let elemental shields harm the player in god mode.
|
|
|
|
bool godmode = attacker == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
|
|
|
if (godmode)
|
|
|
|
return;
|
2022-03-06 21:56:02 +02:00
|
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
2014-07-15 21:53:11 +02:00
|
|
|
for (int i = 0; i < 3; ++i)
|
|
|
|
{
|
2014-08-16 22:38:22 +02:00
|
|
|
float magnitude = victim.getClass()
|
|
|
|
.getCreatureStats(victim)
|
|
|
|
.getMagicEffects()
|
|
|
|
.get(ESM::MagicEffect::FireShield + i)
|
|
|
|
.getMagnitude();
|
2014-07-15 21:53:11 +02:00
|
|
|
|
|
|
|
if (!magnitude)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker);
|
|
|
|
float saveTerm = attacker.getClass().getSkill(attacker, ESM::Skill::Destruction)
|
|
|
|
+ 0.2f * attackerStats.getAttribute(ESM::Attribute::Willpower).getModified()
|
|
|
|
+ 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified();
|
|
|
|
|
2015-03-08 17:42:07 +13:00
|
|
|
float fatigueMax = attackerStats.getFatigue().getModified();
|
|
|
|
float fatigueCurrent = attackerStats.getFatigue().getCurrent();
|
2014-07-15 21:53:11 +02:00
|
|
|
|
2015-03-08 17:42:07 +13:00
|
|
|
float normalisedFatigue = floor(fatigueMax) == 0 ? 1 : std::max(0.0f, (fatigueCurrent / fatigueMax));
|
2014-07-15 21:53:11 +02:00
|
|
|
|
|
|
|
saveTerm *= 1.25f * normalisedFatigue;
|
|
|
|
|
2022-03-06 21:56:02 +02:00
|
|
|
float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99(prng));
|
2014-07-15 21:53:11 +02:00
|
|
|
|
|
|
|
int element = ESM::MagicEffect::FireDamage;
|
|
|
|
if (i == 1)
|
|
|
|
element = ESM::MagicEffect::ShockDamage;
|
|
|
|
if (i == 2)
|
|
|
|
element = ESM::MagicEffect::FrostDamage;
|
|
|
|
|
2014-07-15 22:06:46 +02:00
|
|
|
float elementResistance
|
|
|
|
= MWMechanics::getEffectResistanceAttribute(element, &attackerStats.getMagicEffects());
|
2014-07-15 21:53:11 +02:00
|
|
|
|
|
|
|
x = std::min(100.f, x + elementResistance);
|
|
|
|
|
2018-08-29 18:38:12 +03:00
|
|
|
static const float fElementalShieldMult = MWBase::Environment::get()
|
|
|
|
.getWorld()
|
|
|
|
->getStore()
|
|
|
|
.get<ESM::GameSetting>()
|
|
|
|
.find("fElementalShieldMult")
|
|
|
|
->mValue.getFloat();
|
2014-07-15 21:53:11 +02:00
|
|
|
x = fElementalShieldMult * magnitude * (1.f - 0.01f * x);
|
2014-07-20 16:22:52 +02:00
|
|
|
|
|
|
|
// Note swapped victim and attacker, since the attacker takes the damage here.
|
|
|
|
x = scaleDamage(x, victim, attacker);
|
|
|
|
|
2014-07-15 21:53:11 +02:00
|
|
|
MWMechanics::DynamicStat<float> health = attackerStats.getHealth();
|
|
|
|
health.setCurrent(health.getCurrent() - x);
|
|
|
|
attackerStats.setHealth(health);
|
2021-02-07 12:55:10 +01:00
|
|
|
|
2022-10-18 09:26:55 +02:00
|
|
|
MWBase::Environment::get().getSoundManager()->playSound3D(
|
|
|
|
attacker, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f);
|
2014-07-15 21:53:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-02 23:14:17 +02:00
|
|
|
void reduceWeaponCondition(float damage, bool hit, MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker)
|
|
|
|
{
|
|
|
|
if (weapon.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!hit)
|
|
|
|
damage = 0.f;
|
|
|
|
|
|
|
|
const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
|
|
|
|
if (weaphashealth)
|
|
|
|
{
|
|
|
|
int weaphealth = weapon.getClass().getItemHealth(weapon);
|
|
|
|
|
2017-03-25 22:40:11 +04:00
|
|
|
bool godmode
|
|
|
|
= attacker == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
2014-08-02 23:14:17 +02:00
|
|
|
|
2017-03-25 22:40:11 +04:00
|
|
|
// weapon condition does not degrade when godmode is on
|
|
|
|
if (!godmode)
|
|
|
|
{
|
2018-08-29 18:38:12 +03:00
|
|
|
const float fWeaponDamageMult = MWBase::Environment::get()
|
|
|
|
.getWorld()
|
|
|
|
->getStore()
|
|
|
|
.get<ESM::GameSetting>()
|
|
|
|
.find("fWeaponDamageMult")
|
|
|
|
->mValue.getFloat();
|
2017-03-25 22:40:11 +04:00
|
|
|
float x = std::max(1.f, fWeaponDamageMult * damage);
|
|
|
|
|
|
|
|
weaphealth -= std::min(int(x), weaphealth);
|
|
|
|
weapon.getCellRef().setCharge(weaphealth);
|
|
|
|
}
|
2014-08-02 23:14:17 +02:00
|
|
|
|
|
|
|
// Weapon broken? unequip it
|
|
|
|
if (weaphealth == 0)
|
2023-01-16 23:51:04 +01:00
|
|
|
weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon);
|
2014-08-02 23:14:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-12 03:08:58 +01:00
|
|
|
void adjustWeaponDamage(float& damage, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker)
|
2014-08-02 23:14:17 +02:00
|
|
|
{
|
|
|
|
if (weapon.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
|
2018-10-24 18:51:34 +03:00
|
|
|
if (weaphashealth)
|
2014-08-02 23:14:17 +02:00
|
|
|
{
|
2018-10-25 15:45:31 +03:00
|
|
|
damage *= weapon.getClass().getItemNormalizedHealth(weapon);
|
2014-08-02 23:14:17 +02:00
|
|
|
}
|
2015-03-12 03:08:58 +01:00
|
|
|
|
|
|
|
static const float fDamageStrengthBase = MWBase::Environment::get()
|
|
|
|
.getWorld()
|
|
|
|
->getStore()
|
|
|
|
.get<ESM::GameSetting>()
|
2018-08-29 18:38:12 +03:00
|
|
|
.find("fDamageStrengthBase")
|
|
|
|
->mValue.getFloat();
|
2015-03-12 03:08:58 +01:00
|
|
|
static const float fDamageStrengthMult = MWBase::Environment::get()
|
|
|
|
.getWorld()
|
|
|
|
->getStore()
|
|
|
|
.get<ESM::GameSetting>()
|
2018-08-29 18:38:12 +03:00
|
|
|
.find("fDamageStrengthMult")
|
|
|
|
->mValue.getFloat();
|
2015-03-12 03:08:58 +01:00
|
|
|
damage *= fDamageStrengthBase
|
|
|
|
+ (attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified()
|
|
|
|
* fDamageStrengthMult * 0.1f);
|
2014-08-02 23:14:17 +02:00
|
|
|
}
|
2014-12-31 23:13:36 +01:00
|
|
|
|
2015-06-26 05:15:07 +02:00
|
|
|
void getHandToHandDamage(
|
|
|
|
const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, float& damage, bool& healthdmg, float attackStrength)
|
2014-12-31 23:13:36 +01:00
|
|
|
{
|
|
|
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
2022-04-12 18:48:27 +03:00
|
|
|
static const float minstrike = store.get<ESM::GameSetting>().find("fMinHandToHandMult")->mValue.getFloat();
|
|
|
|
static const float maxstrike = store.get<ESM::GameSetting>().find("fMaxHandToHandMult")->mValue.getFloat();
|
2015-03-08 17:42:07 +13:00
|
|
|
damage = static_cast<float>(attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand));
|
2015-06-26 05:15:07 +02:00
|
|
|
damage *= minstrike + ((maxstrike - minstrike) * attackStrength);
|
2014-12-31 23:13:36 +01:00
|
|
|
|
|
|
|
MWMechanics::CreatureStats& otherstats = victim.getClass().getCreatureStats(victim);
|
2015-08-20 18:12:37 +12:00
|
|
|
healthdmg = otherstats.isParalyzed() || otherstats.getKnockedDown();
|
2014-12-31 23:13:36 +01:00
|
|
|
bool isWerewolf = (attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf());
|
2018-07-28 10:10:01 -03:00
|
|
|
|
2018-07-28 23:13:56 -03:00
|
|
|
// Options in the launcher's combo box: unarmedFactorsStrengthComboBox
|
|
|
|
// 0 = Do not factor strength into hand-to-hand combat.
|
|
|
|
// 1 = Factor into werewolf hand-to-hand combat.
|
|
|
|
// 2 = Ignore werewolves.
|
|
|
|
int factorStrength = Settings::Manager::getInt("strength influences hand to hand", "Game");
|
|
|
|
if (factorStrength == 1 || (factorStrength == 2 && !isWerewolf))
|
|
|
|
{
|
2018-07-28 10:10:01 -03:00
|
|
|
damage
|
|
|
|
*= attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified()
|
|
|
|
/ 40.0f;
|
|
|
|
}
|
|
|
|
|
2014-12-31 23:13:36 +01:00
|
|
|
if (isWerewolf)
|
|
|
|
{
|
|
|
|
healthdmg = true;
|
|
|
|
// GLOB instead of GMST because it gets updated during a quest
|
2023-02-07 00:37:55 +01:00
|
|
|
damage *= MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sWerewolfClawMult);
|
2014-12-31 23:13:36 +01:00
|
|
|
}
|
2022-04-12 18:48:27 +03:00
|
|
|
if (healthdmg)
|
|
|
|
{
|
|
|
|
static const float fHandtoHandHealthPer
|
|
|
|
= store.get<ESM::GameSetting>().find("fHandtoHandHealthPer")->mValue.getFloat();
|
|
|
|
damage *= fHandtoHandHealthPer;
|
|
|
|
}
|
2014-12-31 23:13:36 +01:00
|
|
|
|
|
|
|
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
|
|
|
if (isWerewolf)
|
|
|
|
{
|
2022-03-06 21:56:02 +02:00
|
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
2022-10-12 19:51:42 +02:00
|
|
|
const ESM::Sound* sound = store.get<ESM::Sound>().searchRandom("WolfHit", prng);
|
2014-12-31 23:13:36 +01:00
|
|
|
if (sound)
|
|
|
|
sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f);
|
|
|
|
}
|
2019-07-14 23:56:38 +03:00
|
|
|
else if (!healthdmg)
|
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type
The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID
Slowly going through all the changes to make, still hundreds of errors
a lot of functions/structures use std::string or stringview to designate an ID. So it takes time
Continues slowly replacing ids. There are technically more and more compilation errors
I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type
Continue moving forward, changes to the stores
slowly moving along
Starting to see the fruit of those changes.
still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type.
More replacements. Things are starting to get easier
I can see more and more often the issue is that the function is awaiting a RefId, but is given a string
there is less need to go down functions and to fix a long list of them.
Still moving forward, and for the first time error count is going down!
Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably
Cells are back to using string for the name, haven't fixed everything yet. Many other changes
Under the bar of 400 compilation errors.
more good progress <100 compile errors!
More progress
Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string
some more progress on other fronts
Mostly game settings clean
one error opened a lot of other errors. Down to 18, but more will prbably appear
only link errors left??
Fixed link errors
OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
|
|
|
sndMgr->playSound3D(victim, ESM::RefId::stringRefId("Hand To Hand Hit"), 1.0f, 1.0f);
|
2014-12-31 23:13:36 +01:00
|
|
|
}
|
2015-01-11 14:25:46 +13:00
|
|
|
|
2015-06-26 05:15:07 +02:00
|
|
|
void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float attackStrength)
|
2015-01-31 22:28:23 +01:00
|
|
|
{
|
|
|
|
// somewhat of a guess, but using the weapon weight makes sense
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& store
|
|
|
|
= MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
2022-04-12 18:48:27 +03:00
|
|
|
static const float fFatigueAttackBase = store.find("fFatigueAttackBase")->mValue.getFloat();
|
|
|
|
static const float fFatigueAttackMult = store.find("fFatigueAttackMult")->mValue.getFloat();
|
|
|
|
static const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->mValue.getFloat();
|
2015-01-31 22:28:23 +01:00
|
|
|
CreatureStats& stats = attacker.getClass().getCreatureStats(attacker);
|
|
|
|
MWMechanics::DynamicStat<float> fatigue = stats.getFatigue();
|
|
|
|
const float normalizedEncumbrance = attacker.getClass().getNormalizedEncumbrance(attacker);
|
2017-03-25 22:40:11 +04:00
|
|
|
|
|
|
|
bool godmode = attacker == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
|
|
|
|
|
|
|
if (!godmode)
|
|
|
|
{
|
|
|
|
float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult;
|
|
|
|
if (!weapon.isEmpty())
|
|
|
|
fatigueLoss += weapon.getClass().getWeight(weapon) * attackStrength * fWeaponFatigueMult;
|
|
|
|
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
|
|
|
|
stats.setFatigue(fatigue);
|
|
|
|
}
|
2015-01-31 22:28:23 +01:00
|
|
|
}
|
|
|
|
|
2016-11-16 20:15:25 +01:00
|
|
|
float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2)
|
|
|
|
{
|
|
|
|
osg::Vec3f pos1(actor1.getRefData().getPosition().asVec3());
|
|
|
|
osg::Vec3f pos2(actor2.getRefData().getPosition().asVec3());
|
|
|
|
|
2020-04-22 16:06:42 +03:00
|
|
|
float d = getAggroDistance(actor1, pos1, pos2);
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
static const int iFightDistanceBase = MWBase::Environment::get()
|
|
|
|
.getWorld()
|
|
|
|
->getStore()
|
|
|
|
.get<ESM::GameSetting>()
|
2018-08-29 18:38:12 +03:00
|
|
|
.find("iFightDistanceBase")
|
|
|
|
->mValue.getInteger();
|
2016-11-16 20:15:25 +01:00
|
|
|
static const float fFightDistanceMultiplier = MWBase::Environment::get()
|
|
|
|
.getWorld()
|
|
|
|
->getStore()
|
|
|
|
.get<ESM::GameSetting>()
|
2018-08-29 18:38:12 +03:00
|
|
|
.find("fFightDistanceMultiplier")
|
|
|
|
->mValue.getFloat();
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
return (iFightDistanceBase - fFightDistanceMultiplier * d);
|
|
|
|
}
|
2019-04-28 16:41:10 +04:00
|
|
|
|
2019-10-19 12:40:34 +02:00
|
|
|
float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs)
|
|
|
|
{
|
|
|
|
if (canActorMoveByZAxis(actor))
|
|
|
|
return distanceIgnoreZ(lhs, rhs);
|
|
|
|
return distance(lhs, rhs);
|
|
|
|
}
|
2014-01-21 01:01:21 +01:00
|
|
|
}
|