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) if s := c.ShipGroup(sgi).State(); s != game.StateInOrbit && s != game.StateUpgrade { return e.NewShipsBusyError() } pl := c.MustPlanet(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 "compensate" upgrade cost of selected group, if it is in upgrade state // TODO: this is not tested 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 (c *Cache) UpgradeShipGroup(sgi int, tech game.Tech, v float64) { sg := *(c.ShipGroup(sgi)) st := c.ShipGroupShipClass(sgi) c.g.ShipGroups[sgi] = game.UpgradeGroupPreference(sg, *st, tech, v) }