diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 02e143286..f07bb3eb9 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -536,6 +536,9 @@ namespace MWBase /// @see MWWorld::WeatherManager::getStormDirection virtual Ogre::Vector3 getStormDirection() const = 0; + + /// Resets all actors in the current active cells to their original location within that cell. + virtual void resetActors() = 0; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3785ba9ae..75094861e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -280,7 +280,7 @@ namespace MWMechanics || actor1.getClass().getCreatureStats(actor1).isDead()) return; - const ESM::Position& actor1Pos = actor2.getRefData().getPosition(); + const ESM::Position& actor1Pos = actor1.getRefData().getPosition(); const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); float sqrDist = Ogre::Vector3(actor1Pos.pos).squaredDistance(Ogre::Vector3(actor2Pos.pos)); if (sqrDist > 7168*7168) @@ -756,7 +756,7 @@ namespace MWMechanics MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); // Make the summoned creature follow its master and help in fights - AiFollow package(ptr.getRefData().getHandle()); + AiFollow package(ptr.getCellRef().getRefId()); summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); int creatureActorId = summonedCreatureStats.getActorId(); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index c10c982b8..b61e5a121 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -59,6 +59,29 @@ void suggestCombatRange(int rangeTypes, float& rangeAttack, float& rangeFollow) } } +int numEffectsToCure (const MWWorld::Ptr& actor, int effectFilter=-1) +{ + int toCure=0; + const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); + for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) + { + const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; + for (std::vector::const_iterator effectIt = params.mEffects.begin(); + effectIt != params.mEffects.end(); ++effectIt) + { + int effectId = effectIt->mEffectId; + if (effectFilter != -1 && effectId != effectFilter) + continue; + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful + && effectIt->mDuration > 3 // Don't attempt to cure if effect runs out shortly anyway + ) + ++toCure; + } + } + return toCure; +} + } namespace MWMechanics @@ -178,20 +201,24 @@ namespace MWMechanics { // NOTE: target may be empty - float baseRating = 1; + float rating = 1; switch (effect.mEffectID) { case ESM::MagicEffect::Soultrap: case ESM::MagicEffect::AlmsiviIntervention: case ESM::MagicEffect::DivineIntervention: - case ESM::MagicEffect::CalmCreature: case ESM::MagicEffect::CalmHumanoid: + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::FrenzyHumanoid: + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::DemoralizeHumanoid: + case ESM::MagicEffect::DemoralizeCreature: + case ESM::MagicEffect::RallyHumanoid: + case ESM::MagicEffect::RallyCreature: case ESM::MagicEffect::Charm: case ESM::MagicEffect::DetectAnimal: case ESM::MagicEffect::DetectEnchantment: case ESM::MagicEffect::DetectKey: - case ESM::MagicEffect::FrenzyCreature: - case ESM::MagicEffect::FrenzyHumanoid: case ESM::MagicEffect::Telekinesis: case ESM::MagicEffect::Mark: case ESM::MagicEffect::Recall: @@ -204,16 +231,39 @@ namespace MWMechanics case ESM::MagicEffect::Lock: case ESM::MagicEffect::Open: case ESM::MagicEffect::TurnUndead: + case ESM::MagicEffect::WeaknessToCommonDisease: + case ESM::MagicEffect::WeaknessToBlightDisease: + case ESM::MagicEffect::WeaknessToCorprusDisease: + case ESM::MagicEffect::CureCommonDisease: + case ESM::MagicEffect::CureBlightDisease: + case ESM::MagicEffect::CureCorprusDisease: + case ESM::MagicEffect::Invisibility: return 0.f; case ESM::MagicEffect::Feather: - return 0.f; // TODO: check if target is overencumbered + if (actor.getClass().getEncumbrance(actor) - actor.getClass().getCapacity(actor) >= 0) + return 100.f; + else + return 0.f; case ESM::MagicEffect::Levitate: return 0.f; // AI isn't designed to take advantage of this, and could be perceived as unfair anyway - // TODO: check if Beast race (can't wear boots or helm) - /* case ESM::MagicEffect::BoundBoots: case ESM::MagicEffect::BoundHelm: - */ + if (actor.getClass().isNpc()) + { + // Beast races can't wear helmets or boots + std::string raceid = actor.get()->mBase->mRace; + const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(raceid); + if (race->mData.mFlags & ESM::Race::Beast) + return 0.f; + } + // Intended fall-through + // Creatures can not wear armor + case ESM::MagicEffect::BoundCuirass: + case ESM::MagicEffect::BoundGloves: + if (!actor.getClass().isNpc()) + return 0.f; + break; + case ESM::MagicEffect::RestoreHealth: case ESM::MagicEffect::RestoreMagicka: case ESM::MagicEffect::RestoreFatigue: @@ -231,25 +281,13 @@ namespace MWMechanics } break; - // Give a small boost to all direct damage effects. This is combat, after all! - case ESM::MagicEffect::FireDamage: - case ESM::MagicEffect::ShockDamage: - case ESM::MagicEffect::FrostDamage: - case ESM::MagicEffect::Poison: - case ESM::MagicEffect::AbsorbHealth: - case ESM::MagicEffect::DamageHealth: - baseRating *= 4; - break; - - case ESM::MagicEffect::Paralyze: // *Evil laughter* - baseRating *= 5; - break; - - // TODO: rate these effects very high if we are currently suffering from negative effects that could be cured + // Prefer Cure effects over Dispel, because Dispel also removes positive effects case ESM::MagicEffect::Dispel: + return 1000.f * numEffectsToCure(actor); case ESM::MagicEffect::CureParalyzation: + return 1001.f * numEffectsToCure(actor, ESM::MagicEffect::Paralyze); case ESM::MagicEffect::CurePoison: - break; + return 1001.f * numEffectsToCure(actor, ESM::MagicEffect::Poison); default: break; @@ -261,12 +299,10 @@ namespace MWMechanics const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); - baseRating *= magicEffect->mData.mBaseCost; + rating *= magicEffect->mData.mBaseCost; - if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) - return 0.f; // No clue how useful this would be; will need special cases for each effect - - float rating = baseRating * (effect.mMagnMin + effect.mMagnMax)/2.f; + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + rating *= (effect.mMagnMin + effect.mMagnMax)/2.f; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) rating *= effect.mDuration; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 6563d0e62..a1f0d994a 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -684,9 +684,23 @@ namespace MWMechanics if (item.getCellRef().getEnchantmentCharge() < castCost) { - // TODO: Should there be a sound here? if (mCaster.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); + + // Failure sound + int school = 0; + for (std::vector::const_iterator effectIt (enchantment->mEffects.mList.begin()); + effectIt!=enchantment->mEffects.mList.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + school = magicEffect->mData.mSchool; + break; + } + 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 diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 7397355e3..626332564 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -431,5 +431,6 @@ op 0x2000294-0x20002ab: SetMagicEffect op 0x20002ac-0x20002c3: SetMagicEffect, explicit op 0x20002c4-0x20002db: ModMagicEffect op 0x20002dc-0x20002f3: ModMagicEffect, explicit +op 0x20002f4: ResetActors -opcodes 0x20002f4-0x3ffffff unused +opcodes 0x20002f5-0x3ffffff unused diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 3355e705f..42f44512a 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -719,6 +719,15 @@ namespace MWScript } }; + class OpResetActors : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::Environment::get().getWorld()->resetActors(); + } + }; void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -759,6 +768,7 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorldExplicit,new OpMoveWorld); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngle, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngleExplicit, new OpGetStartingAngle); + interpreter.installSegment5(Compiler::Transformation::opcodeResetActors, new OpResetActors); } } } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index d241278d1..59fc86d0d 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -358,7 +358,7 @@ namespace MWWorld Class::copyToCell(const Ptr &ptr, CellStore &cell) const { Ptr newPtr = copyToCellImpl(ptr, cell); - + newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference return newPtr; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f19a40313..c1147fa03 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2989,4 +2989,31 @@ namespace MWWorld if (!interpreterContext.hasActivationBeenHandled()) interpreterContext.executeActivation(object, actor); } + + struct ResetActorsFunctor + { + bool operator() (Ptr ptr) + { + // Can't reset actors that were moved to a different cell, because we don't know what cell they came from. + // This could be fixed once we properly track actor cell changes, but may not be desirable behaviour anyhow. + if (ptr.getClass().isActor() && ptr.getCellRef().getRefNum().mContentFile != -1) + { + const ESM::Position& origPos = ptr.getCellRef().getPosition(); + MWBase::Environment::get().getWorld()->moveObject(ptr, origPos.pos[0], origPos.pos[1], origPos.pos[2]); + MWBase::Environment::get().getWorld()->rotateObject(ptr, origPos.rot[0], origPos.rot[1], origPos.rot[2]); + ptr.getClass().adjustPosition(ptr, false); + } + return true; + } + }; + void World::resetActors() + { + for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); + iter!=mWorldScene->getActiveCells().end(); ++iter) + { + CellStore* cellstore = *iter; + ResetActorsFunctor functor; + cellstore->forEach(functor); + } + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4dade0f21..eddd056e0 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -611,6 +611,9 @@ namespace MWWorld /// @see MWWorld::WeatherManager::getStormDirection virtual Ogre::Vector3 getStormDirection() const; + + /// Resets all actors in the current active cells to their original location within that cell. + virtual void resetActors(); }; } diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index a2dc97a1a..e12ab65eb 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -527,6 +527,8 @@ namespace Compiler extensions.registerInstruction("move","cf",opcodeMove,opcodeMoveExplicit); extensions.registerInstruction("moveworld","cf",opcodeMoveWorld,opcodeMoveWorldExplicit); extensions.registerFunction("getstartingangle",'f',"c",opcodeGetStartingAngle,opcodeGetStartingAngleExplicit); + extensions.registerInstruction("resetactors","",opcodeResetActors); + extensions.registerInstruction("ra","",opcodeResetActors); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index b37a78d62..440475773 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -484,6 +484,7 @@ namespace Compiler const int opcodeMoveExplicit = 0x2000207; const int opcodeMoveWorld = 0x2000208; const int opcodeMoveWorldExplicit = 0x2000209; + const int opcodeResetActors = 0x20002f4; } namespace User