Merge branch 'add_loop_and_vfx_id_to_world_vfx_spawn' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled

add loop + vfxId params to world.vfx.spawn and add world.vfx.remove

See merge request OpenMW/openmw!4503
This commit is contained in:
fallchildren 2025-04-25 17:18:14 +00:00
commit a5b8c4b736
10 changed files with 113 additions and 35 deletions

View file

@ -516,9 +516,12 @@ namespace MWBase
virtual void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; virtual void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0;
virtual void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride, virtual void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true, bool useAmbientLight = true) const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true, bool useAmbientLight = true,
std::string_view effectId = {}, bool loop = false)
= 0; = 0;
virtual void removeEffect(std::string_view effectId) = 0;
/// @see MWWorld::WeatherManager::isInStorm /// @see MWWorld::WeatherManager::isInStorm
virtual bool isInStorm() const = 0; virtual bool isInStorm() const = 0;

View file

@ -312,28 +312,34 @@ namespace MWLua
sol::table api(context.mLua->unsafeState(), sol::create); sol::table api(context.mLua->unsafeState(), sol::create);
auto world = MWBase::Environment::get().getWorld(); auto world = MWBase::Environment::get().getWorld();
api["spawn"] api["remove"] = [world, context](std::string_view vfxId) {
= [world, context](std::string_view model, const osg::Vec3f& worldPos, sol::optional<sol::table> options) { context.mLuaManager->addAction([world, vfxId = vfxId] { world->removeEffect(vfxId); }, "openmw.vfx.remove");
if (options) };
{
bool magicVfx = options->get_or("mwMagicVfx", true); api["spawn"] = [world, context](
std::string texture = options->get_or<std::string>("particleTextureOverride", ""); std::string_view model, const osg::Vec3f& worldPos, sol::optional<sol::table> options) {
float scale = options->get_or("scale", 1.f); if (options)
bool useAmbientLight = options->get_or("useAmbientLight", true); {
context.mLuaManager->addAction( bool magicVfx = options->get_or("mwMagicVfx", true);
[world, model = VFS::Path::Normalized(model), texture = std::move(texture), worldPos, scale, std::string texture = options->get_or<std::string>("particleTextureOverride", "");
magicVfx, useAmbientLight]() { float scale = options->get_or("scale", 1.f);
world->spawnEffect(model, texture, worldPos, scale, magicVfx, useAmbientLight); std::string_view vfxId = options->get_or<std::string_view>("vfxId", "");
}, bool loop = options->get_or("loop", false);
"openmw.vfx.spawn"); bool useAmbientLight = options->get_or("useAmbientLight", true);
} context.mLuaManager->addAction(
else [world, model = VFS::Path::Normalized(model), texture = std::move(texture), worldPos, scale,
{ magicVfx, useAmbientLight, vfxId, loop]() {
context.mLuaManager->addAction([world, model = VFS::Path::Normalized(model), world->spawnEffect(model, texture, worldPos, scale, magicVfx, useAmbientLight, vfxId, loop);
worldPos]() { world->spawnEffect(model, "", worldPos, 1.f); }, },
"openmw.vfx.spawn"); "openmw.vfx.spawn");
} }
}; else
{
context.mLuaManager->addAction([world, model = VFS::Path::Normalized(model),
worldPos]() { world->spawnEffect(model, "", worldPos, 1.f); },
"openmw.vfx.spawn");
}
};
return api; return api;
} }

View file

@ -28,7 +28,8 @@ namespace MWRender
} }
void EffectManager::addEffect(VFS::Path::NormalizedView model, std::string_view textureOverride, void EffectManager::addEffect(VFS::Path::NormalizedView model, std::string_view textureOverride,
const osg::Vec3f& worldPosition, float scale, bool isMagicVFX, bool useAmbientLight) const osg::Vec3f& worldPosition, float scale, bool isMagicVFX, bool useAmbientLight, std::string_view effectId,
bool loop)
{ {
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(model); osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(model);
@ -36,6 +37,8 @@ namespace MWRender
Effect effect; Effect effect;
effect.mAnimTime = std::make_shared<EffectAnimationTime>(); effect.mAnimTime = std::make_shared<EffectAnimationTime>();
effect.loop = loop;
effect.effectId = std::string(effectId);
SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor;
node->accept(findMaxLengthVisitor); node->accept(findMaxLengthVisitor);
@ -67,17 +70,47 @@ namespace MWRender
mResourceSystem->getSceneManager()->setUpNormalsRTForStateSet(node->getOrCreateStateSet(), false); mResourceSystem->getSceneManager()->setUpNormalsRTForStateSet(node->getOrCreateStateSet(), false);
std::lock_guard<std::mutex> lock(mEffectsMutex);
mEffects.push_back(std::move(effect)); mEffects.push_back(std::move(effect));
} }
void EffectManager::removeEffect(std::string_view effectId)
{
std::lock_guard<std::mutex> lock(mEffectsMutex);
mEffects.erase(std::remove_if(mEffects.begin(), mEffects.end(),
[effectId, this](Effect& effect) {
if (effectId.compare(effect.effectId) == 0)
{
mParentNode->removeChild(effect.mTransform);
return true;
}
return false;
}),
mEffects.end());
}
void EffectManager::update(float dt) void EffectManager::update(float dt)
{ {
std::lock_guard<std::mutex> lock(mEffectsMutex);
mEffects.erase(std::remove_if(mEffects.begin(), mEffects.end(), mEffects.erase(std::remove_if(mEffects.begin(), mEffects.end(),
[dt, this](Effect& effect) { [dt, this](Effect& effect) {
bool remove = false;
effect.mAnimTime->addTime(dt); effect.mAnimTime->addTime(dt);
const auto remove = effect.mAnimTime->getTime() >= effect.mMaxControllerLength; if (effect.mAnimTime->getTime() >= effect.mMaxControllerLength)
if (remove) {
mParentNode->removeChild(effect.mTransform); if (effect.loop)
{
float remainder = effect.mAnimTime->getTime() - effect.mMaxControllerLength;
effect.mAnimTime->resetTime(remainder);
}
else
{
mParentNode->removeChild(effect.mTransform);
remove = true;
}
}
return remove; return remove;
}), }),
mEffects.end()); mEffects.end());
@ -85,6 +118,7 @@ namespace MWRender
void EffectManager::clear() void EffectManager::clear()
{ {
std::lock_guard<std::mutex> lock(mEffectsMutex);
for (const auto& effect : mEffects) for (const auto& effect : mEffects)
{ {
mParentNode->removeChild(effect.mTransform); mParentNode->removeChild(effect.mTransform);

View file

@ -35,7 +35,10 @@ namespace MWRender
/// Add an effect. When it's finished playing, it will be removed automatically. /// Add an effect. When it's finished playing, it will be removed automatically.
void addEffect(VFS::Path::NormalizedView model, std::string_view textureOverride, void addEffect(VFS::Path::NormalizedView model, std::string_view textureOverride,
const osg::Vec3f& worldPosition, float scale, bool isMagicVFX = true, bool useAmbientLight = true); const osg::Vec3f& worldPosition, float scale, bool isMagicVFX = true, bool useAmbientLight = true,
std::string_view effectId = {}, bool loop = false);
void removeEffect(std::string_view effectId);
void update(float dt); void update(float dt);
@ -45,11 +48,14 @@ namespace MWRender
private: private:
struct Effect struct Effect
{ {
std::string effectId;
float mMaxControllerLength; float mMaxControllerLength;
bool loop;
std::shared_ptr<EffectAnimationTime> mAnimTime; std::shared_ptr<EffectAnimationTime> mAnimTime;
osg::ref_ptr<osg::PositionAttitudeTransform> mTransform; osg::ref_ptr<osg::PositionAttitudeTransform> mTransform;
}; };
std::mutex mEffectsMutex;
std::vector<Effect> mEffects; std::vector<Effect> mEffects;
osg::ref_ptr<osg::Group> mParentNode; osg::ref_ptr<osg::Group> mParentNode;

View file

@ -1253,9 +1253,15 @@ namespace MWRender
} }
void RenderingManager::spawnEffect(VFS::Path::NormalizedView model, std::string_view texture, void RenderingManager::spawnEffect(VFS::Path::NormalizedView model, std::string_view texture,
const osg::Vec3f& worldPosition, float scale, bool isMagicVFX, bool useAmbientLight) const osg::Vec3f& worldPosition, float scale, bool isMagicVFX, bool useAmbientLight, std::string_view effectId,
bool loop)
{ {
mEffectManager->addEffect(model, texture, worldPosition, scale, isMagicVFX, useAmbientLight); mEffectManager->addEffect(model, texture, worldPosition, scale, isMagicVFX, useAmbientLight, effectId, loop);
}
void RenderingManager::removeEffect(std::string_view effectId)
{
mEffectManager->removeEffect(effectId);
} }
void RenderingManager::notifyWorldSpaceChanged() void RenderingManager::notifyWorldSpaceChanged()

View file

@ -195,7 +195,10 @@ namespace MWRender
SkyManager* getSkyManager(); SkyManager* getSkyManager();
void spawnEffect(VFS::Path::NormalizedView model, std::string_view texture, const osg::Vec3f& worldPosition, void spawnEffect(VFS::Path::NormalizedView model, std::string_view texture, const osg::Vec3f& worldPosition,
float scale = 1.f, bool isMagicVFX = true, bool useAmbientLight = true); float scale = 1.f, bool isMagicVFX = true, bool useAmbientLight = true, std::string_view effectId = {},
bool loop = false);
void removeEffect(std::string_view effectId);
/// Clear all savegame-specific data /// Clear all savegame-specific data
void clear(); void clear();

View file

@ -3689,9 +3689,15 @@ namespace MWWorld
} }
void World::spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride, void World::spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
const osg::Vec3f& worldPos, float scale, bool isMagicVFX, bool useAmbientLight) const osg::Vec3f& worldPos, float scale, bool isMagicVFX, bool useAmbientLight, std::string_view effectId,
bool loop)
{ {
mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX, useAmbientLight); mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX, useAmbientLight, effectId, loop);
}
void World::removeEffect(std::string_view effectId)
{
mRendering->removeEffect(effectId);
} }
struct ResetActorsVisitor struct ResetActorsVisitor

View file

@ -604,8 +604,10 @@ namespace MWWorld
void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override;
void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride, void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true, bool useAmbientLight = true,
bool useAmbientLight = true) override; std::string_view effectId = {}, bool loop = false) override;
void removeEffect(std::string_view effectId) override;
/// @see MWWorld::WeatherManager::isInStorm /// @see MWWorld::WeatherManager::isInStorm
bool isInStorm() const override; bool isInStorm() const override;

View file

@ -7,5 +7,6 @@ return {
SetGameTimeScale = function(scale) world.setGameTimeScale(scale) end, SetGameTimeScale = function(scale) world.setGameTimeScale(scale) end,
SetSimulationTimeScale = function(scale) world.setSimulationTimeScale(scale) end, SetSimulationTimeScale = function(scale) world.setSimulationTimeScale(scale) end,
SpawnVfx = function(data) world.vfx.spawn(data.model, data.position, data.options) end, SpawnVfx = function(data) world.vfx.spawn(data.model, data.position, data.options) end,
RemoveVfx = function(vfxId) world.vfx.remove(vfxId) end,
}, },
} }

View file

@ -196,6 +196,8 @@
-- * `particleTextureOverride` - Name of a particle texture that should override this effect's default texture. (default: "") -- * `particleTextureOverride` - Name of a particle texture that should override this effect's default texture. (default: "")
-- * `scale` - A number that scales the size of the vfx (Default: 1) -- * `scale` - A number that scales the size of the vfx (Default: 1)
-- * `useAmbientLighting` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: 1) -- * `useAmbientLighting` - boolean, vfx get a white ambient light attached in Morrowind. If false don't attach this. (default: 1)
-- * `loop` - boolean, if true the effect will loop until removed (default: 0).
-- * `vfxId` - a string ID that can be used to remove the effect later, using #remove. (Default: "").
-- --
-- @usage -- Spawn a sanctuary effect near the player -- @usage -- Spawn a sanctuary effect near the player
-- local effect = core.magic.effects.records[core.magic.EFFECT_TYPE.Sanctuary] -- local effect = core.magic.effects.records[core.magic.EFFECT_TYPE.Sanctuary]
@ -204,4 +206,13 @@
-- core.sendGlobalEvent('SpawnVfx', {model = model, position = pos}) -- core.sendGlobalEvent('SpawnVfx', {model = model, position = pos})
-- --
---
-- Remove a VFX with the given vfxId. Best invoked through the RemoveVfx global event
-- @function [parent=#VFX] remove
-- @param #string vfxId the vfxId of the vfx to remove.
--
-- @usage -- Remove the vfx with vfxId "myvfx"
-- core.sendGlobalEvent('RemoveVfx', "myvfx")
--
return nil return nil