package controller import ( "math" "slices" "strings" 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) sg := c.ShipGroup(sgi) if s := sg.State(); s != game.StateInOrbit { // && s != game.StateUpgrade return e.NewShipsBusyError() } p := c.MustPlanet(sg.Destination) if p.Owned() && !p.OwnedBy(c.g.Race[ri].ID) { return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", p.Number, groupIndex) } upgradeValidTech := map[string]game.Tech{ strings.ToLower(game.TechDrive.String()): game.TechDrive, strings.ToLower(game.TechWeapons.String()): game.TechWeapons, strings.ToLower(game.TechShields.String()): game.TechShields, strings.ToLower(game.TechCargo.String()): game.TechCargo, strings.ToLower(game.TechAll.String()): game.TechAll, } techRequest, ok := upgradeValidTech[strings.ToLower(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] = FutureUpgradeLevel(c.g.Race[ri].TechLevel(tech), sg.TechLevel(tech).F(), limitLevel) } else { targetLevel[tech] = CurrentUpgradingLevel(sg, tech) } sumLevels += targetLevel[tech] } productionCapacity := c.PlanetProductionCapacity(p.Number) uc := GroupUpgradeCost(sg, *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 := sg.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 = GroupUpgradeCost(sg, *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 < sg.Number { 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] = UpgradeGroupPreference(sg, *st, tech, v) } // helpers type UpgradeCalc struct { Cost map[game.Tech]float64 } func (uc UpgradeCalc) UpgradeCost(ships uint) float64 { var sum float64 for _, v := range uc.Cost { sum += v } return sum * float64(ships) } func (uc UpgradeCalc) UpgradeMaxShips(resources float64) uint { return uint(math.Floor(resources / uc.UpgradeCost(1))) } func BlockUpgradeCost(blockMass, currentBlockTech, targetBlockTech float64) float64 { if blockMass == 0 || targetBlockTech <= currentBlockTech { return 0 } return (1 - currentBlockTech/targetBlockTech) * 10 * blockMass } func GroupUpgradeCost(sg *game.ShipGroup, st game.ShipType, drive, weapons, shields, cargo float64) UpgradeCalc { uc := &UpgradeCalc{Cost: make(map[game.Tech]float64)} if drive > 0 { uc.Cost[game.TechDrive] = BlockUpgradeCost(st.DriveBlockMass(), sg.TechLevel(game.TechDrive).F(), drive) } if weapons > 0 { uc.Cost[game.TechWeapons] = BlockUpgradeCost(st.WeaponsBlockMass(), sg.TechLevel(game.TechWeapons).F(), weapons) } if shields > 0 { uc.Cost[game.TechShields] = BlockUpgradeCost(st.ShieldsBlockMass(), sg.TechLevel(game.TechShields).F(), shields) } if cargo > 0 { uc.Cost[game.TechCargo] = BlockUpgradeCost(st.CargoBlockMass(), sg.TechLevel(game.TechCargo).F(), cargo) } return *uc } 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.F() } 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 UpgradeGroupPreference(sg game.ShipGroup, st game.ShipType, tech game.Tech, v float64) game.ShipGroup { if v <= 0 || st.BlockMass(tech) == 0 || sg.TechLevel(tech).F() >= v { return sg } 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 = game.F(v) su.UpgradeTech[ti].Cost = game.F(BlockUpgradeCost(st.BlockMass(tech), sg.TechLevel(tech).F(), v) * float64(sg.Number)) sg.StateUpgrade = &su return sg }