From ed6b50376795d1ae28f9e6993ef68cbfecf6ff42 Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Mon, 27 Apr 2026 21:31:04 +0200 Subject: [PATCH] implement ships depart in waves --- bin/config/world.toml | 17 ++++++++------- src/lib/config/ConfigLoader.cpp | 1 + src/lib/config/WorldConfig.h | 1 + src/lib/sim/Ship.h | 6 ++++++ src/lib/sim/ShipSystem.cpp | 38 ++++++++++++++++++++++++++++++--- src/lib/sim/ShipSystem.h | 7 ++++++ src/lib/sim/Simulation.cpp | 15 +++++++++++++ src/lib/sim/Simulation.h | 1 + src/test/ConfigLoaderTest.cpp | 2 ++ src/test/config/world.toml | 1 + 10 files changed, 78 insertions(+), 11 deletions(-) diff --git a/bin/config/world.toml b/bin/config/world.toml index 67d2ac9..145301e 100644 --- a/bin/config/world.toml +++ b/bin/config/world.toml @@ -1,28 +1,29 @@ [world] -height_tiles = 60 +height_tiles = 30 refund_percentage = 75 -starting_building_blocks = 100 +starting_building_blocks = 1000 scrap_despawn_seconds = 30 belt_speed_tiles_per_second = 2 tunnel_max_distance = 10 +departure_interval_seconds = 20 [regions] asteroid_width = 40 -player_buffer_width = 10 -contest_zone_width = 30 -enemy_buffer_width = 15 +player_buffer_width = 20 +contest_zone_width = 60 +enemy_buffer_width = 20 [expansion] columns_per_expansion = 10 cost_building_blocks = 200 [push] -push_expand_columns = 20 +push_expand_columns = 10 scaling_factor = 1.2 [waves] -threat_rate_formula = "1*x - 30" -ship_level_formula = "1 + x / 120" +threat_rate_formula = "0.1*x - 60" +ship_level_formula = "1" gap_min_seconds = 15 gap_max_seconds = 45 spawn_duration_seconds = 10 diff --git a/src/lib/config/ConfigLoader.cpp b/src/lib/config/ConfigLoader.cpp index 533e2b2..0121119 100644 --- a/src/lib/config/ConfigLoader.cpp +++ b/src/lib/config/ConfigLoader.cpp @@ -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.beltSpeedTilesPerSecond = requireDouble(tbl["world"]["belt_speed_tiles_per_second"], file, "world.belt_speed_tiles_per_second"); cfg.tunnelMaxDistance = static_cast(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(requireInt(tbl["regions"]["asteroid_width"], file, "regions.asteroid_width")); cfg.regions.playerBufferWidth = static_cast(requireInt(tbl["regions"]["player_buffer_width"], file, "regions.player_buffer_width")); diff --git a/src/lib/config/WorldConfig.h b/src/lib/config/WorldConfig.h index 4eccc6b..4124aeb 100644 --- a/src/lib/config/WorldConfig.h +++ b/src/lib/config/WorldConfig.h @@ -43,6 +43,7 @@ struct WorldConfig double scrapDespawnSeconds; // REQ-RES-SCRAP-DROP double beltSpeedTilesPerSecond; // REQ-GW-BELT-SPEED int tunnelMaxDistance; // REQ-BLD-TUNNEL-PAIR + double departureIntervalSeconds; // REQ-SHP-RALLY WorldRegions regions; WorldExpansion expansion; diff --git a/src/lib/sim/Ship.h b/src/lib/sim/Ship.h index f0d90e6..5daf804 100644 --- a/src/lib/sim/Ship.h +++ b/src/lib/sim/Ship.h @@ -62,6 +62,11 @@ struct HomeReturn QVector2D homePos; }; +struct RallyBehavior +{ + QVector2D rallyPoint; +}; + // --------------------------------------------------------------------------- // Ship // --------------------------------------------------------------------------- @@ -86,6 +91,7 @@ struct Ship std::optional scrapCollector; std::optional repairBehavior; std::optional homeReturn; + std::optional rallyBehavior; // Cleared at the start of the behavior step each tick; the highest-priority // write from behavior systems wins (architecture.md §Movement Arbitration). diff --git a/src/lib/sim/ShipSystem.cpp b/src/lib/sim/ShipSystem.cpp index 1d286ed..49e0e07 100644 --- a/src/lib/sim/ShipSystem.cpp +++ b/src/lib/sim/ShipSystem.cpp @@ -64,6 +64,11 @@ EntityId ShipSystem::spawn(const std::string& schematicId, int level, QVector2D ThreatResponse tr; tr.engagementRange = w.range; ship.threatResponse = tr; + + if (!isEnemy) + { + ship.rallyBehavior = RallyBehavior{m_rallyPoint}; + } } if (def->salvage) @@ -271,11 +276,18 @@ void ShipSystem::tickThreatResponse(const BuildingSystem& buildings) } else { - // No target: patrol rightward (aggressive). + // No target: gather at rally point or patrol rightward once departed. if (3 > s.intent.priority) { - s.intent = MovementIntent{3, QVector2D(s.position.x() + 1000.0f, - s.position.y())}; + if (s.rallyBehavior) + { + s.intent = MovementIntent{3, s.rallyBehavior->rallyPoint}; + } + else + { + s.intent = MovementIntent{3, QVector2D(s.position.x() + 1000.0f, + s.position.y())}; + } } } } @@ -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) // --------------------------------------------------------------------------- diff --git a/src/lib/sim/ShipSystem.h b/src/lib/sim/ShipSystem.h index 761c995..2966785 100644 --- a/src/lib/sim/ShipSystem.h +++ b/src/lib/sim/ShipSystem.h @@ -47,6 +47,12 @@ public: // -- Movement (tick-order step 10) --------------------------------------- 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. // Returns false if ship not found. bool damageShip(EntityId id, float amount); @@ -66,4 +72,5 @@ private: const GameConfig& m_config; std::function m_allocateId; std::vector m_ships; + QVector2D m_rallyPoint; }; diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index 1175033..9ef6351 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -13,6 +13,7 @@ Simulation::Simulation(GameConfig config, unsigned int seed) : m_config(std::move(config)) , m_rng(seed) , m_currentTick(0) + , m_nextDepartureTick(secondsToTicks(m_config.world.departureIntervalSeconds)) , m_nextId(1) , m_buildingBlocksStock(m_config.world.startingBuildingBlocks) , m_gameOver(false) @@ -73,6 +74,7 @@ void Simulation::reset(unsigned int seed) { m_rng.seed(seed); m_currentTick = 0; + m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds); m_nextId = 1; m_buildingBlocksStock = m_config.world.startingBuildingBlocks; m_gameOver = false; @@ -139,6 +141,14 @@ void Simulation::tick() m_beltSystem.tick(); // step 6 // 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->tickHomeReturn(); // priority 4 m_shipSystem->tickThreatResponse(*m_buildingSystem); // priority 3 @@ -217,6 +227,11 @@ void Simulation::placeInitialStructures() QPoint(psAnchorX, ps2Y), Rotation::East, psHp, psHp); m_buildingSystem->initStationWeapon(m_playerStation2Id, psWeapon); + // Rally point: center of the player defence stations' X column, world vertical midpoint. + const float rallyX = static_cast(psAnchorX) + psParsed.footprint.width() / 2.0f; + const float rallyY = static_cast(m_config.world.heightTiles) / 2.0f; + m_shipSystem->setRallyPoint(QVector2D(rallyX, rallyY)); + // Enemy defence stations — generation 0 (initial set). placeEnemyStationSet(0); } diff --git a/src/lib/sim/Simulation.h b/src/lib/sim/Simulation.h index 063612c..ae9e33a 100644 --- a/src/lib/sim/Simulation.h +++ b/src/lib/sim/Simulation.h @@ -92,6 +92,7 @@ private: std::mt19937 m_rng; Tick m_currentTick; + Tick m_nextDepartureTick; EntityId m_nextId; int m_buildingBlocksStock; bool m_gameOver = false; diff --git a/src/test/ConfigLoaderTest.cpp b/src/test/ConfigLoaderTest.cpp index 95c3319..71f70eb 100644 --- a/src/test/ConfigLoaderTest.cpp +++ b/src/test/ConfigLoaderTest.cpp @@ -165,6 +165,7 @@ scrap_despawn_seconds = 30 belt_speed_tiles_per_second = 2 starting_building_blocks = 100 tunnel_max_distance = 10 +departure_interval_seconds = 20 [regions] asteroid_width = 40 @@ -211,6 +212,7 @@ scrap_despawn_seconds = 30 belt_speed_tiles_per_second = 2 starting_building_blocks = 100 tunnel_max_distance = 10 +departure_interval_seconds = 20 [regions] asteroid_width = 40 diff --git a/src/test/config/world.toml b/src/test/config/world.toml index 67d2ac9..c4874a0 100644 --- a/src/test/config/world.toml +++ b/src/test/config/world.toml @@ -5,6 +5,7 @@ starting_building_blocks = 100 scrap_despawn_seconds = 30 belt_speed_tiles_per_second = 2 tunnel_max_distance = 10 +departure_interval_seconds = 20 [regions] asteroid_width = 40