From b5e715eae2c36c91f31de030630cbce3b3498b6e Mon Sep 17 00:00:00 2001 From: oznogon Date: Tue, 30 Dec 2025 14:07:57 -0800 Subject: [PATCH] Split Hull and entity Health into components Separate the presence of a Hull (a marker indicating ship-like integrity) from an entity's health by creating a new Health component. This allows the creation of entities that have health and can be weapons targeted, but otherwise aren't treated as having repairable ship hulls for the purposes of AI targeting or threat estimates, and aren't selectable by Science or Relay unless they also have the Hull component. This remains backward compatible in scripting by translating set/getHull... Lua functions into respective set/getHealth... functions, and adding the Hull component if the set/getHull... functions are used. --- .../api/entity/shiptemplatebasedobject.lua | 75 ++++++------ scripts/api/entity/spaceobject.lua | 44 ++++++- src/ai/ai.cpp | 6 +- src/components/health.h | 36 ++++++ src/components/hull.h | 31 ++--- src/hardware/hardwareController.cpp | 3 +- src/multiplayer/health.cpp | 8 ++ src/multiplayer/health.h | 6 + src/multiplayer/hull.cpp | 7 +- src/playerInfo.cpp | 6 +- src/screenComponents/indicatorOverlays.cpp | 5 +- src/screenComponents/infoDisplay.cpp | 9 +- src/screenComponents/targetsContainer.cpp | 28 +++-- src/screenComponents/targetsContainer.h | 2 +- src/screens/crew6/relayScreen.cpp | 17 ++- src/screens/crew6/scienceScreen.cpp | 9 +- src/screens/extra/damcon.cpp | 11 +- src/screens/gm/gameMasterScreen.cpp | 5 +- src/screens/gm/tweak.cpp | 11 +- src/screens/spectatorScreen.cpp | 5 +- src/script/components.cpp | 19 +-- src/script/enum.h | 2 +- src/systems/damage.cpp | 114 ++++++++++-------- src/systems/damage.h | 2 +- src/systems/docking.cpp | 16 ++- src/systems/energysystem.cpp | 5 +- src/systems/gravity.cpp | 4 +- src/systems/missilesystem.cpp | 10 +- src/threatLevelEstimate.cpp | 15 ++- 29 files changed, 327 insertions(+), 184 deletions(-) create mode 100644 src/components/health.h create mode 100644 src/multiplayer/health.cpp create mode 100644 src/multiplayer/health.h diff --git a/scripts/api/entity/shiptemplatebasedobject.lua b/scripts/api/entity/shiptemplatebasedobject.lua index a4c7caf866..ac36194443 100644 --- a/scripts/api/entity/shiptemplatebasedobject.lua +++ b/scripts/api/entity/shiptemplatebasedobject.lua @@ -21,8 +21,27 @@ function Entity:setTemplate(template_name) error("Failed to find template: " .. tostring(template_name), 2) end -- print("Setting template:" .. template_name) + + -- Convert old hull component setters to health + hull for backward compatibility + if template.hull and type(template.hull) == "table" then + -- Create health component from hull data + local hull_data = template.hull + comp.health = { + current = hull_data.current or 100.0, + max = hull_data.max or 100.0, + allow_destruction = hull_data.allow_destruction, + damaged_by_energy = hull_data.damaged_by_energy, + damaged_by_kinetic = hull_data.damaged_by_kinetic, + damaged_by_emp = hull_data.damaged_by_emp, + on_destruction = hull_data.on_destruction, + on_taking_damage = hull_data.on_taking_damage + } + -- Create hull as empty marker component + comp.hull = {} + end + for key, value in next, template, nil do - if string.sub(key, 1, 2) ~= "__" then + if string.sub(key, 1, 2) ~= "__" and key ~= "hull" then comp[key] = value end end @@ -91,47 +110,35 @@ end function Entity:getTypeName() return self.components.typename.type_name end ---- Returns this STBO's hull points. ---- Example: stbo:getHull() +--- DEPRECATED: Returns this entity's health points. +--- Example: entity:getHull() function Entity:getHull() - if self.components.hull then return self.components.hull.current end - return 0 + return self:getHealth() end ---- Returns this STBO's maximum limit of hull points. ---- Example: stbo:getHullMax() +--- DEPRECATED: Returns this entity's maximum limit of health points. +--- Example: entity:getHullMax() function Entity:getHullMax() - if self.components.hull then return self.components.hull.max end - return 0 + return self:getHealthMax() end ---- Sets this STBO's hull points. +--- Adds the Hull marker component to the entity. +--- DEPRECATED: If passed an amount, also sets this entity's current health value. --- If set to a value larger than the maximum, this sets the value to the limit. --- If set to a value less than 0, this sets the value to 0. ---- Note that setting this value to 0 doesn't immediately destroy the STBO. ---- Example: stbo:setHull(100) -- sets the hull point limit to either 100, or the limit if less than 100 +--- Note that setting this value to 0 doesn't immediately destroy the entity. +--- Example: entity:setHull(100) -- sets the hull point limit to either 100, or the limit if less than 100 function Entity:setHull(amount) - if self.components.hull then self.components.hull.current = amount end + self.components.hull = {} + self:setHealth(amount) return self end ---- Sets this STBO's maximum limit of hull points. ---- Note that SpaceStations can't repair their own hull, so this only changes the percentage of remaining hull. ---- Example: stbo:setHullMax(100) -- sets the hull point limit to 100 +--- Adds the Hull marker component to the entity. +--- DEPRECATED: If passed an amount, also sets this entity's maximum limit of health points. +--- Example: entity:setHullMax(100) -- sets the hull point limit to 100 function Entity:setHullMax(amount) - if self.components.hull then self.components.hull.max = amount end + self.components.hull = {} + self:setHealthMax(amount) return self end ---- Defines whether this STBO can be destroyed by damage. ---- Defaults to true. ---- Example: stbo:setCanBeDestroyed(false) -- prevents the STBO from being destroyed by damage -function Entity:setCanBeDestroyed(allow_destroy) - if self.components.hull then self.components.hull.allow_destruction = allow_destroy end - return self -end ---- Returns whether the STBO can be destroyed by damage. ---- Example: stbo:getCanBeDestroyed() -function Entity:getCanBeDestroyed() - if self.components.hull then return self.components.hull.allow_destruction end - return false -end --- Returns the shield points for this STBO's shield segment with the given index. --- Shield segments are 0-indexed. --- Example for a ship with two shield segments: @@ -283,12 +290,12 @@ end --- [DEPRECATED] --- Use ShipTemplateBasedObject:getShieldLevel() with an index value. function Entity:getFrontShield() - return self.getShieldLevel(0) + return self:getShieldLevel(0) end --- [DEPRECATED] --- Use ShipTemplateBasedObject:setShieldsMax(). function Entity:getFrontShieldMax() - return self.getShieldMax(0) + return self:getShieldMax(0) end --- [DEPRECATED] --- Use ShipTemplateBasedObject:setShieldLevel() with an index value. @@ -305,12 +312,12 @@ end --- [DEPRECATED] --- Use ShipTemplateBasedObject:getShieldLevel() with an index value. function Entity:getRearShield() - return self.getShieldLevel(1) + return self:getShieldLevel(1) end --- [DEPRECATED] --- Use ShipTemplateBasedObject:setShieldsMax(). function Entity:getRearShieldMax() - return self.getShieldMax(1) + return self:getShieldMax(1) end --- [DEPRECATED] --- Use ShipTemplateBasedObject:setShieldLevel() with an index value. diff --git a/scripts/api/entity/spaceobject.lua b/scripts/api/entity/spaceobject.lua index d8c77cf404..c0d1b55fb2 100644 --- a/scripts/api/entity/spaceobject.lua +++ b/scripts/api/entity/spaceobject.lua @@ -458,12 +458,52 @@ function Entity:isScannedByFaction(faction_name) end return false end +--- Returns this entity's health points. +--- Example: entity:getHealth() +function Entity:getHealth() + if self.components.health then return self.components.health.current end + return 0 +end +--- Returns this entity's maximum limit of health points. +--- Example: entity:getHealthMax() +function Entity:getHealthMax() + if self.components.health then return self.components.health.max end + return 0 +end +--- Sets this entity's health points. +--- If set to a value larger than the maximum, this sets the value to the limit. +--- If set to a value less than 0, this sets the value to 0. +--- Note that setting this value to 0 doesn't immediately destroy the entity. +--- Example: entity:setHealth(100) -- sets the health point limit to either 100, or the limit if less than 100 +function Entity:setHealth(amount) + if self.components.health then self.components.health.current = amount end + return self +end +--- Sets this entity's maximum limit of health points. +--- Example: entity:setHealthMax(100) -- sets the health point limit to 100 +function Entity:setHealthMax(amount) + if self.components.health then self.components.health.max = amount end + return self +end +--- Defines whether this entity can be destroyed by damage. +--- Defaults to true. +--- Example: entity:setCanBeDestroyed(false) -- prevents the entity from being destroyed by damage +function Entity:setCanBeDestroyed(allow_destroy) + if self.components.health then self.components.health.allow_destruction = allow_destroy end + return self +end +--- Returns whether the entity can be destroyed by damage. +--- Example: entity:getCanBeDestroyed() +function Entity:getCanBeDestroyed() + if self.components.health then return self.components.health.allow_destruction end + return false +end --- Defines a function to call when this SpaceObject is destroyed by any means. --- Example: --- -- Prints to the console window or logging file when this SpaceObject is destroyed --- obj:onDestroyed(function() print("Object destroyed!") end) function Entity:onDestroyed(callback) - --TODO: Cases where we do not have hull - if self.components.hull then self.components.hull.on_destruction = callback end + --TODO: Cases where we do not have health + if self.components.health then self.components.health.on_destruction = callback end return self end diff --git a/src/ai/ai.cpp b/src/ai/ai.cpp index d487207de5..1c08125c58 100644 --- a/src/ai/ai.cpp +++ b/src/ai/ai.cpp @@ -6,6 +6,7 @@ #include "components/impulse.h" #include "components/warpdrive.h" #include "components/jumpdrive.h" +#include "components/health.h" #include "components/hull.h" #include "components/beamweapon.h" #include "components/missiletubes.h" @@ -527,8 +528,8 @@ void ShipAI::runOrders() } if (bay->flags & DockingBay::Repair) { - auto hull = owner.getComponent(); - if (hull && hull->current < hull->max) + auto health = owner.getComponent(); + if (health && health->current < health->max) allow_undock = false; } } @@ -837,6 +838,7 @@ sp::ecs::Entity ShipAI::findBestTarget(glm::vec2 position, float radius) auto owner_position = ot->getPosition(); for(auto entity : sp::CollisionSystem::queryArea(position - glm::vec2(radius, radius), position + glm::vec2(radius, radius))) { + // Seek only entities with Hull components. if (!entity.hasComponent() || Faction::getRelation(owner, entity) != FactionRelation::Enemy || entity == target) continue; if (RadarBlockSystem::isRadarBlockedFrom(owner_position, entity, short_range)) diff --git a/src/components/health.h b/src/components/health.h new file mode 100644 index 0000000000..9739be6bdd --- /dev/null +++ b/src/components/health.h @@ -0,0 +1,36 @@ +#pragma once + +#include "script/callback.h" +#include "systems/damage.h" + +// Component for entities that can take damage and be destroyed. +// This component tracks current and max health values for all damageable +// entities, and implements damage and destruction callbacks. +// Entities are typically destroyed if daamaged while at zero health. +// Destructability can be disabled to prevent player ship destruction in LARP +// scenarios or tutorials. +// +// For UI display purposes, player-facing interfaces by default display the +// health of entities ONLY if they have both Health and Hull components. +// Entities that have only Health can be targeted but don't show health values +// in player UI. +class Health +{ +public: + float current = 100.0f; + float max = 100.0f; + bool allow_destruction = true; + int damaged_by_flags = (1 << int(DamageType::Energy)) | (1 << int(DamageType::Kinetic)); + float damage_indicator = 0.0f; // Visual damage flash timer (1.5s) + + sp::script::Callback on_destruction; + sp::script::Callback on_taking_damage; +}; + +// Component indicates that an entity lacks a hull or health value, but an +// area damage effect (i.e. explosion) in the area still destroys it. +class DestroyedByAreaDamage +{ +public: + int damaged_by_flags = (1 << int(DamageType::Energy)) | (1 << int(DamageType::Kinetic)); +}; diff --git a/src/components/hull.h b/src/components/hull.h index 78e4830a12..41b9bc80df 100644 --- a/src/components/hull.h +++ b/src/components/hull.h @@ -1,27 +1,16 @@ #pragma once -#include "script/callback.h" -#include "systems/damage.h" - - -// Component to indicate that this entity has a hull and can get hull damage. -// Usually entities are destroyed once they reach zero hull. But you can disable this to prevent player ship destruction in LARP scenarios or tutorials. +// Marker component to indicate that this entity has a hull and can take hull +// damage. Entity health, presented as hull integrity, is tracked in the Health +// component. +// If an entity has the Hull component, it gains certain ship-like properties: +// - Automatic health regeneration from DockingBay entities with the +// DockingBay::Repair flag (as hull repairs) +// - Can be targeted by AI +// - Can be selected on Relay and Science radars +// - With Health component, display of health values as hull percentage/points +// in user interfaces class Hull { public: - float current = 100.0f; - float max = 100.0f; - bool allow_destruction = true; - int damaged_by_flags = (1 << int(DamageType::Energy)) | (1 << int(DamageType::Kinetic)); - float damage_indicator = 0.0f; - - sp::script::Callback on_destruction; - sp::script::Callback on_taking_damage; }; - -// Not having actual hull, but an explosion in the area will destroy this entity. -class DestroyedByAreaDamage -{ -public: - int damaged_by_flags = (1 << int(DamageType::Energy)) | (1 << int(DamageType::Kinetic)); -}; \ No newline at end of file diff --git a/src/hardware/hardwareController.cpp b/src/hardware/hardwareController.cpp index 8ad4637994..18a311ba20 100644 --- a/src/hardware/hardwareController.cpp +++ b/src/hardware/hardwareController.cpp @@ -5,6 +5,7 @@ #include "playerInfo.h" #include "ecs/query.h" +#include "components/health.h" #include "components/hull.h" #include "components/shields.h" #include "components/reactor.h" @@ -377,7 +378,7 @@ bool HardwareController::getVariableValue(string variable_name, float& value) value = bool(ship) ? 1.0f : 0.0f; return true; } - SHIP_VARIABLE("Hull", Hull, 100.0f * c->current / c->max); + SHIP_VARIABLE("Hull", Health, 100.0f * c->current / c->max); SHIP_VARIABLE("FrontShield", Shields, c->entries.size() > 0 ? c->entries[0].percentage() : 0.0f); SHIP_VARIABLE("RearShield", Shields, c->entries.size() > 1 ? c->entries[1].percentage() : 0.0f); SHIP_VARIABLE("Shield0", Shields, c->entries.size() > 0 ? c->entries[0].percentage() : 0.0f); diff --git a/src/multiplayer/health.cpp b/src/multiplayer/health.cpp new file mode 100644 index 0000000000..712cc5deb6 --- /dev/null +++ b/src/multiplayer/health.cpp @@ -0,0 +1,8 @@ +#include "multiplayer/health.h" +#include "multiplayer.h" + +BASIC_REPLICATION_IMPL(HealthReplication, Health) + BASIC_REPLICATION_FIELD(current); + BASIC_REPLICATION_FIELD(max); + BASIC_REPLICATION_FIELD(damage_indicator); +} diff --git a/src/multiplayer/health.h b/src/multiplayer/health.h new file mode 100644 index 0000000000..172dc9aa75 --- /dev/null +++ b/src/multiplayer/health.h @@ -0,0 +1,6 @@ +#pragma once + +#include "multiplayer/basic.h" +#include "components/health.h" + +BASIC_REPLICATION_CLASS(HealthReplication, Health); diff --git a/src/multiplayer/hull.cpp b/src/multiplayer/hull.cpp index 001dd7f4bb..f734829c28 100644 --- a/src/multiplayer/hull.cpp +++ b/src/multiplayer/hull.cpp @@ -2,8 +2,5 @@ #include "multiplayer.h" -BASIC_REPLICATION_IMPL(HullReplication, Hull) - BASIC_REPLICATION_FIELD(current); - BASIC_REPLICATION_FIELD(max); - BASIC_REPLICATION_FIELD(damage_indicator); -} \ No newline at end of file +// Hull is now a marker component with no fields to replicate +EMPTY_REPLICATION_IMPL(HullReplication, Hull) \ No newline at end of file diff --git a/src/playerInfo.cpp b/src/playerInfo.cpp index 5cf2d857f5..99bb277c01 100644 --- a/src/playerInfo.cpp +++ b/src/playerInfo.cpp @@ -31,6 +31,7 @@ #include "components/collision.h" #include "components/impulse.h" +#include "components/health.h" #include "components/hull.h" #include "components/customshipfunction.h" #include "components/shiplog.h" @@ -953,8 +954,9 @@ void PlayerInfo::onReceiveClientCommand(int32_t client_id, sp::io::DataBuffer& p trace.max_size = 10.0; trace.color = {96, 192, 128, 255}; trace.flags = RadarTrace::LongRange; - auto& hull = p.addComponent(); - hull.current = hull.max = 1; + auto& health = p.addComponent(); + health.current = health.max = 1; + p.addComponent(); p.addComponent(); auto model = "SensorBuoy/SensorBuoyMKI.model"; auto idx = irandom(1, 3); diff --git a/src/screenComponents/indicatorOverlays.cpp b/src/screenComponents/indicatorOverlays.cpp index 3d347bf723..066676d8ba 100644 --- a/src/screenComponents/indicatorOverlays.cpp +++ b/src/screenComponents/indicatorOverlays.cpp @@ -8,6 +8,7 @@ #include "components/warpdrive.h" #include "components/jumpdrive.h" #include "components/shields.h" +#include "components/health.h" #include "components/hull.h" #include "multiplayer_server.h" #include "i18n.h" @@ -80,8 +81,8 @@ void GuiIndicatorOverlays::onDraw(sp::RenderTarget& renderer) shield_low_warning_overlay->setAlpha(0); } - if (auto hull = my_spaceship.getComponent()) - hull_hit_overlay->setAlpha(128 * (hull->damage_indicator / 1.5f)); + if (auto health = my_spaceship.getComponent()) + hull_hit_overlay->setAlpha(128 * (health->damage_indicator / 1.5f)); }else{ shield_hit_overlay->setAlpha(0); shield_low_warning_overlay->setAlpha(0); diff --git a/src/screenComponents/infoDisplay.cpp b/src/screenComponents/infoDisplay.cpp index de593fe88a..81c7aafea7 100644 --- a/src/screenComponents/infoDisplay.cpp +++ b/src/screenComponents/infoDisplay.cpp @@ -5,6 +5,7 @@ #include "components/reactor.h" #include "components/collision.h" #include "components/shields.h" +#include "components/health.h" #include "components/hull.h" #include "components/coolant.h" #include "playerInfo.h" @@ -97,10 +98,12 @@ HullInfoDisplay::HullInfoDisplay(GuiContainer* owner, const string& id, float di void HullInfoDisplay::onUpdate() { - if (auto hull = my_spaceship.getComponent()) + // Show Health as Hull, and only if the entity also has Hull. + auto health = my_spaceship.getComponent(); + if (health && my_spaceship.hasComponent()) { - setValue(toNearbyIntString(100.0f * hull->current / hull->max) + "%"); - if (hull->current < hull->max / 4.0f) + setValue(toNearbyIntString(100.0f * health->current / health->max) + "%"); + if (health->current < health->max / 4.0f) setBackColor(glm::u8vec4(255, 0, 0, 255)); else setBackColor(glm::u8vec4{255,255,255,255}); diff --git a/src/screenComponents/targetsContainer.cpp b/src/screenComponents/targetsContainer.cpp index d5c0608764..9f69b6c8b1 100644 --- a/src/screenComponents/targetsContainer.cpp +++ b/src/screenComponents/targetsContainer.cpp @@ -1,12 +1,14 @@ #include "targetsContainer.h" #include "playerInfo.h" +#include "ecs/query.h" + #include "systems/collision.h" -#include "components/hull.h" #include "components/collision.h" +#include "components/comms.h" +#include "components/health.h" +#include "components/hull.h" #include "components/scanning.h" #include "components/radar.h" -#include "ecs/query.h" - TargetsContainer::TargetsContainer() { @@ -199,21 +201,21 @@ bool TargetsContainer::isValidTarget(sp::ecs::Entity entity, ESelectionType sele { if (entity == my_spaceship) return false; - switch(selection_type) + switch (selection_type) { - case Selectable: + case Selectable: // Used by Relay if (entity.hasComponent()) return true; - if (entity.getComponent()) return true; - if (entity.getComponent()) return true; + if (entity.hasComponent()) return true; + if (entity.hasComponent()) return true; break; - case Targetable: - if (entity.hasComponent()) return true; + case Targetable: // Used by Weapons + if (entity.hasComponent()) return true; break; - case Scannable: + case Scannable: // Used by Science if (entity.hasComponent()) return true; - if (entity.getComponent()) return true; - if (entity.getComponent()) return true; - if (entity.getComponent()) return true; + if (entity.hasComponent()) return true; + if (entity.hasComponent()) return true; + if (entity.hasComponent()) return true; break; } return false; diff --git a/src/screenComponents/targetsContainer.h b/src/screenComponents/targetsContainer.h index 3e792e1037..4cd9954700 100644 --- a/src/screenComponents/targetsContainer.h +++ b/src/screenComponents/targetsContainer.h @@ -41,4 +41,4 @@ class TargetsContainer bool isValidTarget(sp::ecs::Entity entity, ESelectionType selection_type); }; -#endif//TARGETS_CONTAINER_H +#endif//TARGETS_CONTAINER_H \ No newline at end of file diff --git a/src/screens/crew6/relayScreen.cpp b/src/screens/crew6/relayScreen.cpp index f44502c8c1..0e5caf49fd 100644 --- a/src/screens/crew6/relayScreen.cpp +++ b/src/screens/crew6/relayScreen.cpp @@ -29,11 +29,24 @@ //TODO: This function does not belong here. static bool canHack(sp::ecs::Entity entity) { + if (!my_spaceship) return false; + if (my_spaceship == entity || !my_spaceship.hasComponent()) return false; auto scanstate = entity.getComponent(); - //TODO: Check if there are actually hackable systems. if (scanstate && scanstate->getStateFor(my_spaceship) == ScanState::State::NotScanned) return true; - return Faction::getRelation(entity, my_spaceship) != FactionRelation::Friendly; + + // Check for hackable ShipSystems. + bool has_hackable_systems = false; + for (int n = 0; n < static_cast(ShipSystem::Type::COUNT); n++) + { + auto sys = ShipSystem::get(entity, ShipSystem::Type(n)); + if (sys && sys->can_be_hacked) has_hackable_systems = true; + }; + + if (Faction::getRelation(entity, my_spaceship) == FactionRelation::Friendly) + return false; + else + return has_hackable_systems; } RelayScreen::RelayScreen(GuiContainer* owner, bool allow_comms) diff --git a/src/screens/crew6/scienceScreen.cpp b/src/screens/crew6/scienceScreen.cpp index 0e3dfeeb11..4014b5e35b 100644 --- a/src/screens/crew6/scienceScreen.cpp +++ b/src/screens/crew6/scienceScreen.cpp @@ -8,6 +8,7 @@ #include "components/beamweapon.h" #include "components/shields.h" +#include "components/health.h" #include "components/hull.h" #include "components/collision.h" #include "components/radar.h" @@ -419,8 +420,12 @@ void ScienceScreen::onDraw(sp::RenderTarget& renderer) } info_shields->setValue(str); } - if (auto hull = target.getComponent()) - info_hull->setValue(int(ceil(hull->current))); + // Show health only if entity has both Health and Hull. + if (auto health = target.getComponent()) + { + if (target.hasComponent()) + info_hull->setValue(static_cast(ceil(health->current))); + } } // On a full scan, show tactical and systems data (if any), and its diff --git a/src/screens/extra/damcon.cpp b/src/screens/extra/damcon.cpp index 719fc06d0d..ad5c341c42 100644 --- a/src/screens/extra/damcon.cpp +++ b/src/screens/extra/damcon.cpp @@ -4,6 +4,7 @@ #include "screenComponents/shieldFreqencySelect.h" #include "screenComponents/shipInternalView.h" #include "screenComponents/customShipFunctions.h" +#include "components/health.h" #include "components/hull.h" #include "i18n.h" @@ -36,10 +37,12 @@ void DamageControlScreen::onDraw(sp::RenderTarget& renderer) if (my_spaceship) { - auto hull = my_spaceship.getComponent(); - if (hull) { - hull_display->setValue(string(int(100 * hull->current / hull->max)) + "%"); - if (hull->current < hull->max / 4.0f) + auto health = my_spaceship.getComponent(); + auto hull = my_spaceship.hasComponent(); + if (health && hull) + { + hull_display->setValue(string(int(100 * health->current / health->max)) + "%"); + if (health->current < health->max / 4.0f) hull_display->setBackColor(glm::u8vec4(255, 0, 0, 255)); else hull_display->setBackColor(glm::u8vec4{255,255,255,255}); diff --git a/src/screens/gm/gameMasterScreen.cpp b/src/screens/gm/gameMasterScreen.cpp index 3bacb1e662..2ebd4e5884 100644 --- a/src/screens/gm/gameMasterScreen.cpp +++ b/src/screens/gm/gameMasterScreen.cpp @@ -11,6 +11,7 @@ #include "components/faction.h" #include "components/collision.h" #include "components/gravity.h" +#include "components/health.h" #include "components/hull.h" #include "components/comms.h" #include "components/player.h" @@ -39,8 +40,8 @@ static std::unordered_map getGMInfo(sp::ecs::Entity entity) result[trMark("gm_info", "CallSign")] = cs->callsign; if (auto tn = entity.getComponent()) result[trMark("gm_info", "Type")] = tn->localized; - if (auto hull = entity.getComponent()) - result[trMark("gm_info", "Hull")] = string(hull->current) + "/" + string(hull->max); + if (auto health = entity.getComponent()) + result[trMark("gm_info", "Health")] = string(health->current) + "/" + string(health->max); //for(int n=0; n()) - info[trMark("gm_info", "Hull")] = string(int(100.0f * hull->current / hull->max)) + "%"; + if (auto health = target.getComponent()) + info[trMark("gm_info", "Health")] = string(int(100.0f * health->current / health->max)) + "%"; for (auto i = info.begin(); i != info.end(); i++) selection_info[i->first] = i->second; diff --git a/src/script/components.cpp b/src/script/components.cpp index 2d64e25e64..789ca09793 100644 --- a/src/script/components.cpp +++ b/src/script/components.cpp @@ -16,6 +16,7 @@ #include "components/name.h" #include "components/moveto.h" #include "components/lifetime.h" +#include "components/health.h" #include "components/hull.h" #include "components/shields.h" #include "components/docking.h" @@ -362,15 +363,17 @@ void initComponentScriptBindings() sp::script::ComponentHandler::name("allow_radar_link"); BIND_MEMBER(AllowRadarLink, owner); + sp::script::ComponentHandler::name("health"); + BIND_MEMBER(Health, current); + BIND_MEMBER(Health, max); + BIND_MEMBER(Health, allow_destruction); + BIND_MEMBER(Health, on_destruction); + BIND_MEMBER(Health, on_taking_damage); + BIND_MEMBER_FLAG(Health, damaged_by_flags, "damaged_by_energy", (1 << int(DamageType::Energy))); + BIND_MEMBER_FLAG(Health, damaged_by_flags, "damaged_by_kinetic", (1 << int(DamageType::Kinetic))); + BIND_MEMBER_FLAG(Health, damaged_by_flags, "damaged_by_emp", (1 << int(DamageType::EMP))); + sp::script::ComponentHandler::name("hull"); - BIND_MEMBER(Hull, current); - BIND_MEMBER(Hull, max); - BIND_MEMBER(Hull, allow_destruction); - BIND_MEMBER(Hull, on_destruction); - BIND_MEMBER(Hull, on_taking_damage); - BIND_MEMBER_FLAG(Hull, damaged_by_flags, "damaged_by_energy", (1 << int(DamageType::Energy))); - BIND_MEMBER_FLAG(Hull, damaged_by_flags, "damaged_by_kinetic", (1 << int(DamageType::Kinetic))); - BIND_MEMBER_FLAG(Hull, damaged_by_flags, "damaged_by_emp", (1 << int(DamageType::EMP))); sp::script::ComponentHandler::name("shields"); BIND_MEMBER_NAMED(Shields, front_system.health, "front_health"); diff --git a/src/script/enum.h b/src/script/enum.h index 4744438c16..57456311f8 100644 --- a/src/script/enum.h +++ b/src/script/enum.h @@ -1,7 +1,7 @@ #pragma once #include "script/environment.h" -#include "components/hull.h" +#include "components/health.h" #include "components/collision.h" #include "components/faction.h" #include "components/ai.h" diff --git a/src/systems/damage.cpp b/src/systems/damage.cpp index ef26fc71c1..8db52837e9 100644 --- a/src/systems/damage.cpp +++ b/src/systems/damage.cpp @@ -2,7 +2,7 @@ #include "systems/collision.h" #include "ecs/query.h" #include "components/collision.h" -#include "components/hull.h" +#include "components/health.h" #include "components/shields.h" #include "components/beamweapon.h" #include "components/radar.h" @@ -15,9 +15,9 @@ void DamageSystem::update(float delta) { - for(auto [entity, hull] : sp::ecs::Query()) { - if (hull.damage_indicator > 0.0f) - hull.damage_indicator -= delta; + for(auto [entity, health] : sp::ecs::Query()) { + if (health.damage_indicator > 0.0f) + health.damage_indicator -= delta; } } @@ -82,7 +82,7 @@ void DamageSystem::applyDamage(sp::ecs::Entity entity, float amount, const Damag if (amount > 0.0f) { - takeHullDamage(entity, amount, info); + takeHealthDamage(entity, amount, info); if (auto dbad = entity.getComponent()) { if (dbad->damaged_by_flags & (1 << int(info.type))) { entity.destroy(); @@ -91,39 +91,44 @@ void DamageSystem::applyDamage(sp::ecs::Entity entity, float amount, const Damag } } -void DamageSystem::takeHullDamage(sp::ecs::Entity entity, float amount, const DamageInfo& info) +void DamageSystem::takeHealthDamage(sp::ecs::Entity entity, float amount, const DamageInfo& info) { - auto hull = entity.getComponent(); - if (!hull) + auto health = entity.getComponent(); + if (!health) return; - if (!(hull->damaged_by_flags & (1 << int(info.type)))) + if (!(health->damaged_by_flags & (1 << int(info.type)))) return; // If taking non-EMP damage, light up the hull damage overlay. - hull->damage_indicator = 1.5f; + health->damage_indicator = 1.5f; - if (gameGlobalInfo->use_system_damage && hull) + if (gameGlobalInfo->use_system_damage && health) { if (auto sys = ShipSystem::get(entity, info.system_target)) { //Target specific system - float system_damage = (amount / hull->max) * 2.0f; - if (info.type == DamageType::Energy) - system_damage *= 3.0f; //Beam weapons do more system damage, as they penetrate the hull easier. - sys->health -= system_damage; - if (sys->health < -1.0f) - sys->health = -1.0f; - - for(int n=0; n<2; n++) + if (health->max > 0.0f) { - auto random_system = ShipSystem::Type(irandom(0, ShipSystem::COUNT - 1)); - //Damage the system compared to the amount of hull damage you would do. If we have less hull strength you get more system damage. - float system_damage = (amount / hull->max) * 1.0f; - sys = ShipSystem::get(entity, random_system); - if (sys) { - sys->health -= system_damage; - if (sys->health < -1.0f) - sys->health = -1.0f; + float system_damage = (amount / health->max) * 2.0f; + // Beam weapons penetrate the hull more easily and deal more + // system damage. + if (info.type == DamageType::Energy) + system_damage *= 3.0f; + sys->health -= system_damage; + if (sys->health < -1.0f) + sys->health = -1.0f; + + for(int n=0; n<2; n++) + { + auto random_system = ShipSystem::Type(irandom(0, ShipSystem::COUNT - 1)); + //Damage the system compared to the amount of hull damage you would do. If we have less hull strength you get more system damage. + float system_damage = (amount / health->max) * 1.0f; + sys = ShipSystem::get(entity, random_system); + if (sys) { + sys->health -= system_damage; + if (sys->health < -1.0f) + sys->health = -1.0f; + } } } @@ -132,40 +137,45 @@ void DamageSystem::takeHullDamage(sp::ecs::Entity entity, float amount, const Da else amount *= 0.5f; }else{ - //Damage the system compared to the amount of hull damage you would do. If we have less hull strength you get more system damage. - float system_damage = (amount / hull->max) * 3.0f; - if (info.type == DamageType::Energy) - system_damage *= 2.5f; //Beam weapons do more system damage, as they penetrate the hull easier. - - auto random_system = ShipSystem::Type(irandom(0, ShipSystem::COUNT - 1)); - sys = ShipSystem::get(entity, random_system); - if (sys) { - sys->health -= system_damage; - if (sys->health < -1.0f) - sys->health = -1.0f; + // Damage the system compared to the amount of hull damage dealt. If + // we have less hull strength, we take more system damage. + if (health->max > 0.0f) + { + float system_damage = (amount / health->max) * 3.0f; + // Beam weapons penetrate the hull more easily and deal more + // system damage. + if (info.type == DamageType::Energy) + system_damage *= 2.5f; + auto random_system = ShipSystem::Type(irandom(0, ShipSystem::COUNT - 1)); + sys = ShipSystem::get(entity, random_system); + if (sys) { + sys->health -= system_damage; + if (sys->health < -1.0f) + sys->health = -1.0f; + } } } } - hull->current -= amount; - if (hull->current <= 0.0f && !hull->allow_destruction) + health->current -= amount; + if (health->current <= 0.0f && !health->allow_destruction) { - hull->current = 1; + health->current = 1; } - if (hull->current <= 0.0f) + if (health->current <= 0.0f) { destroyedByDamage(entity, info); return; } - if (hull->on_taking_damage) + if (health->on_taking_damage) { if (info.instigator) { - LuaConsole::checkResult(hull->on_taking_damage.call(entity, info.instigator)); + LuaConsole::checkResult(health->on_taking_damage.call(entity, info.instigator)); } else { - LuaConsole::checkResult(hull->on_taking_damage.call(entity)); + LuaConsole::checkResult(health->on_taking_damage.call(entity)); } } } @@ -181,13 +191,12 @@ void DamageSystem::destroyedByDamage(sp::ecs::Entity entity, const DamageInfo& i } } + auto health = entity.getComponent(); if (info.instigator) { float points = 0; - auto hull = entity.getComponent(); - if (hull) - points += hull->max * 0.1f; + if (health) points += health->max * 0.1f; auto shields = entity.getComponent(); if (shields && !shields->entries.empty()) { @@ -195,21 +204,20 @@ void DamageSystem::destroyedByDamage(sp::ecs::Entity entity, const DamageInfo& i points += shield.max * 0.1f; points /= shields->entries.size(); } - + if (Faction::getRelation(info.instigator, entity) == FactionRelation::Enemy) Faction::getInfo(info.instigator).reputation_points += points; else Faction::getInfo(info.instigator).reputation_points = std::max(Faction::getInfo(info.instigator).reputation_points - points, 0.0f); } - auto hull = entity.getComponent(); - if (hull->on_destruction) + if (health && health->on_destruction) { if (info.instigator) { - LuaConsole::checkResult(hull->on_destruction.call(entity, info.instigator)); + LuaConsole::checkResult(health->on_destruction.call(entity, info.instigator)); } else { - LuaConsole::checkResult(hull->on_destruction.call(entity)); + LuaConsole::checkResult(health->on_destruction.call(entity)); } } diff --git a/src/systems/damage.h b/src/systems/damage.h index a20aaf9c85..487ef9c6f1 100644 --- a/src/systems/damage.h +++ b/src/systems/damage.h @@ -40,6 +40,6 @@ class DamageSystem : public sp::ecs::System static void applyDamage(sp::ecs::Entity entity, float amount, const DamageInfo& info); private: - static void takeHullDamage(sp::ecs::Entity entity, float amount, const DamageInfo& info); + static void takeHealthDamage(sp::ecs::Entity entity, float amount, const DamageInfo& info); static void destroyedByDamage(sp::ecs::Entity entity, const DamageInfo& info); }; diff --git a/src/systems/docking.cpp b/src/systems/docking.cpp index 8785b3ebc8..db29a277c2 100644 --- a/src/systems/docking.cpp +++ b/src/systems/docking.cpp @@ -4,6 +4,7 @@ #include "components/impulse.h" #include "components/maneuveringthrusters.h" #include "components/reactor.h" +#include "components/health.h" #include "components/hull.h" #include "components/warpdrive.h" #include "components/jumpdrive.h" @@ -61,14 +62,17 @@ void DockingSystem::update(float delta) } auto bay = docking_port.target.getComponent(); - if (bay && (bay->flags & DockingBay::Repair)) //Check if what we are docked to allows hull repairs, and if so, do it. + // Check if what we are docked to allows hull repairs, and if + // so, do it. + if (bay && (bay->flags & DockingBay::Repair)) { - auto hull = entity.getComponent(); - if (hull && hull->current < hull->max) + const auto hull = entity.hasComponent(); + auto health = entity.getComponent(); + if (hull && health && health->current < health->max) { - hull->current += delta; - if (hull->current > hull->max) - hull->current = hull->max; + health->current += delta; + if (health->current > health->max) + health->current = health->max; } } diff --git a/src/systems/energysystem.cpp b/src/systems/energysystem.cpp index 5121d76e28..682c30a8e5 100644 --- a/src/systems/energysystem.cpp +++ b/src/systems/energysystem.cpp @@ -2,6 +2,7 @@ #include "ecs/query.h" #include "components/coolant.h" #include "components/reactor.h" +#include "components/health.h" #include "components/hull.h" #include "components/collision.h" #include "components/rendering.h" @@ -55,8 +56,8 @@ void EnergySystem::update(float delta) // destroying the ship and damaging a 0.5U radius. if (reactor.health < -0.9f && reactor.heat_level == 1.0f && reactor.overload_explode && game_server) { - auto hull = entity.getComponent(); - if (hull && hull->allow_destruction) { + auto health = entity.getComponent(); + if (health && health->allow_destruction) { auto transform = entity.getComponent(); if (transform) { auto e = sp::ecs::Entity::create(); diff --git a/src/systems/gravity.cpp b/src/systems/gravity.cpp index ba25da5d5a..a859ec3b80 100644 --- a/src/systems/gravity.cpp +++ b/src/systems/gravity.cpp @@ -1,6 +1,7 @@ #include "systems/gravity.h" #include "components/gravity.h" #include "components/collision.h" +#include "components/health.h" #include "components/hull.h" #include "systems/collision.h" #include "systems/damage.h" @@ -60,7 +61,8 @@ void GravitySystem::update(float delta) DamageSystem::applyDamage(target, 100000.0, info); //try to destroy the object by inflicting a huge amount of damage if (target) { - if (!target.hasComponent() || target.getComponent()->allow_destruction) + auto health = target.getComponent(); + if (!health || health->allow_destruction) target.destroy(); return; } diff --git a/src/systems/missilesystem.cpp b/src/systems/missilesystem.cpp index c7f64b2e22..6c30616baa 100644 --- a/src/systems/missilesystem.cpp +++ b/src/systems/missilesystem.cpp @@ -4,7 +4,7 @@ #include "components/missiletubes.h" #include "components/missile.h" #include "components/lifetime.h" -#include "components/hull.h" +#include "components/health.h" #include "components/radar.h" #include "components/docking.h" #include "components/warpdrive.h" @@ -137,15 +137,15 @@ void MissileSystem::collision(sp::ecs::Entity a, sp::ecs::Entity b, float force) if (!game_server) return; auto deot = a.getComponent(); if (deot && deot->trigger_holdoff_delay <= 0.0f) { - auto hull = b.getComponent(); - if (!hull) return; + auto health = b.getComponent(); + if (!health) return; deot->triggered = true; } auto eot = a.getComponent(); if (!eot) return; if (eot->owner == b) return; - auto hull = b.getComponent(); - if (!hull) return; + auto health = b.getComponent(); + if (!health) return; explode(a, b, *eot); } diff --git a/src/threatLevelEstimate.cpp b/src/threatLevelEstimate.cpp index fb81739735..a5b4ebd15c 100644 --- a/src/threatLevelEstimate.cpp +++ b/src/threatLevelEstimate.cpp @@ -1,6 +1,7 @@ #include "gameGlobalInfo.h" #include "threatLevelEstimate.h" #include "ecs/query.h" +#include "components/health.h" #include "components/hull.h" #include "components/collision.h" #include "components/shields.h" @@ -52,9 +53,10 @@ float ThreatLevelEstimate::getThreatFor(sp::ecs::Entity ship) float threat = 0.0; - auto hull = ship.getComponent(); - if (hull) - threat += hull->max - hull->current; + auto health = ship.getComponent(); + bool hull = ship.hasComponent(); + if (hull && health) + threat += health->max - health->current; auto shields = ship.getComponent(); if (shields) { if (shields->active) @@ -81,10 +83,11 @@ float ThreatLevelEstimate::getThreatFor(sp::ecs::Entity ship) } bool is_being_attacked = false; - hull = entity.getComponent(); + health = entity.getComponent(); + hull = entity.hasComponent(); float score = 200.0f; - if (hull) - score += hull->max; + if (hull && health) + score += health->max; auto shields = entity.getComponent(); if (shields) { for(auto& shield : shields->entries) {