mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-03-14 01:19:59 +00:00
WIP
Probably a first properly working commit? Push to get clang compiler feedback hit engine handler and function binding pass things by reference Reverted late-miss behaviour, fail early on miss with 0 damage and all other flags set to false
This commit is contained in:
parent
a49a900a7b
commit
5775a9af0c
@ -9,6 +9,7 @@
|
||||
|
||||
#include "../mwgui/mode.hpp"
|
||||
#include "../mwrender/animationpriority.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include <components/sdlutil/events.hpp>
|
||||
|
||||
namespace MWWorld
|
||||
@ -59,6 +60,9 @@ namespace MWBase
|
||||
virtual void noGame() = 0;
|
||||
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void actorHit(const MWWorld::Ptr& target, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker,
|
||||
const MWWorld::DamageData& damageData, const MWWorld::HitConfig& hitConfig)
|
||||
= 0;
|
||||
virtual void objectTeleported(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0;
|
||||
virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
|
||||
|
@ -283,8 +283,14 @@ namespace MWClass
|
||||
|
||||
if (!success)
|
||||
{
|
||||
victim.getClass().onHit(
|
||||
victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee);
|
||||
MWWorld::DamageData dmgData = {
|
||||
.mDamage = 0.0f,
|
||||
.mAffectsHealth = false,
|
||||
.mAffectsFatigue = false,
|
||||
.mIsSuccessful = false,
|
||||
.mHitPosition = osg::Vec3f(),
|
||||
};
|
||||
victim.getClass().onHit(victim, MWWorld::Ptr(), ptr, dmgData, MWMechanics::DamageSourceType::Melee);
|
||||
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
||||
return;
|
||||
}
|
||||
@ -342,15 +348,20 @@ namespace MWClass
|
||||
|
||||
MWMechanics::diseaseContact(victim, ptr);
|
||||
|
||||
victim.getClass().onHit(
|
||||
victim, damage, healthdmg, weapon, ptr, hitPosition, true, MWMechanics::DamageSourceType::Melee);
|
||||
MWWorld::DamageData dmgData = {
|
||||
.mDamage = damage,
|
||||
.mAffectsHealth = healthdmg,
|
||||
.mAffectsFatigue = !healthdmg,
|
||||
.mIsSuccessful = true,
|
||||
.mHitPosition = hitPosition,
|
||||
};
|
||||
victim.getClass().onHit(victim, weapon, ptr, dmgData, MWMechanics::DamageSourceType::Melee);
|
||||
}
|
||||
|
||||
void Creature::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
||||
const MWMechanics::DamageSourceType sourceType) const
|
||||
void Creature::onHit(const MWWorld::Ptr& target, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker,
|
||||
const MWWorld::DamageData& damageData, const MWWorld::HitConfig& hitConfig) const
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(target);
|
||||
|
||||
// Self defense
|
||||
bool setOnPcHitMe = true;
|
||||
@ -378,37 +389,39 @@ namespace MWClass
|
||||
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||
// First handle the attacked actor
|
||||
if ((stats.getHitAttemptActorId() == -1)
|
||||
&& (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer()))
|
||||
&& (statsAttacker.getAiSequence().isInCombat(target) || attacker == MWMechanics::getPlayer()))
|
||||
stats.setHitAttemptActorId(statsAttacker.getActorId());
|
||||
|
||||
// Next handle the attacking actor
|
||||
if ((statsAttacker.getHitAttemptActorId() == -1)
|
||||
&& (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer()))
|
||||
&& (statsAttacker.getAiSequence().isInCombat(target) || attacker == MWMechanics::getPlayer()))
|
||||
statsAttacker.setHitAttemptActorId(stats.getActorId());
|
||||
}
|
||||
|
||||
if (!object.isEmpty())
|
||||
stats.setLastHitAttemptObject(object.getCellRef().getRefId());
|
||||
if (!weapon.isEmpty())
|
||||
stats.setLastHitAttemptObject(weapon.getCellRef().getRefId());
|
||||
|
||||
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||
{
|
||||
const ESM::RefId& script = ptr.get<ESM::Creature>()->mBase->mScript;
|
||||
const ESM::RefId& script = target.get<ESM::Creature>()->mBase->mScript;
|
||||
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
||||
if (!script.empty())
|
||||
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||
target.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||
}
|
||||
|
||||
if (!successful)
|
||||
if (!damageData.mIsSuccessful)
|
||||
{
|
||||
// Missed
|
||||
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer() && hitConfig.mPlaySFX)
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(
|
||||
ptr, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f);
|
||||
target, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!object.isEmpty())
|
||||
stats.setLastHitObject(object.getCellRef().getRefId());
|
||||
if (!weapon.isEmpty())
|
||||
stats.setLastHitObject(weapon.getCellRef().getRefId());
|
||||
|
||||
auto damage = damageData.mDamage
|
||||
|
||||
if (damage < 0.001f)
|
||||
damage = 0;
|
||||
@ -424,31 +437,35 @@ namespace MWClass
|
||||
* getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f
|
||||
+ getGmst().iKnockDownOddsBase->mValue.getInteger();
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng))
|
||||
if (!hitConfig.mAvoidKnockdown && damageData.mAffectsHealth && agilityTerm <= damage
|
||||
&& knockdownTerm <= Misc::Rng::roll0to99(prng))
|
||||
stats.setKnockedDown(true);
|
||||
else
|
||||
else if (!hitConfig.mAvoidHitReaction)
|
||||
stats.setHitRecovery(true); // Is this supposed to always occur?
|
||||
}
|
||||
|
||||
if (ishealth)
|
||||
if (damageData.mAffectsHealth)
|
||||
{
|
||||
damage *= damage / (damage + getArmorRating(ptr));
|
||||
damage *= damage / (damage + getArmorRating(target));
|
||||
damage = std::max(1.f, damage);
|
||||
if (!attacker.isEmpty())
|
||||
{
|
||||
damage = scaleDamage(damage, attacker, ptr);
|
||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition);
|
||||
damage = scaleDamage(damage, attacker, target);
|
||||
if (hitConfig.mPlayVFX)
|
||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(target, damageData.mHitPosition);
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(
|
||||
ptr, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f);
|
||||
if (hitConfig.mPlaySFX)
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(
|
||||
target, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f);
|
||||
|
||||
MWMechanics::DynamicStat<float> health(stats.getHealth());
|
||||
health.setCurrent(health.getCurrent() - damage);
|
||||
stats.setHealth(health);
|
||||
}
|
||||
else
|
||||
if (damageData.mAffectsFatigue)
|
||||
{
|
||||
// If health was also damaged - damage was scaled using scaleDamage, is that undesirable here?
|
||||
MWMechanics::DynamicStat<float> fatigue(stats.getFatigue());
|
||||
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
||||
stats.setFatigue(fatigue);
|
||||
|
@ -66,9 +66,9 @@ namespace MWClass
|
||||
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim,
|
||||
const osg::Vec3f& hitPosition, bool success) const override;
|
||||
|
||||
void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
||||
const MWMechanics::DamageSourceType sourceType) const override;
|
||||
void onHit(const MWWorld::Ptr& target, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker,
|
||||
const MWWorld::DamageData& damageData,
|
||||
const MWWorld::HitConfig& hitConfig = MWWorld::HitConfig(), const MWMechanics::DamageSourceType sourceType) const override;
|
||||
|
||||
std::unique_ptr<MWWorld::Action> activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override;
|
||||
///< Generate action for activation
|
||||
|
@ -620,8 +620,16 @@ namespace MWClass
|
||||
float damage = 0.0f;
|
||||
if (!success)
|
||||
{
|
||||
othercls.onHit(
|
||||
victim, damage, false, weapon, ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee);
|
||||
MWWorld::DamageData dmgData = {
|
||||
.mDamage = damage,
|
||||
.mAffectsHealth = false,
|
||||
.mAffectsFatigue = false,
|
||||
.mIsSuccessful = false,
|
||||
.mHitPosition = hitPosition,
|
||||
};
|
||||
|
||||
othercls.onHit(victim, weapon, ptr, dmgData, MWMechanics::DamageSourceType::Melee);
|
||||
|
||||
MWMechanics::reduceWeaponCondition(damage, false, weapon, ptr);
|
||||
MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage);
|
||||
return;
|
||||
@ -694,19 +702,29 @@ namespace MWClass
|
||||
|
||||
MWMechanics::diseaseContact(victim, ptr);
|
||||
|
||||
othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true, MWMechanics::DamageSourceType::Melee);
|
||||
}
|
||||
MWWorld::DamageData dmgData = {
|
||||
.mDamage = damage,
|
||||
.mAffectsHealth = healthdmg,
|
||||
.mAffectsFatigue = !healthdmg,
|
||||
.mIsSuccessful = true,
|
||||
.mHitPosition = hitPosition,
|
||||
};
|
||||
|
||||
void Npc::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
||||
othercls.onHit(victim, weapon, ptr, dmgData, MWMechanics::DamageSourceType::Melee);
|
||||
}
|
||||
// target = ptr , weapon = object
|
||||
void Npc::onHit(const MWWorld::Ptr& target, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker,
|
||||
const MWWorld::DamageData& damageData, const MWWorld::HitConfig& hitConfig,
|
||||
const MWMechanics::DamageSourceType sourceType) const
|
||||
{
|
||||
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(target);
|
||||
bool wasDead = stats.isDead();
|
||||
|
||||
bool setOnPcHitMe = true;
|
||||
|
||||
auto damage = damageData.mDamage;
|
||||
|
||||
// NOTE: 'object' and/or 'attacker' may be empty.
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
|
||||
{
|
||||
@ -725,41 +743,42 @@ namespace MWClass
|
||||
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||
// First handle the attacked actor
|
||||
if ((stats.getHitAttemptActorId() == -1)
|
||||
&& (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer()))
|
||||
&& (statsAttacker.getAiSequence().isInCombat(target) || attacker == MWMechanics::getPlayer()))
|
||||
stats.setHitAttemptActorId(statsAttacker.getActorId());
|
||||
|
||||
// Next handle the attacking actor
|
||||
if ((statsAttacker.getHitAttemptActorId() == -1)
|
||||
&& (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer()))
|
||||
&& (statsAttacker.getAiSequence().isInCombat(target) || attacker == MWMechanics::getPlayer()))
|
||||
statsAttacker.setHitAttemptActorId(stats.getActorId());
|
||||
}
|
||||
|
||||
if (!object.isEmpty())
|
||||
stats.setLastHitAttemptObject(object.getCellRef().getRefId());
|
||||
if (!weapon.isEmpty())
|
||||
stats.setLastHitAttemptObject(weapon.getCellRef().getRefId());
|
||||
|
||||
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||
{
|
||||
const ESM::RefId& script = getScript(ptr);
|
||||
const ESM::RefId& script = getScript(target);
|
||||
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
||||
if (!script.empty())
|
||||
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||
target.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||
}
|
||||
|
||||
if (!successful)
|
||||
if (!damageData.mIsSuccessful)
|
||||
{
|
||||
// Missed
|
||||
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f);
|
||||
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer() && hitConfig.mPlaySFX)
|
||||
sndMgr->playSound3D(target, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f);
|
||||
MWBase::Environment::get().getLuaManager()->actorHit(target, weapon, attacker, damageData, hitConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!object.isEmpty())
|
||||
stats.setLastHitObject(object.getCellRef().getRefId());
|
||||
if (!weapon.isEmpty())
|
||||
stats.setLastHitObject(weapon.getCellRef().getRefId());
|
||||
|
||||
if (damage < 0.001f)
|
||||
damage = 0;
|
||||
|
||||
bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||
bool godmode = target == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||
|
||||
if (godmode)
|
||||
damage = 0;
|
||||
@ -775,7 +794,7 @@ namespace MWClass
|
||||
int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->mValue.getInteger();
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
if (Misc::Rng::roll0to99(prng) < chance)
|
||||
MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("hit"));
|
||||
MWBase::Environment::get().getDialogueManager()->say(target, ESM::RefId::stringRefId("hit"));
|
||||
|
||||
// Check for knockdown
|
||||
float agilityTerm
|
||||
@ -783,12 +802,13 @@ namespace MWClass
|
||||
float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
|
||||
* gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f
|
||||
+ gmst.iKnockDownOddsBase->mValue.getInteger();
|
||||
if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng))
|
||||
if (!hitConfig.mAvoidKnockdown && damageData.mAffectsHealth && agilityTerm <= damage
|
||||
&& knockdownTerm <= Misc::Rng::roll0to99(prng))
|
||||
stats.setKnockedDown(true);
|
||||
else
|
||||
else if (!hitConfig.mAvoidHitReaction)
|
||||
stats.setHitRecovery(true); // Is this supposed to always occur?
|
||||
|
||||
if (damage > 0 && ishealth)
|
||||
if (damage > 0 && damageData.mAffectsHealth)
|
||||
{
|
||||
// Hit percentages:
|
||||
// cuirass = 30%
|
||||
@ -808,13 +828,13 @@ namespace MWClass
|
||||
int hitslot = hitslots[Misc::Rng::rollDice(20, prng)];
|
||||
|
||||
float unmitigatedDamage = damage;
|
||||
float x = damage / (damage + getArmorRating(ptr));
|
||||
float x = damage / (damage + getArmorRating(target));
|
||||
damage *= std::max(gmst.fCombatArmorMinMult->mValue.getFloat(), x);
|
||||
int damageDiff = static_cast<int>(unmitigatedDamage - damage);
|
||||
damage = std::max(1.f, damage);
|
||||
damageDiff = std::max(1, damageDiff);
|
||||
|
||||
MWWorld::InventoryStore& inv = getInventoryStore(ptr);
|
||||
MWWorld::InventoryStore& inv = getInventoryStore(target);
|
||||
MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot);
|
||||
MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr());
|
||||
bool hasArmor = !armor.isEmpty() && armor.getType() == ESM::Armor::sRecordId;
|
||||
@ -836,7 +856,7 @@ namespace MWClass
|
||||
{
|
||||
// Unarmed creature attacks don't affect armor condition unless it was
|
||||
// explicitly requested.
|
||||
if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()
|
||||
if (!weapon.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()
|
||||
|| Settings::game().mUnarmedCreatureAttacksDamageArmor)
|
||||
{
|
||||
int armorhealth = armor.getClass().getItemHealth(armor);
|
||||
@ -849,46 +869,48 @@ namespace MWClass
|
||||
}
|
||||
|
||||
ESM::RefId skill = armor.getClass().getEquipmentSkill(armor);
|
||||
if (ptr == MWMechanics::getPlayer())
|
||||
if (target == MWMechanics::getPlayer())
|
||||
skillUsageSucceeded(ptr, skill, ESM::Skill::Armor_HitByOpponent);
|
||||
|
||||
if (skill == ESM::Skill::LightArmor)
|
||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
|
||||
sndMgr->playSound3D(target, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
|
||||
else if (skill == ESM::Skill::MediumArmor)
|
||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
|
||||
sndMgr->playSound3D(target, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
|
||||
else if (skill == ESM::Skill::HeavyArmor)
|
||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
|
||||
sndMgr->playSound3D(target, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
|
||||
}
|
||||
else if (ptr == MWMechanics::getPlayer())
|
||||
else if (target == MWMechanics::getPlayer())
|
||||
skillUsageSucceeded(ptr, ESM::Skill::Unarmored, ESM::Skill::Armor_HitByOpponent);
|
||||
}
|
||||
}
|
||||
|
||||
if (ishealth)
|
||||
if (damageData.mAffectsHealth)
|
||||
{
|
||||
if (!attacker.isEmpty() && !godmode)
|
||||
damage = scaleDamage(damage, attacker, ptr);
|
||||
damage = scaleDamage(damage, attacker, target);
|
||||
|
||||
if (damage > 0.0f)
|
||||
{
|
||||
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f);
|
||||
if (ptr == MWMechanics::getPlayer())
|
||||
if (hitConfig.mPlaySFX)
|
||||
sndMgr->playSound3D(target, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f);
|
||||
if (target == MWMechanics::getPlayer() && hitConfig.mPlayVFX)
|
||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay();
|
||||
if (!attacker.isEmpty())
|
||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition);
|
||||
if (!attacker.isEmpty() && hitConfig.mPlayVFX)
|
||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(target, damageData.mHitPosition);
|
||||
}
|
||||
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
|
||||
MWMechanics::DynamicStat<float> health(getCreatureStats(target).getHealth());
|
||||
health.setCurrent(health.getCurrent() - damage);
|
||||
stats.setHealth(health);
|
||||
}
|
||||
else
|
||||
if (damageData.mAffectsFatigue)
|
||||
{
|
||||
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
||||
// If health was also damaged - damage was scaled using scaleDamage, is that undesirable here?
|
||||
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(target).getFatigue());
|
||||
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
||||
stats.setFatigue(fatigue);
|
||||
}
|
||||
|
||||
if (!wasDead && getCreatureStats(ptr).isDead())
|
||||
if (!wasDead && getCreatureStats(target).isDead())
|
||||
{
|
||||
// NPC was killed
|
||||
if (!attacker.isEmpty() && attacker.getClass().isNpc()
|
||||
@ -897,8 +919,17 @@ namespace MWClass
|
||||
attacker.getClass().getNpcStats(attacker).addWerewolfKill();
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker);
|
||||
MWBase::Environment::get().getMechanicsManager()->actorKilled(target, attacker);
|
||||
}
|
||||
|
||||
// Trigger Lua engine handler
|
||||
//
|
||||
// TO DO: add this to creatures and others.
|
||||
// TO DO: if a lua script will call actor's hit function from this engine handler - an infinite loop of hits may
|
||||
// occure, probably should allow to pass a flag that will disable an event for the lua triggered hit. But at the
|
||||
// same time mods might want to read hit events triggered by other mods, so maybe some way to uniquely identify
|
||||
// a hit should be introduced?
|
||||
MWBase::Environment::get().getLuaManager()->actorHit(target, weapon, attacker, damageData, hitConfig);
|
||||
}
|
||||
|
||||
std::unique_ptr<MWWorld::Action> Npc::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const
|
||||
|
@ -81,8 +81,9 @@ namespace MWClass
|
||||
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim,
|
||||
const osg::Vec3f& hitPosition, bool success) const override;
|
||||
|
||||
void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
||||
void onHit(const MWWorld::Ptr& target, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker,
|
||||
const MWWorld::DamageData& damageData,
|
||||
const MWWorld::HitConfig& hitConfig = MWWorld::HitConfig(),
|
||||
const MWMechanics::DamageSourceType sourceType) const override;
|
||||
|
||||
void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector<std::string_view>& models) const override;
|
||||
|
@ -74,6 +74,42 @@ namespace MWLua
|
||||
mGlobalScripts.onUseItem(GObject(obj), GObject(actor), event.mForce);
|
||||
}
|
||||
|
||||
void operator()(const OnActorHit& event) const
|
||||
{
|
||||
MWWorld::Ptr target = getPtr(event.mTarget);
|
||||
if (target.isEmpty())
|
||||
return;
|
||||
|
||||
MWLua::LObject weaponObj;
|
||||
if (event.mWeapon.has_value())
|
||||
{
|
||||
weaponObj = LObject(getPtr(event.mWeapon.value()));
|
||||
}
|
||||
|
||||
MWWorld::Ptr attacker;
|
||||
MWLua::LObject attackerObj;
|
||||
if (event.mAttacker.has_value())
|
||||
{
|
||||
attacker = getPtr(event.mAttacker.value());
|
||||
attackerObj = LObject(attacker);
|
||||
}
|
||||
|
||||
/*sol::optional<const MWLua::LObject> attackerObj = [&] {
|
||||
|
||||
}();*/
|
||||
if (event.mWeapon.has_value() && event.mAttacker.has_value())
|
||||
{
|
||||
// sol::optional doesnt work properly with const LObject now, so until then only run this when all of
|
||||
// the objects are present
|
||||
if (auto* scripts = getLocalScripts(target))
|
||||
scripts->onActorHit(LObject(target), weaponObj, attackerObj, event.mDamageData, event.mHitConfig);
|
||||
if (!attacker.isEmpty())
|
||||
if (auto* scripts = getLocalScripts(attacker))
|
||||
scripts->onActorHit(
|
||||
LObject(target), weaponObj, attackerObj, event.mDamageData, event.mHitConfig);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const OnConsume& event) const
|
||||
{
|
||||
MWWorld::Ptr actor = getPtr(event.mActor);
|
||||
|
@ -1,11 +1,13 @@
|
||||
#ifndef MWLUA_ENGINEEVENTS_H
|
||||
#define MWLUA_ENGINEEVENTS_H
|
||||
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#include <components/esm3/cellref.hpp> // defines RefNum that is used as a unique id
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
@ -42,6 +44,14 @@ namespace MWLua
|
||||
ESM::RefNum mObject;
|
||||
bool mForce;
|
||||
};
|
||||
struct OnActorHit
|
||||
{
|
||||
ESM::RefNum mTarget;
|
||||
std::optional<ESM::RefNum> mWeapon;
|
||||
std::optional<ESM::RefNum> mAttacker;
|
||||
MWWorld::DamageData mDamageData;
|
||||
MWWorld::HitConfig mHitConfig;
|
||||
};
|
||||
struct OnConsume
|
||||
{
|
||||
ESM::RefNum mActor;
|
||||
@ -70,7 +80,7 @@ namespace MWLua
|
||||
std::string mSkill;
|
||||
std::string mSource;
|
||||
};
|
||||
using Event = std::variant<OnActive, OnInactive, OnConsume, OnActivate, OnUseItem, OnNewExterior, OnTeleported,
|
||||
using Event = std::variant<OnActive, OnInactive, OnConsume, OnActivate, OnUseItem, OnActorHit, OnNewExterior, OnTeleported,
|
||||
OnAnimationTextKey, OnSkillUse, OnSkillLevelUp>;
|
||||
|
||||
void clear() { mQueue.clear(); }
|
||||
|
@ -232,7 +232,7 @@ namespace MWLua
|
||||
[&](LuaUtil::LuaView& view) { addPackage("openmw.self", sol::make_object(view.sol(), &mData)); });
|
||||
registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers,
|
||||
&mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse,
|
||||
&mOnSkillLevelUp });
|
||||
&mOnSkillLevelUp, &mOnActorHit });
|
||||
}
|
||||
|
||||
void LocalScripts::setActive(bool active)
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include <components/lua/luastate.hpp>
|
||||
|
||||
#include <components/lua/scriptscontainer.hpp>
|
||||
|
||||
#include "../mwbase/luamanager.hpp"
|
||||
@ -71,6 +72,11 @@ namespace MWLua
|
||||
bool isActive() const override { return mData.mIsActive; }
|
||||
void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); }
|
||||
void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); }
|
||||
void onActorHit(const LObject& target, sol::optional<const MWLua::LObject> weapon,
|
||||
sol::optional<const MWLua::LObject> attacker, MWWorld::DamageData damageData, MWWorld::HitConfig hitConfig)
|
||||
{
|
||||
callEngineHandlers(mOnActorHit, target, weapon, attacker, damageData, hitConfig);
|
||||
}
|
||||
void onTeleported() { callEngineHandlers(mOnTeleportedHandlers); }
|
||||
void onAnimationTextKey(std::string_view groupname, std::string_view key)
|
||||
{
|
||||
@ -98,6 +104,7 @@ namespace MWLua
|
||||
EngineHandlerList mOnActiveHandlers{ "onActive" };
|
||||
EngineHandlerList mOnInactiveHandlers{ "onInactive" };
|
||||
EngineHandlerList mOnConsumeHandlers{ "onConsume" };
|
||||
EngineHandlerList mOnActorHit{ "onActorHit" };
|
||||
EngineHandlerList mOnActivatedHandlers{ "onActivated" };
|
||||
EngineHandlerList mOnTeleportedHandlers{ "onTeleported" };
|
||||
EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" };
|
||||
|
@ -514,6 +514,15 @@ namespace MWLua
|
||||
}
|
||||
}
|
||||
|
||||
void LuaManager::actorHit(const MWWorld::Ptr& target, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker,
|
||||
const MWWorld::DamageData& damageData, const MWWorld::HitConfig& hitConfig)
|
||||
{
|
||||
auto weaponId = !weapon.isEmpty() ? std::optional<ESM::RefNum>(getId(weapon)) : std::nullopt;
|
||||
auto attackerId = !weapon.isEmpty() ? std::optional<ESM::RefNum>(getId(attacker)) : std::nullopt;
|
||||
mEngineEvents.addToQueue(
|
||||
EngineEvents::OnActorHit{ getId(target), weaponId, attackerId, damageData, hitConfig });
|
||||
}
|
||||
|
||||
void LuaManager::inputEvent(const InputEvent& event)
|
||||
{
|
||||
if (!MyGUI::InputManager::getInstance().isModalAny()
|
||||
|
@ -75,6 +75,8 @@ namespace MWLua
|
||||
void noGame() override;
|
||||
void objectAddedToScene(const MWWorld::Ptr& ptr) override;
|
||||
void objectRemovedFromScene(const MWWorld::Ptr& ptr) override;
|
||||
void actorHit(const MWWorld::Ptr& target, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker,
|
||||
const MWWorld::DamageData& damageData, const MWWorld::HitConfig& hitConfig) override;
|
||||
void inputEvent(const InputEvent& event) override;
|
||||
void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) override
|
||||
{
|
||||
|
@ -170,6 +170,34 @@ namespace MWLua
|
||||
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(*it);
|
||||
}
|
||||
|
||||
void addActorHitBindings(const Context& context)
|
||||
{
|
||||
// Damage data usertype containing damage properties
|
||||
sol::usertype<MWWorld::DamageData> dmgDataT
|
||||
= context.mLua->sol().new_usertype<MWWorld::DamageData>("DamageData");
|
||||
|
||||
dmgDataT["damage"] = sol::readonly_property([](const MWWorld::DamageData& d) { return d.mDamage; });
|
||||
dmgDataT["affectsHealth"]
|
||||
= sol::readonly_property([](const MWWorld::DamageData& d) { return d.mAffectsHealth; });
|
||||
dmgDataT["affectsFatigue"]
|
||||
= sol::readonly_property([](const MWWorld::DamageData& d) { return d.mAffectsFatigue; });
|
||||
dmgDataT["isSuccessful"] = sol::readonly_property([](const MWWorld::DamageData& d) { return d.mIsSuccessful; });
|
||||
dmgDataT["hitPosition"] = sol::readonly_property([](const MWWorld::DamageData& d) { return d.mHitPosition; });
|
||||
dmgDataT["forceMagical"] = sol::readonly_property([](const MWWorld::DamageData& d) { return d.mForceMagical; });
|
||||
|
||||
// Damage data usertype containing damage properties
|
||||
sol::usertype<MWWorld::HitConfig> hitConfigT
|
||||
= context.mLua->sol().new_usertype<MWWorld::HitConfig>("HitConfig");
|
||||
|
||||
hitConfigT["avoidHitReaction"]
|
||||
= sol::readonly_property([](const MWWorld::HitConfig& h) { return h.mAvoidHitReaction; });
|
||||
hitConfigT["avoidKnockdown"]
|
||||
= sol::readonly_property([](const MWWorld::HitConfig& h) { return h.mAvoidKnockdown; });
|
||||
hitConfigT["playVFX"] = sol::readonly_property([](const MWWorld::HitConfig& h) { return h.mPlayVFX; });
|
||||
hitConfigT["playSFX"] = sol::readonly_property([](const MWWorld::HitConfig& h) { return h.mPlaySFX; });
|
||||
hitConfigT["id"] = sol::readonly_property([](const MWWorld::HitConfig& h) { return h.mId; });
|
||||
}
|
||||
|
||||
void addActorBindings(sol::table actor, const Context& context)
|
||||
{
|
||||
sol::state_view lua = context.sol();
|
||||
@ -297,6 +325,45 @@ namespace MWLua
|
||||
const MWWorld::Class& cls = o.ptr().getClass();
|
||||
return cls.getCurrentSpeed(o.ptr());
|
||||
};
|
||||
actor["hit"]
|
||||
= [](const Object& target, const sol::optional<Object> weapon, const sol::optional<Object> attacker,
|
||||
const sol::object& damageDataObj, const sol::object& hitConfigObj) {
|
||||
const MWWorld::Class& targetCls = target.ptr().getClass();
|
||||
|
||||
auto targetPtr = target.ptr();
|
||||
|
||||
MWWorld::Ptr weaponPtr;
|
||||
MWWorld::Ptr attackerPtr;
|
||||
if (weapon.has_value())
|
||||
weaponPtr = weapon.value().ptr();
|
||||
if (attacker.has_value())
|
||||
attackerPtr = attacker.value().ptr();
|
||||
|
||||
sol::table d = LuaUtil::cast<sol::table>(damageDataObj);
|
||||
sol::table h = LuaUtil::cast<sol::table>(hitConfigObj);
|
||||
|
||||
MWWorld::DamageData dmgData;
|
||||
if (d["damage"] != sol::nil)
|
||||
dmgData.mDamage = ((sol::object)d["damage"]).as<float>();
|
||||
if (d["affectsHealth"] != sol::nil)
|
||||
dmgData.mAffectsHealth = ((sol::object)d["affectsHealth"]).as<bool>();
|
||||
if (d["affectsFatigue"] != sol::nil)
|
||||
dmgData.mAffectsFatigue = ((sol::object)d["affectsFatigue"]).as<bool>();
|
||||
if (d["isSuccessful"] != sol::nil)
|
||||
dmgData.mIsSuccessful = ((sol::object)d["isSuccessful"]).as<bool>();
|
||||
dmgData.mHitPosition = d.get_or("hitPosition", dmgData.mHitPosition);
|
||||
if (d["forceMagical"] != sol::nil)
|
||||
dmgData.mForceMagical = ((sol::object)d["forceMagical"]).as<bool>();
|
||||
|
||||
MWWorld::HitConfig hitConfig;
|
||||
hitConfig.mAvoidHitReaction = h.get_or("avoidHitReaction", hitConfig.mAvoidHitReaction);
|
||||
hitConfig.mAvoidKnockdown = h.get_or("avoidKnockdown", hitConfig.mAvoidKnockdown);
|
||||
hitConfig.mPlayVFX = h.get_or("playVFX", hitConfig.mPlayVFX);
|
||||
hitConfig.mPlaySFX = h.get_or("playSFX", hitConfig.mPlaySFX);
|
||||
hitConfig.mId = h.get_or("id", hitConfig.mId);
|
||||
|
||||
targetCls.onHit(targetPtr, weaponPtr, attackerPtr, dmgData, hitConfig);
|
||||
};
|
||||
|
||||
// for compatibility; should be removed later
|
||||
actor["runSpeed"] = actor["getRunSpeed"];
|
||||
@ -422,6 +489,6 @@ namespace MWLua
|
||||
|
||||
addActorStatsBindings(actor, context);
|
||||
addActorMagicBindings(actor, context);
|
||||
addActorHitBindings(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -80,5 +80,7 @@ namespace MWLua
|
||||
return LuaUtil::makeReadOnly(travelDests);
|
||||
});
|
||||
}
|
||||
|
||||
void addActorHitBindings(const Context& context);
|
||||
}
|
||||
#endif // MWLUA_ACTOR_H
|
||||
|
@ -240,8 +240,14 @@ namespace MWMechanics
|
||||
|
||||
if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue))
|
||||
{
|
||||
victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false,
|
||||
MWMechanics::DamageSourceType::Ranged);
|
||||
MWWorld::DamageData dmgData = {
|
||||
.mDamage = damage,
|
||||
.mAffectsHealth = true,
|
||||
.mAffectsFatigue = false,
|
||||
.mIsSuccessful = false,
|
||||
.mHitPosition = osg::Vec3f(),
|
||||
};
|
||||
victim.getClass().onHit(victim, projectile, attacker, dmgData, MWMechanics::DamageSourceType::Ranged);
|
||||
MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker);
|
||||
return;
|
||||
}
|
||||
@ -299,8 +305,14 @@ namespace MWMechanics
|
||||
victim.getClass().getContainerStore(victim).add(projectile, 1);
|
||||
}
|
||||
|
||||
victim.getClass().onHit(
|
||||
victim, damage, true, projectile, attacker, hitPosition, true, MWMechanics::DamageSourceType::Ranged);
|
||||
MWWorld::DamageData dmgData = {
|
||||
.mDamage = damage,
|
||||
.mAffectsHealth = true,
|
||||
.mAffectsFatigue = false,
|
||||
.mIsSuccessful = true,
|
||||
.mHitPosition = hitPosition,
|
||||
};
|
||||
victim.getClass().onHit(victim, projectile, attacker, dmgData, MWMechanics::DamageSourceType::Ranged);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,8 +357,17 @@ namespace
|
||||
// Notify the target actor they've been hit
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
||||
target.getClass().onHit(
|
||||
target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true, MWMechanics::DamageSourceType::Magical);
|
||||
{
|
||||
MWWorld::DamageData dmgData = {
|
||||
.mDamage = 0.0f,
|
||||
.mAffectsHealth = true,
|
||||
.mAffectsFatigue = false,
|
||||
.mIsSuccessful = true,
|
||||
.mHitPosition = osg::Vec3f(),
|
||||
};
|
||||
target.getClass().onHit(target, MWWorld::Ptr(), caster, dmgData, MWMechanics::DamageSourceType::Magical);
|
||||
}
|
||||
|
||||
// Apply resistances
|
||||
if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances))
|
||||
{
|
||||
|
@ -118,9 +118,9 @@ namespace MWWorld
|
||||
{
|
||||
throw std::runtime_error("class cannot hit");
|
||||
}
|
||||
|
||||
void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker,
|
||||
const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const
|
||||
|
||||
void Class::onHit(const Ptr& target, const Ptr& weapon, const Ptr& attacker, const DamageData& damageData,
|
||||
const HitConfig& hitConfig, const MWMechanics::DamageSourceType sourceType) const
|
||||
{
|
||||
throw std::runtime_error("class cannot be hit");
|
||||
}
|
||||
|
@ -58,6 +58,25 @@ namespace MWWorld
|
||||
class CellStore;
|
||||
class Action;
|
||||
|
||||
struct DamageData
|
||||
{
|
||||
float mDamage;
|
||||
bool mAffectsHealth = true;
|
||||
bool mAffectsFatigue = false;
|
||||
bool mIsSuccessful = true;
|
||||
osg::Vec3f mHitPosition = osg::Vec3f(0, 0, 0);
|
||||
bool mForceMagical = false;
|
||||
};
|
||||
|
||||
struct HitConfig
|
||||
{
|
||||
bool mAvoidHitReaction = false;
|
||||
bool mAvoidKnockdown = false;
|
||||
bool mPlayVFX = true;
|
||||
bool mPlaySFX = true;
|
||||
std::string mId;
|
||||
};
|
||||
|
||||
/// \brief Base class for referenceable esm records
|
||||
class Class
|
||||
{
|
||||
@ -144,13 +163,13 @@ namespace MWWorld
|
||||
/// enums. ignored for creature attacks.
|
||||
/// (default implementation: throw an exception)
|
||||
|
||||
virtual void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object,
|
||||
const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful,
|
||||
const MWMechanics::DamageSourceType sourceType) const;
|
||||
virtual void onHit(const Ptr& target, const Ptr& weapon, const Ptr& attacker, const DamageData& damageData,
|
||||
const HitConfig& hitConfig = HitConfig(),const MWMechanics::DamageSourceType sourceType) const;
|
||||
/// To do: Update the description
|
||||
///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is
|
||||
/// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the
|
||||
/// actor responsible for the attack. \a successful specifies if the hit is
|
||||
/// successful or not. \a sourceType classifies the damage source.
|
||||
/// actor responsible for the attack, and \a successful specifies if the hit is
|
||||
/// successful or not.
|
||||
|
||||
virtual std::unique_ptr<Action> activate(const Ptr& ptr, const Ptr& actor) const;
|
||||
///< Generate action for activation (default implementation: return a null action).
|
||||
|
Loading…
x
Reference in New Issue
Block a user