package controller import ( "galaxy/model/report" "github.com/google/uuid" ) func TransformBattle(c *Cache, b *Battle) *report.BattleReport { r := &report.BattleReport{ ID: b.ID, Planet: b.Planet, PlanetName: c.MustPlanet(b.Planet).Name, Races: make(map[int]uuid.UUID), Ships: make(map[int]report.BattleReportGroup), Protocol: make([]report.BattleActionReport, len(b.Protocol)), } cacheShipClass := make(map[uuid.UUID]int) cacheRaceName := make(map[uuid.UUID]int) processedGroup := make(map[int]bool) addShipGroup := func(groupId int, inBattle bool) int { shipClass := c.ShipGroupShipClass(groupId) sg := c.ShipGroup(groupId) // Several ship-groups of the same race/class can take part // in the same battle (different tech upgrades, arrivals from // different planets, …). They share a single // BattleReportGroup entry keyed by ShipClass.ID — when a // later group lands on a cached class we add its Number and // NumberLeft into the existing entry instead of dropping // them, so the protocol's per-class destroy counts reconcile // with the recorded totals. `processedGroup` guards against // double-counting a single groupId across multiple shots in // the protocol — `ship()` runs on every attacker and defender // reference, the merge must happen once per groupId. if existing, ok := cacheShipClass[shipClass.ID]; ok { if !processedGroup[groupId] { bg := r.Ships[existing] bg.Number += b.InitialNumbers[groupId] bg.NumberLeft += sg.Number if inBattle { bg.InBattle = true } r.Ships[existing] = bg processedGroup[groupId] = true } return existing } itemNumber := len(r.Ships) bg := &report.BattleReportGroup{ Race: c.g.Race[c.RaceIndex(sg.OwnerID)].Name, InBattle: inBattle, Number: b.InitialNumbers[groupId], NumberLeft: sg.Number, ClassName: shipClass.Name, LoadType: sg.CargoString(), LoadQuantity: report.F(sg.Load.F()), Tech: make(map[string]report.Float, len(sg.Tech)), } for t, v := range sg.Tech { bg.Tech[t.String()] = report.F(v.F()) } r.Ships[itemNumber] = *bg cacheShipClass[shipClass.ID] = itemNumber processedGroup[groupId] = true return itemNumber } ship := func(groupId int) int { return addShipGroup(groupId, true) } race := func(groupId int) int { race := c.ShipGroupOwnerRace(groupId) if v, ok := cacheRaceName[race.ID]; ok { return v } else { itemNumber := len(r.Races) r.Races[itemNumber] = race.ID cacheRaceName[race.ID] = itemNumber return itemNumber } } for i := range b.Protocol { r.Protocol[i] = report.BattleActionReport{ Attacker: race(b.Protocol[i].Attacker), AttackerShipClass: ship(b.Protocol[i].Attacker), Defender: race(b.Protocol[i].Defender), DefenderShipClass: ship(b.Protocol[i].Defender), Destroyed: b.Protocol[i].Destroyed, } } for sgi, inBattle := range b.ObserverGroups { if !inBattle { addShipGroup(sgi, false) } } return r }