417 lines
13 KiB
Go
417 lines
13 KiB
Go
package controller
|
|
|
|
import (
|
|
"fmt"
|
|
"iter"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
e "github.com/iliadenisov/galaxy/internal/error"
|
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
|
)
|
|
|
|
func (c *Controller) RenamePlanet(raceName string, planetNumber int, typeName string) error {
|
|
ri, err := c.Cache.raceIndex(raceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.Cache.RenamePlanet(ri, planetNumber, typeName)
|
|
}
|
|
|
|
func (c *Cache) RenamePlanet(ri int, number int, name string) error {
|
|
n, ok := validateTypeName(name)
|
|
if !ok {
|
|
return e.NewEntityTypeNameValidationError("%q", n)
|
|
}
|
|
if number < 0 {
|
|
return e.NewPlanetNumberError(number)
|
|
}
|
|
p, ok := c.Planet(uint(number))
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("planet #%d", number)
|
|
}
|
|
if !p.OwnedBy(c.g.Race[ri].ID) {
|
|
return e.NewEntityNotOwnedError("planet #%d", number)
|
|
}
|
|
c.g.Map.Planet[c.MustPlanetIndex(p.Number)].Name = n
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) PlanetProduction(raceName string, planetNumber int, prodType, subject string) error {
|
|
ri, err := c.Cache.raceIndex(raceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var prod game.ProductionType
|
|
switch game.ProductionType(strings.ToUpper(prodType)) {
|
|
case game.ProductionMaterial:
|
|
prod = game.ProductionMaterial
|
|
case game.ProductionCapital:
|
|
prod = game.ProductionCapital
|
|
case game.ResearchDrive:
|
|
prod = game.ResearchDrive
|
|
case game.ResearchWeapons:
|
|
prod = game.ResearchWeapons
|
|
case game.ResearchShields:
|
|
prod = game.ResearchShields
|
|
case game.ResearchCargo:
|
|
prod = game.ResearchCargo
|
|
case game.ResearchScience:
|
|
prod = game.ResearchScience
|
|
case game.ProductionShip:
|
|
prod = game.ProductionShip
|
|
default:
|
|
return e.NewProductionInvalidError(prodType)
|
|
}
|
|
return c.Cache.PlanetProduction(ri, planetNumber, prod, subject)
|
|
}
|
|
|
|
func (c *Cache) PlanetProduction(ri int, number int, prod game.ProductionType, subj string) error {
|
|
c.validateRaceIndex(ri)
|
|
if number < 0 {
|
|
return e.NewPlanetNumberError(number)
|
|
}
|
|
p, ok := c.Planet(uint(number))
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("planet #%d", number)
|
|
}
|
|
if !p.OwnedBy(c.g.Race[ri].ID) {
|
|
return e.NewEntityNotOwnedError("planet #%d", number)
|
|
}
|
|
var subjectID *uuid.UUID
|
|
if (prod == game.ResearchScience || prod == game.ProductionShip) && subj == "" {
|
|
return e.NewEntityTypeNameValidationError("%s=%q", prod, subj)
|
|
}
|
|
|
|
if prod == game.ResearchScience {
|
|
i := slices.IndexFunc(c.g.Race[ri].Sciences, func(s game.Science) bool { return s.Name == subj })
|
|
if i < 0 {
|
|
return e.NewEntityNotExistsError("science %q", subj)
|
|
}
|
|
subjectID = &c.g.Race[ri].Sciences[i].ID
|
|
}
|
|
|
|
if prod == game.ProductionShip {
|
|
st, _, ok := c.ShipClass(ri, subj)
|
|
if !ok {
|
|
return e.NewEntityNotExistsError("ship type %q", subj)
|
|
}
|
|
if p.Production.Type == game.ProductionShip &&
|
|
p.Production.SubjectID != nil &&
|
|
*p.Production.SubjectID == st.ID {
|
|
// Planet already produces this ship type, keeping progress intact
|
|
return nil
|
|
}
|
|
subjectID = &st.ID
|
|
}
|
|
|
|
if p.Production.Type == game.ProductionShip && (prod != game.ProductionShip || *subjectID != *p.Production.SubjectID) {
|
|
mat, _ := c.MustShipType(ri, *p.Production.SubjectID).ProductionCost()
|
|
p.Mat(p.Material.F() + mat*(*p.Production.Progress).F())
|
|
*p.Production.Progress = 0.
|
|
} else if prod == game.ProductionShip {
|
|
// new ship class to produce; otherwise we must have been returned from the func earlier
|
|
progress := game.F(0.)
|
|
p.Production.Progress = &progress
|
|
}
|
|
|
|
if prod != game.ProductionShip {
|
|
p.Production.Progress = nil
|
|
}
|
|
|
|
p.Production.Type = prod
|
|
p.Production.SubjectID = subjectID
|
|
return nil
|
|
}
|
|
|
|
// TODO: test
|
|
func (c *Cache) PlanetProductionDisplayName(pn uint) string {
|
|
p := c.MustPlanet(pn)
|
|
if !p.Owned() {
|
|
return "-"
|
|
}
|
|
ri := c.RaceIndex(*p.Owner)
|
|
switch pt := p.Production.Type; pt {
|
|
case game.ResearchDrive:
|
|
return "Drive"
|
|
case game.ResearchWeapons:
|
|
return "Weapons"
|
|
case game.ResearchShields:
|
|
return "Shields"
|
|
case game.ResearchCargo:
|
|
return "Cargo"
|
|
case game.ProductionMaterial:
|
|
return "Material"
|
|
case game.ProductionCapital:
|
|
return "Capital"
|
|
case game.ProductionShip:
|
|
return c.MustShipType(ri, *p.Production.SubjectID).Name
|
|
case game.ResearchScience:
|
|
i := slices.IndexFunc(c.g.Race[ri].Sciences, func(sc game.Science) bool { return sc.ID == *p.Production.SubjectID })
|
|
if i < 0 {
|
|
panic("researching science not found")
|
|
}
|
|
return c.g.Race[ri].Sciences[i].Name
|
|
default:
|
|
return string(pt)
|
|
}
|
|
}
|
|
|
|
func (c *Cache) Planet(planetNumber uint) (*game.Planet, bool) {
|
|
if c.cachePlanetByPlanetNumber == nil {
|
|
c.cachePlanetByPlanetNumber = make(map[uint]*game.Planet)
|
|
for p := range c.g.Map.Planet {
|
|
c.cachePlanetByPlanetNumber[c.g.Map.Planet[p].Number] = &c.g.Map.Planet[p]
|
|
}
|
|
}
|
|
if v, ok := c.cachePlanetByPlanetNumber[planetNumber]; ok {
|
|
return v, true
|
|
} else {
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
func (c *Cache) MustPlanet(pn uint) *game.Planet {
|
|
if v, ok := c.Planet(pn); ok {
|
|
return v
|
|
} else {
|
|
panic(fmt.Sprintf("planet not found by number=%d", pn))
|
|
}
|
|
}
|
|
|
|
func (c *Cache) MustPlanetIndex(pn uint) int {
|
|
if idx := slices.IndexFunc(c.g.Map.Planet, func(p game.Planet) bool { return p.Number == pn }); idx < 0 {
|
|
panic(fmt.Sprintf("planet not found by number=%d", pn))
|
|
} else {
|
|
return idx
|
|
}
|
|
}
|
|
|
|
// Свободный "Производственный Потенциал" (L)
|
|
// промышленность * 0.75 + население * 0.25
|
|
// за вычетом затрат, расходуемых в течение хода на модернизацию кораблей
|
|
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()
|
|
}
|
|
return p.ProductionCapacity() - busyResources
|
|
}
|
|
|
|
// TODO: test upgrade & upgrade cancel
|
|
func (c *Cache) TurnPlanetProductions() {
|
|
// cancel upgrade for groups on wiped planets
|
|
for sgi := range c.ShipGroupsIndex() {
|
|
sg := c.ShipGroup(sgi)
|
|
if sg.State() == game.StateUpgrade && !c.MustPlanet(sg.Destination).Owned() {
|
|
sg.StateUpgrade = nil
|
|
}
|
|
}
|
|
|
|
for pn := range c.listProducingPlanets() {
|
|
p := c.MustPlanet(pn)
|
|
ri := c.RaceIndex(*p.Owner)
|
|
r := &c.g.Race[ri]
|
|
|
|
// upgrade groups and return to in_orbit state
|
|
productionAvailable := p.ProductionCapacity()
|
|
for sg := range c.shipGroupsInUpgrade(p.Number) {
|
|
cost := sg.StateUpgrade.Cost()
|
|
if productionAvailable >= cost {
|
|
for i := range sg.StateUpgrade.UpgradeTech {
|
|
sg.Tech = sg.Tech.Set(sg.StateUpgrade.UpgradeTech[i].Tech, sg.StateUpgrade.UpgradeTech[i].Level.F())
|
|
}
|
|
productionAvailable -= cost
|
|
}
|
|
sg.StateUpgrade = nil
|
|
}
|
|
|
|
switch pt := p.Production.Type; pt {
|
|
case game.ProductionShip:
|
|
st := c.MustShipType(ri, *p.Production.SubjectID)
|
|
if ships := ProduceShip(p, productionAvailable, st.EmptyMass()); ships > 0 {
|
|
c.createShipsUnsafe(ri, st.ID, p.Number, ships)
|
|
}
|
|
case game.ResearchScience:
|
|
sc := c.mustScience(ri, *p.Production.SubjectID)
|
|
ResearchTech(r, p.ProductionCapacity(), sc.Drive.F(), sc.Weapons.F(), sc.Shields.F(), sc.Cargo.F())
|
|
case game.ResearchDrive:
|
|
ResearchTech(r, p.ProductionCapacity(), 1., 0, 0, 0)
|
|
case game.ResearchWeapons:
|
|
ResearchTech(r, p.ProductionCapacity(), 0, 1., 0, 0)
|
|
case game.ResearchShields:
|
|
ResearchTech(r, p.ProductionCapacity(), 0, 0, 1., 0)
|
|
case game.ResearchCargo:
|
|
ResearchTech(r, p.ProductionCapacity(), 0, 0, 0, 1.)
|
|
case game.ProductionMaterial:
|
|
p.ProduceMaterial()
|
|
case game.ProductionCapital:
|
|
p.ProduceIndustry()
|
|
default:
|
|
panic(fmt.Sprintf("unprocessed production type: '%v' for planet: #%d owner=%v", pt, pn, p.Owner))
|
|
}
|
|
|
|
// last step: increase population / colonists
|
|
p.ProducePopulation()
|
|
}
|
|
c.TurnMergeEqualShipGroups()
|
|
}
|
|
|
|
// listProducingPlanets iterates over all inhabited planet numbers with defined production type.
|
|
// Planets producing ships guaranteed to be iterated first for correct turn actions order.
|
|
func (c *Cache) listProducingPlanets() iter.Seq[uint] {
|
|
ordered := make([]int, 0)
|
|
for i := range c.g.Map.Planet {
|
|
if !c.g.Map.Planet[i].Owned() || c.g.Map.Planet[i].Production.Type == game.ProductionNone {
|
|
continue
|
|
}
|
|
ordered = append(ordered, i)
|
|
}
|
|
slices.SortFunc(ordered, func(l, r int) int {
|
|
if c.g.Map.Planet[l].Production.Type == game.ProductionShip && c.g.Map.Planet[r].Production.Type != game.ProductionShip {
|
|
return -1
|
|
}
|
|
if c.g.Map.Planet[l].Production.Type != game.ProductionShip && c.g.Map.Planet[r].Production.Type == game.ProductionShip {
|
|
return 1
|
|
}
|
|
return 0
|
|
})
|
|
return func(yield func(uint) bool) {
|
|
for _, i := range ordered {
|
|
if !yield(c.g.Map.Planet[i].Number) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Internal funcs
|
|
|
|
func (c *Cache) putPopulation(pn uint, v float64) {
|
|
c.MustPlanet(pn).Pop(v)
|
|
}
|
|
|
|
func (c *Cache) putColonists(pn uint, v float64) {
|
|
c.MustPlanet(pn).Col(v)
|
|
}
|
|
|
|
func (c *Cache) putMaterial(pn uint, v float64) {
|
|
c.MustPlanet(pn).Mat(v)
|
|
}
|
|
|
|
func ProduceShip_old(p *game.Planet, productionAvailable, shipMass float64) uint {
|
|
if productionAvailable <= 0 {
|
|
return 0
|
|
}
|
|
// CAP_perShip := shipMass / p.Resources.F()
|
|
CAP_perShip := ShipCapitalCost(shipMass, float64(p.Resources))
|
|
productionForMass := ShipProductionCost(shipMass)
|
|
ships := uint(0)
|
|
flZero := game.F(0.)
|
|
p.Production.Progress = &flZero
|
|
for productionAvailable > 0 {
|
|
var productionExtraCAP float64
|
|
if CAP_deficit := p.Capital.F() - CAP_perShip; CAP_deficit < 0 {
|
|
productionExtraCAP = -CAP_deficit
|
|
}
|
|
productionCost := productionExtraCAP + productionForMass
|
|
if productionAvailable >= productionCost {
|
|
productionAvailable -= productionCost
|
|
p.Cap(p.Capital.F() - (CAP_perShip - productionExtraCAP))
|
|
ships++
|
|
} else {
|
|
progress := game.F(productionAvailable / productionCost)
|
|
productionAvailable -= productionCost * progress.F()
|
|
p.Production.Progress = &progress
|
|
|
|
v := (productionForMass + CAP_perShip) * float64(progress)
|
|
fmt.Println("P=", v)
|
|
// fmt.Println("productionForMass", productionForMass, "CAP_perShip", CAP_perShip, "productionCost", productionCost, "productionAvailable", productionAvailable, "progress", progress)
|
|
break
|
|
}
|
|
}
|
|
return ships
|
|
}
|
|
|
|
func ProduceShip(p *game.Planet, productionAvailable, shipMass float64) uint {
|
|
if productionAvailable <= 0 {
|
|
return 0
|
|
}
|
|
// MAT_perShip := shipMass / p.Resources.F()
|
|
MAT_perShip := ShipMaterialCost(shipMass, float64(p.Resources))
|
|
fmt.Println("MAT_perShip", MAT_perShip)
|
|
productionForMass := ShipProductionCost(shipMass)
|
|
ships := uint(0)
|
|
fval := game.F(0.)
|
|
p.Production.Progress = &fval
|
|
for productionAvailable > 0 {
|
|
var productionExtraMAT float64
|
|
MAT_deficit := float64(p.Material) - MAT_perShip
|
|
// fmt.Println("> MAT:", p.Material)
|
|
if MAT_deficit < 0 {
|
|
productionExtraMAT = (-MAT_deficit)
|
|
// p.Mat(0)
|
|
} else {
|
|
// p.Mat(float64(p.Material) - MAT_deficit)
|
|
}
|
|
// fmt.Println("< MAT:", p.Material)
|
|
productionCost := productionExtraMAT + productionForMass
|
|
// fmt.Println("ships:", ships, "cost:", productionCost, "avail:", productionAvailable)
|
|
fmt.Println()
|
|
fmt.Println("productionExtraMAT", productionExtraMAT)
|
|
fmt.Println("productionForMass", productionForMass)
|
|
fmt.Println("productionCost", productionCost)
|
|
fmt.Println("productionAvailable", productionAvailable)
|
|
if productionAvailable >= productionCost {
|
|
productionAvailable -= productionCost
|
|
// fmt.Println("> MAT:", p.Material)
|
|
if MAT_deficit < 0 {
|
|
// productionExtraMAT = -MAT_deficit
|
|
p.Mat(0)
|
|
} else {
|
|
p.Mat(float64(p.Material) - MAT_deficit)
|
|
}
|
|
// fmt.Println("< MAT:", p.Material)
|
|
// fmt.Println("> MAT:", p.Material)
|
|
// p.Mat(float64(p.Material) - (MAT_perShip - productionExtraMAT))
|
|
// fmt.Println("< MAT:", p.Material)
|
|
ships++
|
|
} else {
|
|
progress := productionAvailable / productionCost
|
|
// productionAvailable -= productionCost * progress
|
|
pval := game.F(progress)
|
|
p.Production.Progress = &pval
|
|
aval := game.F(productionAvailable)
|
|
p.Production.FreeProd = &aval
|
|
|
|
// fmt.Println("MAT_deficit", MAT_deficit)
|
|
|
|
fmt.Println()
|
|
fmt.Println("productionExtraMAT", productionExtraMAT)
|
|
fmt.Println("productionForMass", productionForMass)
|
|
fmt.Println("productionCost", productionCost)
|
|
fmt.Println("productionAvailable", productionAvailable)
|
|
fmt.Println("progress", progress)
|
|
v := ShipProductionCost(MAT_perShip * float64(progress))
|
|
fmt.Println("MAT=", v)
|
|
break
|
|
}
|
|
}
|
|
return ships
|
|
}
|
|
|
|
func ShipProductionCost(shipMass float64) float64 {
|
|
return shipMass * 10.
|
|
}
|
|
func ShipMaterialCost(shipMass, planetResource float64) float64 {
|
|
return shipMass / planetResource
|
|
}
|
|
|
|
func ShipCapitalCost(shipMass, planetResource float64) float64 {
|
|
return shipMass / planetResource
|
|
}
|
|
|
|
// TODO: при смене производства на планете проверить, сколько материалов высвободится в MAT
|