fix(game): fight before departure and reorder the turn sequence #77
@@ -32,14 +32,23 @@ type BattleAction struct {
|
||||
func CollectPlanetGroups(c *Cache) map[uint]map[int]bool {
|
||||
planetGroup := make(map[uint]map[int]bool)
|
||||
for groupIndex := range c.ShipGroupsIndex() {
|
||||
state := c.ShipGroup(groupIndex).State()
|
||||
if state == game.StateInOrbit || state == game.StateUpgrade {
|
||||
planetNumber := c.ShipGroup(groupIndex).Destination
|
||||
if _, ok := planetGroup[planetNumber]; !ok {
|
||||
planetGroup[planetNumber] = make(map[int]bool)
|
||||
}
|
||||
planetGroup[planetNumber][groupIndex] = false
|
||||
sg := c.ShipGroup(groupIndex)
|
||||
var planetNumber uint
|
||||
switch sg.State() {
|
||||
case game.StateInOrbit, game.StateUpgrade:
|
||||
planetNumber = sg.Destination
|
||||
case game.StateLaunched:
|
||||
// Ordered to depart but still physically at the origin planet, so
|
||||
// it joins the pre-departure battle there; only survivors then
|
||||
// enter hyperspace.
|
||||
planetNumber = sg.StateInSpace.Origin
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if _, ok := planetGroup[planetNumber]; !ok {
|
||||
planetGroup[planetNumber] = make(map[int]bool)
|
||||
}
|
||||
planetGroup[planetNumber][groupIndex] = false
|
||||
}
|
||||
for pl := range planetGroup {
|
||||
if len(planetGroup[pl]) < 2 {
|
||||
@@ -50,7 +59,18 @@ func CollectPlanetGroups(c *Cache) map[uint]map[int]bool {
|
||||
}
|
||||
|
||||
func FilterBattleGroups(c *Cache, groups map[int]bool) []int {
|
||||
return slices.DeleteFunc(slices.Collect(maps.Keys(groups)), func(groupIndex int) bool { return c.ShipGroup(groupIndex).State() != game.StateInOrbit })
|
||||
return slices.DeleteFunc(slices.Collect(maps.Keys(groups)), func(groupIndex int) bool {
|
||||
// Everything physically present at the planet fights: ships in orbit,
|
||||
// ships being upgraded, and ships ordered to depart that have not yet
|
||||
// entered hyperspace (Launched). Only ships already in hyperspace are
|
||||
// out of reach.
|
||||
switch c.ShipGroup(groupIndex).State() {
|
||||
case game.StateInOrbit, game.StateUpgrade, game.StateLaunched:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FilterBattleOpponents(c *Cache, attIdx, defIdx int, cacheProbability map[int]map[int]float64) bool {
|
||||
|
||||
@@ -326,3 +326,56 @@ func TestSingleBattleOneSidedWipe(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, 5, kills, "exactly the five transports are destroyed")
|
||||
}
|
||||
|
||||
// TestCollectPlanetGroupsIncludesLaunchedAndUpgrade checks that every group
|
||||
// physically at a planet — in orbit, being upgraded, or ordered to depart but
|
||||
// not yet flown (Launched) — is collected for, and kept in, the battle.
|
||||
func TestCollectPlanetGroupsIncludesLaunchedAndUpgrade(t *testing.T) {
|
||||
c, _ := newCache()
|
||||
// group 0: in orbit at Planet_0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1))
|
||||
// group 1: ordered to depart Planet_0 (Launched), still physically there
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||
c.ShipGroup(1).StateInSpace = &game.InSpace{Origin: R0_Planet_0_num}
|
||||
c.ShipGroup(1).Destination = R0_Planet_2_num
|
||||
assert.Equal(t, game.StateLaunched, c.ShipGroup(1).State())
|
||||
// group 2: being upgraded at Planet_0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||
c.ShipGroup(2).StateUpgrade = &game.InUpgrade{UpgradeTech: []game.UpgradePreference{{Tech: game.TechDrive, Level: 2.0, Cost: 100}}}
|
||||
assert.Equal(t, game.StateUpgrade, c.ShipGroup(2).State())
|
||||
|
||||
pg := controller.CollectPlanetGroups(c)
|
||||
assert.Contains(t, pg, R0_Planet_0_num)
|
||||
assert.Len(t, pg[R0_Planet_0_num], 3)
|
||||
for _, idx := range []int{0, 1, 2} {
|
||||
assert.Contains(t, pg[R0_Planet_0_num], idx)
|
||||
}
|
||||
battleGroups := controller.FilterBattleGroups(c, pg[R0_Planet_0_num])
|
||||
assert.Len(t, battleGroups, 3)
|
||||
}
|
||||
|
||||
// TestProduceBattlesLaunchedFightsAtOrigin checks that a group ordered to
|
||||
// depart (Launched) still fights the pre-departure battle at its origin
|
||||
// planet, rather than escaping into hyperspace before the fight.
|
||||
func TestProduceBattlesLaunchedFightsAtOrigin(t *testing.T) {
|
||||
c, g := newCache()
|
||||
assert.NoError(t, g.RaceRelation(Race_0.Name, Race_1.Name, game.RelationWar.String()))
|
||||
assert.NoError(t, g.RaceRelation(Race_1.Name, Race_0.Name, game.RelationWar.String()))
|
||||
|
||||
// Race_0: armed group in orbit at Planet_0.
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 10))
|
||||
// Race_1: armed group ordered to depart Planet_0 (Launched), still there.
|
||||
c.CreateShipsUnsafe_T(Race_1_idx, c.MustShipClass(Race_1_idx, Race_1_Gunship).ID, R0_Planet_0_num, 10)
|
||||
c.ShipGroup(1).StateInSpace = &game.InSpace{Origin: R0_Planet_0_num}
|
||||
c.ShipGroup(1).Destination = R0_Planet_2_num
|
||||
assert.Equal(t, game.StateLaunched, c.ShipGroup(1).State())
|
||||
|
||||
battles := controller.ProduceBattles(c)
|
||||
assert.Len(t, battles, 1)
|
||||
assert.True(t, battles[0].ObserverGroups[1], "launched group must be marked in-battle")
|
||||
if c.ShipGroup(0).Number == 0 {
|
||||
assert.Greater(t, c.ShipGroup(1).Number, uint(0))
|
||||
} else {
|
||||
assert.Zero(t, c.ShipGroup(1).Number)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,25 +20,29 @@ func (c *Controller) MakeTurn() error {
|
||||
c.Cache.g.Turn += 1
|
||||
c.Cache.g.Stage = 0
|
||||
|
||||
// 01. Вышедшие расы удаляются из списка участвующих рас перед началом просчета очередного хода
|
||||
// 01. Вышедшие расы удаляются из списка участвующих рас перед началом просчёта очередного хода.
|
||||
c.Cache.TurnWipeExtinctRaces()
|
||||
|
||||
// 02. Товары загружаются на корабли, находящиеся в начале грузовых маршрутов, и корабли входят в гиперпространство (но ещё не полетели)
|
||||
c.Cache.SendRoutedGroups()
|
||||
|
||||
// 03. Корабли, где это возможно, объединяются в группы.
|
||||
// 02. Корабли, где это возможно, объединяются в группы (до боя и до отправки по маршрутам).
|
||||
c.Cache.TurnMergeEqualShipGroups()
|
||||
|
||||
// 04. Враждующие корабли вступают в схватку.
|
||||
// 03. Враждующие корабли вступают в схватку у планеты отправления. Корабли, которым отдан
|
||||
// приказ на отлёт (статус Launched), ещё стоят на планете и участвуют в бою; в
|
||||
// гиперпространство уходят только уцелевшие — так нельзя уклониться от боя.
|
||||
battles := ProduceBattles(c.Cache)
|
||||
|
||||
// 04. Товары загружаются на корабли в начале грузовых маршрутов, и эти корабли входят в
|
||||
// гиперпространство. Загрузка после боя: маршрутные транспорты сражаются пустыми и не
|
||||
// могут уклониться от боя, скрывшись в гиперпространстве.
|
||||
c.Cache.SendRoutedGroups()
|
||||
|
||||
// 05. Корабли пролетают сквозь гиперпространство.
|
||||
c.Cache.MoveShipGroups()
|
||||
|
||||
// 06. Корабли, где это возможно, объединяются в группы.
|
||||
c.Cache.TurnMergeEqualShipGroups()
|
||||
|
||||
// 07. Враждующие корабли снова вступают в схватку (это происходит после выхода из гиперпространства).
|
||||
// 07. Враждующие корабли снова вступают в схватку (после выхода из гиперпространства).
|
||||
battles = append(battles, ProduceBattles(c.Cache)...)
|
||||
|
||||
// 08. Корабли бомбят вражеские планеты.
|
||||
|
||||
@@ -162,7 +162,7 @@ func (c *Cache) PlanetProductionCapacity(planetNumber uint) float64 {
|
||||
p := c.MustPlanet(planetNumber)
|
||||
var busyResources float64
|
||||
for sg := range c.shipGroupsInUpgrade(p.Number) {
|
||||
busyResources += sg.StateUpgrade.Cost()
|
||||
busyResources += c.upgradeCostNow(sg)
|
||||
}
|
||||
return p.ProductionCapacity() - busyResources
|
||||
}
|
||||
@@ -181,10 +181,15 @@ func (c *Cache) TurnPlanetProductions() {
|
||||
ri := c.RaceIndex(*p.Owner)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
// upgrade groups and return to in_orbit state
|
||||
productionAvailable := c.PlanetProductionCapacity(pn)
|
||||
// Upgrade groups (most expensive first) and return them to the
|
||||
// in-orbit state, paying for each upgrade once out of the planet's
|
||||
// full production potential; whatever remains feeds this turn's
|
||||
// production below. Starting from PlanetProductionCapacity here would
|
||||
// have charged every applied upgrade twice, since that helper already
|
||||
// nets out the reserved upgrade cost for the report.
|
||||
productionAvailable := p.ProductionCapacity()
|
||||
for sg := range c.shipGroupsInUpgrade(p.Number) {
|
||||
cost := sg.StateUpgrade.Cost()
|
||||
cost := c.upgradeCostNow(sg)
|
||||
if productionAvailable >= cost {
|
||||
for i := range sg.StateUpgrade.UpgradeTech {
|
||||
sg.Tech = sg.Tech.Set(sg.StateUpgrade.UpgradeTech[i].Tech, util.Fixed3(sg.StateUpgrade.UpgradeTech[i].Level.F()))
|
||||
|
||||
@@ -208,7 +208,40 @@ func TestProduceShips(t *testing.T) {
|
||||
assert.Equal(t, game.StateInOrbit, c.ShipGroup(0).State())
|
||||
assert.Equal(t, 1.5, c.ShipGroup(0).TechLevel(game.TechDrive).F())
|
||||
|
||||
assert.Equal(t, 4346.676567656759, c.MustPlanet(R0_Planet_0_num).Material.F())
|
||||
// The pending upgrade is now charged once (not twice) against the planet's
|
||||
// production potential, so MAT production keeps the budget it previously
|
||||
// lost to the double charge (the pre-fix value here was ~4346.68).
|
||||
assert.InDelta(t, 7173.3432, c.MustPlanet(R0_Planet_0_num).Material.F(), 0.001)
|
||||
}
|
||||
|
||||
// TestUpgradeDoesNotDoubleChargeProduction guards that a pending upgrade is
|
||||
// paid for once out of the planet's production potential, leaving the rest for
|
||||
// the turn's production. The pre-fix code subtracted the upgrade cost twice
|
||||
// (PlanetProductionCapacity already nets it out for the report, and the apply
|
||||
// loop netted it again), which both starved production and could skip
|
||||
// affordable upgrades.
|
||||
func TestUpgradeDoesNotDoubleChargeProduction(t *testing.T) {
|
||||
c, _ := newCache()
|
||||
p := c.MustPlanet(R0_Planet_0_num)
|
||||
p.Population = 1000
|
||||
p.Industry = 1000 // ProductionCapacity = 1000*0.75 + 1000*0.25 = 1000
|
||||
p.Resources = 1 // material produced == leftover production budget
|
||||
p.Colonists = 0
|
||||
p.Material = 0
|
||||
assert.NoError(t, c.PlanetProduce(Race_0_idx, int(R0_Planet_0_num), game.ProductionMaterial, ""))
|
||||
|
||||
// One Cruiser with a pending drive upgrade 1.1 -> 2.0:
|
||||
// block cost = (1 - 1.1/2.0) * 10 * 15 = 67.5 for the single ship.
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||
c.ShipGroup(0).StateUpgrade = &game.InUpgrade{
|
||||
UpgradeTech: []game.UpgradePreference{{Tech: game.TechDrive, Level: 2.0}},
|
||||
}
|
||||
|
||||
c.TurnPlanetProductions()
|
||||
|
||||
assert.InDelta(t, 2.0, c.ShipGroup(0).TechLevel(game.TechDrive).F(), 0.0001)
|
||||
// 1000 - 67.5 = 932.5; the pre-fix double charge would have left 865.
|
||||
assert.InDelta(t, 932.5, c.MustPlanet(R0_Planet_0_num).Material.F(), 0.01)
|
||||
}
|
||||
|
||||
func TestProduceShip(t *testing.T) {
|
||||
|
||||
@@ -491,7 +491,7 @@ func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup]
|
||||
}
|
||||
}
|
||||
slices.SortFunc(result, func(a, b int) int {
|
||||
return cmp.Compare(c.g.ShipGroups[b].StateUpgrade.Cost(), c.g.ShipGroups[a].StateUpgrade.Cost())
|
||||
return cmp.Compare(c.upgradeCostNow(&c.g.ShipGroups[b]), c.upgradeCostNow(&c.g.ShipGroups[a]))
|
||||
})
|
||||
for i := range result {
|
||||
if !yield(&c.g.ShipGroups[result[i]]) {
|
||||
|
||||
@@ -34,15 +34,20 @@ func (c *Cache) MoveShipGroups() {
|
||||
|
||||
func (c *Cache) moveShipGroup(i int, delta float64) {
|
||||
sg := c.ShipGroup(i)
|
||||
originX, originY, ok := sg.Coord()
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("ship group state invalid: %v", sg.State()))
|
||||
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)
|
||||
arrived := false
|
||||
var x, y float64
|
||||
x, y, arrived =
|
||||
util.NextTravelCoord(c.g.Map.Width, c.g.Map.Height, originX, originY, destPlanet.X.F(), destPlanet.Y.F(), delta)
|
||||
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
|
||||
|
||||
@@ -45,3 +45,20 @@ func TestListMoveableGroupIds(t *testing.T) {
|
||||
assert.NotEqual(t, game.StateTransfer, sg.State())
|
||||
}
|
||||
}
|
||||
|
||||
// TestMoveLaunchedGroupFromOrigin guards the launched-coordinate fix: a group
|
||||
// just sent by an order is Launched with no stored hyperspace position, so its
|
||||
// first leg must start from the origin planet. The pre-fix code dereferenced
|
||||
// the nil launch coordinate and panicked.
|
||||
func TestMoveLaunchedGroupFromOrigin(t *testing.T) {
|
||||
c, g := newCache()
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
|
||||
assert.NoError(t, g.ShipGroupSend(Race_0.Name, c.ShipGroup(0).ID, R0_Planet_2_num))
|
||||
assert.Equal(t, game.StateLaunched, c.ShipGroup(0).State())
|
||||
|
||||
// Must not panic on the nil launch coordinate. Planet_0 (1,1) -> Planet_2
|
||||
// (3,3) is ~2.83 ly; a Cruiser covers it in one turn and arrives.
|
||||
c.MoveShipGroups()
|
||||
assert.Equal(t, game.StateInOrbit, c.ShipGroup(0).State())
|
||||
assert.Equal(t, R0_Planet_2_num, c.ShipGroup(0).Destination)
|
||||
}
|
||||
|
||||
@@ -196,6 +196,24 @@ func FutureUpgradeLevel(raceLevel, groupLevel, limit float64) float64 {
|
||||
return target
|
||||
}
|
||||
|
||||
// upgradeCostNow returns the production cost to apply group sg's pending
|
||||
// upgrade to its CURRENT ship count. The cost stored on StateUpgrade is fixed
|
||||
// when the order is validated; if the group has since lost ships (for example
|
||||
// in the pre-departure battle, now that upgrading groups take part in it), the
|
||||
// stored total is stale, so the cost is recomputed from the stored target
|
||||
// levels, the group's current tech, and its current ship count.
|
||||
func (c *Cache) upgradeCostNow(sg *game.ShipGroup) float64 {
|
||||
if sg.StateUpgrade == nil {
|
||||
return 0
|
||||
}
|
||||
st := c.MustShipType(c.RaceIndex(sg.OwnerID), sg.TypeID)
|
||||
var perShip float64
|
||||
for _, pref := range sg.StateUpgrade.UpgradeTech {
|
||||
perShip += calc.BlockUpgradeCost(st.BlockMass(pref.Tech), sg.TechLevel(pref.Tech).F(), pref.Level.F())
|
||||
}
|
||||
return perShip * float64(sg.Number)
|
||||
}
|
||||
|
||||
func UpgradeGroupPreference(sg game.ShipGroup, st game.ShipType, tech game.Tech, v float64) game.ShipGroup {
|
||||
if v <= 0 || st.BlockMass(tech) == 0 || sg.TechLevel(tech).F() >= v {
|
||||
return sg
|
||||
|
||||
@@ -170,3 +170,24 @@ func TestShipGroupUpgrade(t *testing.T) {
|
||||
g.ShipGroupUpgrade(Race_0.Name, c.ShipGroup(3).ID, "DRIVE", 1.3),
|
||||
e.GenericErrorText(e.ErrShipsBusy))
|
||||
}
|
||||
|
||||
// TestUpgradeCostTracksShipLosses checks that the production reserved for a
|
||||
// pending upgrade follows the group's CURRENT ship count. Upgrading groups now
|
||||
// take part in the pre-departure battle and may lose ships, which would leave
|
||||
// the cost stored at order time stale; the cost is recomputed instead.
|
||||
func TestUpgradeCostTracksShipLosses(t *testing.T) {
|
||||
c, _ := newCache()
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 4))
|
||||
// Pending drive upgrade with a deliberately stale stored cost.
|
||||
c.ShipGroup(0).StateUpgrade = &game.InUpgrade{
|
||||
UpgradeTech: []game.UpgradePreference{{Tech: game.TechDrive, Level: 2.0, Cost: 9999}},
|
||||
}
|
||||
before := c.PlanetProductionCapacity(R0_Planet_0_num)
|
||||
// Two ships lost, as in the pre-departure battle.
|
||||
c.ShipGroupDestroyItem(0)
|
||||
c.ShipGroupDestroyItem(0)
|
||||
after := c.PlanetProductionCapacity(R0_Planet_0_num)
|
||||
// Fewer ships reserve less production. With the stale stored cost this
|
||||
// would be unchanged.
|
||||
assert.Greater(t, after, before)
|
||||
}
|
||||
|
||||
+30
-27
@@ -849,9 +849,12 @@ Freighter загруженный 15 ед. груза при технологии
|
||||
находится с агрессором в состоянии мира, также вступит в сражение. Это вовсе
|
||||
не означает, что у нападающих есть право первого выстрела, все сражающиеся
|
||||
стороны находятся в абсолютно равных условиях ведения сражения, и первым
|
||||
выстрелит тот, кто более удачлив. Отосланные с планеты вручную или по
|
||||
маршруту корабли уже вошли в гиперпространство и участия в сражениях не
|
||||
принимают (см. "Последовательность действий").
|
||||
выстрелит тот, кто более удачлив. Корабли, которым отдан приказ на отлёт, а
|
||||
также корабли, отправляемые по установленным грузовым маршрутам, к началу хода
|
||||
ещё находятся на планете отправления и принимают участие в сражении у этой
|
||||
планеты — в гиперпространство уходят лишь уцелевшие в нём корабли. Корабль,
|
||||
уже находящийся в гиперпространстве, в сражениях не участвует вплоть до
|
||||
прибытия на планету назначения (см. "Последовательность действий").
|
||||
|
||||
В каждом раунде сражения все корабли получают шанс выстрелить по противнику,
|
||||
разумеется, если в этом же раунде его противники не были более удачливы и не
|
||||
@@ -1080,57 +1083,57 @@ Freighter загруженный 15 ед. груза при технологии
|
||||
Последовательность действий
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
После того, как получены приказы от всех рас, определено
|
||||
производство, загружены товары, корабли вошли в гиперпространство и
|
||||
т.д. происходит сам ход, т.е. следующая последовательность действий:
|
||||
После того, как получены приказы от всех рас, происходит сам ход, т.е.
|
||||
следующая последовательность действий:
|
||||
|
||||
- Корабли передаются новым владельцам.
|
||||
- Корабли передаются новым владельцам.
|
||||
|
||||
- Расы, покинувшие игру, освобождаются от своего имущества.
|
||||
|
||||
- ------------------------------------ \
|
||||
} "Выполнение отданных приказов (всех?)"
|
||||
- Корабли разгружаются согласно отданным приказам. /
|
||||
- Выполняются все отданные расами приказы. В частности, корабли разгружаются
|
||||
согласно приказам, а кораблям может быть отдан приказ на отлёт: такие корабли
|
||||
получают готовность к отлёту, но физически остаются на планете отправления.
|
||||
|
||||
- Корабли, где это возможно, объединяются в группы.
|
||||
|
||||
- Товары загружаются на корабли, находящиеся в начале грузовых
|
||||
маршрутов.
|
||||
- Враждующие корабли вступают в схватку у планет отправления. Корабли, которым
|
||||
отдан приказ на отлёт, ещё стоят на планете и участвуют в этой схватке; в
|
||||
гиперпространство уходят только уцелевшие.
|
||||
|
||||
- Корабли входят в гиперпространство.
|
||||
- Товары загружаются на корабли, находящиеся в начале грузовых маршрутов
|
||||
(после схватки — маршрутные транспорты сражаются незагруженными).
|
||||
|
||||
- Враждующие корабли вступают в схватку.
|
||||
|
||||
- Корабли пролетают сквозь гиперпространство.
|
||||
- Корабли (готовые к отлёту и снаряжённые по маршрутам) входят в
|
||||
гиперпространство и пролетают сквозь него.
|
||||
|
||||
- Корабли, где это возможно, объединяются в группы.
|
||||
|
||||
- Враждующие корабли вступают в схватку (после выхода из гиперпространства).
|
||||
- Враждующие корабли вступают в схватку (после выхода из гиперпространства,
|
||||
у планет назначения).
|
||||
|
||||
- Корабли бомбят вражеские планеты.
|
||||
|
||||
+ На планетах модернизируются корабли.
|
||||
- На планетах модернизируются корабли.
|
||||
|
||||
- На планетах строятся корабли (с учётом производственного потенциала,
|
||||
оставшегося от модернизации кораблей).
|
||||
|
||||
+ Корабли, где это возможно, объединяются в группы.
|
||||
- Корабли, где это возможно, объединяются в группы.
|
||||
|
||||
- На планетах производится промышленность, добывается сырье,
|
||||
разрабатываются новые технологии.
|
||||
- На планетах производится промышленность, добывается сырьё, разрабатываются
|
||||
новые технологии.
|
||||
|
||||
- Увеличивается население планет.
|
||||
|
||||
- Корабли разгружаются в конце грузовых маршрутов.
|
||||
|
||||
+ Выгруженные колонисты увеличивают население планеты (если население
|
||||
планеты ниже её размера).
|
||||
- Выгруженные колонисты увеличивают население планеты (если население планеты
|
||||
ниже её размера).
|
||||
|
||||
- Накопленная и выгруженная промышленность увеличивает
|
||||
производственный уровень планеты (если производственный уровень
|
||||
планеты ниже уровня населения).
|
||||
- Накопленная и выгруженная промышленность увеличивает производственный уровень
|
||||
планеты (если производственный уровень планеты ниже уровня населения).
|
||||
|
||||
- Происходит отмена маршрутов, выходящих за зону полета кораблей.
|
||||
- Происходит отмена маршрутов, выходящих за зону полёта кораблей.
|
||||
|
||||
- Происходит голосование.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user