Particle Scripting API (#7061)

* Expose particle effects to scripting

* Allow creation of particle sources

* Add API for particle sources

* bugfixes
This commit is contained in:
BMagnu authored and GitHub committed 2025-09-28 10:40:46 +02:00
1 parent 2c04313c59
commit dc50aaa86b
5 files changed
+289

No files matched your search

+13
View File
@@ -38,6 +38,8 @@ void ParticleManager::shutdown() {
ParticleSource* ParticleManager::createSource() {
ParticleSource* source;
m_sourceValidityCounter++;
// If we are currently in the onFrame function, adding stuff to the vector would invalidate the iterator currently in use
if (m_processingSources) {
m_deferredSourceAdding.emplace_back();
@@ -80,9 +82,12 @@ void ParticleManager::doFrame(float) {
TRACE_SCOPE(tracing::ProcessParticleEffects);
m_processingSources = true;
bool changehappened = false;
for (auto source = std::begin(m_sources); source != std::end(m_sources);) {
if (!source->isValid() || !source->process()) {
changehappened = true;
// if we're sitting on the very last source, popping-back will invalidate the iterator!
if (std::next(source) == m_sources.end()) {
m_sources.pop_back();
@@ -102,9 +107,13 @@ void ParticleManager::doFrame(float) {
m_processingSources = false;
for (auto& source : m_deferredSourceAdding) {
changehappened = true;
m_sources.push_back(std::move(source));
}
m_deferredSourceAdding.clear();
if (changehappened)
m_sourceValidityCounter++;
}
ParticleEffectHandle ParticleManager::addEffect(ParticleEffect&& effect)
@@ -179,6 +188,10 @@ void ParticleManager::clearSources() {
m_deferredSourceAdding.clear();
}
uint32_t ParticleManager::getSourceValidityCounter() const {
return m_sourceValidityCounter;
}
namespace util {
ParticleEffectHandle parseEffect(const SCP_string& objectName)
{
+4
View File
@@ -37,6 +37,8 @@ class ParticleManager {
SCP_vector<ParticleSource> m_sources; //!< The currently active sources
bool m_processingSources = false; //!< @c true if sources are currently being processed
uint32_t m_sourceValidityCounter = 0;
/**
* If the sources are currently being processed, no additional sources can be added. Instead, they are added to this
* vector and then added to the main vector when processing is done.
@@ -148,6 +150,8 @@ class ParticleManager {
* @return A wrapper class which allows access to the created sources
*/
ParticleSource* createSource(ParticleEffectHandle index);
uint32_t getSourceValidityCounter() const;
};
namespace internal {
+13
View File
@@ -11,6 +11,7 @@
#include "scripting/api/objs/team_colors.h"
#include "scripting/api/objs/weaponclass.h"
#include "scripting/api/objs/wingformation.h"
#include "scripting/api/objs/particle.h"
#include "decals/decals.h"
#include "fireball/fireballs.h"
@@ -18,6 +19,7 @@
#include "mission/missionmessage.h"
#include "ship/ship.h"
#include "weapon/weapon.h"
#include "particle/ParticleManager.h"
extern bool Ships_inited;
@@ -373,5 +375,16 @@ ADE_FUNC(__len, l_Tables_TeamColors, nullptr, "Number of team colors", "number"
return ade_set_args(L, "i", static_cast<int>(Team_Names.size()));
}
//*****SUBLIBRARY: Tables/ShipTypes
ADE_LIB_DERIV(l_Tables_ParticleEffects, "ParticleEffects", nullptr, nullptr, l_Tables);
ADE_INDEXER(l_Tables_ParticleEffects, "string Name", "Array of particle effects", "particle_effect", "Particle Effect handle, or invalid handle if name is invalid")
{
const char* name;
if (!ade_get_args(L, "*s", &name))
return ade_set_error(L, "o", l_ParticleEffect.Set(particle::ParticleEffectHandle::invalid()));
return ade_set_args(L, "o", l_ParticleEffect.Set(particle::ParticleManager::get()->getEffectByName(name)));
}
}
}
+239
View File
@@ -4,6 +4,9 @@
#include "particle.h"
#include "vecmath.h"
#include "object.h"
#include "model.h"
#include "particle/ParticleManager.h"
#include "particle/ParticleEffect.h"
namespace scripting {
namespace api {
@@ -20,6 +23,18 @@ bool particle_h::isValid() const {
return !part.expired();
}
particle_source_h::particle_source_h(particle::ParticleSource* source_p, uint32_t sourceValidityCounter_p) {
this->source = source_p;
this->sourceValidityCounter = sourceValidityCounter_p;
}
particle::ParticleSource* particle_source_h::Get() const {
return isValid() ? this->source : nullptr;
}
bool particle_source_h::isValid() const {
return particle::ParticleManager::get()->getSourceValidityCounter() == sourceValidityCounter;
}
//**********HANDLE: Particle
ADE_OBJ(l_Particle, particle_h, "particle", "Handle to a particle");
@@ -214,6 +229,230 @@ ADE_FUNC_DEPRECATED(setColor, l_Particle, "number r, number g, number b", "Sets
}
ADE_OBJ(l_ParticleEffect, ::particle::ParticleEffectHandle, "particle_effect", "Handle to a tabled particle effect");
ADE_FUNC(getName, l_ParticleEffect, nullptr, "Returns the name under which this effect is stored", "string", "the name of the particle effect, or an empty string for an invalid handle")
{
::particle::ParticleEffectHandle ph;
if (!ade_get_args(L, "o", l_ParticleEffect.Get(&ph)))
return ade_set_error(L, "s", "");
if (!ph.isValid())
return ade_set_error(L, "s", "");
const auto& particle_effect = particle::ParticleManager::get()->getEffect(ph);
if (particle_effect.empty())
return ade_set_error(L, "s", "");
return ade_set_args(L, "s", particle_effect.front().getName().c_str());
}
ADE_FUNC(createSource, l_ParticleEffect, nullptr, "Creates a new particle source, spawning particles as per this particle effect", "particle_source", "the particle source, or nil for an invalid handle")
{
::particle::ParticleEffectHandle ph;
if (!ade_get_args(L, "o", l_ParticleEffect.Get(&ph)))
return ADE_RETURN_NIL;
if (!ph.isValid())
return ADE_RETURN_NIL;
auto source = particle::ParticleManager::get()->createSource(ph);
return ade_set_args(L, "o", l_ParticleSource.Set(particle_source_h(source, particle::ParticleManager::get()->getSourceValidityCounter())));
}
ADE_OBJ(l_ParticleSource, particle_source_h, "particle_source", "Handle to a particle source. Only valid immediately after acquiring.");
ADE_FUNC(setNormal, l_ParticleSource, "vector normal", "Sets the normal vector of this particle source.", nullptr, nullptr)
{
particle_source_h ps;
vec3d normal;
if (!ade_get_args(L, "oo", l_ParticleSource.Get(&ps), l_Vector.Get(&normal)))
return ADE_RETURN_NIL;
particle::ParticleSource* psp = ps.Get();
if (psp == nullptr)
return ADE_RETURN_NIL;
vm_vec_normalize_safe(&normal);
psp->setNormal(normal);
return ADE_RETURN_NIL;
}
ADE_FUNC(setTriggerRadius, l_ParticleSource, "number triggerRadius", "Sets the trigger radius of this particle source.", nullptr, nullptr)
{
particle_source_h ps;
float trigger;
if (!ade_get_args(L, "of", l_ParticleSource.Get(&ps), &trigger))
return ADE_RETURN_NIL;
particle::ParticleSource* psp = ps.Get();
if (psp == nullptr)
return ADE_RETURN_NIL;
psp->setTriggerRadius(trigger);
return ADE_RETURN_NIL;
}
ADE_FUNC(setTriggerVelocity, l_ParticleSource, "number triggerVelocity", "Sets the trigger velocity of this particle source.", nullptr, nullptr)
{
particle_source_h ps;
float trigger;
if (!ade_get_args(L, "of", l_ParticleSource.Get(&ps), &trigger))
return ADE_RETURN_NIL;
particle::ParticleSource* psp = ps.Get();
if (psp == nullptr)
return ADE_RETURN_NIL;
psp->setTriggerVelocity(trigger);
return ADE_RETURN_NIL;
}
ADE_FUNC(createOnCoordinates, l_ParticleSource, "vector position, orientation orientation, [vector velocity = zero_vector, orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source at the specified position.", "boolean", "returns true if the source was successfully created, false otherwise")
{
particle_source_h ps;
vec3d position;
matrix_h orientation;
vec3d velocity = ZERO_VECTOR;
matrix_h orientationOverride(&vmd_identity_matrix);
bool orientationOverrideRelative = true;
if (!ade_get_args(L, "ooo|oob", l_ParticleSource.Get(&ps), l_Vector.Get(&position), l_Matrix.Get(&orientation), l_Vector.Get(&velocity), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
return ade_set_args(L, "b", false);
particle::ParticleSource* psp = ps.Get();
if (psp == nullptr)
return ade_set_args(L, "b", false);
psp->setHost(std::make_unique<EffectHostVector>(position, *orientation.GetMatrix(), velocity, *orientationOverride.GetMatrix(), orientationOverrideRelative));
psp->finishCreation();
return ade_set_args(L, "b", true);
}
ADE_FUNC(createOnObject, l_ParticleSource, "object object, vector offset, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source, attached to the specified object.", "boolean", "returns true if the source was successfully created, false otherwise")
{
particle_source_h ps;
object_h objh;
vec3d position;
matrix_h orientationOverride(&vmd_identity_matrix);
bool orientationOverrideRelative = true;
if (!ade_get_args(L, "ooo|ob", l_ParticleSource.Get(&ps), l_Object.Get(&objh), l_Vector.Get(&position), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
return ade_set_args(L, "b", false);
if (!objh.isValid())
return ade_set_args(L, "b", false);
particle::ParticleSource* psp = ps.Get();
if (psp == nullptr)
return ade_set_args(L, "b", false);
psp->setHost(std::make_unique<EffectHostObject>(objh.objp(), position, *orientationOverride.GetMatrix(), orientationOverrideRelative));
psp->finishCreation();
return ade_set_args(L, "b", true);
}
ADE_FUNC(createOnSubmodel, l_ParticleSource, "object object, submodel submodel, vector offset, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source, attached to the specified submodel.", "boolean", "returns true if the source was successfully created, false otherwise")
{
particle_source_h ps;
object_h objh;
submodel_h subobjh;
vec3d position;
matrix_h orientationOverride(&vmd_identity_matrix);
bool orientationOverrideRelative = true;
if (!ade_get_args(L, "oooo|ob", l_ParticleSource.Get(&ps), l_Object.Get(&objh), l_Submodel.Get(&subobjh), l_Vector.Get(&position), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
return ade_set_args(L, "b", false);
if (!(subobjh.isValid() && objh.isValid()))
return ade_set_args(L, "b", false);
particle::ParticleSource* psp = ps.Get();
if (psp == nullptr)
return ade_set_args(L, "b", false);
psp->setHost(std::make_unique<EffectHostSubmodel>(objh.objp(), subobjh.GetSubmodelIndex(), position, *orientationOverride.GetMatrix(), orientationOverrideRelative));
psp->finishCreation();
return ade_set_args(L, "b", true);
}
ADE_FUNC(createOnTurret, l_ParticleSource, "object object, submodel submodel, number firepoint, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source, attached to the specified turret firepoint.", "boolean", "returns true if the source was successfully created, false otherwise")
{
particle_source_h ps;
object_h objh;
submodel_h subobjh;
int firepoint;
matrix_h orientationOverride(&vmd_identity_matrix);
bool orientationOverrideRelative = true;
if (!ade_get_args(L, "oooi|ob", l_ParticleSource.Get(&ps), l_Object.Get(&objh), l_Submodel.Get(&subobjh), &firepoint, l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
return ade_set_args(L, "b", false);
if (!(subobjh.isValid() && objh.isValid()))
return ade_set_args(L, "b", false);
particle::ParticleSource* psp = ps.Get();
if (psp == nullptr)
return ade_set_args(L, "b", false);
psp->setHost(std::make_unique<EffectHostTurret>(objh.objp(), subobjh.GetSubmodelIndex(), firepoint, *orientationOverride.GetMatrix(), orientationOverrideRelative));
psp->finishCreation();
return ade_set_args(L, "b", true);
}
ADE_FUNC(createOnBeam, l_ParticleSource, "object object, submodel submodel, number firepoint, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source along the length of the beam.", "boolean", "returns true if the source was successfully created, false otherwise")
{
particle_source_h ps;
object_h objh;
matrix_h orientationOverride(&vmd_identity_matrix);
bool orientationOverrideRelative = true;
if (!ade_get_args(L, "ooo|ob", l_ParticleSource.Get(&ps), l_Object.Get(&objh), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
return ade_set_args(L, "b", false);
if (!objh.isValid() || objh.objp()->type != OBJ_BEAM)
return ade_set_args(L, "b", false);
particle::ParticleSource* psp = ps.Get();
if (psp == nullptr)
return ade_set_args(L, "b", false);
psp->setHost(std::make_unique<EffectHostBeam>(objh.objp(), *orientationOverride.GetMatrix(), orientationOverrideRelative));
psp->finishCreation();
return ade_set_args(L, "b", true);
}
ADE_FUNC(createOnParticle, l_ParticleSource, "particle particle, [orientation orientationOverride = identity, boolean orientationOverrideIsRelative = true]", "Actually spawns this particle source, attached to the specified persistent particle.", "boolean", "returns true if the source was successfully created, false otherwise")
{
particle_source_h ps;
particle_h particle;
matrix_h orientationOverride(&vmd_identity_matrix);
bool orientationOverrideRelative = true;
if (!ade_get_args(L, "ooo|ob", l_ParticleSource.Get(&ps), l_Particle.Get(&particle), l_Matrix.Get(&orientationOverride), &orientationOverrideRelative))
return ade_set_args(L, "b", false);
if (!particle.isValid())
return ade_set_args(L, "b", false);
particle::ParticleSource* psp = ps.Get();
if (psp == nullptr)
return ade_set_args(L, "b", false);
psp->setHost(std::make_unique<EffectHostParticle>(particle.Get(), *orientationOverride.GetMatrix(), orientationOverrideRelative));
psp->finishCreation();
return ade_set_args(L, "b", true);
}
}
}
+20
View File
@@ -2,6 +2,7 @@
#include "scripting/ade_api.h"
#include "particle/particle.h"
#include "particle/ParticleSource.h"
namespace scripting {
namespace api {
@@ -22,6 +23,25 @@ class particle_h
DECLARE_ADE_OBJ(l_Particle, particle_h);
DECLARE_ADE_OBJ(l_ParticleEffect, particle::ParticleEffectHandle);
class particle_source_h
{
protected:
particle::ParticleSource* source = nullptr;
uint32_t sourceValidityCounter = 0;
public:
particle_source_h() = default;
explicit particle_source_h(particle::ParticleSource* source_p, uint32_t sourceValidityCounter_p);
particle::ParticleSource* Get() const;
bool isValid() const;
};
DECLARE_ADE_OBJ(l_ParticleSource, particle_source_h);
}
}