change repair_tool application and add beams for salvager and repair_tool
This commit is contained in:
@@ -678,9 +678,11 @@ ModulesConfig ConfigLoader::loadModules(const std::string& path)
|
||||
if (rMt.contains("repair_rate_hz_formula") || rMt.contains("repair_range_m_formula"))
|
||||
{
|
||||
ModuleRepairCapability cap;
|
||||
cap.repairRateFormula = requireFormula(rMt["repair_rate_hz_formula"],
|
||||
cap.repairRateFormula = requireFormula(rMt["repair_rate_hz_formula"],
|
||||
file, rPath + ".repair_rate_hz_formula");
|
||||
cap.repairRangeFormula = requireFormula(rMt["repair_range_m_formula"],
|
||||
cap.repairAmountHpFormula = requireFormula(rMt["repair_amount_hp_formula"],
|
||||
file, rPath + ".repair_amount_hp_formula");
|
||||
cap.repairRangeFormula = requireFormula(rMt["repair_range_m_formula"],
|
||||
file, rPath + ".repair_range_m_formula");
|
||||
def.repairCapability = std::move(cap);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ struct ModuleSalvageCapability
|
||||
|
||||
struct ModuleRepairCapability
|
||||
{
|
||||
Formula repairRateFormula;
|
||||
Formula repairRateFormula; // repair cycles per second
|
||||
Formula repairAmountHpFormula; // HP restored per cycle
|
||||
Formula repairRangeFormula;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@ constexpr int kTickRateHz = 30;
|
||||
constexpr double kTickDurationMs = 1000.0 / kTickRateHz;
|
||||
constexpr double kTickDurationSeconds = 1.0 / kTickRateHz;
|
||||
|
||||
// Delay between a tool activating (emitting its beam) and its effect being
|
||||
// applied — half the 0.3 s beam duration. Shared by weapons, repair tools, and
|
||||
// salvage modules so all three apply their effect mid-beam (REQ-SHP-FIRING,
|
||||
// REQ-SHP-FIRING-BEAM).
|
||||
constexpr Tick kBeamImpactDelayTicks = 5;
|
||||
|
||||
// Converts a wall-clock duration (in seconds, as it appears in config TOML) to
|
||||
// an integer tick count. Rounds to nearest to avoid systematic drift from
|
||||
// repeated conversions.
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
struct RepairToolComponent
|
||||
{
|
||||
float ratePerTick;
|
||||
float repairAmountHp; // HP restored per repair cycle
|
||||
int repairIntervalTicks; // cycle period = kTickRateHz / repair-rate (cycles/s); 0 = never
|
||||
int cooldownTicksRemaining; // ticks until this tool may start its next cycle
|
||||
float range_tiles;
|
||||
std::optional<entt::entity> currentTarget;
|
||||
};
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
#include "tracing.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
static constexpr Tick kWeaponImpactDelayTicks = 5;
|
||||
|
||||
CombatSystem::CombatSystem(const GameConfig& config)
|
||||
: m_config(config)
|
||||
{
|
||||
@@ -20,7 +18,7 @@ CombatSystem::CombatSystem(const GameConfig& config)
|
||||
void CombatSystem::tick(Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
BuildingSystem& /*buildings*/,
|
||||
std::vector<WeaponFiredEvent>& outWeaponFiredEvents)
|
||||
std::vector<BeamFiredEvent>& outBeamFiredEvents)
|
||||
{
|
||||
TRACE();
|
||||
// All weapons (ships and stations) are child entities linked via ModuleOwnerComponent.
|
||||
@@ -31,7 +29,7 @@ void CombatSystem::tick(Tick currentTick,
|
||||
{
|
||||
const PositionComponent& pos = admin.get<PositionComponent>(owner.owner);
|
||||
const FactionComponent& faction = admin.get<FactionComponent>(owner.owner);
|
||||
resolveWeapon(owner.owner, weapon, pos, faction, currentTick, admin, outWeaponFiredEvents);
|
||||
resolveWeapon(owner.owner, weapon, pos, faction, currentTick, admin, outBeamFiredEvents);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,7 +40,7 @@ void CombatSystem::resolveWeapon(
|
||||
const FactionComponent& ownFaction,
|
||||
Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
std::vector<WeaponFiredEvent>& out)
|
||||
std::vector<BeamFiredEvent>& out)
|
||||
{
|
||||
if (weapon.cooldownTicks > 0.0f)
|
||||
{
|
||||
@@ -109,9 +107,10 @@ void CombatSystem::resolveWeapon(
|
||||
|
||||
const entt::entity targetEntity = *weapon.currentTarget;
|
||||
m_pendingDamage.push_back({targetEntity, weapon.damage,
|
||||
currentTick + kWeaponImpactDelayTicks});
|
||||
currentTick + kBeamImpactDelayTicks});
|
||||
|
||||
WeaponFiredEvent evt;
|
||||
BeamFiredEvent evt;
|
||||
evt.kind = BeamKind::Weapon;
|
||||
evt.shooter = shipEntity;
|
||||
evt.target = targetEntity;
|
||||
evt.emittedAt = currentTick;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "Building.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "WeaponFiredEvent.h"
|
||||
#include "BeamFiredEvent.h"
|
||||
#include "GameConfig.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "Tick.h"
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
void tick(Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
BuildingSystem& buildings,
|
||||
std::vector<WeaponFiredEvent>& outWeaponFiredEvents);
|
||||
std::vector<BeamFiredEvent>& outBeamFiredEvents);
|
||||
|
||||
void applyPendingDamage(Tick currentTick, EntityAdmin& admin);
|
||||
|
||||
@@ -47,7 +47,7 @@ private:
|
||||
const FactionComponent& ownFaction,
|
||||
Tick currentTick,
|
||||
EntityAdmin& admin,
|
||||
std::vector<WeaponFiredEvent>& out);
|
||||
std::vector<BeamFiredEvent>& out);
|
||||
|
||||
const GameConfig& m_config;
|
||||
};
|
||||
|
||||
@@ -19,52 +19,91 @@ RepairSystem::RepairSystem(EntityAdmin& admin)
|
||||
{
|
||||
}
|
||||
|
||||
void RepairSystem::tick()
|
||||
void RepairSystem::tick(Tick currentTick, std::vector<BeamFiredEvent>& outBeamFiredEvents)
|
||||
{
|
||||
TRACE();
|
||||
// Apply heals whose mid-beam delay has elapsed (cycles started on prior ticks).
|
||||
applyPendingHeals(currentTick);
|
||||
|
||||
const std::vector<RepairableInfo> repairables = buildRepairables(m_admin);
|
||||
|
||||
m_admin.forEach<RepairToolComponent, ModuleOwnerComponent>(
|
||||
[&](entt::entity /*re*/, RepairToolComponent& tool, const ModuleOwnerComponent& owner)
|
||||
{
|
||||
if (tool.cooldownTicksRemaining > 0) { --tool.cooldownTicksRemaining; }
|
||||
if (tool.cooldownTicksRemaining > 0) { return; }
|
||||
if (tool.repairIntervalTicks <= 0) { return; }
|
||||
if (!m_admin.hasAll<PositionComponent>(owner.owner)) { return; }
|
||||
const QVector2D ownerPos = m_admin.get<PositionComponent>(owner.owner).value;
|
||||
|
||||
// Honour the executor-set target if it is still valid and in range.
|
||||
// Choose a target: honour the executor-set target if it is still valid
|
||||
// and in range, else fall back to the nearest damaged friendly in range.
|
||||
std::optional<entt::entity> target;
|
||||
if (tool.currentTarget)
|
||||
{
|
||||
const entt::entity t = *tool.currentTarget;
|
||||
if (m_admin.isValid(t) && m_admin.hasAll<HealthComponent, PositionComponent>(t))
|
||||
{
|
||||
HealthComponent& th = m_admin.get<HealthComponent>(t);
|
||||
const HealthComponent& th = m_admin.get<HealthComponent>(t);
|
||||
const float dist =
|
||||
(m_admin.get<PositionComponent>(t).value - ownerPos).length();
|
||||
if (th.hp > 0.0f && th.hp < th.maxHp && dist <= tool.range_tiles)
|
||||
{
|
||||
th.hp = std::min(th.hp + tool.ratePerTick, th.maxHp);
|
||||
return;
|
||||
target = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: heal the nearest damaged friendly within tool range.
|
||||
tool.currentTarget = std::nullopt;
|
||||
float bestDist = tool.range_tiles;
|
||||
for (const RepairableInfo& r : repairables)
|
||||
if (!target)
|
||||
{
|
||||
if (r.isEnemy) { continue; }
|
||||
if (r.hp <= 0.0f || r.hp >= r.maxHp) { continue; }
|
||||
const float dist = (r.position - ownerPos).length();
|
||||
if (dist < bestDist)
|
||||
tool.currentTarget = std::nullopt;
|
||||
float bestDist = tool.range_tiles;
|
||||
for (const RepairableInfo& r : repairables)
|
||||
{
|
||||
bestDist = dist;
|
||||
tool.currentTarget = r.entity;
|
||||
if (r.isEnemy) { continue; }
|
||||
if (r.hp <= 0.0f || r.hp >= r.maxHp) { continue; }
|
||||
const float dist = (r.position - ownerPos).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
tool.currentTarget = r.entity;
|
||||
}
|
||||
}
|
||||
target = tool.currentTarget;
|
||||
}
|
||||
|
||||
if (!tool.currentTarget) { return; }
|
||||
if (!target) { return; }
|
||||
|
||||
HealthComponent& targetHealth = m_admin.get<HealthComponent>(*tool.currentTarget);
|
||||
targetHealth.hp = std::min(targetHealth.hp + tool.ratePerTick, targetHealth.maxHp);
|
||||
// Start a repair cycle: emit the beam now, apply the heal mid-beam, and
|
||||
// begin the cooldown at cycle start (not at effect application).
|
||||
outBeamFiredEvents.push_back(
|
||||
BeamFiredEvent{BeamKind::Repair, owner.owner, *target, currentTick});
|
||||
m_pendingHeals.push_back({*target, tool.repairAmountHp,
|
||||
currentTick + kBeamImpactDelayTicks});
|
||||
tool.cooldownTicksRemaining = tool.repairIntervalTicks;
|
||||
});
|
||||
}
|
||||
|
||||
void RepairSystem::applyPendingHeals(Tick currentTick)
|
||||
{
|
||||
std::vector<PendingHeal>::iterator it = m_pendingHeals.begin();
|
||||
while (it != m_pendingHeals.end())
|
||||
{
|
||||
if (it->appliesAt <= currentTick)
|
||||
{
|
||||
if (m_admin.isValid(it->target) && m_admin.hasAll<HealthComponent>(it->target))
|
||||
{
|
||||
HealthComponent& h = m_admin.get<HealthComponent>(it->target);
|
||||
if (h.hp > 0.0f && h.hp < h.maxHp)
|
||||
{
|
||||
h.hp = std::min(h.hp + it->amountHp, h.maxHp);
|
||||
}
|
||||
}
|
||||
it = m_pendingHeals.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "BeamFiredEvent.h"
|
||||
#include "Tick.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
class EntityAdmin;
|
||||
|
||||
// World-mutation system for repair modules: validates each tool's target (set by
|
||||
// RepairExecutor), falls back to the nearest damaged friendly in range, and
|
||||
// applies healing. Runs every tick, independent of behavior selection.
|
||||
// World-mutation system for repair modules: each tool runs a cycle on its own
|
||||
// cooldown. When a cycle starts it picks a target (the RepairExecutor-set target,
|
||||
// else the nearest damaged friendly in range), emits a repair beam, and schedules
|
||||
// the heal for mid-beam (kBeamImpactDelayTicks later) — mirroring weapon firing.
|
||||
// Runs every tick, independent of behavior selection.
|
||||
class RepairSystem
|
||||
{
|
||||
public:
|
||||
explicit RepairSystem(EntityAdmin& admin);
|
||||
|
||||
void tick();
|
||||
void tick(Tick currentTick, std::vector<BeamFiredEvent>& outBeamFiredEvents);
|
||||
|
||||
private:
|
||||
EntityAdmin& m_admin;
|
||||
struct PendingHeal
|
||||
{
|
||||
entt::entity target;
|
||||
float amountHp;
|
||||
Tick appliesAt;
|
||||
};
|
||||
|
||||
void applyPendingHeals(Tick currentTick);
|
||||
|
||||
EntityAdmin& m_admin;
|
||||
std::vector<PendingHeal> m_pendingHeals;
|
||||
};
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
#include "BuildingSystem.h"
|
||||
#include "DeliverScrapBehavior.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include <map>
|
||||
|
||||
#include "ModuleOwnerComponent.h"
|
||||
#include "PositionComponent.h"
|
||||
#include "SalvageCargoComponent.h"
|
||||
#include "ScrapDataComponent.h"
|
||||
#include "ScrapSystem.h"
|
||||
#include "tracing.h"
|
||||
|
||||
@@ -19,9 +22,13 @@ SalvagerSystem::SalvagerSystem(EntityAdmin& admin)
|
||||
{
|
||||
}
|
||||
|
||||
void SalvagerSystem::tick(ScrapSystem& scraps, BuildingSystem& buildings)
|
||||
void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& buildings,
|
||||
std::vector<BeamFiredEvent>& outBeamFiredEvents)
|
||||
{
|
||||
TRACE();
|
||||
// Apply collections whose mid-beam delay has elapsed (cycles started earlier).
|
||||
applyPendingCollections(currentTick, scraps);
|
||||
|
||||
const std::vector<ScrapInfo> allScrap = scraps.allScrapInfo();
|
||||
|
||||
// Tick down per-module collection cooldowns.
|
||||
@@ -31,23 +38,39 @@ void SalvagerSystem::tick(ScrapSystem& scraps, BuildingSystem& buildings)
|
||||
if (c.cooldownTicksRemaining > 0) { --c.cooldownTicksRemaining; }
|
||||
});
|
||||
|
||||
// Collection: each ready, in-range module collects one scrap.
|
||||
// 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.
|
||||
std::map<entt::entity, int> claimedUnits;
|
||||
for (const PendingCollection& pc : m_pendingCollections)
|
||||
{
|
||||
++claimedUnits[pc.scrap];
|
||||
}
|
||||
|
||||
// Cycle start: each ready, in-range module with free cargo begins a collection
|
||||
// cycle — emit the beam now, collect one scrap mid-beam, start the cooldown now.
|
||||
m_admin.forEach<SalvageCargoComponent, ModuleOwnerComponent>(
|
||||
[&](entt::entity /*ce*/, SalvageCargoComponent& c, const ModuleOwnerComponent& o)
|
||||
[&](entt::entity moduleEntity, SalvageCargoComponent& c, const ModuleOwnerComponent& o)
|
||||
{
|
||||
if (c.current >= c.capacity || c.cooldownTicksRemaining > 0) { return; }
|
||||
if (c.collectionIntervalTicks <= 0) { return; }
|
||||
if (!m_admin.hasAll<PositionComponent>(o.owner)) { return; }
|
||||
|
||||
const QVector2D ownerPos = m_admin.get<PositionComponent>(o.owner).value;
|
||||
for (const ScrapInfo& si : allScrap)
|
||||
{
|
||||
if ((si.position - ownerPos).length() > c.collectionRange_tiles) { continue; }
|
||||
if (scraps.consume(si.entity))
|
||||
if (claimedUnits[si.entity] >= m_admin.get<ScrapDataComponent>(si.entity).amount)
|
||||
{
|
||||
++c.current;
|
||||
c.cooldownTicksRemaining = c.collectionIntervalTicks;
|
||||
break;
|
||||
continue; // every remaining unit of this pile is already spoken for
|
||||
}
|
||||
outBeamFiredEvents.push_back(
|
||||
BeamFiredEvent{BeamKind::Salvage, o.owner, si.entity, currentTick});
|
||||
m_pendingCollections.push_back({moduleEntity, si.entity,
|
||||
currentTick + kBeamImpactDelayTicks});
|
||||
++claimedUnits[si.entity];
|
||||
c.cooldownTicksRemaining = c.collectionIntervalTicks;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -77,3 +100,27 @@ void SalvagerSystem::tick(ScrapSystem& scraps, BuildingSystem& buildings)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SalvagerSystem::applyPendingCollections(Tick currentTick, ScrapSystem& scraps)
|
||||
{
|
||||
std::vector<PendingCollection>::iterator it = m_pendingCollections.begin();
|
||||
while (it != m_pendingCollections.end())
|
||||
{
|
||||
if (it->appliesAt <= currentTick)
|
||||
{
|
||||
if (m_admin.isValid(it->module) && m_admin.hasAll<SalvageCargoComponent>(it->module))
|
||||
{
|
||||
SalvageCargoComponent& c = m_admin.get<SalvageCargoComponent>(it->module);
|
||||
if (c.current < c.capacity && scraps.collectOne(it->scrap))
|
||||
{
|
||||
++c.current;
|
||||
}
|
||||
}
|
||||
it = m_pendingCollections.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "BeamFiredEvent.h"
|
||||
#include "Tick.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
class BuildingSystem;
|
||||
class EntityAdmin;
|
||||
class ScrapSystem;
|
||||
|
||||
// World-mutation system for salvage modules: collects scrap into cargo and
|
||||
// delivers full cargo at a SalvageBay. Runs every tick, independent of which
|
||||
// behavior the AiSystem selected.
|
||||
// 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
|
||||
// (kBeamImpactDelayTicks later) — mirroring weapon firing. Also delivers full
|
||||
// cargo at a SalvageBay. Runs every tick, independent of behavior selection.
|
||||
class SalvagerSystem
|
||||
{
|
||||
public:
|
||||
explicit SalvagerSystem(EntityAdmin& admin);
|
||||
|
||||
void tick(ScrapSystem& scraps, BuildingSystem& buildings);
|
||||
void tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem& buildings,
|
||||
std::vector<BeamFiredEvent>& outBeamFiredEvents);
|
||||
|
||||
private:
|
||||
EntityAdmin& m_admin;
|
||||
struct PendingCollection
|
||||
{
|
||||
entt::entity module;
|
||||
entt::entity scrap;
|
||||
Tick appliesAt;
|
||||
};
|
||||
|
||||
void applyPendingCollections(Tick currentTick, ScrapSystem& scraps);
|
||||
|
||||
EntityAdmin& m_admin;
|
||||
std::vector<PendingCollection> m_pendingCollections;
|
||||
};
|
||||
|
||||
@@ -46,6 +46,25 @@ std::optional<int> ScrapSystem::consume(entt::entity 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::allScrapInfo() const
|
||||
{
|
||||
std::vector<ScrapInfo> result;
|
||||
|
||||
@@ -28,6 +28,11 @@ public:
|
||||
// Removes the scrap and returns its 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,
|
||||
// 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> allScrapInfo() const;
|
||||
|
||||
|
||||
@@ -157,9 +157,14 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
if (modDef->repairCapability)
|
||||
{
|
||||
RepairToolComponent rt;
|
||||
rt.ratePerTick = static_cast<float>(
|
||||
modDef->repairCapability->repairRateFormula.evaluate(mx))
|
||||
/ static_cast<float>(kTickRateHz);
|
||||
const double repairRateHz =
|
||||
modDef->repairCapability->repairRateFormula.evaluate(mx);
|
||||
rt.repairIntervalTicks = (repairRateHz > 0.0)
|
||||
? static_cast<int>(kTickRateHz / repairRateHz + 0.5)
|
||||
: 0;
|
||||
rt.repairAmountHp = static_cast<float>(
|
||||
modDef->repairCapability->repairAmountHpFormula.evaluate(mx));
|
||||
rt.cooldownTicksRemaining = 0;
|
||||
rt.range_tiles = static_cast<float>(
|
||||
modDef->repairCapability->repairRangeFormula.evaluate(mx)) / tileSize;
|
||||
rt.currentTarget = std::nullopt;
|
||||
@@ -321,8 +326,15 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level,
|
||||
for (entt::entity child : repairChildren)
|
||||
{
|
||||
RepairToolComponent& rt = m_admin.get<RepairToolComponent>(child);
|
||||
applyMod(rt.ratePerTick, "repair_rate", repairMods);
|
||||
applyMod(rt.range_tiles, "repair_range", repairMods);
|
||||
// Apply rate modifier: compute cycles/s from interval, apply, convert back.
|
||||
float fRate = (rt.repairIntervalTicks > 0)
|
||||
? static_cast<float>(kTickRateHz) / static_cast<float>(rt.repairIntervalTicks)
|
||||
: 0.0f;
|
||||
applyMod(fRate, "repair_rate", repairMods);
|
||||
applyMod(rt.range_tiles, "repair_range", repairMods);
|
||||
rt.repairIntervalTicks = (fRate > 0.0f)
|
||||
? static_cast<int>(static_cast<float>(kTickRateHz) / fRate + 0.5f)
|
||||
: 0;
|
||||
}
|
||||
|
||||
// --- Pass 3: attach behavior components based on capability presence -----
|
||||
|
||||
31
src/lib/eventsystem/event/BeamFiredEvent.h
Normal file
31
src/lib/eventsystem/event/BeamFiredEvent.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "Event.h"
|
||||
#include "Tick.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
// The kind of tool that produced a beam. Used by the renderer to choose the
|
||||
// beam color (REQ-SHP-FIRING-BEAM).
|
||||
enum class BeamKind
|
||||
{
|
||||
Weapon,
|
||||
Repair,
|
||||
Salvage,
|
||||
};
|
||||
|
||||
// Transient record emitted whenever a weapon fires, a repair tool starts a heal
|
||||
// cycle, or a salvage module starts a collection cycle (REQ-SHP-FIRING,
|
||||
// REQ-SHP-FIRING-BEAM). Buffered in a sim-owned vector during the tick, then
|
||||
// drained and re-emitted via EventManager by the UI frame handler.
|
||||
struct BeamFiredEvent : public Event
|
||||
{
|
||||
BeamFiredEvent() = default;
|
||||
BeamFiredEvent(BeamKind kind, entt::entity shooter, entt::entity target, Tick emittedAt)
|
||||
: kind(kind), shooter(shooter), target(target), emittedAt(emittedAt) {}
|
||||
|
||||
BeamKind kind = BeamKind::Weapon;
|
||||
entt::entity shooter = entt::null;
|
||||
entt::entity target = entt::null;
|
||||
Tick emittedAt = 0;
|
||||
};
|
||||
@@ -23,7 +23,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InspectWindowClosedEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ArenaStartRequestedEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ArenaInspectRequestedEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WeaponFiredEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BeamFiredEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DebugDrawToggledEvent.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Event.h"
|
||||
#include "Tick.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
struct WeaponFiredEvent : public Event
|
||||
{
|
||||
WeaponFiredEvent() = default;
|
||||
WeaponFiredEvent(entt::entity shooter, entt::entity target, Tick emittedAt)
|
||||
: shooter(shooter), target(target), emittedAt(emittedAt) {}
|
||||
|
||||
entt::entity shooter = entt::null;
|
||||
entt::entity target = entt::null;
|
||||
Tick emittedAt = 0;
|
||||
};
|
||||
@@ -60,7 +60,7 @@ ShipStats calculateShipStats(const GameConfig& config,
|
||||
// --- Pass 1: base capability stats per module instance -------------------
|
||||
struct WeaponInstance { float damage; float range_tiles; float rate_hz; };
|
||||
struct SalvageInstance { float range_tiles; float rate; };
|
||||
struct RepairInstance { float rate_hps; float range_tiles; };
|
||||
struct RepairInstance { float rate_hz; float amount_hp; float range_tiles; };
|
||||
|
||||
std::vector<WeaponInstance> weaponInstances;
|
||||
std::vector<SalvageInstance> salvageInstances;
|
||||
@@ -93,7 +93,8 @@ ShipStats calculateShipStats(const GameConfig& config,
|
||||
if (def->repairCapability)
|
||||
{
|
||||
RepairInstance ri;
|
||||
ri.rate_hps = static_cast<float>(def->repairCapability->repairRateFormula.evaluate(mx));
|
||||
ri.rate_hz = static_cast<float>(def->repairCapability->repairRateFormula.evaluate(mx));
|
||||
ri.amount_hp = static_cast<float>(def->repairCapability->repairAmountHpFormula.evaluate(mx));
|
||||
ri.range_tiles = static_cast<float>(def->repairCapability->repairRangeFormula.evaluate(mx) / tileSize);
|
||||
repairInstances.push_back(ri);
|
||||
}
|
||||
@@ -238,9 +239,9 @@ ShipStats calculateShipStats(const GameConfig& config,
|
||||
float maxRange = 0.0f;
|
||||
for (RepairInstance& ri : repairInstances)
|
||||
{
|
||||
applyMod(ri.rate_hps, "repair_rate", repairMods);
|
||||
applyMod(ri.rate_hz, "repair_rate", repairMods);
|
||||
applyMod(ri.range_tiles, "repair_range", repairMods);
|
||||
combinedRate += ri.rate_hps;
|
||||
combinedRate += ri.rate_hz * ri.amount_hp;
|
||||
if (ri.range_tiles > maxRange) { maxRange = ri.range_tiles; }
|
||||
}
|
||||
result.repair = ShipStats::RepairStats{combinedRate, maxRange};
|
||||
@@ -303,7 +304,10 @@ ShipStats buildShipStatsFromEntity(const EntityAdmin& admin, entt::entity shipEn
|
||||
{
|
||||
if (owner.owner != shipEntity) { return; }
|
||||
hasRepair = true;
|
||||
repairRate += r.ratePerTick * kTickRateHz;
|
||||
const float cyclesPerSec = (r.repairIntervalTicks > 0)
|
||||
? static_cast<float>(kTickRateHz) / static_cast<float>(r.repairIntervalTicks)
|
||||
: 0.0f;
|
||||
repairRate += cyclesPerSec * r.repairAmountHp;
|
||||
if (r.range_tiles > repairMaxRange) { repairMaxRange = r.range_tiles; }
|
||||
});
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ void Simulation::reset(unsigned int seed)
|
||||
m_playerStation2Entity = entt::null;
|
||||
m_currentEnemyStationEntities[0] = entt::null;
|
||||
m_currentEnemyStationEntities[1] = entt::null;
|
||||
m_weaponFiredEvents.clear();
|
||||
m_beamFiredEvents.clear();
|
||||
m_pendingSchematicChoices.clear();
|
||||
|
||||
m_admin.clear();
|
||||
@@ -248,12 +248,13 @@ void Simulation::tick()
|
||||
// movement intent + preferred module targets only — no world mutation).
|
||||
m_aiSystem->tick(m_admin, *m_buildingSystem, *m_scrapSystem);
|
||||
// Module systems perform the world mutation (collection/delivery, healing).
|
||||
m_salvagerSystem->tick(*m_scrapSystem, *m_buildingSystem);
|
||||
m_repairSystem->tick();
|
||||
// Each emits its tool beams and applies its own delayed (mid-beam) effects.
|
||||
m_salvagerSystem->tick(m_currentTick, *m_scrapSystem, *m_buildingSystem, m_beamFiredEvents);
|
||||
m_repairSystem->tick(m_currentTick, m_beamFiredEvents);
|
||||
|
||||
// Step 8: combat resolution
|
||||
m_combatSystem->tick(m_currentTick, m_admin,
|
||||
*m_buildingSystem, m_weaponFiredEvents);
|
||||
*m_buildingSystem, m_beamFiredEvents);
|
||||
|
||||
// Step 8b: deferred damage whose impact tick has arrived
|
||||
m_combatSystem->applyPendingDamage(m_currentTick, m_admin);
|
||||
@@ -828,10 +829,10 @@ bool Simulation::isItemUnlocked(const std::string& itemId) const
|
||||
// Drains
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::vector<WeaponFiredEvent> Simulation::drainWeaponFiredEvents()
|
||||
std::vector<BeamFiredEvent> Simulation::drainBeamFiredEvents()
|
||||
{
|
||||
std::vector<WeaponFiredEvent> result;
|
||||
result.swap(m_weaponFiredEvents);
|
||||
std::vector<BeamFiredEvent> result;
|
||||
result.swap(m_beamFiredEvents);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "BuildingType.h"
|
||||
#include "BuildingId.h"
|
||||
#include "EventHandler.h"
|
||||
#include "WeaponFiredEvent.h"
|
||||
#include "BeamFiredEvent.h"
|
||||
#include "GameConfig.h"
|
||||
#include "Rotation.h"
|
||||
#include "Tick.h"
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
|
||||
// Returns all fire events accumulated since the last drain, clearing the
|
||||
// internal queue. Call once per rendered frame (REQ-SHP-FIRING-BEAM).
|
||||
std::vector<WeaponFiredEvent> drainWeaponFiredEvents();
|
||||
std::vector<BeamFiredEvent> drainBeamFiredEvents();
|
||||
|
||||
// Returns the pending schematic choices (empty if no drop is pending).
|
||||
const std::vector<SchematicChoiceOption>& getPendingSchematicChoices() const;
|
||||
@@ -192,6 +192,6 @@ private:
|
||||
std::unique_ptr<WaveSystem> m_waveSystem;
|
||||
std::unique_ptr<CombatSystem> m_combatSystem;
|
||||
|
||||
std::vector<WeaponFiredEvent> m_weaponFiredEvents;
|
||||
std::vector<BeamFiredEvent> m_beamFiredEvents;
|
||||
std::vector<SchematicChoiceOption> m_pendingSchematicChoices;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user