746 lines
24 KiB
Go
746 lines
24 KiB
Go
package game
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/iliadenisov/galaxy/internal/number"
|
|
)
|
|
|
|
type CargoType string
|
|
|
|
const (
|
|
CargoColonist CargoType = "COL" // Колонисты
|
|
CargoMaterial CargoType = "MAT" // Сырьё
|
|
CargoCapital CargoType = "CAP" // Промышленность
|
|
)
|
|
|
|
var (
|
|
CargoTypeSet map[string]CargoType = map[string]CargoType{
|
|
CargoColonist.String(): CargoColonist,
|
|
CargoMaterial.String(): CargoMaterial,
|
|
CargoCapital.String(): CargoCapital,
|
|
}
|
|
)
|
|
|
|
func (ct CargoType) Ref() *CargoType {
|
|
return &ct
|
|
}
|
|
|
|
func (ct CargoType) String() string {
|
|
return string(ct)
|
|
}
|
|
|
|
type ShipGroupState string
|
|
|
|
const (
|
|
StateInOrbit ShipGroupState = "In_Orbit"
|
|
StateLaunched ShipGroupState = "Launched"
|
|
StateInSpace ShipGroupState = "In_Space"
|
|
StateUpgrade ShipGroupState = "Upgrade"
|
|
StateTransfer ShipGroupState = "Transfer_Status"
|
|
)
|
|
|
|
type InSpace struct {
|
|
Origin uint `json:"origin"`
|
|
X float64 `json:"x"`
|
|
Y float64 `json:"y"`
|
|
// zero is for Launched status
|
|
// TODO: calculate range dynamically
|
|
Range float64 `json:"range"`
|
|
}
|
|
|
|
func (is InSpace) Equal(other InSpace) bool {
|
|
return is.Origin == other.Origin && is.X == other.X && is.Y == other.Y
|
|
}
|
|
|
|
func (is InSpace) Launched() bool {
|
|
return is.Range == 0
|
|
}
|
|
|
|
type InUpgrade struct {
|
|
UpgradeTech []UpgradePreference `json:"preference"`
|
|
}
|
|
|
|
func (iu InUpgrade) Cost() float64 {
|
|
var sum float64
|
|
for i := range iu.UpgradeTech {
|
|
sum += iu.UpgradeTech[i].Cost
|
|
}
|
|
return sum
|
|
}
|
|
|
|
func (iu InUpgrade) TechCost(t Tech) float64 {
|
|
for i := range iu.UpgradeTech {
|
|
if iu.UpgradeTech[i].Tech == t {
|
|
return iu.UpgradeTech[i].Cost
|
|
}
|
|
}
|
|
return 0.
|
|
}
|
|
|
|
type UpgradePreference struct {
|
|
Tech Tech `json:"tech"`
|
|
Level float64 `json:"level"`
|
|
Cost float64 `json:"cost"`
|
|
}
|
|
|
|
type Tech string
|
|
|
|
const (
|
|
TechAll Tech = "ALL"
|
|
TechDrive Tech = "DRIVE"
|
|
TechWeapons Tech = "WEAPONS"
|
|
TechShields Tech = "SHIELDS"
|
|
TechCargo Tech = "CARGO"
|
|
)
|
|
|
|
func (t Tech) String() string {
|
|
return string(t)
|
|
}
|
|
|
|
type ShipGroup struct {
|
|
Index uint `json:"index"` // FIXME: use UUID for Group Index (ordered)
|
|
OwnerID uuid.UUID `json:"ownerId"` // Race link
|
|
TypeID uuid.UUID `json:"typeId"` // ShipType link
|
|
FleetID *uuid.UUID `json:"fleetId,omitempty"` // Fleet link
|
|
Number uint `json:"number"` // Number (quantity) ships of specific ShipType
|
|
|
|
CargoType *CargoType `json:"loadType,omitempty"`
|
|
Load float64 `json:"load"` // Cargo loaded - "Масса груза"
|
|
|
|
Tech TechSet `json:"tech"`
|
|
|
|
// TODO: TEST: Destination, Origin, Range
|
|
Destination uint `json:"destination"`
|
|
StateInSpace *InSpace `json:"stateInSpace,omitempty"`
|
|
StateUpgrade *InUpgrade `json:"stateUpgrade,omitempty"`
|
|
}
|
|
|
|
func (sg ShipGroup) TechLevel(t Tech) float64 {
|
|
return sg.Tech.Value(t)
|
|
}
|
|
|
|
// TODO: refactor to separate method with *ShipGroup as parameter
|
|
func (sg *ShipGroup) SetTechLevel(t Tech, v float64) {
|
|
sg.Tech = sg.Tech.Set(t, v)
|
|
}
|
|
|
|
func (sg ShipGroup) State() ShipGroupState {
|
|
switch {
|
|
case sg.StateInSpace == nil && sg.StateUpgrade == nil:
|
|
return StateInOrbit
|
|
case sg.StateInSpace != nil && sg.StateUpgrade == nil:
|
|
if sg.StateInSpace.Range > 0 {
|
|
return StateInSpace
|
|
}
|
|
return StateLaunched
|
|
case sg.StateUpgrade != nil && sg.StateInSpace == nil:
|
|
return StateUpgrade
|
|
default:
|
|
panic(fmt.Sprintf("ambigous group state: in_space=%#v upgrage=%#v", sg.StateInSpace, sg.StateUpgrade))
|
|
}
|
|
}
|
|
|
|
func (sg ShipGroup) OnPlanet() (uint, bool) {
|
|
switch sg.State() {
|
|
case StateInOrbit:
|
|
return sg.Destination, true
|
|
case StateLaunched:
|
|
return sg.StateInSpace.Origin, true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func (sg ShipGroup) Equal(other ShipGroup) bool {
|
|
return sg.OwnerID == other.OwnerID &&
|
|
sg.TypeID == other.TypeID &&
|
|
sg.FleetID == other.FleetID &&
|
|
sg.TechLevel(TechDrive) == other.TechLevel(TechDrive) &&
|
|
sg.TechLevel(TechWeapons) == other.TechLevel(TechWeapons) &&
|
|
sg.TechLevel(TechShields) == other.TechLevel(TechShields) &&
|
|
sg.TechLevel(TechCargo) == other.TechLevel(TechCargo) &&
|
|
sg.CargoType == other.CargoType &&
|
|
sg.Load/float64(sg.Number) == other.Load/float64(other.Number) &&
|
|
sg.State() == other.State()
|
|
}
|
|
|
|
// Грузоподъёмность
|
|
func (sg ShipGroup) CargoCapacity(st *ShipType) float64 {
|
|
return sg.TechLevel(TechCargo) * (st.Cargo + (st.Cargo*st.Cargo)/20) * float64(sg.Number)
|
|
}
|
|
|
|
// Масса перевозимого груза -
|
|
// общее количество единиц груза, деленное на технологический уровень Грузоперевозок
|
|
func (sg ShipGroup) CarryingMass() float64 {
|
|
return sg.Load / sg.TechLevel(TechCargo)
|
|
}
|
|
|
|
// Масса группы без учёта груза
|
|
func (sg ShipGroup) EmptyMass(st *ShipType) float64 {
|
|
return st.EmptyMass() * float64(sg.Number)
|
|
}
|
|
|
|
// Полная масса -
|
|
// массу корабля самого по себе плюс масса перевозимого груза
|
|
func (sg ShipGroup) FullMass(st *ShipType) float64 {
|
|
return sg.EmptyMass(st) + sg.CarryingMass()
|
|
}
|
|
|
|
// Эффективность двигателя -
|
|
// равна мощности Двигателей, умноженной на технологический уровень блока Двигателей
|
|
func (sg ShipGroup) DriveEffective(st *ShipType) float64 {
|
|
return st.Drive * sg.TechLevel(TechDrive)
|
|
}
|
|
|
|
// Корабли перемещаются за один ход на количество световых лет, равное
|
|
// эффективности двигателя, умноженной на 20 и деленной на "Полную массу" корабля
|
|
func (sg ShipGroup) Speed(st *ShipType) float64 {
|
|
return sg.DriveEffective(st) * 20 / sg.FullMass(st)
|
|
}
|
|
|
|
func (sg ShipGroup) UpgradeDriveCost(st *ShipType, drive float64) float64 {
|
|
return (1 - sg.TechLevel(TechDrive)/drive) * 10 * st.Drive
|
|
}
|
|
|
|
// TODO: test on other values
|
|
func (sg ShipGroup) UpgradeWeaponsCost(st *ShipType, weapons float64) float64 {
|
|
return (1 - sg.TechLevel(TechWeapons)/weapons) * 10 * st.WeaponsBlockMass()
|
|
}
|
|
|
|
func (sg ShipGroup) UpgradeShieldsCost(st *ShipType, shields float64) float64 {
|
|
return (1 - sg.TechLevel(TechShields)/shields) * 10 * st.Shields
|
|
}
|
|
|
|
func (sg ShipGroup) UpgradeCargoCost(st *ShipType, cargo float64) float64 {
|
|
return (1 - sg.TechLevel(TechCargo)/cargo) * 10 * st.Cargo
|
|
}
|
|
|
|
// Мощность бомбардировки
|
|
// TODO: maybe rounding must be done only for display?
|
|
func (sg ShipGroup) BombingPower(st *ShipType) float64 {
|
|
// return math.Sqrt(sg.Type.Weapons * sg.Weapons)
|
|
result := (math.Sqrt(st.Weapons*sg.TechLevel(TechWeapons))/10. + 1.) *
|
|
st.Weapons *
|
|
sg.TechLevel(TechWeapons) *
|
|
float64(st.Armament) *
|
|
float64(sg.Number)
|
|
return number.Fixed3(result)
|
|
}
|
|
|
|
// JoinEqualGroups iterates over all races and joins their respective equal ship groups.
|
|
// Used in turn production.
|
|
// func JoinEqualGroups(g *Game) {
|
|
// // for i := range g.Race {
|
|
// // g.joinEqualGroupsInternal(i)
|
|
// // }
|
|
// }
|
|
|
|
// func (g *Game) JoinEqualGroups(raceName string) error {
|
|
// ri, err := g.raceIndex(raceName)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// g.joinEqualGroupsInternal(ri)
|
|
// return nil
|
|
// }
|
|
|
|
// func (g *Game) joinEqualGroupsInternal(ri int) {
|
|
// shipGroups := slices.Collect(maps.Values(maps.Collect(g.listIndexShipGroups(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
|
|
// }
|
|
// g.ShipGroups = slices.DeleteFunc(g.ShipGroups, func(v ShipGroup) bool { return v.OwnerID == g.Race[ri].ID })
|
|
// g.ShipGroups = append(g.ShipGroups, shipGroups...)
|
|
// }
|
|
|
|
// func (g *Game) BreakGroup(raceName string, groupIndex, quantity uint) error {
|
|
// ri, err := g.raceIndex(raceName)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// return g.breakGroupInternal(ri, groupIndex, quantity)
|
|
// }
|
|
|
|
// func (g *Game) DisassembleGroup(raceName string, groupIndex, quantity uint) error {
|
|
// ri, err := g.raceIndex(raceName)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// return g.disassembleGroupInternal(ri, groupIndex, quantity)
|
|
// }
|
|
|
|
// func (g *Game) disassembleGroupInternal(ri int, groupIndex, quantity uint) error {
|
|
// sgi := -1
|
|
// var maxIndex uint
|
|
// for i, sg := range g.listIndexShipGroups(ri) {
|
|
// if sgi < 0 && sg.Index == groupIndex {
|
|
// sgi = i
|
|
// }
|
|
// if sg.Index > maxIndex {
|
|
// maxIndex = sg.Index
|
|
// }
|
|
// }
|
|
// if sgi < 0 {
|
|
// return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
// }
|
|
|
|
// if g.ShipGroups[sgi].State() != StateInOrbit {
|
|
// return e.NewShipsBusyError()
|
|
// }
|
|
|
|
// if g.ShipGroups[sgi].Number < quantity {
|
|
// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
|
|
// }
|
|
|
|
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
|
|
// if pl < 0 {
|
|
// return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination)
|
|
// }
|
|
// var sti int
|
|
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
|
|
// // hard to test, need manual game data invalidation
|
|
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
|
|
// }
|
|
|
|
// if quantity > 0 && quantity < g.ShipGroups[sgi].Number {
|
|
// // make new group for disassembly
|
|
// nsgi, err := g.breakGroupSafe(ri, groupIndex, quantity)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// sgi = nsgi
|
|
// }
|
|
|
|
// if g.ShipGroups[sgi].CargoType != nil {
|
|
// ct := *g.ShipGroups[sgi].CargoType
|
|
// load := g.ShipGroups[sgi].Load
|
|
// switch ct {
|
|
// case CargoColonist:
|
|
// if g.Map.Planet[pl].Owner == g.Race[ri].ID {
|
|
// g.Map.Planet[pl] = UnloadColonists(g.Map.Planet[pl], load)
|
|
// }
|
|
// case CargoMaterial:
|
|
// g.Map.Planet[pl].Material += load
|
|
// case CargoCapital:
|
|
// g.Map.Planet[pl].Capital += load
|
|
// }
|
|
// }
|
|
|
|
// g.Map.Planet[pl].Material += g.ShipGroups[sgi].EmptyMass(&g.Race[ri].ShipTypes[sti])
|
|
|
|
// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...)
|
|
|
|
// return nil
|
|
// }
|
|
|
|
// func (g *Game) LoadCargo(raceName string, groupIndex uint, cargoType string, ships uint, quantity float64) error {
|
|
// ri, err := g.raceIndex(raceName)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// ct, ok := CargoTypeSet[cargoType]
|
|
// if !ok {
|
|
// return e.NewCargoTypeInvalidError(cargoType)
|
|
// }
|
|
// return g.loadCargoInternal(ri, groupIndex, ct, ships, quantity)
|
|
// }
|
|
|
|
// func (g *Game) UnloadCargo(raceName string, groupIndex uint, ships uint, quantity float64) error {
|
|
// ri, err := g.raceIndex(raceName)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// return g.unloadCargoInternal(ri, groupIndex, ships, quantity)
|
|
// }
|
|
|
|
// // Промышленность и Сырье могут быть выгружены на любой планете.
|
|
// // Колонисты могут быть высажены только на планеты, принадлежащие Вам или на необитаемые планеты.
|
|
// func (g *Game) unloadCargoInternal(ri int, groupIndex uint, ships uint, quantity float64) error {
|
|
// if ships == 0 && quantity > 0 {
|
|
// return e.NewCargoQuantityWithoutGroupBreakError()
|
|
// }
|
|
// sgi := -1
|
|
// for i, sg := range g.listIndexShipGroups(ri) {
|
|
// if sgi < 0 && sg.Index == groupIndex {
|
|
// sgi = i
|
|
// }
|
|
// }
|
|
// if sgi < 0 {
|
|
// return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
// }
|
|
// if g.ShipGroups[sgi].State() != StateInOrbit {
|
|
// return e.NewShipsBusyError()
|
|
// }
|
|
// var sti int
|
|
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
|
|
// // hard to test, need manual game data invalidation
|
|
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
|
|
// }
|
|
// if g.Race[ri].ShipTypes[sti].Cargo < 1 {
|
|
// return e.NewNoCargoBayError("ship_type %q", g.Race[ri].ShipTypes[sti].Name)
|
|
// }
|
|
// if g.ShipGroups[sgi].CargoType == nil || g.ShipGroups[sgi].Load == 0 {
|
|
// return e.NewCargoUnloadEmptyError()
|
|
// }
|
|
// ct := *g.ShipGroups[sgi].CargoType
|
|
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
|
|
// if pl < 0 {
|
|
// return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination)
|
|
// }
|
|
// if ct == CargoColonist {
|
|
// if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID {
|
|
// return e.NewEntityNotOwnedError("planet #%d unload %v", g.Map.Planet[pl].Number, ct)
|
|
// }
|
|
// if g.Map.Planet[pl].Owner == uuid.Nil {
|
|
// g.Map.Planet[pl].Owner = g.Race[ri].ID
|
|
// }
|
|
// }
|
|
// var availableOnPlanet *float64
|
|
// switch ct {
|
|
// case CargoMaterial:
|
|
// availableOnPlanet = &g.Map.Planet[pl].Material
|
|
// case CargoCapital:
|
|
// availableOnPlanet = &g.Map.Planet[pl].Capital
|
|
// case CargoColonist:
|
|
// availableOnPlanet = &g.Map.Planet[pl].Colonists
|
|
// default:
|
|
// return e.NewGameStateError("CargoType not accepted: %v", ct)
|
|
// }
|
|
// if ships > 0 && ships < g.ShipGroups[sgi].Number {
|
|
// nsgi, err := g.breakGroupSafe(ri, groupIndex, ships)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// sgi = nsgi
|
|
// }
|
|
// toBeUnloaded := quantity
|
|
// if quantity == 0 {
|
|
// toBeUnloaded = g.ShipGroups[sgi].Load
|
|
// }
|
|
// if toBeUnloaded > g.ShipGroups[sgi].Load {
|
|
// return e.NewCargoUnoadNotEnoughError("load: %.03f", g.ShipGroups[sgi].Load)
|
|
// }
|
|
// *availableOnPlanet += toBeUnloaded
|
|
// g.ShipGroups[sgi].Load -= toBeUnloaded
|
|
// if g.ShipGroups[sgi].Load == 0 {
|
|
// g.ShipGroups[sgi].CargoType = nil
|
|
// }
|
|
// return nil
|
|
// }
|
|
|
|
// // Корабль может нести только один тип груза одновременно.
|
|
// // Возможные типы груза - это колонисты, сырье и промышленность.
|
|
// // Груз может быть доставлен на борт корабля с Вашей или не занятой планеты, на которой он имеется.
|
|
// func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, ships uint, quantity float64) error {
|
|
// if ships == 0 && quantity > 0 {
|
|
// return e.NewCargoQuantityWithoutGroupBreakError()
|
|
// }
|
|
// sgi := -1
|
|
// for i, sg := range g.listIndexShipGroups(ri) {
|
|
// if sgi < 0 && sg.Index == groupIndex {
|
|
// sgi = i
|
|
// }
|
|
// }
|
|
// if sgi < 0 {
|
|
// return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
// }
|
|
// if g.ShipGroups[sgi].State() != StateInOrbit {
|
|
// return e.NewShipsBusyError()
|
|
// }
|
|
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination })
|
|
// if pl < 0 {
|
|
// return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination)
|
|
// }
|
|
// if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID {
|
|
// return e.NewEntityNotOwnedError("planet #%d", g.Map.Planet[pl].Number)
|
|
// }
|
|
// var sti int
|
|
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
|
|
// // hard to test, need manual game data invalidation
|
|
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
|
|
// }
|
|
// if g.Race[ri].ShipTypes[sti].Cargo < 1 {
|
|
// return e.NewNoCargoBayError("ship_type %q", g.Race[ri].ShipTypes[sti].Name)
|
|
// }
|
|
// if g.ShipGroups[sgi].CargoType != nil && *g.ShipGroups[sgi].CargoType != ct {
|
|
// return e.NewCargoLoadNotEqualError("cargo: %v", *g.ShipGroups[sgi].CargoType)
|
|
// }
|
|
// if ships > 0 && ships < g.ShipGroups[sgi].Number {
|
|
// nsgi, err := g.breakGroupSafe(ri, groupIndex, ships)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// sgi = nsgi
|
|
// }
|
|
// capacity := g.ShipGroups[sgi].CargoCapacity(&g.Race[ri].ShipTypes[sti])
|
|
// freeShipGroupCargoLoad := capacity - g.ShipGroups[sgi].Load
|
|
// if freeShipGroupCargoLoad == 0 {
|
|
// return e.NewCargoLoadNoSpaceLeftError()
|
|
// }
|
|
// var availableOnPlanet *float64
|
|
// switch ct {
|
|
// case CargoMaterial:
|
|
// availableOnPlanet = &g.Map.Planet[pl].Material
|
|
// case CargoCapital:
|
|
// availableOnPlanet = &g.Map.Planet[pl].Capital
|
|
// case CargoColonist:
|
|
// availableOnPlanet = &g.Map.Planet[pl].Colonists
|
|
// default:
|
|
// return e.NewGameStateError("CargoType not accepted: %v", ct)
|
|
// }
|
|
// if quantity > *availableOnPlanet || *availableOnPlanet == 0 {
|
|
// return e.NewCargoLoadNotEnoughError("planet: #%d, %s=%.03f", g.Map.Planet[pl].Number, ct, *availableOnPlanet)
|
|
// }
|
|
// toBeLoaded := quantity
|
|
// if quantity == 0 {
|
|
// toBeLoaded = *availableOnPlanet
|
|
// }
|
|
// if toBeLoaded > freeShipGroupCargoLoad {
|
|
// toBeLoaded = freeShipGroupCargoLoad
|
|
// }
|
|
// *availableOnPlanet = *availableOnPlanet - toBeLoaded
|
|
// g.ShipGroups[sgi].Load += toBeLoaded
|
|
// if g.ShipGroups[sgi].Load > 0 {
|
|
// g.ShipGroups[sgi].CargoType = &ct
|
|
// }
|
|
// return nil
|
|
// }
|
|
|
|
// func (g *Game) GiveawayGroup(raceName, raceAcceptor string, groupIndex, quantity uint) error {
|
|
// ri, err := g.raceIndex(raceName)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// riAccept, err := g.raceIndex(raceAcceptor)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// return g.giveawayGroupInternal(ri, riAccept, groupIndex, quantity)
|
|
// }
|
|
|
|
// func (g *Game) giveawayGroupInternal(ri, riAccept int, groupIndex, quantity uint) (err error) {
|
|
// if ri == riAccept {
|
|
// return e.NewSameRaceError(g.Race[riAccept].Name)
|
|
// }
|
|
// sgi := -1
|
|
// for i, sg := range g.listIndexShipGroups(ri) {
|
|
// if sgi < 0 && sg.Index == groupIndex {
|
|
// sgi = i
|
|
// }
|
|
// }
|
|
// if sgi < 0 {
|
|
// return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
// }
|
|
// if g.ShipGroups[sgi].Number < quantity {
|
|
// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
|
|
// }
|
|
|
|
// var sti int
|
|
// if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 {
|
|
// // hard to test, need manual game data invalidation
|
|
// return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID)
|
|
// }
|
|
|
|
// var stAcc int
|
|
// if stAcc = slices.IndexFunc(g.Race[riAccept].ShipTypes, func(st ShipType) bool { return st.Name == g.Race[ri].ShipTypes[sti].Name }); stAcc >= 0 &&
|
|
// !g.Race[ri].ShipTypes[sti].Equal(g.Race[riAccept].ShipTypes[stAcc]) {
|
|
// return e.NewGiveawayGroupShipsTypeNotEqualError("race %w, ship type %w", g.Race[riAccept].Name, g.Race[riAccept].ShipTypes[stAcc].Name)
|
|
// }
|
|
// if stAcc < 0 {
|
|
// stAcc, err = g.createShipTypeInternal(riAccept,
|
|
// g.Race[ri].ShipTypes[sti].Name,
|
|
// g.Race[ri].ShipTypes[sti].Drive,
|
|
// g.Race[ri].ShipTypes[sti].Weapons,
|
|
// g.Race[ri].ShipTypes[sti].Shields,
|
|
// g.Race[ri].ShipTypes[sti].Cargo,
|
|
// int(g.Race[ri].ShipTypes[sti].Armament))
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// }
|
|
|
|
// var maxIndex uint
|
|
// for sg := range g.listShipGroups(riAccept) {
|
|
// if sg.Index > maxIndex {
|
|
// maxIndex = sg.Index
|
|
// }
|
|
// }
|
|
|
|
// g.ShipGroups = append(g.ShipGroups, ShipGroup{
|
|
// Index: maxIndex + 1,
|
|
// OwnerID: g.Race[riAccept].ID,
|
|
// TypeID: g.Race[riAccept].ShipTypes[stAcc].ID,
|
|
// Number: uint(quantity),
|
|
|
|
// CargoType: g.ShipGroups[sgi].CargoType,
|
|
// Load: g.ShipGroups[sgi].Load,
|
|
|
|
// Tech: maps.Clone(g.ShipGroups[sgi].Tech),
|
|
|
|
// Destination: g.ShipGroups[sgi].Destination,
|
|
// StateInSpace: g.ShipGroups[sgi].StateInSpace,
|
|
// StateUpgrade: g.ShipGroups[sgi].StateUpgrade,
|
|
// })
|
|
|
|
// if quantity == 0 || quantity == g.ShipGroups[sgi].Number {
|
|
// g.ShipGroups = append(g.ShipGroups[:sgi], g.ShipGroups[sgi+1:]...)
|
|
// } else {
|
|
// g.ShipGroups[sgi].Number -= quantity
|
|
// }
|
|
|
|
// return nil
|
|
// }
|
|
|
|
// func (g *Game) breakGroupInternal(ri int, groupIndex, quantity uint) error {
|
|
// sgi := -1
|
|
// var maxIndex uint
|
|
// for i, sg := range g.listIndexShipGroups(ri) {
|
|
// if sgi < 0 && sg.Index == groupIndex {
|
|
// sgi = i
|
|
// }
|
|
// if sg.Index > maxIndex {
|
|
// maxIndex = sg.Index
|
|
// }
|
|
// }
|
|
// if sgi < 0 {
|
|
// return e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
// }
|
|
|
|
// if g.ShipGroups[sgi].State() != StateInOrbit {
|
|
// return e.NewShipsBusyError()
|
|
// }
|
|
|
|
// if g.ShipGroups[sgi].Number < quantity {
|
|
// return e.NewBeakGroupNumberNotEnoughError("%d<%d", g.ShipGroups[sgi].Number, quantity)
|
|
// }
|
|
|
|
// if quantity == 0 || quantity == g.ShipGroups[sgi].Number {
|
|
// g.ShipGroups[sgi].FleetID = nil
|
|
// } else {
|
|
// if _, err := g.breakGroupSafe(ri, groupIndex, quantity); err != nil {
|
|
// return err
|
|
// }
|
|
// }
|
|
|
|
// return nil
|
|
// }
|
|
|
|
// func (g *Game) breakGroupSafe(ri int, groupIndex uint, newGroupShips uint) (int, error) {
|
|
// sgi := -1
|
|
// var maxIndex uint
|
|
// for i, sg := range g.listIndexShipGroups(ri) {
|
|
// if sgi < 0 && sg.Index == groupIndex {
|
|
// sgi = i
|
|
// }
|
|
// if sg.Index > maxIndex {
|
|
// maxIndex = sg.Index
|
|
// }
|
|
// }
|
|
// if sgi < 0 {
|
|
// return -1, e.NewEntityNotExistsError("group #%d", groupIndex)
|
|
// }
|
|
// if g.ShipGroups[sgi].Number < newGroupShips {
|
|
// return -1, e.NewBreakGroupIllegalNumberError("group #%d ships: %d -> %d", g.ShipGroups[sgi].Index, g.ShipGroups[sgi].Number, newGroupShips)
|
|
// }
|
|
// newGroup := g.ShipGroups[sgi]
|
|
// if g.ShipGroups[sgi].CargoType != nil {
|
|
// newGroup.Load = g.ShipGroups[sgi].Load / float64(g.ShipGroups[sgi].Number) * float64(newGroupShips)
|
|
// g.ShipGroups[sgi].Load -= newGroup.Load
|
|
// }
|
|
// newGroup.Number = newGroupShips
|
|
// g.ShipGroups[sgi].Number -= newGroup.Number
|
|
// newGroup.Index = maxIndex + 1
|
|
// newGroup.FleetID = nil
|
|
// g.ShipGroups = append(g.ShipGroups, newGroup)
|
|
// return len(g.ShipGroups) - 1, nil
|
|
// }
|
|
|
|
// func (g *Game) createShips(ri int, shipTypeName string, planetNumber int, quantity int) error {
|
|
// st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == shipTypeName })
|
|
// if st < 0 {
|
|
// return e.NewEntityNotExistsError("ship type %w", shipTypeName)
|
|
// }
|
|
// pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == uint(planetNumber) })
|
|
// if pl < 0 {
|
|
// return e.NewEntityNotExistsError("planet #%d", planetNumber)
|
|
// }
|
|
// if g.Map.Planet[pl].Owner != g.Race[ri].ID {
|
|
// return e.NewEntityNotOwnedError("planet #%d", planetNumber)
|
|
// }
|
|
|
|
// var maxIndex uint
|
|
// for _, sg := range g.listIndexShipGroups(ri) {
|
|
// if sg.Index > maxIndex {
|
|
// maxIndex = sg.Index
|
|
// }
|
|
// }
|
|
// g.ShipGroups = append(g.ShipGroups, ShipGroup{
|
|
// Index: maxIndex + 1,
|
|
// OwnerID: g.Race[ri].ID,
|
|
// TypeID: g.Race[ri].ShipTypes[st].ID,
|
|
// Destination: g.Map.Planet[pl].Number,
|
|
// Number: uint(quantity),
|
|
// Tech: map[Tech]float64{
|
|
// TechDrive: g.Race[ri].TechLevel(TechDrive),
|
|
// TechWeapons: g.Race[ri].TechLevel(TechWeapons),
|
|
// TechShields: g.Race[ri].TechLevel(TechShields),
|
|
// TechCargo: g.Race[ri].TechLevel(TechCargo),
|
|
// },
|
|
// })
|
|
// return nil
|
|
// }
|
|
|
|
// func (g Game) listShipGroups(ri int) iter.Seq[ShipGroup] {
|
|
// return func(yield func(ShipGroup) bool) {
|
|
// for _, sg := range g.listIndexShipGroups(ri) {
|
|
// if !yield(sg) {
|
|
// return
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// func (g Game) listIndexShipGroups(ri int) iter.Seq2[int, ShipGroup] {
|
|
// return func(yield func(int, ShipGroup) bool) {
|
|
// for i := range g.ShipGroups {
|
|
// if g.ShipGroups[i].OwnerID == g.Race[ri].ID {
|
|
// if !yield(i, g.ShipGroups[i]) {
|
|
// return
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// func MustShipGroup(g *Game, ri int, index uint) ShipGroup {
|
|
// for sg := range g.listShipGroups(ri) {
|
|
// if sg.Index == index {
|
|
// return sg
|
|
// }
|
|
// }
|
|
// panic(fmt.Sprintf("race i=%d have no group i=%d", ri, index))
|
|
// }
|
|
|
|
// func maxUint(a, b uint) uint {
|
|
// if b > a {
|
|
// return b
|
|
// }
|
|
// return a
|
|
// }
|