package transcoder import ( "errors" "fmt" model "galaxy/model/order" commonfbs "galaxy/schema/fbs/common" fbs "galaxy/schema/fbs/order" flatbuffers "github.com/google/flatbuffers/go" ) type encodedCommand struct { cmdID string cmdApplied *bool cmdErrCode *int payloadType fbs.CommandPayload payloadOffset flatbuffers.UOffsetT } func encodeOrderCommand(builder *flatbuffers.Builder, command model.DecodableCommand, index int) (encodedCommand, error) { if command == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil", index) } switch cmd := command.(type) { case *model.CommandRaceQuit: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } fbs.CommandRaceQuitStart(builder) payload := fbs.CommandRaceQuitEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandRaceQuit, payload), nil case *model.CommandRaceVote: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } acceptor := builder.CreateString(cmd.Acceptor) fbs.CommandRaceVoteStart(builder) fbs.CommandRaceVoteAddAcceptor(builder, acceptor) payload := fbs.CommandRaceVoteEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandRaceVote, payload), nil case *model.CommandRaceRelation: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } relation, err := relationToFBS(cmd.Relation) if err != nil { return encodedCommand{}, fmt.Errorf("encode order command %d: %w", index, err) } acceptor := builder.CreateString(cmd.Acceptor) fbs.CommandRaceRelationStart(builder) fbs.CommandRaceRelationAddAcceptor(builder, acceptor) fbs.CommandRaceRelationAddRelation(builder, relation) payload := fbs.CommandRaceRelationEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandRaceRelation, payload), nil case *model.CommandShipClassCreate: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } name := builder.CreateString(cmd.Name) fbs.CommandShipClassCreateStart(builder) fbs.CommandShipClassCreateAddName(builder, name) fbs.CommandShipClassCreateAddDrive(builder, cmd.Drive) fbs.CommandShipClassCreateAddArmament(builder, int64(cmd.Armament)) fbs.CommandShipClassCreateAddWeapons(builder, cmd.Weapons) fbs.CommandShipClassCreateAddShields(builder, cmd.Shields) fbs.CommandShipClassCreateAddCargo(builder, cmd.Cargo) payload := fbs.CommandShipClassCreateEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipClassCreate, payload), nil case *model.CommandShipClassMerge: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } name := builder.CreateString(cmd.Name) target := builder.CreateString(cmd.Target) fbs.CommandShipClassMergeStart(builder) fbs.CommandShipClassMergeAddName(builder, name) fbs.CommandShipClassMergeAddTarget(builder, target) payload := fbs.CommandShipClassMergeEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipClassMerge, payload), nil case *model.CommandShipClassRemove: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } name := builder.CreateString(cmd.Name) fbs.CommandShipClassRemoveStart(builder) fbs.CommandShipClassRemoveAddName(builder, name) payload := fbs.CommandShipClassRemoveEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipClassRemove, payload), nil case *model.CommandShipGroupBreak: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } id := builder.CreateString(cmd.ID) newID := builder.CreateString(cmd.NewID) fbs.CommandShipGroupBreakStart(builder) fbs.CommandShipGroupBreakAddId(builder, id) fbs.CommandShipGroupBreakAddNewId(builder, newID) fbs.CommandShipGroupBreakAddQuantity(builder, int64(cmd.Quantity)) payload := fbs.CommandShipGroupBreakEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipGroupBreak, payload), nil case *model.CommandShipGroupLoad: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } cargo, err := shipGroupCargoToFBS(cmd.Cargo) if err != nil { return encodedCommand{}, fmt.Errorf("encode order command %d: %w", index, err) } id := builder.CreateString(cmd.ID) fbs.CommandShipGroupLoadStart(builder) fbs.CommandShipGroupLoadAddId(builder, id) fbs.CommandShipGroupLoadAddCargo(builder, cargo) fbs.CommandShipGroupLoadAddQuantity(builder, cmd.Quantity) payload := fbs.CommandShipGroupLoadEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipGroupLoad, payload), nil case *model.CommandShipGroupUnload: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } id := builder.CreateString(cmd.ID) fbs.CommandShipGroupUnloadStart(builder) fbs.CommandShipGroupUnloadAddId(builder, id) fbs.CommandShipGroupUnloadAddQuantity(builder, cmd.Quantity) payload := fbs.CommandShipGroupUnloadEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipGroupUnload, payload), nil case *model.CommandShipGroupSend: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } id := builder.CreateString(cmd.ID) fbs.CommandShipGroupSendStart(builder) fbs.CommandShipGroupSendAddId(builder, id) fbs.CommandShipGroupSendAddDestination(builder, int64(cmd.Destination)) payload := fbs.CommandShipGroupSendEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipGroupSend, payload), nil case *model.CommandShipGroupUpgrade: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } tech, err := shipGroupUpgradeTechToFBS(cmd.Tech) if err != nil { return encodedCommand{}, fmt.Errorf("encode order command %d: %w", index, err) } id := builder.CreateString(cmd.ID) fbs.CommandShipGroupUpgradeStart(builder) fbs.CommandShipGroupUpgradeAddId(builder, id) fbs.CommandShipGroupUpgradeAddTech(builder, tech) fbs.CommandShipGroupUpgradeAddLevel(builder, cmd.Level) payload := fbs.CommandShipGroupUpgradeEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipGroupUpgrade, payload), nil case *model.CommandShipGroupMerge: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } fbs.CommandShipGroupMergeStart(builder) payload := fbs.CommandShipGroupMergeEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipGroupMerge, payload), nil case *model.CommandShipGroupDismantle: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } id := builder.CreateString(cmd.ID) fbs.CommandShipGroupDismantleStart(builder) fbs.CommandShipGroupDismantleAddId(builder, id) payload := fbs.CommandShipGroupDismantleEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipGroupDismantle, payload), nil case *model.CommandShipGroupTransfer: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } id := builder.CreateString(cmd.ID) acceptor := builder.CreateString(cmd.Acceptor) fbs.CommandShipGroupTransferStart(builder) fbs.CommandShipGroupTransferAddId(builder, id) fbs.CommandShipGroupTransferAddAcceptor(builder, acceptor) payload := fbs.CommandShipGroupTransferEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipGroupTransfer, payload), nil case *model.CommandShipGroupJoinFleet: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } id := builder.CreateString(cmd.ID) name := builder.CreateString(cmd.Name) fbs.CommandShipGroupJoinFleetStart(builder) fbs.CommandShipGroupJoinFleetAddId(builder, id) fbs.CommandShipGroupJoinFleetAddName(builder, name) payload := fbs.CommandShipGroupJoinFleetEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandShipGroupJoinFleet, payload), nil case *model.CommandFleetMerge: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } name := builder.CreateString(cmd.Name) target := builder.CreateString(cmd.Target) fbs.CommandFleetMergeStart(builder) fbs.CommandFleetMergeAddName(builder, name) fbs.CommandFleetMergeAddTarget(builder, target) payload := fbs.CommandFleetMergeEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandFleetMerge, payload), nil case *model.CommandFleetSend: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } name := builder.CreateString(cmd.Name) fbs.CommandFleetSendStart(builder) fbs.CommandFleetSendAddName(builder, name) fbs.CommandFleetSendAddDestination(builder, int64(cmd.Destination)) payload := fbs.CommandFleetSendEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandFleetSend, payload), nil case *model.CommandScienceCreate: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } name := builder.CreateString(cmd.Name) fbs.CommandScienceCreateStart(builder) fbs.CommandScienceCreateAddName(builder, name) fbs.CommandScienceCreateAddDrive(builder, cmd.Drive) fbs.CommandScienceCreateAddWeapons(builder, cmd.Weapons) fbs.CommandScienceCreateAddShields(builder, cmd.Shields) fbs.CommandScienceCreateAddCargo(builder, cmd.Cargo) payload := fbs.CommandScienceCreateEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandScienceCreate, payload), nil case *model.CommandScienceRemove: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } name := builder.CreateString(cmd.Name) fbs.CommandScienceRemoveStart(builder) fbs.CommandScienceRemoveAddName(builder, name) payload := fbs.CommandScienceRemoveEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandScienceRemove, payload), nil case *model.CommandPlanetRename: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } name := builder.CreateString(cmd.Name) fbs.CommandPlanetRenameStart(builder) fbs.CommandPlanetRenameAddNumber(builder, int64(cmd.Number)) fbs.CommandPlanetRenameAddName(builder, name) payload := fbs.CommandPlanetRenameEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandPlanetRename, payload), nil case *model.CommandPlanetProduce: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } production, err := planetProductionToFBS(cmd.Production) if err != nil { return encodedCommand{}, fmt.Errorf("encode order command %d: %w", index, err) } subject := builder.CreateString(cmd.Subject) fbs.CommandPlanetProduceStart(builder) fbs.CommandPlanetProduceAddNumber(builder, int64(cmd.Number)) fbs.CommandPlanetProduceAddProduction(builder, production) fbs.CommandPlanetProduceAddSubject(builder, subject) payload := fbs.CommandPlanetProduceEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandPlanetProduce, payload), nil case *model.CommandPlanetRouteSet: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } loadType, err := planetRouteLoadTypeToFBS(cmd.LoadType) if err != nil { return encodedCommand{}, fmt.Errorf("encode order command %d: %w", index, err) } fbs.CommandPlanetRouteSetStart(builder) fbs.CommandPlanetRouteSetAddOrigin(builder, int64(cmd.Origin)) fbs.CommandPlanetRouteSetAddDestination(builder, int64(cmd.Destination)) fbs.CommandPlanetRouteSetAddLoadType(builder, loadType) payload := fbs.CommandPlanetRouteSetEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandPlanetRouteSet, payload), nil case *model.CommandPlanetRouteRemove: if cmd == nil { return encodedCommand{}, fmt.Errorf("encode order command %d: command is nil %T", index, command) } loadType, err := planetRouteLoadTypeToFBS(cmd.LoadType) if err != nil { return encodedCommand{}, fmt.Errorf("encode order command %d: %w", index, err) } fbs.CommandPlanetRouteRemoveStart(builder) fbs.CommandPlanetRouteRemoveAddOrigin(builder, int64(cmd.Origin)) fbs.CommandPlanetRouteRemoveAddLoadType(builder, loadType) payload := fbs.CommandPlanetRouteRemoveEnd(builder) return encodedCommandFromMeta(cmd.CommandMeta, fbs.CommandPayloadCommandPlanetRouteRemove, payload), nil default: return encodedCommand{}, fmt.Errorf("encode order command %d: unsupported command type %T", index, command) } } func encodedCommandFromMeta(meta model.CommandMeta, payloadType fbs.CommandPayload, payloadOffset flatbuffers.UOffsetT) encodedCommand { return encodedCommand{ cmdID: meta.CmdID, cmdApplied: cloneBoolPointer(meta.CmdApplied), cmdErrCode: cloneIntPointer(meta.CmdErrCode), payloadType: payloadType, payloadOffset: payloadOffset, } } func decodeOrderCommand(flatCommand *fbs.CommandItem, index int) (model.DecodableCommand, error) { commandMeta := model.CommandMeta{ CmdID: string(flatCommand.CmdId()), CmdApplied: cloneBoolPointer(flatCommand.CmdApplied()), } if cmdErrCode := flatCommand.CmdErrorCode(); cmdErrCode != nil { decodedCmdErrCode, err := int64ToInt(*cmdErrCode, "cmd_error_code") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } commandMeta.CmdErrCode = &decodedCmdErrCode } payloadType := flatCommand.PayloadType() if payloadType == fbs.CommandPayloadNONE { return nil, fmt.Errorf("decode order command %d: payload type is NONE", index) } payload := new(flatbuffers.Table) if !flatCommand.Payload(payload) { return nil, fmt.Errorf("decode order command %d: payload is missing", index) } switch payloadType { case fbs.CommandPayloadCommandRaceQuit: commandMeta.CmdType = model.CommandTypeRaceQuit return &model.CommandRaceQuit{CommandMeta: commandMeta}, nil case fbs.CommandPayloadCommandRaceVote: commandMeta.CmdType = model.CommandTypeRaceVote commandPayload := new(fbs.CommandRaceVote) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandRaceVote{ CommandMeta: commandMeta, Acceptor: string(commandPayload.Acceptor()), }, nil case fbs.CommandPayloadCommandRaceRelation: commandPayload := new(fbs.CommandRaceRelation) commandPayload.Init(payload.Bytes, payload.Pos) relation, err := relationFromFBS(commandPayload.Relation()) if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } commandMeta.CmdType = model.CommandTypeRaceRelation return &model.CommandRaceRelation{ CommandMeta: commandMeta, Acceptor: string(commandPayload.Acceptor()), Relation: relation, }, nil case fbs.CommandPayloadCommandShipClassCreate: commandMeta.CmdType = model.CommandTypeShipClassCreate commandPayload := new(fbs.CommandShipClassCreate) commandPayload.Init(payload.Bytes, payload.Pos) armament, err := int64ToInt(commandPayload.Armament(), "armament") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } return &model.CommandShipClassCreate{ CommandMeta: commandMeta, Name: string(commandPayload.Name()), Drive: commandPayload.Drive(), Armament: armament, Weapons: commandPayload.Weapons(), Shields: commandPayload.Shields(), Cargo: commandPayload.Cargo(), }, nil case fbs.CommandPayloadCommandShipClassMerge: commandMeta.CmdType = model.CommandTypeShipClassMerge commandPayload := new(fbs.CommandShipClassMerge) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandShipClassMerge{ CommandMeta: commandMeta, Name: string(commandPayload.Name()), Target: string(commandPayload.Target()), }, nil case fbs.CommandPayloadCommandShipClassRemove: commandMeta.CmdType = model.CommandTypeShipClassRemove commandPayload := new(fbs.CommandShipClassRemove) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandShipClassRemove{ CommandMeta: commandMeta, Name: string(commandPayload.Name()), }, nil case fbs.CommandPayloadCommandShipGroupBreak: commandMeta.CmdType = model.CommandTypeShipGroupBreak commandPayload := new(fbs.CommandShipGroupBreak) commandPayload.Init(payload.Bytes, payload.Pos) quantity, err := int64ToInt(commandPayload.Quantity(), "quantity") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } return &model.CommandShipGroupBreak{ CommandMeta: commandMeta, ID: string(commandPayload.Id()), NewID: string(commandPayload.NewId()), Quantity: quantity, }, nil case fbs.CommandPayloadCommandShipGroupLoad: commandPayload := new(fbs.CommandShipGroupLoad) commandPayload.Init(payload.Bytes, payload.Pos) cargo, err := shipGroupCargoFromFBS(commandPayload.Cargo()) if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } commandMeta.CmdType = model.CommandTypeShipGroupLoad return &model.CommandShipGroupLoad{ CommandMeta: commandMeta, ID: string(commandPayload.Id()), Cargo: cargo, Quantity: commandPayload.Quantity(), }, nil case fbs.CommandPayloadCommandShipGroupUnload: commandMeta.CmdType = model.CommandTypeShipGroupUnload commandPayload := new(fbs.CommandShipGroupUnload) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandShipGroupUnload{ CommandMeta: commandMeta, ID: string(commandPayload.Id()), Quantity: commandPayload.Quantity(), }, nil case fbs.CommandPayloadCommandShipGroupSend: commandMeta.CmdType = model.CommandTypeShipGroupSend commandPayload := new(fbs.CommandShipGroupSend) commandPayload.Init(payload.Bytes, payload.Pos) destination, err := int64ToInt(commandPayload.Destination(), "destination") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } return &model.CommandShipGroupSend{ CommandMeta: commandMeta, ID: string(commandPayload.Id()), Destination: destination, }, nil case fbs.CommandPayloadCommandShipGroupUpgrade: commandPayload := new(fbs.CommandShipGroupUpgrade) commandPayload.Init(payload.Bytes, payload.Pos) tech, err := shipGroupUpgradeTechFromFBS(commandPayload.Tech()) if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } commandMeta.CmdType = model.CommandTypeShipGroupUpgrade return &model.CommandShipGroupUpgrade{ CommandMeta: commandMeta, ID: string(commandPayload.Id()), Tech: tech, Level: commandPayload.Level(), }, nil case fbs.CommandPayloadCommandShipGroupMerge: commandMeta.CmdType = model.CommandTypeShipGroupMerge return &model.CommandShipGroupMerge{CommandMeta: commandMeta}, nil case fbs.CommandPayloadCommandShipGroupDismantle: commandMeta.CmdType = model.CommandTypeShipGroupDismantle commandPayload := new(fbs.CommandShipGroupDismantle) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandShipGroupDismantle{ CommandMeta: commandMeta, ID: string(commandPayload.Id()), }, nil case fbs.CommandPayloadCommandShipGroupTransfer: commandMeta.CmdType = model.CommandTypeShipGroupTransfer commandPayload := new(fbs.CommandShipGroupTransfer) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandShipGroupTransfer{ CommandMeta: commandMeta, ID: string(commandPayload.Id()), Acceptor: string(commandPayload.Acceptor()), }, nil case fbs.CommandPayloadCommandShipGroupJoinFleet: commandMeta.CmdType = model.CommandTypeShipGroupJoinFleet commandPayload := new(fbs.CommandShipGroupJoinFleet) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandShipGroupJoinFleet{ CommandMeta: commandMeta, ID: string(commandPayload.Id()), Name: string(commandPayload.Name()), }, nil case fbs.CommandPayloadCommandFleetMerge: commandMeta.CmdType = model.CommandTypeFleetMerge commandPayload := new(fbs.CommandFleetMerge) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandFleetMerge{ CommandMeta: commandMeta, Name: string(commandPayload.Name()), Target: string(commandPayload.Target()), }, nil case fbs.CommandPayloadCommandFleetSend: commandMeta.CmdType = model.CommandTypeFleetSend commandPayload := new(fbs.CommandFleetSend) commandPayload.Init(payload.Bytes, payload.Pos) destination, err := int64ToInt(commandPayload.Destination(), "destination") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } return &model.CommandFleetSend{ CommandMeta: commandMeta, Name: string(commandPayload.Name()), Destination: destination, }, nil case fbs.CommandPayloadCommandScienceCreate: commandMeta.CmdType = model.CommandTypeScienceCreate commandPayload := new(fbs.CommandScienceCreate) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandScienceCreate{ CommandMeta: commandMeta, Name: string(commandPayload.Name()), Drive: commandPayload.Drive(), Weapons: commandPayload.Weapons(), Shields: commandPayload.Shields(), Cargo: commandPayload.Cargo(), }, nil case fbs.CommandPayloadCommandScienceRemove: commandMeta.CmdType = model.CommandTypeScienceRemove commandPayload := new(fbs.CommandScienceRemove) commandPayload.Init(payload.Bytes, payload.Pos) return &model.CommandScienceRemove{ CommandMeta: commandMeta, Name: string(commandPayload.Name()), }, nil case fbs.CommandPayloadCommandPlanetRename: commandMeta.CmdType = model.CommandTypePlanetRename commandPayload := new(fbs.CommandPlanetRename) commandPayload.Init(payload.Bytes, payload.Pos) number, err := int64ToInt(commandPayload.Number(), "number") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } return &model.CommandPlanetRename{ CommandMeta: commandMeta, Number: number, Name: string(commandPayload.Name()), }, nil case fbs.CommandPayloadCommandPlanetProduce: commandPayload := new(fbs.CommandPlanetProduce) commandPayload.Init(payload.Bytes, payload.Pos) production, err := planetProductionFromFBS(commandPayload.Production()) if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } number, err := int64ToInt(commandPayload.Number(), "number") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } commandMeta.CmdType = model.CommandTypePlanetProduce return &model.CommandPlanetProduce{ CommandMeta: commandMeta, Number: number, Production: production, Subject: string(commandPayload.Subject()), }, nil case fbs.CommandPayloadCommandPlanetRouteSet: commandPayload := new(fbs.CommandPlanetRouteSet) commandPayload.Init(payload.Bytes, payload.Pos) loadType, err := planetRouteLoadTypeFromFBS(commandPayload.LoadType()) if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } origin, err := int64ToInt(commandPayload.Origin(), "origin") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } destination, err := int64ToInt(commandPayload.Destination(), "destination") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } commandMeta.CmdType = model.CommandTypePlanetRouteSet return &model.CommandPlanetRouteSet{ CommandMeta: commandMeta, Origin: origin, Destination: destination, LoadType: loadType, }, nil case fbs.CommandPayloadCommandPlanetRouteRemove: commandPayload := new(fbs.CommandPlanetRouteRemove) commandPayload.Init(payload.Bytes, payload.Pos) loadType, err := planetRouteLoadTypeFromFBS(commandPayload.LoadType()) if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } origin, err := int64ToInt(commandPayload.Origin(), "origin") if err != nil { return nil, fmt.Errorf("decode order command %d: %w", index, err) } commandMeta.CmdType = model.CommandTypePlanetRouteRemove return &model.CommandPlanetRouteRemove{ CommandMeta: commandMeta, Origin: origin, LoadType: loadType, }, nil default: return nil, fmt.Errorf("decode order command %d: unknown command payload type %d", index, payloadType) } } // int64ToInt narrows v to a Go int. Returns an error when v overflows // the platform `int` range (only possible on 32-bit builds; on 64-bit // the check is a no-op). fieldName is used in the error for caller // context. func int64ToInt(value int64, field string) (int, error) { maxInt := int64(int(^uint(0) >> 1)) minInt := -maxInt - 1 if value < minInt || value > maxInt { return 0, fmt.Errorf("%s value %d overflows int", field, value) } return int(value), nil } func relationToFBS(value string) (fbs.Relation, error) { switch value { case "WAR": return fbs.RelationWAR, nil case "PEACE": return fbs.RelationPEACE, nil default: return fbs.RelationUNKNOWN, fmt.Errorf("unsupported relation value %q", value) } } func relationFromFBS(value fbs.Relation) (string, error) { switch value { case fbs.RelationWAR: return "WAR", nil case fbs.RelationPEACE: return "PEACE", nil case fbs.RelationUNKNOWN: return "", errors.New("relation value UNKNOWN is not allowed") default: return "", fmt.Errorf("unsupported relation enum value %d", value) } } func shipGroupCargoToFBS(value string) (fbs.ShipGroupCargo, error) { switch value { case "COL": return fbs.ShipGroupCargoCOL, nil case "MAT": return fbs.ShipGroupCargoMAT, nil case "CAP": return fbs.ShipGroupCargoCAP, nil default: return fbs.ShipGroupCargoUNKNOWN, fmt.Errorf("unsupported ship group cargo value %q", value) } } func shipGroupCargoFromFBS(value fbs.ShipGroupCargo) (string, error) { switch value { case fbs.ShipGroupCargoCOL: return "COL", nil case fbs.ShipGroupCargoMAT: return "MAT", nil case fbs.ShipGroupCargoCAP: return "CAP", nil case fbs.ShipGroupCargoUNKNOWN: return "", errors.New("ship group cargo value UNKNOWN is not allowed") default: return "", fmt.Errorf("unsupported ship group cargo enum value %d", value) } } func shipGroupUpgradeTechToFBS(value string) (fbs.ShipGroupUpgradeTech, error) { switch value { case "ALL": return fbs.ShipGroupUpgradeTechALL, nil case "DRIVE": return fbs.ShipGroupUpgradeTechDRIVE, nil case "WEAPONS": return fbs.ShipGroupUpgradeTechWEAPONS, nil case "SHIELDS": return fbs.ShipGroupUpgradeTechSHIELDS, nil case "CARGO": return fbs.ShipGroupUpgradeTechCARGO, nil default: return fbs.ShipGroupUpgradeTechUNKNOWN, fmt.Errorf("unsupported ship group upgrade tech value %q", value) } } func shipGroupUpgradeTechFromFBS(value fbs.ShipGroupUpgradeTech) (string, error) { switch value { case fbs.ShipGroupUpgradeTechALL: return "ALL", nil case fbs.ShipGroupUpgradeTechDRIVE: return "DRIVE", nil case fbs.ShipGroupUpgradeTechWEAPONS: return "WEAPONS", nil case fbs.ShipGroupUpgradeTechSHIELDS: return "SHIELDS", nil case fbs.ShipGroupUpgradeTechCARGO: return "CARGO", nil case fbs.ShipGroupUpgradeTechUNKNOWN: return "", errors.New("ship group upgrade tech value UNKNOWN is not allowed") default: return "", fmt.Errorf("unsupported ship group upgrade tech enum value %d", value) } } func planetProductionToFBS(value string) (fbs.PlanetProduction, error) { switch value { case "MAT": return fbs.PlanetProductionMAT, nil case "CAP": return fbs.PlanetProductionCAP, nil case "DRIVE": return fbs.PlanetProductionDRIVE, nil case "WEAPONS": return fbs.PlanetProductionWEAPONS, nil case "SHIELDS": return fbs.PlanetProductionSHIELDS, nil case "CARGO": return fbs.PlanetProductionCARGO, nil case "SCIENCE": return fbs.PlanetProductionSCIENCE, nil case "SHIP": return fbs.PlanetProductionSHIP, nil default: return fbs.PlanetProductionUNKNOWN, fmt.Errorf("unsupported planet production value %q", value) } } func planetProductionFromFBS(value fbs.PlanetProduction) (string, error) { switch value { case fbs.PlanetProductionMAT: return "MAT", nil case fbs.PlanetProductionCAP: return "CAP", nil case fbs.PlanetProductionDRIVE: return "DRIVE", nil case fbs.PlanetProductionWEAPONS: return "WEAPONS", nil case fbs.PlanetProductionSHIELDS: return "SHIELDS", nil case fbs.PlanetProductionCARGO: return "CARGO", nil case fbs.PlanetProductionSCIENCE: return "SCIENCE", nil case fbs.PlanetProductionSHIP: return "SHIP", nil case fbs.PlanetProductionUNKNOWN: return "", errors.New("planet production value UNKNOWN is not allowed") default: return "", fmt.Errorf("unsupported planet production enum value %d", value) } } func planetRouteLoadTypeToFBS(value string) (fbs.PlanetRouteLoadType, error) { switch value { case "MAT": return fbs.PlanetRouteLoadTypeMAT, nil case "CAP": return fbs.PlanetRouteLoadTypeCAP, nil case "COL": return fbs.PlanetRouteLoadTypeCOL, nil case "EMP": return fbs.PlanetRouteLoadTypeEMP, nil default: return fbs.PlanetRouteLoadTypeUNKNOWN, fmt.Errorf("unsupported planet route load type value %q", value) } } func planetRouteLoadTypeFromFBS(value fbs.PlanetRouteLoadType) (string, error) { switch value { case fbs.PlanetRouteLoadTypeMAT: return "MAT", nil case fbs.PlanetRouteLoadTypeCAP: return "CAP", nil case fbs.PlanetRouteLoadTypeCOL: return "COL", nil case fbs.PlanetRouteLoadTypeEMP: return "EMP", nil case fbs.PlanetRouteLoadTypeUNKNOWN: return "", errors.New("planet route load type value UNKNOWN is not allowed") default: return "", fmt.Errorf("unsupported planet route load type enum value %d", value) } } func cloneBoolPointer(value *bool) *bool { if value == nil { return nil } cloned := *value return &cloned } func cloneIntPointer(value *int) *int { if value == nil { return nil } cloned := *value return &cloned } // UserGamesCommandToPayload converts model.UserGamesCommand to // FlatBuffers bytes suitable for the authenticated gateway transport. // `GameID` is required. func UserGamesCommandToPayload(req *model.UserGamesCommand) ([]byte, error) { if req == nil { return nil, errors.New("encode user games command payload: request is nil") } builder := flatbuffers.NewBuilder(1024) commandsVec, err := encodeCommandItemVector(builder, req.Commands, "user games command") if err != nil { return nil, err } fbs.UserGamesCommandStart(builder) hi, lo := uuidToHiLo(req.GameID) fbs.UserGamesCommandAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo)) if commandsVec != 0 { fbs.UserGamesCommandAddCommands(builder, commandsVec) } offset := fbs.UserGamesCommandEnd(builder) fbs.FinishUserGamesCommandBuffer(builder, offset) return builder.FinishedBytes(), nil } // PayloadToUserGamesCommand converts FlatBuffers payload bytes into // model.UserGamesCommand. func PayloadToUserGamesCommand(data []byte) (result *model.UserGamesCommand, err error) { if len(data) == 0 { return nil, errors.New("decode user games command payload: data is empty") } defer func() { if recovered := recover(); recovered != nil { result = nil err = fmt.Errorf("decode user games command payload: panic recovered: %v", recovered) } }() flat := fbs.GetRootAsUserGamesCommand(data, 0) gameID := flat.GameId(nil) if gameID == nil { return nil, errors.New("decode user games command payload: game_id is missing") } out := &model.UserGamesCommand{ GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()), } count := flat.CommandsLength() if count > 0 { out.Commands = make([]model.DecodableCommand, count) flatCommand := new(fbs.CommandItem) for i := 0; i < count; i++ { if !flat.Commands(flatCommand, i) { return nil, fmt.Errorf("decode user games command %d: command item is missing", i) } cmd, decodeErr := decodeOrderCommand(flatCommand, i) if decodeErr != nil { return nil, decodeErr } out.Commands[i] = cmd } } return out, nil } // UserGamesOrderToPayload converts model.UserGamesOrder to FlatBuffers // bytes suitable for the authenticated gateway transport. func UserGamesOrderToPayload(req *model.UserGamesOrder) ([]byte, error) { if req == nil { return nil, errors.New("encode user games order payload: request is nil") } builder := flatbuffers.NewBuilder(1024) commandsVec, err := encodeCommandItemVector(builder, req.Commands, "user games order") if err != nil { return nil, err } fbs.UserGamesOrderStart(builder) hi, lo := uuidToHiLo(req.GameID) fbs.UserGamesOrderAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo)) fbs.UserGamesOrderAddUpdatedAt(builder, int64(req.UpdatedAt)) if commandsVec != 0 { fbs.UserGamesOrderAddCommands(builder, commandsVec) } offset := fbs.UserGamesOrderEnd(builder) fbs.FinishUserGamesOrderBuffer(builder, offset) return builder.FinishedBytes(), nil } // PayloadToUserGamesOrder converts FlatBuffers payload bytes into // model.UserGamesOrder. func PayloadToUserGamesOrder(data []byte) (result *model.UserGamesOrder, err error) { if len(data) == 0 { return nil, errors.New("decode user games order payload: data is empty") } defer func() { if recovered := recover(); recovered != nil { result = nil err = fmt.Errorf("decode user games order payload: panic recovered: %v", recovered) } }() flat := fbs.GetRootAsUserGamesOrder(data, 0) gameID := flat.GameId(nil) if gameID == nil { return nil, errors.New("decode user games order payload: game_id is missing") } updatedAt, convErr := int64ToInt(flat.UpdatedAt(), "updated_at") if convErr != nil { return nil, fmt.Errorf("decode user games order payload: %w", convErr) } out := &model.UserGamesOrder{ GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()), UpdatedAt: updatedAt, } count := flat.CommandsLength() if count > 0 { out.Commands = make([]model.DecodableCommand, count) flatCommand := new(fbs.CommandItem) for i := 0; i < count; i++ { if !flat.Commands(flatCommand, i) { return nil, fmt.Errorf("decode user games order %d: command item is missing", i) } cmd, decodeErr := decodeOrderCommand(flatCommand, i) if decodeErr != nil { return nil, decodeErr } out.Commands[i] = cmd } } return out, nil } // EmptyUserGamesCommandResponsePayload returns a FlatBuffers-encoded // empty `UserGamesCommandResponse` buffer. Used by gateway to ack a // successful `MessageTypeUserGamesCommand` even though the engine // returns 204 No Content — the typed envelope keeps the message-type // contract symmetric with other authenticated routes. func EmptyUserGamesCommandResponsePayload() []byte { builder := flatbuffers.NewBuilder(16) fbs.UserGamesCommandResponseStart(builder) offset := fbs.UserGamesCommandResponseEnd(builder) fbs.FinishUserGamesCommandResponseBuffer(builder, offset) return builder.FinishedBytes() } // EmptyUserGamesOrderResponsePayload mirrors // EmptyUserGamesCommandResponsePayload for `MessageTypeUserGamesOrder`. func EmptyUserGamesOrderResponsePayload() []byte { builder := flatbuffers.NewBuilder(16) fbs.UserGamesOrderResponseStart(builder) offset := fbs.UserGamesOrderResponseEnd(builder) fbs.FinishUserGamesOrderResponseBuffer(builder, offset) return builder.FinishedBytes() } // encodeCommandItemVector serialises a slice of DecodableCommand into a // FlatBuffers vector of CommandItem. Used by UserGamesCommandToPayload // and UserGamesOrderToPayload to keep the per-command encoding logic in // one place. func encodeCommandItemVector(builder *flatbuffers.Builder, commands []model.DecodableCommand, opLabel string) (flatbuffers.UOffsetT, error) { offsets := make([]flatbuffers.UOffsetT, len(commands)) for i := range commands { encoded, err := encodeOrderCommand(builder, commands[i], i) if err != nil { return 0, fmt.Errorf("encode %s: %w", opLabel, err) } cmdID := builder.CreateString(encoded.cmdID) fbs.CommandItemStart(builder) fbs.CommandItemAddCmdId(builder, cmdID) if encoded.cmdApplied != nil { fbs.CommandItemAddCmdApplied(builder, *encoded.cmdApplied) } if encoded.cmdErrCode != nil { fbs.CommandItemAddCmdErrorCode(builder, int64(*encoded.cmdErrCode)) } fbs.CommandItemAddPayloadType(builder, encoded.payloadType) fbs.CommandItemAddPayload(builder, encoded.payloadOffset) offsets[i] = fbs.CommandItemEnd(builder) } if len(offsets) == 0 { return 0, nil } // `UserGamesCommandStartCommandsVector` and the corresponding // `UserGamesOrderStartCommandsVector` are identical helpers (both // expand to `builder.StartVector(4, numElems, 4)`); we use the // command flavour for both message types so the helper has a // single dependency point. fbs.UserGamesCommandStartCommandsVector(builder, len(offsets)) for i := len(offsets) - 1; i >= 0; i-- { builder.PrependUOffsetT(offsets[i]) } return builder.EndVector(len(offsets)), nil }