643 lines
19 KiB
Go
643 lines
19 KiB
Go
package controller
|
|
|
|
import (
|
|
"fmt"
|
|
"iter"
|
|
"maps"
|
|
"slices"
|
|
|
|
"github.com/google/uuid"
|
|
e "github.com/iliadenisov/galaxy/internal/error"
|
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
|
)
|
|
|
|
func (c *Cache) CreateShips(ri int, shipTypeName string, planetNumber uint, quantity int) error {
|
|
class, _, ok := c.ShipClass(ri, shipTypeName)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("ship class %w", shipTypeName)
|
|
|
|
}
|
|
|
|
p, ok := c.Planet(planetNumber)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("planet #%d", planetNumber)
|
|
}
|
|
if p.Owner != c.g.Race[ri].ID {
|
|
return e.NewEntityNotOwnedError("planet #%d", planetNumber)
|
|
}
|
|
|
|
// FIXME: move maxindex to appendShipGroup
|
|
nextIndex := c.ShipGroupMaxIndex(ri) + 1
|
|
c.appendShipGroup(ri, class, &game.ShipGroup{
|
|
Index: nextIndex,
|
|
OwnerID: c.g.Race[ri].ID,
|
|
TypeID: class.ID,
|
|
Destination: p.Number,
|
|
Number: uint(quantity),
|
|
Tech: map[game.Tech]float64{
|
|
game.TechDrive: c.g.Race[ri].TechLevel(game.TechDrive),
|
|
game.TechWeapons: c.g.Race[ri].TechLevel(game.TechWeapons),
|
|
game.TechShields: c.g.Race[ri].TechLevel(game.TechShields),
|
|
game.TechCargo: c.g.Race[ri].TechLevel(game.TechCargo),
|
|
},
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// 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) ShipGroupFleet(groupIndex int, fID *uuid.UUID) {
|
|
c.validateShipGroupIndex(groupIndex)
|
|
c.g.ShipGroups[groupIndex].FleetID = fID
|
|
}
|
|
|
|
func (c *Cache) ShipGroupShipsNumber(groupIndex int, number uint) {
|
|
c.validateShipGroupIndex(groupIndex)
|
|
if c.g.ShipGroups[groupIndex].Number > 0 {
|
|
c.g.ShipGroups[groupIndex].Load = c.g.ShipGroups[groupIndex].Load / 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) ShipGroupMaxIndex(ri int) uint {
|
|
var max uint = 0
|
|
for i := range c.g.ShipGroups {
|
|
if r := c.ShipGroupOwnerRaceIndex(i); r == ri && c.ShipGroup(i).Index > max {
|
|
max = c.ShipGroup(i).Index
|
|
}
|
|
}
|
|
return max
|
|
}
|
|
|
|
func (c *Cache) ShipGroupOwnerRaceIndex(groupIndex int) int {
|
|
c.validateShipGroupIndex(groupIndex)
|
|
if len(c.raceIndexByShipGroupIndex) == 0 {
|
|
c.cacheShipsAndGroups()
|
|
}
|
|
if v, ok := c.raceIndexByShipGroupIndex[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) ShipGroupNumber(i int, n uint) {
|
|
c.validateShipGroupIndex(i)
|
|
c.g.ShipGroups[i].Number = n
|
|
}
|
|
|
|
func (c *Cache) DeleteShipGroup(i int) {
|
|
c.validateShipGroupIndex(i)
|
|
c.unsafeDeleteShipGroup(i)
|
|
}
|
|
|
|
func (c *Cache) DeleteKilledShipGroups() {
|
|
for i := len(c.g.ShipGroups) - 1; i >= 0; i-- {
|
|
if c.g.ShipGroups[i].Number == 0 {
|
|
c.unsafeDeleteShipGroup(i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Controller) JoinEqualGroups(raceName string) error {
|
|
ri, err := c.Cache.raceIndex(raceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Cache.JoinEqualGroups(ri)
|
|
return nil
|
|
}
|
|
|
|
func (c *Cache) CmdJoinEqualGroups() {
|
|
for i := range c.g.Race {
|
|
c.JoinEqualGroups(i)
|
|
}
|
|
}
|
|
|
|
func (c *Cache) JoinEqualGroups(ri int) {
|
|
c.validateRaceIndex(ri)
|
|
shipGroups := slices.Collect(c.listShipGroups(ri))
|
|
origin := len(shipGroups)
|
|
if origin < 2 {
|
|
return
|
|
}
|
|
for i := 0; i < len(shipGroups)-1; i++ {
|
|
for j := len(shipGroups) - 1; j > i; j-- {
|
|
if shipGroups[i].Equal(*shipGroups[j]) {
|
|
shipGroups[i].Index = maxUint(shipGroups[i].Index, shipGroups[j].Index)
|
|
shipGroups[i].Number += shipGroups[j].Number
|
|
shipGroups = append(shipGroups[:j], shipGroups[j+1:]...)
|
|
}
|
|
}
|
|
}
|
|
if len(shipGroups) == 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)
|
|
}
|
|
}
|
|
|
|
// c.g.ShipGroups = slices.DeleteFunc(c.g.ShipGroups, func(v game.ShipGroup) bool { return v.OwnerID == c.g.Race[ri].ID })
|
|
for _, idx := range toDelete {
|
|
c.unsafeDeleteShipGroup(idx)
|
|
}
|
|
|
|
// c.g.ShipGroups = append(c.g.ShipGroups, shipGroups...)
|
|
for i := range shipGroups {
|
|
c.g.ShipGroups = append(c.g.ShipGroups, *shipGroups[i])
|
|
}
|
|
|
|
c.invalidateShipGroupCache()
|
|
}
|
|
|
|
func (c *Controller) BreakGroup(raceName string, groupIndex, quantity uint) error {
|
|
ri, err := c.Cache.raceIndex(raceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.Cache.BreakGroup(ri, groupIndex, quantity)
|
|
}
|
|
|
|
func (c *Controller) DisassembleGroup(raceName string, groupIndex, quantity uint) error {
|
|
ri, err := c.Cache.raceIndex(raceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.Cache.DisassembleGroup(ri, groupIndex, quantity)
|
|
}
|
|
|
|
func (c *Cache) DisassembleGroup(ri int, groupIndex, quantity uint) error {
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
}
|
|
|
|
if c.ShipGroup(sgi).State() != game.StateInOrbit {
|
|
return e.NewShipsBusyError()
|
|
}
|
|
|
|
if c.ShipGroup(sgi).Number < quantity {
|
|
return e.NewBeakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
|
|
}
|
|
|
|
p, ok := c.Planet(c.ShipGroup(sgi).Destination)
|
|
if !ok {
|
|
return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
|
}
|
|
|
|
// pl := slices.IndexFunc(g.Map.Planet, func(p game.Planet) bool { return p.Number == c.ShipGroup(sgi).Destination })
|
|
// if pl < 0 {
|
|
// return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
|
// }
|
|
|
|
st := c.ShipGroupShipClass(sgi)
|
|
// var sti int
|
|
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.ID == c.ShipGroup(sgi).TypeID }); sti < 0 {
|
|
// // hard to test, need manual game data invalidation
|
|
// return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID)
|
|
// }
|
|
|
|
if quantity > 0 && quantity < c.ShipGroup(sgi).Number {
|
|
// make new group for disassembly
|
|
nsgi, err := c.breakGroupSafe(ri, groupIndex, quantity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sgi = nsgi
|
|
}
|
|
|
|
if c.ShipGroup(sgi).CargoType != nil {
|
|
ct := *c.ShipGroup(sgi).CargoType
|
|
load := c.ShipGroup(sgi).Load
|
|
switch ct {
|
|
case game.CargoColonist:
|
|
if p.Owner == c.g.Race[ri].ID {
|
|
pn := UnloadColonists(*p, load)
|
|
p = &pn
|
|
}
|
|
case game.CargoMaterial:
|
|
p.Material += load
|
|
case game.CargoCapital:
|
|
p.Capital += load
|
|
}
|
|
}
|
|
|
|
p.Material += c.ShipGroup(sgi).EmptyMass(st)
|
|
|
|
// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...)
|
|
c.unsafeDeleteShipGroup(sgi)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) LoadCargo(raceName string, groupIndex uint, cargoType string, ships uint, quantity float64) error {
|
|
ri, err := c.Cache.raceIndex(raceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ct, ok := game.CargoTypeSet[cargoType]
|
|
if !ok {
|
|
return e.NewCargoTypeInvalidError(cargoType)
|
|
}
|
|
return c.Cache.LoadCargo(ri, groupIndex, ct, ships, quantity)
|
|
}
|
|
|
|
// Корабль может нести только один тип груза одновременно.
|
|
// Возможные типы груза - это колонисты, сырье и промышленность.
|
|
// Груз может быть доставлен на борт корабля с Вашей или не занятой планеты, на которой он имеется.
|
|
func (c *Cache) LoadCargo(ri int, groupIndex uint, ct game.CargoType, ships uint, quantity float64) error {
|
|
if ships == 0 && quantity > 0 {
|
|
return e.NewCargoQuantityWithoutGroupBreakError()
|
|
}
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
}
|
|
if c.ShipGroup(sgi).State() != game.StateInOrbit {
|
|
return e.NewShipsBusyError()
|
|
}
|
|
p, ok := c.Planet(c.ShipGroup(sgi).Destination)
|
|
if !ok {
|
|
return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
|
}
|
|
|
|
// pl := slices.IndexFunc(g.Map.Planet, func(p game.Planet) bool { return p.Number == c.ShipGroup(sgi).Destination })
|
|
// if pl < 0 {
|
|
// return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
|
// }
|
|
if p.Owner != uuid.Nil && p.Owner != c.g.Race[ri].ID {
|
|
return e.NewEntityNotOwnedError("planet #%d", p.Number)
|
|
}
|
|
st := c.ShipGroupShipClass(sgi)
|
|
// var sti int
|
|
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.ID == c.ShipGroup(sgi).TypeID }); sti < 0 {
|
|
// // hard to test, need manual game data invalidation
|
|
// return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID)
|
|
// }
|
|
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)
|
|
}
|
|
if ships > 0 && ships < c.ShipGroup(sgi).Number {
|
|
nsgi, err := c.breakGroupSafe(ri, groupIndex, ships)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sgi = nsgi
|
|
}
|
|
capacity := c.ShipGroup(sgi).CargoCapacity(st)
|
|
freeShipGroupCargoLoad := capacity - c.ShipGroup(sgi).Load
|
|
if freeShipGroupCargoLoad == 0 {
|
|
return e.NewCargoLoadNoSpaceLeftError()
|
|
}
|
|
var availableOnPlanet *float64
|
|
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 > *availableOnPlanet || *availableOnPlanet == 0 {
|
|
return e.NewCargoLoadNotEnoughError("planet: #%d, %s=%.03f", p.Number, ct, *availableOnPlanet)
|
|
}
|
|
toBeLoaded := quantity
|
|
if quantity == 0 {
|
|
toBeLoaded = *availableOnPlanet
|
|
}
|
|
if toBeLoaded > freeShipGroupCargoLoad {
|
|
toBeLoaded = freeShipGroupCargoLoad
|
|
}
|
|
*availableOnPlanet = *availableOnPlanet - toBeLoaded
|
|
c.ShipGroup(sgi).Load += toBeLoaded
|
|
if c.ShipGroup(sgi).Load > 0 {
|
|
c.ShipGroup(sgi).CargoType = &ct
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) UnloadCargo(raceName string, groupIndex uint, ships uint, quantity float64) error {
|
|
ri, err := c.Cache.raceIndex(raceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.Cache.UnloadCargo(ri, groupIndex, ships, quantity)
|
|
}
|
|
|
|
// Промышленность и Сырье могут быть выгружены на любой планете.
|
|
// Колонисты могут быть высажены только на планеты, принадлежащие Вам или на необитаемые планеты.
|
|
func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float64) error {
|
|
c.validateRaceIndex(ri)
|
|
if ships == 0 && quantity > 0 {
|
|
return e.NewCargoQuantityWithoutGroupBreakError()
|
|
}
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
}
|
|
if c.ShipGroup(sgi).State() != game.StateInOrbit {
|
|
return e.NewShipsBusyError()
|
|
}
|
|
st := c.ShipGroupShipClass(sgi)
|
|
// var sti int
|
|
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.ID == c.ShipGroup(sgi).TypeID }); sti < 0 {
|
|
// // hard to test, need manual game data invalidation
|
|
// return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID)
|
|
// }
|
|
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, ok := c.Planet(c.ShipGroup(sgi).Destination)
|
|
if !ok {
|
|
return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
|
}
|
|
// pl := slices.IndexFunc(g.Map.Planet, func(p game.Planet) bool { return p.Number == c.ShipGroup(sgi).Destination })
|
|
// if pl < 0 {
|
|
// return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination)
|
|
// }
|
|
if ct == game.CargoColonist {
|
|
if p.Owner != uuid.Nil && p.Owner != c.g.Race[ri].ID {
|
|
return e.NewEntityNotOwnedError("planet #%d unload %v", p.Number, ct)
|
|
}
|
|
if p.Owner == uuid.Nil {
|
|
p.Owner = c.g.Race[ri].ID
|
|
}
|
|
}
|
|
var availableOnPlanet *float64
|
|
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 ships > 0 && ships < c.ShipGroup(sgi).Number {
|
|
nsgi, err := c.breakGroupSafe(ri, groupIndex, ships)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sgi = nsgi
|
|
}
|
|
toBeUnloaded := quantity
|
|
if quantity == 0 {
|
|
toBeUnloaded = c.ShipGroup(sgi).Load
|
|
}
|
|
if toBeUnloaded > c.ShipGroup(sgi).Load {
|
|
return e.NewCargoUnoadNotEnoughError("load: %.03f", c.ShipGroup(sgi).Load)
|
|
}
|
|
*availableOnPlanet += toBeUnloaded
|
|
c.ShipGroup(sgi).Load -= toBeUnloaded
|
|
if c.ShipGroup(sgi).Load == 0 {
|
|
c.ShipGroup(sgi).CargoType = nil
|
|
}
|
|
return nil
|
|
}
|
|
func (c *Controller) GiveawayGroup(raceName, raceAcceptor string, groupIndex, quantity uint) error {
|
|
ri, err := c.Cache.raceIndex(raceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
riAccept, err := c.Cache.raceIndex(raceAcceptor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.Cache.GiveawayGroup(ri, riAccept, groupIndex, quantity)
|
|
}
|
|
|
|
func (c *Cache) GiveawayGroup(ri, riAccept int, groupIndex, quantity uint) (err error) {
|
|
if ri == riAccept {
|
|
return e.NewSameRaceError(c.g.Race[riAccept].Name)
|
|
}
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
}
|
|
if c.ShipGroup(sgi).Number < quantity {
|
|
return e.NewBeakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
|
|
}
|
|
|
|
st := c.ShipGroupShipClass(sgi)
|
|
// var sti int
|
|
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == c.ShipGroup(sgi).TypeID }); sti < 0 {
|
|
// // hard to test, need manual game data invalidation
|
|
// return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID)
|
|
// }
|
|
|
|
var stAcc int
|
|
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]) {
|
|
return e.NewGiveawayGroupShipsTypeNotEqualError("race %w, ship type %w", c.g.Race[riAccept].Name, c.g.Race[riAccept].ShipTypes[stAcc].Name)
|
|
}
|
|
if stAcc < 0 {
|
|
err = c.CreateShipType(riAccept,
|
|
st.Name,
|
|
st.Drive,
|
|
int(st.Armament),
|
|
st.Weapons,
|
|
st.Shields,
|
|
st.Cargo)
|
|
stAcc = len(c.g.Race[ri].ShipTypes) - 1
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// maxIndex := c.ShipGroupMaxIndex(riAccept)
|
|
// var maxIndex uint
|
|
// for sg := range g.listShipGroups(riAccept) {
|
|
// if sg.Index > maxIndex {
|
|
// maxIndex = sg.Index
|
|
// }
|
|
// }
|
|
|
|
c.appendShipGroup(ri, st, &game.ShipGroup{
|
|
// Index: maxIndex + 1,
|
|
OwnerID: c.g.Race[riAccept].ID,
|
|
TypeID: c.g.Race[riAccept].ShipTypes[stAcc].ID,
|
|
Number: uint(quantity),
|
|
|
|
CargoType: c.ShipGroup(sgi).CargoType,
|
|
Load: c.ShipGroup(sgi).Load,
|
|
|
|
Tech: maps.Clone(c.ShipGroup(sgi).Tech),
|
|
|
|
Destination: c.ShipGroup(sgi).Destination,
|
|
StateInSpace: c.ShipGroup(sgi).StateInSpace,
|
|
StateUpgrade: c.ShipGroup(sgi).StateUpgrade,
|
|
})
|
|
|
|
// g.ShipGroups = append(g.ShipGroups, game.ShipGroup{
|
|
// Index: maxIndex + 1,
|
|
// OwnerID: g.Race[riAccept].ID,
|
|
// TypeID: g.Race[riAccept].ShipTypes[stAcc].ID,
|
|
// Number: uint(quantity),
|
|
|
|
// CargoType: c.ShipGroup(sgi).CargoType,
|
|
// Load: c.ShipGroup(sgi).Load,
|
|
|
|
// Tech: maps.Clone(c.ShipGroup(sgi).Tech),
|
|
|
|
// Destination: c.ShipGroup(sgi).Destination,
|
|
// StateInSpace: c.ShipGroup(sgi).StateInSpace,
|
|
// StateUpgrade: c.ShipGroup(sgi).StateUpgrade,
|
|
// })
|
|
|
|
if quantity == 0 || quantity == c.ShipGroup(sgi).Number {
|
|
c.unsafeDeleteShipGroup(sgi)
|
|
// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...)
|
|
} else {
|
|
c.ShipGroup(sgi).Number -= quantity
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Cache) BreakGroup(ri int, groupIndex, quantity uint) error {
|
|
c.validateRaceIndex(ri)
|
|
sgi := -1
|
|
for i := range c.ShipGroupsIndex() {
|
|
if c.ShipGroupOwnerRaceIndex(i) == ri && c.ShipGroup(i).Index == groupIndex {
|
|
sgi = i
|
|
break
|
|
}
|
|
}
|
|
if sgi < 0 {
|
|
return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
}
|
|
|
|
if c.ShipGroup(sgi).State() != game.StateInOrbit {
|
|
return e.NewShipsBusyError()
|
|
}
|
|
|
|
if c.ShipGroup(sgi).Number < quantity {
|
|
return e.NewBeakGroupNumberNotEnoughError("%d<%d", c.ShipGroup(sgi).Number, quantity)
|
|
}
|
|
|
|
if quantity == 0 || quantity == c.ShipGroup(sgi).Number {
|
|
c.ShipGroupFleet(sgi, nil)
|
|
} else {
|
|
if _, err := c.breakGroupSafe(ri, groupIndex, quantity); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Cache) breakGroupSafe(ri int, groupIndex uint, newGroupShips uint) (int, error) {
|
|
c.validateRaceIndex(ri)
|
|
sgi, ok := c.raceShipGroupIndex(ri, groupIndex)
|
|
if !ok {
|
|
return -1, e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
}
|
|
// maxIndex := c.ShipGroupMaxIndex(ri)
|
|
if c.ShipGroup(sgi).Number < newGroupShips {
|
|
return -1, e.NewBreakGroupIllegalNumberError("group #%d ships: %d -> %d", c.ShipGroup(sgi).Index, c.ShipGroup(sgi).Number, newGroupShips)
|
|
}
|
|
newGroup := *c.ShipGroup(sgi)
|
|
if c.ShipGroup(sgi).CargoType != nil {
|
|
newGroup.Load = c.ShipGroup(sgi).Load / float64(c.ShipGroup(sgi).Number) * float64(newGroupShips)
|
|
// c.ShipGroup(sgi).Load -= newGroup.Load
|
|
}
|
|
newGroup.Number = newGroupShips
|
|
c.ShipGroupShipsNumber(sgi, c.ShipGroup(sgi).Number-newGroup.Number)
|
|
// c.ShipGroup(sgi).Number -= newGroup.Number
|
|
// newGroup.Index = maxIndex + 1
|
|
newGroup.FleetID = nil
|
|
st := c.ShipGroupShipClass(sgi)
|
|
// c.g.ShipGroups = append(c.g.ShipGroups, newGroup)
|
|
return c.appendShipGroup(ri, st, &newGroup), nil
|
|
}
|
|
|
|
// Internal funcs
|
|
|
|
func (c *Cache) appendShipGroup(ri int, class *game.ShipType, sg *game.ShipGroup) int {
|
|
c.validateRaceIndex(ri)
|
|
sg.Index = c.ShipGroupMaxIndex(ri) + 1
|
|
c.g.ShipGroups = append(c.g.ShipGroups, *sg)
|
|
i := len(c.g.ShipGroups) - 1
|
|
c.cacheShipGroup(i, ri, class)
|
|
return i
|
|
}
|
|
|
|
func (c *Cache) raceShipGroupIndex(ri int, index uint) (int, bool) {
|
|
c.validateRaceIndex(ri)
|
|
for i := range c.ShipGroupsIndex() {
|
|
if c.ShipGroupOwnerRaceIndex(i) == ri && c.ShipGroup(i).Index == index {
|
|
return i, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
func (c *Cache) listShipGroups(ri int) iter.Seq[*game.ShipGroup] {
|
|
c.validateRaceIndex(ri)
|
|
return func(yield func(*game.ShipGroup) bool) {
|
|
for i := range c.g.ShipGroups {
|
|
if ri == c.ShipGroupOwnerRaceIndex(i) {
|
|
if !yield(&c.g.ShipGroups[i]) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Cache) shipGroupsInUpgrade(planetNumber uint) iter.Seq[*game.ShipGroup] {
|
|
return func(yield func(*game.ShipGroup) bool) {
|
|
for sg := range c.g.ShipGroups {
|
|
if c.g.ShipGroups[sg].Destination == planetNumber && c.g.ShipGroups[sg].State() == game.StateUpgrade {
|
|
if !yield(&c.g.ShipGroups[sg]) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Cache) unsafeDeleteShipGroup(i int) {
|
|
c.g.ShipGroups = append(c.g.ShipGroups[:i], c.g.ShipGroups[i+1:]...)
|
|
delete(c.raceIndexByShipGroupIndex, i)
|
|
delete(c.shipClassByShipGroupIndex, i)
|
|
}
|
|
|
|
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)))
|
|
}
|
|
}
|