From 360cfe6b42deb82f3115eefde5a0adb45ad0a721 Mon Sep 17 00:00:00 2001 From: Dave Kapell Date: Sat, 11 Apr 2026 22:35:02 -0400 Subject: [PATCH 1/4] Ramp up HVLI damage over 2U --- src/components/lifetime.h | 1 + src/components/missile.h | 1 + src/script/components.cpp | 1 + src/systems/missilesystem.cpp | 20 +++++++++++++++++--- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/components/lifetime.h b/src/components/lifetime.h index d054f38158..fbeaafe8ac 100644 --- a/src/components/lifetime.h +++ b/src/components/lifetime.h @@ -6,5 +6,6 @@ class LifeTime { public: float lifetime = 1.0; + float initial_lifetime = 1.0; sp::script::Callback on_expire; }; diff --git a/src/components/missile.h b/src/components/missile.h index 891639e0cd..45495f4571 100644 --- a/src/components/missile.h +++ b/src/components/missile.h @@ -31,6 +31,7 @@ class ExplodeOnTouch sp::ecs::Entity owner; DamageType damage_type = DamageType::Kinetic; string explosion_sfx; + float full_damage_after = 0.0f; }; class ExplodeOnTimeout { diff --git a/src/script/components.cpp b/src/script/components.cpp index c11385cfec..3c0fbe80fd 100644 --- a/src/script/components.cpp +++ b/src/script/components.cpp @@ -666,6 +666,7 @@ void initComponentScriptBindings() BIND_MEMBER(MoveTo, on_arrival); sp::script::ComponentHandler::name("lifetime"); BIND_MEMBER(LifeTime, lifetime); + BIND_MEMBER(LifeTime, initial_lifetime); BIND_MEMBER(LifeTime, on_expire); sp::script::ComponentHandler::name("faction"); diff --git a/src/systems/missilesystem.cpp b/src/systems/missilesystem.cpp index fe66aaf61c..c68747baaa 100644 --- a/src/systems/missilesystem.cpp +++ b/src/systems/missilesystem.cpp @@ -166,7 +166,17 @@ void MissileSystem::explode(sp::ecs::Entity source, sp::ecs::Entity target, Expl if (eot.blast_range > 100.0f || !target) { DamageSystem::damageArea(transform->getPosition(), eot.blast_range, eot.damage_at_edge, eot.damage_at_center, info, eot.blast_range / 2); } else { - DamageSystem::applyDamage(target, eot.damage_at_center, info); + if (eot.full_damage_after > 0.0f){ + auto lifetime = source.getComponent(); + float alive_for = lifetime->initial_lifetime - lifetime->lifetime; + if (alive_for > eot.full_damage_after){ + DamageSystem::applyDamage(target, eot.damage_at_center, info); + } else { + DamageSystem::applyDamage(target, eot.damage_at_center * (alive_for / eot.full_damage_after), info); + } + } else { + DamageSystem::applyDamage(target, eot.damage_at_center, info); + } } auto e = sp::ecs::Entity::create(); @@ -292,6 +302,7 @@ void MissileSystem::spawnProjectile(sp::ecs::Entity source, MissileTubes::MountP mc.damage_at_edge = 10.0f * category_modifier; mc.blast_range = 20.0f * category_modifier; mc.explosion_sfx = "sfx/explosion.wav"; + mc.full_damage_after = 2000.0f / (mwd.speed / category_modifier); // Full damage after 2U missile.addComponent(0.1f, 0.0f, 0.0f); } break; @@ -349,8 +360,11 @@ void MissileSystem::spawnProjectile(sp::ecs::Entity source, MissileTubes::MountP cpe.life_time = 10.0f; } - if (tube.type_loaded != MW_Mine) - missile.addComponent().lifetime = mwd.lifetime * category_modifier; + if (tube.type_loaded != MW_Mine){ + auto& lifetime = missile.addComponent(); + lifetime.lifetime = mwd.lifetime * category_modifier; + lifetime.initial_lifetime = mwd.lifetime * category_modifier; + } if (tube.type_loaded != MW_Mine) { auto& dbad = missile.addComponent(); From aa52a6f1200e85d3414db78a3c584b325e343470 Mon Sep 17 00:00:00 2001 From: Dave Kapell Date: Sat, 11 Apr 2026 22:41:44 -0400 Subject: [PATCH 2/4] Expose full_damage_after to lua --- src/script/components.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script/components.cpp b/src/script/components.cpp index 3c0fbe80fd..b0d2e4d6dd 100644 --- a/src/script/components.cpp +++ b/src/script/components.cpp @@ -310,6 +310,7 @@ void initComponentScriptBindings() BIND_MEMBER(ExplodeOnTouch, owner); BIND_MEMBER(ExplodeOnTouch, damage_type); BIND_MEMBER(ExplodeOnTouch, explosion_sfx); + BIND_MEMBER(ExplodeOnTouch, full_damage_after); sp::script::ComponentHandler::name("delayed_explode_on_touch"); BIND_MEMBER(DelayedExplodeOnTouch, delay); From 48e39435a13288662bbed3e8a6d625742ca6ce93 Mon Sep 17 00:00:00 2001 From: Dave Kapell Date: Sat, 11 Apr 2026 23:08:54 -0400 Subject: [PATCH 3/4] Change full_damage_after to be based on 1U for legacy compatibility --- scripts/science_db.lua | 2 +- src/systems/missilesystem.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/science_db.lua b/scripts/science_db.lua index 67f16c3328..e84fddb2f2 100644 --- a/scripts/science_db.lua +++ b/scripts/science_db.lua @@ -230,7 +230,7 @@ item:addKeyValue(_('Sizes'), _('Small (S) / Medium (M) / Large (L)')) populateMissileStats(item, missile_stat_keys, hvli_stats) item:setLongDescription(_([[A high-velocity lead impactor (HVLI) fires a simple slug of lead at a high velocity. This weapon is usually found in simpler ships since it does not require guidance computers. This also means its projectiles fly in a straight line from its tube and can't pursue a target. -Each shot from an HVLI fires a burst of 5 projectiles, which increases the chance to hit but requires precision aiming to be effective. It reaches its full damage potential at a range of 2u.]])) +Each shot from an HVLI fires a burst of 5 projectiles, which increases the chance to hit but requires precision aiming to be effective. It reaches its full damage potential at a range of 1u.]])) local function angleDifference(angle_a, angle_b) local ret = (angle_b or 0) - (angle_a or 0) diff --git a/src/systems/missilesystem.cpp b/src/systems/missilesystem.cpp index c68747baaa..86d706d43c 100644 --- a/src/systems/missilesystem.cpp +++ b/src/systems/missilesystem.cpp @@ -302,7 +302,7 @@ void MissileSystem::spawnProjectile(sp::ecs::Entity source, MissileTubes::MountP mc.damage_at_edge = 10.0f * category_modifier; mc.blast_range = 20.0f * category_modifier; mc.explosion_sfx = "sfx/explosion.wav"; - mc.full_damage_after = 2000.0f / (mwd.speed / category_modifier); // Full damage after 2U + mc.full_damage_after = 1000.0f / (mwd.speed / category_modifier); // Full damage after 1U missile.addComponent(0.1f, 0.0f, 0.0f); } break; From 029fc95d71c2fc56914cc787d448182fdb453ca0 Mon Sep 17 00:00:00 2001 From: Dave Kapell Date: Mon, 12 Oct 2026 09:16:10 -0400 Subject: [PATCH 4/4] refactor lifetime into a dedicated system --- CMakeLists.txt | 2 ++ scripts/api/entity/scanprobe.lua | 6 +++--- scripts/api/gm.lua | 4 ++-- src/components/lifetime.h | 4 ++-- src/init/ecs.cpp | 2 ++ src/script/components.cpp | 4 ++-- src/systems/lifetimesystem.cpp | 28 ++++++++++++++++++++++++++++ src/systems/lifetimesystem.h | 9 +++++++++ src/systems/missilesystem.cpp | 23 +++-------------------- src/systems/missilesystem.h | 2 +- src/systems/probe.cpp | 2 +- 11 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 src/systems/lifetimesystem.cpp create mode 100644 src/systems/lifetimesystem.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a28abac700..d469f6d49a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -393,6 +393,8 @@ set(MAIN_SOURCES src/systems/maneuvering.cpp src/systems/beamweapon.h src/systems/beamweapon.cpp + src/systems/lifetimesystem.h + src/systems/lifetimesystem.cpp src/systems/missilesystem.h src/systems/missilesystem.cpp src/systems/warpsystem.h diff --git a/scripts/api/entity/scanprobe.lua b/scripts/api/entity/scanprobe.lua index 2bbda31424..450de4e74d 100644 --- a/scripts/api/entity/scanprobe.lua +++ b/scripts/api/entity/scanprobe.lua @@ -9,7 +9,7 @@ function ScanProbe() local e = createEntity() e.components = { transform = {}, - lifetime = {lifetime=60*10}, + lifetime = {maximum=60*10}, radar_trace = { icon="radar/probe.png", min_size=10.0, @@ -54,13 +54,13 @@ end --- Defaults to 600 seconds (10 minutes). --- Example: probe:setLifetime(60 * 5) -- sets the lifetime to 5 minutes function Entity:setLifetime(lifetime) - if self.components.lifetime then self.components.lifetime.lifetime = lifetime end + if self.components.lifetime then self.components.lifetime.maximum = lifetime end return self end --- Returns this ScanProbe's remaining lifetime. --- Example: probe:getLifetime() function Entity:getLifetime() - if self.components.lifetime then return self.components.lifetime.lifetime end + if self.components.lifetime then return self.components.lifetime.maximum - self.components.alive_for end return 0.0 end --- Sets this ScanProbe's owner entity. diff --git a/scripts/api/gm.lua b/scripts/api/gm.lua index 45e22e3122..8516665d0b 100644 --- a/scripts/api/gm.lua +++ b/scripts/api/gm.lua @@ -555,8 +555,8 @@ end function __exportScanProbe(entity) local extras = __exportBasics(entity) local lt = entity.components.lifetime - if lt and lt.lifetime then - extras = extras .. string.format(":setLifetime(%.0f)", lt.lifetime) + if lt and lt.maximum then + extras = extras .. string.format(":setLifetime(%.0f)", lt.maximum) end local mt = entity.components.move_to if mt and mt.speed then diff --git a/src/components/lifetime.h b/src/components/lifetime.h index fbeaafe8ac..f2cef54b9c 100644 --- a/src/components/lifetime.h +++ b/src/components/lifetime.h @@ -5,7 +5,7 @@ class LifeTime { public: - float lifetime = 1.0; - float initial_lifetime = 1.0; + float maximum = 1.0; + float alive_for = 0.0; sp::script::Callback on_expire; }; diff --git a/src/init/ecs.cpp b/src/init/ecs.cpp index 294db64331..6e6cbd7986 100644 --- a/src/init/ecs.cpp +++ b/src/init/ecs.cpp @@ -49,6 +49,7 @@ #include "systems/shieldsystem.h" #include "systems/shipsystemssystem.h" #include "systems/coolantsystem.h" +#include "systems/lifetimesystem.h" #include "systems/missilesystem.h" #include "systems/maneuvering.h" #include "systems/energysystem.h" @@ -145,6 +146,7 @@ void initSystemsAndComponents() engine->registerSystem(); engine->registerSystem(); engine->registerSystem(); + engine->registerSystem(); engine->registerSystem(); engine->registerSystem(); engine->registerSystem(); diff --git a/src/script/components.cpp b/src/script/components.cpp index 54c7f56e10..dda7e66f9b 100644 --- a/src/script/components.cpp +++ b/src/script/components.cpp @@ -670,8 +670,8 @@ void initComponentScriptBindings() BIND_MEMBER(MoveTo, target); BIND_MEMBER(MoveTo, on_arrival); sp::script::ComponentHandler::name("lifetime"); - BIND_MEMBER(LifeTime, lifetime); - BIND_MEMBER(LifeTime, initial_lifetime); + BIND_MEMBER(LifeTime, maximum); + BIND_MEMBER(LifeTime, alive_for); BIND_MEMBER(LifeTime, on_expire); sp::script::ComponentHandler::name("faction"); diff --git a/src/systems/lifetimesystem.cpp b/src/systems/lifetimesystem.cpp new file mode 100644 index 0000000000..8510c74937 --- /dev/null +++ b/src/systems/lifetimesystem.cpp @@ -0,0 +1,28 @@ +#include "systems/lifetimesystem.h" + + +#include "components/missile.h" +#include "components/lifetime.h" +#include "ecs/query.h" +#include "multiplayer_server.h" +#include "systems/missilesystem.h" + +void LifeTimeSystem::update(float delta) +{ + if (game_server) { + // TODO: Not really part of missile + for(auto [entity, lifetime] : sp::ecs::Query()) { + lifetime.alive_for += delta; + if (lifetime.alive_for >= lifetime.maximum) { + if (entity.hasComponent() and entity.hasComponent()) { + if (auto eot = entity.getComponent()) { + if (auto missilesystem = entity.getComponent()){ + missilesystem->explode(entity, {}, *eot); + } + } + } + entity.destroy(); + } + } + } +} diff --git a/src/systems/lifetimesystem.h b/src/systems/lifetimesystem.h new file mode 100644 index 0000000000..6c716e25ee --- /dev/null +++ b/src/systems/lifetimesystem.h @@ -0,0 +1,9 @@ +#pragma once + +#include "ecs/system.h" + +class LifeTimeSystem: public sp::ecs::System +{ +public: + void update(float delta) override; +}; diff --git a/src/systems/missilesystem.cpp b/src/systems/missilesystem.cpp index 7f9380063a..b0405cc7c9 100644 --- a/src/systems/missilesystem.cpp +++ b/src/systems/missilesystem.cpp @@ -115,21 +115,6 @@ void MissileSystem::update(float delta) explode(entity, {}, deot); } } - - if (game_server) { - // TODO: Not really part of missile - for(auto [entity, lifetime] : sp::ecs::Query()) { - lifetime.lifetime -= delta; - if (lifetime.lifetime <= 0.0f) { - if (entity.hasComponent()) { - if (auto eot = entity.getComponent()) { - explode(entity, {}, *eot); - } - } - entity.destroy(); - } - } - } } void MissileSystem::collision(sp::ecs::Entity a, sp::ecs::Entity b, float force) @@ -168,11 +153,10 @@ void MissileSystem::explode(sp::ecs::Entity source, sp::ecs::Entity target, Expl } else { if (eot.full_damage_after > 0.0f){ auto lifetime = source.getComponent(); - float alive_for = lifetime->initial_lifetime - lifetime->lifetime; - if (alive_for > eot.full_damage_after){ + if (lifetime->alive_for > eot.full_damage_after){ DamageSystem::applyDamage(target, eot.damage_at_center, info); } else { - DamageSystem::applyDamage(target, eot.damage_at_center * (alive_for / eot.full_damage_after), info); + DamageSystem::applyDamage(target, eot.damage_at_center * (lifetime->alive_for / eot.full_damage_after), info); } } else { DamageSystem::applyDamage(target, eot.damage_at_center, info); @@ -363,8 +347,7 @@ void MissileSystem::spawnProjectile(sp::ecs::Entity source, MissileTubes::MountP if (tube.type_loaded != MW_Mine){ auto& lifetime = missile.addComponent(); - lifetime.lifetime = mwd.lifetime * category_modifier; - lifetime.initial_lifetime = mwd.lifetime * category_modifier; + lifetime.maximum = mwd.lifetime * category_modifier; } if (tube.type_loaded != MW_Mine) { diff --git a/src/systems/missilesystem.h b/src/systems/missilesystem.h index 634a35fc15..520caed6ae 100644 --- a/src/systems/missilesystem.h +++ b/src/systems/missilesystem.h @@ -23,8 +23,8 @@ class MissileSystem static void startUnload(sp::ecs::Entity source, MissileTubes::MountPoint& tube); static void fire(sp::ecs::Entity source, MissileTubes::MountPoint& tube, float target_angle, sp::ecs::Entity target); static float calculateFiringSolution(sp::ecs::Entity source, const MissileTubes::MountPoint& tube, sp::ecs::Entity target); + static void explode(sp::ecs::Entity source, sp::ecs::Entity target, ExplodeOnTouch& eot); private: - static void explode(sp::ecs::Entity source, sp::ecs::Entity target, ExplodeOnTouch& eot); static void spawnProjectile(sp::ecs::Entity source, MissileTubes::MountPoint& tube, float angle, sp::ecs::Entity target); }; diff --git a/src/systems/probe.cpp b/src/systems/probe.cpp index 8eb69984e4..c35278d752 100644 --- a/src/systems/probe.cpp +++ b/src/systems/probe.cpp @@ -26,7 +26,7 @@ sp::ecs::Entity ProbeSystem::launch(sp::ecs::Entity ship, glm::vec2 target) auto probe = sp::ecs::Entity::create(); probe.addComponent(*ship_transform); probe.addComponent().callsign = probe.toString().split(":", 1)[0] + "P"; - probe.addComponent().lifetime = 600.0f; // 600 sec., 10 min. + probe.addComponent().maximum = 600.0f; // 600 sec., 10 min. auto& hull = probe.addComponent(); hull.current = hull.max = 1;