package controller import ( "cmp" "fmt" "iter" "slices" "github.com/google/uuid" "github.com/iliadenisov/galaxy/internal/model/game" mr "github.com/iliadenisov/galaxy/internal/model/report" "github.com/iliadenisov/galaxy/internal/util" ) func (c *Cache) Report(t uint, battles []*mr.BattleReport, bombings []*mr.Bombing) iter.Seq[*mr.Report] { report := c.InitReport(t) return func(yield func(*mr.Report) bool) { for i := range c.listRaceActingIdx() { c.ReportRace(i, report, battles, bombings) if !yield(report) { break } } } } func (c *Cache) InitReport(t uint) *mr.Report { report := &mr.Report{ Turn: t, Width: c.g.Map.Width, Height: c.g.Map.Height, PlanetCount: uint32(len(c.g.Map.Planet)), Player: make([]mr.Player, len(c.g.Race)), LocalScience: make([]mr.Science, 0, 10), OtherScience: make([]mr.OtherScience, 0, 10), LocalShipClass: make([]mr.ShipClass, 0, 20), OtherShipClass: make([]mr.OthersShipClass, 0, 50), Battle: make([]uuid.UUID, 0, 10), Bombing: make([]*mr.Bombing, 0, 10), IncomingGroup: make([]mr.IncomingGroup, 0, 10), OnPlanetGroupCache: make(map[uint][]int), InSpaceGroupRangeCache: make(map[int]map[uint]float64), } sumVote, sumPop, sumInd := make(map[int]float64), make(map[int]float64), make(map[int]float64) planets := make(map[int]uint16) for pi := range c.g.Map.Planet { p := &c.g.Map.Planet[pi] if !p.Owned() { continue } ri := c.RaceIndex(*p.Owner) sumPop[ri] += p.Population.F() sumInd[ri] += p.Industry.F() planets[ri] = planets[ri] + 1 } for ri := range c.listRaceIdx() { r := &c.g.Race[ri] rr := &report.Player[ri] rr.ID = r.ID rr.Name = r.Name rr.Extinct = r.Extinct rr.Drive = mr.F(r.TechLevel(game.TechDrive)) rr.Weapons = mr.F(r.TechLevel(game.TechWeapons)) rr.Shields = mr.F(r.TechLevel(game.TechShields)) rr.Cargo = mr.F(r.TechLevel(game.TechCargo)) rr.Planets = planets[ri] rr.Population = mr.F(sumPop[ri]) rr.Industry = mr.F(sumInd[ri]) // give voices by race index if vi := slices.IndexFunc(c.g.Race, func(v game.Race) bool { return r.VoteFor == v.ID }); vi < 0 { panic(fmt.Sprintf("voting for unknown race, id=%v", r.VoteFor)) } else { sumVote[vi] += r.Votes.F() dest := &report.Player[vi] dest.Votes = mr.F(sumVote[vi]) } } slices.SortFunc(report.Player, func(a, b mr.Player) int { return cmp.Compare(a.Name, b.Name) }) for sgi := range c.g.ShipGroups { sg := &c.g.ShipGroups[sgi] if sg.State() == game.StateInSpace { // pre-calculate distances from in_space ship groups to every planet if _, ok := report.InSpaceGroupRangeCache[sgi]; !ok { report.InSpaceGroupRangeCache[sgi] = make(map[uint]float64) } for pi := range c.g.Map.Planet { p2 := &c.g.Map.Planet[pi] distance := util.ShortDistance(c.g.Map.Width, c.g.Map.Height, sg.StateInSpace.X.F(), sg.StateInSpace.Y.F(), p2.X.F(), p2.Y.F()) report.InSpaceGroupRangeCache[sgi][p2.Number] = distance } } else { // collect all orbiting ship groups by planet report.OnPlanetGroupCache[sg.Destination] = append(report.OnPlanetGroupCache[sg.Destination], sgi) } } return report } func (c *Cache) ReportRace(ri int, rep *mr.Report, battles []*mr.BattleReport, bombings []*mr.Bombing) { c.validateRaceIndex(ri) r := &c.g.Race[ri] rep.Race = r.Name rep.RaceID = r.ID // votes based on population rep.Votes = mr.F(r.Votes.F()) // relations for i := range r.Relations { rii := slices.IndexFunc(rep.Player, func(v mr.Player) bool { return v.ID == r.Relations[i].RaceID }) if rii < 0 { panic(fmt.Sprintf("opponent race for relation not found, id=%v", r.Relations[i].RaceID)) } rep.Player[rii].Relation = r.Relations[i].Relation.String() } // self-relation is undefined if i := slices.IndexFunc(rep.Player, func(v mr.Player) bool { return v.ID == r.ID }); i < 0 { panic(fmt.Sprintf("race not found in report, id=%v", r.ID)) } else { rep.Player[i].Relation = "-" } // sciences c.ReportLocalScience(ri, rep) c.ReportOtherScience(ri, rep) // ship classes c.ReportLocalShipClass(ri, rep) c.ReportOtherShipClass(ri, rep) // battles c.ReportBattle(ri, rep, battles) // bombings c.ReportBombing(ri, rep, bombings) // incoming groups c.ReportIncomingGroup(ri, rep) // player's planets c.ReportLocalPlanet(ri, rep) // ships in production c.ReportShipProduction(ri, rep) // cargo routes c.ReportRoute(ri, rep) // others' planets c.ReportOtherPlanet(ri, rep) // uninhabited planets c.ReportUninhabitedPlanet(ri, rep) // unidentified planets c.ReportUnidentifiedPlanet(ri, rep) // fleets c.ReportLocalFleet(ri, rep) // player's groups c.ReportLocalGroup(ri, rep) // others' groups c.ReportOtherGroup(ri, rep) // unidentified groups c.ReportUnidentifiedGroup(ri, rep) } func (c *Cache) ReportLocalScience(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.LocalScience) for i := range r.Sciences { sliceIndexValidate(&rep.LocalScience, i) rep.LocalScience[i].Name = r.Sciences[i].Name rep.LocalScience[i].Drive = mr.F(r.Sciences[i].Drive.F()) rep.LocalScience[i].Weapons = mr.F(r.Sciences[i].Weapons.F()) rep.LocalScience[i].Shields = mr.F(r.Sciences[i].Shields.F()) rep.LocalScience[i].Cargo = mr.F(r.Sciences[i].Cargo.F()) } slices.SortFunc(rep.LocalScience, func(a, b mr.Science) int { return cmp.Compare(a.Name, b.Name) }) } func (c *Cache) ReportOtherScience(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.OtherScience) i := 0 for sg := range c.listShipGroups(ri) { if sg.State() != game.StateInOrbit { continue } p := c.MustPlanet(sg.Destination) if !p.Owned() || p.OwnedBy(r.ID) || p.Production.Type != game.ResearchScience { continue } ownerIdx := c.RaceIndex(*p.Owner) owner := &c.g.Race[ownerIdx] sc := c.mustScience(ownerIdx, *p.Production.SubjectID) sliceIndexValidate(&rep.OtherScience, i) rep.OtherScience[i].Name = owner.Name rep.OtherScience[i].Drive = mr.F(sc.Drive.F()) rep.OtherScience[i].Weapons = mr.F(sc.Weapons.F()) rep.OtherScience[i].Shields = mr.F(sc.Shields.F()) rep.OtherScience[i].Cargo = mr.F(sc.Cargo.F()) i++ } slices.SortFunc(rep.OtherScience, func(a, b mr.OtherScience) int { return cmp.Or(cmp.Compare(a.Race, b.Race), cmp.Compare(a.Name, b.Name)) }) } func (c *Cache) ReportLocalShipClass(ri int, report *mr.Report) { c.validateRaceIndex(ri) clear(report.LocalShipClass) i := 0 for st := range c.ListShipTypes(ri) { sliceIndexValidate(&report.LocalShipClass, i) report.LocalShipClass[i].Name = st.Name report.LocalShipClass[i].Drive = mr.F(st.Drive.F()) report.LocalShipClass[i].Armament = st.Armament report.LocalShipClass[i].Weapons = mr.F(st.Weapons.F()) report.LocalShipClass[i].Shields = mr.F(st.Shields.F()) report.LocalShipClass[i].Cargo = mr.F(st.Cargo.F()) report.LocalShipClass[i].Mass = mr.F(st.EmptyMass()) i++ } slices.SortFunc(report.LocalShipClass, func(a, b mr.ShipClass) int { return cmp.Compare(a.Name, b.Name) }) } func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.OtherShipClass) i := 0 used := make(map[uuid.UUID]map[string]bool) skip := func(ownerID uuid.UUID, className string) bool { if ownerID == r.ID { return true } if _, ok := used[ownerID]; ok { if _, ok := used[ownerID][className]; ok { return true } } else { used[ownerID] = make(map[string]bool) } used[ownerID][className] = true return false } // add visible ship classes from battles // for bi := range battle { // for si := range battle[bi].Ships { // g := battle[bi].Ships[si] // if skip(g.OwnerID, g.ClassName) { // continue // } // sliceIndexValidate(&rep.OtherShipClass, i) // rep.OtherShipClass[i].Race = c.g.Race[c.RaceIndex(g.OwnerID)].Name // rep.OtherShipClass[i].Name = g.ClassName // rep.OtherShipClass[i].Drive = g.DriveTech // rep.OtherShipClass[i].Armament = g.ClassArmament // rep.OtherShipClass[i].Weapons = g.WeaponsTech // rep.OtherShipClass[i].Shields = g.ShieldsTech // rep.OtherShipClass[i].Cargo = g.CargoTech // rep.OtherShipClass[i].Mass = g.ClassMass // i++ // } // } // add visible ships from owned and observed planets for pn := range rep.OnPlanetGroupCache { p := c.MustPlanet(pn) if p.OwnedBy(r.ID) || slices.IndexFunc(rep.OnPlanetGroupCache[pn], func(sgi int) bool { return c.ShipGroup(sgi).OwnerID == r.ID }) >= 0 { for _, sgi := range rep.OnPlanetGroupCache[pn] { sg := c.ShipGroup(sgi) st := c.ShipGroupShipClass(sgi) if skip(sg.OwnerID, st.Name) { continue } sliceIndexValidate(&rep.OtherShipClass, i) rep.OtherShipClass[i].Race = c.g.Race[c.RaceIndex(sg.OwnerID)].Name rep.OtherShipClass[i].Name = st.Name rep.OtherShipClass[i].Drive = mr.F(st.Drive.F()) rep.OtherShipClass[i].Armament = st.Armament rep.OtherShipClass[i].Weapons = mr.F(st.Weapons.F()) rep.OtherShipClass[i].Shields = mr.F(st.Shields.F()) rep.OtherShipClass[i].Cargo = mr.F(st.Cargo.F()) rep.OtherShipClass[i].Mass = mr.F(st.EmptyMass()) i++ } } } slices.SortFunc(rep.OtherShipClass, func(a, b mr.OthersShipClass) int { return cmp.Or(cmp.Compare(a.Race, b.Race), cmp.Compare(a.Name, b.Name)) }) } func (c *Cache) ReportBattle(ri int, rep *mr.Report, br []*mr.BattleReport) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.Battle) i := 0 for bi := range br { visible := false for k := range br[bi].Races { visible = visible || br[bi].Races[k] == r.ID } if !visible { continue } sliceIndexValidate(&rep.Battle, i) rep.Battle[i] = br[bi].ID i++ } } func (c *Cache) ReportBombing(ri int, rep *mr.Report, bombing []*mr.Bombing) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.Bombing) i := 0 for bi := range bombing { pn := bombing[bi].Number visible := bombing[bi].PlanetOwnedID == r.ID // planet may be bombed and wiped for _, sgi := range rep.OnPlanetGroupCache[pn] { sg := c.ShipGroup(sgi) visible = visible || (sg.OwnerID == r.ID && sg.Destination == pn) } if !visible { continue } sliceIndexValidate(&rep.Bombing, i) rep.Bombing[i] = bombing[bi] i++ } slices.SortFunc(rep.Bombing, func(a, b *mr.Bombing) int { return cmp.Or(cmp.Compare(a.Number, b.Number), boolCompare(a.Wiped, b.Wiped)) }) } func (c *Cache) ReportIncomingGroup(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.IncomingGroup) i := 0 for sgi := range c.ShipGroupsIndex() { sg := c.ShipGroup(sgi) st := c.ShipGroupShipClass(sgi) if sg.OwnerID == r.ID || sg.State() != game.StateInSpace { continue } p1 := c.MustPlanet(sg.StateInSpace.Origin) p2 := c.MustPlanet(sg.Destination) if !p2.OwnedBy(r.ID) { continue } distance := util.ShortDistance(c.g.Map.Width, c.g.Map.Height, p1.X.F(), p1.Y.F(), p2.X.F(), p2.Y.F()) var speed, mass float64 if sg.FleetID != nil { speed, mass = c.FleetSpeedAndMass(c.MustFleetIndex(*sg.FleetID)) } else { speed, mass = sg.Speed(st), sg.FullMass(st) } sliceIndexValidate(&rep.IncomingGroup, i) rep.IncomingGroup[i].Origin = sg.StateInSpace.Origin rep.IncomingGroup[i].Destination = sg.Destination rep.IncomingGroup[i].Distance = mr.F(distance) rep.IncomingGroup[i].Speed = mr.F(speed) rep.IncomingGroup[i].Mass = mr.F(mass) i++ } } func (c *Cache) ReportLocalPlanet(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.LocalPlanet) i := 0 for pi := range c.g.Map.Planet { p := &c.g.Map.Planet[pi] if !p.OwnedBy(r.ID) { continue } sliceIndexValidate(&rep.LocalPlanet, i) c.localPlanet(&rep.LocalPlanet[i], p) // rep.LocalPlanet[i].UnidentifiedPlanet.Number = p.Number // rep.LocalPlanet[i].UnidentifiedPlanet.X = mr.F(p.X.F()) // rep.LocalPlanet[i].UnidentifiedPlanet.Y = mr.F(p.Y.F()) // rep.LocalPlanet[i].UninhabitedPlanet.Size = mr.F(p.Size.F()) // rep.LocalPlanet[i].UninhabitedPlanet.Name = p.Name // rep.LocalPlanet[i].UninhabitedPlanet.Resources = mr.F(p.Resources.F()) // rep.LocalPlanet[i].UninhabitedPlanet.Capital = mr.F(p.Capital.F()) // rep.LocalPlanet[i].UninhabitedPlanet.Material = mr.F(p.Material.F()) // rep.LocalPlanet[i].Industry = mr.F(p.Industry.F()) // rep.LocalPlanet[i].Population = mr.F(p.Population.F()) // rep.LocalPlanet[i].Colonists = mr.F(p.Colonists.F()) // rep.LocalPlanet[i].Production = c.PlanetProductionDisplayName(p.Number) // rep.LocalPlanet[i].FreeIndustry = mr.F(p.ProductionCapacity()) // for _, sgi := range rep.PlanetGroupsCache[p.Number] { // sg := c.ShipGroup(sgi) // if sg.StateUpgrade == nil { // break // } // // between-turn report: ships upgrading on the planet decreases free indistrial potential // rep.LocalPlanet[i].FreeIndustry -= mr.F(sg.StateUpgrade.Cost()) // } i++ } } func (c *Cache) ReportOtherPlanet(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.OtherPlanet) i := 0 for sg := range c.listShipGroups(ri) { if sg.State() != game.StateInOrbit { continue } p := c.MustPlanet(sg.Destination) if !p.Owned() || p.OwnedBy(r.ID) { continue } sliceIndexValidate(&rep.OtherPlanet, i) c.localPlanet(&rep.OtherPlanet[i].LocalPlanet, p) rep.OtherPlanet[i].Owner = c.g.Race[c.RaceIndex(*p.Owner)].Name i++ } } func (c *Cache) ReportUninhabitedPlanet(ri int, rep *mr.Report) { c.validateRaceIndex(ri) clear(rep.UninhabitedPlanet) i := 0 for sg := range c.listShipGroups(ri) { if sg.State() != game.StateInOrbit { continue } p := c.MustPlanet(sg.Destination) if p.Owned() { continue } sliceIndexValidate(&rep.UninhabitedPlanet, i) uninhabitedPlanet(&rep.UninhabitedPlanet[i], p) i++ } } func (c *Cache) ReportUnidentifiedPlanet(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.UnidentifiedPlanet) i := 0 for pi := range c.g.Map.Planet { p := &c.g.Map.Planet[pi] // skip player's owned planets if p.OwnedBy(r.ID) { continue } // skip planets where player's group are orbiting if slices.IndexFunc(rep.OnPlanetGroupCache[p.Number], func(sgi int) bool { return c.ShipGroup(sgi).OwnerID == r.ID }) >= 0 { continue } sliceIndexValidate(&rep.UnidentifiedPlanet, i) unidentifiedPlanet(&rep.UnidentifiedPlanet[i], p) i++ } } func (c *Cache) ReportShipProduction(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.ShipProduction) i := 0 for pi := range c.g.Map.Planet { p := &c.g.Map.Planet[pi] if !p.OwnedBy(r.ID) || p.Production.Type != game.ProductionShip { continue } st := c.MustShipType(ri, *p.Production.SubjectID) sliceIndexValidate(&rep.ShipProduction, i) rep.ShipProduction[pi].Planet = p.Number rep.ShipProduction[pi].Class = st.Name rep.ShipProduction[pi].Cost = mr.F(ShipProductionCost(st.EmptyMass())) rep.ShipProduction[pi].Free = mr.F(c.PlanetProductionCapacity(p.Number)) rep.ShipProduction[pi].ProdUsed = mr.F((*p.Production.ProdUsed).F()) rep.ShipProduction[pi].Percent = mr.F((*p.Production.Progress).F()) i++ } } func (c *Cache) ReportRoute(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.Route) i := 0 for pi := range c.g.Map.Planet { p := &c.g.Map.Planet[pi] if !p.OwnedBy(r.ID) || len(p.Route) == 0 { continue } sliceIndexValidate(&rep.Route, i) rep.Route[i].Planet = p.Number // rep.Route[i].Route = make(map[uint]string) for rt, dest := range p.Route { rep.Route[i].Route[dest] = rt.String() } i++ } } func (c *Cache) ReportLocalFleet(ri int, rep *mr.Report) { c.validateRaceIndex(ri) clear(rep.LocalFleet) i := 0 for fl := range c.listFleets(ri) { fi := c.MustFleetIndex(fl.ID) gid := slices.Collect(c.fleetGroupIds(ri, fi)) if len(gid) == 0 { continue } speed, _ := c.FleetSpeedAndMass(fi) fleetState := c.FleetState(fl.ID) sliceIndexValidate(&rep.LocalFleet, i) rep.LocalFleet[i].Name = fl.Name rep.LocalFleet[i].Groups = uint(len(gid)) rep.LocalFleet[i].Speed = mr.F(speed) rep.LocalFleet[i].State = fleetState.State.String() rep.LocalFleet[i].Destination = fleetState.Destination if inSpace, ok := fleetState.InSpace(); ok { rep.LocalFleet[i].Origin = &inSpace.Origin p2 := c.MustPlanet(rep.LocalFleet[i].Destination) rangeToDestination := mr.F(util.ShortDistance(c.g.Map.Width, c.g.Map.Height, inSpace.X.F(), inSpace.Y.F(), p2.X.F(), p2.Y.F())) rep.LocalFleet[i].Range = &rangeToDestination } i++ } } func (c *Cache) ReportLocalGroup(ri int, rep *mr.Report) { c.validateRaceIndex(ri) clear(rep.LocalGroup) i := 0 for sg := range c.listShipGroups(ri) { sliceIndexValidate(&rep.LocalGroup, i) st := c.MustShipType(ri, sg.TypeID) c.otherGroup(&rep.LocalGroup[i].OtherGroup, sg, st) rep.LocalGroup[i].ID = sg.ID rep.LocalGroup[i].State = sg.State().String() if sg.FleetID != nil { rep.LocalGroup[i].Fleet = &c.g.Fleets[c.MustFleetIndex(*sg.FleetID)].Name } // rep.LocalGroup[i].Number = sg.Number // rep.LocalGroup[i].Class = st.Name // // rep.LocalGroup[i].Tech = make(map[string]mr.Float) // for t, v := range sg.Tech { // rep.LocalGroup[i].Tech[t.String()] = mr.F(v) // } // rep.LocalGroup[i].Cargo = sg.CargoString() // rep.LocalGroup[i].Load = mr.F(sg.Load.F()) // rep.LocalGroup[i].Destination = sg.Destination // if sg.State() == game.StateInSpace { // rep.LocalGroup[i].Origin = &sg.StateInSpace.Origin // p2 := c.MustPlanet(rep.LocalGroup[i].Destination) // rangeToDestination := mr.F(util.ShortDistance(c.g.Map.Width, c.g.Map.Height, sg.StateInSpace.X.F(), sg.StateInSpace.Y.F(), p2.X.F(), p2.Y.F())) // rep.LocalGroup[i].Range = &rangeToDestination // } // rep.LocalGroup[i].Speed = mr.F(sg.Speed(st)) // rep.LocalGroup[i].Mass = mr.F(st.EmptyMass()) i++ } } func (c *Cache) ReportOtherGroup(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] clear(rep.OtherGroup) used := make(map[int]bool) skip := func(sgi int) bool { if c.ShipGroup(sgi).OwnerID == r.ID { return true } if _, ok := used[sgi]; ok { return true } used[sgi] = true return false } i := 0 // visible groups from owned and observed planets for pn := range rep.OnPlanetGroupCache { p := c.MustPlanet(pn) if p.OwnedBy(r.ID) || slices.IndexFunc(rep.OnPlanetGroupCache[pn], func(sgi int) bool { return c.ShipGroup(sgi).OwnerID == r.ID }) >= 0 { for _, sgi := range rep.OnPlanetGroupCache[pn] { sg := c.ShipGroup(sgi) st := c.ShipGroupShipClass(sgi) if skip(sgi) { continue } sliceIndexValidate(&rep.OtherGroup, i) c.otherGroup(&rep.OtherGroup[i], sg, st) i++ } } } } func (c *Cache) ReportUnidentifiedGroup(ri int, rep *mr.Report) { c.validateRaceIndex(ri) r := &c.g.Race[ri] flightDistance := r.FlightDistance() clear(rep.UnidentifiedGroup) i := 0 for sgi := range rep.InSpaceGroupRangeCache { sg := c.ShipGroup(sgi) if sg.OwnerID == rep.RaceID { continue } if sg.StateInSpace == nil { panic(fmt.Sprintf("pre-calculated distance group not in space: i=%d", sgi)) } for pi := range c.g.Map.Planet { p := &c.g.Map.Planet[pi] if !p.OwnedBy(r.ID) { continue } if v, ok := rep.InSpaceGroupRangeCache[sgi][p.Number]; !ok { panic(fmt.Sprintf("distance cache not pre-calculated: i=%d p=#%d", sgi, p.Number)) } else if v <= flightDistance { sliceIndexValidate(&rep.UnidentifiedGroup, i) rep.UnidentifiedGroup[i].X = mr.F(sg.StateInSpace.X.F()) rep.UnidentifiedGroup[i].Y = mr.F(sg.StateInSpace.Y.F()) i++ } } } } func (c *Cache) otherGroup(v *mr.OtherGroup, sg *game.ShipGroup, st *game.ShipType) { v.Number = sg.Number v.Class = st.Name // rep.LocalGroup[i].Tech = make(map[string]mr.Float) for t, val := range sg.Tech { v.Tech[t.String()] = mr.F(val.F()) } v.Cargo = sg.CargoString() v.Load = mr.F(sg.Load.F()) v.Destination = sg.Destination if sg.State() == game.StateInSpace { v.Origin = &sg.StateInSpace.Origin p2 := c.MustPlanet(v.Destination) rangeToDestination := mr.F(util.ShortDistance(c.g.Map.Width, c.g.Map.Height, sg.StateInSpace.X.F(), sg.StateInSpace.Y.F(), p2.X.F(), p2.Y.F())) v.Range = &rangeToDestination } v.Speed = mr.F(sg.Speed(st)) v.Mass = mr.F(st.EmptyMass()) } func (c *Cache) localPlanet(v *mr.LocalPlanet, p *game.Planet) { uninhabitedPlanet(&v.UninhabitedPlanet, p) v.Industry = mr.F(p.Industry.F()) v.Population = mr.F(p.Population.F()) v.Colonists = mr.F(p.Colonists.F()) v.Production = c.PlanetProductionDisplayName(p.Number) // between-turn report: ships upgrading on the planet decreases free indistrial potential v.FreeIndustry = mr.F(c.PlanetProductionCapacity(p.Number)) } func uninhabitedPlanet(v *mr.UninhabitedPlanet, p *game.Planet) { unidentifiedPlanet(&v.UnidentifiedPlanet, p) v.Size = mr.F(p.Size.F()) v.Name = p.Name v.Resources = mr.F(p.Resources.F()) v.Capital = mr.F(p.Capital.F()) v.Material = mr.F(p.Material.F()) } func unidentifiedPlanet(v *mr.UnidentifiedPlanet, p *game.Planet) { v.Number = p.Number v.X = mr.F(p.X.F()) v.Y = mr.F(p.Y.F()) } func sliceIndexValidate[S ~[]E, E any](s *S, i int) { if cap(*s) < i+1 { *s = slices.Grow(*s, 10) } if len(*s) < i+1 { *s = (*s)[:i+1] } } func boolCompare(a, b bool) int { if a == b { return 0 } if a == false { return -1 } return 1 }