implement scrap and ship skeleton

This commit is contained in:
2026-04-20 07:32:18 +02:00
parent bf29cc40e3
commit 411be72a5c
14 changed files with 646 additions and 3 deletions

View File

@@ -5,6 +5,10 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/Building.h
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/Scrap.h
${CMAKE_CURRENT_SOURCE_DIR}/Ship.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h
PARENT_SCOPE
)
@@ -14,6 +18,8 @@ SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/TickDriver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.cpp
PARENT_SCOPE
)

14
src/lib/sim/Scrap.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <QVector2D>
#include "EntityId.h"
#include "Tick.h"
struct Scrap
{
EntityId id;
QVector2D position;
int amount;
Tick despawnAt;
};

View File

@@ -0,0 +1,44 @@
#include "ScrapSystem.h"
#include <algorithm>
ScrapSystem::ScrapSystem(std::function<EntityId()> allocateId)
: m_allocateId(std::move(allocateId))
{
}
EntityId ScrapSystem::spawn(QVector2D position, int amount, Tick despawnAt)
{
Scrap s;
s.id = m_allocateId();
s.position = position;
s.amount = amount;
s.despawnAt = despawnAt;
m_scraps.push_back(s);
return s.id;
}
void ScrapSystem::tickDespawn(Tick currentTick)
{
m_scraps.erase(
std::remove_if(m_scraps.begin(), m_scraps.end(),
[currentTick](const Scrap& s) { return s.despawnAt <= currentTick; }),
m_scraps.end());
}
const Scrap* ScrapSystem::findScrap(EntityId id) const
{
for (const Scrap& s : m_scraps)
{
if (s.id == id)
{
return &s;
}
}
return nullptr;
}
std::vector<Scrap> ScrapSystem::allScraps() const
{
return m_scraps;
}

26
src/lib/sim/ScrapSystem.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <functional>
#include <vector>
#include <QVector2D>
#include "EntityId.h"
#include "Scrap.h"
#include "Tick.h"
class ScrapSystem
{
public:
explicit ScrapSystem(std::function<EntityId()> allocateId);
EntityId spawn(QVector2D position, int amount, Tick despawnAt);
void tickDespawn(Tick currentTick);
const Scrap* findScrap(EntityId id) const;
std::vector<Scrap> allScraps() const;
private:
std::function<EntityId()> m_allocateId;
std::vector<Scrap> m_scraps;
};

90
src/lib/sim/Ship.h Normal file
View File

@@ -0,0 +1,90 @@
#pragma once
#include <optional>
#include <string>
#include <QVector2D>
#include "EntityId.h"
#include "MovementIntent.h"
// ---------------------------------------------------------------------------
// Hardware components — derived from config at spawn, stored on ship
// ---------------------------------------------------------------------------
struct Weapon
{
float damage;
float range;
float fireRateHz;
float cooldownTicks;
std::optional<EntityId> currentTarget;
};
struct SalvageCargo
{
int capacity;
int current;
};
struct RepairTool
{
float ratePerTick;
float range;
std::optional<EntityId> currentTarget;
};
// ---------------------------------------------------------------------------
// Behavior components — AI state consumed by step-6 behavior systems
// ---------------------------------------------------------------------------
struct ThreatResponse
{
float engagementRange;
std::optional<EntityId> currentTarget;
};
struct ScrapCollector
{
std::optional<QVector2D> scrapTarget;
EntityId deliveryBay; // kInvalidEntityId until assigned at a salvage bay
};
struct RepairBehavior
{
std::optional<EntityId> currentTarget;
};
struct HomeReturn
{
float retreatHpFraction;
QVector2D homePos;
};
// ---------------------------------------------------------------------------
// Ship
// ---------------------------------------------------------------------------
struct Ship
{
EntityId id;
QVector2D position;
QVector2D velocity;
float hp;
float maxHp;
float speedPerTick; // pre-evaluated from speedFormula / kTickRateHz
int level;
std::string blueprintId;
std::optional<Weapon> weapon;
std::optional<SalvageCargo> cargo;
std::optional<RepairTool> repairTool;
std::optional<ThreatResponse> threatResponse;
std::optional<ScrapCollector> scrapCollector;
std::optional<RepairBehavior> repairBehavior;
std::optional<HomeReturn> homeReturn;
// Cleared at the start of the behavior step each tick; the highest-priority
// write from behavior systems wins (architecture.md §Movement Arbitration).
MovementIntent intent;
};

120
src/lib/sim/ShipSystem.cpp Normal file
View File

@@ -0,0 +1,120 @@
#include "ShipSystem.h"
#include <algorithm>
#include <cassert>
#include "Tick.h"
ShipSystem::ShipSystem(const GameConfig& config,
std::function<EntityId()> allocateId)
: m_config(config)
, m_allocateId(std::move(allocateId))
{
}
const ShipDef* ShipSystem::findShipDef(const std::string& blueprintId) const
{
for (const ShipDef& def : m_config.ships.ships)
{
if (def.id == blueprintId)
{
return &def;
}
}
return nullptr;
}
EntityId ShipSystem::spawn(const std::string& blueprintId, int level, QVector2D position)
{
const ShipDef* def = findShipDef(blueprintId);
assert(def != nullptr);
const double x = static_cast<double>(level);
Ship ship;
ship.id = m_allocateId();
ship.position = position;
ship.velocity = QVector2D(0.0f, 0.0f);
ship.maxHp = static_cast<float>(def->health.hpFormula.evaluate(x));
ship.hp = ship.maxHp;
ship.speedPerTick = static_cast<float>(
def->movement.speedFormula.evaluate(x))
/ static_cast<float>(kTickRateHz);
ship.level = level;
ship.blueprintId = blueprintId;
ship.intent = MovementIntent{0, QVector2D(0.0f, 0.0f)};
if (def->combat)
{
Weapon w;
w.damage = static_cast<float>(def->combat->damageFormula.evaluate(x));
w.range = static_cast<float>(def->combat->attackRangeFormula.evaluate(x));
w.fireRateHz = static_cast<float>(def->combat->attackRateFormula.evaluate(x));
w.cooldownTicks = 0.0f;
ship.weapon = w;
ThreatResponse tr;
tr.engagementRange = w.range;
ship.threatResponse = tr;
}
if (def->salvage)
{
SalvageCargo cargo;
cargo.capacity = def->salvage->cargoCapacity;
cargo.current = 0;
ship.cargo = cargo;
ScrapCollector sc;
sc.scrapTarget = std::nullopt;
sc.deliveryBay = kInvalidEntityId;
ship.scrapCollector = sc;
}
if (def->repair)
{
RepairTool rt;
rt.ratePerTick = static_cast<float>(def->repair->repairRateFormula.evaluate(x));
rt.range = static_cast<float>(def->repair->repairRangeFormula.evaluate(x));
ship.repairTool = rt;
RepairBehavior rb;
ship.repairBehavior = rb;
}
m_ships.push_back(ship);
return ship.id;
}
void ShipSystem::despawn(EntityId id)
{
m_ships.erase(
std::remove_if(m_ships.begin(), m_ships.end(),
[id](const Ship& s) { return s.id == id; }),
m_ships.end());
}
const Ship* ShipSystem::findShip(EntityId id) const
{
for (const Ship& s : m_ships)
{
if (s.id == id)
{
return &s;
}
}
return nullptr;
}
std::vector<Ship> ShipSystem::allShips() const
{
return m_ships;
}
void ShipSystem::forEach(std::function<void(Ship&)> fn)
{
for (Ship& s : m_ships)
{
fn(s);
}
}

31
src/lib/sim/ShipSystem.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <functional>
#include <vector>
#include <QVector2D>
#include "EntityId.h"
#include "GameConfig.h"
#include "Ship.h"
class ShipSystem
{
public:
ShipSystem(const GameConfig& config,
std::function<EntityId()> allocateId);
EntityId spawn(const std::string& blueprintId, int level, QVector2D position);
void despawn(EntityId id);
const Ship* findShip(EntityId id) const;
std::vector<Ship> allShips() const;
void forEach(std::function<void(Ship&)> fn);
private:
const ShipDef* findShipDef(const std::string& blueprintId) const;
const GameConfig& m_config;
std::function<EntityId()> m_allocateId;
std::vector<Ship> m_ships;
};

View File

@@ -1,6 +1,8 @@
#include "Simulation.h"
#include "BuildingSystem.h"
#include "ScrapSystem.h"
#include "ShipSystem.h"
Simulation::Simulation(const GameConfig& config, unsigned int seed)
: m_config(config)
@@ -16,6 +18,8 @@ Simulation::Simulation(const GameConfig& config, unsigned int seed)
[this]() { return allocateId(); },
[this](int amount) { m_buildingBlocksStock += amount; },
m_rng);
m_shipSystem = std::make_unique<ShipSystem>(config, [this]() { return allocateId(); });
m_scrapSystem = std::make_unique<ScrapSystem>([this]() { return allocateId(); });
}
Simulation::~Simulation() = default;
@@ -27,6 +31,7 @@ void Simulation::tick()
m_buildingSystem->tickProduction(m_currentTick); // step 4
m_buildingSystem->tickBeltPush(); // step 5
m_beltSystem.tick(); // step 6
m_scrapSystem->tickDespawn(m_currentTick); // step 11
++m_currentTick;
}
@@ -75,6 +80,26 @@ const BeltSystem& Simulation::belts() const
return m_beltSystem;
}
ShipSystem& Simulation::ships()
{
return *m_shipSystem;
}
const ShipSystem& Simulation::ships() const
{
return *m_shipSystem;
}
ScrapSystem& Simulation::scraps()
{
return *m_scrapSystem;
}
const ScrapSystem& Simulation::scraps() const
{
return *m_scrapSystem;
}
EntityId Simulation::allocateId()
{
return m_nextId++;

View File

@@ -12,6 +12,8 @@
#include "Tick.h"
class BuildingSystem;
class ShipSystem;
class ScrapSystem;
class Simulation
{
@@ -36,6 +38,10 @@ public:
const BuildingSystem& buildings() const;
BeltSystem& belts();
const BeltSystem& belts() const;
ShipSystem& ships();
const ShipSystem& ships() const;
ScrapSystem& scraps();
const ScrapSystem& scraps() const;
private:
EntityId allocateId(); // Strictly increasing; never returns kInvalidEntityId.
@@ -49,6 +55,8 @@ private:
BeltSystem m_beltSystem;
std::unique_ptr<BuildingSystem> m_buildingSystem;
std::unique_ptr<ShipSystem> m_shipSystem;
std::unique_ptr<ScrapSystem> m_scrapSystem;
std::vector<FireEvent> m_fireEvents;
std::vector<BlueprintDropEvent> m_blueprintDropEvents;