1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-25 15:35:23 +00:00
OpenMW/apps/openmw/mwworld/projectilemanager.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

804 lines
31 KiB
C++
Raw Normal View History

#include "projectilemanager.hpp"
2016-09-05 02:52:00 +09:00
#include <iomanip>
#include <memory>
#include <optional>
#include <sstream>
2015-06-01 21:41:13 +02:00
#include <osg/PositionAttitudeTransform>
2018-08-14 23:05:43 +04:00
#include <components/debug/debuglog.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadrace.hpp>
#include <components/esm3/projectilestate.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/convert.hpp>
#include <components/misc/resourcehelpers.hpp>
2015-06-01 21:41:13 +02:00
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
2015-06-01 21:41:13 +02:00
#include <components/sceneutil/controller.hpp>
2015-12-05 00:44:04 +01:00
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/nodecallback.hpp>
#include <components/sceneutil/visitor.hpp>
2014-05-17 05:21:17 +02:00
2023-06-27 23:41:06 +02:00
#include <components/settings/values.hpp>
#include "../mwworld/class.hpp"
2015-02-09 15:01:49 +01:00
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
2015-08-21 21:12:39 +12:00
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/combat.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/weapontype.hpp"
#include "../mwrender/animation.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "../mwrender/util.hpp"
#include "../mwrender/vismask.hpp"
#include "../mwsound/sound.hpp"
2015-06-01 21:41:13 +02:00
#include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/projectile.hpp"
2015-06-01 21:41:13 +02:00
namespace
{
ESM::EffectList getMagicBoltData(std::vector<ESM::RefId>& projectileIDs, std::set<ESM::RefId>& sounds, float& speed,
std::string& texture, std::string& sourceName, const ESM::RefId& id)
{
2023-04-20 21:07:53 +02:00
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
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;
2016-09-15 01:44:53 +09:00
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
2023-04-20 21:07:53 +02:00
= MWBase::Environment::get().getESMStore()->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())
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
projectileIDs.emplace_back(ESM::RefId::stringRefId("VFX_DefaultBolt"));
else
projectileIDs.push_back(magicEffect->mBolt);
if (!magicEffect->mBoltSound.empty())
sounds.emplace(magicEffect->mBoltSound);
else
2023-06-26 20:42:52 +02:00
sounds.emplace(MWBase::Environment::get()
.getESMStore()
->get<ESM::Skill>()
.find(magicEffect->mData.mSchool)
->mSchool->mBoltSound);
projectileEffects.mList.push_back(*iter);
}
2022-09-22 21:26:05 +03:00
if (count != 0)
speed /= count;
// the particle texture is only used if there is only one projectile
2016-09-08 02:24:47 +09:00
if (projectileEffects.mList.size() == 1)
{
const ESM::MagicEffect* magicEffect
2023-04-20 21:07:53 +02:00
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(
effects->mList.begin()->mEffectID);
texture = magicEffect->mParticle;
}
2022-09-22 21:26:05 +03:00
if (projectileEffects.mList.size()
> 1) // insert a VFX_Multiple projectile if there are multiple projectile effects
{
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
const ESM::RefId ID = ESM::RefId::stringRefId("VFX_Multiple" + std::to_string(effects->mList.size()));
std::vector<ESM::RefId>::iterator it;
it = projectileIDs.begin();
2019-01-07 17:47:39 +04:00
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
2023-04-20 21:07:53 +02:00
= MWBase::Environment::get().getESMStore()->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;
}
}
2015-06-01 21:41:13 +02:00
namespace MWWorld
{
ProjectileManager::ProjectileManager(osg::Group* parent, Resource::ResourceSystem* resourceSystem,
MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics)
2015-06-01 21:41:13 +02:00
: mParent(parent)
, mResourceSystem(resourceSystem)
, mRendering(rendering)
2015-06-01 21:41:13 +02:00
, mPhysics(physics)
2017-09-23 18:54:17 +02:00
, mCleanupTimer(0.0f)
{
}
2015-12-05 00:03:25 +01:00
/// Rotates an osg::PositionAttitudeTransform over time.
class RotateCallback : public SceneUtil::NodeCallback<RotateCallback, osg::PositionAttitudeTransform*>
2015-12-05 00:03:25 +01:00
{
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)
2015-12-05 00:03:25 +01:00
{
double time = nv->getFrameStamp()->getSimulationTime();
osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis);
node->setAttitude(orient);
2015-12-05 00:03:25 +01:00
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)
{
2015-06-01 21:41:13 +02:00
state.mNode = new osg::PositionAttitudeTransform;
state.mNode->setNodeMask(MWRender::Mask_Effect);
state.mNode->setPosition(pos);
state.mNode->setAttitude(orient);
2016-12-03 19:09:03 -07:00
osg::Group* attachTo = state.mNode;
2015-12-05 00:03:25 +01:00
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);
2015-12-05 00:03:25 +01:00
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)
{
2016-09-05 02:52:00 +09:00
std::ostringstream nodeName;
nodeName << "Dummy" << std::setw(2) << std::setfill('0') << iter;
2023-04-20 21:07:53 +02:00
const ESM::Weapon* weapon
= MWBase::Environment::get().getESMStore()->get<ESM::Weapon>().find(state.mIdMagic.at(iter));
2022-08-21 18:53:38 +02:00
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)
2016-12-03 19:09:03 -07:00
{
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);
2016-12-03 19:09:03 -07:00
projectileLight->setPosition(osg::Vec4(pos, 1.0));
2022-09-22 21:26:05 +03:00
2016-12-03 19:09:03 -07:00
SceneUtil::LightSource* projectileLightSource = new SceneUtil::LightSource;
projectileLightSource->setNodeMask(MWRender::Mask_Lighting);
2016-12-03 19:09:03 -07:00
projectileLightSource->setRadius(66.f);
2022-09-22 21:26:05 +03:00
2016-12-03 19:09:03 -07:00
state.mNode->addChild(projectileLightSource);
projectileLightSource->setLight(projectileLight);
}
2015-12-05 00:44:04 +01:00
state.mNode->addCullCallback(new SceneUtil::LightListCallback);
2015-12-05 00:03:25 +01:00
mParent->addChild(state.mNode);
2015-06-01 21:41:13 +02:00
state.mEffectAnimationTime = std::make_shared<MWRender::EffectAnimationTime>();
2015-06-01 21:41:13 +02:00
SceneUtil::AssignControllerSourcesVisitor assignVisitor(state.mEffectAnimationTime);
state.mNode->accept(assignVisitor);
MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile);
}
2015-06-01 21:41:13 +02:00
void ProjectileManager::update(State& state, float duration)
{
2015-06-01 21:41:13 +02:00
state.mEffectAnimationTime->addTime(duration);
}
void ProjectileManager::launchMagicBolt(
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
const ESM::RefId& 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;
2015-06-01 21:41:13 +02:00
osg::Quat orient;
if (caster.getClass().isActor())
2015-06-01 21:41:13 +02:00
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
2015-06-01 21:41:13 +02:00
orient.makeRotate(osg::Vec3f(0, 1, 0), osg::Vec3f(fallbackDirection));
MagicBoltState state;
2014-05-17 05:21:17 +02:00
state.mSpellId = spellId;
2015-06-01 21:41:13 +02:00
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
2016-09-05 00:04:11 +02:00
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;
}
2023-04-20 21:07:53 +02:00
MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), 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();
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
for (const auto& 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(
2023-04-20 21:07:53 +02:00
MWBase::Environment::get().getESMStore()->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);
}
2021-06-23 23:13:59 +02:00
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();
2015-06-01 21:41:13 +02:00
state.mVelocity = orient * osg::Vec3f(0, 1, 0) * speed;
state.mIdArrow = projectile.getCellRef().getRefId();
2015-06-01 21:41:13 +02:00
state.mCasterHandle = actor;
state.mAttackStrength = attackStrength;
int type = projectile.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown;
2023-04-20 21:07:53 +02:00
MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), 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)
{
2017-09-23 18:54:17 +02:00
periodicCleanup(dt);
moveProjectiles(dt);
moveMagicBolts(dt);
}
2017-09-23 18:54:17 +02:00
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)
2017-09-23 18:54:17 +02:00
{
if (isCleanable(projectileState))
cleanupProjectile(projectileState);
2017-09-23 18:54:17 +02:00
}
for (auto& magicBoltState : mMagicBolts)
2017-09-23 18:54:17 +02:00
{
if (isCleanable(magicBoltState))
cleanupMagicBolt(magicBoltState);
2017-09-23 18:54:17 +02:00
}
}
}
void ProjectileManager::moveMagicBolts(float duration)
{
2023-06-27 23:41:06 +02:00
const bool normaliseRaceSpeed = Settings::game().mNormaliseRaceSpeed;
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;
}
}
2023-04-20 21:07:53 +02:00
const auto& store = *MWBase::Environment::get().getESMStore();
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;
}
2015-06-01 21:41:13 +02:00
osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0);
direction.normalize();
projectile->setVelocity(direction * speed);
update(magicBoltState, duration);
2015-06-01 21:41:13 +02:00
// 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)
{
2021-01-24 15:15:51 +01:00
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);
}
2017-11-23 00:32:22 +01:00
update(projectileState, duration);
2015-06-01 21:41:13 +02:00
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.
2023-04-20 21:07:53 +02:00
MWWorld::ManualRef projectileRef(*MWBase::Environment::get().getESMStore(), 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() && invIt->getCellRef().getRefId() == projectileState.mBowId)
bow = *invIt;
}
const auto hitPosition = Misc::Convert::toOsg(projectile->getHitPosition());
if (projectile->getHitWater())
mRendering->emitWaterRipple(hitPosition);
MWMechanics::projectileHit(
caster, target, bow, projectileRef.getPtr(), hitPosition, projectileState.mAttackStrength);
projectileState.mToDelete = true;
}
2023-04-20 21:07:53 +02:00
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
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 = Misc::Convert::toOsg(projectile->getHitPosition());
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
2022-08-22 17:44:49 +02:00
{
MWWorld::ManualRef ref(esmStore, magicBoltState.mSpellId);
const MWWorld::Ptr& ptr = ref.getPtr();
effects = &esmStore.get<ESM::Enchantment>().find(ptr.getClass().getEnchantment(ptr))->mEffects;
}
2022-09-04 14:51:19 +02:00
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());
}
2017-09-23 18:54:17 +02:00
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
{
mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
2017-09-23 18:54:17 +02:00
}
void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state)
{
mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
2017-09-23 18:54:17 +02:00
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();
}
2014-05-17 05:21:17 +02:00
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;
2015-06-01 21:41:13 +02:00
state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition()));
state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude()));
2014-05-17 05:21:17 +02:00
state.mActorId = it->mActorId;
state.mBowId = it->mBowId;
state.mVelocity = it->mVelocity;
state.mAttackStrength = it->mAttackStrength;
2014-05-17 05:21:17 +02:00
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);
2015-06-01 21:41:13 +02:00
state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition()));
state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude()));
2014-05-17 05:21:17 +02:00
state.mActorId = it->mActorId;
state.mSlot = it->mSlot;
2014-05-17 05:21:17 +02:00
state.mSpellId = it->mSpellId;
state.mSpeed = it->mSpeed;
state.save(writer);
writer.endRecord(ESM::REC_MPRJ);
}
}
2015-01-22 19:04:59 +01:00
bool ProjectileManager::readRecord(ESM::ESMReader& reader, uint32_t type)
2014-05-17 05:21:17 +02:00
{
2016-12-03 19:09:03 -07:00
if (type == ESM::REC_PROJ)
2014-05-17 05:21:17 +02:00
{
2016-12-03 19:09:03 -07:00
ESM::ProjectileState esm;
2014-05-17 05:21:17 +02:00
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;
2014-05-17 05:21:17 +02:00
std::string model;
try
{
2023-04-20 21:07:53 +02:00
MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), esm.mId);
2014-05-17 05:21:17 +02:00
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);
2014-05-17 05:21:17 +02:00
}
catch (...)
{
return true;
}
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false, false,
osg::Vec4(0, 0, 0, 0));
2014-05-17 05:21:17 +02:00
mProjectiles.push_back(state);
return true;
}
if (type == ESM::REC_MPRJ)
2014-05-17 05:21:17 +02:00
{
2016-12-03 19:09:03 -07:00
ESM::MagicBoltState esm;
2014-05-17 05:21:17 +02:00
esm.load(reader);
MagicBoltState state;
state.mIdMagic.push_back(esm.mId);
2014-05-17 05:21:17 +02:00
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 (...)
{
2018-08-14 23:05:43 +04:00
Log(Debug::Warning) << "Warning: Failed to recreate magic projectile from saved data (id \""
<< state.mSpellId << "\" no longer exists?)";
return true;
}
2016-09-05 05:41:24 +09:00
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.
2014-05-17 05:21:17 +02:00
std::string model;
try
{
2023-04-20 21:07:53 +02:00
MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), state.mIdMagic.at(0));
2014-05-17 05:21:17 +02:00
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);
2014-05-17 05:21:17 +02:00
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
for (const auto& 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);
}
2014-05-17 05:21:17 +02:00
mMagicBolts.push_back(state);
return true;
}
return false;
}
int ProjectileManager::countSavedGameRecords() const
{
return mMagicBolts.size() + mProjectiles.size();
}
2015-06-01 21:41:13 +02:00
MWWorld::Ptr ProjectileManager::State::getCaster()
{
if (!mCasterHandle.isEmpty())
return mCasterHandle;
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId);
}
}