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

216 lines
7.6 KiB
Go

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)
}