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>
568 lines
16 KiB
Go
568 lines
16 KiB
Go
package controller
|
|
|
|
import (
|
|
"cmp"
|
|
"fmt"
|
|
"iter"
|
|
"maps"
|
|
"slices"
|
|
|
|
"galaxy/util"
|
|
|
|
e "galaxy/error"
|
|
|
|
"galaxy/game/internal/model/game"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ShipGroup is a proxy func, nothing to cache
|
|
func (c *Cache) ShipGroup(groupIndex int) *game.ShipGroup {
|
|
c.validateShipGroupIndex(groupIndex)
|
|
return &c.g.ShipGroups[groupIndex]
|
|
}
|
|
|
|
func (c *Cache) internalShipGroupJoinFleet(groupIndex int, fID uuid.UUID) {
|
|
c.validateShipGroupIndex(groupIndex)
|
|
c.g.ShipGroups[groupIndex].FleetID = &fID
|
|
c.invalidateFleetCache()
|
|
}
|
|
|
|
func (c *Cache) ShipGroupShipsNumber(groupIndex int, number uint) {
|
|
c.validateShipGroupIndex(groupIndex)
|
|
if c.g.ShipGroups[groupIndex].Number > 0 {
|
|
c.g.ShipGroups[groupIndex].Load = game.F(c.g.ShipGroups[groupIndex].Load.F() / float64(c.g.ShipGroups[groupIndex].Number) * float64(number))
|
|
}
|
|
c.g.ShipGroups[groupIndex].Number = number
|
|
}
|
|
|
|
func (c *Cache) ShipGroupsIndex() iter.Seq[int] {
|
|
return func(yield func(int) bool) {
|
|
for i := range c.g.ShipGroups {
|
|
if !yield(i) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Cache) ShipGroupOwnerRaceIndex(groupIndex int) int {
|
|
c.validateShipGroupIndex(groupIndex)
|
|
if len(c.cacheRaceIndexByShipGroupIndex) == 0 {
|
|
c.cacheShipsAndGroups()
|
|
}
|
|
if v, ok := c.cacheRaceIndexByShipGroupIndex[groupIndex]; ok {
|
|
return v
|
|
} else {
|
|
panic(fmt.Sprintf("ShipGroupRace: group not found by index=%v", groupIndex))
|
|
}
|
|
}
|
|
|
|
func (c *Cache) ShipGroupOwnerRace(groupIndex int) *game.Race {
|
|
return &c.g.Race[c.ShipGroupOwnerRaceIndex(groupIndex)]
|
|
}
|
|
|
|
func (c *Cache) ShipGroupDestroyItem(i int) {
|
|
c.validateShipGroupIndex(i)
|
|
sg := &c.g.ShipGroups[i]
|
|
if sg.Number == 0 {
|
|
panic("group has no ships")
|
|
}
|
|
sg.Load = game.F(sg.Load.F() / float64(sg.Number) * float64(sg.Number-1))
|
|
sg.Number -= 1
|
|
}
|
|
|
|
func (c *Cache) DeleteKilledShipGroups() {
|
|
keepFleet := make(map[uuid.UUID]bool, len(c.g.Fleets))
|
|
for sgi := len(c.g.ShipGroups) - 1; sgi >= 0; sgi-- {
|
|
if c.g.ShipGroups[sgi].FleetID != nil {
|
|
id := *c.g.ShipGroups[sgi].FleetID
|
|
keepFleet[id] = keepFleet[id] || c.g.ShipGroups[sgi].Number > 0
|
|
}
|
|
if c.g.ShipGroups[sgi].Number == 0 {
|
|
c.g.ShipGroups = append(c.g.ShipGroups[:sgi], c.g.ShipGroups[sgi+1:]...)
|
|
}
|
|
}
|
|
c.invalidateShipGroupCache()
|
|
for id, keep := range keepFleet {
|
|
if keep {
|
|
continue
|
|
}
|
|
c.unsafeDeleteFleet(c.MustFleetIndex(id))
|
|
}
|
|
}
|
|
|
|
func (c *Cache) TurnMergeEqualShipGroups() {
|
|
for i := range c.listRaceActingIdx() {
|
|
c.transferPendingGroups(i)
|
|
c.shipGroupMerge(i)
|
|
}
|
|
}
|
|
|
|
func (c *Cache) transferPendingGroups(ri int) {
|
|
c.validateRaceIndex(ri)
|
|
for sg := range c.listShipGroups(ri) {
|
|
if sg.State() == game.StateTransfer {
|
|
sg.StateTransfer = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// shipGroupMerge merges several equal ship groups into one
|
|
func (c *Cache) shipGroupMerge(ri int) {
|
|
c.validateRaceIndex(ri)
|
|
raceGroups := make([]game.ShipGroup, 0)
|
|
for sg := range c.listShipGroups(ri) {
|
|
raceGroups = append(raceGroups, *sg)
|
|
}
|
|
|
|
origin := len(raceGroups)
|
|
if origin < 2 {
|
|
return
|
|
}
|
|
for i := 0; i < len(raceGroups)-1; i++ {
|
|
for j := len(raceGroups) - 1; j > i; j-- {
|
|
if raceGroups[i].Equal(raceGroups[j]) {
|
|
raceGroups[i].ID = raceGroups[j].ID // resulting group will have latest ID
|
|
raceGroups[i].Number += raceGroups[j].Number
|
|
raceGroups = append(raceGroups[:j], raceGroups[j+1:]...)
|
|
}
|
|
}
|
|
}
|
|
if len(raceGroups) == origin {
|
|
return
|
|
}
|
|
|
|
toDelete := make([]int, 0)
|
|
for i := range c.ShipGroupsIndex() {
|
|
if c.ShipGroup(i).OwnerID == c.g.Race[ri].ID {
|
|
toDelete = append(toDelete, i)
|
|
}
|
|
}
|
|
|
|
slices.Sort(toDelete)
|
|
slices.Reverse(toDelete)
|
|
for _, sgi := range toDelete {
|
|
c.unsafeDeleteShipGroup(sgi)
|
|
}
|
|
|
|
for i := range raceGroups {
|
|
c.appendShipGroup(ri, &raceGroups[i])
|
|
}
|
|
}
|
|
|
|
func (c *Cache) shipGroupDismantle(ri int, groupIndex uuid.UUID) error {
|
|
c.validateRaceIndex(ri)
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
}
|
|
|
|
if state := c.ShipGroup(sgi).State(); state != game.StateInOrbit {
|
|
return e.NewShipsBusyError("state: %s", state)
|
|
}
|
|
|
|
pl, ok := c.Planet(c.ShipGroup(sgi).Destination)
|
|
if !ok {
|
|
return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
|
}
|
|
p := *pl
|
|
|
|
st := c.ShipGroupShipClass(sgi)
|
|
|
|
if c.ShipGroup(sgi).CargoType != nil {
|
|
ct := *c.ShipGroup(sgi).CargoType
|
|
load := c.ShipGroup(sgi).Load.F()
|
|
switch ct {
|
|
case game.CargoColonist:
|
|
if p.OwnedBy(c.g.Race[ri].ID) {
|
|
p = game.UnloadColonists(p, load)
|
|
}
|
|
case game.CargoMaterial:
|
|
p.Material = p.Material.Add(load)
|
|
case game.CargoCapital:
|
|
p.Capital = p.Capital.Add(load)
|
|
}
|
|
}
|
|
|
|
p.Material = p.Material.Add(c.ShipGroup(sgi).EmptyMass(st))
|
|
|
|
c.unsafeDeleteShipGroup(sgi)
|
|
|
|
c.g.Map.Planet[c.MustPlanetIndex(p.Number)] = p
|
|
|
|
return nil
|
|
}
|
|
|
|
// Корабль может нести только один тип груза одновременно.
|
|
// Возможные типы груза - это колонисты, сырье и промышленность.
|
|
// Груз может быть доставлен на борт корабля с Вашей или не занятой планеты, на которой он имеется.
|
|
// Указанное количество груза равномерно распределяется между всеми кораблями группы.
|
|
func (c *Cache) shipGroupLoad(ri int, groupID uuid.UUID, ct game.CargoType, quantity float64) error {
|
|
c.validateRaceIndex(ri)
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupID)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("group %s", groupID)
|
|
}
|
|
if state := c.ShipGroup(sgi).State(); state != game.StateInOrbit {
|
|
return e.NewShipsBusyError("state: %s", state)
|
|
}
|
|
p, ok := c.Planet(c.ShipGroup(sgi).Destination)
|
|
if !ok {
|
|
return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
|
}
|
|
|
|
if p.Owned() && !p.OwnedBy(c.g.Race[ri].ID) {
|
|
return e.NewEntityNotOwnedError("planet #%d", p.Number)
|
|
}
|
|
st := c.ShipGroupShipClass(sgi)
|
|
|
|
if st.Cargo < 1 {
|
|
return e.NewNoCargoBayError("ship_type %q", st.Name)
|
|
}
|
|
if c.ShipGroup(sgi).CargoType != nil && *c.ShipGroup(sgi).CargoType != ct {
|
|
return e.NewCargoLoadNotEqualError("cargo: %v", *c.ShipGroup(sgi).CargoType)
|
|
}
|
|
|
|
capacity := c.ShipGroup(sgi).CargoCapacity(st)
|
|
freeShipGroupCargoLoad := capacity - float64(c.ShipGroup(sgi).Load)
|
|
if freeShipGroupCargoLoad == 0 {
|
|
return e.NewCargoLoadNoSpaceLeftError()
|
|
}
|
|
var availableOnPlanet *game.Float
|
|
switch ct {
|
|
case game.CargoMaterial:
|
|
availableOnPlanet = &p.Material
|
|
case game.CargoCapital:
|
|
availableOnPlanet = &p.Capital
|
|
case game.CargoColonist:
|
|
availableOnPlanet = &p.Colonists
|
|
default:
|
|
return e.NewGameStateError("CargoType not accepted: %v", ct)
|
|
}
|
|
if quantity > float64(*availableOnPlanet) || *availableOnPlanet == 0 {
|
|
return e.NewCargoLoadNotEnoughError("planet: #%d, %s=%.03f", p.Number, ct, *availableOnPlanet)
|
|
}
|
|
toBeLoaded := quantity
|
|
if quantity == 0 {
|
|
toBeLoaded = float64(*availableOnPlanet)
|
|
}
|
|
if toBeLoaded > freeShipGroupCargoLoad {
|
|
toBeLoaded = freeShipGroupCargoLoad
|
|
}
|
|
*availableOnPlanet = (*availableOnPlanet).Add(-toBeLoaded)
|
|
c.ShipGroup(sgi).Load = c.ShipGroup(sgi).Load.Add(toBeLoaded)
|
|
if c.ShipGroup(sgi).Load > 0 {
|
|
c.ShipGroup(sgi).CargoType = &ct
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Промышленность и Сырье могут быть выгружены на любой планете.
|
|
// Колонисты могут быть высажены только на планеты, принадлежащие Вам или на необитаемые планеты.
|
|
func (c *Cache) shipGroupUnload(ri int, groupID uuid.UUID, quantity float64) error {
|
|
c.validateRaceIndex(ri)
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupID)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("group %s", groupID)
|
|
}
|
|
if state := c.ShipGroup(sgi).State(); state != game.StateInOrbit {
|
|
return e.NewShipsBusyError("state: %s", state)
|
|
}
|
|
st := c.ShipGroupShipClass(sgi)
|
|
if st.Cargo < 1 {
|
|
return e.NewNoCargoBayError("ship_type %q", st.Name)
|
|
}
|
|
if c.ShipGroup(sgi).CargoType == nil || c.ShipGroup(sgi).Load == 0 {
|
|
return e.NewCargoUnloadEmptyError()
|
|
}
|
|
|
|
ct := *c.ShipGroup(sgi).CargoType
|
|
p := c.MustPlanet(c.ShipGroup(sgi).Destination)
|
|
|
|
if ct == game.CargoColonist && p.Owned() && !p.OwnedBy(c.g.Race[ri].ID) {
|
|
return e.NewEntityNotOwnedError("planet #%d unload %v", p.Number, ct)
|
|
}
|
|
|
|
c.unsafeUnloadCargo(sgi, UnloadCargoRequest(float64(c.ShipGroup(sgi).Load), quantity))
|
|
return nil
|
|
}
|
|
|
|
func UnloadCargoRequest(load, quantity float64) float64 {
|
|
result := quantity
|
|
if result == 0 || result > load {
|
|
result = load
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (c *Cache) shipGroupIndexByID(id uuid.UUID) (int, bool) {
|
|
for sgi := range c.g.ShipGroups {
|
|
if c.g.ShipGroups[sgi].ID == id {
|
|
return sgi, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
func (c *Cache) unsafeUnloadCargo(sgi int, q float64) {
|
|
if q <= 0 {
|
|
return
|
|
}
|
|
if st := c.ShipGroup(sgi).State(); st != game.StateInOrbit {
|
|
panic(fmt.Sprintf("invalid group state: %v", st))
|
|
}
|
|
c.validateShipGroupIndex(sgi)
|
|
p := c.MustPlanet(c.ShipGroup(sgi).Destination)
|
|
ct := *c.ShipGroup(sgi).CargoType
|
|
|
|
var availableOnPlanet *game.Float
|
|
switch ct {
|
|
case game.CargoColonist:
|
|
availableOnPlanet = &p.Colonists
|
|
if !p.Owned() {
|
|
p.Own(c.ShipGroup(sgi).OwnerID)
|
|
p.Production = game.ProductionCapital.AsType(uuid.Nil)
|
|
}
|
|
case game.CargoMaterial:
|
|
availableOnPlanet = &p.Material
|
|
case game.CargoCapital:
|
|
availableOnPlanet = &p.Capital
|
|
}
|
|
*availableOnPlanet = (*availableOnPlanet).Add(q)
|
|
|
|
c.ShipGroup(sgi).Load = c.ShipGroup(sgi).Load.Add(-q)
|
|
if c.ShipGroup(sgi).Load == 0 {
|
|
c.ShipGroup(sgi).CargoType = nil
|
|
}
|
|
|
|
p.UnpackColonists()
|
|
p.UnpackCapital()
|
|
}
|
|
|
|
func (c *Cache) shipGroupTransfer(ri, riAccept int, groupID uuid.UUID) (err error) {
|
|
c.validateRaceIndex(ri)
|
|
if ri == riAccept {
|
|
return e.NewSameRaceError(c.g.Race[riAccept].Name)
|
|
}
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupID)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("group %s", groupID)
|
|
}
|
|
sg := c.ShipGroup(sgi)
|
|
state := sg.State()
|
|
if state == game.StateTransfer {
|
|
return e.NewShipsBusyError("state: %s", state)
|
|
}
|
|
|
|
st := c.ShipGroupShipClass(sgi)
|
|
|
|
var stAcc int
|
|
var name = st.Name
|
|
if stAcc = slices.IndexFunc(c.g.Race[riAccept].ShipTypes, func(v game.ShipType) bool { return v.Name == st.Name }); stAcc >= 0 &&
|
|
!st.Equal(c.g.Race[riAccept].ShipTypes[stAcc]) {
|
|
name = util.AppendRandomSuffix(name)
|
|
}
|
|
if stAcc < 0 || name != st.Name {
|
|
err = c.ShipClassCreate(riAccept,
|
|
name,
|
|
st.Drive.F(),
|
|
int(st.Armament),
|
|
st.Weapons.F(),
|
|
st.Shields.F(),
|
|
st.Cargo.F())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stAcc = len(c.g.Race[riAccept].ShipTypes) - 1
|
|
}
|
|
|
|
newGroup := *(sg)
|
|
newGroup.ID = uuid.New()
|
|
newGroup.TypeID = c.g.Race[riAccept].ShipTypes[stAcc].ID
|
|
newGroup.Tech = maps.Clone(sg.Tech)
|
|
if state == game.StateLaunched {
|
|
newGroup.StateTransfer = true
|
|
}
|
|
|
|
c.appendShipGroup(riAccept, &newGroup)
|
|
c.unsafeDeleteShipGroup(sgi)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Cache) ShipGroupBreak(ri int, groupID, newID uuid.UUID, quantity uint) (err error) {
|
|
c.validateRaceIndex(ri)
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupID)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("group %s", groupID)
|
|
}
|
|
for sgi := range c.g.ShipGroups {
|
|
if c.g.ShipGroups[sgi].ID == newID {
|
|
return e.NewEntityDuplicateIdentifierError("group %s", newID)
|
|
}
|
|
}
|
|
|
|
if state := c.ShipGroup(sgi).State(); state != game.StateInOrbit {
|
|
return e.NewShipsBusyError()
|
|
}
|
|
|
|
if c.ShipGroup(sgi).Number < quantity {
|
|
return e.NewBreakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
|
|
}
|
|
|
|
if quantity > 0 && quantity < c.ShipGroup(sgi).Number {
|
|
if sgi, err = c.breakGroup(ri, groupID, quantity); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
c.ShipGroup(sgi).FleetID = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Cache) breakGroup(ri int, groupID uuid.UUID, newGroupShips uint) (int, error) {
|
|
c.validateRaceIndex(ri)
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupID)
|
|
if !ok {
|
|
return -1, e.NewEntityNotExistsError("group %s", groupID)
|
|
}
|
|
if c.ShipGroup(sgi).Number < newGroupShips {
|
|
return -1, e.NewBreakGroupIllegalNumberError("group=%s ships: %d -> %d", c.ShipGroup(sgi).ID, c.ShipGroup(sgi).Number, newGroupShips)
|
|
}
|
|
return c.unsafeBreakGroup(ri, sgi, newGroupShips), nil
|
|
}
|
|
|
|
func (c *Cache) unsafeBreakGroup(ri, sgi int, newGroupShips uint) int {
|
|
newGroup := *c.ShipGroup(sgi)
|
|
if c.ShipGroup(sgi).CargoType != nil {
|
|
newGroup.Load = game.F(float64(c.ShipGroup(sgi).Load) / float64(c.ShipGroup(sgi).Number) * float64(newGroupShips))
|
|
}
|
|
newGroup.Number = newGroupShips
|
|
c.ShipGroupShipsNumber(sgi, c.ShipGroup(sgi).Number-newGroup.Number)
|
|
newGroup.FleetID = nil
|
|
return c.appendShipGroup(ri, &newGroup)
|
|
}
|
|
|
|
// Internal funcs
|
|
|
|
func (c *Cache) raceShipGroupIndex(ri int, id uuid.UUID) (int, bool) {
|
|
c.validateRaceIndex(ri)
|
|
for i := range c.ShipGroupsIndex() {
|
|
if c.ShipGroupOwnerRaceIndex(i) == ri && c.ShipGroup(i).ID == id {
|
|
return i, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
func (c *Cache) listShipGroupIdx(ri int) iter.Seq[int] {
|
|
c.validateRaceIndex(ri)
|
|
return func(yield func(int) bool) {
|
|
for i := range c.g.ShipGroups {
|
|
if ri == c.ShipGroupOwnerRaceIndex(i) {
|
|
if !yield(i) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Cache) listShipGroups(ri int) iter.Seq[*game.ShipGroup] {
|
|
c.validateRaceIndex(ri)
|
|
return func(yield func(*game.ShipGroup) bool) {
|
|
for sgi := range c.listShipGroupIdx(ri) {
|
|
if !yield(c.ShipGroup(sgi)) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup] {
|
|
return func(yield func(*game.ShipGroup) bool) {
|
|
result := make([]int, 0)
|
|
for sg := range c.g.ShipGroups {
|
|
// number checked for further sanity after battles
|
|
if c.g.ShipGroups[sg].Number > 0 && c.g.ShipGroups[sg].Destination == planetNumber && c.g.ShipGroups[sg].State() == game.StateUpgrade {
|
|
result = append(result, sg)
|
|
}
|
|
}
|
|
slices.SortFunc(result, func(a, b int) int {
|
|
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]]) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Cache) unsafeDeleteShipGroup(sgi int) {
|
|
c.validateShipGroupIndex(sgi)
|
|
|
|
sg := c.ShipGroup(sgi)
|
|
if sg.FleetID != nil {
|
|
fi := c.MustFleetIndex(*sg.FleetID)
|
|
fleetGroups := slices.Collect(c.fleetGroupIds(c.RaceIndex(sg.OwnerID), fi))
|
|
if len(fleetGroups) == 1 {
|
|
// remove fleet when deleting last group in the fleet
|
|
c.unsafeDeleteFleet(fi)
|
|
}
|
|
}
|
|
|
|
c.g.ShipGroups = append(c.g.ShipGroups[:sgi], c.g.ShipGroups[sgi+1:]...)
|
|
c.invalidateShipGroupCache()
|
|
}
|
|
|
|
func (c *Cache) validateShipGroupIndex(i int) {
|
|
if i >= len(c.g.ShipGroups) {
|
|
panic(fmt.Sprintf("group index out of range: %d >= %d", i, len(c.g.ShipGroups)))
|
|
}
|
|
}
|
|
|
|
func (c *Cache) unsafeCreateShips(ri int, classID uuid.UUID, planet uint, quantity uint) int {
|
|
st := c.MustShipType(ri, classID)
|
|
level := func(t game.Tech) float64 {
|
|
if t == game.TechDrive && st.DriveBlockMass() > 0 {
|
|
return util.Fixed3(c.g.Race[ri].TechLevel(game.TechDrive))
|
|
}
|
|
if t == game.TechWeapons && st.WeaponsBlockMass() > 0 {
|
|
return util.Fixed3(c.g.Race[ri].TechLevel(game.TechWeapons))
|
|
}
|
|
if t == game.TechShields && st.ShieldsBlockMass() > 0 {
|
|
return util.Fixed3(c.g.Race[ri].TechLevel(game.TechShields))
|
|
}
|
|
if t == game.TechCargo && st.CargoBlockMass() > 0 {
|
|
return util.Fixed3(c.g.Race[ri].TechLevel(game.TechCargo))
|
|
}
|
|
return 0
|
|
}
|
|
return c.appendShipGroup(ri, &game.ShipGroup{
|
|
OwnerID: c.g.Race[ri].ID,
|
|
TypeID: classID,
|
|
Destination: planet,
|
|
Number: uint(quantity),
|
|
Tech: map[game.Tech]game.Float{
|
|
game.TechDrive: game.F(level(game.TechDrive)),
|
|
game.TechWeapons: game.F(level(game.TechWeapons)),
|
|
game.TechShields: game.F(level(game.TechShields)),
|
|
game.TechCargo: game.F(level(game.TechCargo)),
|
|
},
|
|
})
|
|
}
|
|
|
|
func (c *Cache) appendShipGroup(ri int, sg *game.ShipGroup) int {
|
|
c.validateRaceIndex(ri)
|
|
sg.ID = uuid.New()
|
|
sg.OwnerID = c.g.Race[ri].ID
|
|
sg.FleetID = nil
|
|
c.g.ShipGroups = append(c.g.ShipGroups, *sg)
|
|
i := len(c.g.ShipGroups) - 1
|
|
c.invalidateShipGroupCache()
|
|
return i
|
|
}
|