From b5a696892aa7b0936f0840ccc57fc8612b434d58 Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:31:00 +0900 Subject: [PATCH 1/3] Add curve to control the duration of a particle source --- code/particle/ParticleEffect.cpp | 10 +++++++--- code/particle/ParticleEffect.h | 3 ++- code/particle/ParticleSource.cpp | 6 ++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/code/particle/ParticleEffect.cpp b/code/particle/ParticleEffect.cpp index 2068a69eadf..36252794c59 100644 --- a/code/particle/ParticleEffect.cpp +++ b/code/particle/ParticleEffect.cpp @@ -416,13 +416,17 @@ void ParticleEffect::pageIn() { } } -std::pair ParticleEffect::getEffectDuration() const { +std::pair ParticleEffect::getEffectDuration(float interp, const ParticleSource& source, size_t effectNumber) const { std::pair timing; timing.first = _timestamp(fl2i(m_delayRange.next() * 1000.0f)); if (m_duration == Duration::ALWAYS) timing.second = TIMESTAMP::never(); - else - timing.second = timestamp_delta(timing.first, fl2i(m_durationRange.next() * 1000.0f)); + else { + const auto& [pos, hostOrientation] = source.m_host->getPositionAndOrientation(m_parent_local, interp, m_manual_offset); + auto modularCurvesInput = std::forward_as_tuple(source, effectNumber, pos); + + timing.second = timestamp_delta(timing.first, fl2i(m_durationRange.next() * 1000.0f * m_modular_curves.get_output(ParticleCurvesOutput::SOURCE_DURATION_MULT, modularCurvesInput))); + } return timing; } diff --git a/code/particle/ParticleEffect.h b/code/particle/ParticleEffect.h index fdc277715e0..55e073b46d2 100644 --- a/code/particle/ParticleEffect.h +++ b/code/particle/ParticleEffect.h @@ -75,6 +75,7 @@ class ParticleEffect { RADIUS_MULT, LENGTH_MULT, LIFETIME_MULT, + SOURCE_DURATION_MULT, VOLUME_VELOCITY_MULT, INHERIT_VELOCITY_MULT, POSITION_INHERIT_VELOCITY_MULT, @@ -236,7 +237,7 @@ class ParticleEffect { const SCP_string& getName() const { return m_name; } - std::pair getEffectDuration() const; + std::pair getEffectDuration(float interp, const ParticleSource& source, size_t effectNumber) const; float getNextSpawnDelay() const; diff --git a/code/particle/ParticleSource.cpp b/code/particle/ParticleSource.cpp index cf94e233701..b69fe0d04c1 100644 --- a/code/particle/ParticleSource.cpp +++ b/code/particle/ParticleSource.cpp @@ -34,8 +34,10 @@ void ParticleSource::finishCreation() { m_host->setupProcessing(); - for (const auto& effect : ParticleManager::get()->getEffect(m_effect)) { - const auto& [begin, end] = effect.getEffectDuration(); + const auto& effectList = ParticleManager::get()->getEffect(m_effect); + for (size_t i = 0; i < effectList.size(); i++) { + const auto& effect = effectList[i]; + const auto& [begin, end] = effect.getEffectDuration(0.0, *this, i); m_timing.emplace_back(SourceTiming{timestamp_delta(begin, 0), begin, end}); } } From c00e7ac69af7734c1bab372f4f24db704d44c68d Mon Sep 17 00:00:00 2001 From: BMagnu <6238428+BMagnu@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:39:10 +0900 Subject: [PATCH 2/3] Also add fractional Life Left curve input for sources --- code/particle/ParticleEffect.h | 1 + code/particle/ParticleSource.cpp | 5 +++++ code/particle/ParticleSource.h | 2 ++ 3 files changed, 8 insertions(+) diff --git a/code/particle/ParticleEffect.h b/code/particle/ParticleEffect.h index 55e073b46d2..c802ae5217a 100644 --- a/code/particle/ParticleEffect.h +++ b/code/particle/ParticleEffect.h @@ -300,6 +300,7 @@ class ParticleEffect { std::pair {"Host Ship Time Until Explosion", modular_curves_submember_input<&ParticleSource::m_host, &EffectHost::getParentObjAndSig, 0, &Objects, &obj_get_instance_maybe, &ship::final_death_time, static_cast(×tamp_until)>{}}) .derive_modular_curves_input_only_subset( //Effect Number std::pair {"Spawntime Left", modular_curves_functional_full_input<&ParticleSource::getEffectRemainingTime>{}}, + std::pair {"Life Left", modular_curves_functional_full_input<&ParticleSource::getEffectRemainingLife>{}}, std::pair {"Time Running", modular_curves_functional_full_input<&ParticleSource::getEffectRunningTime>{}}) .derive_modular_curves_input_only_subset( //Sampled spawn position std::pair {"Pixel Size At Emitter", modular_curves_functional_full_input<&ParticleSource::getEffectPixelSize>{}}, diff --git a/code/particle/ParticleSource.cpp b/code/particle/ParticleSource.cpp index b69fe0d04c1..0dd4c73e603 100644 --- a/code/particle/ParticleSource.cpp +++ b/code/particle/ParticleSource.cpp @@ -113,6 +113,11 @@ float ParticleSource::getEffectRunningTime(const std::tuple& source) { + const auto& timing = std::get<0>(source).m_timing[std::get<1>(source)]; + return i2fl(timestamp_get_delta(timing.m_nextCreation, timing.m_endTimestamp)) / i2fl(timestamp_get_delta(timing.m_startTimestamp, timing.m_endTimestamp)) ; +} + float ParticleSource::getEffectPixelSize(const std::tuple& source) { return std::get<0>(source).getEffect()[std::get<1>(source)].getApproximatePixelSize(std::get<2>(source)); } diff --git a/code/particle/ParticleSource.h b/code/particle/ParticleSource.h index df5c2c2c7f8..c4cd709d1ad 100644 --- a/code/particle/ParticleSource.h +++ b/code/particle/ParticleSource.h @@ -75,6 +75,8 @@ class ParticleSource { static float getEffectRunningTime(const std::tuple& source); + static float getEffectRemainingLife(const std::tuple& source); + static float getEffectPixelSize(const std::tuple& source); static float getEffectApparentSize(const std::tuple& source); From d8269a32ef340ba2e2fcd12ed6ac111971c741ab Mon Sep 17 00:00:00 2001 From: Birk Magnussen <6238428+BMagnu@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:10:16 +0900 Subject: [PATCH 3/3] Actually register new particle curve output --- code/particle/ParticleEffect.h | 1 + 1 file changed, 1 insertion(+) diff --git a/code/particle/ParticleEffect.h b/code/particle/ParticleEffect.h index c802ae5217a..0824139850e 100644 --- a/code/particle/ParticleEffect.h +++ b/code/particle/ParticleEffect.h @@ -252,6 +252,7 @@ class ParticleEffect { std::pair {"Radius Mult", ParticleCurvesOutput::RADIUS_MULT}, std::pair {"Length Mult", ParticleCurvesOutput::LENGTH_MULT}, std::pair {"Lifetime Mult", ParticleCurvesOutput::LIFETIME_MULT}, + std::pair {"Source Duration Mult", ParticleCurvesOutput::SOURCE_DURATION_MULT}, std::pair {"Velocity Volume Mult", ParticleCurvesOutput::VOLUME_VELOCITY_MULT}, std::pair {"Velocity Inherit Mult", ParticleCurvesOutput::INHERIT_VELOCITY_MULT}, std::pair {"Velocity Position Inherit Mult", ParticleCurvesOutput::POSITION_INHERIT_VELOCITY_MULT},