implement ships depart in waves
This commit is contained in:
@@ -1,28 +1,29 @@
|
|||||||
[world]
|
[world]
|
||||||
height_tiles = 60
|
height_tiles = 30
|
||||||
refund_percentage = 75
|
refund_percentage = 75
|
||||||
starting_building_blocks = 100
|
starting_building_blocks = 1000
|
||||||
scrap_despawn_seconds = 30
|
scrap_despawn_seconds = 30
|
||||||
belt_speed_tiles_per_second = 2
|
belt_speed_tiles_per_second = 2
|
||||||
tunnel_max_distance = 10
|
tunnel_max_distance = 10
|
||||||
|
departure_interval_seconds = 20
|
||||||
|
|
||||||
[regions]
|
[regions]
|
||||||
asteroid_width = 40
|
asteroid_width = 40
|
||||||
player_buffer_width = 10
|
player_buffer_width = 20
|
||||||
contest_zone_width = 30
|
contest_zone_width = 60
|
||||||
enemy_buffer_width = 15
|
enemy_buffer_width = 20
|
||||||
|
|
||||||
[expansion]
|
[expansion]
|
||||||
columns_per_expansion = 10
|
columns_per_expansion = 10
|
||||||
cost_building_blocks = 200
|
cost_building_blocks = 200
|
||||||
|
|
||||||
[push]
|
[push]
|
||||||
push_expand_columns = 20
|
push_expand_columns = 10
|
||||||
scaling_factor = 1.2
|
scaling_factor = 1.2
|
||||||
|
|
||||||
[waves]
|
[waves]
|
||||||
threat_rate_formula = "1*x - 30"
|
threat_rate_formula = "0.1*x - 60"
|
||||||
ship_level_formula = "1 + x / 120"
|
ship_level_formula = "1"
|
||||||
gap_min_seconds = 15
|
gap_min_seconds = 15
|
||||||
gap_max_seconds = 45
|
gap_max_seconds = 45
|
||||||
spawn_duration_seconds = 10
|
spawn_duration_seconds = 10
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ WorldConfig ConfigLoader::loadWorld(const std::string& path)
|
|||||||
cfg.scrapDespawnSeconds = requireDouble(tbl["world"]["scrap_despawn_seconds"], file, "world.scrap_despawn_seconds");
|
cfg.scrapDespawnSeconds = requireDouble(tbl["world"]["scrap_despawn_seconds"], file, "world.scrap_despawn_seconds");
|
||||||
cfg.beltSpeedTilesPerSecond = requireDouble(tbl["world"]["belt_speed_tiles_per_second"], file, "world.belt_speed_tiles_per_second");
|
cfg.beltSpeedTilesPerSecond = requireDouble(tbl["world"]["belt_speed_tiles_per_second"], file, "world.belt_speed_tiles_per_second");
|
||||||
cfg.tunnelMaxDistance = static_cast<int>(requireInt(tbl["world"]["tunnel_max_distance"], file, "world.tunnel_max_distance"));
|
cfg.tunnelMaxDistance = static_cast<int>(requireInt(tbl["world"]["tunnel_max_distance"], file, "world.tunnel_max_distance"));
|
||||||
|
cfg.departureIntervalSeconds = requireDouble(tbl["world"]["departure_interval_seconds"], file, "world.departure_interval_seconds");
|
||||||
|
|
||||||
cfg.regions.asteroidWidth = static_cast<int>(requireInt(tbl["regions"]["asteroid_width"], file, "regions.asteroid_width"));
|
cfg.regions.asteroidWidth = static_cast<int>(requireInt(tbl["regions"]["asteroid_width"], file, "regions.asteroid_width"));
|
||||||
cfg.regions.playerBufferWidth = static_cast<int>(requireInt(tbl["regions"]["player_buffer_width"], file, "regions.player_buffer_width"));
|
cfg.regions.playerBufferWidth = static_cast<int>(requireInt(tbl["regions"]["player_buffer_width"], file, "regions.player_buffer_width"));
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ struct WorldConfig
|
|||||||
double scrapDespawnSeconds; // REQ-RES-SCRAP-DROP
|
double scrapDespawnSeconds; // REQ-RES-SCRAP-DROP
|
||||||
double beltSpeedTilesPerSecond; // REQ-GW-BELT-SPEED
|
double beltSpeedTilesPerSecond; // REQ-GW-BELT-SPEED
|
||||||
int tunnelMaxDistance; // REQ-BLD-TUNNEL-PAIR
|
int tunnelMaxDistance; // REQ-BLD-TUNNEL-PAIR
|
||||||
|
double departureIntervalSeconds; // REQ-SHP-RALLY
|
||||||
|
|
||||||
WorldRegions regions;
|
WorldRegions regions;
|
||||||
WorldExpansion expansion;
|
WorldExpansion expansion;
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ struct HomeReturn
|
|||||||
QVector2D homePos;
|
QVector2D homePos;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RallyBehavior
|
||||||
|
{
|
||||||
|
QVector2D rallyPoint;
|
||||||
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Ship
|
// Ship
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -86,6 +91,7 @@ struct Ship
|
|||||||
std::optional<ScrapCollector> scrapCollector;
|
std::optional<ScrapCollector> scrapCollector;
|
||||||
std::optional<RepairBehavior> repairBehavior;
|
std::optional<RepairBehavior> repairBehavior;
|
||||||
std::optional<HomeReturn> homeReturn;
|
std::optional<HomeReturn> homeReturn;
|
||||||
|
std::optional<RallyBehavior> rallyBehavior;
|
||||||
|
|
||||||
// Cleared at the start of the behavior step each tick; the highest-priority
|
// Cleared at the start of the behavior step each tick; the highest-priority
|
||||||
// write from behavior systems wins (architecture.md §Movement Arbitration).
|
// write from behavior systems wins (architecture.md §Movement Arbitration).
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ EntityId ShipSystem::spawn(const std::string& schematicId, int level, QVector2D
|
|||||||
ThreatResponse tr;
|
ThreatResponse tr;
|
||||||
tr.engagementRange = w.range;
|
tr.engagementRange = w.range;
|
||||||
ship.threatResponse = tr;
|
ship.threatResponse = tr;
|
||||||
|
|
||||||
|
if (!isEnemy)
|
||||||
|
{
|
||||||
|
ship.rallyBehavior = RallyBehavior{m_rallyPoint};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (def->salvage)
|
if (def->salvage)
|
||||||
@@ -271,14 +276,21 @@ void ShipSystem::tickThreatResponse(const BuildingSystem& buildings)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No target: patrol rightward (aggressive).
|
// No target: gather at rally point or patrol rightward once departed.
|
||||||
if (3 > s.intent.priority)
|
if (3 > s.intent.priority)
|
||||||
|
{
|
||||||
|
if (s.rallyBehavior)
|
||||||
|
{
|
||||||
|
s.intent = MovementIntent{3, s.rallyBehavior->rallyPoint};
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
s.intent = MovementIntent{3, QVector2D(s.position.x() + 1000.0f,
|
s.intent = MovementIntent{3, QVector2D(s.position.x() + 1000.0f,
|
||||||
s.position.y())};
|
s.position.y())};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Enemy ship: target nearest player ship or player building.
|
// Enemy ship: target nearest player ship or player building.
|
||||||
@@ -636,6 +648,26 @@ void ShipSystem::tickScrapCollector(ScrapSystem& scraps, const BuildingSystem& b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Rally point management (REQ-SHP-RALLY)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void ShipSystem::setRallyPoint(QVector2D point)
|
||||||
|
{
|
||||||
|
m_rallyPoint = point;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShipSystem::triggerRallyDeparture()
|
||||||
|
{
|
||||||
|
for (Ship& s : m_ships)
|
||||||
|
{
|
||||||
|
if (!s.isEnemy)
|
||||||
|
{
|
||||||
|
s.rallyBehavior = std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tickMovement (tick-order step 10)
|
// tickMovement (tick-order step 10)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ public:
|
|||||||
// -- Movement (tick-order step 10) ---------------------------------------
|
// -- Movement (tick-order step 10) ---------------------------------------
|
||||||
void tickMovement();
|
void tickMovement();
|
||||||
|
|
||||||
|
// Set the rally point that newly spawned player combat ships will loiter at.
|
||||||
|
void setRallyPoint(QVector2D point);
|
||||||
|
|
||||||
|
// Release all gathered player combat ships to advance toward the enemy.
|
||||||
|
void triggerRallyDeparture();
|
||||||
|
|
||||||
// Reduce ship HP by amount. Does not remove the ship; step 9 handles death.
|
// Reduce ship HP by amount. Does not remove the ship; step 9 handles death.
|
||||||
// Returns false if ship not found.
|
// Returns false if ship not found.
|
||||||
bool damageShip(EntityId id, float amount);
|
bool damageShip(EntityId id, float amount);
|
||||||
@@ -66,4 +72,5 @@ private:
|
|||||||
const GameConfig& m_config;
|
const GameConfig& m_config;
|
||||||
std::function<EntityId()> m_allocateId;
|
std::function<EntityId()> m_allocateId;
|
||||||
std::vector<Ship> m_ships;
|
std::vector<Ship> m_ships;
|
||||||
|
QVector2D m_rallyPoint;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
|||||||
: m_config(std::move(config))
|
: m_config(std::move(config))
|
||||||
, m_rng(seed)
|
, m_rng(seed)
|
||||||
, m_currentTick(0)
|
, m_currentTick(0)
|
||||||
|
, m_nextDepartureTick(secondsToTicks(m_config.world.departureIntervalSeconds))
|
||||||
, m_nextId(1)
|
, m_nextId(1)
|
||||||
, m_buildingBlocksStock(m_config.world.startingBuildingBlocks)
|
, m_buildingBlocksStock(m_config.world.startingBuildingBlocks)
|
||||||
, m_gameOver(false)
|
, m_gameOver(false)
|
||||||
@@ -73,6 +74,7 @@ void Simulation::reset(unsigned int seed)
|
|||||||
{
|
{
|
||||||
m_rng.seed(seed);
|
m_rng.seed(seed);
|
||||||
m_currentTick = 0;
|
m_currentTick = 0;
|
||||||
|
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
||||||
m_nextId = 1;
|
m_nextId = 1;
|
||||||
m_buildingBlocksStock = m_config.world.startingBuildingBlocks;
|
m_buildingBlocksStock = m_config.world.startingBuildingBlocks;
|
||||||
m_gameOver = false;
|
m_gameOver = false;
|
||||||
@@ -139,6 +141,14 @@ void Simulation::tick()
|
|||||||
m_beltSystem.tick(); // step 6
|
m_beltSystem.tick(); // step 6
|
||||||
|
|
||||||
// Step 7: ship behavior systems (movement arbitration via intent priority)
|
// Step 7: ship behavior systems (movement arbitration via intent priority)
|
||||||
|
|
||||||
|
// Departure timer: release gathered combat ships on a fixed interval (REQ-SHP-RALLY).
|
||||||
|
if (m_currentTick >= m_nextDepartureTick)
|
||||||
|
{
|
||||||
|
m_shipSystem->triggerRallyDeparture();
|
||||||
|
m_nextDepartureTick += secondsToTicks(m_config.world.departureIntervalSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
m_shipSystem->clearMovementIntents();
|
m_shipSystem->clearMovementIntents();
|
||||||
m_shipSystem->tickHomeReturn(); // priority 4
|
m_shipSystem->tickHomeReturn(); // priority 4
|
||||||
m_shipSystem->tickThreatResponse(*m_buildingSystem); // priority 3
|
m_shipSystem->tickThreatResponse(*m_buildingSystem); // priority 3
|
||||||
@@ -217,6 +227,11 @@ void Simulation::placeInitialStructures()
|
|||||||
QPoint(psAnchorX, ps2Y), Rotation::East, psHp, psHp);
|
QPoint(psAnchorX, ps2Y), Rotation::East, psHp, psHp);
|
||||||
m_buildingSystem->initStationWeapon(m_playerStation2Id, psWeapon);
|
m_buildingSystem->initStationWeapon(m_playerStation2Id, psWeapon);
|
||||||
|
|
||||||
|
// Rally point: center of the player defence stations' X column, world vertical midpoint.
|
||||||
|
const float rallyX = static_cast<float>(psAnchorX) + psParsed.footprint.width() / 2.0f;
|
||||||
|
const float rallyY = static_cast<float>(m_config.world.heightTiles) / 2.0f;
|
||||||
|
m_shipSystem->setRallyPoint(QVector2D(rallyX, rallyY));
|
||||||
|
|
||||||
// Enemy defence stations — generation 0 (initial set).
|
// Enemy defence stations — generation 0 (initial set).
|
||||||
placeEnemyStationSet(0);
|
placeEnemyStationSet(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ private:
|
|||||||
std::mt19937 m_rng;
|
std::mt19937 m_rng;
|
||||||
|
|
||||||
Tick m_currentTick;
|
Tick m_currentTick;
|
||||||
|
Tick m_nextDepartureTick;
|
||||||
EntityId m_nextId;
|
EntityId m_nextId;
|
||||||
int m_buildingBlocksStock;
|
int m_buildingBlocksStock;
|
||||||
bool m_gameOver = false;
|
bool m_gameOver = false;
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ scrap_despawn_seconds = 30
|
|||||||
belt_speed_tiles_per_second = 2
|
belt_speed_tiles_per_second = 2
|
||||||
starting_building_blocks = 100
|
starting_building_blocks = 100
|
||||||
tunnel_max_distance = 10
|
tunnel_max_distance = 10
|
||||||
|
departure_interval_seconds = 20
|
||||||
|
|
||||||
[regions]
|
[regions]
|
||||||
asteroid_width = 40
|
asteroid_width = 40
|
||||||
@@ -211,6 +212,7 @@ scrap_despawn_seconds = 30
|
|||||||
belt_speed_tiles_per_second = 2
|
belt_speed_tiles_per_second = 2
|
||||||
starting_building_blocks = 100
|
starting_building_blocks = 100
|
||||||
tunnel_max_distance = 10
|
tunnel_max_distance = 10
|
||||||
|
departure_interval_seconds = 20
|
||||||
|
|
||||||
[regions]
|
[regions]
|
||||||
asteroid_width = 40
|
asteroid_width = 40
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ starting_building_blocks = 100
|
|||||||
scrap_despawn_seconds = 30
|
scrap_despawn_seconds = 30
|
||||||
belt_speed_tiles_per_second = 2
|
belt_speed_tiles_per_second = 2
|
||||||
tunnel_max_distance = 10
|
tunnel_max_distance = 10
|
||||||
|
departure_interval_seconds = 20
|
||||||
|
|
||||||
[regions]
|
[regions]
|
||||||
asteroid_width = 40
|
asteroid_width = 40
|
||||||
|
|||||||
Reference in New Issue
Block a user