#include "spellcasting.hpp" #include #include #include #include #include #include /* Start of tes3mp addition Include additional headers for multiplayer purposes */ #include #include "../mwmp/Main.hpp" #include "../mwmp/Networking.hpp" #include "../mwmp/PlayerList.hpp" #include "../mwmp/LocalPlayer.hpp" #include "../mwmp/ObjectList.hpp" #include "../mwmp/CellController.hpp" #include "../mwmp/MechanicsHelper.hpp" /* End of tes3mp addition */ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" #include "npcstats.hpp" #include "actorutil.hpp" #include "aifollow.hpp" namespace MWMechanics { ESM::Skill::SkillEnum spellSchoolToSkill(int school) { std::map schoolSkillMap; // maps spell school to skill id schoolSkillMap[0] = ESM::Skill::Alteration; schoolSkillMap[1] = ESM::Skill::Conjuration; schoolSkillMap[3] = ESM::Skill::Illusion; schoolSkillMap[2] = ESM::Skill::Destruction; schoolSkillMap[4] = ESM::Skill::Mysticism; schoolSkillMap[5] = ESM::Skill::Restoration; assert(schoolSkillMap.find(school) != schoolSkillMap.end()); return schoolSkillMap[school]; } float calcEffectCost(const ESM::ENAMstruct& effect) { const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); int minMagn = 1; int maxMagn = 1; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { minMagn = effect.mMagnMin; maxMagn = effect.mMagnMax; } int duration = 0; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) duration = effect.mDuration; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() .get().find("fEffectCostMult")->getFloat(); float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; x *= 1 + duration; x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; return x * fEffectCostMult; } float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float y = std::numeric_limits::max(); float lowestSkill = 0; for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) { float x = static_cast(it->mDuration); const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( it->mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) x = std::max(1.f, x); x *= 0.1f * magicEffect->mData.mBaseCost; x *= 0.5f * (it->mMagnMin + it->mMagnMax); x *= it->mArea * 0.05f * magicEffect->mData.mBaseCost; if (it->mRange == ESM::RT_Target) x *= 1.5f; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fEffectCostMult")->getFloat(); x *= fEffectCostMult; float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool)); if (s - x < y) { y = s - x; if (effectiveSchool) *effectiveSchool = magicEffect->mData.mSchool; lowestSkill = s; } } CreatureStats& stats = actor.getClass().getCreatureStats(actor); int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck); return castChance; } float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) { bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); CreatureStats& stats = actor.getClass().getCreatureStats(actor); float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus; castChance *= stats.getFatigueTerm(); if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude()&& !godmode) return 0; if (spell->mData.mType == ESM::Spell::ST_Power) return stats.getSpells().canUsePower(spell) ? 100 : 0; if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost && !godmode) return 0; if (spell->mData.mFlags & ESM::Spell::F_Always) return 100; if (godmode) { return 100; } if (!cap) return std::max(0.f, castChance); else return std::max(0.f, std::min(100.f, castChance)); } float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka); } int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) { int school = 0; getSpellSuccessChance(spellId, actor, &school); return school; } int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) { int school = 0; getSpellSuccessChance(spell, actor, &school); return school; } bool spellIncreasesSkill(const ESM::Spell *spell) { if (spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always)) return true; return false; } bool spellIncreasesSkill(const std::string &spellId) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); return spellIncreasesSkill(spell); } float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects) { short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); float resistance = 0; if (resistanceEffect != -1) resistance += actorEffects->get(resistanceEffect).getMagnitude(); if (weaknessEffect != -1) resistance -= actorEffects->get(weaknessEffect).getMagnitude(); if (effectId == ESM::MagicEffect::FireDamage) resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude(); if (effectId == ESM::MagicEffect::ShockDamage) resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude(); if (effectId == ESM::MagicEffect::FrostDamage) resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude(); return resistance; } float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell, const MagicEffects* effects) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( effectId); const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects(); if (effects) magicEffects = effects; // Effects with no resistance attribute belonging to them can not be resisted if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) return 0.f; float resistance = getEffectResistanceAttribute(effectId, magicEffects); int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); float luck = static_cast(stats.getAttribute(ESM::Attribute::Luck).getModified()); float x = (willpower + 0.1f * luck) * stats.getFatigueTerm(); // This makes spells that are easy to cast harder to resist and vice versa float castChance = 100.f; if (spell != NULL && !caster.isEmpty() && caster.getClass().isActor()) { castChance = getSpellSuccessChance(spell, caster, NULL, false); // Uncapped casting chance } if (castChance > 0) x *= 50 / castChance; float roll = Misc::Rng::rollClosedProbability() * 100; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) roll -= resistance; if (x <= roll) x = 0; else { if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) x = 100; else x = roll / std::min(x, 100.f); } x = std::min(x + resistance, 100.f); return x; } float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell, const MagicEffects* effects) { float resistance = getEffectResistance(effectId, actor, caster, spell, effects); return 1 - resistance / 100.f; } /// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure. bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer) { switch (effectId) { case ESM::MagicEffect::Levitate: if (!MWBase::Environment::get().getWorld()->isLevitationEnabled()) { if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); return false; } break; case ESM::MagicEffect::Soultrap: if (!target.getClass().isNpc() // no messagebox for NPCs && (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) { if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); return true; // must still apply to get visual effect and have target regard it as attack } break; case ESM::MagicEffect::AlmsiviIntervention: case ESM::MagicEffect::DivineIntervention: case ESM::MagicEffect::Mark: case ESM::MagicEffect::Recall: if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) { if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); return false; } break; case ESM::MagicEffect::WaterWalking: if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target)) return false; MWBase::World *world = MWBase::Environment::get().getWorld(); if (!world->isWaterWalkingCastableOnTarget(target)) { if (castByPlayer && caster == target) MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}"); return false; } break; } return true; } CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool isScripted) : mCaster(caster) , mTarget(target) , mStack(false) , mHitPosition(0,0,0) , mAlwaysSucceed(false) , mFromProjectile(fromProjectile) , mIsScripted(isScripted) { } void CastSpell::launchMagicBolt () { osg::Vec3f fallbackDirection (0,1,0); // Fall back to a "caster to target" direction if we have no other means of determining it // (e.g. when cast by a non-actor) if (!mTarget.isEmpty()) fallbackDirection = osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection); } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) return; // If none of the effects need to apply, we can early-out bool found = false; for (std::vector::const_iterator iter (effects.mList.begin()); iter!=effects.mList.end(); ++iter) { if (iter->mRange == range) { found = true; break; } } if (!found) return; const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); 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 : ESM::MagicEffect::ResistBlightDisease; float x = target.getClass().getCreatureStats(target).getMagicEffects().get(requiredResistance).getMagnitude(); if (Misc::Rng::roll0to99() <= x) { // Fully resisted, show message if (target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); return; } } ESM::EffectList reflectedEffects; std::vector appliedLastingEffects; // HACK: cache target's magic effects here, and add any applied effects to it. Use the cached effects for determining resistance. // 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.isEmpty() && target.getClass().isActor()) targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); ActiveSpells targetSpells; if (!target.isEmpty() && target.getClass().isActor()) targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); bool canCastAnEffect = false; // For bound equipment.If this remains false // throughout the iteration of this spell's // effects, we display a "can't re-cast" message for (std::vector::const_iterator effectIt (effects.mList.begin()); !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt) { if (effectIt->mRange != range) continue; const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); // Re-casting a bound equipment effect has no effect if the spell is still active if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells.isSpellActive(mId)) { if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}"); continue; } else canCastAnEffect = true; if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer)) continue; // caster needs to be an actor for linked effects (e.g. Absorb) if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked && (caster.isEmpty() || !caster.getClass().isActor())) continue; // If player is healing someone, show the target's HP bar if (castByPlayer && target != caster && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth && target.getClass().isActor()) MWBase::Environment::get().getWindowManager()->setEnemy(target); // Try absorbing if it's a spell // NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, not sure // if that is worth replicating. bool absorbed = false; if (spell && caster != target && target.getClass().isActor()) { float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); absorbed = (Misc::Rng::roll0to99() < absorb); if (absorbed) { const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); // Magicka is increased by cost of spell DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); target.getClass().getCreatureStats(target).setMagicka(magicka); } } float magnitudeMult = 1; if (!absorbed && target.getClass().isActor()) { bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; // Reflect harmful effects if (isHarmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); bool isReflected = (Misc::Rng::roll0to99() < reflect); if (isReflected) { const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( "meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, ""); reflectedEffects.mList.push_back(*effectIt); continue; } } // Try resisting magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); if (magnitudeMult == 0) { // Fully resisted, show message if (target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); else if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); } else if (isHarmful && castByPlayer && target != caster) { // If player is attempting to cast a harmful spell and it wasn't fully resisted, show the target's HP bar MWBase::Environment::get().getWindowManager()->setEnemy(target); } if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) magnitudeMult = 0; // Notify the target actor they've been hit if (target != caster && !caster.isEmpty() && isHarmful) target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); } if (magnitudeMult > 0 && !absorbed) { float random = Misc::Rng::rollClosedProbability(); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; magnitude *= magnitudeMult; if (!target.getClass().isActor()) { // non-actor objects have no list of active magic effects, so have to apply instantly if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude)) continue; } else // target.getClass().isActor() == true { bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); if (hasDuration && effectIt->mDuration == 0) { // duration 0 means apply full magnitude instantly bool wasDead = target.getClass().getCreatureStats(target).isDead(); effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude); bool isDead = target.getClass().getCreatureStats(target).isDead(); /* Start of tes3mp addition If the victim was a LocalPlayer or LocalActor who died, record their attacker as the deathReason */ if (!wasDead && isDead) { bool isSuicide = target == caster || caster.isEmpty(); if (target == MWMechanics::getPlayer()) { mwmp::Main::get().getLocalPlayer()->killer = isSuicide ? MechanicsHelper::getTarget(target) : MechanicsHelper::getTarget(caster); } else if (mwmp::Main::get().getCellController()->isLocalActor(target)) { mwmp::Main::get().getCellController()->getLocalActor(target)->killer = isSuicide ? MechanicsHelper::getTarget(target) : MechanicsHelper::getTarget(caster); } } /* End of tes3mp addition */ if (!wasDead && isDead) MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster); } else { // add to list of active effects, to apply in next frame ActiveSpells::ActiveEffect effect; effect.mEffectId = effectIt->mEffectID; effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; if (!hasDuration) effect.mDuration = 1.0f; else effect.mDuration = static_cast(effectIt->mDuration); effect.mMagnitude = magnitude; targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); appliedLastingEffects.push_back(effect); // Unequip all items, if a spell with the ExtraSpell effect was casted if (effectIt->mEffectID == ESM::MagicEffect::ExtraSpell && target.getClass().hasInventoryStore(target)) { MWWorld::InventoryStore& store = target.getClass().getInventoryStore(target); store.unequipAll(target); } // Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc()) || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) && !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel()) { MWMechanics::AiFollow package(caster, true); target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); } // For absorb effects, also apply the effect to the caster - but with a negative // magnitude, since we're transferring stats from the target to the caster if (!caster.isEmpty() && caster.getClass().isActor()) { for (int i=0; i<5; ++i) { if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) { std::vector absorbEffects; ActiveSpells::ActiveEffect effect_ = effect; effect_.mMagnitude *= -1; absorbEffects.push_back(effect_); if (reflected && Settings::Manager::getBool("classic reflect absorb attribute behavior", "Game")) target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true, absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId()); else caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); } } } } } // Re-casting a summon effect will remove the creature from previous castings of that effect. if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor()) { CreatureStats& targetStats = target.getClass().getCreatureStats(target); std::map::iterator findCreature = targetStats.getSummonedCreatureMap().find(std::make_pair(effectIt->mEffectID, mId)); if (findCreature != targetStats.getSummonedCreatureMap().end()) { MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, findCreature->second); targetStats.getSummonedCreatureMap().erase(findCreature); } } if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) { static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!magicEffect->mHitSound.empty()) sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f); else sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); // Add VFX const ESM::Static* castStatic; if (!magicEffect->mHit.empty()) castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); else castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); std::string texture = magicEffect->mParticle; bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; // Note: in case of non actor, a free effect should be fine as well MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); if (anim) anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture); } } } if (!exploded) MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile); 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); } } } bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) { short effectId = effect.mId; if (target.getClass().canLock(target)) { if (effectId == ESM::MagicEffect::Lock) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::MagicEffect *magiceffect = store.get().find(effectId); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); if (animation) animation->addSpellCastGlow(magiceffect); if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude { if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); /* Start of tes3mp change (major) Disable unilateral locking on this client and expect the server's reply to our packet to do it instead */ //target.getClass().lock(target, static_cast(magnitude)); /* End of tes3mp change (major) */ /* Start of tes3mp addition Send an ID_OBJECT_LOCK packet every time an object is locked here */ mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList(); objectList->reset(); objectList->addObjectLock(target, static_cast(magnitude)); objectList->sendObjectLock(); /* End of tes3mp addition */ } return true; } else if (effectId == ESM::MagicEffect::Open) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::MagicEffect *magiceffect = store.get().find(effectId); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); if (animation) animation->addSpellCastGlow(magiceffect); if (target.getCellRef().getLockLevel() <= magnitude) { if (target.getCellRef().getLockLevel() > 0) { MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); if (!caster.isEmpty()) MWBase::Environment::get().getMechanicsManager()->objectOpened(getPlayer(), target); // Use the player instead of the caster for vanilla crime compatibility if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); } /* Start of tes3mp change (major) Disable unilateral locking on this client and expect the server's reply to our packet to do it instead */ //target.getClass().unlock(target); /* End of tes3mp change (major) */ /* Start of tes3mp addition Send an ID_OBJECT_LOCK packet every time an object is unlocked here */ mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList(); objectList->reset(); objectList->addObjectLock(target, 0); objectList->sendObjectLock(); /* End of tes3mp addition */ } else MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); return true; } } else if (target.getClass().isActor() && effectId == ESM::MagicEffect::Dispel) { target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude, true); return true; } else if (target.getClass().isActor() && target == getPlayer()) { MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (effectId == ESM::MagicEffect::DivineIntervention) { MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker"); anim->removeEffect(ESM::MagicEffect::DivineIntervention); const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() .search("VFX_Summon_end"); if (fx) anim->addEffect("meshes\\" + fx->mModel, -1); return true; } else if (effectId == ESM::MagicEffect::AlmsiviIntervention) { MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker"); anim->removeEffect(ESM::MagicEffect::AlmsiviIntervention); const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() .search("VFX_Summon_end"); if (fx) anim->addEffect("meshes\\" + fx->mModel, -1); return true; } else if (effectId == ESM::MagicEffect::Mark) { MWBase::Environment::get().getWorld()->getPlayer().markPosition( target.getCell(), target.getRefData().getPosition()); /* Start of tes3mp addition Send a PlayerMiscellaneous packet with the player's new mark location */ mwmp::Main::get().getLocalPlayer()->sendMarkLocation(*target.getCell()->getCell(), target.getRefData().getPosition()); /* End of tes3mp addition */ return true; } else if (effectId == ESM::MagicEffect::Recall) { MWWorld::CellStore* markedCell = NULL; ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); if (markedCell) { MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, markedPosition, false); action.execute(target); anim->removeEffect(ESM::MagicEffect::Recall); } return true; } } return false; } bool CastSpell::cast(const std::string &id) { if (const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return cast(spell); if (const ESM::Potion *potion = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return cast(potion); if (const ESM::Ingredient *ingredient = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return cast(ingredient); throw std::runtime_error("ID type cannot be casted"); } bool CastSpell::cast(const MWWorld::Ptr &item, bool launchProjectile) { std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) throw std::runtime_error("can't cast an item without an enchantment"); mSourceName = item.getClass().getName(item); mId = item.getCellRef().getRefId(); const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); mStack = false; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); // Check if there's enough charge left if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), mCaster); if (item.getCellRef().getEnchantmentCharge() == -1) item.getCellRef().setEnchantmentCharge(static_cast(enchantment->mData.mCharge)); if (godmode) castCost = 0; if (item.getCellRef().getEnchantmentCharge() < castCost) { if (mCaster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); // Failure sound int school = 0; if (!enchantment->mEffects.mList.empty()) { short effectId = enchantment->mEffects.mList.front().mEffectID; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); school = magicEffect->mData.mSchool; } static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); return false; } // Reduce charge item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost); } if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) { if (mCaster == getPlayer()) mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); } if (enchantment->mData.mType == ESM::Enchantment::CastOnce && !godmode) { item.getContainerStore()->remove(item, 1, mCaster); } else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes) { if (mCaster == getPlayer()) { mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); } } inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); bool isProjectile = false; if (item.getTypeName() == typeid(ESM::Weapon).name()) { 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(); else if (isProjectile || !mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); return true; } bool CastSpell::cast(const ESM::Potion* potion) { mSourceName = potion->mName; mId = potion->mId; mStack = true; inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); return true; } bool CastSpell::cast(const ESM::Spell* spell) { mSourceName = spell->mName; mId = spell->mId; mStack = false; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); int school = 0; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mIsScripted) { school = getSpellSchool(spell, mCaster); CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster); if (!godmode) { // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) static const float fFatigueSpellBase = store.get().find("fFatigueSpellBase")->getFloat(); static const float fFatigueSpellMult = store.get().find("fFatigueSpellMult")->getFloat(); DynamicStat fatigue = stats.getFatigue(); const float normalizedEncumbrance = mCaster.getClass().getNormalizedEncumbrance(mCaster); float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); stats.setFatigue(fatigue); bool fail = false; /* Start of tes3mp change (major) Make spell casting fail based on the attack success rated determined in LocalPlayer and LocalActor's updateAttack() */ mwmp::Attack *localAttack = NULL; mwmp::Attack *dedicatedAttack = MechanicsHelper::getDedicatedAttack(mCaster); if (dedicatedAttack) dedicatedAttack->pressed = false; else localAttack = MechanicsHelper::getLocalAttack(mCaster); // Check success if ((localAttack && localAttack->success == false) || (dedicatedAttack && dedicatedAttack->success == false)) { if (mCaster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); fail = true; } /* End of tes3mp change (major) */ if (fail) { // Failure sound static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); return false; } } // A power can be used once per 24h if (spell->mData.mType == ESM::Spell::ST_Power) stats.getSpells().usePower(spell); } if (mCaster == getPlayer() && spellIncreasesSkill()) mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) playSpellCastingEffects(mId); inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); if (!mTarget.isEmpty()) inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); launchMagicBolt(); return true; } bool CastSpell::cast (const ESM::Ingredient* ingredient) { mId = ingredient->mId; mStack = true; mSourceName = ingredient->mName; ESM::ENAMstruct effect; effect.mEffectID = ingredient->mData.mEffectID[0]; effect.mSkill = ingredient->mData.mSkills[0]; effect.mAttribute = ingredient->mData.mAttributes[0]; effect.mRange = ESM::RT_Self; effect.mArea = 0; const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( effect.mEffectID); const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster); const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + 0.2f * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) * creatureStats.getFatigueTerm(); int roll = Misc::Rng::roll0to99(); if (roll > x) { // "X has no effect on you" std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage50")->getString(); message = boost::str(boost::format(message) % ingredient->mName); MWBase::Environment::get().getWindowManager()->messageBox(message); return false; } float magnitude = 0; float y = roll / std::min(x, 100.f); y *= 0.25f * x; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) effect.mDuration = 1; else effect.mDuration = static_cast(y); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost)); else magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost)); magnitude = std::max(1.f, magnitude); } else magnitude = 1; effect.mMagnMax = static_cast(magnitude); effect.mMagnMin = static_cast(magnitude); ESM::EffectList effects; effects.mList.push_back(effect); inflict(mCaster, mCaster, effects, ESM::RT_Self); return true; } void CastSpell::playSpellCastingEffects(const std::string &spellid){ const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().find(spellid); for (std::vector::const_iterator iter = spell->mEffects.mList.begin(); iter != spell->mEffects.mList.end(); ++iter) { const ESM::MagicEffect *effect; effect = store.get().find(iter->mEffectID); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (animation && mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx even if they are disabled (animation == NULL) { const ESM::Static* castStatic; if (!effect->mCasting.empty()) castStatic = store.get().find (effect->mCasting); else castStatic = store.get().find ("VFX_DefaultCast"); std::string texture = effect->mParticle; animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture); } if (animation && !mCaster.getClass().isActor()) animation->addSpellCastGlow(effect); static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mCastSound.empty()) sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); else sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); } } bool CastSpell::spellIncreasesSkill() { if (mIsScripted) return false; return MWMechanics::spellIncreasesSkill(mId); } int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) { /* * Each point of enchant skill above/under 10 subtracts/adds * one percent of enchantment cost while minimum is 1. */ int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant); const float result = castCost - (castCost / 100) * (eSkill - 10); return static_cast((result < 1) ? 1 : result); } bool isSummoningEffect(int effectId) { return ((effectId >= ESM::MagicEffect::SummonScamp && effectId <= ESM::MagicEffect::SummonStormAtronach) || effectId == ESM::MagicEffect::SummonCenturionSphere || (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05)); } bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) { if (ptr.getClass().hasInventoryStore(ptr)) { MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator item = inv.getSlot(slot); if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) { if (!item->getClass().hasItemHealth(*item)) return false; int charge = item->getClass().getItemHealth(*item); if (charge == 0) return false; // Store remainder of disintegrate amount (automatically subtracted if > 1) item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate)); charge = item->getClass().getItemHealth(*item); charge -= std::min(static_cast(disintegrate), charge); item->getCellRef().setCharge(charge); if (charge == 0) { // Will unequip the broken item and try to find a replacement if (ptr != getPlayer()) inv.autoEquip(ptr); else inv.unequipItem(*item, ptr); } return true; } } return false; } void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude) { DynamicStat stat = creatureStats.getDynamic(index); stat.setCurrent(stat.getCurrent() + magnitude, index == 2); creatureStats.setDynamic(index, stat); } bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude) { if (magnitude == 0.f) return false; bool receivedMagicDamage = false; switch (effectKey.mId) { case ESM::MagicEffect::DamageAttribute: { AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); attr.damage(magnitude); creatureStats.setAttribute(effectKey.mArg, attr); break; } case ESM::MagicEffect::RestoreAttribute: { AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); attr.restore(magnitude); creatureStats.setAttribute(effectKey.mArg, attr); break; } case ESM::MagicEffect::RestoreHealth: case ESM::MagicEffect::RestoreMagicka: case ESM::MagicEffect::RestoreFatigue: adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude); break; case ESM::MagicEffect::DamageHealth: receivedMagicDamage = true; adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); break; case ESM::MagicEffect::DamageMagicka: case ESM::MagicEffect::DamageFatigue: adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); break; case ESM::MagicEffect::AbsorbHealth: if (magnitude > 0.f) receivedMagicDamage = true; adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); break; case ESM::MagicEffect::AbsorbMagicka: case ESM::MagicEffect::AbsorbFatigue: adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); break; case ESM::MagicEffect::DisintegrateArmor: { // According to UESP int priorities[] = { MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet, MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Boots }; for (unsigned int i=0; iisExterior()) break; float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); float damageScale = 1.f - timeDiff / 7.f; // When cloudy, the sun damage effect is halved static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fMagicSunBlockedMult")->getFloat(); int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); if (weather > 1) damageScale *= fMagicSunBlockedMult; adjustDynamicStat(creatureStats, 0, -magnitude * damageScale); if (magnitude * damageScale > 0.f) receivedMagicDamage = true; break; } case ESM::MagicEffect::FireDamage: case ESM::MagicEffect::ShockDamage: case ESM::MagicEffect::FrostDamage: case ESM::MagicEffect::Poison: { adjustDynamicStat(creatureStats, 0, -magnitude); receivedMagicDamage = true; break; } case ESM::MagicEffect::DamageSkill: case ESM::MagicEffect::RestoreSkill: { if (!actor.getClass().isNpc()) break; NpcStats &npcStats = actor.getClass().getNpcStats(actor); SkillValue& skill = npcStats.getSkill(effectKey.mArg); if (effectKey.mId == ESM::MagicEffect::RestoreSkill) skill.restore(magnitude); else skill.damage(magnitude); break; } case ESM::MagicEffect::CurePoison: actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); break; case ESM::MagicEffect::CureParalyzation: actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); break; case ESM::MagicEffect::CureCommonDisease: actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease(); break; case ESM::MagicEffect::CureBlightDisease: actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease(); break; case ESM::MagicEffect::CureCorprusDisease: actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease(); break; case ESM::MagicEffect::RemoveCurse: actor.getClass().getCreatureStats(actor).getSpells().purgeCurses(); break; default: return false; } if (receivedMagicDamage && actor == getPlayer()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); return true; } std::string getSummonedCreature(int effectId) { static std::map summonMap; if (summonMap.empty()) { summonMap[ESM::MagicEffect::SummonAncestralGhost] = "sMagicAncestralGhostID"; summonMap[ESM::MagicEffect::SummonBonelord] = "sMagicBonelordID"; summonMap[ESM::MagicEffect::SummonBonewalker] = "sMagicLeastBonewalkerID"; summonMap[ESM::MagicEffect::SummonCenturionSphere] = "sMagicCenturionSphereID"; summonMap[ESM::MagicEffect::SummonClannfear] = "sMagicClannfearID"; summonMap[ESM::MagicEffect::SummonDaedroth] = "sMagicDaedrothID"; summonMap[ESM::MagicEffect::SummonDremora] = "sMagicDremoraID"; summonMap[ESM::MagicEffect::SummonFabricant] = "sMagicFabricantID"; summonMap[ESM::MagicEffect::SummonFlameAtronach] = "sMagicFlameAtronachID"; summonMap[ESM::MagicEffect::SummonFrostAtronach] = "sMagicFrostAtronachID"; summonMap[ESM::MagicEffect::SummonGoldenSaint] = "sMagicGoldenSaintID"; summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "sMagicGreaterBonewalkerID"; summonMap[ESM::MagicEffect::SummonHunger] = "sMagicHungerID"; summonMap[ESM::MagicEffect::SummonScamp] = "sMagicScampID"; summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "sMagicSkeletalMinionID"; summonMap[ESM::MagicEffect::SummonStormAtronach] = "sMagicStormAtronachID"; summonMap[ESM::MagicEffect::SummonWingedTwilight] = "sMagicWingedTwilightID"; summonMap[ESM::MagicEffect::SummonWolf] = "sMagicCreature01ID"; summonMap[ESM::MagicEffect::SummonBear] = "sMagicCreature02ID"; summonMap[ESM::MagicEffect::SummonBonewolf] = "sMagicCreature03ID"; summonMap[ESM::MagicEffect::SummonCreature04] = "sMagicCreature04ID"; summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID"; } std::map::const_iterator it = summonMap.find(effectId); if (it == summonMap.end()) return std::string(); else return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->getString(); } void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key, const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, float /*magnitude*/, float /*remainingTime*/, float /*totalTime*/) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(key.mId); const ESM::Static* castStatic; if (!magicEffect->mHit.empty()) castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); else castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); std::string texture = magicEffect->mParticle; bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor); if (anim && loop) anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture); } }