Files
galaxy-game/internal/model/game/group_upgrade.go
T
2026-01-14 22:17:24 +02:00

244 lines
8.4 KiB
Go

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
}