From 811e9ad9f37262aca859833234f9033f24aac7e2 Mon Sep 17 00:00:00 2001 From: NeveHanter Date: Mon, 19 Dec 2016 10:15:19 +0100 Subject: [PATCH 1/2] Fixed bug https://bugs.openmw.org/issues/3617 by allowing touch and target enchantments from ranged weapons and their projectiles to explode even when colliding with non-activable objects, terrain, water slab or when shoot underwater. Also allowed projectiles to fly through the dead bodies as in vanilla. --- apps/openmw/mwbase/world.hpp | 5 +- apps/openmw/mwmechanics/combat.cpp | 90 +++++++++++------------ apps/openmw/mwmechanics/combat.hpp | 3 +- apps/openmw/mwmechanics/spellcasting.cpp | 46 +++++++----- apps/openmw/mwmechanics/spellcasting.hpp | 3 +- apps/openmw/mwworld/projectilemanager.cpp | 31 ++++---- apps/openmw/mwworld/worldimp.cpp | 8 +- apps/openmw/mwworld/worldimp.hpp | 5 +- 8 files changed, 100 insertions(+), 91 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c9c4a22a7..705a57367 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -534,8 +534,9 @@ namespace MWBase 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, - const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) = 0; + 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, const bool fromProjectile=false) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 41d8b9595..29d1f9c7c 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -33,7 +33,7 @@ float signedAngleRadians (const osg::Vec3f& v1, const osg::Vec3f& v2, const osg: 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) : ""; if (!enchantmentName.empty()) @@ -42,7 +42,7 @@ namespace MWMechanics enchantmentName); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { - MWMechanics::CastSpell cast(attacker, victim); + MWMechanics::CastSpell cast(attacker, victim, fromProjectile); cast.mHitPosition = hitPosition; cast.cast(object, false); return true; @@ -165,69 +165,69 @@ namespace MWMechanics 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) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &gmst = world->getStore().get(); - if(victim.isEmpty() || !victim.getClass().isActor() || victim.getClass().getCreatureStats(victim).isDead()) - // Can't hit non-actors or dead actors - { - reduceWeaponCondition(0.f, false, weapon, attacker); - return; - } + bool validVictim = !victim.isEmpty() && victim.getClass().isActor(); - if(attacker == getPlayer()) - MWBase::Environment::get().getWindowManager()->setEnemy(victim); + float damage = 0.f; + if (validVictim) + { + if (attacker == getPlayer()) + MWBase::Environment::get().getWindowManager()->setEnemy(victim); - int weapskill = ESM::Skill::Marksman; - if(!weapon.isEmpty()) - weapskill = weapon.getClass().getEquipmentSkill(weapon); + int weaponSkill = ESM::Skill::Marksman; + if (!weapon.isEmpty()) + weaponSkill = weapon.getClass().getEquipmentSkill(weapon); - int skillValue = attacker.getClass().getSkill(attacker, - weapon.getClass().getEquipmentSkill(weapon)); + int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); - if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) - { - victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, osg::Vec3f(), false); - MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); - return; - } + if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) + { + victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false); + MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker); + return; + } + const unsigned char* attack = weapon.get()->mBase->mData.mChop; + damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage - const unsigned char* attack = weapon.get()->mBase->mData.mChop; - float damage = attack[0] + ((attack[1]-attack[0])*attackStrength); // Bow/crossbow damage + // Arrow/bolt damage + // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon + attack = projectile.get()->mBase->mData.mChop; + damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); - // Arrow/bolt damage - // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon - attack = projectile.get()->mBase->mData.mChop; - damage += attack[0] + ((attack[1]-attack[0])*attackStrength); + adjustWeaponDamage(damage, weapon, attacker); - adjustWeaponDamage(damage, weapon, attacker); - reduceWeaponCondition(damage, true, weapon, attacker); + if(attacker == getPlayer()) + attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0); - if(attacker == getPlayer()) - attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); + if (victim.getClass().getCreatureStats(victim).getKnockedDown()) + damage *= gmst.find("fCombatKODamageMult")->getFloat(); + } - if (victim.getClass().getCreatureStats(victim).getKnockedDown()) - damage *= gmst.find("fCombatKODamageMult")->getFloat(); + reduceWeaponCondition(damage, validVictim, weapon, attacker); - // Apply "On hit" effect of the weapon - bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, weapon, hitPosition); + // Apply "On hit" effect of the weapon & projectile + bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, weapon, hitPosition, true); 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 (victim != getPlayer() - && !appliedEnchantment) + if (validVictim) { - float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); - if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) - victim.getClass().getContainerStore(victim).add(projectile, 1, victim); - } + // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory + if (victim != getPlayer() && !appliedEnchantment) + { + 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) diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 3f733763a..12961dc4b 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -6,7 +6,8 @@ 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? bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 43d77b99d..9c7da9c2e 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -273,12 +273,13 @@ namespace MWMechanics 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) , mTarget(target) , mStack(false) , mHitPosition(0,0,0) , mAlwaysSucceed(false) + , mFromProjectile(fromProjectile) { } @@ -300,7 +301,7 @@ namespace MWMechanics void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, 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; // If none of the effects need to apply, we can early-out @@ -318,7 +319,7 @@ namespace MWMechanics return; const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().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) ? ESM::MagicEffect::ResistCommonDisease @@ -341,13 +342,13 @@ namespace MWMechanics // 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. MagicEffects targetEffects; - if (target.getClass().isActor()) + if (!target.isEmpty() && target.getClass().isActor()) targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); ActiveSpells targetSpells; - if (target.getClass().isActor()) + if (!target.isEmpty() && target.getClass().isActor()) targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); bool canCastAnEffect = false; // For bound equipment.If this remains false @@ -355,7 +356,7 @@ namespace MWMechanics // effects, we display a "can't re-cast" message for (std::vector::const_iterator effectIt (effects.mList.begin()); - effectIt!=effects.mList.end(); ++effectIt) + !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt) { if (effectIt->mRange != range) continue; @@ -555,18 +556,20 @@ namespace MWMechanics } 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()) - inflict(caster, target, reflectedEffects, range, true, exploded); + if (!target.isEmpty()) { + if (!reflectedEffects.mList.empty()) + inflict(caster, target, reflectedEffects, range, true, exploded); - if (!appliedLastingEffects.empty()) - { - int casterActorId = -1; - if (!caster.isEmpty() && caster.getClass().isActor()) - casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, - mSourceName, casterActorId); + if (!appliedLastingEffects.empty()) + { + int casterActorId = -1; + if (!caster.isEmpty() && caster.getClass().isActor()) + casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, + mSourceName, casterActorId); + } } } @@ -742,14 +745,19 @@ namespace MWMechanics 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 *ref = item.get(); + 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) launchMagicBolt(enchantment->mEffects); - else if (!mTarget.isEmpty()) + else if (isProjectile || !mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); return true; diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 6e25acf50..91bb6821a 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -78,9 +78,10 @@ namespace MWMechanics std::string mSourceName; // Display name for spell, potion, etc osg::Vec3f mHitPosition; // Used for spawning area orb 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: - 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); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 4d342336d..3d9652664 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -409,26 +409,23 @@ namespace MWWorld bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); 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); - - // 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::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); + 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.mHit ? result.mHitPos : newPos, it->mAttackStrength); + if (underwater) mRendering->emitWaterRipple(newPos); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ccd2a25e0..c0dd40a97 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3199,8 +3199,8 @@ namespace MWWorld mRendering->spawnEffect(model, textureOverride, worldPos); } - void World::explodeSpell(const osg::Vec3f &origin, const ESM::EffectList &effects, const Ptr &caster, const Ptr& ignore, - ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) + void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType, + const std::string& id, const std::string& sourceName, const bool fromProjectile) { std::map > toApply; for (std::vector::const_iterator effectIt = effects.mList.begin(); @@ -3211,8 +3211,8 @@ namespace MWWorld 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 - if (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 + 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 except when spell is from the projectile enchantment // Spawn the explosion orb effect const ESM::Static* areaStatic; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 0d91bc641..4bf27162b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -637,8 +637,9 @@ namespace MWWorld 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, - const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName); + 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, + const bool fromProjectile=false); virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); From 5a8efa16493cdc94bf42a5dcfc155cbe81f9edb7 Mon Sep 17 00:00:00 2001 From: Martin-Kevin Neumann Date: Sun, 15 Jan 2017 14:17:22 +0100 Subject: [PATCH 2/2] added refactoring of shader handling from visvalda's fog shader --- apps/openmw/mwrender/water.cpp | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 18ef8b560..b99c128a3 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -30,6 +30,8 @@ #include #include +#include + #include #include @@ -189,29 +191,6 @@ public: } }; -osg::ref_ptr readShader (osg::Shader::Type type, const std::string& file, const std::map& defineMap = std::map()) -{ - osg::ref_ptr shader (new osg::Shader(type)); - - // use boost in favor of osg::Shader::readShaderFile, to handle utf-8 path issues on Windows - boost::filesystem::ifstream inStream; - inStream.open(boost::filesystem::path(file)); - std::stringstream strstream; - strstream << inStream.rdbuf(); - - std::string shaderSource = strstream.str(); - - for (std::map::const_iterator it = defineMap.begin(); it != defineMap.end(); ++it) - { - size_t pos = shaderSource.find(it->first); - if (pos != std::string::npos) - shaderSource.replace(pos, it->first.length(), it->second); - } - - shader->setShaderSource(shaderSource); - return shader; -} - osg::ref_ptr readPngImage (const std::string& file) { // use boost in favor of osgDB::readImage, to handle utf-8 path issues on Windows @@ -524,10 +503,11 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R { // use a define map to conditionally compile the shader std::map defineMap; - defineMap.insert(std::make_pair(std::string("@refraction_enabled"), std::string(refraction ? "1" : "0"))); + defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0"))); - osg::ref_ptr vertexShader (readShader(osg::Shader::VERTEX, mResourcePath + "/shaders/water_vertex.glsl", defineMap)); - osg::ref_ptr fragmentShader (readShader(osg::Shader::FRAGMENT, mResourcePath + "/shaders/water_fragment.glsl", defineMap)); + Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); + osg::ref_ptr vertexShader (shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); + osg::ref_ptr fragmentShader (shaderMgr.getShader("water_fragment.glsl", defineMap, osg::Shader::FRAGMENT)); osg::ref_ptr normalMap (new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png"))); if (normalMap->getImage())