Rename ship/station scrap drop entities to "debris"

This commit is contained in:
2026-07-23 20:51:05 +02:00
parent 11daa61714
commit 8b71fe1a03
48 changed files with 468 additions and 443 deletions

View File

@@ -43,7 +43,7 @@ AiSystem::AiSystem(const GameConfig& config)
}
void AiSystem::tick(EntityAdmin& admin, const BuildingSystem& buildings,
const ScrapSystem& scraps)
const DebrisSystem& debris)
{
TRACE();
@@ -54,7 +54,7 @@ void AiSystem::tick(EntityAdmin& admin, const BuildingSystem& buildings,
m_retreatEvaluator.evaluate(admin);
m_attackEvaluator.evaluate(admin);
m_repairEvaluator.evaluate(admin);
m_salvageScrapEvaluator.evaluate(admin, scraps);
m_salvageScrapEvaluator.evaluate(admin, debris);
m_deliverScrapEvaluator.evaluate(admin, buildings);
// Phase 2: pick the highest-scoring behavior per ship.

View File

@@ -19,7 +19,7 @@
class BuildingSystem;
class EntityAdmin;
class ScrapSystem;
class DebrisSystem;
struct GameConfig;
// Orchestrates ship-behavior decision-making in three batched phases:
@@ -34,7 +34,7 @@ class AiSystem
public:
explicit AiSystem(const GameConfig& config);
void tick(EntityAdmin& admin, const BuildingSystem& buildings, const ScrapSystem& scraps);
void tick(EntityAdmin& admin, const BuildingSystem& buildings, const DebrisSystem& debris);
private:
void selectWinningBehaviors(EntityAdmin& admin);

View File

@@ -23,7 +23,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/RepairSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/SalvagerSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/DebrisSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h
PARENT_SCOPE
)
@@ -53,7 +53,7 @@ SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/RepairSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/SalvagerSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DebrisSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.cpp
PARENT_SCOPE
)

View File

@@ -0,0 +1,78 @@
#include "DebrisSystem.h"
#include "DespawnAtComponent.h"
#include "EntityAdmin.h"
#include "PositionComponent.h"
#include "DebrisComponent.h"
#include "tracing.h"
DebrisSystem::DebrisSystem(EntityAdmin& admin)
: m_admin(admin)
{
}
entt::entity DebrisSystem::spawn(QVector2D position, int amount, Tick despawnAt)
{
return m_admin.spawnDebris(position, amount, despawnAt);
}
void DebrisSystem::tickDespawn(Tick currentTick)
{
TRACE();
std::vector<entt::entity> expired;
m_admin.forEach<DespawnAtComponent>(
[&expired, currentTick](entt::entity e, DespawnAtComponent& d)
{
if (d.tick <= currentTick)
{
expired.push_back(e);
}
});
for (entt::entity e : expired)
{
m_admin.destroy(e);
}
}
std::optional<int> DebrisSystem::consume(entt::entity entity)
{
if (!m_admin.isValid(entity) || !m_admin.hasAll<DebrisComponent>(entity))
{
return std::nullopt;
}
int amount = m_admin.get<DebrisComponent>(entity).amount;
m_admin.destroy(entity);
return amount;
}
bool DebrisSystem::collectOne(entt::entity entity)
{
if (!m_admin.isValid(entity) || !m_admin.hasAll<DebrisComponent>(entity))
{
return false;
}
DebrisComponent& data = m_admin.get<DebrisComponent>(entity);
if (data.amount <= 0)
{
return false;
}
--data.amount;
if (data.amount <= 0)
{
m_admin.destroy(entity);
}
return true;
}
std::vector<DebrisInfo> DebrisSystem::getAllDebrisInfo() const
{
std::vector<DebrisInfo> result;
m_admin.forEach<DebrisComponent>(
[&result, this](entt::entity e, const DebrisComponent& sd)
{
result.push_back(DebrisInfo{e, m_admin.get<PositionComponent>(e).value, sd.amount});
});
return result;
}

View File

@@ -11,31 +11,35 @@
class EntityAdmin;
struct ScrapInfo
// A piece of debris and the scrap amount it still holds (REQ-RES-DEBRIS-DROP).
struct DebrisInfo
{
entt::entity entity;
QVector2D position;
int amount;
};
class ScrapSystem
// Manages debris entities: the salvageable objects dropped by destroyed ships and
// defence stations (REQ-RES-DEBRIS-DROP). Each piece carries a scrap amount that
// salvage modules collect one unit at a time (REQ-SHP-SALVAGE).
class DebrisSystem
{
public:
explicit ScrapSystem(EntityAdmin& admin);
explicit DebrisSystem(EntityAdmin& admin);
entt::entity spawn(QVector2D position, int amount, Tick despawnAt);
void tickDespawn(Tick currentTick);
// Removes the scrap and returns its amount, or nullopt if not found.
// Removes the debris and returns its remaining scrap amount, or nullopt if not found.
std::optional<int> consume(entt::entity entity);
// Collects a single scrap unit from the pile: decrements its amount by one,
// Collects a single scrap unit from the debris: decrements its amount by one,
// destroying the entity once depleted. Returns true if a scrap was collected,
// false if the entity is invalid or already empty (REQ-SHP-SALVAGE).
bool collectOne(entt::entity entity);
// Lightweight snapshot for callers that need to iterate all scrap.
std::vector<ScrapInfo> getAllScrapInfo() const;
// Lightweight snapshot for callers that need to iterate all debris.
std::vector<DebrisInfo> getAllDebrisInfo() const;
private:
EntityAdmin& m_admin;

View File

@@ -14,8 +14,8 @@
#include "ModuleOwnerComponent.h"
#include "PositionComponent.h"
#include "SalvagerComponent.h"
#include "ScrapDataComponent.h"
#include "ScrapSystem.h"
#include "DebrisComponent.h"
#include "DebrisSystem.h"
#include "tracing.h"
SalvagerSystem::SalvagerSystem(EntityAdmin& admin)
@@ -23,14 +23,14 @@ SalvagerSystem::SalvagerSystem(EntityAdmin& admin)
{
}
void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& buildings,
void SalvagerSystem::tick(Tick currentTick, DebrisSystem& debris, BuildingSystem& buildings,
std::vector<BeamFiredEvent>& outBeamFiredEvents)
{
TRACE();
// Apply collections whose mid-beam delay has elapsed (cycles started earlier).
applyPendingCollections(currentTick, scraps);
applyPendingCollections(currentTick, debris);
const std::vector<ScrapInfo> allScrap = scraps.getAllScrapInfo();
const std::vector<DebrisInfo> allDebris = debris.getAllDebrisInfo();
// Tick down per-module collection cooldowns.
m_admin.forEach<SalvagerComponent>(
@@ -40,8 +40,8 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem&
});
// Scrap units already claimed by not-yet-applied collection cycles, so two
// modules don't both target the last unit of the same pile (the claim would be
// dropped at apply time). A pile is available while its amount exceeds its claims.
// modules don't both target the last unit of the same debris (the claim would be
// dropped at apply time). Debris is available while its amount exceeds its claims.
std::map<entt::entity, int> claimedUnits;
// Collection cycles already in flight toward each ship's shared cargo pool, so
// concurrent modules on the same ship never start more cycles than the remaining
@@ -49,7 +49,7 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem&
std::map<entt::entity, int> pendingByShip;
for (const PendingCollection& pc : m_pendingCollections)
{
++claimedUnits[pc.scrap];
++claimedUnits[pc.debris];
++pendingByShip[pc.ship];
}
@@ -66,12 +66,12 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem&
if (cargo.current + pendingByShip[o.owner] >= cargo.maxCapacity) { return; }
const QVector2D ownerPos = m_admin.get<PositionComponent>(o.owner).value;
for (const ScrapInfo& si : allScrap)
for (const DebrisInfo& si : allDebris)
{
if ((si.position - ownerPos).length() > s.collectionRange_tiles) { continue; }
if (claimedUnits[si.entity] >= m_admin.get<ScrapDataComponent>(si.entity).amount)
if (claimedUnits[si.entity] >= m_admin.get<DebrisComponent>(si.entity).amount)
{
continue; // every remaining unit of this pile is already spoken for
continue; // every remaining unit of this debris is already spoken for
}
outBeamFiredEvents.push_back(
BeamFiredEvent{BeamKind::Salvage, o.owner, si.entity, currentTick});
@@ -107,7 +107,7 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem&
});
}
void SalvagerSystem::applyPendingCollections(Tick currentTick, ScrapSystem& scraps)
void SalvagerSystem::applyPendingCollections(Tick currentTick, DebrisSystem& debris)
{
std::vector<PendingCollection>::iterator it = m_pendingCollections.begin();
while (it != m_pendingCollections.end())
@@ -117,7 +117,7 @@ void SalvagerSystem::applyPendingCollections(Tick currentTick, ScrapSystem& scra
if (m_admin.isValid(it->ship) && m_admin.hasAll<CargoComponent>(it->ship))
{
CargoComponent& cargo = m_admin.get<CargoComponent>(it->ship);
if (cargo.current < cargo.maxCapacity && scraps.collectOne(it->scrap))
if (cargo.current < cargo.maxCapacity && debris.collectOne(it->debris))
{
++cargo.current;
}

View File

@@ -9,11 +9,11 @@
class BuildingSystem;
class EntityAdmin;
class ScrapSystem;
class DebrisSystem;
// World-mutation system for salvage modules: each module runs a collection cycle
// on its own cooldown. When a cycle starts it emits a salvage beam toward an
// in-range scrap pile and schedules the collection of one scrap for mid-beam
// in-range piece of debris and schedules the collection of one scrap for mid-beam
// (kBeamImpactDelayTicks later) — mirroring weapon firing. Also delivers full
// cargo at a SalvageBay. Runs every tick, independent of behavior selection.
class SalvagerSystem
@@ -21,18 +21,18 @@ class SalvagerSystem
public:
explicit SalvagerSystem(EntityAdmin& admin);
void tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& buildings,
void tick(Tick currentTick, DebrisSystem& debris, BuildingSystem& buildings,
std::vector<BeamFiredEvent>& outBeamFiredEvents);
private:
struct PendingCollection
{
entt::entity ship;
entt::entity scrap;
entt::entity debris;
Tick appliesAt;
};
void applyPendingCollections(Tick currentTick, ScrapSystem& scraps);
void applyPendingCollections(Tick currentTick, DebrisSystem& debris);
EntityAdmin& m_admin;
std::vector<PendingCollection> m_pendingCollections;

View File

@@ -1,78 +0,0 @@
#include "ScrapSystem.h"
#include "DespawnAtComponent.h"
#include "EntityAdmin.h"
#include "PositionComponent.h"
#include "ScrapDataComponent.h"
#include "tracing.h"
ScrapSystem::ScrapSystem(EntityAdmin& admin)
: m_admin(admin)
{
}
entt::entity ScrapSystem::spawn(QVector2D position, int amount, Tick despawnAt)
{
return m_admin.spawnScrap(position, amount, despawnAt);
}
void ScrapSystem::tickDespawn(Tick currentTick)
{
TRACE();
std::vector<entt::entity> expired;
m_admin.forEach<DespawnAtComponent>(
[&expired, currentTick](entt::entity e, DespawnAtComponent& d)
{
if (d.tick <= currentTick)
{
expired.push_back(e);
}
});
for (entt::entity e : expired)
{
m_admin.destroy(e);
}
}
std::optional<int> ScrapSystem::consume(entt::entity entity)
{
if (!m_admin.isValid(entity) || !m_admin.hasAll<ScrapDataComponent>(entity))
{
return std::nullopt;
}
int amount = m_admin.get<ScrapDataComponent>(entity).amount;
m_admin.destroy(entity);
return amount;
}
bool ScrapSystem::collectOne(entt::entity entity)
{
if (!m_admin.isValid(entity) || !m_admin.hasAll<ScrapDataComponent>(entity))
{
return false;
}
ScrapDataComponent& data = m_admin.get<ScrapDataComponent>(entity);
if (data.amount <= 0)
{
return false;
}
--data.amount;
if (data.amount <= 0)
{
m_admin.destroy(entity);
}
return true;
}
std::vector<ScrapInfo> ScrapSystem::getAllScrapInfo() const
{
std::vector<ScrapInfo> result;
m_admin.forEach<ScrapDataComponent>(
[&result, this](entt::entity e, const ScrapDataComponent& sd)
{
result.push_back(ScrapInfo{e, m_admin.get<PositionComponent>(e).value, sd.amount});
});
return result;
}

View File

@@ -96,7 +96,7 @@ entt::entity ShipSystem::spawn(const std::string& schematicId,
layout.has_value() ? layout->placedModules : def->defaultModules;
// Derive the scrap dropped on destruction from the ship's as-built threat cost
// (REQ-RES-SCRAP-DROP): round(threat * scrap_per_threat), floored at 1 for any
// (REQ-RES-DEBRIS-DROP): round(threat * scrap_per_threat), floored at 1 for any
// ship with threat > 0. Computed once here since threat is level-independent.
const double threatCost = calculateShipThreatCost(m_config.threatCosts, m_config,
schematicId, modules);
@@ -392,7 +392,7 @@ entt::entity ShipSystem::spawn(const std::string& schematicId,
}
SalvageScrapBehavior salvage;
salvage.scrapTarget = std::nullopt;
salvage.debrisTarget = std::nullopt;
salvage.maxCollectionRange_tiles = maxCollRange;
salvage.orbitRadius_tiles =
maxCollRange * static_cast<float>(m_config.world.orbitFactor);

View File

@@ -11,15 +11,15 @@
#include "EntityAdmin.h"
#include "PositionComponent.h"
#include "SalvageScrapBehavior.h"
#include "ScrapSystem.h"
#include "DebrisSystem.h"
#include "SensorRangeComponent.h"
#include "tracing.h"
void SalvageScrapEvaluator::evaluate(EntityAdmin& admin, const ScrapSystem& scraps)
void SalvageScrapEvaluator::evaluate(EntityAdmin& admin, const DebrisSystem& debris)
{
TRACE();
const std::unordered_map<entt::entity, CargoState> cargoByShip = buildCargoByShip(admin);
const std::vector<ScrapInfo> allScrap = scraps.getAllScrapInfo();
const std::vector<DebrisInfo> allDebris = debris.getAllDebrisInfo();
admin.forEach<SalvageScrapBehavior, PositionComponent, SensorRangeComponent>(
[&](entt::entity e, SalvageScrapBehavior& salvage, const PositionComponent& pos,
@@ -31,15 +31,15 @@ void SalvageScrapEvaluator::evaluate(EntityAdmin& admin, const ScrapSystem& scra
if (cargoFull)
{
salvage.scrapTarget = std::nullopt;
salvage.debrisTarget = std::nullopt;
salvage.score = BehaviorScores::kInactive;
return;
}
// Find nearest scrap within sensor range.
// Find nearest debris within sensor range.
float bestDist = sensor.value_tiles;
std::optional<QVector2D> bestPos;
for (const ScrapInfo& si : allScrap)
for (const DebrisInfo& si : allDebris)
{
const float dist = (si.position - pos.value).length();
if (dist < bestDist)
@@ -49,7 +49,7 @@ void SalvageScrapEvaluator::evaluate(EntityAdmin& admin, const ScrapSystem& scra
}
}
salvage.scrapTarget = bestPos;
salvage.debrisTarget = bestPos;
salvage.score = bestPos ? BehaviorScores::kSalvage : BehaviorScores::kInactive;
});
}

View File

@@ -1,13 +1,13 @@
#pragma once
class EntityAdmin;
class ScrapSystem;
class DebrisSystem;
// When cargo is not full, finds the nearest scrap within sensor range and sets
// it as the target, scoring high. Scores inactive when cargo is full or no scrap
// When cargo is not full, finds the nearest debris within sensor range and sets
// it as the target, scoring high. Scores inactive when cargo is full or no debris
// is in range (Advance then handles roaming).
class SalvageScrapEvaluator
{
public:
void evaluate(EntityAdmin& admin, const ScrapSystem& scraps);
void evaluate(EntityAdmin& admin, const DebrisSystem& debris);
};

View File

@@ -17,8 +17,8 @@ void SalvageScrapExecutor::execute(EntityAdmin& admin)
MovementIntentComponent& intent)
{
if (selected.winner != BehaviorKind::SalvageScrap) { return; }
if (!salvage.scrapTarget) { return; }
intent = MovementIntentComponent{true, *salvage.scrapTarget,
if (!salvage.debrisTarget) { return; }
intent = MovementIntentComponent{true, *salvage.debrisTarget,
salvage.orbitRadius_tiles};
});
}