Merge pull request #298 from OpenMW/master

Add OpenMW commits up to 26 Sep 2017
This commit is contained in:
David Cernat 2017-09-26 21:54:55 +03:00 committed by GitHub
commit ddf0dfed55
22 changed files with 584 additions and 264 deletions

View file

@ -17,6 +17,7 @@ set(ESSIMPORTER_FILES
importscri.cpp importscri.cpp
importscpt.cpp importscpt.cpp
importproj.cpp importproj.cpp
importsplm.cpp
importercontext.cpp importercontext.cpp
converter.cpp converter.cpp
convertacdt.cpp convertacdt.cpp

View file

@ -1,12 +1,12 @@
#include "converter.hpp" #include "converter.hpp"
#include <stdexcept> #include <stdexcept>
#include <algorithm>
#include <osgDB/WriteFile> #include <osgDB/WriteFile>
#include <components/esm/creaturestate.hpp> #include <components/esm/creaturestate.hpp>
#include <components/esm/containerstate.hpp> #include <components/esm/containerstate.hpp>
#include <components/esm/projectilestate.hpp>
#include "convertcrec.hpp" #include "convertcrec.hpp"
#include "convertcntc.hpp" #include "convertcntc.hpp"
@ -461,11 +461,7 @@ namespace ESSImport
if (!pnam.isMagic()) if (!pnam.isMagic())
{ {
ESM::ProjectileState out; ESM::ProjectileState out;
out.mId = pnam.mArrowId.toString(); convertBaseState(out, pnam);
out.mPosition = pnam.mPosition;
out.mOrientation.mValues[0] = out.mOrientation.mValues[1] = out.mOrientation.mValues[2] = 0.0f;
out.mOrientation.mValues[3] = 1.0f;
out.mActorId = convertActorId(pnam.mActorId.toString(), *mContext);
out.mBowId = pnam.mBowId.toString(); out.mBowId = pnam.mBowId.toString();
out.mVelocity = pnam.mVelocity; out.mVelocity = pnam.mVelocity;
@ -477,16 +473,49 @@ namespace ESSImport
} }
else else
{ {
// TODO: Implement magic projectile conversion. ESM::MagicBoltState out;
convertBaseState(out, pnam);
/*esm.startRecord(ESM::REC_MPRJ); auto it = std::find_if(mContext->mActiveSpells.begin(), mContext->mActiveSpells.end(),
[&pnam](const SPLM::ActiveSpell& spell) -> bool { return spell.mIndex == pnam.mSplmIndex; });
if (it == mContext->mActiveSpells.end())
{
std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() << "\" (invalid spell link)" << std::endl;
continue;
}
out.mSpellId = it->mSPDT.mId.toString();
out.mSpeed = pnam.mSpeed * 0.001f; // not sure where this factor comes from
esm.startRecord(ESM::REC_MPRJ);
out.save(esm); out.save(esm);
esm.endRecord(ESM::REC_MPRJ);*/ esm.endRecord(ESM::REC_MPRJ);
std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() << "\" (not implemented)" << std::endl;
continue;
} }
} }
} }
void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam)
{
base.mId = pnam.mArrowId.toString();
base.mPosition = pnam.mPosition;
osg::Quat orient;
orient.makeRotate(osg::Vec3f(0,1,0), pnam.mVelocity);
base.mOrientation = orient;
base.mActorId = convertActorId(pnam.mActorId.toString(), *mContext);
}
void ConvertSPLM::read(ESM::ESMReader& esm)
{
mSPLM.load(esm);
mContext->mActiveSpells = mSPLM.mActiveSpells;
}
void ConvertSPLM::write(ESM::ESMWriter& esm)
{
std::cerr << "Warning: Skipped active spell conversion (not implemented)" << std::endl;
}
} }

View file

@ -22,6 +22,7 @@
#include <components/esm/globalscript.hpp> #include <components/esm/globalscript.hpp>
#include <components/esm/queststate.hpp> #include <components/esm/queststate.hpp>
#include <components/esm/stolenitems.hpp> #include <components/esm/stolenitems.hpp>
#include <components/esm/projectilestate.hpp>
#include "importcrec.hpp" #include "importcrec.hpp"
#include "importcntc.hpp" #include "importcntc.hpp"
@ -36,6 +37,7 @@
#include "importjour.hpp" #include "importjour.hpp"
#include "importscpt.hpp" #include "importscpt.hpp"
#include "importproj.h" #include "importproj.h"
#include "importsplm.h"
#include "convertacdt.hpp" #include "convertacdt.hpp"
#include "convertnpcc.hpp" #include "convertnpcc.hpp"
@ -602,9 +604,19 @@ public:
virtual void read(ESM::ESMReader& esm) override; virtual void read(ESM::ESMReader& esm) override;
virtual void write(ESM::ESMWriter& esm) override; virtual void write(ESM::ESMWriter& esm) override;
private: private:
void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam);
PROJ mProj; PROJ mProj;
}; };
class ConvertSPLM : public Converter
{
public:
virtual void read(ESM::ESMReader& esm) override;
virtual void write(ESM::ESMWriter& esm) override;
private:
SPLM mSPLM;
};
} }
#endif #endif

View file

@ -271,6 +271,7 @@ namespace ESSImport
const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value; const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value;
const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value; const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value;
const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value; const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value;
const unsigned int recSPLM = ESM::FourCC<'S','P','L','M'>::value;
std::map<unsigned int, std::shared_ptr<Converter> > converters; std::map<unsigned int, std::shared_ptr<Converter> > converters;
converters[ESM::REC_GLOB] = std::shared_ptr<Converter>(new ConvertGlobal()); converters[ESM::REC_GLOB] = std::shared_ptr<Converter>(new ConvertGlobal());
@ -304,12 +305,12 @@ namespace ESSImport
converters[recJOUR ] = std::shared_ptr<Converter>(new ConvertJOUR()); converters[recJOUR ] = std::shared_ptr<Converter>(new ConvertJOUR());
converters[ESM::REC_SCPT] = std::shared_ptr<Converter>(new ConvertSCPT()); converters[ESM::REC_SCPT] = std::shared_ptr<Converter>(new ConvertSCPT());
converters[ESM::REC_PROJ] = std::shared_ptr<Converter>(new ConvertPROJ()); converters[ESM::REC_PROJ] = std::shared_ptr<Converter>(new ConvertPROJ());
converters[recSPLM] = std::shared_ptr<Converter>(new ConvertSPLM());
// TODO: // TODO:
// - REGN (weather in certain regions?) // - REGN (weather in certain regions?)
// - VFXM // - VFXM
// - SPLM (active spell effects) // - SPLM (active spell effects)
// - PROJ (magic projectiles in air)
std::set<unsigned int> unknownRecords; std::set<unsigned int> unknownRecords;

View file

@ -15,7 +15,7 @@
#include "importcrec.hpp" #include "importcrec.hpp"
#include "importcntc.hpp" #include "importcntc.hpp"
#include "importplayer.hpp" #include "importplayer.hpp"
#include "importsplm.h"
@ -54,6 +54,8 @@ namespace ESSImport
std::map<std::string, ESM::Creature> mCreatures; std::map<std::string, ESM::Creature> mCreatures;
std::map<std::string, ESM::NPC> mNpcs; std::map<std::string, ESM::NPC> mNpcs;
std::vector<SPLM::ActiveSpell> mActiveSpells;
Context() Context()
: mDay(0) : mDay(0)
, mMonth(0) , mMonth(0)

View file

@ -0,0 +1,43 @@
#include "importsplm.h"
#include <components/esm/esmreader.hpp>
namespace ESSImport
{
void SPLM::load(ESM::ESMReader& esm)
{
while (esm.isNextSub("NAME"))
{
ActiveSpell spell;
esm.getHT(spell.mIndex);
esm.getHNT(spell.mSPDT, "SPDT");
spell.mTarget = esm.getHNOString("TNAM");
while (esm.isNextSub("NPDT"))
{
ActiveEffect effect;
esm.getHT(effect.mNPDT);
// Effect-specific subrecords can follow:
// - INAM for disintegration and bound effects
// - CNAM for summoning and command effects
// - VNAM for vampirism
// NOTE: There can be multiple INAMs per effect.
// TODO: Needs more research.
esm.skipHSubUntil("NAM0"); // sentinel
esm.getSubName();
esm.skipHSub();
spell.mActiveEffects.push_back(effect);
}
unsigned char xnam; // sentinel
esm.getHNT(xnam, "XNAM");
mActiveSpells.push_back(spell);
}
}
}

View file

@ -0,0 +1,81 @@
#ifndef OPENMW_ESSIMPORT_IMPORTSPLM_H
#define OPENMW_ESSIMPORT_IMPORTSPLM_H
#include <vector>
#include <components/esm/esmcommon.hpp>
#include <components/esm/util.hpp>
namespace ESM
{
class ESMReader;
}
namespace ESSImport
{
struct SPLM
{
#pragma pack(push)
#pragma pack(1)
struct SPDT // 160 bytes
{
int mType; // 1 = spell, 2 = enchantment, 3 = potion
ESM::NAME32 mId; // base ID of a spell/enchantment/potion
unsigned char mUnknown[4*4];
ESM::NAME32 mCasterId;
ESM::NAME32 mSourceId; // empty for spells
unsigned char mUnknown2[4*11];
};
struct NPDT // 56 bytes
{
ESM::NAME32 mAffectedActorId;
unsigned char mUnknown[4*2];
int mMagnitude;
float mSecondsActive;
unsigned char mUnknown2[4*2];
};
struct INAM // 40 bytes
{
int mUnknown;
unsigned char mUnknown2;
ESM::FIXED_STRING<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration
};
struct CNAM // 36 bytes
{
int mUnknown; // seems to always be 0
ESM::NAME32 mSummonedOrCommandedActor[32];
};
struct VNAM // 4 bytes
{
int mUnknown;
};
#pragma pack(pop)
struct ActiveEffect
{
NPDT mNPDT;
};
struct ActiveSpell
{
int mIndex;
SPDT mSPDT;
std::string mTarget;
std::vector<ActiveEffect> mActiveEffects;
};
std::vector<ActiveSpell> mActiveSpells;
void load(ESM::ESMReader& esm);
};
}
#endif

View file

@ -522,8 +522,7 @@ namespace MWBase
virtual void castSpell (const MWWorld::Ptr& actor) = 0; virtual void castSpell (const MWWorld::Ptr& actor) = 0;
virtual void launchMagicBolt (const std::string& spellId, bool stack, const ESM::EffectList& effects, virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0;
const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0;
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0; const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0;

View file

@ -200,6 +200,7 @@ namespace MWMechanics
stopWalking(actor, storage); stopWalking(actor, storage);
currentCell = actor.getCell(); currentCell = actor.getCell();
storage.mPopulateAvailableNodes = true; storage.mPopulateAvailableNodes = true;
mStoredInitialActorPosition = false;
} }
mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); mRemainingDuration -= ((duration*MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600);

View file

@ -202,6 +202,8 @@ static const StateInfo sMovementList[] = {
{ CharState_TurnLeft, "turnleft" }, { CharState_TurnLeft, "turnleft" },
{ CharState_TurnRight, "turnright" }, { CharState_TurnRight, "turnright" },
{ CharState_SwimTurnLeft, "swimturnleft" },
{ CharState_SwimTurnRight, "swimturnright" },
}; };
static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])]; static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])];
@ -261,32 +263,61 @@ void CharacterController::refreshHitRecoilAnims()
bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery();
bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);
if(mHitState == CharState_None) if(mHitState == CharState_None)
{ {
if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0)
&& mAnimation->hasAnimation("knockout")) && mAnimation->hasAnimation("knockout"))
{ {
mHitState = CharState_KnockOut; if (isSwimming && mAnimation->hasAnimation("swimknockout"))
mCurrentHit = "knockout"; {
mHitState = CharState_SwimKnockOut;
mCurrentHit = "swimknockout";
}
else
{
mHitState = CharState_KnockOut;
mCurrentHit = "knockout";
}
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
} }
else if(knockdown && mAnimation->hasAnimation("knockdown")) else if(knockdown && mAnimation->hasAnimation("knockdown"))
{ {
mHitState = CharState_KnockDown; if (isSwimming && mAnimation->hasAnimation("swimknockdown"))
mCurrentHit = "knockdown"; {
mHitState = CharState_SwimKnockDown;
mCurrentHit = "swimknockdown";
}
else
{
mHitState = CharState_KnockDown;
mCurrentHit = "knockdown";
}
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else if (recovery) else if (recovery)
{ {
std::string anim = chooseRandomGroup("hit"); std::string anim = isSwimming ? chooseRandomGroup("swimhit") : chooseRandomGroup("hit");
if (mAnimation->hasAnimation(anim)) if (isSwimming && mAnimation->hasAnimation(anim))
{ {
mHitState = CharState_Hit; mHitState = CharState_SwimHit;
mCurrentHit = anim; mCurrentHit = anim;
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else
{
anim = chooseRandomGroup("hit");
if (mAnimation->hasAnimation(anim))
{
mHitState = CharState_Hit;
mCurrentHit = anim;
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
}
}
} }
else if (block && mAnimation->hasAnimation("shield")) else if (block && mAnimation->hasAnimation("shield"))
{ {
@ -298,7 +329,7 @@ void CharacterController::refreshHitRecoilAnims()
} }
// Cancel upper body animations // Cancel upper body animations
if (mHitState == CharState_KnockDown || mHitState == CharState_KnockOut) if (isKnockedOut() || isKnockedDown())
{ {
if (mUpperBodyState > UpperCharState_WeapEquiped) if (mUpperBodyState > UpperCharState_WeapEquiped)
{ {
@ -323,9 +354,9 @@ void CharacterController::refreshHitRecoilAnims()
mPtr.getClass().getCreatureStats(mPtr).setBlock(false); mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
mHitState = CharState_None; mHitState = CharState_None;
} }
else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0)
{ {
mHitState = CharState_KnockDown; mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown;
mAnimation->disable(mCurrentHit); mAnimation->disable(mCurrentHit);
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0);
} }
@ -551,7 +582,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
// FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update),
// the idle animation should be displayed // the idle animation should be displayed
if ((mUpperBodyState != UpperCharState_Nothing if ((mUpperBodyState != UpperCharState_Nothing
|| (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight) || (mMovementState != CharState_None && !isTurning())
|| mHitState != CharState_None) || mHitState != CharState_None)
&& !mPtr.getClass().isBipedal(mPtr)) && !mPtr.getClass().isBipedal(mPtr))
idle = CharState_None; idle = CharState_None;
@ -637,6 +668,12 @@ void CharacterController::playDeath(float startpoint, CharacterState death)
case CharState_SwimDeath: case CharState_SwimDeath:
mCurrentDeath = "swimdeath"; mCurrentDeath = "swimdeath";
break; break;
case CharState_SwimDeathKnockDown:
mCurrentDeath = "swimdeathknockdown";
break;
case CharState_SwimDeathKnockOut:
mCurrentDeath = "swimdeathknockout";
break;
case CharState_DeathKnockDown: case CharState_DeathKnockDown:
mCurrentDeath = "deathknockdown"; mCurrentDeath = "deathknockdown";
break; break;
@ -704,7 +741,15 @@ void CharacterController::playRandomDeath(float startpoint)
MWBase::Environment::get().getWorld()->useDeathCamera(); MWBase::Environment::get().getWorld()->useDeathCamera();
} }
if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown"))
{
mDeathState = CharState_SwimDeathKnockDown;
}
else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout"))
{
mDeathState = CharState_SwimDeathKnockOut;
}
else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath"))
{ {
mDeathState = CharState_SwimDeath; mDeathState = CharState_SwimDeath;
} }
@ -906,16 +951,17 @@ void CharacterController::handleTextKey(const std::string &groupname, const std:
mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust);
else if(evt.compare(off, len, "hit") == 0) else if(evt.compare(off, len, "hit") == 0)
{ {
if (groupname == "attack1") if (groupname == "attack1" || groupname == "swimattack1")
mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop);
else if (groupname == "attack2") else if (groupname == "attack2" || groupname == "swimattack2")
mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash);
else if (groupname == "attack3") else if (groupname == "attack3" || groupname == "swimattack3")
mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust);
else else
mPtr.getClass().hit(mPtr, mAttackStrength); mPtr.getClass().hit(mPtr, mAttackStrength);
} }
else if (!groupname.empty() && groupname.compare(0, groupname.size()-1, "attack") == 0 else if (!groupname.empty()
&& (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0)
&& evt.compare(off, len, "start") == 0) && evt.compare(off, len, "start") == 0)
{ {
std::multimap<float, std::string>::const_iterator hitKey = key; std::multimap<float, std::string>::const_iterator hitKey = key;
@ -935,11 +981,11 @@ void CharacterController::handleTextKey(const std::string &groupname, const std:
} }
if (!hasHitKey) if (!hasHitKey)
{ {
if (groupname == "attack1") if (groupname == "attack1" || groupname == "swimattack1")
mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop);
else if (groupname == "attack2") else if (groupname == "attack2" || groupname == "swimattack2")
mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash);
else if (groupname == "attack3") else if (groupname == "attack3" || groupname == "swimattack3")
mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust);
} }
} }
@ -1095,13 +1141,14 @@ bool CharacterController::updateCreatureState()
} }
if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation
{ {
bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);
int roll = Misc::Rng::rollDice(3); // [0, 2] int roll = Misc::Rng::rollDice(3); // [0, 2]
if (roll == 0) if (roll == 0)
mCurrentWeapon = "attack1"; mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack1") ? "swimattack1" : "attack1";
else if (roll == 1) else if (roll == 1)
mCurrentWeapon = "attack2"; mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack2") ? "swimattack2" : "attack2";
else else
mCurrentWeapon = "attack3"; mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack3") ? "swimattack3" : "attack3";
} }
if (!mCurrentWeapon.empty()) if (!mCurrentWeapon.empty())
@ -1181,8 +1228,8 @@ bool CharacterController::updateWeaponState()
bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell &&
mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell;
if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut if(weaptype != mWeaponType && !isKnockedOut() &&
&& mHitState != CharState_Hit) !isKnockedDown() && !isRecovery())
{ {
forcestateupdate = true; forcestateupdate = true;
@ -1444,13 +1491,13 @@ bool CharacterController::updateWeaponState()
} }
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && mHitState != CharState_KnockDown) if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
mAttackStrength = complete; mAttackStrength = complete;
} }
else else
{ {
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && mHitState != CharState_KnockDown) if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
{ {
float attackStrength = complete; float attackStrength = complete;
if (!mPtr.getClass().isNpc()) if (!mPtr.getClass().isNpc())
@ -1488,7 +1535,7 @@ bool CharacterController::updateWeaponState()
complete = 0.f; complete = 0.f;
mUpperBodyState = UpperCharState_MaxAttackToMinHit; mUpperBodyState = UpperCharState_MaxAttackToMinHit;
} }
else if (mHitState == CharState_KnockDown) else if (isKnockedDown())
{ {
if (mUpperBodyState > UpperCharState_WeapEquiped) if (mUpperBodyState > UpperCharState_WeapEquiped)
mUpperBodyState = UpperCharState_WeapEquiped; mUpperBodyState = UpperCharState_WeapEquiped;
@ -1948,22 +1995,22 @@ void CharacterController::update(float duration)
else if(rot.z() != 0.0f && !inwater && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) else if(rot.z() != 0.0f && !inwater && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()))
{ {
if(rot.z() > 0.0f) if(rot.z() > 0.0f)
movestate = CharState_TurnRight; movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
else if(rot.z() < 0.0f) else if(rot.z() < 0.0f)
movestate = CharState_TurnLeft; movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
} }
} }
mTurnAnimationThreshold -= duration; mTurnAnimationThreshold -= duration;
if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft) if (isTurning())
mTurnAnimationThreshold = 0.05f; mTurnAnimationThreshold = 0.05f;
else if (movestate == CharState_None && (mMovementState == CharState_TurnRight || mMovementState == CharState_TurnLeft) else if (movestate == CharState_None && isTurning()
&& mTurnAnimationThreshold > 0) && mTurnAnimationThreshold > 0)
{ {
movestate = mMovementState; movestate = mMovementState;
} }
if(movestate != CharState_None && movestate != CharState_TurnLeft && movestate != CharState_TurnRight) if(movestate != CharState_None && !isTurning())
clearAnimQueue(); clearAnimQueue();
if(mAnimQueue.empty() || inwater || sneak) if(mAnimQueue.empty() || inwater || sneak)
@ -1989,7 +2036,7 @@ void CharacterController::update(float duration)
if (inJump) if (inJump)
mMovementAnimationControlled = false; mMovementAnimationControlled = false;
if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) if (isTurning())
{ {
if (duration > 0) if (duration > 0)
mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast<float>(osg::PI))); mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast<float>(osg::PI)));
@ -2002,7 +2049,7 @@ void CharacterController::update(float duration)
if (!mSkipAnim) if (!mSkipAnim)
{ {
if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut) if(!isKnockedDown() && !isKnockedOut())
{ {
if (rot != osg::Vec3f()) if (rot != osg::Vec3f())
world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true); world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true);
@ -2359,9 +2406,30 @@ bool CharacterController::isReadyToBlock() const
return updateCarriedLeftVisible(mWeaponType); return updateCarriedLeftVisible(mWeaponType);
} }
bool CharacterController::isKnockedDown() const
{
return mHitState == CharState_KnockDown ||
mHitState == CharState_SwimKnockDown;
}
bool CharacterController::isKnockedOut() const bool CharacterController::isKnockedOut() const
{ {
return mHitState == CharState_KnockOut; return mHitState == CharState_KnockOut ||
mHitState == CharState_SwimKnockOut;
}
bool CharacterController::isTurning() const
{
return mMovementState == CharState_TurnLeft ||
mMovementState == CharState_TurnRight ||
mMovementState == CharState_SwimTurnLeft ||
mMovementState == CharState_SwimTurnRight;
}
bool CharacterController::isRecovery() const
{
return mHitState == CharState_Hit ||
mHitState == CharState_SwimHit;
} }
bool CharacterController::isAttackingOrSpell() const bool CharacterController::isAttackingOrSpell() const

View file

@ -88,6 +88,8 @@ enum CharacterState {
CharState_TurnLeft, CharState_TurnLeft,
CharState_TurnRight, CharState_TurnRight,
CharState_SwimTurnLeft,
CharState_SwimTurnRight,
CharState_Jump, CharState_Jump,
@ -97,12 +99,17 @@ enum CharacterState {
CharState_Death4, CharState_Death4,
CharState_Death5, CharState_Death5,
CharState_SwimDeath, CharState_SwimDeath,
CharState_SwimDeathKnockDown,
CharState_SwimDeathKnockOut,
CharState_DeathKnockDown, CharState_DeathKnockDown,
CharState_DeathKnockOut, CharState_DeathKnockOut,
CharState_Hit, CharState_Hit,
CharState_SwimHit,
CharState_KnockDown, CharState_KnockDown,
CharState_KnockOut, CharState_KnockOut,
CharState_SwimKnockDown,
CharState_SwimKnockOut,
CharState_Block CharState_Block
}; };
@ -265,9 +272,12 @@ public:
bool isAttackPrepairing() const; bool isAttackPrepairing() const;
bool isReadyToBlock() const; bool isReadyToBlock() const;
bool isKnockedDown() const;
bool isKnockedOut() const; bool isKnockedOut() const;
bool isRecovery() const;
bool isSneaking() const; bool isSneaking() const;
bool isRunning() const; bool isRunning() const;
bool isTurning() const;
bool isAttackingOrSpell() const; bool isAttackingOrSpell() const;
void setAttackingOrSpell(bool attackingOrSpell); void setAttackingOrSpell(bool attackingOrSpell);

View file

@ -345,7 +345,7 @@ namespace MWMechanics
{ {
} }
void CastSpell::launchMagicBolt (const ESM::EffectList& effects) void CastSpell::launchMagicBolt ()
{ {
osg::Vec3f fallbackDirection (0,1,0); osg::Vec3f fallbackDirection (0,1,0);
@ -356,8 +356,7 @@ namespace MWMechanics
osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- osg::Vec3f(mTarget.getRefData().getPosition().asVec3())-
osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); osg::Vec3f(mCaster.getRefData().getPosition().asVec3());
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, effects, MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection);
mCaster, mSourceName, fallbackDirection);
} }
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
@ -865,7 +864,7 @@ namespace MWMechanics
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
if (launchProjectile) if (launchProjectile)
launchMagicBolt(enchantment->mEffects); launchMagicBolt();
else if (isProjectile || !mTarget.isEmpty()) else if (isProjectile || !mTarget.isEmpty())
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target);
@ -973,7 +972,7 @@ namespace MWMechanics
if (!mTarget.isEmpty()) if (!mTarget.isEmpty())
inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch);
launchMagicBolt(spell->mEffects); launchMagicBolt();
return true; return true;
} }

View file

@ -106,7 +106,7 @@ namespace MWMechanics
void playSpellCastingEffects(const std::string &spellid); void playSpellCastingEffects(const std::string &spellid);
/// Launch a bolt with the given effects. /// Launch a bolt with the given effects.
void launchMagicBolt (const ESM::EffectList& effects); void launchMagicBolt ();
/// @note \a target can be any type of object, not just actors. /// @note \a target can be any type of object, not just actors.
/// @note \a caster can be any type of object, or even an empty object. /// @note \a caster can be any type of object, or even an empty object.

View file

@ -1,6 +1,7 @@
#include "projectilemanager.hpp" #include "projectilemanager.hpp"
#include <iomanip> #include <iomanip>
#include <iostream>
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
@ -41,13 +42,29 @@
namespace namespace
{ {
ESM::EffectList getMagicBoltData(std::vector<std::string>& projectileIDs, std::vector<std::string>& sounds, float& speed, std::string& texture, const ESM::EffectList& effects) ESM::EffectList getMagicBoltData(std::vector<std::string>& projectileIDs, std::vector<std::string>& sounds, float& speed, std::string& texture, std::string& sourceName, const std::string& id)
{ {
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
const ESM::EffectList* effects;
if (const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(id)) // check if it's a spell
{
sourceName = spell->mName;
effects = &spell->mEffects;
}
else // check if it's an enchanted item
{
MWWorld::ManualRef ref(esmStore, id);
MWWorld::Ptr ptr = ref.getPtr();
const ESM::Enchantment* ench = esmStore.get<ESM::Enchantment>().find(ptr.getClass().getEnchantment(ptr));
sourceName = ptr.getClass().getName(ptr);
effects = &ench->mEffects;
}
int count = 0; int count = 0;
speed = 0.0f; speed = 0.0f;
ESM::EffectList projectileEffects; ESM::EffectList projectileEffects;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin()); for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects->mList.begin());
iter!=effects.mList.end(); ++iter) iter!=effects->mList.end(); ++iter)
{ {
const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
iter->mEffectID); iter->mEffectID);
@ -82,14 +99,14 @@ namespace
if (projectileEffects.mList.size() == 1) if (projectileEffects.mList.size() == 1)
{ {
const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effects.mList.begin()->mEffectID); effects->mList.begin()->mEffectID);
texture = magicEffect->mParticle; texture = magicEffect->mParticle;
} }
if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects
{ {
std::ostringstream ID; std::ostringstream ID;
ID << "VFX_Multiple" << effects.mList.size(); ID << "VFX_Multiple" << effects->mList.size();
std::vector<std::string>::iterator it; std::vector<std::string>::iterator it;
it = projectileIDs.begin(); it = projectileIDs.begin();
it = projectileIDs.insert(it, ID.str()); it = projectileIDs.insert(it, ID.str());
@ -132,6 +149,7 @@ namespace MWWorld
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
, mRendering(rendering) , mRendering(rendering)
, mPhysics(physics) , mPhysics(physics)
, mCleanupTimer(0.0f)
{ {
} }
@ -235,8 +253,7 @@ namespace MWWorld
state.mEffectAnimationTime->addTime(duration); state.mEffectAnimationTime->addTime(duration);
} }
void ProjectileManager::launchMagicBolt(const std::string &spellId, bool stack, const ESM::EffectList &effects, const Ptr &caster, void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection)
const std::string &sourceName, const osg::Vec3f& fallbackDirection)
{ {
osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); osg::Vec3f pos = caster.getRefData().getPosition().asVec3();
if (caster.getClass().isActor()) if (caster.getClass().isActor())
@ -257,18 +274,16 @@ namespace MWWorld
orient.makeRotate(osg::Vec3f(0,1,0), osg::Vec3f(fallbackDirection)); orient.makeRotate(osg::Vec3f(0,1,0), osg::Vec3f(fallbackDirection));
MagicBoltState state; MagicBoltState state;
state.mSourceName = sourceName;
state.mSpellId = spellId; state.mSpellId = spellId;
state.mCasterHandle = caster; state.mCasterHandle = caster;
if (caster.getClass().isActor()) if (caster.getClass().isActor())
state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); state.mActorId = caster.getClass().getCreatureStats(caster).getActorId();
else else
state.mActorId = -1; state.mActorId = -1;
state.mStack = stack;
std::string texture = ""; std::string texture = "";
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, effects); state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId);
// Non-projectile should have been removed by getMagicBoltData // Non-projectile should have been removed by getMagicBoltData
if (state.mEffects.mList.empty()) if (state.mEffects.mList.empty())
@ -277,7 +292,7 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr(); MWWorld::Ptr ptr = ref.getPtr();
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(effects); osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
@ -312,10 +327,49 @@ namespace MWWorld
void ProjectileManager::update(float dt) void ProjectileManager::update(float dt)
{ {
periodicCleanup(dt);
moveProjectiles(dt); moveProjectiles(dt);
moveMagicBolts(dt); moveMagicBolts(dt);
} }
void ProjectileManager::periodicCleanup(float dt)
{
mCleanupTimer -= dt;
if (mCleanupTimer <= 0.0f)
{
mCleanupTimer = 2.0f;
auto isCleanable = [](const ProjectileManager::State& state) -> bool
{
const float farawayThreshold = 72000.0f;
osg::Vec3 playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3();
return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold;
};
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();)
{
if (isCleanable(*it))
{
cleanupProjectile(*it);
it = mProjectiles.erase(it);
}
else
++it;
}
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
{
if (isCleanable(*it))
{
cleanupMagicBolt(*it);
it = mMagicBolts.erase(it);
}
else
++it;
}
}
}
void ProjectileManager::moveMagicBolts(float duration) void ProjectileManager::moveMagicBolts(float duration)
{ {
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
@ -364,7 +418,7 @@ namespace MWWorld
cast.mHitPosition = pos; cast.mHitPosition = pos;
cast.mId = it->mSpellId; cast.mId = it->mSpellId;
cast.mSourceName = it->mSourceName; cast.mSourceName = it->mSourceName;
cast.mStack = it->mStack; cast.mStack = false;
cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true); cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true);
} }
} }
@ -454,20 +508,30 @@ namespace MWWorld
} }
} }
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
{
mParent->removeChild(state.mNode);
}
void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state)
{
mParent->removeChild(state.mNode);
for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++)
{
MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter));
}
}
void ProjectileManager::clear() void ProjectileManager::clear()
{ {
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{ {
mParent->removeChild(it->mNode); cleanupProjectile(*it);
} }
mProjectiles.clear(); mProjectiles.clear();
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{ {
mParent->removeChild(it->mNode); cleanupMagicBolt(*it);
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++)
{
MWBase::Environment::get().getSoundManager()->stopSound(it->mSounds.at(soundIter));
}
} }
mMagicBolts.clear(); mMagicBolts.clear();
} }
@ -504,11 +568,7 @@ namespace MWWorld
state.mActorId = it->mActorId; state.mActorId = it->mActorId;
state.mSpellId = it->mSpellId; state.mSpellId = it->mSpellId;
state.mEffects = it->mEffects;
state.mSound = it->mSoundIds.at(0);
state.mSourceName = it->mSourceName;
state.mSpeed = it->mSpeed; state.mSpeed = it->mSpeed;
state.mStack = it->mStack;
state.save(writer); state.save(writer);
@ -553,13 +613,21 @@ namespace MWWorld
esm.load(reader); esm.load(reader);
MagicBoltState state; MagicBoltState state;
state.mSourceName = esm.mSourceName;
state.mIdMagic.push_back(esm.mId); state.mIdMagic.push_back(esm.mId);
state.mSpellId = esm.mSpellId; state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId; state.mActorId = esm.mActorId;
state.mStack = esm.mStack;
std::string texture = ""; std::string texture = "";
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, esm.mEffects);
try
{
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId);
}
catch(...)
{
std::cerr << "Warning: Failed to recreate magic projectile from saved data (id \"" << state.mSpellId << "\" no longer exists?)" << std::endl;
return true;
}
state.mSpeed = esm.mSpeed; // speed is derived from non-projectile effects as well as state.mSpeed = esm.mSpeed; // speed is derived from non-projectile effects as well as
// projectile effects, so we can't calculate it from the save // projectile effects, so we can't calculate it from the save
// file's effect list, which is already trimmed of non-projectile // file's effect list, which is already trimmed of non-projectile
@ -577,7 +645,7 @@ namespace MWWorld
return true; return true;
} }
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(esm.mEffects); osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

View file

@ -49,8 +49,7 @@ namespace MWWorld
MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics);
/// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used.
void launchMagicBolt (const std::string &spellId, bool stack, const ESM::EffectList& effects, void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection);
const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection);
void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength);
@ -69,6 +68,7 @@ namespace MWWorld
Resource::ResourceSystem* mResourceSystem; Resource::ResourceSystem* mResourceSystem;
MWRender::RenderingManager* mRendering; MWRender::RenderingManager* mRendering;
MWPhysics::PhysicsSystem* mPhysics; MWPhysics::PhysicsSystem* mPhysics;
float mCleanupTimer;
struct State struct State
{ {
@ -101,8 +101,6 @@ namespace MWWorld
float mSpeed; float mSpeed;
bool mStack;
std::vector<MWBase::Sound*> mSounds; std::vector<MWBase::Sound*> mSounds;
std::vector<std::string> mSoundIds; std::vector<std::string> mSoundIds;
}; };
@ -119,6 +117,10 @@ namespace MWWorld
std::vector<MagicBoltState> mMagicBolts; std::vector<MagicBoltState> mMagicBolts;
std::vector<ProjectileState> mProjectiles; std::vector<ProjectileState> mProjectiles;
void cleanupProjectile(ProjectileState& state);
void cleanupMagicBolt(MagicBoltState& state);
void periodicCleanup(float dt);
void moveProjectiles(float dt); void moveProjectiles(float dt);
void moveMagicBolts(float dt); void moveMagicBolts(float dt);

View file

@ -2223,6 +2223,9 @@ namespace MWWorld
bool World::isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const bool World::isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const
{ {
if (!cell)
return false;
if (!(cell->getCell()->hasWater())) { if (!(cell->getCell()->hasWater())) {
return false; return false;
} }
@ -2989,10 +2992,9 @@ namespace MWWorld
mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength);
} }
void World::launchMagicBolt (const std::string &spellId, bool stack, const ESM::EffectList& effects, void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection)
const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection)
{ {
mProjectileManager->launchMagicBolt(spellId, stack, effects, caster, sourceName, fallbackDirection); mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection);
} }
const std::vector<std::string>& World::getContentFiles() const const std::vector<std::string>& World::getContentFiles() const

View file

@ -635,8 +635,7 @@ namespace MWWorld
*/ */
virtual void castSpell (const MWWorld::Ptr& actor); virtual void castSpell (const MWWorld::Ptr& actor);
virtual void launchMagicBolt (const std::string& spellId, bool stack, const ESM::EffectList& effects, virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override;
const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection);
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength);

View file

@ -27,11 +27,7 @@ namespace ESM
BaseProjectileState::save(esm); BaseProjectileState::save(esm);
esm.writeHNString ("SPEL", mSpellId); esm.writeHNString ("SPEL", mSpellId);
esm.writeHNString ("SRCN", mSourceName);
mEffects.save(esm);
esm.writeHNT ("SPED", mSpeed); esm.writeHNT ("SPED", mSpeed);
esm.writeHNT ("STCK", mStack);
esm.writeHNString ("SOUN", mSound);
} }
void MagicBoltState::load(ESMReader &esm) void MagicBoltState::load(ESMReader &esm)
@ -39,11 +35,14 @@ namespace ESM
BaseProjectileState::load(esm); BaseProjectileState::load(esm);
mSpellId = esm.getHNString("SPEL"); mSpellId = esm.getHNString("SPEL");
mSourceName = esm.getHNString ("SRCN"); if (esm.isNextSub("SRCN")) // for backwards compatibility
mEffects.load(esm); esm.skipHSub();
ESM::EffectList().load(esm); // for backwards compatibility
esm.getHNT (mSpeed, "SPED"); esm.getHNT (mSpeed, "SPED");
esm.getHNT (mStack, "STCK"); if (esm.isNextSub("STCK")) // for backwards compatibility
mSound = esm.getHNString ("SOUN"); esm.skipHSub();
if (esm.isNextSub("SOUN")) // for backwards compatibility
esm.skipHSub();
} }
void ProjectileState::save(ESMWriter &esm) const void ProjectileState::save(ESMWriter &esm) const

View file

@ -31,11 +31,7 @@ namespace ESM
struct MagicBoltState : public BaseProjectileState struct MagicBoltState : public BaseProjectileState
{ {
std::string mSpellId; std::string mSpellId;
std::string mSourceName;
ESM::EffectList mEffects;
float mSpeed; float mSpeed;
bool mStack;
std::string mSound;
void load (ESMReader &esm); void load (ESMReader &esm);
void save (ESMWriter &esm) const; void save (ESMWriter &esm) const;

View file

@ -8,7 +8,7 @@
namespace namespace
{ {
// somewhat arbitrary though 64KB buffers didn't seem to improve performance any // somewhat arbitrary though 64KB buffers didn't seem to improve performance any
const size_t sBufferSize = 4096; const size_t sBufferSize = 8192;
} }
namespace Files namespace Files

View file

@ -6,138 +6,8 @@ namespace Nif
{ {
//Private functions //Private functions
uint8_t NIFStream::read_byte()
{
uint8_t byte;
inp->read((char*)&byte, 1);
return byte;
}
uint16_t NIFStream::read_le16()
{
uint8_t buffer[2];
inp->read((char*)buffer, 2);
return buffer[0] | (buffer[1]<<8);
}
uint32_t NIFStream::read_le32()
{
uint8_t buffer[4];
inp->read((char*)buffer, 4);
return buffer[0] | (buffer[1]<<8) | (buffer[2]<<16) | (buffer[3]<<24);
}
float NIFStream::read_le32f()
{
union {
uint32_t i;
float f;
} u = { read_le32() };
return u.f;
}
//Public functions //Public functions
osg::Vec2f NIFStream::getVector2()
{
osg::Vec2f vec;
for(size_t i = 0;i < 2;i++)
vec._v[i] = getFloat();
return vec;
}
osg::Vec3f NIFStream::getVector3()
{
osg::Vec3f vec;
for(size_t i = 0;i < 3;i++)
vec._v[i] = getFloat();
return vec;
}
osg::Vec4f NIFStream::getVector4()
{
osg::Vec4f vec;
for(size_t i = 0;i < 4;i++)
vec._v[i] = getFloat();
return vec;
}
Matrix3 NIFStream::getMatrix3()
{
Matrix3 mat;
for(size_t i = 0;i < 3;i++)
{
for(size_t j = 0;j < 3;j++)
mat.mValues[i][j] = getFloat();
}
return mat;
}
osg::Quat NIFStream::getQuaternion()
{
osg::Quat quat;
quat.w() = getFloat();
quat.x() = getFloat();
quat.y() = getFloat();
quat.z() = getFloat();
return quat;
}
Transformation NIFStream::getTrafo()
{
Transformation t;
t.pos = getVector3();
t.rotation = getMatrix3();
t.scale = getFloat();
return t;
}
std::string NIFStream::getString(size_t length)
{
std::vector<char> str (length+1, 0);
inp->read(&str[0], length);
return &str[0];
}
std::string NIFStream::getString()
{
size_t size = read_le32();
return getString(size);
}
std::string NIFStream::getVersionString()
{
std::string result;
std::getline(*inp, result);
return result;
}
void NIFStream::getUShorts(std::vector<unsigned short> &vec, size_t size)
{
vec.resize(size);
for(size_t i = 0;i < vec.size();i++)
vec[i] = getUShort();
}
void NIFStream::getFloats(std::vector<float> &vec, size_t size)
{
vec.resize(size);
for(size_t i = 0;i < vec.size();i++)
vec[i] = getFloat();
}
void NIFStream::getVector2s(std::vector<osg::Vec2f> &vec, size_t size)
{
vec.resize(size);
for(size_t i = 0;i < vec.size();i++)
vec[i] = getVector2();
}
void NIFStream::getVector3s(std::vector<osg::Vec3f> &vec, size_t size)
{
vec.resize(size);
for(size_t i = 0;i < vec.size();i++)
vec[i] = getVector3();
}
void NIFStream::getVector4s(std::vector<osg::Vec4f> &vec, size_t size)
{
vec.resize(size);
for(size_t i = 0;i < vec.size();i++)
vec[i] = getVector4();
}
void NIFStream::getQuaternions(std::vector<osg::Quat> &quat, size_t size)
{
quat.resize(size);
for(size_t i = 0;i < quat.size();i++)
quat[i] = getQuaternion();
}
} }

View file

@ -21,16 +21,70 @@ namespace Nif
class NIFFile; class NIFFile;
/*
readLittleEndianBufferOfType: This template should only be used with non POD data types
*/
template <uint32_t numInstances, typename T, typename IntegerT> inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest)
{
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
pIStream->read((char*)dest, numInstances * sizeof(T));
#else
uint8_t* destByteBuffer = (uint8_t*)dest;
pIStream->read((char*)dest, numInstances * sizeof(T));
/*
Due to the loop iterations being known at compile time,
this nested loop will most likely be unrolled
For example, for 2 instances of a 4 byte data type, you should get the below result
*/
union {
IntegerT i;
T t;
} u;
for (uint32_t i = 0; i < numInstances; i++)
{
u = { 0 };
for (uint32_t byte = 0; byte < sizeof(T); byte++)
u.i |= (((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8));
dest[i] = u.t;
}
#endif
}
/*
readLittleEndianDynamicBufferOfType: This template should only be used with non POD data types
*/
template <typename T, typename IntegerT> inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances)
{
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
pIStream->read((char*)dest, numInstances * sizeof(T));
#else
uint8_t* destByteBuffer = (uint8_t*)dest;
pIStream->read((char*)dest, numInstances * sizeof(T));
union {
IntegerT i;
T t;
} u;
for (uint32_t i = 0; i < numInstances; i++)
{
u.i = 0;
for (uint32_t byte = 0; byte < sizeof(T); byte++)
u.i |= ((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8);
dest[i] = u.t;
}
#endif
}
template<typename type, typename IntegerT> type inline readLittleEndianType(Files::IStreamPtr &pIStream)
{
type val;
readLittleEndianBufferOfType<1,type,IntegerT>(pIStream, (type*)&val);
return val;
}
class NIFStream { class NIFStream {
/// Input stream /// Input stream
Files::IStreamPtr inp; Files::IStreamPtr inp;
uint8_t read_byte();
uint16_t read_le16();
uint32_t read_le32();
float read_le32f();
public: public:
NIFFile * const file; NIFFile * const file;
@ -39,33 +93,117 @@ public:
void skip(size_t size) { inp->ignore(size); } void skip(size_t size) { inp->ignore(size); }
char getChar() { return read_byte(); } char getChar()
short getShort() { return read_le16(); } {
unsigned short getUShort() { return read_le16(); } return readLittleEndianType<char,char>(inp);
int getInt() { return read_le32(); } }
unsigned int getUInt() { return read_le32(); } short getShort()
float getFloat() { return read_le32f(); } {
return readLittleEndianType<short,short>(inp);
}
unsigned short getUShort()
{
return readLittleEndianType<unsigned short,unsigned short>(inp);
}
int getInt()
{
return readLittleEndianType<int,int>(inp);
}
unsigned int getUInt()
{
return readLittleEndianType<unsigned int,unsigned int>(inp);
}
float getFloat()
{
return readLittleEndianType<float,uint32_t>(inp);
}
osg::Vec2f getVector2(); osg::Vec2f getVector2() {
osg::Vec3f getVector3(); osg::Vec2f vec;
osg::Vec4f getVector4(); readLittleEndianBufferOfType<2,float,uint32_t>(inp, (float*)&vec._v[0]);
Matrix3 getMatrix3(); return vec;
osg::Quat getQuaternion(); }
Transformation getTrafo(); osg::Vec3f getVector3() {
osg::Vec3f vec;
readLittleEndianBufferOfType<3, float,uint32_t>(inp, (float*)&vec._v[0]);
return vec;
}
osg::Vec4f getVector4() {
osg::Vec4f vec;
readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&vec._v[0]);
return vec;
}
Matrix3 getMatrix3() {
Matrix3 mat;
readLittleEndianBufferOfType<9, float,uint32_t>(inp, (float*)&mat.mValues);
return mat;
}
osg::Quat getQuaternion() {
float f[4];
readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&f);
osg::Quat quat;
quat.w() = f[0];
quat.x() = f[1];
quat.y() = f[2];
quat.z() = f[3];
return quat;
}
Transformation getTrafo() {
Transformation t;
t.pos = getVector3();
t.rotation = getMatrix3();
t.scale = getFloat();
return t;
}
///Read in a string of the given length ///Read in a string of the given length
std::string getString(size_t length); std::string getString(size_t length) {
///Read in a string of the length specified in the file std::vector<char> str(length + 1, 0);
std::string getString();
///This is special since the version string doesn't start with a number, and ends with "\n"
std::string getVersionString();
void getUShorts(std::vector<unsigned short> &vec, size_t size); inp->read(&str[0], length);
void getFloats(std::vector<float> &vec, size_t size);
void getVector2s(std::vector<osg::Vec2f> &vec, size_t size); return &str[0];
void getVector3s(std::vector<osg::Vec3f> &vec, size_t size); }
void getVector4s(std::vector<osg::Vec4f> &vec, size_t size); ///Read in a string of the length specified in the file
void getQuaternions(std::vector<osg::Quat> &quat, size_t size); std::string getString() {
size_t size = readLittleEndianType<uint32_t,uint32_t>(inp);
return getString(size);
}
///This is special since the version string doesn't start with a number, and ends with "\n"
std::string getVersionString() {
std::string result;
std::getline(*inp, result);
return result;
}
void getUShorts(std::vector<unsigned short> &vec, size_t size) {
vec.resize(size);
readLittleEndianDynamicBufferOfType<unsigned short,unsigned short>(inp, &vec.front(), size);
}
void getFloats(std::vector<float> &vec, size_t size) {
vec.resize(size);
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, &vec.front(), size);
}
void getVector2s(std::vector<osg::Vec2f> &vec, size_t size) {
vec.resize(size);
/* The packed storage of each Vec2f is 2 floats exactly */
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp,(float*) &vec.front(), size*2);
}
void getVector3s(std::vector<osg::Vec3f> &vec, size_t size) {
vec.resize(size);
/* The packed storage of each Vec3f is 3 floats exactly */
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, (float*) &vec.front(), size*3);
}
void getVector4s(std::vector<osg::Vec4f> &vec, size_t size) {
vec.resize(size);
/* The packed storage of each Vec4f is 4 floats exactly */
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, (float*) &vec.front(), size*4);
}
void getQuaternions(std::vector<osg::Quat> &quat, size_t size) {
quat.resize(size);
for (size_t i = 0;i < quat.size();i++)
quat[i] = getQuaternion();
}
}; };
} }