package game import ( "fmt" "slices" "github.com/google/uuid" e "github.com/iliadenisov/galaxy/internal/error" ) 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 } func (st ShipType) Equal(o ShipType) bool { return st.Drive == o.Drive && st.Weapons == o.Weapons && st.Armament == o.Armament && st.Shields == o.Shields && st.Cargo == o.Cargo } func (st ShipType) BlockMass(t Tech) float64 { switch t { case TechDrive: return st.DriveBlockMass() case TechWeapons: return st.WeaponsBlockMass() case TechShields: return st.ShieldsBlockMass() case TechCargo: return st.CargoBlockMass() default: panic("BlockMass: unexpectec tech: " + t.String()) } } func (st ShipType) DriveBlockMass() float64 { return st.Drive } func (st ShipType) WeaponsBlockMass() float64 { if st.Armament == 0 || st.Weapons == 0 { return 0 } return float64(st.Armament+1) * (st.Weapons / 2) } func (st ShipType) ShieldsBlockMass() float64 { return st.Shields } func (st ShipType) CargoBlockMass() float64 { return st.Cargo } func (st ShipType) EmptyMass() float64 { shipMass := st.DriveBlockMass() + st.ShieldsBlockMass() + st.CargoBlockMass() + st.WeaponsBlockMass() return shipMass } // ProductionCost returns Material (MAT) and Population (POP) to produce this [ShipType] func (st ShipType) ProductionCost() (mat float64, pop float64) { mat = st.EmptyMass() pop = mat * 10 return } func (g Game) mustShipType(id uuid.UUID) *ShipType { for ri := range g.Race { if st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == id }); st >= 0 { return &g.Race[ri].ShipTypes[st] } } panic(fmt.Sprintf("mustShipType: ShipType not found: %v", id)) } func (g Game) ShipTypes(raceName string) ([]ShipType, error) { ri, err := g.raceIndex(raceName) if err != nil { return nil, err } return g.shipTypesInternal(ri), nil } func (g Game) shipTypesInternal(ri int) []ShipType { return g.Race[ri].ShipTypes } func (g Game) DeleteShipType(raceName, typeName string) error { ri, err := g.raceIndex(raceName) if err != nil { return err } return g.deleteShipTypeInternal(ri, typeName) } func (g Game) deleteShipTypeInternal(ri int, name string) error { st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name }) if st < 0 { return e.NewEntityNotExistsError("ship type %w", name) } if pl := slices.IndexFunc(g.Map.Planet, func(p Planet) bool { return p.Production.Type == ProductionShip && p.Production.SubjectID != nil && g.Race[ri].ShipTypes[st].ID == *p.Production.SubjectID }); pl >= 0 { return e.NewDeleteShipTypePlanetProductionError(g.Map.Planet[pl].Name) } for sg := range g.listShipGroups(ri) { if sg.TypeID == g.Race[ri].ShipTypes[st].ID { return e.NewDeleteShipTypeExistingGroupError("group: %v", sg.Index) } } g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...) return nil } func (g Game) CreateShipType(raceName, typeName string, d, w, s, c float64, a int) error { ri, err := g.raceIndex(raceName) if err != nil { return err } _, err = g.createShipTypeInternal(ri, typeName, d, w, s, c, a) return err } func (g Game) createShipTypeInternal(ri int, name string, d, w, s, c float64, a int) (int, error) { if err := checkShipTypeValues(d, w, s, c, a); err != nil { return -1, err } n, ok := validateTypeName(name) if !ok { return -1, e.NewEntityTypeNameValidationError("%q", n) } if st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name }); st >= 0 { return -1, e.NewEntityTypeNameDuplicateError("ship type %w", g.Race[ri].ShipTypes[st].Name) } g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes, ShipType{ ID: uuid.New(), ShipTypeReport: ShipTypeReport{ Name: n, Drive: d, Weapons: w, Shields: s, Cargo: c, Armament: uint(a), }, }) return len(g.Race[ri].ShipTypes) - 1, nil } func (g Game) MergeShipType(race, name, targetName string) error { ri, err := g.raceIndex(race) if err != nil { return err } return g.mergeShipTypeInternal(ri, name, targetName) } func (g Game) mergeShipTypeInternal(ri int, name, targetName string) error { st := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == name }) if st < 0 { return e.NewEntityNotExistsError("source ship type %w", name) } if name == targetName { return e.NewEntityTypeNameEqualityError("ship type %q", targetName) } tt := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.Name == targetName }) if tt < 0 { return e.NewEntityNotExistsError("target ship type %w", name) } if !g.Race[ri].ShipTypes[st].Equal(g.Race[ri].ShipTypes[tt]) { return e.NewMergeShipTypeNotEqualError() } // switch planet productions to the new type for pl := range g.Map.Planet { if g.Map.Planet[pl].Owner == g.Race[ri].ID && g.Map.Planet[pl].Production.Type == ProductionShip && g.Map.Planet[pl].Production.SubjectID != nil && *g.Map.Planet[pl].Production.SubjectID == g.Race[ri].ShipTypes[st].ID { g.Map.Planet[pl].Production.SubjectID = &g.Race[ri].ShipTypes[tt].ID } } // switch ship groups to the new type for sg := range g.ShipGroups { if g.ShipGroups[sg].OwnerID == g.Race[ri].ID && g.ShipGroups[sg].TypeID == g.Race[ri].ShipTypes[st].ID { g.ShipGroups[sg].TypeID = g.Race[ri].ShipTypes[tt].ID } } // remove the source type g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...) return nil } 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 } func ShipClass(g *Game, ri int, classID uuid.UUID) (ShipType, bool) { if len(g.Race) < ri+1 { panic(fmt.Sprintf("ShipClass: game race index %d invalid: len=%d", ri, len(g.Race))) } sti := slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == classID }) if sti < 0 { return ShipType{}, false } return g.Race[ri].ShipTypes[sti], true }