b4abf90ec5
Per the documented turn order (game/rules.txt "Последовательность действий"), no ship should dodge the pre-departure battle by slipping into hyperspace. MakeTurn now runs merge -> battle -> load+launch routed groups -> fly -> merge -> battle, so: - ships ordered to depart (Launched) and ships being upgraded now take part in the pre-departure battle at their planet (CollectPlanetGroups / FilterBattleGroups); only survivors then enter hyperspace; - routed transports are loaded and launched AFTER that battle, so they fight empty and cannot escape it. A just-launched group has no stored hyperspace position, so moveShipGroup starts its first leg from the origin planet; the previous code read the nil launch coordinate and would panic. Because upgrading groups can now lose ships in the battle, the pending upgrade cost is recomputed from the group's current ship count instead of the value stored when the order was validated. Rules: reordered "Последовательность действий" and rewrote the combat note that ordered/routed ships skip the battle. Tests: launched-group move from origin, launched/upgrade groups taking part in battle, upgrade cost tracking ship losses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
145 lines
5.7 KiB
Go
145 lines
5.7 KiB
Go
package controller
|
|
|
|
import (
|
|
"maps"
|
|
"slices"
|
|
|
|
"galaxy/model/report"
|
|
|
|
"galaxy/game/internal/model/game"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func (c *Controller) MakeTurn() error {
|
|
if err := c.applyOrders(c.Cache.g.Turn); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Next turn
|
|
c.Cache.g.Turn += 1
|
|
c.Cache.g.Stage = 0
|
|
|
|
// 01. Вышедшие расы удаляются из списка участвующих рас перед началом просчёта очередного хода.
|
|
c.Cache.TurnWipeExtinctRaces()
|
|
|
|
// 02. Корабли, где это возможно, объединяются в группы (до боя и до отправки по маршрутам).
|
|
c.Cache.TurnMergeEqualShipGroups()
|
|
|
|
// 03. Враждующие корабли вступают в схватку у планеты отправления. Корабли, которым отдан
|
|
// приказ на отлёт (статус Launched), ещё стоят на планете и участвуют в бою; в
|
|
// гиперпространство уходят только уцелевшие — так нельзя уклониться от боя.
|
|
battles := ProduceBattles(c.Cache)
|
|
|
|
// 04. Товары загружаются на корабли в начале грузовых маршрутов, и эти корабли входят в
|
|
// гиперпространство. Загрузка после боя: маршрутные транспорты сражаются пустыми и не
|
|
// могут уклониться от боя, скрывшись в гиперпространстве.
|
|
c.Cache.SendRoutedGroups()
|
|
|
|
// 05. Корабли пролетают сквозь гиперпространство.
|
|
c.Cache.MoveShipGroups()
|
|
|
|
// 06. Корабли, где это возможно, объединяются в группы.
|
|
c.Cache.TurnMergeEqualShipGroups()
|
|
|
|
// 07. Враждующие корабли снова вступают в схватку (после выхода из гиперпространства).
|
|
battles = append(battles, ProduceBattles(c.Cache)...)
|
|
|
|
// 08. Корабли бомбят вражеские планеты.
|
|
bombings := c.Cache.ProduceBombings()
|
|
|
|
// 09. На планетах строятся корабли.
|
|
// 10. Корабли, где это возможно, объединяются в группы.
|
|
// 11. На планетах производится промышленность, добывается сырье, разрабатываются новые технологии.
|
|
// 12. Увеличивается население планет.
|
|
c.Cache.TurnPlanetProductions()
|
|
|
|
// 13. Товары выгружаются в конце грузовых маршрутов.
|
|
// 14. Выгруженные колонисты увеличивают население планеты (если население планеты ниже её размера).
|
|
// 15. Накопленная и выгруженная промышленность увеличивает производственный уровень планеты (если производственный уровень планеты ниже уровня населения).
|
|
c.Cache.TurnUnloadEnroutedGroups()
|
|
|
|
// 16. Происходит отмена маршрутов, выходящих за зону полета кораблей.
|
|
c.Cache.RemoveUnreachableRoutes()
|
|
|
|
// 17. Происходит голосование.
|
|
winners := c.Cache.TurnCalculateVotes()
|
|
c.Cache.TurnAcceptWinners(winners)
|
|
|
|
/*** Last steps ***/
|
|
|
|
// Store bombings
|
|
bombingReport := make([]*report.Bombing, len(bombings))
|
|
if len(bombings) > 0 {
|
|
if err := c.repo.SaveBombings(c.Cache.g.Turn, bombings); err != nil {
|
|
return err
|
|
}
|
|
for i := range bombings {
|
|
bombingReport[i].Planet = bombings[i].Planet
|
|
bombingReport[i].PlanetOwnedID = bombings[i].PlanetOwnedID
|
|
bombingReport[i].Number = bombings[i].Number
|
|
bombingReport[i].Owner = bombings[i].Owner
|
|
bombingReport[i].Attacker = bombings[i].Attacker
|
|
bombingReport[i].Production = bombings[i].Production
|
|
bombingReport[i].Industry = report.F(bombings[i].Industry.F())
|
|
bombingReport[i].Population = report.F(bombings[i].Population.F())
|
|
bombingReport[i].Colonists = report.F(bombings[i].Colonists.F())
|
|
bombingReport[i].Capital = report.F(bombings[i].Capital.F())
|
|
bombingReport[i].Material = report.F(bombings[i].Material.F())
|
|
bombingReport[i].AttackPower = report.F(bombings[i].AttackPower.F())
|
|
bombingReport[i].Wiped = bombings[i].Wiped
|
|
}
|
|
}
|
|
|
|
// Store battles
|
|
battleReport := make([]*report.BattleReport, len(battles))
|
|
if len(battles) > 0 {
|
|
battleMeta := make([]game.BattleMeta, len(battles))
|
|
for i := range battles {
|
|
b := battles[i]
|
|
|
|
observers := make(map[uuid.UUID]bool)
|
|
for sgi := range b.ObserverGroups {
|
|
observers[c.Cache.ShipGroup(sgi).OwnerID] = true
|
|
}
|
|
|
|
battleMeta[i] = game.BattleMeta{
|
|
Turn: c.Cache.g.Turn,
|
|
Planet: b.Planet,
|
|
BattleID: b.ID,
|
|
ObserverIDs: slices.Collect(maps.Keys(observers)),
|
|
}
|
|
|
|
report := TransformBattle(c.Cache, b)
|
|
if err := c.repo.SaveBattle(c.Cache.g.Turn, report, &battleMeta[i]); err != nil {
|
|
return err
|
|
}
|
|
battleReport[i] = report
|
|
}
|
|
}
|
|
|
|
// Remove killed ship groups
|
|
c.Cache.DeleteKilledShipGroups()
|
|
|
|
// Store game state for the new turn and 'current' state as well
|
|
if err := c.repo.SaveNewTurn(c.Cache.g.Turn, c.Cache.g); err != nil {
|
|
return err
|
|
}
|
|
|
|
for rep := range c.Cache.Report(c.Cache.g.Turn, battleReport, bombingReport) {
|
|
if err := c.repo.SaveReport(c.Cache.g.Turn, rep); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i := range c.Cache.g.Race {
|
|
if c.Cache.g.Race[i].Extinct {
|
|
continue
|
|
}
|
|
c.Cache.g.Race[i].TTL -= 1
|
|
}
|
|
|
|
// [ ] monitor memory consumption at this point?
|
|
return nil
|
|
}
|