diff --git a/internal/error/generic.go b/internal/error/generic.go index 6ca66b5..54122a4 100644 --- a/internal/error/generic.go +++ b/internal/error/generic.go @@ -41,10 +41,14 @@ const ( ErrInputScienceSumValues ErrInputProductionInvalid ErrInputCargoTypeInvalid + ErrInputCargoQuantityWithoutGroupBreak ErrInputCargoLoadNotEnough ErrInputCargoLoadNotEqual - ErrInputCargoLoadNoCargoBay + ErrInputNoCargoBay ErrInputCargoLoadNoSpaceLeft + ErrInputCargoUnloadEmpty + ErrInputCargoUnoadNotEnough + ErrInputBreakGroupIllegalNumber ) func GenericErrorText(code int) string { @@ -99,14 +103,22 @@ func GenericErrorText(code int) string { return "Invalid Production type" case ErrInputCargoTypeInvalid: return "Invalid cargo type" + case ErrInputCargoQuantityWithoutGroupBreak: + return "Cargo quantity should be specified only for a new group number of ships" case ErrInputCargoLoadNotEnough: return "Not enough cargo to load" case ErrInputCargoLoadNotEqual: return "Ship(s) already loaded with another cargo" - case ErrInputCargoLoadNoCargoBay: + case ErrInputNoCargoBay: return "Ship type is not designed to carry cargo" case ErrInputCargoLoadNoSpaceLeft: return "No space left on the ships to load cargo" + case ErrInputCargoUnloadEmpty: + return "Ships are not carrying any cargo" + case ErrInputCargoUnoadNotEnough: + return "Not enough cargo on the ships(s)" + case ErrInputBreakGroupIllegalNumber: + return "Illegal ships number to make new group" case ErrMergeShipTypeNotEqual: return "Source and target ship types are not the same" case ErrJoinFleetGroupNumberNotEnough: diff --git a/internal/error/input.go b/internal/error/input.go index fb421b2..94e6922 100644 --- a/internal/error/input.go +++ b/internal/error/input.go @@ -76,6 +76,10 @@ func NewCargoTypeInvalidError(arg ...any) error { return newGenericError(ErrInputCargoTypeInvalid, arg...) } +func NewCargoQuantityWithoutGroupBreakError(arg ...any) error { + return newGenericError(ErrInputCargoQuantityWithoutGroupBreak, arg...) +} + func NewCargoLoadNotEnoughError(arg ...any) error { return newGenericError(ErrInputCargoLoadNotEnough, arg...) } @@ -84,14 +88,26 @@ func NewCargoLoadNotEqualError(arg ...any) error { return newGenericError(ErrInputCargoLoadNotEqual, arg...) } -func NewCargoLoadNoCargoBayError(arg ...any) error { - return newGenericError(ErrInputCargoLoadNoCargoBay, arg...) +func NewNoCargoBayError(arg ...any) error { + return newGenericError(ErrInputNoCargoBay, arg...) } func NewCargoLoadNoSpaceLeftError(arg ...any) error { return newGenericError(ErrInputCargoLoadNoSpaceLeft, arg...) } +func NewCargoUnloadEmptyError(arg ...any) error { + return newGenericError(ErrInputCargoUnloadEmpty, arg...) +} + +func NewCargoUnoadNotEnoughError(arg ...any) error { + return newGenericError(ErrInputCargoUnoadNotEnough, arg...) +} + +func NewBreakGroupIllegalNumberError(arg ...any) error { + return newGenericError(ErrInputBreakGroupIllegalNumber, arg...) +} + func NewMergeShipTypeNotEqualError(arg ...any) error { return newGenericError(ErrMergeShipTypeNotEqual, arg...) } diff --git a/internal/model/game/fleet.go b/internal/model/game/fleet.go index bb6be8e..2161266 100644 --- a/internal/model/game/fleet.go +++ b/internal/model/game/fleet.go @@ -90,6 +90,7 @@ func (g *Game) joinShipGroupToFleetInternal(ri int, fleetName string, group, cou } } + // FIXME: if g.ShipGroups[sgi].FleetID != nil { // delete old fleet if empty, ALSO mind breaking group } if count > 0 && g.ShipGroups[sgi].Number != count { newGroup := g.ShipGroups[sgi] newGroup.Number -= count diff --git a/internal/model/game/group.go b/internal/model/game/group.go index 60ab39c..badff67 100644 --- a/internal/model/game/group.go +++ b/internal/model/game/group.go @@ -145,7 +145,7 @@ func (g *Game) BreakGroup(raceName string, groupIndex, quantity uint) error { return g.breakGroupInternal(ri, groupIndex, quantity) } -func (g *Game) LoadCargo(raceName string, groupIndex uint, cargoType string, quantity float64) error { +func (g *Game) LoadCargo(raceName string, groupIndex uint, cargoType string, ships uint, quantity float64) error { ri, err := g.raceIndex(raceName) if err != nil { return err @@ -154,10 +154,102 @@ func (g *Game) LoadCargo(raceName string, groupIndex uint, cargoType string, qua if !ok { return e.NewCargoTypeInvalidError(cargoType) } - return g.loadCargoInternal(ri, groupIndex, ct, quantity) + return g.loadCargoInternal(ri, groupIndex, ct, ships, quantity) } -func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, quantity float64) error { +func (g *Game) UnloadCargo(raceName string, groupIndex uint, ships uint, quantity float64) error { + ri, err := g.raceIndex(raceName) + if err != nil { + return err + } + return g.unloadCargoInternal(ri, groupIndex, ships, quantity) +} + +// Промышленность и Сырье могут быть выгружены на любой планете. +// Колонисты могут быть высажены только на планеты, принадлежащие Вам или на необитаемые планеты. +func (g *Game) unloadCargoInternal(ri int, groupIndex uint, ships uint, quantity float64) error { + if ships == 0 && quantity > 0 { + return e.NewCargoQuantityWithoutGroupBreakError() + } + 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) + } + if g.ShipGroups[sgi].State != "In_Orbit" || g.ShipGroups[sgi].Origin != nil || g.ShipGroups[sgi].Range != nil { + return e.NewShipsBusyError() + } + 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) + } + if g.Race[ri].ShipTypes[sti].Cargo < 1 { + return e.NewNoCargoBayError("ship_type %q", g.Race[ri].ShipTypes[sti].Name) + } + if g.ShipGroups[sgi].CargoType == nil || g.ShipGroups[sgi].Load == 0 { + return e.NewCargoUnloadEmptyError() + } + ct := *g.ShipGroups[sgi].CargoType + 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 ct == CargoColonist { + if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID { + return e.NewEntityNotOwnedError("planet #%d unload %v", g.Map.Planet[pl].Number, ct) + } + if g.Map.Planet[pl].Owner == uuid.Nil { + g.Map.Planet[pl].Owner = g.Race[ri].ID + } + } + var availableOnPlanet *float64 + switch ct { + case CargoMaterial: + availableOnPlanet = &g.Map.Planet[pl].Material + case CargoCapital: + availableOnPlanet = &g.Map.Planet[pl].Capital + case CargoColonist: + availableOnPlanet = &g.Map.Planet[pl].Colonists + default: + return e.NewGameStateError("CargoType not accepted: %v", ct) + } + if ships > 0 && ships < g.ShipGroups[sgi].Number { + nsgi, err := g.breakGroupSafe(ri, groupIndex, ships) + if err != nil { + return err + } + sgi = nsgi + } + toBeUnloaded := quantity + if quantity == 0 { + toBeUnloaded = g.ShipGroups[sgi].Load + } + if toBeUnloaded > g.ShipGroups[sgi].Load { + return e.NewCargoUnoadNotEnoughError("load: %.03f", g.ShipGroups[sgi].Load) + } + *availableOnPlanet += toBeUnloaded + g.ShipGroups[sgi].Load -= toBeUnloaded + if g.ShipGroups[sgi].Load == 0 { + g.ShipGroups[sgi].CargoType = nil + } + return nil +} + +/* + TODO: multi-command processing: Game data should NOT be changed when error is returned +*/ +// Корабль может нести только один тип груза одновременно. +// Возможные типы груза - это колонисты, сырье и промышленность. +// Груз может быть доставлен на борт корабля с Вашей или не занятой планеты, на которой он имеется. +func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, ships uint, quantity float64) error { + if ships == 0 && quantity > 0 { + return e.NewCargoQuantityWithoutGroupBreakError() + } sgi := -1 for i, sg := range g.listIndexShipGroups(ri) { if sgi < 0 && sg.Index == groupIndex { @@ -174,8 +266,8 @@ func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, quantity if pl < 0 { return e.NewGameStateError("planet #%d", g.ShipGroups[sgi].Destination) } - if g.Map.Planet[pl].Owner != g.Race[ri].ID { - return e.NewEntityNotOwnedError("planet %#d", g.Map.Planet[pl].Number) + if g.Map.Planet[pl].Owner != uuid.Nil && g.Map.Planet[pl].Owner != g.Race[ri].ID { + return e.NewEntityNotOwnedError("planet #%d", g.Map.Planet[pl].Number) } var sti int if sti = slices.IndexFunc(g.Race[ri].ShipTypes, func(st ShipType) bool { return st.ID == g.ShipGroups[sgi].TypeID }); sti < 0 { @@ -183,11 +275,18 @@ func (g *Game) loadCargoInternal(ri int, groupIndex uint, ct CargoType, quantity return e.NewGameStateError("not found: ShipType ID=%v", g.ShipGroups[sgi].TypeID) } if g.Race[ri].ShipTypes[sti].Cargo < 1 { - return e.NewCargoLoadNoCargoBayError("ship_type %q", g.Race[ri].ShipTypes[sti].Name) + return e.NewNoCargoBayError("ship_type %q", g.Race[ri].ShipTypes[sti].Name) } if g.ShipGroups[sgi].CargoType != nil && *g.ShipGroups[sgi].CargoType != ct { return e.NewCargoLoadNotEqualError("cargo: %v", *g.ShipGroups[sgi].CargoType) } + if ships > 0 && ships < g.ShipGroups[sgi].Number { + nsgi, err := g.breakGroupSafe(ri, groupIndex, ships) + if err != nil { + return err + } + sgi = nsgi + } capacity := g.ShipGroups[sgi].CargoCapacity(&g.Race[ri].ShipTypes[sti]) freeShipGroupCargoLoad := capacity - g.ShipGroups[sgi].Load if freeShipGroupCargoLoad == 0 { @@ -338,21 +437,44 @@ func (g *Game) breakGroupInternal(ri int, groupIndex, quantity uint) error { if quantity == 0 || quantity == g.ShipGroups[sgi].Number { g.ShipGroups[sgi].FleetID = nil } else { - newGroup := g.ShipGroups[sgi] - if g.ShipGroups[sgi].CargoType != nil { - newGroup.Load = g.ShipGroups[sgi].Load / float64(g.ShipGroups[sgi].Number) * float64(quantity) - g.ShipGroups[sgi].Load -= newGroup.Load + if _, err := g.breakGroupSafe(ri, groupIndex, quantity); err != nil { + return err } - newGroup.Number = quantity - g.ShipGroups[sgi].Number -= newGroup.Number - newGroup.Index = maxIndex + 1 - newGroup.FleetID = nil - g.ShipGroups = append(g.ShipGroups, newGroup) } return nil } +func (g *Game) breakGroupSafe(ri int, groupIndex uint, newGroupShips uint) (int, error) { + sgi := -1 + var maxIndex uint + for i, sg := range g.listIndexShipGroups(ri) { + if sgi < 0 && sg.Index == groupIndex { + sgi = i + } + if sg.Index > maxIndex { + maxIndex = sg.Index + } + } + if sgi < 0 { + return -1, e.NewEntityNotExistsError("group #%d", groupIndex) + } + if g.ShipGroups[sgi].Number < newGroupShips { + return -1, e.NewBreakGroupIllegalNumberError("group #%d ships: %d -> %d", g.ShipGroups[sgi].Index, g.ShipGroups[sgi].Number, newGroupShips) + } + newGroup := g.ShipGroups[sgi] + if g.ShipGroups[sgi].CargoType != nil { + newGroup.Load = g.ShipGroups[sgi].Load / float64(g.ShipGroups[sgi].Number) * float64(newGroupShips) + g.ShipGroups[sgi].Load -= newGroup.Load + } + newGroup.Number = newGroupShips + g.ShipGroups[sgi].Number -= newGroup.Number + newGroup.Index = maxIndex + 1 + newGroup.FleetID = nil + g.ShipGroups = append(g.ShipGroups, newGroup) + return len(g.ShipGroups) - 1, nil +} + func (g *Game) joinEqualGroupsInternal(ri int) { shipGroups := slices.Collect(maps.Values(maps.Collect(g.listIndexShipGroups(ri)))) origin := len(shipGroups) @@ -385,7 +507,7 @@ func (g *Game) createShips(ri int, shipTypeName string, planetNumber int, quanti return e.NewEntityNotExistsError("planet #%d", planetNumber) } if g.Map.Planet[pl].Owner != g.Race[ri].ID { - return e.NewEntityNotOwnedError("planet %#d", planetNumber) + return e.NewEntityNotOwnedError("planet #%d", planetNumber) } var maxIndex uint diff --git a/internal/model/game/group_test.go b/internal/model/game/group_test.go index 5d52af7..6cb6d6e 100644 --- a/internal/model/game/group_test.go +++ b/internal/model/game/group_test.go @@ -507,61 +507,193 @@ func TestLoadCargo(t *testing.T) { assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) g.ShipGroups[4].Destination = R1_Planet_1_num + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 5) + // tests assert.ErrorContains(t, - g.LoadCargo("UnknownRace", 1, game.CargoMaterial.String(), 0), + g.LoadCargo("UnknownRace", 1, game.CargoMaterial.String(), 0, 0), e.GenericErrorText(e.ErrInputUnknownRace)) assert.ErrorContains(t, - g.LoadCargo(Race_0.Name, 1, "GOLD", 0), + g.LoadCargo(Race_0.Name, 1, "GOLD", 0, 0), e.GenericErrorText(e.ErrInputCargoTypeInvalid)) assert.ErrorContains(t, - g.LoadCargo(Race_0.Name, 555, game.CargoMaterial.String(), 0), + g.LoadCargo(Race_0.Name, 555, game.CargoMaterial.String(), 0, 0), e.GenericErrorText(e.ErrInputEntityNotExists)) assert.ErrorContains(t, - g.LoadCargo(Race_0.Name, 3, game.CargoMaterial.String(), 0), + g.LoadCargo(Race_0.Name, 3, game.CargoMaterial.String(), 0, 0), e.GenericErrorText(e.ErrShipsBusy)) assert.ErrorContains(t, - g.LoadCargo(Race_0.Name, 5, game.CargoMaterial.String(), 0), + g.LoadCargo(Race_0.Name, 5, game.CargoMaterial.String(), 0, 0), e.GenericErrorText(e.ErrInputEntityNotOwned)) assert.ErrorContains(t, - g.LoadCargo(Race_0.Name, 2, game.CargoMaterial.String(), 0), - e.GenericErrorText(e.ErrInputCargoLoadNoCargoBay)) + g.LoadCargo(Race_0.Name, 2, game.CargoMaterial.String(), 0, 0), + e.GenericErrorText(e.ErrInputNoCargoBay)) assert.ErrorContains(t, - g.LoadCargo(Race_0.Name, 4, game.CargoMaterial.String(), 0), + g.LoadCargo(Race_0.Name, 4, game.CargoMaterial.String(), 0, 0), e.GenericErrorText(e.ErrInputCargoLoadNotEqual)) // initial planet is empty assert.ErrorContains(t, - g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0), + g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0), e.GenericErrorText(e.ErrInputCargoLoadNotEnough)) // add cargo to planet g.Map.Planet[0].Material = 100 // not enough on the planet assert.ErrorContains(t, - g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 101), + g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 101), e.GenericErrorText(e.ErrInputCargoLoadNotEnough)) + // quantity > ships + assert.ErrorContains(t, + g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 1), + e.GenericErrorText(e.ErrInputCargoQuantityWithoutGroupBreak)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 5) + + // break group and load maximum + assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 2, 0)) + assert.Equal(t, 58.0, g.Map.Planet[0].Material) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 6) + assert.Nil(t, g.ShipGroups[0].CargoType) + assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[5].CargoType) + assert.Equal(t, uint(9), g.ShipGroups[0].Number) + assert.Equal(t, 0.0, g.ShipGroups[0].Load) + assert.Equal(t, uint(2), g.ShipGroups[5].Number) + assert.Equal(t, 42.0, g.ShipGroups[5].Load) + + // break group and load limited + assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 2, 18)) + assert.Equal(t, 40.0, g.Map.Planet[0].Material) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) + assert.Nil(t, g.ShipGroups[0].CargoType) + assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[6].CargoType) + assert.Equal(t, uint(7), g.ShipGroups[0].Number) + assert.Equal(t, 0.0, g.ShipGroups[0].Load) + assert.Equal(t, uint(2), g.ShipGroups[6].Number) + assert.Equal(t, 18.0, g.ShipGroups[6].Load) + + // add cargo to planet + g.Map.Planet[0].Material = 100 // loading all available cargo - assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0)) + assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) assert.Equal(t, 0.0, g.Map.Planet[0].Material) assert.Equal(t, 100.0, g.ShipGroups[0].Load) // free: 131.0 assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[0].CargoType) // add cargo to planet g.Map.Planet[0].Material = 200 - assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 31)) + assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 31)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) assert.Equal(t, 169.0, g.Map.Planet[0].Material) assert.Equal(t, 131.0, g.ShipGroups[0].Load) // free: 100.0 assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[0].CargoType) // load to maximum cargo space left - assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0)) - assert.Equal(t, 69.0, g.Map.Planet[0].Material) - assert.Equal(t, 231.0, g.ShipGroups[0].Load) // free: 0.0 + assert.NoError(t, g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 11, 0)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) + assert.Equal(t, 153.0, g.Map.Planet[0].Material) + assert.Equal(t, 147.0, g.ShipGroups[0].Load) // free: 0.0 assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[0].CargoType) // ship group is full assert.ErrorContains(t, - g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0), + g.LoadCargo(Race_0.Name, 1, game.CargoMaterial.String(), 0, 0), e.GenericErrorText(e.ErrInputCargoLoadNoSpaceLeft)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) +} + +func TestUnloadCargo(t *testing.T) { + g := copyGame() + + // 1: idx = 0 / Ready to unload COL + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 10)) + + // 2: idx = 1 / Has no cargo bay + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 1)) + + // 3: idx = 2 / In_Space + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 7)) + g.ShipGroups[2].Origin = &R0_Planet_2_num + rng := 31.337 + g.ShipGroups[2].Range = &rng + g.ShipGroups[2].State = "In_Space" + + // 4: idx = 3 / loaded with COL + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) + g.ShipGroups[3].CargoType = game.CargoColonist.Ref() + g.ShipGroups[3].Load = 1.234 + + // 5: idx = 4 / on foreign planet / loaded with COL + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) + g.ShipGroups[4].Destination = R1_Planet_1_num + g.ShipGroups[4].CargoType = game.CargoColonist.Ref() + g.ShipGroups[4].Load = 1.234 + + // 6: idx = 5 / on foreign planet / loaded with MAT + assert.NoError(t, g.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_0_num, 11)) + g.ShipGroups[5].Destination = R1_Planet_1_num + g.ShipGroups[5].CargoType = game.CargoMaterial.Ref() + g.ShipGroups[5].Load = 100.0 + + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 6) + + // tests + assert.ErrorContains(t, + g.UnloadCargo("UnknownRace", 1, 0, 0), + e.GenericErrorText(e.ErrInputUnknownRace)) + assert.ErrorContains(t, + g.UnloadCargo(Race_0.Name, 555, 0, 0), + e.GenericErrorText(e.ErrInputEntityNotExists)) + assert.ErrorContains(t, + g.UnloadCargo(Race_0.Name, 3, 0, 0), + e.GenericErrorText(e.ErrShipsBusy)) + assert.ErrorContains(t, + g.UnloadCargo(Race_0.Name, 2, 0, 0), + e.GenericErrorText(e.ErrInputNoCargoBay)) + assert.ErrorContains(t, + g.UnloadCargo(Race_0.Name, 1, 0, 0), + e.GenericErrorText(e.ErrInputCargoUnloadEmpty)) + assert.ErrorContains(t, + g.UnloadCargo(Race_0.Name, 5, 0, 0), + e.GenericErrorText(e.ErrInputEntityNotOwned)) + g.ShipGroups[0].CargoType = game.CargoColonist.Ref() + g.ShipGroups[0].Load = 100 + assert.ErrorContains(t, + g.UnloadCargo(Race_0.Name, 1, 11, 101), + e.GenericErrorText(e.ErrInputCargoUnoadNotEnough)) + assert.ErrorContains(t, + g.UnloadCargo(Race_0.Name, 1, 0, 1), + e.GenericErrorText(e.ErrInputCargoQuantityWithoutGroupBreak)) + + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 6) + + // unload MAT on foreign planet / break group + assert.NoError(t, g.UnloadCargo(Race_0.Name, 6, 3, 0)) + assert.Equal(t, 27.273, number.Fixed3(g.Map.Planet[1].Material)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 7) + assert.Equal(t, uint(3), g.ShipGroups[6].Number) + assert.Nil(t, g.ShipGroups[6].CargoType) + assert.Equal(t, 0.0, g.ShipGroups[6].Load) + assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[5].CargoType) + assert.Equal(t, uint(8), g.ShipGroups[5].Number) + assert.Equal(t, 72.727, number.Fixed3(g.ShipGroups[5].Load)) + + // unload MAT on foreign planet / break group / limited MAT + assert.NoError(t, g.UnloadCargo(Race_0.Name, 6, 3, 20.0)) + assert.Equal(t, 47.273, number.Fixed3(g.Map.Planet[1].Material)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 8) + assert.Equal(t, uint(3), g.ShipGroups[7].Number) + assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[7].CargoType) + assert.Equal(t, 7.273, number.Fixed3(g.ShipGroups[7].Load)) + assert.Equal(t, game.CargoMaterial.Ref(), g.ShipGroups[5].CargoType) + assert.Equal(t, uint(5), g.ShipGroups[5].Number) + assert.Equal(t, 45.455, number.Fixed3(g.ShipGroups[5].Load)) + + // unload ALL + assert.NoError(t, g.UnloadCargo(Race_0.Name, 1, 0, 0)) + assert.Equal(t, 100.0, number.Fixed3(g.Map.Planet[0].Colonists)) + assert.Len(t, slices.Collect(g.ListShipGroups(Race_0_idx)), 8) + assert.Equal(t, uint(10), g.ShipGroups[0].Number) + assert.Nil(t, g.ShipGroups[0].CargoType) + assert.Equal(t, 0.0, number.Fixed3(g.ShipGroups[0].Load)) } diff --git a/internal/model/game/planet.go b/internal/model/game/planet.go index 1e82ee9..e9cd69e 100644 --- a/internal/model/game/planet.go +++ b/internal/model/game/planet.go @@ -33,7 +33,7 @@ type PlanetReport struct { } type Planet struct { - Owner uuid.UUID `json:"owner"` + Owner uuid.UUID `json:"owner"` // FIXME: nil value when no owner PlanetReport } @@ -98,7 +98,7 @@ func (g Game) renamePlanetInternal(ri int, number int, name string) error { return e.NewEntityNotExistsError("planet #%d", number) } if g.Map.Planet[pl].Owner != g.Race[ri].ID { - return e.NewEntityNotOwnedError("planet %#d", number) + return e.NewEntityNotOwnedError("planet #%d", number) } g.Map.Planet[pl].Name = n return nil diff --git a/internal/model/game/production.go b/internal/model/game/production.go index 9503059..3f11d71 100644 --- a/internal/model/game/production.go +++ b/internal/model/game/production.go @@ -76,7 +76,7 @@ func (g Game) planetProductionInternal(ri int, number int, prod PlanetProduction return e.NewEntityNotExistsError("planet #%d", number) } if g.Map.Planet[i].Owner != g.Race[ri].ID { - return e.NewEntityNotOwnedError("planet %#d", number) + return e.NewEntityNotOwnedError("planet #%d", number) } g.Map.Planet[i].Production.Progress = nil var subjectID *uuid.UUID