package controller import ( "github.com/google/uuid" e "github.com/iliadenisov/galaxy/internal/error" "github.com/iliadenisov/galaxy/internal/model/game" ) func (c *Controller) UpgradeGroup(raceName string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error { ri, err := c.Cache.raceIndex(raceName) if err != nil { return err } return c.Cache.UpgradeGroup(ri, groupIndex, techInput, limitShips, limitLevel) } func (c *Cache) UpgradeGroup(ri int, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error { c.validateRaceIndex(ri) sgi, ok := c.raceShipGroupIndex(ri, groupIndex) if !ok { return e.NewEntityNotExistsError("group #%d", groupIndex) } st := c.ShipGroupShipClass(sgi) // 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) // } // var sti int // if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == c.ShipGroup(sgi).TypeID }); sti < 0 { // // hard to test, need manual game data invalidation // return e.NewGameStateError("not found: ShipType ID=%v", c.ShipGroup(sgi).TypeID) // } // st := g.Race[ri].ShipTypes[sti] if s := c.ShipGroup(sgi).State(); s != game.StateInOrbit && s != game.StateUpgrade { return e.NewShipsBusyError() } pl := c.MustPlanet(c.ShipGroup(sgi).Destination) // pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == c.ShipGroup(sgi).Destination }) // if pl < 0 { // return e.NewGameStateError("planet #%d", c.ShipGroup(sgi).Destination) // } if pl.Owner != uuid.Nil && pl.Owner != c.g.Race[ri].ID { return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", pl.Number, groupIndex) } upgradeValidTech := map[string]game.Tech{ game.TechDrive.String(): game.TechDrive, game.TechWeapons.String(): game.TechWeapons, game.TechShields.String(): game.TechShields, game.TechCargo.String(): game.TechCargo, game.TechAll.String(): game.TechAll, } techRequest, ok := upgradeValidTech[techInput] if !ok { return e.NewTechUnknownError(techInput) } var blockMasses map[game.Tech]float64 = map[game.Tech]float64{ game.TechDrive: st.DriveBlockMass(), game.TechWeapons: st.WeaponsBlockMass(), game.TechShields: st.ShieldsBlockMass(), game.TechCargo: st.CargoBlockMass(), } switch { case techRequest != game.TechAll && blockMasses[techRequest] == 0: return e.NewUpgradeShipTechNotUsedError() case techRequest == game.TechAll && limitLevel != 0: return e.NewUpgradeParameterNotAllowedError("tech=%s max_level=%f", techRequest.String(), limitLevel) } targetLevel := make(map[game.Tech]float64) var sumLevels float64 for _, tech := range []game.Tech{game.TechDrive, game.TechWeapons, game.TechShields, game.TechCargo} { if techRequest == game.TechAll || tech == techRequest { if c.g.Race[ri].TechLevel(tech) < limitLevel { return e.NewUpgradeTechLevelInsufficientError("%s=%.03f < %.03f", tech.String(), c.g.Race[ri].TechLevel(tech), limitLevel) } targetLevel[tech] = game.FutureUpgradeLevel(c.g.Race[ri].TechLevel(tech), c.ShipGroup(sgi).TechLevel(tech), limitLevel) } else { targetLevel[tech] = game.CurrentUpgradingLevel(c.g.ShipGroups[sgi], tech) } sumLevels += targetLevel[tech] } productionCapacity := c.PlanetProductionCapacity(pl.Number) if c.ShipGroup(sgi).State() == game.StateUpgrade { // to calculate actual capacity we must substract upgrade cost of selected group, if is upgrade state productionCapacity -= c.ShipGroup(sgi).StateUpgrade.Cost() } uc := game.GroupUpgradeCost(*(c.ShipGroup(sgi)), *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo]) costForShip := uc.UpgradeCost(1) if costForShip == 0 { return e.NewUpgradeShipsAlreadyUpToDateError("%#v", targetLevel) } shipsToUpgrade := c.ShipGroup(sgi).Number // НЕ БОЛЕЕ УКАЗАННОГО if limitShips > 0 && shipsToUpgrade > limitShips { shipsToUpgrade = limitShips } maxUpgradableShips := uc.UpgradeMaxShips(productionCapacity) /* 1. считаем стоимость модернизации одного корабля 2. считаем сколько кораблей можно модернизировать 3. если не хватает даже на 1 корабль, ограничиваемся одним кораблём и пересчитываем коэффициент пропорционально массе блоков 4. иначе, считаем истинное количество кораблей с учётом ограничения maxShips */ blockMassSum := st.EmptyMass() coef := productionCapacity / costForShip if maxUpgradableShips == 0 { if limitLevel > 0 { return e.NewUpgradeInsufficientResourcesError("ship cost=%.03f L=%.03f", costForShip, productionCapacity) } sumLevels = sumLevels * coef for tech := range targetLevel { if blockMasses[tech] > 0 { proportional := sumLevels * (blockMasses[tech] / blockMassSum) targetLevel[tech] = proportional } } maxUpgradableShips = 1 } else if maxUpgradableShips > shipsToUpgrade { maxUpgradableShips = shipsToUpgrade } // sanity check uc = game.GroupUpgradeCost(*(c.ShipGroup(sgi)), *st, targetLevel[game.TechDrive], targetLevel[game.TechWeapons], targetLevel[game.TechShields], targetLevel[game.TechCargo]) costForGroup := uc.UpgradeCost(maxUpgradableShips) if costForGroup > productionCapacity { e.NewGameStateError("cost recalculation: coef=%f cost(%d)=%f L=%f", coef, maxUpgradableShips, costForGroup, productionCapacity) } // break group if needed if maxUpgradableShips < c.ShipGroup(sgi).Number { if c.ShipGroup(sgi).State() == game.StateUpgrade { return e.NewUpgradeGroupBreakNotAllowedError("ships=%d max=%d", c.ShipGroup(sgi).Number, maxUpgradableShips) } nsgi, err := c.breakGroupSafe(ri, groupIndex, maxUpgradableShips) if err != nil { return err } sgi = nsgi } // finally, fill group upgrade prefs for tech := range targetLevel { if targetLevel[tech] > 0 { c.upgradeShipGroup(sgi, tech, targetLevel[tech]) } } return nil } // func CurrentUpgradingLevel(sg game.ShipGroup, tech game.Tech) float64 { // if sg.StateUpgrade == nil { // return 0 // } // ti := slices.IndexFunc(sg.StateUpgrade.UpgradeTech, func(pref game.UpgradePreference) bool { return pref.Tech == tech }) // if ti >= 0 { // return sg.StateUpgrade.UpgradeTech[ti].Level // } // return 0 // } // func FutureUpgradeLevel(raceLevel, groupLevel, limit float64) float64 { // target := limit // if target == 0 || target > raceLevel { // target = raceLevel // } // if groupLevel == target { // return 0 // } // return target // } func (c *Cache) upgradeShipGroup(sgi int, tech game.Tech, v float64) { sg := *(c.ShipGroup(sgi)) st := c.ShipGroupShipClass(sgi) // if v <= 0 || st.BlockMass(tech) == 0 || sg.TechLevel(tech) >= v { // return // } // var su game.InUpgrade // if sg.StateUpgrade != nil { // su = *sg.StateUpgrade // } else { // su = game.InUpgrade{UpgradeTech: []game.UpgradePreference{}} // } // ti := slices.IndexFunc(su.UpgradeTech, func(pref game.UpgradePreference) bool { return pref.Tech == tech }) // if ti < 0 { // su.UpgradeTech = append(su.UpgradeTech, game.UpgradePreference{Tech: tech}) // ti = len(su.UpgradeTech) - 1 // } // su.UpgradeTech[ti].Level = v // su.UpgradeTech[ti].Cost = game.BlockUpgradeCost(st.BlockMass(tech), sg.TechLevel(tech), v) * float64(sg.Number) // sg.StateUpgrade = &su c.g.ShipGroups[sgi] = game.UpgradeGroupPreference(sg, *st, tech, v) }