Merge pull request #1170 from NeveHanter/bugfix-3617

Enchanted arrows don't explode when hitting the ground (Bug #3617)
pull/128/head
scrawl 8 years ago committed by GitHub
commit 8f5418c12c

@ -534,8 +534,9 @@ namespace MWBase
virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0; virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0;
virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster,
const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) = 0; const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id,
const std::string& sourceName, const bool fromProjectile=false) = 0;
virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;

@ -33,7 +33,7 @@ float signedAngleRadians (const osg::Vec3f& v1, const osg::Vec3f& v2, const osg:
namespace MWMechanics namespace MWMechanics
{ {
bool applyOnStrikeEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition) bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition, const bool fromProjectile)
{ {
std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : "";
if (!enchantmentName.empty()) if (!enchantmentName.empty())
@ -42,7 +42,7 @@ namespace MWMechanics
enchantmentName); enchantmentName);
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
{ {
MWMechanics::CastSpell cast(attacker, victim); MWMechanics::CastSpell cast(attacker, victim, fromProjectile);
cast.mHitPosition = hitPosition; cast.mHitPosition = hitPosition;
cast.cast(object, false); cast.cast(object, false);
return true; return true;
@ -165,69 +165,69 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}");
} }
void projectileHit(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, MWWorld::Ptr weapon, const MWWorld::Ptr &projectile, void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile,
const osg::Vec3f& hitPosition, float attackStrength) const osg::Vec3f& hitPosition, float attackStrength)
{ {
MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
if(victim.isEmpty() || !victim.getClass().isActor() || victim.getClass().getCreatureStats(victim).isDead()) bool validVictim = !victim.isEmpty() && victim.getClass().isActor();
// Can't hit non-actors or dead actors
{
reduceWeaponCondition(0.f, false, weapon, attacker);
return;
}
if(attacker == getPlayer()) float damage = 0.f;
MWBase::Environment::get().getWindowManager()->setEnemy(victim); if (validVictim)
{
if (attacker == getPlayer())
MWBase::Environment::get().getWindowManager()->setEnemy(victim);
int weapskill = ESM::Skill::Marksman; int weaponSkill = ESM::Skill::Marksman;
if(!weapon.isEmpty()) if (!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon); weaponSkill = weapon.getClass().getEquipmentSkill(weapon);
int skillValue = attacker.getClass().getSkill(attacker, int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon));
weapon.getClass().getEquipmentSkill(weapon));
if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue))
{ {
victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, osg::Vec3f(), false); victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false);
MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker);
return; return;
} }
const unsigned char* attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage
const unsigned char* attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop; // Arrow/bolt damage
float damage = attack[0] + ((attack[1]-attack[0])*attackStrength); // Bow/crossbow 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);
// Arrow/bolt damage adjustWeaponDamage(damage, weapon, attacker);
// 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);
adjustWeaponDamage(damage, weapon, attacker); if(attacker == getPlayer())
reduceWeaponCondition(damage, true, weapon, attacker); attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
if(attacker == getPlayer()) if (victim.getClass().getCreatureStats(victim).getKnockedDown())
attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); damage *= gmst.find("fCombatKODamageMult")->getFloat();
}
if (victim.getClass().getCreatureStats(victim).getKnockedDown()) reduceWeaponCondition(damage, validVictim, weapon, attacker);
damage *= gmst.find("fCombatKODamageMult")->getFloat();
// Apply "On hit" effect of the weapon // Apply "On hit" effect of the weapon & projectile
bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, weapon, hitPosition); bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, weapon, hitPosition, true);
if (weapon != projectile) if (weapon != projectile)
appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition); appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition, true);
// Non-enchanted arrows shot at enemies have a chance to turn up in their inventory if (validVictim)
if (victim != getPlayer()
&& !appliedEnchantment)
{ {
float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory
if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) if (victim != getPlayer() && !appliedEnchantment)
victim.getClass().getContainerStore(victim).add(projectile, 1, victim); {
} float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat();
if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f)
victim.getClass().getContainerStore(victim).add(projectile, 1, victim);
}
victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true); victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true);
}
} }
float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue) float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue)

@ -6,7 +6,8 @@
namespace MWMechanics namespace MWMechanics
{ {
bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition); bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition,
const bool fromProjectile=false);
/// @return can we block the attack? /// @return can we block the attack?
bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength); bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength);

@ -274,12 +274,13 @@ namespace MWMechanics
return true; return true;
} }
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target) CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile)
: mCaster(caster) : mCaster(caster)
, mTarget(target) , mTarget(target)
, mStack(false) , mStack(false)
, mHitPosition(0,0,0) , mHitPosition(0,0,0)
, mAlwaysSucceed(false) , mAlwaysSucceed(false)
, mFromProjectile(fromProjectile)
{ {
} }
@ -301,7 +302,7 @@ namespace MWMechanics
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
{ {
if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead())
return; return;
// If none of the effects need to apply, we can early-out // If none of the effects need to apply, we can early-out
@ -319,7 +320,7 @@ namespace MWMechanics
return; return;
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
if (spell && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) if (spell && !target.isEmpty() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
{ {
int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ? int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ?
ESM::MagicEffect::ResistCommonDisease ESM::MagicEffect::ResistCommonDisease
@ -342,13 +343,13 @@ namespace MWMechanics
// This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell.
// Otherwise, they'd only apply after the whole spell was added. // Otherwise, they'd only apply after the whole spell was added.
MagicEffects targetEffects; MagicEffects targetEffects;
if (target.getClass().isActor()) if (!target.isEmpty() && target.getClass().isActor())
targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); targetEffects += target.getClass().getCreatureStats(target).getMagicEffects();
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
ActiveSpells targetSpells; ActiveSpells targetSpells;
if (target.getClass().isActor()) if (!target.isEmpty() && target.getClass().isActor())
targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); targetSpells = target.getClass().getCreatureStats(target).getActiveSpells();
bool canCastAnEffect = false; // For bound equipment.If this remains false bool canCastAnEffect = false; // For bound equipment.If this remains false
@ -356,7 +357,7 @@ namespace MWMechanics
// effects, we display a "can't re-cast" message // effects, we display a "can't re-cast" message
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin()); for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
effectIt!=effects.mList.end(); ++effectIt) !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt)
{ {
if (effectIt->mRange != range) if (effectIt->mRange != range)
continue; continue;
@ -565,18 +566,20 @@ namespace MWMechanics
} }
if (!exploded) if (!exploded)
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName); MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile);
if (!reflectedEffects.mList.empty()) if (!target.isEmpty()) {
inflict(caster, target, reflectedEffects, range, true, exploded); if (!reflectedEffects.mList.empty())
inflict(caster, target, reflectedEffects, range, true, exploded);
if (!appliedLastingEffects.empty()) if (!appliedLastingEffects.empty())
{ {
int casterActorId = -1; int casterActorId = -1;
if (!caster.isEmpty() && caster.getClass().isActor()) if (!caster.isEmpty() && caster.getClass().isActor())
casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); casterActorId = caster.getClass().getCreatureStats(caster).getActorId();
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
mSourceName, casterActorId); mSourceName, casterActorId);
}
} }
} }
@ -757,14 +760,19 @@ namespace MWMechanics
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
if (!mTarget.isEmpty()) bool isProjectile = false;
if (item.getTypeName() == typeid(ESM::Weapon).name())
{ {
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); const MWWorld::LiveCellRef<ESM::Weapon> *ref = item.get<ESM::Weapon>();
isProjectile = ref->mBase->mData.mType == ESM::Weapon::Arrow || ref->mBase->mData.mType == ESM::Weapon::Bolt || ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown;
} }
if (isProjectile || !mTarget.isEmpty())
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
if (launchProjectile) if (launchProjectile)
launchMagicBolt(enchantment->mEffects); launchMagicBolt(enchantment->mEffects);
else if (!mTarget.isEmpty()) else if (isProjectile || !mTarget.isEmpty())
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target);
return true; return true;

@ -78,9 +78,10 @@ namespace MWMechanics
std::string mSourceName; // Display name for spell, potion, etc std::string mSourceName; // Display name for spell, potion, etc
osg::Vec3f mHitPosition; // Used for spawning area orb osg::Vec3f mHitPosition; // Used for spawning area orb
bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false)
bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon)
public: public:
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false);
bool cast (const ESM::Spell* spell); bool cast (const ESM::Spell* spell);

@ -411,26 +411,23 @@ namespace MWWorld
bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos);
if (result.mHit || underwater) if (result.mHit || underwater)
{ {
if (result.mHit) MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow);
// Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && it->mIdArrow != it->mBowId)
{ {
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
// Try to get a Ptr to the bow that was used. It might no longer exist. if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId))
MWWorld::Ptr bow = projectileRef.getPtr(); bow = *invIt;
if (!caster.isEmpty() && it->mIdArrow != it->mBowId)
{
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId))
bow = *invIt;
}
if (caster.isEmpty())
caster = result.mHitObject;
MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHitPos, it->mAttackStrength);
} }
if (caster.isEmpty())
caster = result.mHitObject;
MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, it->mAttackStrength);
if (underwater) if (underwater)
mRendering->emitWaterRipple(newPos); mRendering->emitWaterRipple(newPos);

@ -3199,8 +3199,8 @@ namespace MWWorld
mRendering->spawnEffect(model, textureOverride, worldPos); mRendering->spawnEffect(model, textureOverride, worldPos);
} }
void World::explodeSpell(const osg::Vec3f &origin, const ESM::EffectList &effects, const Ptr &caster, const Ptr& ignore, void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType,
ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) const std::string& id, const std::string& sourceName, const bool fromProjectile)
{ {
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply; std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.mList.begin(); for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.mList.begin();
@ -3211,8 +3211,8 @@ namespace MWWorld
if (effectIt->mRange != rangeType || (effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) if (effectIt->mRange != rangeType || (effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
continue; // Not right range type, or not area effect and hit an actor continue; // Not right range type, or not area effect and hit an actor
if (effectIt->mRange == ESM::RT_Touch && (!ignore.isEmpty()) && (!ignore.getClass().isActor() && !ignore.getClass().canBeActivated(ignore))) if (!fromProjectile && effectIt->mRange == ESM::RT_Touch && (!ignore.isEmpty()) && (!ignore.getClass().isActor() && !ignore.getClass().canBeActivated(ignore)))
continue; // Don't play explosion for touch spells on non-activatable objects continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from the projectile enchantment
// Spawn the explosion orb effect // Spawn the explosion orb effect
const ESM::Static* areaStatic; const ESM::Static* areaStatic;

@ -637,8 +637,9 @@ namespace MWWorld
virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos); virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos);
virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore,
const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName); ESM::RangeType rangeType, const std::string& id, const std::string& sourceName,
const bool fromProjectile=false);
virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor);

Loading…
Cancel
Save