feat: cargo unload challenge
This commit is contained in:
@@ -1,23 +1,5 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
/*
|
|
||||||
TODO: Препроцессинг и сохранение приказов
|
|
||||||
|
|
||||||
Когда приказ (последовательность команд) поступает на сервер, игрок получает
|
|
||||||
уведомление о том, что его команды приняты к производству. Каждая команда из
|
|
||||||
приказа проверяется на корректность и получает отдельное подтверждение. Игрок
|
|
||||||
может послать любое количество приказов по своему усмотрению, однако, каждый
|
|
||||||
новый приказ отменяет предыдущий. Таким образом, можно исправить неверно
|
|
||||||
составленный приказ, но при этом необходимо повторить те команды, которые
|
|
||||||
были отданы верно. К счастью, программа-клиент помогает игроку не запутаться
|
|
||||||
в этом процессе и берёт на себя контроль за целостностью приказов.
|
|
||||||
|
|
||||||
!!! Убедиться, что раса не покинула игру.
|
|
||||||
|
|
||||||
При производстве хода раса может быть исключена по TTL=0.
|
|
||||||
В этом случае нужно игнорировать некоторые приказы, например, передачу ей кораблей.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *Controller) MakeTurn() error {
|
func (c *Controller) MakeTurn() error {
|
||||||
|
|
||||||
if err := c.applyOrders(c.Cache.g.Turn); err != nil {
|
if err := c.applyOrders(c.Cache.g.Turn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
e "github.com/iliadenisov/galaxy/internal/error"
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/order"
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,11 +109,12 @@ func (c *Controller) applyCommand(actor string, cmd order.DecodableCommand) (err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test commands ordering
|
|
||||||
func (c *Controller) applyOrders(t uint) error {
|
func (c *Controller) applyOrders(t uint) error {
|
||||||
raceOrder := make(map[int][]order.DecodableCommand)
|
raceOrder := make(map[int][]order.DecodableCommand)
|
||||||
unloadGroups := make([]uuid.UUID, 0)
|
commandRace := make(map[string]string)
|
||||||
unloadQuantities := make([]float64, 0)
|
challenge := make(map[string]*order.CommandShipGroupUnload)
|
||||||
|
cmdApplied := make(map[string]bool)
|
||||||
|
|
||||||
for ri := range c.Cache.listRaceActingIdx() {
|
for ri := range c.Cache.listRaceActingIdx() {
|
||||||
o, ok, err := c.Repo.LoadOrder(t, c.Cache.g.Race[ri].ID)
|
o, ok, err := c.Repo.LoadOrder(t, c.Cache.g.Race[ri].ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -123,30 +125,33 @@ func (c *Controller) applyOrders(t uint) error {
|
|||||||
}
|
}
|
||||||
raceOrder[ri] = o.Commands
|
raceOrder[ri] = o.Commands
|
||||||
for i := range o.Commands {
|
for i := range o.Commands {
|
||||||
if o.Commands[i].CommandType() == order.CommandTypeShipGroupUnload {
|
commandRace[o.Commands[i].CommandID()] = c.Cache.g.Race[ri].Name
|
||||||
unloadCommand := o.Commands[i].(order.CommandShipGroupUnload)
|
if v, ok := order.AsCommand[*order.CommandShipGroupUnload](o.Commands[i]); ok {
|
||||||
unloadGroups = append(unloadGroups, uuid.MustParse(unloadCommand.ID))
|
if _, ok := challenge[v.ID]; ok {
|
||||||
unloadQuantities = append(unloadQuantities, unloadCommand.Quantity)
|
panic(fmt.Sprintf("unload command %s already cached", v.ID))
|
||||||
|
}
|
||||||
|
if ok, err := c.shouldChallenge(v); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok {
|
||||||
|
challenge[v.ID] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Cache.shipGroupUnloadColonistChallenge(unloadGroups, unloadQuantities); err != nil {
|
for _, cmdID := range c.challengeUnload(challenge) {
|
||||||
return err
|
if err := c.applyCommand(commandRace[cmdID], challenge[cmdID]); err == nil {
|
||||||
|
cmdApplied[cmdID] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ri := range raceOrder {
|
for ri := range raceOrder {
|
||||||
for _, cmd := range raceOrder[ri] {
|
for _, cmd := range raceOrder[ri] {
|
||||||
if cmd.CommandType() == order.CommandTypeShipGroupUnload {
|
if v, ok := cmdApplied[cmd.CommandID()]; ok && v {
|
||||||
// group unload commands execution should be failed at this point,
|
|
||||||
// otherwise produce no-errors
|
|
||||||
if v, ok := order.AsCommand[*order.CommandShipGroupUnload](cmd); ok {
|
|
||||||
v.Result(0)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// any command might fail due to challenged planets colonization
|
// any command might fail due to challenged planets colonization
|
||||||
_ = c.applyCommand(c.Cache.g.Race[ri].Name, cmd)
|
_ = c.applyCommand(commandRace[cmd.CommandID()], cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,3 +163,57 @@ func (c *Controller) applyOrders(t uint) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) shouldChallenge(cmd *order.CommandShipGroupUnload) (resut bool, err error) {
|
||||||
|
sgi, ok := c.Cache.shipGroupIndexByID(uuid.MustParse(cmd.ID))
|
||||||
|
if !ok {
|
||||||
|
err = e.NewGameStateError("challenge group unload: group not found: %v", cmd.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sg := c.Cache.ShipGroup(sgi)
|
||||||
|
pn, ok := sg.AtPlanet()
|
||||||
|
if !ok || sg.CargoType == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
p := c.Cache.MustPlanet(pn)
|
||||||
|
if p.Owned() || *sg.CargoType != game.CargoColonist {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) challengeUnload(challenge map[string]*order.CommandShipGroupUnload) []string {
|
||||||
|
if len(challenge) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
planetRaceQuantity := make(map[uint]map[int]float64, 0)
|
||||||
|
raceCommand := make(map[uint]map[int][]string)
|
||||||
|
for cmdID, cmd := range challenge {
|
||||||
|
sgi, ok := c.Cache.shipGroupIndexByID(uuid.MustParse(cmd.ID))
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("challenge group unload: group not found: %v", cmd.ID))
|
||||||
|
}
|
||||||
|
sg := c.Cache.ShipGroup(sgi)
|
||||||
|
ri := c.Cache.ShipGroupOwnerRaceIndex(sgi)
|
||||||
|
pn, ok := sg.AtPlanet()
|
||||||
|
if _, ok := raceCommand[pn]; !ok {
|
||||||
|
raceCommand[pn] = make(map[int][]string)
|
||||||
|
}
|
||||||
|
raceCommand[pn][ri] = append(raceCommand[pn][ri], cmdID)
|
||||||
|
if _, ok := planetRaceQuantity[pn]; !ok {
|
||||||
|
planetRaceQuantity[pn] = make(map[int]float64)
|
||||||
|
}
|
||||||
|
planetRaceQuantity[pn][ri] = planetRaceQuantity[pn][ri] + UnloadCargoRequest(float64(sg.Load), cmd.Quantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]string, 0)
|
||||||
|
for pn := range planetRaceQuantity {
|
||||||
|
if len(planetRaceQuantity[pn]) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
winner := MaxOrRandomLoadId(planetRaceQuantity[pn], func(ri int) float64 { return float64(c.Cache.g.Race[ri].Votes) })
|
||||||
|
result = append(result, raceCommand[pn][winner]...)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -171,9 +171,6 @@ func (c *Cache) listRoutedSendGroupIds(pn uint) iter.Seq[int] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Невозможно лишь выгрузить колонистов на чужой планете.
|
// Невозможно лишь выгрузить колонистов на чужой планете.
|
||||||
/*
|
|
||||||
TODO: Очерёдность выгрузки согласно правилам
|
|
||||||
*/
|
|
||||||
func (c *Cache) TurnUnloadEnroutedGroups() {
|
func (c *Cache) TurnUnloadEnroutedGroups() {
|
||||||
for i := range c.g.Map.Planet {
|
for i := range c.g.Map.Planet {
|
||||||
p := &c.g.Map.Planet[i]
|
p := &c.g.Map.Planet[i]
|
||||||
@@ -245,7 +242,7 @@ func (c *Cache) selectColUnloadGroup(groups []int) (result iter.Seq[int]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// select winner to unload cargo
|
// select winner to unload cargo
|
||||||
id := MaxOrRandomLoadId(loadByRace)
|
id := MaxOrRandomLoadId(loadByRace, func(ri int) float64 { return float64(c.g.Race[ri].Votes) })
|
||||||
result = slices.Values(groupByRace[id])
|
result = slices.Values(groupByRace[id])
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -277,23 +274,25 @@ func (c *Cache) listRoutedUnloadShipGroupIds(pn uint, routeType game.RouteType)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MaxOrRandomLoadId(loadByRace map[int]float64) int {
|
func MaxOrRandomLoadId(raceLoad map[int]float64, pop func(int) float64) int {
|
||||||
if len(loadByRace) < 2 {
|
if len(raceLoad) < 2 {
|
||||||
panic("loadByRace must contain at least 2 keys")
|
panic("loadByRace must contain at least 2 keys")
|
||||||
}
|
}
|
||||||
IDs := slices.Collect(maps.Keys(loadByRace))
|
raceIndex := slices.Collect(maps.Keys(raceLoad))
|
||||||
slices.SortFunc(IDs, func(id1, id2 int) int {
|
slices.SortFunc(raceIndex, func(ria, rib int) int {
|
||||||
return cmp.Or(
|
return cmp.Or(
|
||||||
cmp.Compare(loadByRace[id2], loadByRace[id1]),
|
// maximum quantity of unloading colonists
|
||||||
|
cmp.Compare(raceLoad[rib], raceLoad[ria]),
|
||||||
|
|
||||||
|
// maximum population of the race
|
||||||
|
cmp.Compare(pop(rib), pop(ria)),
|
||||||
|
|
||||||
|
// Random winner
|
||||||
|
cmp.Compare(rand.Float64(), rand.Float64()),
|
||||||
|
|
||||||
|
// in theoty, unreacheable option, but let's randomize again
|
||||||
|
cmp.Compare(rand.Float64(), rand.Float64()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
return raceIndex[0]
|
||||||
// no single winner with highest load
|
|
||||||
if loadByRace[IDs[0]] == loadByRace[IDs[1]] {
|
|
||||||
// remove IDs which load less than maximum
|
|
||||||
IDs = slices.DeleteFunc(IDs, func(v int) bool { return loadByRace[v] < loadByRace[IDs[0]] })
|
|
||||||
// IDs[0] will have random index
|
|
||||||
rand.Shuffle(len(IDs), func(i, j int) { IDs[i], IDs[j] = IDs[j], IDs[i] })
|
|
||||||
}
|
|
||||||
return IDs[0]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,26 +320,45 @@ func TestListRoutedUnloadShipGroupIds(t *testing.T) {
|
|||||||
func TestMaxOrRandomLoadId(t *testing.T) {
|
func TestMaxOrRandomLoadId(t *testing.T) {
|
||||||
IDtoLoad := make(map[int]float64)
|
IDtoLoad := make(map[int]float64)
|
||||||
|
|
||||||
assert.Panics(t, func() { controller.MaxOrRandomLoadId(IDtoLoad) })
|
pop := func(ri int) float64 {
|
||||||
|
switch ri {
|
||||||
|
case 1:
|
||||||
|
return 0
|
||||||
|
case 3:
|
||||||
|
return 0
|
||||||
|
case 5:
|
||||||
|
return 9.99
|
||||||
|
case 7:
|
||||||
|
return 10
|
||||||
|
case 11:
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Panics(t, func() { controller.MaxOrRandomLoadId(IDtoLoad, pop) })
|
||||||
IDtoLoad[1] = 100.
|
IDtoLoad[1] = 100.
|
||||||
assert.Panics(t, func() { controller.MaxOrRandomLoadId(IDtoLoad) })
|
assert.Panics(t, func() { controller.MaxOrRandomLoadId(IDtoLoad, pop) })
|
||||||
|
|
||||||
IDtoLoad[5] = 100.001
|
IDtoLoad[5] = 100.001
|
||||||
assert.Equal(t, 5, controller.MaxOrRandomLoadId(IDtoLoad))
|
assert.Equal(t, 5, controller.MaxOrRandomLoadId(IDtoLoad, pop))
|
||||||
|
|
||||||
IDtoLoad[3] = 100.
|
IDtoLoad[3] = 100.
|
||||||
assert.NotContains(t, []int{1, 3}, controller.MaxOrRandomLoadId(IDtoLoad))
|
assert.NotContains(t, []int{1, 3}, controller.MaxOrRandomLoadId(IDtoLoad, pop))
|
||||||
|
|
||||||
IDtoLoad[7] = 100.001
|
IDtoLoad[7] = 100.001
|
||||||
|
assert.Equal(t, 7, controller.MaxOrRandomLoadId(IDtoLoad, pop))
|
||||||
|
|
||||||
|
IDtoLoad[11] = 100.001
|
||||||
rndCount := make(map[int]int)
|
rndCount := make(map[int]int)
|
||||||
for range 100 {
|
for range 100 {
|
||||||
id := controller.MaxOrRandomLoadId(IDtoLoad)
|
id := controller.MaxOrRandomLoadId(IDtoLoad, pop)
|
||||||
assert.NotContains(t, []int{1, 3}, id)
|
assert.NotContains(t, []int{1, 3, 5}, id)
|
||||||
assert.Contains(t, []int{5, 7}, id)
|
assert.Contains(t, []int{7, 11}, id)
|
||||||
rndCount[id]++
|
rndCount[id]++
|
||||||
}
|
}
|
||||||
assert.Greater(t, rndCount[5], 10)
|
|
||||||
assert.Greater(t, rndCount[7], 10)
|
assert.Greater(t, rndCount[7], 10)
|
||||||
|
assert.Greater(t, rndCount[11], 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectColUnloadGroup(t *testing.T) {
|
func TestSelectColUnloadGroup(t *testing.T) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"maps"
|
"maps"
|
||||||
"math/rand/v2"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -283,97 +282,16 @@ func (c *Cache) shipGroupUnload(ri int, groupID uuid.UUID, quantity float64) err
|
|||||||
return e.NewEntityNotOwnedError("planet #%d unload %v", p.Number, ct)
|
return e.NewEntityNotOwnedError("planet #%d unload %v", p.Number, ct)
|
||||||
}
|
}
|
||||||
|
|
||||||
toBeUnloaded := quantity
|
c.unsafeUnloadCargo(sgi, UnloadCargoRequest(float64(c.ShipGroup(sgi).Load), quantity))
|
||||||
if quantity == 0 {
|
|
||||||
toBeUnloaded = float64(c.ShipGroup(sgi).Load)
|
|
||||||
}
|
|
||||||
if toBeUnloaded > float64(c.ShipGroup(sgi).Load) {
|
|
||||||
return e.NewCargoUnoadNotEnoughError("load: %.03f", c.ShipGroup(sgi).Load)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.unsafeUnloadCargo(sgi, toBeUnloaded)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) shipGroupUnloadColonistChallenge(groupIDs []uuid.UUID, quantites []float64) error {
|
func UnloadCargoRequest(load, quantity float64) float64 {
|
||||||
if len(groupIDs) != len(quantites) {
|
result := quantity
|
||||||
e.NewGameStateError("challenge group unload: groups=%d quantities=%d", len(groupIDs), len(quantites))
|
if result == 0 || result > load {
|
||||||
|
result = load
|
||||||
}
|
}
|
||||||
if len(groupIDs) == 0 {
|
return result
|
||||||
return nil
|
|
||||||
}
|
|
||||||
type challenger struct {
|
|
||||||
ri int
|
|
||||||
sgi int
|
|
||||||
quantity float64
|
|
||||||
}
|
|
||||||
challenge := make(map[uint]map[int][]challenger, 0)
|
|
||||||
for i, gid := range groupIDs {
|
|
||||||
sgi, ok := c.shipGroupIndexByID(gid)
|
|
||||||
if !ok {
|
|
||||||
return e.NewGameStateError("challenge group unload: group not found: %v", gid)
|
|
||||||
}
|
|
||||||
sg := c.ShipGroup(sgi)
|
|
||||||
pn, ok := sg.AtPlanet()
|
|
||||||
if !ok || sg.CargoType == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p := c.MustPlanet(pn)
|
|
||||||
ri := c.ShipGroupOwnerRaceIndex(sgi)
|
|
||||||
if p.Owned() || *sg.CargoType != game.CargoColonist {
|
|
||||||
if err := c.shipGroupUnload(ri, gid, quantites[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := challenge[pn]; !ok {
|
|
||||||
challenge[pn] = make(map[int][]challenger)
|
|
||||||
}
|
|
||||||
challenge[pn][ri] = append(challenge[pn][ri], challenger{ri: ri, sgi: sgi, quantity: quantites[i]})
|
|
||||||
}
|
|
||||||
for pn := range challenge {
|
|
||||||
if len(challenge[pn]) < 2 {
|
|
||||||
for _, v := range challenge[pn] {
|
|
||||||
for _, ch := range v {
|
|
||||||
if err := c.shipGroupUnload(ch.ri, c.ShipGroup(ch.sgi).ID, ch.quantity); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sum := make(map[int]float64)
|
|
||||||
races := slices.Collect(maps.Keys(challenge[pn]))
|
|
||||||
for ri, groups := range challenge[pn] {
|
|
||||||
for i := range groups {
|
|
||||||
sum[ri] = sum[ri] + groups[i].quantity
|
|
||||||
}
|
|
||||||
c.listProducingPlanets()
|
|
||||||
}
|
|
||||||
|
|
||||||
slices.SortFunc(races, func(ria, rib int) int {
|
|
||||||
return cmp.Or(
|
|
||||||
// Наибольшее количество выгружаемых колонистов
|
|
||||||
cmp.Compare(sum[rib], sum[ria]),
|
|
||||||
|
|
||||||
// Наибольшее количество населения расы (они же голоса)
|
|
||||||
cmp.Compare(c.g.Race[rib].Votes, c.g.Race[ria].Votes),
|
|
||||||
|
|
||||||
// Случайный выбор претендента
|
|
||||||
cmp.Compare(rand.Float64(), rand.Float64()),
|
|
||||||
|
|
||||||
// in theoty, unreacheable option, but let's randomize again
|
|
||||||
cmp.Compare(rand.Float64(), rand.Float64()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, ch := range challenge[pn][races[0]] {
|
|
||||||
if err := c.shipGroupUnload(ch.ri, c.ShipGroup(ch.sgi).ID, ch.quantity); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) shipGroupIndexByID(id uuid.UUID) (int, bool) {
|
func (c *Cache) shipGroupIndexByID(id uuid.UUID) (int, bool) {
|
||||||
|
|||||||
@@ -447,11 +447,6 @@ func TestShipGroupUnload(t *testing.T) {
|
|||||||
assert.ErrorContains(t,
|
assert.ErrorContains(t,
|
||||||
g.ShipGroupUnload(Race_0.Name, c.ShipGroup(4).ID, 0),
|
g.ShipGroupUnload(Race_0.Name, c.ShipGroup(4).ID, 0),
|
||||||
e.GenericErrorText(e.ErrInputEntityNotOwned))
|
e.GenericErrorText(e.ErrInputEntityNotOwned))
|
||||||
c.ShipGroup(0).CargoType = game.CargoColonist.Ref()
|
|
||||||
c.ShipGroup(0).Load = 100
|
|
||||||
assert.ErrorContains(t,
|
|
||||||
g.ShipGroupUnload(Race_0.Name, c.ShipGroup(0).ID, 101),
|
|
||||||
e.GenericErrorText(e.ErrInputCargoUnoadNotEnough))
|
|
||||||
|
|
||||||
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 6)
|
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 6)
|
||||||
|
|
||||||
@@ -468,7 +463,9 @@ func TestShipGroupUnload(t *testing.T) {
|
|||||||
assert.Nil(t, c.ShipGroup(5).CargoType)
|
assert.Nil(t, c.ShipGroup(5).CargoType)
|
||||||
|
|
||||||
// unload ALL
|
// unload ALL
|
||||||
assert.NoError(t, g.ShipGroupUnload(Race_0.Name, c.ShipGroup(0).ID, 0))
|
c.ShipGroup(0).CargoType = game.CargoColonist.Ref()
|
||||||
|
c.ShipGroup(0).Load = 100
|
||||||
|
assert.NoError(t, g.ShipGroupUnload(Race_0.Name, c.ShipGroup(0).ID, 101))
|
||||||
assert.Equal(t, 100.0, number.Fixed3(c.MustPlanet(R0_Planet_0_num).Colonists.F()))
|
assert.Equal(t, 100.0, number.Fixed3(c.MustPlanet(R0_Planet_0_num).Colonists.F()))
|
||||||
assert.Equal(t, 0.0, number.Fixed3(c.ShipGroup(0).Load.F()))
|
assert.Equal(t, 0.0, number.Fixed3(c.ShipGroup(0).Load.F()))
|
||||||
assert.Nil(t, c.ShipGroup(0).CargoType)
|
assert.Nil(t, c.ShipGroup(0).CargoType)
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ const (
|
|||||||
ErrInputNoCargoBay
|
ErrInputNoCargoBay
|
||||||
ErrInputCargoLoadNoSpaceLeft
|
ErrInputCargoLoadNoSpaceLeft
|
||||||
ErrInputCargoUnloadEmpty
|
ErrInputCargoUnloadEmpty
|
||||||
ErrInputCargoUnoadNotEnough
|
|
||||||
ErrInputBreakGroupIllegalNumber
|
ErrInputBreakGroupIllegalNumber
|
||||||
ErrInputTechUnknown
|
ErrInputTechUnknown
|
||||||
ErrInputTechInvalidMixing
|
ErrInputTechInvalidMixing
|
||||||
@@ -133,8 +132,6 @@ func GenericErrorText(code int) string {
|
|||||||
return "No space left on the ships to load cargo"
|
return "No space left on the ships to load cargo"
|
||||||
case ErrInputCargoUnloadEmpty:
|
case ErrInputCargoUnloadEmpty:
|
||||||
return "Ships are not carrying any cargo"
|
return "Ships are not carrying any cargo"
|
||||||
case ErrInputCargoUnoadNotEnough:
|
|
||||||
return "Not enough cargo on the ships(s)"
|
|
||||||
case ErrInputBreakGroupIllegalNumber:
|
case ErrInputBreakGroupIllegalNumber:
|
||||||
return "Illegal ships number to make new group"
|
return "Illegal ships number to make new group"
|
||||||
case ErrMergeShipTypeNotEqual:
|
case ErrMergeShipTypeNotEqual:
|
||||||
|
|||||||
@@ -100,10 +100,6 @@ func NewCargoUnloadEmptyError(arg ...any) error {
|
|||||||
return newGenericError(ErrInputCargoUnloadEmpty, arg...)
|
return newGenericError(ErrInputCargoUnloadEmpty, arg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCargoUnoadNotEnoughError(arg ...any) error {
|
|
||||||
return newGenericError(ErrInputCargoUnoadNotEnough, arg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBreakGroupIllegalNumberError(arg ...any) error {
|
func NewBreakGroupIllegalNumberError(arg ...any) error {
|
||||||
return newGenericError(ErrInputBreakGroupIllegalNumber, arg...)
|
return newGenericError(ErrInputBreakGroupIllegalNumber, arg...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const (
|
|||||||
StateLaunched ShipGroupState = "Launched"
|
StateLaunched ShipGroupState = "Launched"
|
||||||
StateInSpace ShipGroupState = "In_Space"
|
StateInSpace ShipGroupState = "In_Space"
|
||||||
StateUpgrade ShipGroupState = "Upgrade"
|
StateUpgrade ShipGroupState = "Upgrade"
|
||||||
StateTransfer ShipGroupState = "Transfer" // TODO: Группы будут передаваться мгновенно в начале производства хода
|
StateTransfer ShipGroupState = "Transfer" // [ ] Группы будут передаваться мгновенно в начале производства хода
|
||||||
)
|
)
|
||||||
|
|
||||||
func (sgs ShipGroupState) String() string {
|
func (sgs ShipGroupState) String() string {
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ func (o *Order) UnmarshalBinary(data []byte) error {
|
|||||||
return json.Unmarshal(data, o)
|
return json.Unmarshal(data, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AsCommand[E DecodableCommand](c DecodableCommand) (E, bool) {
|
func AsCommand[E DecodableCommand](c DecodableCommand) (result E, ok bool) {
|
||||||
if v, ok := c.(E); ok {
|
if v, ok := c.(E); ok {
|
||||||
return v, true
|
return v, true
|
||||||
}
|
}
|
||||||
return *new(E), false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandType string
|
type CommandType string
|
||||||
@@ -56,6 +56,7 @@ func (ct CommandType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DecodableCommand interface {
|
type DecodableCommand interface {
|
||||||
|
CommandID() string
|
||||||
CommandType() CommandType
|
CommandType() CommandType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +71,10 @@ func (cm CommandMeta) CommandType() CommandType {
|
|||||||
return cm.CmdType
|
return cm.CmdType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cm CommandMeta) CommandID() string {
|
||||||
|
return cm.CmdID
|
||||||
|
}
|
||||||
|
|
||||||
func (cm *CommandMeta) Result(errCode int) {
|
func (cm *CommandMeta) Result(errCode int) {
|
||||||
cm.CmdErrCode = &errCode
|
cm.CmdErrCode = &errCode
|
||||||
cm.CmdApplied = new(bool(errCode == 0))
|
cm.CmdApplied = new(bool(errCode == 0))
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type LocalPlanet struct {
|
|||||||
Colonists Float `json:"colonists"` // COL C - Количество колонистов
|
Colonists Float `json:"colonists"` // COL C - Количество колонистов
|
||||||
Production string `json:"production"`
|
Production string `json:"production"`
|
||||||
FreeIndustry Float `json:"freeInductry"` // Параметр "L" - Свободный производственный потенциал
|
FreeIndustry Float `json:"freeInductry"` // Параметр "L" - Свободный производственный потенциал
|
||||||
// TODO: FreeIndustry - неактуальная информация, т.к. модернизация происходит в процессе производства хода
|
// [ ] FreeIndustry - неактуальная информация, т.к. модернизация происходит в процессе производства хода
|
||||||
}
|
}
|
||||||
|
|
||||||
type UninhabitedPlanet struct {
|
type UninhabitedPlanet struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user