Merge pull request #1470 from MiroslavR/convertmprj

ESS-Importer: Convert magic projectiles
pull/298/head
scrawl 7 years ago committed by GitHub
commit 2494c22d66

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

@ -1,12 +1,12 @@
#include "converter.hpp"
#include <stdexcept>
#include <algorithm>
#include <osgDB/WriteFile>
#include <components/esm/creaturestate.hpp>
#include <components/esm/containerstate.hpp>
#include <components/esm/projectilestate.hpp>
#include "convertcrec.hpp"
#include "convertcntc.hpp"
@ -461,11 +461,7 @@ namespace ESSImport
if (!pnam.isMagic())
{
ESM::ProjectileState out;
out.mId = pnam.mArrowId.toString();
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);
convertBaseState(out, pnam);
out.mBowId = pnam.mBowId.toString();
out.mVelocity = pnam.mVelocity;
@ -477,16 +473,49 @@ namespace ESSImport
}
else
{
// TODO: Implement magic projectile conversion.
ESM::MagicBoltState out;
convertBaseState(out, pnam);
/*esm.startRecord(ESM::REC_MPRJ);
out.save(esm);
esm.endRecord(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; });
std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() << "\" (not implemented)" << std::endl;
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);
esm.endRecord(ESM::REC_MPRJ);
}
}
}
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;
}
}

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

@ -271,6 +271,7 @@ namespace ESSImport
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 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;
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[ESM::REC_SCPT] = std::shared_ptr<Converter>(new ConvertSCPT());
converters[ESM::REC_PROJ] = std::shared_ptr<Converter>(new ConvertPROJ());
converters[recSPLM] = std::shared_ptr<Converter>(new ConvertSPLM());
// TODO:
// - REGN (weather in certain regions?)
// - VFXM
// - SPLM (active spell effects)
// - PROJ (magic projectiles in air)
std::set<unsigned int> unknownRecords;

@ -15,7 +15,7 @@
#include "importcrec.hpp"
#include "importcntc.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::NPC> mNpcs;
std::vector<SPLM::ActiveSpell> mActiveSpells;
Context()
: mDay(0)
, mMonth(0)

@ -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);
}
}
}

@ -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

@ -487,8 +487,7 @@ namespace MWBase
virtual void castSpell (const MWWorld::Ptr& actor) = 0;
virtual void launchMagicBolt (const std::string& spellId, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0;
virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0;
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;

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

@ -106,7 +106,7 @@ namespace MWMechanics
void playSpellCastingEffects(const std::string &spellid);
/// 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 caster can be any type of object, or even an empty object.

@ -1,6 +1,7 @@
#include "projectilemanager.hpp"
#include <iomanip>
#include <iostream>
#include <osg/PositionAttitudeTransform>
@ -41,13 +42,29 @@
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;
speed = 0.0f;
ESM::EffectList projectileEffects;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
iter!=effects.mList.end(); ++iter)
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects->mList.begin());
iter!=effects->mList.end(); ++iter)
{
const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
iter->mEffectID);
@ -82,14 +99,14 @@ namespace
if (projectileEffects.mList.size() == 1)
{
const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effects.mList.begin()->mEffectID);
effects->mList.begin()->mEffectID);
texture = magicEffect->mParticle;
}
if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects
{
std::ostringstream ID;
ID << "VFX_Multiple" << effects.mList.size();
ID << "VFX_Multiple" << effects->mList.size();
std::vector<std::string>::iterator it;
it = projectileIDs.begin();
it = projectileIDs.insert(it, ID.str());
@ -132,6 +149,7 @@ namespace MWWorld
, mResourceSystem(resourceSystem)
, mRendering(rendering)
, mPhysics(physics)
, mCleanupTimer(0.0f)
{
}
@ -235,8 +253,7 @@ namespace MWWorld
state.mEffectAnimationTime->addTime(duration);
}
void ProjectileManager::launchMagicBolt(const std::string &spellId, bool stack, const ESM::EffectList &effects, const Ptr &caster,
const std::string &sourceName, const osg::Vec3f& fallbackDirection)
void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection)
{
osg::Vec3f pos = caster.getRefData().getPosition().asVec3();
if (caster.getClass().isActor())
@ -257,18 +274,16 @@ namespace MWWorld
orient.makeRotate(osg::Vec3f(0,1,0), osg::Vec3f(fallbackDirection));
MagicBoltState state;
state.mSourceName = sourceName;
state.mSpellId = spellId;
state.mCasterHandle = caster;
if (caster.getClass().isActor())
state.mActorId = caster.getClass().getCreatureStats(caster).getActorId();
else
state.mActorId = -1;
state.mStack = stack;
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
if (state.mEffects.mList.empty())
@ -277,7 +292,7 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
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);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
@ -312,10 +327,49 @@ namespace MWWorld
void ProjectileManager::update(float dt)
{
periodicCleanup(dt);
moveProjectiles(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)
{
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
@ -364,7 +418,7 @@ namespace MWWorld
cast.mHitPosition = pos;
cast.mId = it->mSpellId;
cast.mSourceName = it->mSourceName;
cast.mStack = it->mStack;
cast.mStack = false;
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()
{
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{
mParent->removeChild(it->mNode);
cleanupProjectile(*it);
}
mProjectiles.clear();
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{
mParent->removeChild(it->mNode);
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++)
{
MWBase::Environment::get().getSoundManager()->stopSound(it->mSounds.at(soundIter));
}
cleanupMagicBolt(*it);
}
mMagicBolts.clear();
}
@ -504,11 +568,7 @@ namespace MWWorld
state.mActorId = it->mActorId;
state.mSpellId = it->mSpellId;
state.mEffects = it->mEffects;
state.mSound = it->mSoundIds.at(0);
state.mSourceName = it->mSourceName;
state.mSpeed = it->mSpeed;
state.mStack = it->mStack;
state.save(writer);
@ -553,13 +613,21 @@ namespace MWWorld
esm.load(reader);
MagicBoltState state;
state.mSourceName = esm.mSourceName;
state.mIdMagic.push_back(esm.mId);
state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId;
state.mStack = esm.mStack;
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
// projectile effects, so we can't calculate it from the save
// file's effect list, which is already trimmed of non-projectile
@ -577,7 +645,7 @@ namespace MWWorld
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);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

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

@ -2816,10 +2816,9 @@ namespace MWWorld
mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength);
}
void World::launchMagicBolt (const std::string &spellId, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection)
void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, 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

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

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

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

Loading…
Cancel
Save