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>
73 lines
1.8 KiB
Go
73 lines
1.8 KiB
Go
package controller
|
|
|
|
import (
|
|
"fmt"
|
|
"iter"
|
|
|
|
"galaxy/util"
|
|
|
|
"galaxy/game/internal/model/game"
|
|
)
|
|
|
|
func (c *Cache) MoveShipGroups() {
|
|
moved := make(map[int]bool)
|
|
for i := range c.listMoveableGroupIds() {
|
|
if v, ok := moved[i]; ok && v {
|
|
continue
|
|
}
|
|
sg := c.ShipGroup(i)
|
|
|
|
if sg.FleetID != nil {
|
|
fi := c.MustFleetIndex(*sg.FleetID)
|
|
delta, _ := c.FleetSpeedAndMass(fi)
|
|
for fgi := range c.fleetGroupIds(c.RaceIndex(sg.OwnerID), c.MustFleetIndex(*sg.FleetID)) {
|
|
c.moveShipGroup(fgi, delta)
|
|
moved[fgi] = true
|
|
}
|
|
continue
|
|
}
|
|
|
|
c.moveShipGroup(i, sg.Speed(c.ShipGroupShipClass(i)))
|
|
moved[i] = true
|
|
}
|
|
}
|
|
|
|
func (c *Cache) moveShipGroup(i int, delta float64) {
|
|
sg := c.ShipGroup(i)
|
|
var originX, originY float64
|
|
switch sg.State() {
|
|
case game.StateLaunched:
|
|
// Just launched: the group is still at its origin planet and has not
|
|
// stored a hyperspace position yet, so the first leg starts there.
|
|
origin := c.MustPlanet(sg.StateInSpace.Origin)
|
|
originX, originY = origin.X.F(), origin.Y.F()
|
|
case game.StateInSpace:
|
|
originX, originY = sg.StateInSpace.X.F(), sg.StateInSpace.Y.F()
|
|
default:
|
|
panic(fmt.Sprintf("ship group state invalid for move: %v", sg.State()))
|
|
}
|
|
destPlanet := c.MustPlanet(sg.Destination)
|
|
x, y, arrived := util.NextTravelCoord(c.g.Map.Width, c.g.Map.Height, originX, originY, destPlanet.X.F(), destPlanet.Y.F(), delta)
|
|
fx, fy := game.F(x), game.F(y)
|
|
sg.StateInSpace.X = &fx
|
|
sg.StateInSpace.Y = &fy
|
|
if arrived {
|
|
sg.StateInSpace = nil
|
|
}
|
|
}
|
|
|
|
func (c *Cache) listMoveableGroupIds() iter.Seq[int] {
|
|
return func(yield func(int) bool) {
|
|
for i := range c.ShipGroupsIndex() {
|
|
sg := c.ShipGroup(i)
|
|
state := sg.State()
|
|
if !(state == game.StateInOrbit || state == game.StateLaunched || state == game.StateInSpace) {
|
|
continue
|
|
}
|
|
if !yield(i) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|