package controller import ( "errors" "fmt" "github.com/google/uuid" e "github.com/iliadenisov/galaxy/internal/error" "github.com/iliadenisov/galaxy/internal/model/game" "github.com/iliadenisov/galaxy/internal/model/order" ) func (c *Controller) ValidateOrder(actor string, commands ...order.DecodableCommand) (err error) { for i := range commands { if _, ok := commands[i].(order.CommandRaceQuit); ok && i != len(commands)-1 { err = e.NewQuitCommandFollowedByCommandError() } if err != nil { return err } err = errors.Join(err, c.applyCommand(actor, commands[i])) } return } func (c *Controller) applyCommand(actor string, cmd order.DecodableCommand) (err error) { var m *order.CommandMeta if v, ok := order.AsCommand[*order.CommandRaceQuit](cmd); ok { m = &v.CommandMeta err = c.RaceQuit(actor) } else if v, ok := order.AsCommand[*order.CommandRaceVote](cmd); ok { m = &v.CommandMeta err = c.RaceVote(actor, v.Acceptor) } else if v, ok := order.AsCommand[*order.CommandRaceRelation](cmd); ok { m = &v.CommandMeta err = c.RaceRelation(actor, v.Acceptor, v.Relation) } else if v, ok := order.AsCommand[*order.CommandShipClassCreate](cmd); ok { m = &v.CommandMeta err = c.ShipClassCreate(actor, v.Name, v.Drive, int(v.Armament), v.Weapons, v.Shields, v.Cargo) } else if v, ok := order.AsCommand[*order.CommandShipClassMerge](cmd); ok { m = &v.CommandMeta err = c.ShipClassMerge(actor, v.Name, v.Target) } else if v, ok := order.AsCommand[*order.CommandShipClassRemove](cmd); ok { m = &v.CommandMeta err = c.ShipClassRemove(actor, v.Name) } else if v, ok := order.AsCommand[*order.CommandShipGroupLoad](cmd); ok { m = &v.CommandMeta err = c.ShipGroupLoad(actor, uuid.MustParse(v.ID), v.Cargo, v.Quantity) } else if v, ok := order.AsCommand[*order.CommandShipGroupUnload](cmd); ok { m = &v.CommandMeta err = c.ShipGroupUnload(actor, uuid.MustParse(v.ID), v.Quantity) } else if v, ok := order.AsCommand[*order.CommandShipGroupSend](cmd); ok { m = &v.CommandMeta err = c.ShipGroupSend(actor, uuid.MustParse(v.ID), uint(v.Destination)) } else if v, ok := order.AsCommand[*order.CommandShipGroupUpgrade](cmd); ok { m = &v.CommandMeta err = c.ShipGroupUpgrade(actor, uuid.MustParse(v.ID), v.Tech, v.Level) } else if v, ok := order.AsCommand[*order.CommandShipGroupMerge](cmd); ok { m = &v.CommandMeta err = c.ShipGroupMerge(actor) } else if v, ok := order.AsCommand[*order.CommandShipGroupBreak](cmd); ok { m = &v.CommandMeta err = c.ShipGroupBreak(actor, uuid.MustParse(v.ID), uuid.MustParse(v.NewID), uint(v.Quantity)) } else if v, ok := order.AsCommand[*order.CommandShipGroupDismantle](cmd); ok { m = &v.CommandMeta err = c.ShipGroupDismantle(actor, uuid.MustParse(v.ID)) } else if v, ok := order.AsCommand[*order.CommandShipGroupTransfer](cmd); ok { m = &v.CommandMeta err = c.ShipGroupTransfer(actor, v.Acceptor, uuid.MustParse(v.ID)) } else if v, ok := order.AsCommand[*order.CommandShipGroupJoinFleet](cmd); ok { m = &v.CommandMeta err = c.ShipGroupJoinFleet(actor, v.Name, uuid.MustParse(v.ID)) } else if v, ok := order.AsCommand[*order.CommandFleetMerge](cmd); ok { m = &v.CommandMeta err = c.FleetMerge(actor, v.Name, v.Target) } else if v, ok := order.AsCommand[*order.CommandFleetSend](cmd); ok { m = &v.CommandMeta err = c.FleetSend(actor, v.Name, uint(v.Destination)) } else if v, ok := order.AsCommand[*order.CommandScienceCreate](cmd); ok { m = &v.CommandMeta err = c.ScienceCreate(actor, v.Name, v.Drive, v.Weapons, v.Shields, v.Cargo) } else if v, ok := order.AsCommand[*order.CommandScienceRemove](cmd); ok { m = &v.CommandMeta err = c.ScienceRemove(actor, v.Name) } else if v, ok := order.AsCommand[*order.CommandPlanetRename](cmd); ok { m = &v.CommandMeta err = c.PlanetRename(actor, v.Number, v.Name) } else if v, ok := order.AsCommand[*order.CommandPlanetProduce](cmd); ok { m = &v.CommandMeta err = c.PlanetProduce(actor, v.Number, v.Production, v.Subject) } else if v, ok := order.AsCommand[*order.CommandPlanetRouteSet](cmd); ok { m = &v.CommandMeta err = c.PlanetRouteSet(actor, v.LoadType, uint(v.Origin), uint(v.Destination)) } else if v, ok := order.AsCommand[*order.CommandPlanetRouteRemove](cmd); ok { m = &v.CommandMeta err = c.PlanetRouteRemove(actor, v.LoadType, uint(v.Origin)) } else { return e.NewUnrecognizedCommandError(cmd.CommandType().String()) } if ge, ok := errors.AsType[*e.GenericError](err); ok { m.Result(ge.Code) } else if err != nil { panic(fmt.Errorf("error applying command has unknown origin: %w", err)) } else { m.Result(0) } return } func (c *Controller) applyOrders(t uint) error { raceOrder := make(map[int][]order.DecodableCommand) commandRace := make(map[string]string) challenge := make(map[string]*order.CommandShipGroupUnload) cmdApplied := make(map[string]bool) for ri := range c.Cache.listRaceActingIdx() { o, ok, err := c.Repo.LoadOrder(t, c.Cache.g.Race[ri].ID) if err != nil { return err } if !ok { continue } raceOrder[ri] = o.Commands for i := range o.Commands { commandRace[o.Commands[i].CommandID()] = c.Cache.g.Race[ri].Name if v, ok := order.AsCommand[*order.CommandShipGroupUnload](o.Commands[i]); ok { if _, ok := challenge[v.ID]; ok { 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 } } } } for _, cmdID := range c.challengeUnload(challenge) { if err := c.applyCommand(commandRace[cmdID], challenge[cmdID]); err == nil { cmdApplied[cmdID] = true } } for ri := range raceOrder { for _, cmd := range raceOrder[ri] { if v, ok := cmdApplied[cmd.CommandID()]; ok && v { continue } // any command might fail due to challenged planets colonization _ = c.applyCommand(commandRace[cmd.CommandID()], cmd) } } for ri := range c.Cache.listRaceActingIdx() { if err := c.Repo.SaveOrder(t, c.Cache.g.Race[ri].ID, &order.Order{Commands: raceOrder[ri]}); err != nil { return err } } 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 }