Merge branch 'effects'

This commit is contained in:
Marc Zinnschlag 2012-05-19 17:41:47 +02:00
commit 420cd53be0
18 changed files with 548 additions and 51 deletions

View file

@ -48,6 +48,7 @@ add_openmw_dir (mwworld
refdata world physicssystem scene globals class action nullaction actionteleport
containerstore actiontalk actiontake manualref player cellfunctors
cells localscripts customdata weather inventorystore ptr actionread
timestamp
)
add_openmw_dir (mwclass
@ -57,6 +58,7 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics
mechanicsmanager stat creaturestats magiceffects movement actors drawstate spells
activespells
)
add_openmw_dir (mwbase

View file

@ -0,0 +1,162 @@
#include "activespells.hpp"
#include <cstdlib>
#include "../mwbase/environment.hpp"
#include "../mwworld/world.hpp"
namespace MWMechanics
{
void ActiveSpells::update() const
{
bool rebuild = false;
MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
if (mLastUpdate!=now)
{
TContainer::iterator iter (mSpells.begin());
while (iter!=mSpells.end())
if (!timeToExpire (iter))
{
mSpells.erase (iter++);
rebuild = true;
}
else
++iter;
mLastUpdate = now;
}
if (mSpellsChanged)
{
mSpellsChanged = false;
rebuild = true;
}
if (rebuild)
{
mEffects = MagicEffects();
for (TIterator iter (begin()); iter!=end(); ++iter)
{
const ESM::Spell& spell =
*MWBase::Environment::get().getWorld()->getStore().spells.find (iter->first);
const MWWorld::TimeStamp& start = iter->second.first;
float magnitude = iter->second.second;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (spell.effects.list.begin());
iter!=spell.effects.list.end(); ++iter)
{
if (iter->duration)
{
MWWorld::TimeStamp end = start;
end += static_cast<double> (iter->duration)*
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
if (end>now)
{
EffectParam param;
param.mMagnitude = static_cast<int> (
(iter->magnMax-iter->magnMin+1)*magnitude + iter->magnMin);
mEffects.add (*iter, param);
}
}
}
}
}
}
ActiveSpells::ActiveSpells()
: mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
{}
void ActiveSpells::addSpell (const std::string& id)
{
const ESM::Spell& spell = *MWBase::Environment::get().getWorld()->getStore().spells.find (id);
bool found = false;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (spell.effects.list.begin());
iter!=spell.effects.list.end(); ++iter)
{
if (iter->duration)
{
found = true;
break;
}
}
if (!found)
return;
TContainer::iterator iter = mSpells.find (id);
float random = static_cast<float> (std::rand()) / RAND_MAX;
if (iter==mSpells.end())
mSpells.insert (std::make_pair (id,
std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random)));
else
iter->second = std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random);
mSpellsChanged = true;
}
void ActiveSpells::removeSpell (const std::string& id)
{
TContainer::iterator iter = mSpells.find (id);
if (iter!=mSpells.end())
{
mSpells.erase (iter);
mSpellsChanged = true;
}
}
const MagicEffects& ActiveSpells::getMagicEffects() const
{
update();
return mEffects;
}
ActiveSpells::TIterator ActiveSpells::begin() const
{
update();
return mSpells.begin();
}
ActiveSpells::TIterator ActiveSpells::end() const
{
update();
return mSpells.end();
}
double ActiveSpells::timeToExpire (const TIterator& iterator) const
{
const ESM::Spell& spell =
*MWBase::Environment::get().getWorld()->getStore().spells.find (iterator->first);
int duration = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (spell.effects.list.begin());
iter!=spell.effects.list.end(); ++iter)
{
if (iter->duration>duration)
duration = iter->duration;
}
double scaledDuration = duration *
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.first;
if (usedUp>=scaledDuration)
return 0;
return scaledDuration-usedUp;
}
}

View file

@ -0,0 +1,58 @@
#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H
#define GAME_MWMECHANICS_ACTIVESPELLS_H
#include <map>
#include <vector>
#include <string>
#include "../mwworld/timestamp.hpp"
#include "magiceffects.hpp"
namespace ESM
{
struct Spell;
}
namespace MWMechanics
{
/// \brief Lasting spell effects
class ActiveSpells
{
public:
typedef std::map<std::string, std::pair<MWWorld::TimeStamp, float> > TContainer;
typedef TContainer::const_iterator TIterator;
private:
mutable TContainer mSpells; // spellId, (time of casting, relative magnitude)
mutable MagicEffects mEffects;
mutable bool mSpellsChanged;
mutable MWWorld::TimeStamp mLastUpdate;
void update() const;
public:
ActiveSpells();
void addSpell (const std::string& id);
///< Overwrites an existing spell with the same ID. If the spell does not have any
/// non-instant effects, it is ignored.
void removeSpell (const std::string& id);
const MagicEffects& getMagicEffects() const;
TIterator begin() const;
TIterator end() const;
double timeToExpire (const TIterator& iterator) const;
///< Returns time (in in-game hours) until the spell pointed to by \a iterator
/// expires.
};
}
#endif

View file

@ -8,11 +8,30 @@
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "creaturestats.hpp"
namespace MWMechanics
{
void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
{
// magic effects
adjustMagicEffects (ptr);
CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
// calculate dynamic stats
int strength = creatureStats.mAttributes[0].getBase();
int intelligence = creatureStats.mAttributes[1].getBase();
int willpower = creatureStats.mAttributes[2].getBase();
int agility = creatureStats.mAttributes[3].getBase();
int endurance = creatureStats.mAttributes[5].getBase();
double magickaFactor = creatureStats.mMagicEffects.get (EffectKey (84)).mMagnitude*0.1 + 0.5;
creatureStats.mDynamic[0].setBase (static_cast<int> (0.5 * (strength + endurance)));
creatureStats.mDynamic[1].setBase (static_cast<int> (intelligence +
magickaFactor * intelligence));
creatureStats.mDynamic[2].setBase (strength+willpower+agility+endurance);
}
void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused)
@ -22,6 +41,27 @@ namespace MWMechanics
MWWorld::Class::get (ptr).getNpcStats (ptr));
}
void Actors::adjustMagicEffects (const MWWorld::Ptr& creature)
{
CreatureStats& creatureStats = MWWorld::Class::get (creature).getCreatureStats (creature);
MagicEffects now = creatureStats.mSpells.getMagicEffects();
if (creature.getTypeName()==typeid (ESM::NPC).name())
{
MWWorld::InventoryStore& store = MWWorld::Class::get (creature).getInventoryStore (creature);
now += store.getMagicEffects();
}
now += creatureStats.mActiveSpells.getMagicEffects();
MagicEffects diff = MagicEffects::diff (creatureStats.mMagicEffects, now);
creatureStats.mMagicEffects = now;
// TODO apply diff to other stats
}
Actors::Actors() : mDuration (0) {}
void Actors::addActor (const MWWorld::Ptr& ptr)

View file

@ -19,10 +19,10 @@ namespace MWMechanics
std::set<MWWorld::Ptr> mActors;
float mDuration;
void updateActor (const MWWorld::Ptr& ptr, float duration);
void updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused);
void adjustMagicEffects (const MWWorld::Ptr& creature);
public:
Actors();
@ -39,6 +39,11 @@ namespace MWMechanics
void update (std::vector<std::pair<std::string, Ogre::Vector3> >& movement,
float duration, bool paused);
///< Update actor stats and store desired velocity vectors in \a movement
void updateActor (const MWWorld::Ptr& ptr, float duration);
///< This function is normally called automatically during the update process, but it can
/// also be called explicitly at any time to force an update.
};
}

View file

@ -7,6 +7,7 @@
#include "stat.hpp"
#include "magiceffects.hpp"
#include "spells.hpp"
#include "activespells.hpp"
namespace MWMechanics
{
@ -16,6 +17,7 @@ namespace MWMechanics
DynamicStat<int> mDynamic[3]; // health, magicka, fatigue
int mLevel;
Spells mSpells;
ActiveSpells mActiveSpells;
MagicEffects mMagicEffects;
};
}

View file

@ -1,6 +1,8 @@
#include "magiceffects.hpp"
#include <cstdlib>
#include <stdexcept>
#include <components/esm/defs.hpp>
@ -66,6 +68,46 @@ namespace MWMechanics
}
}
void MagicEffects::add (const ESM::EffectList& list)
{
for (std::vector<ESM::ENAMstruct>::const_iterator iter (list.list.begin()); iter!=list.list.end();
++iter)
{
EffectParam param;
if (iter->magnMin>=iter->magnMax)
param.mMagnitude = iter->magnMin;
else
param.mMagnitude = static_cast<int> (
(iter->magnMax-iter->magnMin+1)*
(static_cast<float> (std::rand()) / RAND_MAX) + iter->magnMin);
add (*iter, param);
}
}
MagicEffects& MagicEffects::operator+= (const MagicEffects& effects)
{
if (this==&effects)
{
MagicEffects temp (effects);
*this += temp;
return *this;
}
for (Collection::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter)
{
Collection::iterator result = mCollection.find (iter->first);
if (result!=mCollection.end())
result->second += iter->second;
else
mCollection.insert (*iter);
}
return *this;
}
EffectParam MagicEffects::get (const EffectKey& key) const
{
Collection::const_iterator iter = mCollection.find (key);
@ -85,11 +127,11 @@ namespace MWMechanics
MagicEffects result;
// adding/changing
for (Collection::const_iterator iter (now.Begin()); iter!=now.End(); ++iter)
for (Collection::const_iterator iter (now.begin()); iter!=now.end(); ++iter)
{
Collection::const_iterator other = prev.mCollection.find (iter->first);
if (other==prev.End())
if (other==prev.end())
{
// adding
result.add (iter->first, iter->second);
@ -102,17 +144,16 @@ namespace MWMechanics
}
// removing
for (Collection::const_iterator iter (prev.Begin()); iter!=prev.End(); ++iter)
for (Collection::const_iterator iter (prev.begin()); iter!=prev.end(); ++iter)
{
Collection::const_iterator other = now.mCollection.find (iter->first);
if (other==prev.End())
if (other==prev.end())
{
result.add (iter->first, EffectParam() - iter->second);
}
}
return result;
}
}

View file

@ -6,6 +6,7 @@
namespace ESM
{
struct ENAMstruct;
struct EffectList;
}
namespace MWMechanics
@ -60,12 +61,16 @@ namespace MWMechanics
public:
Collection::const_iterator Begin() const { return mCollection.begin(); }
Collection::const_iterator begin() const { return mCollection.begin(); }
Collection::const_iterator End() const { return mCollection.end(); }
Collection::const_iterator end() const { return mCollection.end(); }
void add (const EffectKey& key, const EffectParam& param);
void add (const ESM::EffectList& list);
MagicEffects& operator+= (const MagicEffects& effects);
EffectParam get (const EffectKey& key) const;
///< This function can safely be used for keys that are not present.

View file

@ -139,42 +139,13 @@ namespace MWMechanics
}
}
// magic effects
adjustMagicEffects (ptr);
// calculate dynamic stats
int strength = creatureStats.mAttributes[0].getBase();
int intelligence = creatureStats.mAttributes[1].getBase();
int willpower = creatureStats.mAttributes[2].getBase();
int agility = creatureStats.mAttributes[3].getBase();
int endurance = creatureStats.mAttributes[5].getBase();
double magickaFactor = creatureStats.mMagicEffects.get (EffectKey (84)).mMagnitude*0.1 + 0.5;
creatureStats.mDynamic[0].setBase (static_cast<int> (0.5 * (strength + endurance)));
creatureStats.mDynamic[1].setBase (static_cast<int> (intelligence +
magickaFactor * intelligence));
creatureStats.mDynamic[2].setBase (strength+willpower+agility+endurance);
// forced update and current value adjustments
mActors.updateActor (ptr, 0);
for (int i=0; i<3; ++i)
creatureStats.mDynamic[i].setCurrent (creatureStats.mDynamic[i].getModified());
}
void MechanicsManager::adjustMagicEffects (MWWorld::Ptr& creature)
{
MWMechanics::CreatureStats& creatureStats =
MWWorld::Class::get (creature).getCreatureStats (creature);
MagicEffects now = creatureStats.mSpells.getMagicEffects();
/// \todo add effects from active spells and equipment
MagicEffects diff = MagicEffects::diff (creatureStats.mMagicEffects, now);
creatureStats.mMagicEffects = now;
// TODO apply diff to other stats
}
MechanicsManager::MechanicsManager()
: mUpdatePlayer (true), mClassSelected (false),

View file

@ -31,8 +31,6 @@ namespace MWMechanics
///< build player according to stored class/race/birthsign information. Will
/// default to the values of the ESM::NPC object, if no explicit information is given.
void adjustMagicEffects (MWWorld::Ptr& creature);
public:
MechanicsManager ();

View file

@ -13,13 +13,7 @@ namespace MWMechanics
{
void Spells::addSpell (const ESM::Spell *spell, MagicEffects& effects) const
{
for (std::vector<ESM::ENAMstruct>::const_iterator iter = spell->effects.list.begin();
iter!=spell->effects.list.end(); ++iter)
{
EffectParam param;
param.mMagnitude = iter->magnMax; /// \todo calculate magnitude
effects.add (EffectKey (*iter), param);
}
effects.add (spell->effects);
}
Spells::TIterator Spells::begin() const

View file

@ -90,7 +90,7 @@ namespace MWWorld
void clear();
///< Empty container.
void flagAsModified();
virtual void flagAsModified();
///< \attention This function is internal to the world model and should not be called from
/// outside.

View file

@ -4,6 +4,12 @@
#include <iterator>
#include <algorithm>
#include <components/esm/loadench.hpp>
#include "../mwbase/environment.hpp"
#include "../mwworld/world.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "class.hpp"
@ -32,7 +38,7 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots)
slots.push_back (end());
}
MWWorld::InventoryStore::InventoryStore()
MWWorld::InventoryStore::InventoryStore() : mMagicEffectsUpToDate (false)
{
initSlots (mSlots);
}
@ -40,11 +46,15 @@ MWWorld::InventoryStore::InventoryStore()
MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
: ContainerStore (store)
{
mMagicEffects = store.mMagicEffects;
mMagicEffectsUpToDate = store.mMagicEffectsUpToDate;
copySlots (store);
}
MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store)
{
mMagicEffects = store.mMagicEffects;
mMagicEffectsUpToDate = store.mMagicEffectsUpToDate;
ContainerStore::operator= (store);
mSlots.clear();
copySlots (store);
@ -201,6 +211,39 @@ void MWWorld::InventoryStore::autoEquip (const MWMechanics::NpcStats& stats)
}
}
const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects()
{
if (!mMagicEffectsUpToDate)
{
mMagicEffects = MWMechanics::MagicEffects();
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
if (*iter!=end())
{
std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter);
if (!enchantmentId.empty())
{
const ESM::Enchantment& enchantment =
*MWBase::Environment::get().getWorld()->getStore().enchants.find (enchantmentId);
if (enchantment.data.type==ESM::Enchantment::ConstantEffect)
mMagicEffects.add (enchantment.effects);
}
}
mMagicEffectsUpToDate = true;
}
return mMagicEffects;
}
void MWWorld::InventoryStore::flagAsModified()
{
ContainerStore::flagAsModified();
mMagicEffectsUpToDate = false;
}
bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2)
{
bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2);

View file

@ -3,6 +3,8 @@
#include "containerstore.hpp"
#include "../mwmechanics/magiceffects.hpp"
namespace MWMechanics
{
struct NpcStats;
@ -41,6 +43,9 @@ namespace MWWorld
private:
mutable MWMechanics::MagicEffects mMagicEffects;
mutable bool mMagicEffectsUpToDate;
typedef std::vector<ContainerStoreIterator> TSlots;
mutable TSlots mSlots;
@ -65,6 +70,15 @@ namespace MWWorld
void autoEquip (const MWMechanics::NpcStats& stats);
///< Auto equip items according to stats and item value.
const MWMechanics::MagicEffects& getMagicEffects();
///< Return magic effects from worn items.
///
/// \todo make this const again, after the constness of Ptrs and iterators has been addressed.
virtual void flagAsModified();
///< \attention This function is internal to the world model and should not be called from
/// outside.
protected:
virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2);

View file

@ -0,0 +1,108 @@
#include "timestamp.hpp"
#include <cmath>
#include <stdexcept>
namespace MWWorld
{
TimeStamp::TimeStamp (float hour, int day)
: mHour (hour), mDay (day)
{
if (hour<0 || hour>=24 || day<0)
throw std::runtime_error ("invalid time stamp");
}
float TimeStamp::getHour() const
{
return mHour;
}
int TimeStamp::getDay() const
{
return mDay;
}
TimeStamp& TimeStamp::operator+= (double hours)
{
if (hours<0)
throw std::runtime_error ("can't move time stamp backwards in time");
hours += mHour;
mHour = static_cast<float> (std::fmod (hours, 24));
mDay += hours / 24;
return *this;
}
bool operator== (const TimeStamp& left, const TimeStamp& right)
{
return left.getHour()==right.getHour() && left.getDay()==right.getDay();
}
bool operator!= (const TimeStamp& left, const TimeStamp& right)
{
return !(left==right);
}
bool operator< (const TimeStamp& left, const TimeStamp& right)
{
if (left.getDay()<right.getDay())
return true;
if (left.getDay()>right.getDay())
return false;
return left.getHour()<right.getHour();
}
bool operator<= (const TimeStamp& left, const TimeStamp& right)
{
return left<right || left==right;
}
bool operator> (const TimeStamp& left, const TimeStamp& right)
{
return !(left<=right);
}
bool operator>= (const TimeStamp& left, const TimeStamp& right)
{
return !(left<right);
}
TimeStamp operator+ (const TimeStamp& stamp, double hours)
{
return TimeStamp (stamp) + hours;
}
TimeStamp operator+ (double hours, const TimeStamp& stamp)
{
return TimeStamp (stamp) + hours;
}
double operator- (const TimeStamp& left, const TimeStamp& right)
{
if (left<right)
return -(right-left);
int days = left.getDay() - right.getDay();
double hours = 0;
if (left.getHour()<right.getHour())
{
hours = 24-right.getHour()+left.getHour();
++days;
}
else
{
hours = left.getHour()-right.getHour();
}
return hours + 24*days;
}
}

View file

@ -0,0 +1,44 @@
#ifndef GAME_MWWORLD_TIMESTAMP_H
#define GAME_MWWORLD_TIMESTAMP_H
namespace MWWorld
{
/// \brief In-game time stamp
///
/// This class is based on the global variables GameHour and DaysPassed.
class TimeStamp
{
float mHour;
int mDay;
public:
explicit TimeStamp (float hour = 0, int day = 0);
///< \oaram hour [0, 23)
/// \param day >=0
float getHour() const;
int getDay() const;
TimeStamp& operator+= (double hours);
///< \param hours >=0
};
bool operator== (const TimeStamp& left, const TimeStamp& right);
bool operator!= (const TimeStamp& left, const TimeStamp& right);
bool operator< (const TimeStamp& left, const TimeStamp& right);
bool operator<= (const TimeStamp& left, const TimeStamp& right);
bool operator> (const TimeStamp& left, const TimeStamp& right);
bool operator>= (const TimeStamp& left, const TimeStamp& right);
TimeStamp operator+ (const TimeStamp& stamp, double hours);
TimeStamp operator+ (double hours, const TimeStamp& stamp);
double operator- (const TimeStamp& left, const TimeStamp& right);
///< Returns the difference between \a left and \a right in in-game hours.
}
#endif

View file

@ -471,6 +471,12 @@ namespace MWWorld
mRendering->skySetDate (mGlobalVariables->getInt ("day"), month);
}
TimeStamp World::getTimeStamp() const
{
return TimeStamp (mGlobalVariables->getFloat ("gamehour"),
mGlobalVariables->getInt ("dayspassed"));
}
bool World::toggleSky()
{
if (mSky)

View file

@ -18,6 +18,7 @@
#include "physicssystem.hpp"
#include "cells.hpp"
#include "localscripts.hpp"
#include "timestamp.hpp"
#include <openengine/bullet/physic.hpp>
#include <openengine/ogre/fader.hpp>
@ -185,6 +186,9 @@ namespace MWWorld
void setDay (int day);
///< Set in-game time day.
TimeStamp getTimeStamp() const;
///< Return current in-game time stamp.
bool toggleSky();
///< \return Resulting mode