implement waves

This commit is contained in:
2026-04-20 14:10:01 +02:00
parent 65de4ddc5c
commit 498b97db20
17 changed files with 1798 additions and 18 deletions

View File

@@ -0,0 +1,246 @@
#include "CombatSystem.h"
#include "BuildingSystem.h"
#include "BuildingType.h"
#include "Ship.h"
#include "ShipSystem.h"
CombatSystem::CombatSystem(const GameConfig& config)
: m_config(config)
{
}
void CombatSystem::tick(Tick currentTick,
ShipSystem& ships,
BuildingSystem& buildings,
std::vector<FireEvent>& outFireEvents)
{
// Ships: iterate and resolve weapon for each combat ship.
ships.forEach([&](Ship& ship)
{
resolveShipWeapon(ship, currentTick, ships, buildings, outFireEvents);
});
// Defence stations: acquire targets and fire.
buildings.forEachBuilding([&](Building& building)
{
if (building.type == BuildingType::PlayerDefenceStation ||
building.type == BuildingType::EnemyDefenceStation)
{
resolveStationWeapon(building, currentTick, ships, buildings, outFireEvents);
}
});
}
void CombatSystem::resolveShipWeapon(Ship& ship, Tick currentTick,
ShipSystem& ships,
BuildingSystem& buildings,
std::vector<FireEvent>& out)
{
if (!ship.weapon || !ship.threatResponse ||
!ship.threatResponse->currentTarget)
{
return;
}
Weapon& w = *ship.weapon;
// Decrement cooldown toward zero.
if (w.cooldownTicks > 0.0f)
{
w.cooldownTicks -= 1.0f;
}
if (w.cooldownTicks > 0.0f)
{
return;
}
const EntityId targetId = *ship.threatResponse->currentTarget;
const std::optional<QVector2D> tPos = targetPosition(targetId, ships, buildings);
if (!tPos)
{
ship.threatResponse->currentTarget = std::nullopt;
return;
}
const float dist = (ship.position - *tPos).length();
if (dist > w.range)
{
return;
}
// Apply damage to the correct pool.
if (ships.findShip(targetId))
{
ships.damageShip(targetId, w.damage);
}
else
{
buildings.damageBuilding(targetId, w.damage);
}
FireEvent evt;
evt.shooter = ship.id;
evt.target = targetId;
evt.emittedAt = currentTick;
out.push_back(evt);
w.cooldownTicks = static_cast<float>(kTickRateHz) / w.fireRateHz;
}
void CombatSystem::resolveStationWeapon(Building& station, Tick currentTick,
ShipSystem& ships,
BuildingSystem& buildings,
std::vector<FireEvent>& out)
{
if (!station.weapon)
{
return;
}
StationWeapon& w = *station.weapon;
const bool stationIsEnemy = (station.type == BuildingType::EnemyDefenceStation);
const QVector2D stationCenter(
station.anchor.x() + station.footprint.width() / 2.0f,
station.anchor.y() + station.footprint.height() / 2.0f);
// Validate or clear existing target.
if (w.currentTarget)
{
const std::optional<QVector2D> tPos =
targetPosition(*w.currentTarget, ships, buildings);
if (!tPos || (stationCenter - *tPos).length() > w.range)
{
w.currentTarget = std::nullopt;
}
}
// Acquire a new target if needed.
if (!w.currentTarget)
{
w.currentTarget = acquireStationTarget(station, stationIsEnemy,
ships, buildings);
}
if (!w.currentTarget)
{
return;
}
// Decrement cooldown.
if (w.cooldownTicks > 0.0f)
{
w.cooldownTicks -= 1.0f;
}
if (w.cooldownTicks > 0.0f)
{
return;
}
const EntityId targetId = *w.currentTarget;
const std::optional<QVector2D> tPos = targetPosition(targetId, ships, buildings);
if (!tPos)
{
w.currentTarget = std::nullopt;
return;
}
if ((stationCenter - *tPos).length() > w.range)
{
return;
}
if (ships.findShip(targetId))
{
ships.damageShip(targetId, w.damage);
}
else
{
buildings.damageBuilding(targetId, w.damage);
}
FireEvent evt;
evt.shooter = station.id;
evt.target = targetId;
evt.emittedAt = currentTick;
out.push_back(evt);
w.cooldownTicks = static_cast<float>(kTickRateHz) / w.fireRateHz;
}
std::optional<EntityId> CombatSystem::acquireStationTarget(
const Building& station, bool stationIsEnemy,
const ShipSystem& ships,
const BuildingSystem& buildings) const
{
const QVector2D stationCenter(
station.anchor.x() + station.footprint.width() / 2.0f,
station.anchor.y() + station.footprint.height() / 2.0f);
const float range = station.weapon->range;
std::optional<EntityId> best;
float bestDist = range;
// Scan ships for valid targets.
for (const Ship& candidate : ships.allShips())
{
const bool isValidTarget = stationIsEnemy ? !candidate.isEnemy
: candidate.isEnemy;
if (!isValidTarget)
{
continue;
}
const float dist = (candidate.position - stationCenter).length();
if (dist < bestDist)
{
bestDist = dist;
best = candidate.id;
}
}
// Enemy stations also target player buildings (HQ, PlayerDefenceStation).
if (stationIsEnemy)
{
for (const Building& b : buildings.allBuildings())
{
if (b.type != BuildingType::Hq &&
b.type != BuildingType::PlayerDefenceStation)
{
continue;
}
const QVector2D bCenter(b.anchor.x() + b.footprint.width() / 2.0f,
b.anchor.y() + b.footprint.height() / 2.0f);
const float dist = (bCenter - stationCenter).length();
if (dist < bestDist)
{
bestDist = dist;
best = b.id;
}
}
}
return best;
}
std::optional<QVector2D> CombatSystem::targetPosition(
EntityId id,
const ShipSystem& ships,
const BuildingSystem& buildings) const
{
const Ship* ship = ships.findShip(id);
if (ship)
{
return ship->position;
}
const Building* bld = buildings.findBuilding(id);
if (bld)
{
return QVector2D(bld->anchor.x() + bld->footprint.width() / 2.0f,
bld->anchor.y() + bld->footprint.height() / 2.0f);
}
return std::nullopt;
}