package controller import ( "fmt" "iter" "slices" "galaxy/calc" "galaxy/util" e "galaxy/error" "galaxy/game/internal/model/game" "github.com/google/uuid" ) func (c *Cache) PlanetRename(ri int, number int, name string) error { n, ok := util.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 *Cache) PlanetProduce(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 { if _, ok := util.ValidateTypeName(subj); !ok { 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) { p.ReleaseMaterial(c.MustShipType(ri, *p.Production.SubjectID).EmptyMass()) } else if prod == game.ProductionShip { // new ship class to produce; otherwise we must have been returned from the func earlier p.Production.Progress = new(game.Float) p.Production.ProdUsed = new(game.Float) } if prod != game.ProductionShip { p.Production.Progress = nil p.Production.ProdUsed = nil } p.Production.Type = prod p.Production.SubjectID = subjectID return nil } 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 } func (c *Cache) TurnPlanetProductions() { for sgi := range c.ShipGroupsIndex() { sg := c.ShipGroup(sgi) // cancel upgrade for groups on wiped planets 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 := c.PlanetProductionCapacity(pn) 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, util.Fixed3(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.unsafeCreateShips(ri, st.ID, p.Number, ships) } case game.ResearchScience: sc := c.mustScience(ri, *p.Production.SubjectID) ResearchTech(r, productionAvailable, sc.Drive.F(), sc.Weapons.F(), sc.Shields.F(), sc.Cargo.F()) case game.ResearchDrive: ResearchTech(r, productionAvailable, 1., 0, 0, 0) case game.ResearchWeapons: ResearchTech(r, productionAvailable, 0, 1., 0, 0) case game.ResearchShields: ResearchTech(r, productionAvailable, 0, 0, 1., 0) case game.ResearchCargo: ResearchTech(r, productionAvailable, 0, 0, 0, 1.) case game.ProductionMaterial: p.ProduceMaterial(productionAvailable) case game.ProductionCapital: p.ProduceIndustry(productionAvailable) 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(p *game.Planet, productionAvailable, shipMass float64) uint { if productionAvailable <= 0 { return 0 } ships := uint(0) pa := productionAvailable PRODcost := calc.ShipProductionCost(shipMass) var MATneed, MATfarm, totalCost float64 for { MATneed = shipMass - float64(p.Material) if MATneed < 0 { MATneed = 0 } MATfarm = MATneed / float64(p.Resources) totalCost = PRODcost + MATfarm if pa < totalCost { progress := pa / totalCost pval := game.F(progress) if p.Production.Progress != nil { pval += *p.Production.Progress } p.Production.Progress = &pval fval := game.F(pa) p.Production.ProdUsed = &fval return ships } else { pa -= totalCost p.Mat(float64(p.Material) - shipMass + MATneed) ships += 1 } } }