package game import ( "math" "github.com/google/uuid" e "github.com/iliadenisov/galaxy/pkg/error" "github.com/iliadenisov/galaxy/pkg/number" ) type ShipTypeReport struct { Name string `json:"name"` Drive float64 `json:"drive"` Armament uint `json:"armament"` Weapons float64 `json:"weapons"` Shields float64 `json:"shields"` Cargo float64 `json:"cargo"` } type ShipType struct { ID uuid.UUID `json:"id"` ShipTypeReport } type ShipTypeReportForeign struct { RaceName string ShipTypeReport } type ShipGroup struct { TypeID uuid.UUID `json:"id"` Type ShipType `json:"-"` // TODO: fill upon load from store Number uint `json:"number"` State string `json:"state"` // TODO: kinda enum: In_Orbit, In_Space, Transfer_State, Upgrade Load float64 `json:"load"` // Cargo loaded - "Масса груза" Drive float64 `json:"drive"` Weapons float64 `json:"weapons"` Shields float64 `json:"shields"` Cargo float64 `json:"cargo"` } type Fleet struct { ShipGroups []ShipGroup `json:"group"` } // TODO: test on real values func (st ShipType) EmptyMass() float64 { shipMass := st.DriveMass() + st.ShieldsMass() + st.CargoMass() + st.WeaponsMass() return shipMass } func (st ShipType) DriveMass() float64 { return st.Drive } func (st ShipType) ShieldsMass() float64 { return st.Shields } func (st ShipType) CargoMass() float64 { return st.Cargo } func (st ShipType) WeaponsMass() float64 { return float64(st.Armament)*(st.Weapons/2) + st.Weapons/2 } // Грузоподъёмность func (sg ShipGroup) CargoCapacity() float64 { return sg.Drive * (sg.Type.Cargo + (sg.Type.Cargo*sg.Type.Cargo)/20) } // "Масса перевозимого груза" func (sg ShipGroup) CarryingMass() float64 { return sg.Load / sg.Cargo } func (sg ShipGroup) FullMass() float64 { return sg.Type.EmptyMass() + sg.CarryingMass() } // "Эффективность двигателя" // равна мощности Двигателей умноженной на текущий технологический уровень блока Двигателей func (sg ShipGroup) DriveEffective() float64 { return sg.Type.Drive * sg.Drive } // TODO: test this func (sg ShipGroup) Speed() float64 { return sg.DriveEffective() * 20 / sg.FullMass() } func (sg ShipGroup) UpgradeDriveCost(drive float64) float64 { return (1 - sg.Drive/drive) * 10 * sg.Type.Drive } // TODO: test on other values func (sg ShipGroup) UpgradeWeaponsCost(weapons float64) float64 { return (1 - sg.Weapons/weapons) * 10 * sg.Type.WeaponsMass() } func (sg ShipGroup) UpgradeShieldsCost(shields float64) float64 { return (1 - sg.Shields/shields) * 10 * sg.Type.Shields } func (sg ShipGroup) UpgradeCargoCost(cargo float64) float64 { return (1 - sg.Cargo/cargo) * 10 * sg.Type.Cargo } // Мощность бомбардировки // TODO: maybe rounding must be done only for display? func (sg ShipGroup) BombingPower() float64 { // return math.Sqrt(sg.Type.Weapons * sg.Weapons) result := (math.Sqrt(sg.Type.Weapons*sg.Weapons)/10. + 1.) * sg.Type.Weapons * sg.Weapons * float64(sg.Type.Armament) * float64(sg.Number) return number.Fixed3(result) } // TODO: test this func (fl Fleet) Speed() float64 { result := math.MaxFloat64 for _, sg := range fl.ShipGroups { if sg.Speed() < result { result = sg.Speed() } } return result } func (g Game) ShipTypes(raceName string) ([]ShipType, error) { raceID, err := g.hostRaceID(raceName) if err != nil { return nil, err } return g.shipTypesInternal(raceID) } func (g Game) shipTypesInternal(race uuid.UUID) ([]ShipType, error) { for r := range g.Race { if g.Race[r].ID == race { return g.Race[r].ShipTypes, nil } } return nil, e.NewGameStateError("ShipTypes: race %v not found", race) } func (g Game) DeleteShipType(raceName, typeName string) error { raceID, err := g.hostRaceID(raceName) if err != nil { return err } return g.deleteShipTypeInternal(raceID, typeName) } func (g Game) deleteShipTypeInternal(race uuid.UUID, name string) error { for r := range g.Race { if g.Race[r].ID == race { for st := range g.Race[r].ShipTypes { if g.Race[r].ShipTypes[st].Name == name { for sg := range g.Race[r].ShipGroups { if g.Race[r].ShipGroups[sg].TypeID == g.Race[r].ShipTypes[st].ID { return e.NewDeleteShipTypeExistingGroupError(g.Race[r].ShipGroups[sg].Number) } } for pl := range g.Map.Planet { if g.Map.Planet[pl].Owner == race && g.Map.Planet[pl].Production.Production == ProductionShip && g.Map.Planet[pl].Production.SubjectID != nil && g.Race[r].ShipTypes[st].ID == *g.Map.Planet[pl].Production.SubjectID { return e.NewDeleteShipTypePlanetProductionError(g.Map.Planet[pl].Name) } } g.Race[r].ShipTypes = append(g.Race[r].ShipTypes[:st], g.Race[r].ShipTypes[st+1:]...) return nil } } return e.NewEntityTypeNameNotExistsError("ship type %w", name) } } return e.NewGameStateError("DeleteShipType: race %v not found", race) } func (g Game) CreateShipType(raceName, typeName string, d, w, s, c float64, a int) error { raceID, err := g.hostRaceID(raceName) if err != nil { return err } return g.createShipTypeInternal(raceID, typeName, d, w, s, c, a) } func (g Game) createShipTypeInternal(race uuid.UUID, n string, d, w, s, c float64, a int) error { if err := checkShipTypeValues(d, w, s, c, a); err != nil { return err } name, ok := validateTypeName(n) if !ok { return e.NewEntityTypeNameValidationError("%q", name) } for r := range g.Race { if g.Race[r].ID == race { for st := range g.Race[r].ShipTypes { if g.Race[r].ShipTypes[st].Name == name { return e.NewEntityTypeNameDuplicateError("ship type %w", g.Race[r].ShipTypes[st].Name) } } id := uuid.New() g.Race[r].ShipTypes = append(g.Race[r].ShipTypes, ShipType{ ID: id, ShipTypeReport: ShipTypeReport{ Name: name, Drive: d, Weapons: w, Shields: s, Cargo: c, Armament: uint(a), }, }) return nil } } return e.NewGameStateError("CreateShipType: race %v not found", race) } func checkShipTypeValues(d, w, s, c float64, a int) error { if !checkShipTypeValueDWSC(d) { return e.NewDriveValueError(d) } if !checkShipTypeValueDWSC(w) { return e.NewWeaponsValueError(w) } if !checkShipTypeValueDWSC(s) { return e.NewShieldsValueError(s) } if !checkShipTypeValueDWSC(c) { return e.NewCargoValueError(s) } if a < 0 { return e.NewShipTypeArmamentValueError(a) } if (w == 0 && a > 0) || (a == 0 && w > 0) { return e.NewShipTypeArmamentAndWeaponsValueError("A=%d W=%.0f", a, w) } if d == 0 && w == 0 && s == 0 && c == 0 && a == 0 { return e.NewShipTypeShipTypeZeroValuesError() } return nil } func checkShipTypeValueDWSC(v float64) bool { return v == 0 || v >= 1 }