Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 37 additions & 35 deletions scripts/api/entity/shiptemplatebasedobject.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,27 @@ function Entity:setTemplate(template_name)
end
local isNewPlayerShip = comp.player_control and not comp.physics
-- 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
Expand Down Expand Up @@ -100,53 +119,36 @@ end
function Entity:getTypeName()
return self.components.typename.type_name
end
--- Returns this entity'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 entity'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 entity'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 entity.
--- Example: stbo:setHull(100) -- sets the hull point limit to either 100, or the limit if less than 100
--- 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 = math.min(math.max(0, amount), self.components.hull.max) end
self.components.hull = {}
self:setHealth(amount)
return self
end
--- Sets this entity's maximum limit of hull points.
--- If its current hull is greater than the new limit, reduce the hull to the limit.
--- 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 = math.max(0, amount)
if self.components.hull.current > self.components.hull.max then
self.components.hull.current = self.components.hull.max
end
end
self.components.hull = {}
self:setHealthMax(amount)
return self
end
--- Defines whether this entity can be destroyed by damage.
--- Defaults to true.
--- Example: stbo:setCanBeDestroyed(false) -- prevents the entity 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 entity 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 entity's shield segment with the given index.
--- 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:
--- stbo:getShieldLevel(0) -- returns front shield points
Expand Down
42 changes: 41 additions & 1 deletion scripts/api/entity/spaceobject.lua
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,47 @@ function Entity:isScannedByFaction(faction_name)
end
return false
end
--- Defines a function to call when this entity is destroyed by any means.
--- 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 entity is destroyed
--- entity:onDestroyed(function() print("Object destroyed!") end)
Expand Down
3 changes: 2 additions & 1 deletion scripts/api/shipTemplate.lua
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ end
--- Sets the number of default hull points for entities created from this ShipTemplate.
--- Example: template:setHull(100)
function ShipTemplate:setHull(amount)
self.hull = {current=amount, max=amount}
self.hull = {max=amount}
self.hull.current = amount -- Avoid default max of 100 being enforced on current value upon init
return self
end
--- Sets the maximum points per shield segment for entities created from this ShipTemplate.
Expand Down
6 changes: 4 additions & 2 deletions src/ai/ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -527,8 +528,8 @@ void ShipAI::runOrders()
}
if (bay->flags & DockingBay::Repair)
{
auto hull = owner.getComponent<Hull>();
if (hull && hull->current < hull->max)
auto health = owner.getComponent<Health>();
if (health && health->getHealth() < health->getHealthMax())
allow_undock = false;
}
}
Expand Down Expand Up @@ -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<Hull>() || Faction::getRelation(owner, entity) != FactionRelation::Enemy || entity == target)
continue;
if (RadarBlockSystem::isRadarBlockedFrom(owner_position, entity, short_range))
Expand Down
42 changes: 42 additions & 0 deletions src/components/health.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#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
{
private:
float current = 100.0f;
float max = 100.0f;
public:
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)

float getHealth() const { return current; }
void setHealth(float value) { current = std::clamp(value, 0.0f, max); }
float getHealthMax() const { return max; }
void setHealthMax(float value) { max = std::max(0.0f, value); if (current > max) current = max; }

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));
};
31 changes: 10 additions & 21 deletions src/components/hull.h
Original file line number Diff line number Diff line change
@@ -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));
};
3 changes: 2 additions & 1 deletion src/hardware/hardwareController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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->getHealth() / c->getHealthMax());
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);
Expand Down
8 changes: 8 additions & 0 deletions src/multiplayer/health.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
6 changes: 6 additions & 0 deletions src/multiplayer/health.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

#include "multiplayer/basic.h"
#include "components/health.h"

BASIC_REPLICATION_CLASS(HealthReplication, Health);
7 changes: 2 additions & 5 deletions src/multiplayer/hull.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
// Hull is now a marker component with no fields to replicate
EMPTY_REPLICATION_IMPL(HullReplication, Hull)
7 changes: 5 additions & 2 deletions src/playerInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -951,8 +952,10 @@ 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>();
hull.current = hull.max = 1;
auto& health = p.addComponent<Health>();
health.setHealth(1);
health.setHealthMax(1);
p.addComponent<Hull>();
p.addComponent<ShareShortRangeRadar>();
auto model = "SensorBuoy/SensorBuoyMKI.model";
auto idx = irandom(1, 3);
Expand Down
5 changes: 3 additions & 2 deletions src/screenComponents/indicatorOverlays.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -80,8 +81,8 @@ void GuiIndicatorOverlays::onDraw(sp::RenderTarget& renderer)
shield_low_warning_overlay->setAlpha(0);
}

if (auto hull = my_spaceship.getComponent<Hull>())
hull_hit_overlay->setAlpha(128 * (hull->damage_indicator / 1.5f));
if (auto health = my_spaceship.getComponent<Health>())
hull_hit_overlay->setAlpha(128 * (health->damage_indicator / 1.5f));
}else{
shield_hit_overlay->setAlpha(0);
shield_low_warning_overlay->setAlpha(0);
Expand Down
14 changes: 9 additions & 5 deletions src/screenComponents/infoDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -97,12 +98,15 @@ HullInfoDisplay::HullInfoDisplay(GuiContainer* owner, const string& id, float di

void HullInfoDisplay::onUpdate()
{
auto hull = my_spaceship.getComponent<Hull>();
setVisible(hull);
if (hull)
auto health = my_spaceship.getComponent<Health>();
const bool hull = my_spaceship.hasComponent<Hull>();
setVisible(health && hull);
if (health && hull)
{
setValue(toNearbyIntString(100.0f * hull->current / hull->max) + "%");
if (hull->current < hull->max / 4.0f)
float current = health->getHealth();
float max = health->getHealthMax();
setValue(toNearbyIntString(100.0f * current / max) + "%");
if (current < max / 4.0f)
setBackColor(glm::u8vec4(255, 0, 0, 255));
else
setBackColor(glm::u8vec4{255,255,255,255});
Expand Down
Loading
Loading