Merge remote-tracking branch 'scrawl/master'

This commit is contained in:
Marc Zinnschlag 2015-01-07 20:22:15 +01:00
commit 38a413a483
33 changed files with 446 additions and 224 deletions

View file

@ -76,7 +76,7 @@ add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor
aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
) )
add_openmw_dir (mwstate add_openmw_dir (mwstate

View file

@ -518,7 +518,8 @@ void OMW::Engine::activate()
return; return;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (player.getClass().getCreatureStats(player).getKnockedDown()) if (player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0
|| player.getClass().getCreatureStats(player).getKnockedDown())
return; return;
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject(); MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject();

View file

@ -111,7 +111,7 @@ namespace MWGui
mCharge->setCaption(boost::lexical_cast<std::string>(mEnchanting.getGemCharge())); mCharge->setCaption(boost::lexical_cast<std::string>(mEnchanting.getGemCharge()));
std::stringstream castCost; std::stringstream castCost;
castCost << mEnchanting.getCastCost(); castCost << mEnchanting.getEffectiveCastCost();
mCastCost->setCaption(castCost.str()); mCastCost->setCaption(castCost.str());
mPrice->setCaption(boost::lexical_cast<std::string>(mEnchanting.getEnchantPrice())); mPrice->setCaption(boost::lexical_cast<std::string>(mEnchanting.getEnchantPrice()));

View file

@ -25,7 +25,7 @@ namespace MWGui
{ {
void EffectSourceVisitor::visit (MWMechanics::EffectKey key, void EffectSourceVisitor::visit (MWMechanics::EffectKey key,
const std::string& sourceName, int casterActorId, const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime, float totalTime) float magnitude, float remainingTime, float totalTime)
{ {
MagicEffectInfo newEffectSource; MagicEffectInfo newEffectSource;

View file

@ -47,7 +47,7 @@ namespace MWGui
virtual ~EffectSourceVisitor() {} virtual ~EffectSourceVisitor() {}
virtual void visit (MWMechanics::EffectKey key, virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, int casterActorId, const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1); float magnitude, float remainingTime = -1, float totalTime = -1);
}; };

View file

@ -103,9 +103,7 @@ namespace MWGui
&& item.getClass().canBeEquipped(item, mActor).first == 0) && item.getClass().canBeEquipped(item, mActor).first == 0)
continue; continue;
float enchantCost = enchant->mData.mCost; int castCost = MWMechanics::getEffectiveEnchantmentCastCost(enchant->mData.mCost, mActor);
int eSkill = mActor.getClass().getSkill(mActor, ESM::Skill::Enchant);
int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
std::string cost = boost::lexical_cast<std::string>(castCost); std::string cost = boost::lexical_cast<std::string>(castCost);
int currentCharge = int(item.getCellRef().getEnchantmentCharge()); int currentCharge = int(item.getCellRef().getEnchantmentCharge());

View file

@ -195,7 +195,7 @@ namespace MWMechanics
float magnitude = effectIt->mMagnitude; float magnitude = effectIt->mMagnitude;
if (magnitude) if (magnitude)
visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->second.mCasterActorId, magnitude, remainingTime, effectIt->mDuration); visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->first, it->second.mCasterActorId, magnitude, remainingTime, effectIt->mDuration);
} }
} }
} }
@ -229,6 +229,22 @@ namespace MWMechanics
mSpellsChanged = true; mSpellsChanged = true;
} }
void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId)
{
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
{
for (std::vector<ActiveEffect>::iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end();)
{
if (effectIt->mEffectId == effectId && it->first == sourceId)
effectIt = it->second.mEffects.erase(effectIt);
else
++effectIt;
}
}
mSpellsChanged = true;
}
void ActiveSpells::purge(int casterActorId) void ActiveSpells::purge(int casterActorId)
{ {
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)

View file

@ -82,6 +82,9 @@ namespace MWMechanics
/// Remove all active effects with this effect id /// Remove all active effects with this effect id
void purgeEffect (short effectId); void purgeEffect (short effectId);
/// Remove all active effects with this effect id and source id
void purgeEffect (short effectId, const std::string& sourceId);
/// Remove all active effects, if roll succeeds (for each effect) /// Remove all active effects, if roll succeeds (for each effect)
void purgeAll (float chance); void purgeAll (float chance);

View file

@ -36,6 +36,7 @@
#include "aipursue.hpp" #include "aipursue.hpp"
#include "actor.hpp" #include "actor.hpp"
#include "summoning.hpp"
namespace namespace
{ {
@ -105,7 +106,7 @@ public:
, mCommanded(false){} , mCommanded(false){}
virtual void visit (MWMechanics::EffectKey key, virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, int casterActorId, const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1) float magnitude, float remainingTime = -1, float totalTime = -1)
{ {
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
@ -165,30 +166,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
} }
} }
void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId)
{
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId);
if (!ptr.isEmpty())
{
// TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation
// plays though, which is a rather lame exploit in vanilla.
MWBase::Environment::get().getWorld()->deleteObject(ptr);
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_End");
if (fx)
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel,
"", Ogre::Vector3(ptr.getRefData().getPosition().pos));
}
else
{
// We didn't find the creature. It's probably in an inactive cell.
// Add to graveyard so we can delete it when the cell becomes active.
std::vector<int>& graveyard = casterStats.getSummonedCreatureGraveyard();
graveyard.push_back(creatureActorId);
}
}
} }
namespace MWMechanics namespace MWMechanics
@ -203,7 +180,7 @@ namespace MWMechanics
: mCreature(trappedCreature) {} : mCreature(trappedCreature) {}
virtual void visit (MWMechanics::EffectKey key, virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, int casterActorId, const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1) float magnitude, float remainingTime = -1, float totalTime = -1)
{ {
if (key.mId != ESM::MagicEffect::Soultrap) if (key.mId != ESM::MagicEffect::Soultrap)
@ -782,131 +759,11 @@ namespace MWMechanics
} }
} }
// Update summon effects UpdateSummonedCreatures updateSummonedCreatures(ptr);
static std::map<int, std::string> summonMap; creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures);
if (summonMap.empty()) if (ptr.getClass().hasInventoryStore(ptr))
{ ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures);
summonMap[ESM::MagicEffect::SummonAncestralGhost] = "sMagicAncestralGhostID"; updateSummonedCreatures.finish();
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<int, int>& creatureMap = creatureStats.getSummonedCreatureMap();
for (std::map<int, std::string>::iterator it = summonMap.begin(); it != summonMap.end(); ++it)
{
bool found = creatureMap.find(it->first) != creatureMap.end();
int magnitude = creatureStats.getMagicEffects().get(it->first).getMagnitude();
if (found != (magnitude > 0))
{
if (magnitude > 0)
{
ESM::Position ipos = ptr.getRefData().getPosition();
Ogre::Vector3 pos(ipos.pos);
Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z);
const float distance = 50;
pos = pos + distance*rot.yAxis();
ipos.pos[0] = pos.x;
ipos.pos[1] = pos.y;
ipos.pos[2] = pos.z;
ipos.rot[0] = 0;
ipos.rot[1] = 0;
ipos.rot[2] = 0;
std::string creatureID =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(it->second)->getString();
if (!creatureID.empty())
{
MWWorld::CellStore* store = ptr.getCell();
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1);
ref.getPtr().getCellRef().setPosition(ipos);
MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr());
// Make the summoned creature follow its master and help in fights
AiFollow package(ptr.getCellRef().getRefId());
summonedCreatureStats.getAiSequence().stack(package, ref.getPtr());
int creatureActorId = summonedCreatureStats.getActorId();
MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos);
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed);
if (anim)
{
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_Start");
if (fx)
anim->addEffect("meshes\\" + fx->mModel, -1, false);
}
creatureMap.insert(std::make_pair(it->first, creatureActorId));
}
}
else
{
// Effect has ended
std::map<int, int>::iterator foundCreature = creatureMap.find(it->first);
cleanupSummonedCreature(creatureStats, foundCreature->second);
creatureMap.erase(foundCreature);
}
}
}
for (std::map<int, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
{
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead())
{
// Purge the magic effect so a new creature can be summoned if desired
creatureStats.getActiveSpells().purgeEffect(it->first);
if (ptr.getClass().hasInventoryStore(ptr))
ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first);
cleanupSummonedCreature(creatureStats, it->second);
creatureMap.erase(it++);
}
else
++it;
}
std::vector<int>& graveyard = creatureStats.getSummonedCreatureGraveyard();
for (std::vector<int>::iterator it = graveyard.begin(); it != graveyard.end(); )
{
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it);
if (!ptr.isEmpty())
{
it = graveyard.erase(it);
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_End");
if (fx)
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel,
"", Ogre::Vector3(ptr.getRefData().getPosition().pos));
MWBase::Environment::get().getWorld()->deleteObject(ptr);
}
else
++it;
}
} }
void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration) void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration)

View file

@ -580,7 +580,7 @@ namespace MWMechanics
buildNewPath(actor, target); //may fail to build a path, check before use buildNewPath(actor, target); //may fail to build a path, check before use
//delete visited path node //delete visited path node
mPathFinder.checkWaypoint(pos.pos[0],pos.pos[1],pos.pos[2]); mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]);
// This works on the borders between the path grid and areas with no waypoints. // This works on the borders between the path grid and areas with no waypoints.
if(inLOS && mPathFinder.getPath().size() > 1) if(inLOS && mPathFinder.getPath().size() > 1)

View file

@ -202,7 +202,7 @@ namespace MWMechanics
// Are we there yet? // Are we there yet?
bool& chooseAction = storage.mChooseAction; bool& chooseAction = storage.mChooseAction;
if(walking && if(walking &&
storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2], 64.f))
{ {
stopWalking(actor, storage); stopWalking(actor, storage);
moveNow = false; moveNow = false;

View file

@ -638,7 +638,7 @@ namespace MWMechanics
mDeathAnimation = index; mDeathAnimation = index;
} }
std::map<int, int>& CreatureStats::getSummonedCreatureMap() std::map<CreatureStats::SummonKey, int>& CreatureStats::getSummonedCreatureMap()
{ {
return mSummonedCreatures; return mSummonedCreatures;
} }

View file

@ -67,8 +67,11 @@ namespace MWMechanics
// The index of the death animation that was played // The index of the death animation that was played
unsigned char mDeathAnimation; unsigned char mDeathAnimation;
// <ESM::MagicEffect index, ActorId> public:
std::map<int, int> mSummonedCreatures; typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID>
private:
std::map<SummonKey, int> mSummonedCreatures; // <SummonKey, ActorId>
// Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet. // Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet.
// This may be necessary when the creature is in an inactive cell. // This may be necessary when the creature is in an inactive cell.
std::vector<int> mSummonGraveyard; std::vector<int> mSummonGraveyard;
@ -216,8 +219,8 @@ namespace MWMechanics
void setBlock(bool value); void setBlock(bool value);
bool getBlock() const; bool getBlock() const;
std::map<int, int>& getSummonedCreatureMap(); std::map<SummonKey, int>& getSummonedCreatureMap(); // <SummonKey, ActorId of summoned creature>
std::vector<int>& getSummonedCreatureGraveyard(); std::vector<int>& getSummonedCreatureGraveyard(); // ActorIds
enum Flag enum Flag
{ {

View file

@ -6,6 +6,7 @@
#include "creaturestats.hpp" #include "creaturestats.hpp"
#include "npcstats.hpp" #include "npcstats.hpp"
#include "spellcasting.hpp"
namespace MWMechanics namespace MWMechanics
{ {
@ -55,7 +56,7 @@ namespace MWMechanics
enchantment.mData.mCharge = getGemCharge(); enchantment.mData.mCharge = getGemCharge();
enchantment.mData.mAutocalc = 0; enchantment.mData.mAutocalc = 0;
enchantment.mData.mType = mCastStyle; enchantment.mData.mType = mCastStyle;
enchantment.mData.mCost = getCastCost(); enchantment.mData.mCost = getBaseCastCost();
store.remove(mSoulGemPtr, 1, player); store.remove(mSoulGemPtr, 1, player);
@ -65,7 +66,7 @@ namespace MWMechanics
if(mSelfEnchanting) if(mSelfEnchanting)
{ {
if(std::rand()/static_cast<double> (RAND_MAX)*100 < getEnchantChance()) if(getEnchantChance()<std::rand()/static_cast<double> (RAND_MAX)*100)
return false; return false;
mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2);
@ -199,23 +200,19 @@ namespace MWMechanics
} }
int Enchanting::getCastCost() const int Enchanting::getBaseCastCost() const
{ {
if (mCastStyle == ESM::Enchantment::ConstantEffect) if (mCastStyle == ESM::Enchantment::ConstantEffect)
return 0; return 0;
const float enchantCost = getEnchantPoints(); return getEnchantPoints();
}
int Enchanting::getEffectiveCastCost() const
{
int baseCost = getBaseCastCost();
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player); return getEffectiveEnchantmentCastCost(baseCost, player);
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
/*
* Each point of enchant skill above/under 10 subtracts/adds
* one percent of enchantment cost while minimum is 1.
*/
const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10);
return static_cast<int>((castCost < 1) ? 1 : castCost);
} }

View file

@ -36,7 +36,8 @@ namespace MWMechanics
void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object) void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object)
int getCastStyle() const; int getCastStyle() const;
int getEnchantPoints() const; int getEnchantPoints() const;
int getCastCost() const; int getBaseCastCost() const; // To be saved in the enchantment's record
int getEffectiveCastCost() const; // Effective cost taking player Enchant skill into account, used for preview purposes in the UI
int getEnchantPrice() const; int getEnchantPrice() const;
int getMaxEnchantValue() const; int getMaxEnchantValue() const;
int getGemCharge() const; int getGemCharge() const;

View file

@ -73,7 +73,7 @@ namespace MWMechanics
struct EffectSourceVisitor struct EffectSourceVisitor
{ {
virtual void visit (MWMechanics::EffectKey key, virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, int casterActorId, const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1) = 0; float magnitude, float remainingTime = -1, float totalTime = -1) = 0;
}; };

View file

@ -282,28 +282,13 @@ namespace MWMechanics
return Ogre::Math::ATan2(directionX,directionY).valueDegrees(); return Ogre::Math::ATan2(directionX,directionY).valueDegrees();
} }
bool PathFinder::checkWaypoint(float x, float y, float z) bool PathFinder::checkPathCompleted(float x, float y, float z, float tolerance)
{ {
if(mPath.empty()) if(mPath.empty())
return true; return true;
ESM::Pathgrid::Point nextPoint = *mPath.begin(); ESM::Pathgrid::Point nextPoint = *mPath.begin();
if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64) if(sqrDistanceZCorrected(nextPoint, x, y, z) < tolerance*tolerance)
{
mPath.pop_front();
if(mPath.empty()) mIsPathConstructed = false;
return true;
}
return false;
}
bool PathFinder::checkPathCompleted(float x, float y, float z)
{
if(mPath.empty())
return true;
ESM::Pathgrid::Point nextPoint = *mPath.begin();
if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64)
{ {
mPath.pop_front(); mPath.pop_front();
if(mPath.empty()) if(mPath.empty())

View file

@ -39,11 +39,8 @@ namespace MWMechanics
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, bool allowShortcuts = true); const MWWorld::CellStore* cell, bool allowShortcuts = true);
bool checkPathCompleted(float x, float y, float z); bool checkPathCompleted(float x, float y, float z, float tolerance=32.f);
///< \Returns true if the last point of the path has been reached. ///< \Returns true if we are within \a tolerance units of the last path point.
bool checkWaypoint(float x, float y, float z);
///< \Returns true if a way point was reached
float getZAngleToNext(float x, float y) const; float getZAngleToNext(float x, float y) const;

View file

@ -21,6 +21,7 @@
#include "magiceffects.hpp" #include "magiceffects.hpp"
#include "npcstats.hpp" #include "npcstats.hpp"
#include "summoning.hpp"
namespace namespace
{ {
@ -470,6 +471,20 @@ namespace MWMechanics
else else
applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude); applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude);
// Re-casting a summon effect will remove the creature from previous castings of that effect.
if (effectIt->mEffectID >= ESM::MagicEffect::SummonScamp
&& effectIt->mEffectID <= ESM::MagicEffect::SummonStormAtronach
&& !target.isEmpty() && target.getClass().isActor())
{
CreatureStats& targetStats = target.getClass().getCreatureStats(target);
std::map<CreatureStats::SummonKey, int>::iterator found = targetStats.getSummonedCreatureMap().find(std::make_pair(effectIt->mEffectID, mId));
if (found != targetStats.getSummonedCreatureMap().end())
{
cleanupSummonedCreature(targetStats, found->second);
targetStats.getSummonedCreatureMap().erase(found);
}
}
// HACK: Damage attribute/skill actually has a duration, even though the actual effect is instant and permanent. // HACK: Damage attribute/skill actually has a duration, even though the actual effect is instant and permanent.
// This was probably just done to have the effect visible in the magic menu for a while // This was probably just done to have the effect visible in the magic menu for a while
// to notify the player they've been damaged? // to notify the player they've been damaged?
@ -705,9 +720,7 @@ namespace MWMechanics
// Check if there's enough charge left // Check if there's enough charge left
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
{ {
const float enchantCost = enchantment->mData.mCost; const int castCost = getEffectiveEnchantmentCastCost(enchantment->mData.mCost, mCaster);
int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant);
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
if (item.getCellRef().getEnchantmentCharge() == -1) if (item.getCellRef().getEnchantmentCharge() == -1)
item.getCellRef().setEnchantmentCharge(enchantment->mData.mCharge); item.getCellRef().setEnchantmentCharge(enchantment->mData.mCharge);
@ -931,4 +944,16 @@ namespace MWMechanics
return true; return true;
} }
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<int>((result < 1) ? 1 : result);
}
} }

View file

@ -58,6 +58,8 @@ namespace MWMechanics
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
const ESM::Spell* spell = NULL, const MagicEffects* effects = NULL); const ESM::Spell* spell = NULL, const MagicEffects* effects = NULL);
int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);
class CastSpell class CastSpell
{ {
private: private:

View file

@ -249,7 +249,7 @@ namespace MWMechanics
random = it->second.at(i); random = it->second.at(i);
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random;
visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, -1, magnitude); visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, spell->mId, -1, magnitude);
} }
} }
} }

View file

@ -0,0 +1,195 @@
#include "summoning.hpp"
#include <OgreVector3.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwrender/animation.hpp"
#include "creaturestats.hpp"
#include "aifollow.hpp"
namespace MWMechanics
{
void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId)
{
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId);
if (!ptr.isEmpty())
{
// TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation
// plays though, which is a rather lame exploit in vanilla.
MWBase::Environment::get().getWorld()->deleteObject(ptr);
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_End");
if (fx)
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel,
"", Ogre::Vector3(ptr.getRefData().getPosition().pos));
}
else
{
// We didn't find the creature. It's probably in an inactive cell.
// Add to graveyard so we can delete it when the cell becomes active.
std::vector<int>& graveyard = casterStats.getSummonedCreatureGraveyard();
graveyard.push_back(creatureActorId);
}
}
UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor)
: mActor(actor)
{
}
void UpdateSummonedCreatures::visit(EffectKey key, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime)
{
if (key.mId >= ESM::MagicEffect::SummonScamp
&& key.mId <= ESM::MagicEffect::SummonStormAtronach && magnitude > 0)
{
mActiveEffects.insert(std::make_pair(key.mId, sourceId));
}
}
void UpdateSummonedCreatures::finish()
{
static std::map<int, std::string> 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";
}
MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor);
// Update summon effects
std::map<CreatureStats::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
for (std::map<CreatureStats::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
{
bool found = mActiveEffects.find(it->first) != mActiveEffects.end();
if (!found)
{
// Effect has ended
cleanupSummonedCreature(creatureStats, it->second);
creatureMap.erase(it++);
continue;
}
++it;
}
for (std::set<std::pair<int, std::string> >::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it)
{
bool found = creatureMap.find(std::make_pair(it->first, it->second)) != creatureMap.end();
if (!found)
{
ESM::Position ipos = mActor.getRefData().getPosition();
Ogre::Vector3 pos(ipos.pos);
Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z);
const float distance = 50;
pos = pos + distance*rot.yAxis();
ipos.pos[0] = pos.x;
ipos.pos[1] = pos.y;
ipos.pos[2] = pos.z;
ipos.rot[0] = 0;
ipos.rot[1] = 0;
ipos.rot[2] = 0;
const std::string& creatureGmst = summonMap[it->first];
std::string creatureID =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(creatureGmst)->getString();
if (!creatureID.empty())
{
MWWorld::CellStore* store = mActor.getCell();
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1);
ref.getPtr().getCellRef().setPosition(ipos);
MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr());
// Make the summoned creature follow its master and help in fights
AiFollow package(mActor.getCellRef().getRefId());
summonedCreatureStats.getAiSequence().stack(package, ref.getPtr());
int creatureActorId = summonedCreatureStats.getActorId();
MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos);
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed);
if (anim)
{
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_Start");
if (fx)
anim->addEffect("meshes\\" + fx->mModel, -1, false);
}
creatureMap.insert(std::make_pair(*it, creatureActorId));
}
}
}
for (std::map<CreatureStats::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
{
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead())
{
// Purge the magic effect so a new creature can be summoned if desired
creatureStats.getActiveSpells().purgeEffect(it->first.first, it->first.second);
if (mActor.getClass().hasInventoryStore(ptr))
mActor.getClass().getInventoryStore(mActor).purgeEffect(it->first.first, it->first.second);
cleanupSummonedCreature(creatureStats, it->second);
creatureMap.erase(it++);
}
else
++it;
}
std::vector<int>& graveyard = creatureStats.getSummonedCreatureGraveyard();
for (std::vector<int>::iterator it = graveyard.begin(); it != graveyard.end(); )
{
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it);
if (!ptr.isEmpty())
{
it = graveyard.erase(it);
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_End");
if (fx)
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel,
"", Ogre::Vector3(ptr.getRefData().getPosition().pos));
MWBase::Environment::get().getWorld()->deleteObject(ptr);
}
else
++it;
}
}
}

View file

@ -0,0 +1,35 @@
#ifndef OPENMW_MECHANICS_SUMMONING_H
#define OPENMW_MECHANICS_SUMMONING_H
#include <set>
#include "magiceffects.hpp"
#include "../mwworld/ptr.hpp"
namespace MWMechanics
{
class CreatureStats;
struct UpdateSummonedCreatures : public EffectSourceVisitor
{
UpdateSummonedCreatures(const MWWorld::Ptr& actor);
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1);
/// To call after all effect sources have been visited
void finish();
private:
MWWorld::Ptr mActor;
std::set<std::pair<int, std::string> > mActiveEffects;
};
void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId);
}
#endif

View file

@ -8,6 +8,7 @@
#include <OgreSceneManager.h> #include <OgreSceneManager.h>
#include <OgreHardwareVertexBuffer.h> #include <OgreHardwareVertexBuffer.h>
#include <OgreHighLevelGpuProgramManager.h> #include <OgreHighLevelGpuProgramManager.h>
#include <OgreParticle.h>
#include <OgreParticleSystem.h> #include <OgreParticleSystem.h>
#include <OgreEntity.h> #include <OgreEntity.h>
#include <OgreSubEntity.h> #include <OgreSubEntity.h>
@ -431,7 +432,10 @@ void SkyManager::updateRain(float dt)
Ogre::Vector3 pos = it->first->getPosition(); Ogre::Vector3 pos = it->first->getPosition();
pos.z -= mRainSpeed * dt; pos.z -= mRainSpeed * dt;
it->first->setPosition(pos); it->first->setPosition(pos);
if (pos.z < -minHeight) if (pos.z < -minHeight
// Here we might want to add a "splash" effect later
|| MWBase::Environment::get().getWorld()->isUnderwater(
MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), it->first->_getDerivedPosition()))
{ {
it->second.setNull(); it->second.setNull();
mSceneMgr->destroySceneNode(it->first); mSceneMgr->destroySceneNode(it->first);
@ -458,6 +462,12 @@ void SkyManager::updateRain(float dt)
// Create a separate node to control the offset, since a node with setInheritOrientation(false) will still // Create a separate node to control the offset, since a node with setInheritOrientation(false) will still
// consider the orientation of the parent node for its position, just not for its orientation // consider the orientation of the parent node for its position, just not for its orientation
float startHeight = 700; float startHeight = 700;
Ogre::Vector3 worldPos = mParticleNode->_getDerivedPosition();
worldPos += Ogre::Vector3(xOffs, yOffs, startHeight);
if (MWBase::Environment::get().getWorld()->isUnderwater(
MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), worldPos))
return;
Ogre::SceneNode* offsetNode = mParticleNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight)); Ogre::SceneNode* offsetNode = mParticleNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight));
// Spawn a new rain object for each instance. // Spawn a new rain object for each instance.
@ -490,6 +500,30 @@ void SkyManager::update(float duration)
for (unsigned int i=0; i<mParticle->mControllers.size(); ++i) for (unsigned int i=0; i<mParticle->mControllers.size(); ++i)
mParticle->mControllers[i].update(); mParticle->mControllers[i].update();
for (unsigned int i=0; i<mParticle->mParticles.size(); ++i)
{
Ogre::ParticleSystem* psys = mParticle->mParticles[i];
Ogre::ParticleIterator pi = psys->_getIterator();
while (!pi.end())
{
Ogre::Particle *p = pi.getNext();
#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
Ogre::Vector3 pos = p->mPosition;
Ogre::Real& timeToLive = p->mTimeToLive;
#else
Ogre::Vector3 pos = p->position;
Ogre::Real& timeToLive = p->timeToLive;
#endif
if (psys->getKeepParticlesInLocalSpace() && psys->getParentNode())
pos = psys->getParentNode()->convertLocalToWorldPosition(pos);
if (MWBase::Environment::get().getWorld()->isUnderwater(
MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), pos))
timeToLive = 0;
}
}
if (mIsStorm) if (mIsStorm)
mParticleNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection)); mParticleNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection));
} }

View file

@ -388,6 +388,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
case ESM::REC_ENAB: case ESM::REC_ENAB:
case ESM::REC_LEVC: case ESM::REC_LEVC:
case ESM::REC_LEVI: case ESM::REC_LEVI:
case ESM::REC_CAM_:
MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap);
break; break;
@ -439,6 +440,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
// Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again
MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false); MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false);
// Vanilla MW will restart startup scripts when a save game is loaded. This is unintuive,
// but some mods may be using it as a reload detector.
MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup();
// Do not trigger erroneous cellChanged events // Do not trigger erroneous cellChanged events

View file

@ -583,7 +583,8 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito
const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i]; const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i];
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom;
magnitude *= params.mMultiplier; magnitude *= params.mMultiplier;
visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), -1, magnitude); if (magnitude > 0)
visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude);
++i; ++i;
} }
@ -639,6 +640,52 @@ void MWWorld::InventoryStore::purgeEffect(short effectId)
mMagicEffects.remove(MWMechanics::EffectKey(effectId)); mMagicEffects.remove(MWMechanics::EffectKey(effectId));
} }
void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId)
{
TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId);
if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end())
return;
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
{
if (*iter==end())
continue;
if ((*iter)->getClass().getId(**iter) != sourceId)
continue;
std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter);
if (!enchantmentId.empty())
{
const ESM::Enchantment& enchantment =
*MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
continue;
std::vector<EffectParams>& params = effectMagnitudeIt->second;
int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i)
{
if (effectIt->mEffectID != effectId)
continue;
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom;
magnitude *= params[i].mMultiplier;
if (magnitude)
mMagicEffects.add (*effectIt, -magnitude);
params[i].mMultiplier = 0;
break;
}
}
}
}
void MWWorld::InventoryStore::clear() void MWWorld::InventoryStore::clear()
{ {
mSlots.clear(); mSlots.clear();

View file

@ -203,6 +203,9 @@ namespace MWWorld
void purgeEffect (short effectId); void purgeEffect (short effectId);
///< Remove a magic effect ///< Remove a magic effect
void purgeEffect (short effectId, const std::string& sourceId);
///< Remove a magic effect
virtual void clear(); virtual void clear();
///< Empty container. ///< Empty container.

View file

@ -296,15 +296,17 @@ namespace MWWorld
std::string loadingExteriorText = "#{sLoadingMessage3}"; std::string loadingExteriorText = "#{sLoadingMessage3}";
loadingListener->setLabel(loadingExteriorText); loadingListener->setLabel(loadingExteriorText);
const int halfGridSize = Settings::Manager::getInt("exterior grid size", "Cells")/2;
CellStoreCollection::iterator active = mActiveCells.begin(); CellStoreCollection::iterator active = mActiveCells.begin();
while (active!=mActiveCells.end()) while (active!=mActiveCells.end())
{ {
if ((*active)->getCell()->isExterior()) if ((*active)->getCell()->isExterior())
{ {
if (std::abs (X-(*active)->getCell()->getGridX())<=1 && if (std::abs (X-(*active)->getCell()->getGridX())<=halfGridSize &&
std::abs (Y-(*active)->getCell()->getGridY())<=1) std::abs (Y-(*active)->getCell()->getGridY())<=halfGridSize)
{ {
// keep cells within the new 3x3 grid // keep cells within the new grid
++active; ++active;
continue; continue;
} }
@ -314,9 +316,9 @@ namespace MWWorld
int refsToLoad = 0; int refsToLoad = 0;
// get the number of refs to load // get the number of refs to load
for (int x=X-1; x<=X+1; ++x) for (int x=X-halfGridSize; x<=X+halfGridSize; ++x)
{ {
for (int y=Y-1; y<=Y+1; ++y) for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y)
{ {
CellStoreCollection::iterator iter = mActiveCells.begin(); CellStoreCollection::iterator iter = mActiveCells.begin();
@ -339,9 +341,9 @@ namespace MWWorld
loadingListener->setProgressRange(refsToLoad); loadingListener->setProgressRange(refsToLoad);
// Load cells // Load cells
for (int x=X-1; x<=X+1; ++x) for (int x=X-halfGridSize; x<=X+halfGridSize; ++x)
{ {
for (int y=Y-1; y<=Y+1; ++y) for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y)
{ {
CellStoreCollection::iterator iter = mActiveCells.begin(); CellStoreCollection::iterator iter = mActiveCells.begin();

View file

@ -201,6 +201,7 @@ namespace MWWorld
setupPlayer(); setupPlayer();
renderPlayer(); renderPlayer();
mRendering->resetCamera();
MWBase::Environment::get().getWindowManager()->updatePlayer(); MWBase::Environment::get().getWindowManager()->updatePlayer();
@ -304,7 +305,8 @@ namespace MWWorld
+1 // player record +1 // player record
+1 // weather record +1 // weather record
+1 // actorId counter +1 // actorId counter
+1; // levitation/teleport enabled state +1 // levitation/teleport enabled state
+1; // camera
} }
void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
@ -333,6 +335,11 @@ namespace MWWorld
writer.writeHNT("LEVT", mLevitationEnabled); writer.writeHNT("LEVT", mLevitationEnabled);
writer.endRecord(ESM::REC_ENAB); writer.endRecord(ESM::REC_ENAB);
progress.increaseProgress(); progress.increaseProgress();
writer.startRecord(ESM::REC_CAM_);
writer.writeHNT("FIRS", isFirstPerson());
writer.endRecord(ESM::REC_CAM_);
progress.increaseProgress();
} }
void World::readRecord (ESM::ESMReader& reader, int32_t type, void World::readRecord (ESM::ESMReader& reader, int32_t type,
@ -347,6 +354,12 @@ namespace MWWorld
reader.getHNT(mTeleportEnabled, "TELE"); reader.getHNT(mTeleportEnabled, "TELE");
reader.getHNT(mLevitationEnabled, "LEVT"); reader.getHNT(mLevitationEnabled, "LEVT");
return; return;
case ESM::REC_CAM_:
bool firstperson;
reader.getHNT(firstperson, "FIRS");
if (firstperson != isFirstPerson())
togglePOV();
break;
default: default:
if (!mStore.readRecord (reader, type) && if (!mStore.readRecord (reader, type) &&
!mGlobalVariables.readRecord (reader, type) && !mGlobalVariables.readRecord (reader, type) &&
@ -2073,7 +2086,6 @@ namespace MWWorld
MWBase::Environment::get().getMechanicsManager()->add(mPlayer->getPlayer()); MWBase::Environment::get().getMechanicsManager()->add(mPlayer->getPlayer());
mPhysics->addActor(mPlayer->getPlayer()); mPhysics->addActor(mPlayer->getPlayer());
mRendering->resetCamera();
} }
int World::canRest () int World::canRest ()

View file

@ -94,9 +94,10 @@ void ESM::CreatureStats::load (ESMReader &esm)
{ {
int magicEffect; int magicEffect;
esm.getHT(magicEffect); esm.getHT(magicEffect);
std::string source = esm.getHNOString("SOUR");
int actorId; int actorId;
esm.getHNT (actorId, "ACID"); esm.getHNT (actorId, "ACID");
mSummonedCreatureMap[magicEffect] = actorId; mSummonedCreatureMap[std::make_pair(magicEffect, source)] = actorId;
} }
while (esm.isNextSub("GRAV")) while (esm.isNextSub("GRAV"))
@ -204,9 +205,10 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
mAiSequence.save(esm); mAiSequence.save(esm);
mMagicEffects.save(esm); mMagicEffects.save(esm);
for (std::map<int, int>::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it) for (std::map<std::pair<int, std::string>, int>::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it)
{ {
esm.writeHNT ("SUMM", it->first); esm.writeHNT ("SUMM", it->first.first);
esm.writeHNString ("SOUR", it->first.second);
esm.writeHNT ("ACID", it->second); esm.writeHNT ("ACID", it->second);
} }

View file

@ -32,7 +32,7 @@ namespace ESM
bool mHasAiSettings; bool mHasAiSettings;
StatState<int> mAiSettings[4]; StatState<int> mAiSettings[4];
std::map<int, int> mSummonedCreatureMap; std::map<std::pair<int, std::string>, int> mSummonedCreatureMap;
std::vector<int> mSummonGraveyard; std::vector<int> mSummonGraveyard;
ESM::TimeStamp mTradeTime; ESM::TimeStamp mTradeTime;

View file

@ -114,6 +114,7 @@ enum RecNameInts
REC_DCOU = FourCC<'D','C','O','U'>::value, REC_DCOU = FourCC<'D','C','O','U'>::value,
REC_MARK = FourCC<'M','A','R','K'>::value, REC_MARK = FourCC<'M','A','R','K'>::value,
REC_ENAB = FourCC<'E','N','A','B'>::value, REC_ENAB = FourCC<'E','N','A','B'>::value,
REC_CAM_ = FourCC<'C','A','M','_'>::value,
// format 1 // format 1
REC_FILT = 0x544C4946, REC_FILT = 0x544C4946,

View file

@ -123,6 +123,9 @@ local map resolution = 256
local map widget size = 512 local map widget size = 512
local map hud widget size = 256 local map hud widget size = 256
[Cells]
exterior grid size = 3
[Viewing distance] [Viewing distance]
# Limit the rendering distance of small objects # Limit the rendering distance of small objects
limit small object distance = false limit small object distance = false