package game import ( "maps" "math" "slices" ) type UpgradeCalc struct { Cost map[Tech]float64 } func (uc UpgradeCalc) UpgradeCost(ships uint) float64 { var sum float64 for v := range maps.Values(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 ShipGroup, st ShipType, drive, weapons, shields, cargo float64) UpgradeCalc { uc := &UpgradeCalc{Cost: make(map[Tech]float64)} if drive > 0 { uc.Cost[TechDrive] = BlockUpgradeCost(st.DriveBlockMass(), sg.TechLevel(TechDrive), drive) } if weapons > 0 { uc.Cost[TechWeapons] = BlockUpgradeCost(st.WeaponsBlockMass(), sg.TechLevel(TechWeapons), weapons) } if shields > 0 { uc.Cost[TechShields] = BlockUpgradeCost(st.ShieldsBlockMass(), sg.TechLevel(TechShields), shields) } if cargo > 0 { uc.Cost[TechCargo] = BlockUpgradeCost(st.CargoBlockMass(), sg.TechLevel(TechCargo), cargo) } return *uc } // func (g *Game) UpgradeGroup(raceName string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error { // ri, err := g.raceIndex(raceName) // if err != nil { // return err // } // return g.upgradeGroupInternal(ri, groupIndex, techInput, limitShips, limitLevel) // } // func (g *Game) upgradeGroupInternal(ri int, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error { // 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 == g.ShipGroups[sgi].TypeID }); sti < 0 { // // hard to test, need manual game data invalidation // return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID) // } // st := g.Race[ri].ShipTypes[sti] // pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Number == g.ShipGroups[sgi].Destination }) // if pl < 0 { // return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination) // } // if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID { // return e.NewEntityNotOwnedError("planet #%d for upgrade group #%d", g.Map.Planet[pl].Number, groupIndex) // } // if g.ShipGroups[sgi].State() != StateInOrbit && g.ShipGroups[sgi].State() != StateUpgrade { // return e.NewShipsBusyError() // } // upgradeValidTech := map[string]Tech{ // TechDrive.String(): TechDrive, // TechWeapons.String(): TechWeapons, // TechShields.String(): TechShields, // TechCargo.String(): TechCargo, // TechAll.String(): TechAll, // } // techRequest, ok := upgradeValidTech[techInput] // if !ok { // return e.NewTechUnknownError(techInput) // } // var blockMasses map[Tech]float64 = map[Tech]float64{ // TechDrive: st.DriveBlockMass(), // TechWeapons: st.WeaponsBlockMass(), // TechShields: st.ShieldsBlockMass(), // TechCargo: st.CargoBlockMass(), // } // switch { // case techRequest != TechAll && blockMasses[techRequest] == 0: // return e.NewUpgradeShipTechNotUsedError() // case techRequest == TechAll && limitLevel != 0: // return e.NewUpgradeParameterNotAllowedError("tech=%s max_level=%f", techRequest.String(), limitLevel) // } // targetLevel := make(map[Tech]float64) // var sumLevels float64 // for _, tech := range []Tech{TechDrive, TechWeapons, TechShields, TechCargo} { // if techRequest == TechAll || tech == techRequest { // if g.Race[ri].TechLevel(tech) < limitLevel { // return e.NewUpgradeTechLevelInsufficientError("%s=%.03f < %.03f", tech.String(), g.Race[ri].TechLevel(tech), limitLevel) // } // targetLevel[tech] = FutureUpgradeLevel(g.Race[ri].TechLevel(tech), g.ShipGroups[sgi].TechLevel(tech), limitLevel) // } else { // targetLevel[tech] = CurrentUpgradingLevel(g.ShipGroups[sgi], tech) // } // sumLevels += targetLevel[tech] // } // productionCapacity := PlanetProductionCapacity(g, g.Map.Planet[pl].Number) // if g.ShipGroups[sgi].State() == StateUpgrade { // // to calculate actual capacity we must substract upgrade cost of selected group, if is upgrade state // productionCapacity -= g.ShipGroups[sgi].StateUpgrade.Cost() // } // uc := GroupUpgradeCost(g.ShipGroups[sgi], st, targetLevel[TechDrive], targetLevel[TechWeapons], targetLevel[TechShields], targetLevel[TechCargo]) // costForShip := uc.UpgradeCost(1) // if costForShip == 0 { // return e.NewUpgradeShipsAlreadyUpToDateError("%#v", targetLevel) // } // shipsToUpgrade := g.ShipGroups[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 = GroupUpgradeCost(g.ShipGroups[sgi], st, targetLevel[TechDrive], targetLevel[TechWeapons], targetLevel[TechShields], targetLevel[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 < g.ShipGroups[sgi].Number { // if g.ShipGroups[sgi].State() == StateUpgrade { // return e.NewUpgradeGroupBreakNotAllowedError("ships=%d max=%d", g.ShipGroups[sgi].Number, maxUpgradableShips) // } // nsgi, err := g.breakGroupSafe(ri, groupIndex, maxUpgradableShips) // if err != nil { // return err // } // sgi = nsgi // } // // finally, fill group upgrade prefs // for tech := range targetLevel { // if targetLevel[tech] > 0 { // g.ShipGroups[sgi] = UpgradeGroupPreference(g.ShipGroups[sgi], st, tech, targetLevel[tech]) // } // } // return nil // } func CurrentUpgradingLevel(sg ShipGroup, tech Tech) float64 { if sg.StateUpgrade == nil { return 0 } ti := slices.IndexFunc(sg.StateUpgrade.UpgradeTech, func(pref 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 UpgradeGroupPreference(sg ShipGroup, st ShipType, tech Tech, v float64) ShipGroup { if v <= 0 || st.BlockMass(tech) == 0 || sg.TechLevel(tech) >= v { return sg } var su InUpgrade if sg.StateUpgrade != nil { su = *sg.StateUpgrade } else { su = InUpgrade{UpgradeTech: []UpgradePreference{}} } ti := slices.IndexFunc(su.UpgradeTech, func(pref UpgradePreference) bool { return pref.Tech == tech }) if ti < 0 { su.UpgradeTech = append(su.UpgradeTech, UpgradePreference{Tech: tech}) ti = len(su.UpgradeTech) - 1 } su.UpgradeTech[ti].Level = v su.UpgradeTech[ti].Cost = BlockUpgradeCost(st.BlockMass(tech), sg.TechLevel(tech), v) * float64(sg.Number) sg.StateUpgrade = &su return sg }