1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-26 18:35:20 +00:00
OpenMW/apps/openmw/mwworld/projectilemanager.cpp
florent.teppe 1ced0c912e partially revert "Store: moved all the template specialization to its own heaper file, included where it's needed"
This reverts commit 80a25bcd3021f7ebfaf2f864e34532009b9b8aeb.
It didn't really make sense to do all those changes in the same MR

partially Revert "Store refactoring: more forgotten storeSpecialization.hpp"

This reverts commit 9943a5bc96b9025f06cbaac5bb7f1bf51ebc746f.

removed remaining references to storeSpecialization  CMakeLists.txt,  and landmanager.cpp
2022-09-05 17:35:06 +02:00

779 lines
31 KiB
C++

#include "projectilemanager.hpp"
#include <iomanip>
#include <sstream>
#include <memory>
#include <optional>
#include <osg/PositionAttitudeTransform>
#include <components/debug/debuglog.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm3/projectilestate.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/convert.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/controller.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/nodecallback.hpp>
#include <components/settings/settings.hpp>
#include "../mwworld/manualref.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/combat.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/aipackage.hpp"
#include "../mwmechanics/weapontype.hpp"
#include "../mwrender/animation.hpp"
#include "../mwrender/vismask.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "../mwrender/util.hpp"
#include "../mwsound/sound.hpp"
#include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/projectile.hpp"
namespace
{
ESM::EffectList getMagicBoltData(std::vector<std::string>& projectileIDs, std::set<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)
{
const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
iter->mEffectID);
// Speed of multi-effect projectiles should be the average of the constituent effects,
// based on observation of the original engine.
speed += magicEffect->mData.mSpeed;
count++;
if (iter->mRange != ESM::RT_Target)
continue;
if (magicEffect->mBolt.empty())
projectileIDs.emplace_back("VFX_DefaultBolt");
else
projectileIDs.push_back(magicEffect->mBolt);
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
if (!magicEffect->mBoltSound.empty())
sounds.emplace(magicEffect->mBoltSound);
else
sounds.emplace(schools[magicEffect->mData.mSchool] + " bolt");
projectileEffects.mList.push_back(*iter);
}
if (count != 0)
speed /= count;
// the particle texture is only used if there is only one projectile
if (projectileEffects.mList.size() == 1)
{
const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effects->mList.begin()->mEffectID);
texture = magicEffect->mParticle;
}
if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects
{
const std::string ID = "VFX_Multiple" + std::to_string(effects->mList.size());
std::vector<std::string>::iterator it;
it = projectileIDs.begin();
it = projectileIDs.insert(it, ID);
}
return projectileEffects;
}
osg::Vec4 getMagicBoltLightDiffuseColor(const ESM::EffectList& effects)
{
// Calculate combined light diffuse color from magical effects
osg::Vec4 lightDiffuseColor;
float lightDiffuseRed = 0.0f;
float lightDiffuseGreen = 0.0f;
float lightDiffuseBlue = 0.0f;
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);
lightDiffuseRed += (static_cast<float>(magicEffect->mData.mRed) / 255.f);
lightDiffuseGreen += (static_cast<float>(magicEffect->mData.mGreen) / 255.f);
lightDiffuseBlue += (static_cast<float>(magicEffect->mData.mBlue) / 255.f);
}
int numberOfEffects = effects.mList.size();
lightDiffuseColor = osg::Vec4(lightDiffuseRed / numberOfEffects
, lightDiffuseGreen / numberOfEffects
, lightDiffuseBlue / numberOfEffects
, 1.0f);
return lightDiffuseColor;
}
}
namespace MWWorld
{
ProjectileManager::ProjectileManager(osg::Group* parent, Resource::ResourceSystem* resourceSystem,
MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics)
: mParent(parent)
, mResourceSystem(resourceSystem)
, mRendering(rendering)
, mPhysics(physics)
, mCleanupTimer(0.0f)
{
}
/// Rotates an osg::PositionAttitudeTransform over time.
class RotateCallback : public SceneUtil::NodeCallback<RotateCallback, osg::PositionAttitudeTransform*>
{
public:
RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0,-1,0), float rotateSpeed = osg::PI*2)
: mAxis(axis)
, mRotateSpeed(rotateSpeed)
{
}
void operator()(osg::PositionAttitudeTransform* node, osg::NodeVisitor* nv)
{
double time = nv->getFrameStamp()->getSimulationTime();
osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis);
node->setAttitude(orient);
traverse(node, nv);
}
private:
osg::Vec3f mAxis;
float mRotateSpeed;
};
void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient,
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture)
{
state.mNode = new osg::PositionAttitudeTransform;
state.mNode->setNodeMask(MWRender::Mask_Effect);
state.mNode->setPosition(pos);
state.mNode->setAttitude(orient);
osg::Group* attachTo = state.mNode;
if (rotate)
{
osg::ref_ptr<osg::PositionAttitudeTransform> rotateNode (new osg::PositionAttitudeTransform);
rotateNode->addUpdateCallback(new RotateCallback());
state.mNode->addChild(rotateNode);
attachTo = rotateNode;
}
osg::ref_ptr<osg::Node> projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo);
if (state.mIdMagic.size() > 1)
{
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter)
{
std::ostringstream nodeName;
nodeName << "Dummy" << std::setw(2) << std::setfill('0') << iter;
const ESM::Weapon* weapon = MWBase::Environment::get().getWorld()->getStore().get<ESM::Weapon>().find (state.mIdMagic.at(iter));
std::string nameToFind = nodeName.str();
SceneUtil::FindByNameVisitor findVisitor(nameToFind);
attachTo->accept(findVisitor);
if (findVisitor.mFoundNode)
mResourceSystem->getSceneManager()->getInstance(
Misc::ResourceHelpers::correctMeshPath(weapon->mModel, vfs), findVisitor.mFoundNode);
}
}
if (createLight)
{
osg::ref_ptr<osg::Light> projectileLight(new osg::Light);
projectileLight->setAmbient(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
projectileLight->setDiffuse(lightDiffuseColor);
projectileLight->setSpecular(osg::Vec4(0.0f, 0.0f, 0.0f, 0.0f));
projectileLight->setConstantAttenuation(0.f);
projectileLight->setLinearAttenuation(0.1f);
projectileLight->setQuadraticAttenuation(0.f);
projectileLight->setPosition(osg::Vec4(pos, 1.0));
SceneUtil::LightSource* projectileLightSource = new SceneUtil::LightSource;
projectileLightSource->setNodeMask(MWRender::Mask_Lighting);
projectileLightSource->setRadius(66.f);
state.mNode->addChild(projectileLightSource);
projectileLightSource->setLight(projectileLight);
}
state.mNode->addCullCallback(new SceneUtil::LightListCallback);
mParent->addChild(state.mNode);
state.mEffectAnimationTime = std::make_shared<MWRender::EffectAnimationTime>();
SceneUtil::AssignControllerSourcesVisitor assignVisitor (state.mEffectAnimationTime);
state.mNode->accept(assignVisitor);
MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile);
}
void ProjectileManager::update(State& state, float duration)
{
state.mEffectAnimationTime->addTime(duration);
}
void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection, int slot)
{
osg::Vec3f pos = caster.getRefData().getPosition().asVec3();
if (caster.getClass().isActor())
{
// Note: we ignore the collision box offset, this is required to make some flying creatures work as intended.
pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * Constants::TorsoHeight;
}
if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible
return;
osg::Quat orient;
if (caster.getClass().isActor())
orient = osg::Quat(caster.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
* osg::Quat(caster.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
else
orient.makeRotate(osg::Vec3f(0,1,0), osg::Vec3f(fallbackDirection));
MagicBoltState state;
state.mSpellId = spellId;
state.mCasterHandle = caster;
state.mSlot = slot;
if (caster.getClass().isActor())
state.mActorId = caster.getClass().getCreatureStats(caster).getActorId();
else
state.mActorId = -1;
std::string texture;
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())
return;
if (!caster.getClass().isActor() && fallbackDirection.length2() <= 0)
{
Log(Debug::Warning) << "Unable to launch magic bolt (direction to target is empty)";
return;
}
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr();
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
auto model = ptr.getClass().getModel(ptr);
createModel(state, model, pos, orient, true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (const std::string &soundid : state.mSoundIds)
{
MWBase::Sound *sound = sndMgr->playSound3D(pos, soundid, 1.0f, 1.0f,
MWSound::Type::Sfx, MWSound::PlayMode::Loop);
if (sound)
state.mSounds.push_back(sound);
}
// in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape
if (state.mIdMagic.size() > 1)
{
model = Misc::ResourceHelpers::correctMeshPath(
MWBase::Environment::get().getWorld()->getStore().get<ESM::Weapon>().find(state.mIdMagic[1])->mModel,
MWBase::Environment::get().getResourceSystem()->getVFS());
}
state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true);
state.mToDelete = false;
mMagicBolts.push_back(state);
}
void ProjectileManager::launchProjectile(const Ptr& actor, const ConstPtr& projectile, const osg::Vec3f &pos, const osg::Quat &orient, const Ptr& bow, float speed, float attackStrength)
{
ProjectileState state;
state.mActorId = actor.getClass().getCreatureStats(actor).getActorId();
state.mBowId = bow.getCellRef().getRefId();
state.mVelocity = orient * osg::Vec3f(0,1,0) * speed;
state.mIdArrow = projectile.getCellRef().getRefId();
state.mCasterHandle = actor;
state.mAttackStrength = attackStrength;
int type = projectile.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown;
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId());
MWWorld::Ptr ptr = ref.getPtr();
const auto model = ptr.getClass().getModel(ptr);
createModel(state, model, pos, orient, false, false, osg::Vec4(0,0,0,0));
if (!ptr.getClass().getEnchantment(ptr).empty())
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false);
state.mToDelete = false;
mProjectiles.push_back(state);
}
void ProjectileManager::updateCasters()
{
for (auto& state : mProjectiles)
mPhysics->setCaster(state.mProjectileId, state.getCaster());
for (auto& state : mMagicBolts)
{
// casters are identified by actor id in the savegame. objects doesn't have one so they can't be identified back.
// TODO: should object-type caster be restored from savegame?
if (state.mActorId == -1)
continue;
auto caster = state.getCaster();
if (caster.isEmpty())
{
Log(Debug::Error) << "Couldn't find caster with ID " << state.mActorId;
cleanupMagicBolt(state);
continue;
}
mPhysics->setCaster(state.mProjectileId, caster);
}
}
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 (auto& projectileState : mProjectiles)
{
if (isCleanable(projectileState))
cleanupProjectile(projectileState);
}
for (auto& magicBoltState : mMagicBolts)
{
if (isCleanable(magicBoltState))
cleanupMagicBolt(magicBoltState);
}
}
}
void ProjectileManager::moveMagicBolts(float duration)
{
static const bool normaliseRaceSpeed = Settings::Manager::getBool("normalise race speed", "Game");
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (!projectile->isActive())
continue;
// If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame.
MWWorld::Ptr caster = magicBoltState.getCaster();
if (!caster.isEmpty() && caster.getClass().isActor())
{
if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead())
{
cleanupMagicBolt(magicBoltState);
continue;
}
}
const auto& store = MWBase::Environment::get().getWorld()->getStore();
osg::Quat orient = magicBoltState.mNode->getAttitude();
static float fTargetSpellMaxSpeed = store.get<ESM::GameSetting>().find("fTargetSpellMaxSpeed")->mValue.getFloat();
float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed;
if (!normaliseRaceSpeed && !caster.isEmpty() && caster.getClass().isNpc())
{
const auto npc = caster.get<ESM::NPC>()->mBase;
const auto race = store.get<ESM::Race>().find(npc->mRace);
speed *= npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale;
}
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
direction.normalize();
projectile->setVelocity(direction * speed);
update(magicBoltState, duration);
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors;
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
}
}
void ProjectileManager::moveProjectiles(float duration)
{
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (!projectile->isActive())
continue;
// gravity constant - must be way lower than the gravity affecting actors, since we're not
// simulating aerodynamics at all
projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration;
projectile->setVelocity(projectileState.mVelocity);
// rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction.
if (!projectileState.mThrown)
{
osg::Quat orient;
orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity);
projectileState.mNode->setAttitude(orient);
}
update(projectileState, duration);
MWWorld::Ptr caster = projectileState.getCaster();
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors;
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
}
}
void ProjectileManager::processHits()
{
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
const auto pos = projectile->getSimulationPosition();
projectileState.mNode->setPosition(pos);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
auto caster = projectileState.getCaster();
assert(target != caster);
if (caster.isEmpty())
caster = target;
// Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt;
}
if (projectile->getHitWater())
mRendering->emitWaterRipple(pos);
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
projectileState.mToDelete = true;
}
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
const auto pos = projectile->getSimulationPosition();
magicBoltState.mNode->setPosition(pos);
for (const auto& sound : magicBoltState.mSounds)
sound->setPosition(pos);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto caster = magicBoltState.getCaster();
assert(target != caster);
MWMechanics::CastSpell cast(caster, target);
cast.mHitPosition = pos;
cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName;
cast.mSlot = magicBoltState.mSlot;
// Grab original effect list so the indices are correct
const ESM::EffectList* effects;
if (const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(magicBoltState.mSpellId))
effects = &spell->mEffects;
else
{
MWWorld::ManualRef ref(esmStore, magicBoltState.mSpellId);
const MWWorld::Ptr& ptr = ref.getPtr();
effects = &esmStore.get<ESM::Enchantment>().find(ptr.getClass().getEnchantment(ptr))->mEffects;
}
cast.inflict(target, *effects, ESM::RT_Target);
magicBoltState.mToDelete = true;
}
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
cleanupProjectile(projectileState);
}
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
cleanupMagicBolt(magicBoltState);
}
mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }),
mProjectiles.end());
mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }),
mMagicBolts.end());
}
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
{
mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
}
void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state)
{
mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++)
{
MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter));
}
}
void ProjectileManager::clear()
{
for (auto& mProjectile : mProjectiles)
cleanupProjectile(mProjectile);
mProjectiles.clear();
for (auto& mMagicBolt : mMagicBolts)
cleanupMagicBolt(mMagicBolt);
mMagicBolts.clear();
}
void ProjectileManager::write(ESM::ESMWriter &writer, Loading::Listener &progress) const
{
for (std::vector<ProjectileState>::const_iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{
writer.startRecord(ESM::REC_PROJ);
ESM::ProjectileState state;
state.mId = it->mIdArrow;
state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition()));
state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude()));
state.mActorId = it->mActorId;
state.mBowId = it->mBowId;
state.mVelocity = it->mVelocity;
state.mAttackStrength = it->mAttackStrength;
state.save(writer);
writer.endRecord(ESM::REC_PROJ);
}
for (std::vector<MagicBoltState>::const_iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{
writer.startRecord(ESM::REC_MPRJ);
ESM::MagicBoltState state;
state.mId = it->mIdMagic.at(0);
state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition()));
state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude()));
state.mActorId = it->mActorId;
state.mSlot = it->mSlot;
state.mSpellId = it->mSpellId;
state.mSpeed = it->mSpeed;
state.save(writer);
writer.endRecord(ESM::REC_MPRJ);
}
}
bool ProjectileManager::readRecord(ESM::ESMReader &reader, uint32_t type)
{
if (type == ESM::REC_PROJ)
{
ESM::ProjectileState esm;
esm.load(reader);
ProjectileState state;
state.mActorId = esm.mActorId;
state.mBowId = esm.mBowId;
state.mVelocity = esm.mVelocity;
state.mIdArrow = esm.mId;
state.mAttackStrength = esm.mAttackStrength;
state.mToDelete = false;
std::string model;
try
{
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown;
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false);
}
catch(...)
{
return true;
}
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false, false, osg::Vec4(0,0,0,0));
mProjectiles.push_back(state);
return true;
}
if (type == ESM::REC_MPRJ)
{
ESM::MagicBoltState esm;
esm.load(reader);
MagicBoltState state;
state.mIdMagic.push_back(esm.mId);
state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId;
state.mToDelete = false;
state.mSlot = esm.mSlot;
std::string texture;
try
{
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId);
}
catch(...)
{
Log(Debug::Warning) << "Warning: Failed to recreate magic projectile from saved data (id \"" << state.mSpellId << "\" no longer exists?)";
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
// effects. We need to use the stored value.
std::string model;
try
{
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
}
catch(...)
{
return true;
}
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (const std::string &soundid : state.mSoundIds)
{
MWBase::Sound *sound = sndMgr->playSound3D(esm.mPosition, soundid, 1.0f, 1.0f,
MWSound::Type::Sfx, MWSound::PlayMode::Loop);
if (sound)
state.mSounds.push_back(sound);
}
mMagicBolts.push_back(state);
return true;
}
return false;
}
int ProjectileManager::countSavedGameRecords() const
{
return mMagicBolts.size() + mProjectiles.size();
}
MWWorld::Ptr ProjectileManager::State::getCaster()
{
if (!mCasterHandle.isEmpty())
return mCasterHandle;
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId);
}
}