forked from teamnwah/openmw-tes3coop
Projectile models are now spawned (no movement or impact yet). Refactored trap activation to apply range types properly. Handle ContinuousVFX for magic effects (note they aren't stopped yet when the effect ends)
This commit is contained in:
parent
0627e23d3c
commit
9b0e82a37f
16 changed files with 242 additions and 35 deletions
|
@ -58,7 +58,7 @@ add_openmw_dir (mwworld
|
|||
cells localscripts customdata weather inventorystore ptr actionopen actionread
|
||||
actionequip timestamp actionalchemy cellstore actionapply actioneat
|
||||
esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
|
||||
contentloader esmloader omwloader
|
||||
contentloader esmloader omwloader actiontrap
|
||||
)
|
||||
|
||||
add_openmw_dir (mwclass
|
||||
|
|
|
@ -415,6 +415,9 @@ namespace MWBase
|
|||
virtual void castSpell (const MWWorld::Ptr& actor) = 0;
|
||||
|
||||
virtual void updateAnimParts(const MWWorld::Ptr& ptr) = 0;
|
||||
|
||||
virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects,
|
||||
const MWWorld::Ptr& actor, const std::string& sourceName) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/customdata.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/actionapply.hpp"
|
||||
#include "../mwworld/actionopen.hpp"
|
||||
#include "../mwworld/actiontrap.hpp"
|
||||
#include "../mwworld/physicssystem.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
@ -147,11 +147,9 @@ namespace MWClass
|
|||
}
|
||||
else
|
||||
{
|
||||
// Trap activation goes here
|
||||
std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl;
|
||||
boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap));
|
||||
// Activate trap
|
||||
boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr));
|
||||
action->setSound(trapActivationSound);
|
||||
ptr.getCellRef().mTrap = "";
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/nullaction.hpp"
|
||||
#include "../mwworld/failedaction.hpp"
|
||||
#include "../mwworld/actionapply.hpp"
|
||||
#include "../mwworld/actionteleport.hpp"
|
||||
#include "../mwworld/actiondoor.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/physicssystem.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/actiontrap.hpp"
|
||||
|
||||
#include "../mwgui/tooltips.hpp"
|
||||
|
||||
|
@ -109,12 +109,8 @@ namespace MWClass
|
|||
if(!ptr.getCellRef().mTrap.empty())
|
||||
{
|
||||
// Trap activation
|
||||
std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl;
|
||||
|
||||
boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap));
|
||||
boost::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr));
|
||||
action->setSound(trapActivationSound);
|
||||
ptr.getCellRef().mTrap = "";
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
|
|
|
@ -467,9 +467,12 @@ namespace MWClass
|
|||
else
|
||||
{
|
||||
weapon.getCellRef().mEnchantmentCharge -= castCost;
|
||||
// Touch
|
||||
othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ESM::RT_Touch, weapon.getClass().getName(weapon));
|
||||
// Self
|
||||
getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ESM::RT_Self, weapon.getClass().getName(weapon));
|
||||
// TODO: RT_Target
|
||||
// Target
|
||||
MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ namespace MWMechanics
|
|||
if (!timeToExpire (iter))
|
||||
{
|
||||
mSpells.erase (iter++);
|
||||
//onSpellExpired
|
||||
rebuild = true;
|
||||
}
|
||||
else
|
||||
|
@ -227,6 +228,8 @@ namespace MWMechanics
|
|||
if (iter->mRange != range)
|
||||
continue;
|
||||
|
||||
// TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
|
||||
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
iter->mEffectID);
|
||||
|
@ -248,7 +251,8 @@ namespace MWMechanics
|
|||
if (!magicEffect->mHit.empty())
|
||||
{
|
||||
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
|
||||
MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, "");
|
||||
bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
|
||||
MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, loop, "");
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
|
|
@ -513,7 +513,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
|
|||
effect = store.get<ESM::MagicEffect>().find(effectentry.mEffectID);
|
||||
|
||||
const ESM::Static* castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
||||
mAnimation->addEffect("meshes\\" + castStatic->mModel, "");
|
||||
mAnimation->addEffect("meshes\\" + castStatic->mModel);
|
||||
|
||||
switch(effectentry.mRange)
|
||||
{
|
||||
|
|
|
@ -16,6 +16,8 @@ namespace MWMechanics
|
|||
int mId;
|
||||
int mArg; // skill or ability
|
||||
|
||||
// TODO: Add caster here for Absorb effects?
|
||||
|
||||
EffectKey();
|
||||
|
||||
EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {}
|
||||
|
|
|
@ -87,8 +87,8 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node)
|
|||
|
||||
Animation::~Animation()
|
||||
{
|
||||
for (std::vector<NifOgre::ObjectList>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
||||
destroyObjectList(mInsert->getCreator(), *it);
|
||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
||||
destroyObjectList(mInsert->getCreator(), it->mObjects);
|
||||
|
||||
mAnimSources.clear();
|
||||
|
||||
|
@ -929,22 +929,35 @@ Ogre::Vector3 Animation::runAnimation(float duration)
|
|||
}
|
||||
|
||||
|
||||
for (std::vector<NifOgre::ObjectList>::iterator it = mEffects.begin(); it != mEffects.end(); )
|
||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); )
|
||||
{
|
||||
for(size_t i = 0; i < it->mControllers.size() ;i++)
|
||||
NifOgre::ObjectList& objects = it->mObjects;
|
||||
for(size_t i = 0; i < objects.mControllers.size() ;i++)
|
||||
{
|
||||
static_cast<EffectAnimationValue*> (it->mControllers[i].getSource().get())->addTime(duration);
|
||||
static_cast<EffectAnimationValue*> (objects.mControllers[i].getSource().get())->addTime(duration);
|
||||
|
||||
it->mControllers[i].update();
|
||||
objects.mControllers[i].update();
|
||||
}
|
||||
|
||||
if (it->mControllers[0].getSource()->getValue() >= it->mMaxControllerLength)
|
||||
if (objects.mControllers[0].getSource()->getValue() >= objects.mMaxControllerLength)
|
||||
{
|
||||
destroyObjectList(mInsert->getCreator(), *it);
|
||||
it = mEffects.erase(it);
|
||||
if (it->mLoop)
|
||||
{
|
||||
// Start from the beginning again; carry over the remainder
|
||||
float remainder = objects.mControllers[0].getSource()->getValue() - objects.mMaxControllerLength;
|
||||
for(size_t i = 0; i < objects.mControllers.size() ;i++)
|
||||
{
|
||||
static_cast<EffectAnimationValue*> (objects.mControllers[i].getSource().get())->resetTime(remainder);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
destroyObjectList(mInsert->getCreator(), it->mObjects);
|
||||
it = mEffects.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
++it;
|
||||
++it;
|
||||
}
|
||||
|
||||
return movement;
|
||||
|
@ -1011,15 +1024,37 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj)
|
|||
mSkelBase->detachObjectFromBone(obj);
|
||||
}
|
||||
|
||||
void Animation::addEffect(const std::string &model, const std::string &bonename)
|
||||
void Animation::addEffect(const std::string &model, bool loop, const std::string &bonename)
|
||||
{
|
||||
NifOgre::ObjectList list = NifOgre::Loader::createObjects(mInsert, model);
|
||||
for(size_t i = 0;i < list.mControllers.size();i++)
|
||||
// Early out if we already have this effect
|
||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
||||
if (it->mModelName == model)
|
||||
return;
|
||||
|
||||
EffectParams params;
|
||||
params.mModelName = model;
|
||||
params.mObjects = NifOgre::Loader::createObjects(mInsert, model);
|
||||
params.mLoop = loop;
|
||||
|
||||
for(size_t i = 0;i < params.mObjects.mControllers.size();i++)
|
||||
{
|
||||
if(list.mControllers[i].getSource().isNull())
|
||||
list.mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationValue> (new EffectAnimationValue()));
|
||||
if(params.mObjects.mControllers[i].getSource().isNull())
|
||||
params.mObjects.mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationValue> (new EffectAnimationValue()));
|
||||
}
|
||||
mEffects.push_back(params);
|
||||
}
|
||||
|
||||
void Animation::removeEffect(const std::string &model)
|
||||
{
|
||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
||||
{
|
||||
if (it->mModelName == model)
|
||||
{
|
||||
destroyObjectList(mInsert->getCreator(), it->mObjects);
|
||||
mEffects.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mEffects.push_back(list);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ protected:
|
|||
public:
|
||||
EffectAnimationValue() : mTime(0) { }
|
||||
void addTime(float time) { mTime += time; }
|
||||
void resetTime(float value) { mTime = value; }
|
||||
|
||||
virtual Ogre::Real getValue() const;
|
||||
virtual void setValue(Ogre::Real value);
|
||||
|
@ -108,7 +109,14 @@ protected:
|
|||
|
||||
typedef std::map<Ogre::MovableObject*,std::string> ObjectAttachMap;
|
||||
|
||||
std::vector<NifOgre::ObjectList> mEffects;
|
||||
struct EffectParams
|
||||
{
|
||||
std::string mModelName; // Just here so we don't add the same effect twice
|
||||
NifOgre::ObjectList mObjects;
|
||||
bool mLoop;
|
||||
};
|
||||
|
||||
std::vector<EffectParams> mEffects;
|
||||
|
||||
MWWorld::Ptr mPtr;
|
||||
Camera *mCamera;
|
||||
|
@ -189,7 +197,17 @@ public:
|
|||
Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node);
|
||||
virtual ~Animation();
|
||||
|
||||
void addEffect (const std::string& model, const std::string& bonename = "");
|
||||
/**
|
||||
* @brief Add an effect mesh attached to a bone or the insert scene node
|
||||
* @param model
|
||||
* @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true,
|
||||
* you need to remove it manually using removeEffect when the effect should end.
|
||||
* @param bonename Bone to attach to, or empty string to use the scene node instead
|
||||
* @note Will not add an effect twice.
|
||||
*/
|
||||
void addEffect (const std::string& model, bool loop = false, const std::string& bonename = "");
|
||||
|
||||
void removeEffect (const std::string& model);
|
||||
|
||||
void updatePtr(const MWWorld::Ptr &ptr);
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
#ifndef GAME_MWWORLD_ACTIONAPPLY_H
|
||||
#define GAME_MWWORLD_ACTIONAPPLY_H
|
||||
|
||||
|
|
28
apps/openmw/mwworld/actiontrap.cpp
Normal file
28
apps/openmw/mwworld/actiontrap.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include "actiontrap.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "../mwmechanics/activespells.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
||||
void ActionTrap::executeImp(const Ptr &actor)
|
||||
{
|
||||
// TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could
|
||||
// make it lock itself when activated for example.
|
||||
|
||||
actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, ESM::RT_Touch);
|
||||
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(mSpellId);
|
||||
|
||||
MWBase::Environment::get().getWorld()->launchProjectile(mSpellId, spell->mEffects, mTrapSource, spell->mName);
|
||||
|
||||
mTrapSource.getCellRef().mTrap = "";
|
||||
}
|
||||
|
||||
}
|
29
apps/openmw/mwworld/actiontrap.hpp
Normal file
29
apps/openmw/mwworld/actiontrap.hpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#ifndef GAME_MWWORLD_ACTIONTRAP_H
|
||||
#define GAME_MWWORLD_ACTIONTRAP_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "action.hpp"
|
||||
#include "ptr.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class ActionTrap : public Action
|
||||
{
|
||||
std::string mSpellId;
|
||||
MWWorld::Ptr mTrapSource;
|
||||
|
||||
virtual void executeImp (const Ptr& actor);
|
||||
|
||||
public:
|
||||
|
||||
/// @param spellId
|
||||
/// @param actor Actor that activated the trap
|
||||
/// @param trapSource
|
||||
ActionTrap (const Ptr& actor, const std::string& spellId, const Ptr& trapSource)
|
||||
: Action(false, actor), mSpellId(spellId), mTrapSource(trapSource) {}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
|
@ -254,6 +254,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc)
|
|||
|
||||
const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects()
|
||||
{
|
||||
// TODO: Add VFX; update when needed instead of update on request
|
||||
if (!mMagicEffectsUpToDate)
|
||||
{
|
||||
mMagicEffects = MWMechanics::MagicEffects();
|
||||
|
|
|
@ -1122,6 +1122,8 @@ namespace MWWorld
|
|||
|
||||
processDoors(duration);
|
||||
|
||||
moveProjectiles(duration);
|
||||
|
||||
const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration);
|
||||
PtrVelocityList::const_iterator player(results.end());
|
||||
for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();iter++)
|
||||
|
@ -2163,8 +2165,78 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
launchProjectile(selectedSpell, effects, actor, sourceName);
|
||||
|
||||
}
|
||||
|
||||
void World::launchProjectile (const std::string& id, const ESM::EffectList& effects,
|
||||
const MWWorld::Ptr& actor, const std::string& sourceName)
|
||||
{
|
||||
std::string projectileModel;
|
||||
std::string sound;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
|
||||
iter!=effects.mList.end(); ++iter)
|
||||
{
|
||||
if (iter->mRange != ESM::RT_Target)
|
||||
continue;
|
||||
|
||||
const ESM::MagicEffect *magicEffect = getStore().get<ESM::MagicEffect>().find (
|
||||
iter->mEffectID);
|
||||
|
||||
projectileModel = magicEffect->mBolt;
|
||||
|
||||
static const std::string schools[] = {
|
||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
};
|
||||
|
||||
if (!magicEffect->mBoltSound.empty())
|
||||
sound = magicEffect->mBoltSound;
|
||||
else
|
||||
sound = schools[magicEffect->mData.mSchool] + " bolt";
|
||||
|
||||
break;
|
||||
}
|
||||
if (projectileModel.empty())
|
||||
return;
|
||||
|
||||
MWWorld::ManualRef ref(getStore(), projectileModel);
|
||||
ESM::Position pos;
|
||||
pos.pos[0] = actor.getRefData().getPosition().pos[0];
|
||||
pos.pos[1] = actor.getRefData().getPosition().pos[1];
|
||||
pos.pos[2] = actor.getRefData().getPosition().pos[2];
|
||||
pos.rot[0] = actor.getRefData().getPosition().rot[0];
|
||||
pos.rot[1] = actor.getRefData().getPosition().rot[1];
|
||||
pos.rot[2] = actor.getRefData().getPosition().rot[2];
|
||||
ref.getPtr().getCellRef().mPos = pos;
|
||||
MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), *actor.getCell(), pos);
|
||||
|
||||
ProjectileState state;
|
||||
state.mSourceName = sourceName;
|
||||
state.mId = id;
|
||||
state.mActorHandle = actor.getRefData().getHandle();
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f);
|
||||
|
||||
mProjectiles[ptr] = state;
|
||||
}
|
||||
|
||||
void World::moveProjectiles(float duration)
|
||||
{
|
||||
for (std::map<MWWorld::Ptr, ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();)
|
||||
{
|
||||
if (!mWorldScene->isCellActive(*it->first.getCell()))
|
||||
{
|
||||
mProjectiles.erase(it++);
|
||||
continue;
|
||||
}
|
||||
// TODO: Move
|
||||
//moveObject(it->first, newPos.x, newPos.y, newPos.z);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void World::updateAnimParts(const Ptr& actor)
|
||||
{
|
||||
mRendering->updateAnimParts(actor);
|
||||
|
|
|
@ -87,6 +87,20 @@ namespace MWWorld
|
|||
std::map<MWWorld::Ptr, int> mDoorStates;
|
||||
///< only holds doors that are currently moving. 0 means closing, 1 opening
|
||||
|
||||
struct ProjectileState
|
||||
{
|
||||
// Id of spell or enchantment to apply when it hits
|
||||
std::string mId;
|
||||
|
||||
// Actor who casted this projectile
|
||||
std::string mActorHandle;
|
||||
|
||||
// Name of item to display as effect source in magic menu (in case we casted an enchantment)
|
||||
std::string mSourceName;
|
||||
};
|
||||
|
||||
std::map<MWWorld::Ptr, ProjectileState> mProjectiles;
|
||||
|
||||
int getDaysPerMonth (int month) const;
|
||||
|
||||
void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust);
|
||||
|
@ -112,6 +126,8 @@ namespace MWWorld
|
|||
void processDoors(float duration);
|
||||
///< Run physics simulation and modify \a world accordingly.
|
||||
|
||||
void moveProjectiles(float duration);
|
||||
|
||||
void doPhysics(float duration);
|
||||
///< Run physics simulation and modify \a world accordingly.
|
||||
|
||||
|
@ -475,6 +491,9 @@ namespace MWWorld
|
|||
virtual void castSpell (const MWWorld::Ptr& actor);
|
||||
|
||||
virtual void updateAnimParts(const MWWorld::Ptr& ptr);
|
||||
|
||||
virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects,
|
||||
const MWWorld::Ptr& actor, const std::string& sourceName);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue