diff --git a/Makefile b/Makefile index b73faf0..3aedd83 100644 --- a/Makefile +++ b/Makefile @@ -18,12 +18,9 @@ gen: protoc --proto_path proto --go_out=plugins=grpc,paths=source_relative:proto/ proto/roveapi/roveapi.proto test: - @echo Unit tests - go test -v ./... - - @echo Integration tests - docker-compose up --build --exit-code-from=rove-tests --abort-on-container-exit rove-tests - docker-compose down + @echo Run unit and integration tests + docker-compose -f docker-compose-test.yml up --build --exit-code-from=rove-tests --abort-on-container-exit rove-tests + docker-compose -f docker-compose-test.yml down go tool cover -html=/tmp/coverage-data/c.out -o /tmp/coverage.html @echo Done, coverage data can be found in /tmp/coverage.html diff --git a/README.md b/README.md index 2aff518..aad1a6f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ Rove ==== -![Tests](https://github.com/mdiluz/rove/workflows/Tests/badge.svg) ![Docker](https://github.com/mdiluz/rove/workflows/Docker/badge.svg) [![rove](https://snapcraft.io//rove/badge.svg)](https://snapcraft.io/rove) -![Rove](https://github.com/mdiluz/rove/blob/master/data/icon.svg) +![Rove](data/icon.svg) Rove is an asynchronous nomadic game about exploring as part of a loose community. -This repository contains the source code for the `rove-server` deployment and the `rove` command line client. See [mdiluz.github.io/rove](https://mdiluz.github.io/rove/) for game details, and [roveapi.proto](https://github.com/mdiluz/rove/blob/master/proto/roveapi/roveapi.proto) for the current server-client API. +This repository contains the source code for the `rove-server` deployment and the `rove` command line client. See [mdiluz.github.io/rove](https://mdiluz.github.io/rove/) for game details, and [roveapi.proto](proto/roveapi/roveapi.proto) for the current server-client API. diff --git a/cmd/rove-server/internal/routes.go b/cmd/rove-server/internal/routes.go index 0fb41d8..69dd820 100644 --- a/cmd/rove-server/internal/routes.go +++ b/cmd/rove-server/internal/routes.go @@ -5,7 +5,6 @@ import ( "fmt" "log" - "github.com/mdiluz/rove/pkg/rove" "github.com/mdiluz/rove/pkg/version" "github.com/mdiluz/rove/proto/roveapi" ) @@ -35,7 +34,7 @@ func (s *Server) Register(ctx context.Context, req *roveapi.RegisterRequest) (*r return nil, fmt.Errorf("empty account name") } - if acc, err := s.accountant.RegisterAccount(req.Name); err != nil { + if acc, err := s.world.Accountant.RegisterAccount(req.Name); err != nil { return nil, err } else if _, err := s.SpawnRoverForAccount(req.Name); err != nil { @@ -58,13 +57,13 @@ func (s *Server) Register(ctx context.Context, req *roveapi.RegisterRequest) (*r func (s *Server) Status(ctx context.Context, req *roveapi.StatusRequest) (response *roveapi.StatusResponse, err error) { log.Printf("Handling status request: %s\n", req.Account.Name) - if valid, err := s.accountant.VerifySecret(req.Account.Name, req.Account.Secret); err != nil { + if valid, err := s.world.Accountant.VerifySecret(req.Account.Name, req.Account.Secret); err != nil { return nil, err } else if !valid { return nil, fmt.Errorf("Secret incorrect for account %s", req.Account.Name) - } else if resp, err := s.accountant.GetValue(req.Account.Name, "rover"); err != nil { + } else if resp, err := s.world.Accountant.GetValue(req.Account.Name, "rover"); err != nil { return nil, err } else if rover, err := s.world.GetRover(resp); err != nil { @@ -76,40 +75,7 @@ func (s *Server) Status(ctx context.Context, req *roveapi.StatusRequest) (respon inv = append(inv, byte(i.Type)) } - i, q := s.world.RoverCommands(resp) - var incoming, queued []*roveapi.Command - for _, i := range i { - c := &roveapi.Command{ - Command: i.Command, - } - switch i.Command { - case roveapi.CommandType_move: - c.Data = &roveapi.Command_Bearing{ - Bearing: i.Bearing, - } - case roveapi.CommandType_broadcast: - c.Data = &roveapi.Command_Message{ - Message: i.Message, - } - } - incoming = append(incoming, c) - } - for _, q := range q { - c := &roveapi.Command{ - Command: q.Command, - } - switch q.Command { - case roveapi.CommandType_move: - c.Data = &roveapi.Command_Bearing{ - Bearing: q.Bearing, - } - case roveapi.CommandType_broadcast: - c.Data = &roveapi.Command_Message{ - Message: q.Message, - } - } - queued = append(queued, c) - } + queued := s.world.RoverCommands(resp) var logs []*roveapi.Log for _, log := range rover.Logs { logs = append(logs, &roveapi.Log{ @@ -119,21 +85,29 @@ func (s *Server) Status(ctx context.Context, req *roveapi.StatusRequest) (respon } response = &roveapi.StatusResponse{ - Name: rover.Name, - Position: &roveapi.Vector{ - X: int32(rover.Pos.X), - Y: int32(rover.Pos.Y), + Readings: &roveapi.RoverReadings{ + Position: &roveapi.Vector{ + X: int32(rover.Pos.X), + Y: int32(rover.Pos.Y), + }, + Logs: logs, + Wind: s.world.Wind, + }, + Spec: &roveapi.RoverSpecifications{ + Name: rover.Name, + Range: int32(rover.Range), + Capacity: int32(rover.Capacity), + MaximumIntegrity: int32(rover.MaximumIntegrity), + MaximumCharge: int32(rover.MaximumCharge), + }, + Status: &roveapi.RoverStatus{ + Bearing: rover.Bearing, + Inventory: inv, + Integrity: int32(rover.Integrity), + Charge: int32(rover.Charge), + QueuedCommands: queued, + SailPosition: rover.SailPosition, }, - Range: int32(rover.Range), - Inventory: inv, - Capacity: int32(rover.Capacity), - Integrity: int32(rover.Integrity), - MaximumIntegrity: int32(rover.MaximumIntegrity), - Charge: int32(rover.Charge), - MaximumCharge: int32(rover.MaximumCharge), - IncomingCommands: incoming, - QueuedCommands: queued, - Logs: logs, } } return response, nil @@ -143,7 +117,7 @@ func (s *Server) Status(ctx context.Context, req *roveapi.StatusRequest) (respon func (s *Server) Radar(ctx context.Context, req *roveapi.RadarRequest) (*roveapi.RadarResponse, error) { log.Printf("Handling radar request: %s\n", req.Account.Name) - if valid, err := s.accountant.VerifySecret(req.Account.Name, req.Account.Secret); err != nil { + if valid, err := s.world.Accountant.VerifySecret(req.Account.Name, req.Account.Secret); err != nil { return nil, err } else if !valid { @@ -152,7 +126,7 @@ func (s *Server) Radar(ctx context.Context, req *roveapi.RadarRequest) (*roveapi response := &roveapi.RadarResponse{} - resp, err := s.accountant.GetValue(req.Account.Name, "rover") + resp, err := s.world.Accountant.GetValue(req.Account.Name, "rover") if err != nil { return nil, err @@ -175,33 +149,19 @@ func (s *Server) Radar(ctx context.Context, req *roveapi.RadarRequest) (*roveapi func (s *Server) Command(ctx context.Context, req *roveapi.CommandRequest) (*roveapi.CommandResponse, error) { log.Printf("Handling command request: %s and %+v\n", req.Account.Name, req.Commands) - if valid, err := s.accountant.VerifySecret(req.Account.Name, req.Account.Secret); err != nil { + if valid, err := s.world.Accountant.VerifySecret(req.Account.Name, req.Account.Secret); err != nil { return nil, err } else if !valid { return nil, fmt.Errorf("Secret incorrect for account %s", req.Account.Name) } - resp, err := s.accountant.GetValue(req.Account.Name, "rover") + resp, err := s.world.Accountant.GetValue(req.Account.Name, "rover") if err != nil { return nil, err } - var cmds []rove.Command - for _, c := range req.Commands { - n := rove.Command{ - Command: c.Command, - } - switch c.Command { - case roveapi.CommandType_move: - n.Bearing = c.GetBearing() - case roveapi.CommandType_broadcast: - n.Message = c.GetMessage() - } - cmds = append(cmds, n) - } - - if err := s.world.Enqueue(resp, cmds...); err != nil { + if err := s.world.Enqueue(resp, req.Commands...); err != nil { return nil, err } diff --git a/cmd/rove-server/internal/server.go b/cmd/rove-server/internal/server.go index 102c30d..e2b7e16 100644 --- a/cmd/rove-server/internal/server.go +++ b/cmd/rove-server/internal/server.go @@ -4,6 +4,8 @@ import ( "fmt" "log" "net" + "os" + "path" "sync" "github.com/mdiluz/rove/pkg/persistence" @@ -11,8 +13,12 @@ import ( "github.com/mdiluz/rove/proto/roveapi" "github.com/robfig/cron" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/reflection" ) +var cert = os.Getenv("CERT_NAME") + const ( // PersistentData will allow the server to load and save it's state PersistentData = iota @@ -27,9 +33,6 @@ type Server struct { // Internal state world *rove.World - // Accountant - accountant Accountant - // gRPC server netListener net.Listener grpcServ *grpc.Server @@ -80,7 +83,6 @@ func NewServer(opts ...ServerOption) *Server { persistence: EphemeralData, schedule: cron.New(), world: rove.NewWorld(32), - accountant: NewSimpleAccountant(), } // Apply all options @@ -107,8 +109,22 @@ func (s *Server) Initialise(fillWorld bool) (err error) { if err != nil { log.Fatalf("failed to listen: %v", err) } - s.grpcServ = grpc.NewServer() + + // Load TLS + var opts []grpc.ServerOption + if len(os.Getenv("NO_TLS")) == 0 { + pem := path.Join("/etc/letsencrypt/live/", cert, "fullchain.pem") + key := path.Join("/etc/letsencrypt/live/", cert, "privkey.pem") + creds, err := credentials.NewServerTLSFromFile(pem, key) + if err != nil { + log.Fatalf("failed to setup TLS: %v", err) + } + opts = append(opts, grpc.Creds(creds)) + } + + s.grpcServ = grpc.NewServer(opts...) roveapi.RegisterRoveServer(s.grpcServ, s) + reflection.Register(s.grpcServ) return nil } @@ -131,8 +147,8 @@ func (s *Server) Run() { log.Println("Executing server tick") - // Run the command queues - s.world.ExecuteCommandQueues() + // Tick the world + s.world.Tick() // Save out the new world state if err := s.SaveWorld(); err != nil { @@ -188,7 +204,7 @@ func (s *Server) SaveWorld() error { if s.persistence == PersistentData { s.world.RLock() defer s.world.RUnlock() - if err := persistence.SaveAll("world", s.world, "accounts", s.accountant); err != nil { + if err := persistence.SaveAll("world", s.world); err != nil { return fmt.Errorf("failed to save out persistent data: %s", err) } } @@ -200,7 +216,7 @@ func (s *Server) LoadWorld() error { if s.persistence == PersistentData { s.world.Lock() defer s.world.Unlock() - if err := persistence.LoadAll("world", &s.world, "accounts", &s.accountant); err != nil { + if err := persistence.LoadAll("world", &s.world); err != nil { return err } } @@ -209,22 +225,10 @@ func (s *Server) LoadWorld() error { // SpawnRoverForAccount spawns the rover rover for an account func (s *Server) SpawnRoverForAccount(account string) (string, error) { - inst, err := s.world.SpawnRover() + inst, err := s.world.SpawnRover(account) if err != nil { return "", err } - err = s.accountant.AssignData(account, "rover", inst) - if err != nil { - log.Printf("Failed to assign rover to account, %s", err) - - // Try and clear up the rover - if err := s.world.DestroyRover(inst); err != nil { - log.Printf("Failed to destroy rover after failed rover assign: %s", err) - } - - return "", err - } - return inst, nil } diff --git a/cmd/rove-server/internal/server_test.go b/cmd/rove-server/internal/server_test.go index 36db679..40d4b80 100644 --- a/cmd/rove-server/internal/server_test.go +++ b/cmd/rove-server/internal/server_test.go @@ -1,6 +1,7 @@ package internal import ( + "os" "testing" ) @@ -30,6 +31,7 @@ func TestNewServer_OptionPersistentData(t *testing.T) { } func TestServer_Run(t *testing.T) { + os.Setenv("NO_TLS", "1") server := NewServer() if server == nil { t.Error("Failed to create server") @@ -45,6 +47,7 @@ func TestServer_Run(t *testing.T) { } func TestServer_RunPersistentData(t *testing.T) { + os.Setenv("NO_TLS", "1") server := NewServer(OptionPersistentData()) if server == nil { t.Error("Failed to create server") diff --git a/cmd/rove/internal/glyph.go b/cmd/rove/internal/glyph.go index 1a26f5d..4a774e7 100644 --- a/cmd/rove/internal/glyph.go +++ b/cmd/rove/internal/glyph.go @@ -22,6 +22,12 @@ const ( // GlyphRoverLive represents a live rover GlyphRoverLive = Glyph('R') + // GlyphRoverDormant represents a dormant rover + GlyphRoverDormant = Glyph('r') + + // GlyphRoverParts represents spare rover parts + GlyphRoverParts = Glyph('*') + // GlyphRockSmall is a small stashable rock GlyphRockSmall = Glyph('o') @@ -51,8 +57,12 @@ func ObjectGlyph(o roveapi.Object) Glyph { return GlyphRoverLive case roveapi.Object_RockSmall: return GlyphRockSmall + case roveapi.Object_RoverDormant: + return GlyphRoverDormant case roveapi.Object_RockLarge: return GlyphRockLarge + case roveapi.Object_RoverParts: + return GlyphRoverParts } log.Fatalf("Unknown object type: %c", o) diff --git a/cmd/rove/main.go b/cmd/rove/main.go index bd98d52..f1e993d 100644 --- a/cmd/rove/main.go +++ b/cmd/rove/main.go @@ -1,6 +1,7 @@ package main import ( + "crypto/tls" "encoding/json" "fmt" "io/ioutil" @@ -8,6 +9,7 @@ import ( "os" "path" "path/filepath" + "strconv" "time" "github.com/mdiluz/rove/cmd/rove/internal" @@ -15,6 +17,7 @@ import ( "github.com/mdiluz/rove/proto/roveapi" "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" ) var home = os.Getenv("HOME") @@ -22,38 +25,44 @@ var defaultDataPath = path.Join(home, ".local/share/") // Command usage func printUsage() { - fmt.Fprintf(os.Stderr, "Usage: rove COMMAND [ARGS...]\n") - fmt.Fprintln(os.Stderr, "\nCommands") - fmt.Fprintln(os.Stderr, "\tserver-status prints the server status") - fmt.Fprintln(os.Stderr, "\tregister NAME registers an account and stores it (use with -name)") - fmt.Fprintln(os.Stderr, "\tcommand COMMAND [VAL...] issue commands to rover, accepts multiple, see below") - fmt.Fprintln(os.Stderr, "\tradar gathers radar data for the current rover") - fmt.Fprintln(os.Stderr, "\tstatus gets status info for current rover") - fmt.Fprintln(os.Stderr, "\tconfig [HOST] outputs the local config info, optionally sets host") - fmt.Fprintln(os.Stderr, "\thelp outputs this usage information") - fmt.Fprintln(os.Stderr, "\tversion outputs version info") - fmt.Fprintln(os.Stderr, "\nRover commands:") - fmt.Fprintln(os.Stderr, "\tmove BEARING moves the rover in the chosen direction") - fmt.Fprintln(os.Stderr, "\tstash stores the object at the rover location in the inventory") - fmt.Fprintln(os.Stderr, "\trepair uses an inventory object to repair the rover") - fmt.Fprintln(os.Stderr, "\trecharge wait a tick to recharge the rover") - fmt.Fprintln(os.Stderr, "\tbroadcast MSG broadcast a simple ASCII triplet to nearby rovers") - fmt.Fprintln(os.Stderr, "\nEnvironment") - fmt.Fprintln(os.Stderr, "\tROVE_USER_DATA path to user data, defaults to "+defaultDataPath) + fmt.Fprintln(os.Stderr, "Usage: rove ARG [OPT...]") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintln(os.Stderr, "Arguments:") + fmt.Fprintln(os.Stderr, "\tversion outputs version") + fmt.Fprintln(os.Stderr, "\thelp outputs this usage text") + fmt.Fprintln(os.Stderr, "\tconfig [HOST] outputs the local config, optionally sets host") + fmt.Fprintln(os.Stderr, "\tserver-status prints the server status") + fmt.Fprintln(os.Stderr, "\tregister NAME registers an account and spawns a rover") + fmt.Fprintln(os.Stderr, "\tradar prints radar data in ASCII form") + fmt.Fprintln(os.Stderr, "\tstatus gets rover status") + fmt.Fprintln(os.Stderr, "\tcommand CMD [VAL...] [REPEAT] sets the command queue, accepts multiple in sequence") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintln(os.Stderr, "Rover commands:") + fmt.Fprintln(os.Stderr, "\ttoggle toggles the current sail mode") + fmt.Fprintln(os.Stderr, "\tstash stores the object at the rover location in the inventory") + fmt.Fprintln(os.Stderr, "\trepair repairs the rover using inventory item") + fmt.Fprintln(os.Stderr, "\tbroadcast MSG broadcast a simple ASCII triplet to nearby rovers") + fmt.Fprintln(os.Stderr, "\tsalvage salvages a dormant rover for parts") + fmt.Fprintln(os.Stderr, "\ttransfer transfer's control into a dormant rover") + fmt.Fprintln(os.Stderr, "\tupgrade SPEC spends rover parts to upgrade one rover spec (capacity, range, integrity, charge") + fmt.Fprintln(os.Stderr, "\twait waits before performing the next command") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintln(os.Stderr, "Environment") + fmt.Fprintln(os.Stderr, "\tROVE_USER_DATA path to user data, defaults to "+defaultDataPath) } const gRPCport = 9090 // Account stores data for an account type Account struct { - Name string `json:"name"` - Secret string `json:"secret"` + Name string + Secret string } // Config is used to store internal data type Config struct { - Host string `json:"host,omitempty"` - Account Account `json:"account,omitempty"` + Host string + Account Account } // ConfigPath returns the configuration path @@ -128,12 +137,20 @@ func BearingFromString(s string) roveapi.Bearing { switch s { case "N": return roveapi.Bearing_North + case "NE": + return roveapi.Bearing_NorthEast case "E": return roveapi.Bearing_East + case "SE": + return roveapi.Bearing_SouthEast case "S": return roveapi.Bearing_South + case "SW": + return roveapi.Bearing_SouthWest case "W": return roveapi.Bearing_West + case "NW": + return roveapi.Bearing_NorthWest } return roveapi.Bearing_BearingUnknown } @@ -171,8 +188,15 @@ func InnerMain(command string, args ...string) error { return fmt.Errorf("no host set in %s, set one with '%s config {HOST}'", ConfigPath(), os.Args[0]) } + var opts []grpc.DialOption + if len(os.Getenv("NO_TLS")) == 0 { + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) + } else { + opts = append(opts, grpc.WithInsecure()) + } + // Set up the server - clientConn, err := grpc.Dial(fmt.Sprintf("%s:%d", config.Host, gRPCport), grpc.WithInsecure()) + clientConn, err := grpc.Dial(fmt.Sprintf("%s:%d", config.Host, gRPCport), opts...) if err != nil { return err } @@ -224,22 +248,22 @@ func InnerMain(command string, args ...string) error { // Iterate through each command var commands []*roveapi.Command for i := 0; i < len(args); i++ { + + var cmd *roveapi.Command switch args[i] { - case "move": + case "turn": i++ if len(args) == i { - return fmt.Errorf("move command must be passed bearing") + return fmt.Errorf("turn command must be passed a compass bearing") } - var b roveapi.Bearing - if b = BearingFromString(args[i]); b == roveapi.Bearing_BearingUnknown { - return fmt.Errorf("unrecognised bearing: %s", args[i]) + b := BearingFromString(args[i]) + if b == roveapi.Bearing_BearingUnknown { + return fmt.Errorf("turn command must be given a valid bearing %s", args[i]) + } + cmd = &roveapi.Command{ + Command: roveapi.CommandType_turn, + Bearing: b, } - commands = append(commands, - &roveapi.Command{ - Command: roveapi.CommandType_move, - Data: &roveapi.Command_Bearing{Bearing: b}, - }, - ) case "broadcast": i++ if len(args) == i { @@ -247,20 +271,51 @@ func InnerMain(command string, args ...string) error { } else if len(args[i]) > 3 { return fmt.Errorf("broadcast command must be given ASCII triplet of 3 or less: %s", args[i]) } - commands = append(commands, - &roveapi.Command{ - Command: roveapi.CommandType_broadcast, - Data: &roveapi.Command_Message{Message: []byte(args[i])}, - }, - ) + cmd = &roveapi.Command{ + Command: roveapi.CommandType_broadcast, + Data: []byte(args[i]), + } + case "upgrade": + i++ + if len(args) == i { + return fmt.Errorf("upgrade command must be passed a spec to upgrade") + } + var u roveapi.RoverUpgrade + switch args[i] { + case "capacity": + u = roveapi.RoverUpgrade_Capacity + case "range": + u = roveapi.RoverUpgrade_Range + case "integrity": + u = roveapi.RoverUpgrade_MaximumIntegrity + case "charge": + u = roveapi.RoverUpgrade_MaximumCharge + default: + return fmt.Errorf("upgrade command must be passed a known upgrade spec") + } + cmd = &roveapi.Command{ + Command: roveapi.CommandType_upgrade, + Upgrade: u, + } default: // By default just use the command literally - commands = append(commands, - &roveapi.Command{ - Command: roveapi.CommandType(roveapi.CommandType_value[args[i]]), - }, - ) + cmd = &roveapi.Command{ + Command: roveapi.CommandType(roveapi.CommandType_value[args[i]]), + } } + + // Try and convert the next command to a number + number := 0 + if len(args) > i+1 { + num, err := strconv.Atoi(args[i+1]) + if err == nil { + number = num + i++ + } + } + cmd.Repeat = int32(number) + + commands = append(commands, cmd) } _, err := client.Command(ctx, &roveapi.CommandRequest{ @@ -348,6 +403,15 @@ func InnerMain(command string, args ...string) error { func main() { // Bail without any args if len(os.Args) == 1 { + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintln(os.Stderr, "m mm mmm m m mmm") + fmt.Fprintln(os.Stderr, "#\" \" #\" \"# \"m m\" #\" #") + fmt.Fprintln(os.Stderr, "# # # #m# #\"\"\"\"") + fmt.Fprintln(os.Stderr, "# \"#m#\" # \"#mm\"") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintln(os.Stderr, "Rove is an asychronous nomadic game about exploring a planet as part of a loose community.") + fmt.Fprintln(os.Stderr, "Visit https://mdiluz.github.io/rove/ for more information.") + fmt.Fprintf(os.Stderr, "\n") printUsage() os.Exit(1) } diff --git a/cmd/rove/main_test.go b/cmd/rove/main_test.go index dd588e1..250988a 100644 --- a/cmd/rove/main_test.go +++ b/cmd/rove/main_test.go @@ -13,6 +13,7 @@ import ( ) func Test_InnerMain(t *testing.T) { + os.Setenv("NO_TLS", "1") // Use temporary local user data tmp, err := ioutil.TempDir(os.TempDir(), "rove-") @@ -50,12 +51,17 @@ func Test_InnerMain(t *testing.T) { assert.Error(t, InnerMain("command")) // Give it commands - assert.NoError(t, InnerMain("command", "move", "N")) + assert.NoError(t, InnerMain("command", "toggle")) assert.NoError(t, InnerMain("command", "stash")) assert.NoError(t, InnerMain("command", "repair")) + assert.NoError(t, InnerMain("command", "upgrade", "capacity")) assert.NoError(t, InnerMain("command", "broadcast", "abc")) + assert.NoError(t, InnerMain("command", "wait", "10")) + assert.NoError(t, InnerMain("command", "wait", "1", "turn", "NW", "toggle", "broadcast", "zyx")) // Give it malformed commands - assert.Error(t, InnerMain("command", "move", "stash")) + assert.Error(t, InnerMain("command", "unknown")) assert.Error(t, InnerMain("command", "broadcast")) + assert.Error(t, InnerMain("command", "upgrade")) + assert.Error(t, InnerMain("command", "1")) } diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 0000000..aec8cee --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,32 @@ +version: '3' + +services: + rove-test-server: + build: + context: . + dockerfile: Dockerfile + image: rove:latest + ports: + - "9090:9090" + environment: + - PORT=9090 + - DATA_PATH=/tmp/ + - WORDS_FILE=data/words_alpha.txt + - TICK_RATE=10 + - NO_TLS=1 + command: [ "./rove-server"] + + rove-tests: + depends_on: [ rove-test-server ] + build: + context: . + dockerfile: Dockerfile + image: rove:latest + environment: + - ROVE_GRPC=rove-test-server + command: [ "./script/wait-for-it.sh", "rove-test-server:9090", "--", "go", "test", "-v", "./...", "--tags=integration", "-cover", "-coverprofile=/mnt/coverage-data/c.out", "-count", "1" ] + volumes: + - /tmp/coverage-data:/mnt/coverage-data:rw + + + diff --git a/docker-compose.yml b/docker-compose.yml index 71013b5..d5606f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,22 +15,11 @@ services: - PORT=9090 - DATA_PATH=/mnt/rove-server - WORDS_FILE=data/words_alpha.txt - - TICK_RATE=5 + - TICK_RATE=3 + - CERT_NAME=${CERT_NAME} volumes: - persistent-data:/mnt/rove-server:rw + - /etc/letsencrypt/:/etc/letsencrypt/ command: [ "./rove-server"] - rove-tests: - depends_on: [ rove-server ] - build: - context: . - dockerfile: Dockerfile - image: rove:latest - environment: - - ROVE_GRPC=rove-server - command: [ "./script/wait-for-it.sh", "rove-server:9090", "--", "go", "test", "-v", "./...", "--tags=integration", "-cover", "-coverprofile=/mnt/coverage-data/c.out", "-count", "1" ] - volumes: - - /tmp/coverage-data:/mnt/coverage-data:rw - - diff --git a/cmd/rove-server/internal/accounts.go b/pkg/accounts/accounts.go similarity index 91% rename from cmd/rove-server/internal/accounts.go rename to pkg/accounts/accounts.go index 99362ad..eb8637d 100644 --- a/cmd/rove-server/internal/accounts.go +++ b/pkg/accounts/accounts.go @@ -1,4 +1,4 @@ -package internal +package accounts // Accountant decribes something that stores accounts and account values type Accountant interface { @@ -21,8 +21,8 @@ type Accountant interface { // Account represents a registered user type Account struct { // Name simply describes the account and must be unique - Name string `json:"name"` + Name string // Data represents internal account data - Data map[string]string `json:"data"` + Data map[string]string } diff --git a/cmd/rove-server/internal/accounts_test.go b/pkg/accounts/accounts_test.go similarity index 98% rename from cmd/rove-server/internal/accounts_test.go rename to pkg/accounts/accounts_test.go index 9e7891f..bd2416f 100644 --- a/cmd/rove-server/internal/accounts_test.go +++ b/pkg/accounts/accounts_test.go @@ -1,4 +1,4 @@ -package internal +package accounts import ( "testing" diff --git a/cmd/rove-server/internal/simpleAccountant.go b/pkg/accounts/simpleAccountant.go similarity index 97% rename from cmd/rove-server/internal/simpleAccountant.go rename to pkg/accounts/simpleAccountant.go index 3168f30..9d6c43f 100644 --- a/cmd/rove-server/internal/simpleAccountant.go +++ b/pkg/accounts/simpleAccountant.go @@ -1,4 +1,4 @@ -package internal +package accounts import ( "fmt" @@ -9,7 +9,7 @@ import ( // SimpleAccountant manages a set of accounts type SimpleAccountant struct { - Accounts map[string]Account `json:"accounts"` + Accounts map[string]Account } // NewSimpleAccountant creates a new accountant diff --git a/pkg/maths/vector.go b/pkg/maths/vector.go index 8a63708..a74ff16 100644 --- a/pkg/maths/vector.go +++ b/pkg/maths/vector.go @@ -8,8 +8,8 @@ import ( // Vector desribes a 3D vector type Vector struct { - X int `json:"x"` - Y int `json:"y"` + X int + Y int } // Add adds one vector to another @@ -89,13 +89,31 @@ func BearingToVector(b roveapi.Bearing) Vector { switch b { case roveapi.Bearing_North: return Vector{Y: 1} + case roveapi.Bearing_NorthEast: + return Vector{X: 1, Y: 1} case roveapi.Bearing_East: return Vector{X: 1} + case roveapi.Bearing_SouthEast: + return Vector{X: 1, Y: -1} case roveapi.Bearing_South: return Vector{Y: -1} + case roveapi.Bearing_SouthWest: + return Vector{X: -1, Y: -1} case roveapi.Bearing_West: return Vector{X: -1} + case roveapi.Bearing_NorthWest: + return Vector{X: -1, Y: 1} } return Vector{} } + +// Dot returns the dot product of two vectors +func Dot(a Vector, b Vector) int { + return a.X*b.X + a.Y*b.Y +} + +// AngleCos returns the cosine of the angle between two vectors +func AngleCos(a Vector, b Vector) float64 { + return float64(Dot(a, b)) / a.Length() * b.Length() +} diff --git a/pkg/atlas/atlas.go b/pkg/rove/atlas.go similarity index 97% rename from pkg/atlas/atlas.go rename to pkg/rove/atlas.go index 05012aa..ab09dba 100644 --- a/pkg/atlas/atlas.go +++ b/pkg/rove/atlas.go @@ -1,4 +1,4 @@ -package atlas +package rove import ( "github.com/mdiluz/rove/pkg/maths" diff --git a/pkg/atlas/atlas_test.go b/pkg/rove/atlas_test.go similarity index 99% rename from pkg/atlas/atlas_test.go rename to pkg/rove/atlas_test.go index 3d715f1..56e5d8d 100644 --- a/pkg/atlas/atlas_test.go +++ b/pkg/rove/atlas_test.go @@ -1,4 +1,4 @@ -package atlas +package rove import ( "testing" diff --git a/pkg/atlas/chunkAtlas.go b/pkg/rove/chunkAtlas.go similarity index 74% rename from pkg/atlas/chunkAtlas.go rename to pkg/rove/chunkAtlas.go index 3f19b07..1903c7b 100644 --- a/pkg/atlas/chunkAtlas.go +++ b/pkg/rove/chunkAtlas.go @@ -1,62 +1,54 @@ -package atlas +package rove import ( "log" - "math/rand" "github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/proto/roveapi" - "github.com/ojrac/opensimplex-go" ) // chunk represents a fixed square grid of tiles type chunk struct { // Tiles represents the tiles within the chunk - Tiles []byte `json:"tiles"` + Tiles []byte // Objects represents the objects within the chunk // only one possible object per tile for now - Objects map[int]Object `json:"objects"` + Objects map[int]Object } // chunkBasedAtlas represents a grid of Chunks type chunkBasedAtlas struct { // Chunks represents all chunks in the world // This is intentionally not a 2D array so it can be expanded in all directions - Chunks []chunk `json:"chunks"` + Chunks []chunk // LowerBound is the origin of the bottom left corner of the current chunks in world space (current chunks cover >= this value) - LowerBound maths.Vector `json:"lowerBound"` + LowerBound maths.Vector // UpperBound is the top left corner of the current chunks (curent chunks cover < this value) - UpperBound maths.Vector `json:"upperBound"` + UpperBound maths.Vector // ChunkSize is the x/y dimensions of each square chunk - ChunkSize int `json:"chunksize"` + ChunkSize int - // terrainNoise describes the noise function for the terrain - terrainNoise opensimplex.Noise - - // terrainNoise describes the noise function for the terrain - objectNoise opensimplex.Noise + // worldGen is the internal world generator + worldGen WorldGen } const ( - noiseSeed = 1024 - terrainNoiseScale = 6 - objectNoiseScale = 3 + noiseSeed = 1024 ) // NewChunkAtlas creates a new empty atlas func NewChunkAtlas(chunkSize int) Atlas { // Start up with one chunk a := chunkBasedAtlas{ - ChunkSize: chunkSize, - Chunks: make([]chunk, 1), - LowerBound: maths.Vector{X: 0, Y: 0}, - UpperBound: maths.Vector{X: chunkSize, Y: chunkSize}, - terrainNoise: opensimplex.New(noiseSeed), - objectNoise: opensimplex.New(noiseSeed), + ChunkSize: chunkSize, + Chunks: make([]chunk, 1), + LowerBound: maths.Vector{X: 0, Y: 0}, + UpperBound: maths.Vector{X: chunkSize, Y: chunkSize}, + worldGen: NewNoiseWorldGen(noiseSeed), } // Initialise the first chunk a.populate(0) @@ -105,41 +97,16 @@ func (a *chunkBasedAtlas) populate(chunk int) { origin := a.chunkOriginInWorldSpace(chunk) for i := 0; i < a.ChunkSize; i++ { for j := 0; j < a.ChunkSize; j++ { + loc := maths.Vector{X: origin.X + i, Y: origin.Y + j} - // Get the terrain noise value for this location - t := a.terrainNoise.Eval2(float64(origin.X+i)/terrainNoiseScale, float64(origin.Y+j)/terrainNoiseScale) - var tile roveapi.Tile - switch { - case t > 0.5: - tile = roveapi.Tile_Gravel - case t > 0.05: - tile = roveapi.Tile_Sand - default: - tile = roveapi.Tile_Rock - } - c.Tiles[j*a.ChunkSize+i] = byte(tile) + // Set the tile + c.Tiles[j*a.ChunkSize+i] = byte(a.worldGen.GetTile(loc)) - // Get the object noise value for this location - o := a.objectNoise.Eval2(float64(origin.X+i)/objectNoiseScale, float64(origin.Y+j)/objectNoiseScale) - var obj = roveapi.Object_ObjectUnknown - switch { - case o > 0.6: - obj = roveapi.Object_RockLarge - case o > 0.5: - obj = roveapi.Object_RockSmall + // Set the object + obj := a.worldGen.GetObject(loc) + if obj.Type != roveapi.Object_ObjectUnknown { + c.Objects[j*a.ChunkSize+i] = obj } - if obj != roveapi.Object_ObjectUnknown { - c.Objects[j*a.ChunkSize+i] = Object{Type: roveapi.Object(obj)} - } - } - } - - // Set up any objects - for i := 0; i < len(c.Tiles); i++ { - if rand.Intn(16) == 0 { - c.Objects[i] = Object{Type: roveapi.Object_RockLarge} - } else if rand.Intn(32) == 0 { - c.Objects[i] = Object{Type: roveapi.Object_RockSmall} } } @@ -236,12 +203,11 @@ func (a *chunkBasedAtlas) worldSpaceToChunkWithGrow(v maths.Vector) int { // Create the new empty atlas newAtlas := chunkBasedAtlas{ - ChunkSize: a.ChunkSize, - LowerBound: lower, - UpperBound: upper, - Chunks: make([]chunk, size.X*size.Y), - terrainNoise: a.terrainNoise, - objectNoise: a.objectNoise, + ChunkSize: a.ChunkSize, + LowerBound: lower, + UpperBound: upper, + Chunks: make([]chunk, size.X*size.Y), + worldGen: a.worldGen, } // Log that we're resizing diff --git a/pkg/rove/command.go b/pkg/rove/command.go deleted file mode 100644 index e64d646..0000000 --- a/pkg/rove/command.go +++ /dev/null @@ -1,17 +0,0 @@ -package rove - -import "github.com/mdiluz/rove/proto/roveapi" - -// Command represends a single command to execute -type Command struct { - Command roveapi.CommandType `json:"command"` - - // Used in the move command - Bearing roveapi.Bearing `json:"bearing,omitempty"` - - // Used in the broadcast command - Message []byte `json:"message,omitempty"` -} - -// CommandStream is a list of commands to execute in order -type CommandStream []Command diff --git a/pkg/rove/command_test.go b/pkg/rove/command_test.go index 337782f..bf1eed6 100644 --- a/pkg/rove/command_test.go +++ b/pkg/rove/command_test.go @@ -1,6 +1,7 @@ package rove import ( + "encoding/json" "testing" "github.com/mdiluz/rove/pkg/maths" @@ -8,63 +9,297 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCommand_Move(t *testing.T) { - world := NewWorld(8) - a, err := world.SpawnRover() +func TestCommand_Invalid(t *testing.T) { + w := NewWorld(8) + name, err := w.SpawnRover("") assert.NoError(t, err) - pos := maths.Vector{ - X: 1.0, - Y: 2.0, - } - err = world.WarpRover(a, pos) - assert.NoError(t, err, "Failed to set position for rover") - - // Try the move command - moveCommand := Command{Command: roveapi.CommandType_move, Bearing: roveapi.Bearing_North} - assert.NoError(t, world.Enqueue(a, moveCommand), "Failed to execute move command") - - // Tick the world - world.EnqueueAllIncoming() - world.ExecuteCommandQueues() - - newPos, err := world.RoverPosition(a) - assert.NoError(t, err, "Failed to set position for rover") - pos.Add(maths.Vector{X: 0.0, Y: 1}) - assert.Equal(t, pos, newPos, "Failed to correctly set position for rover") + err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_none}) + assert.Error(t, err) } -func TestCommand_Recharge(t *testing.T) { - world := NewWorld(8) - a, err := world.SpawnRover() +func TestCommand_Toggle(t *testing.T) { + w := NewWorld(8) + a, err := w.SpawnRover("") assert.NoError(t, err) - pos := maths.Vector{ - X: 1.0, - Y: 2.0, + + r, err := w.GetRover(a) + assert.NoError(t, err) + assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition) + + err = w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_toggle}) + assert.NoError(t, err) + w.Tick() + + r, err = w.GetRover(a) + assert.NoError(t, err) + assert.Equal(t, roveapi.SailPosition_CatchingWind, r.SailPosition) + + err = w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_toggle}) + assert.NoError(t, err) + w.Tick() + + r, err = w.GetRover(a) + assert.NoError(t, err) + assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition) +} + +func TestCommand_Turn(t *testing.T) { + w := NewWorld(8) + a, err := w.SpawnRover("") + assert.NoError(t, err) + + err = w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_turn, Bearing: roveapi.Bearing_NorthWest}) + assert.NoError(t, err) + w.Tick() + + r, err := w.GetRover(a) + assert.NoError(t, err) + assert.Equal(t, roveapi.Bearing_NorthWest, r.Bearing) +} + +func TestCommand_Stash(t *testing.T) { + w := NewWorld(8) + name, err := w.SpawnRover("") + assert.NoError(t, err) + + info, err := w.GetRover(name) + assert.NoError(t, err) + assert.Empty(t, info.Inventory) + + // Drop a pickup below us + w.Atlas.SetObject(info.Pos, Object{Type: roveapi.Object_RockSmall}) + + // Try and stash it + err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_stash}) + assert.NoError(t, err) + w.Tick() + + // Check we now have it in the inventory + info, err = w.GetRover(name) + assert.NoError(t, err) + assert.Equal(t, 1, len(info.Inventory)) + assert.Equal(t, Object{Type: roveapi.Object_RockSmall}, info.Inventory[0]) + + // Check it's no longer on the atlas + _, obj := w.Atlas.QueryPosition(info.Pos) + assert.Equal(t, Object{Type: roveapi.Object_ObjectUnknown}, obj) +} + +func TestCommand_Repair(t *testing.T) { + w := NewWorld(8) + name, err := w.SpawnRover("") + assert.NoError(t, err) + + info, err := w.GetRover(name) + assert.NoError(t, err) + assert.Equal(t, info.MaximumIntegrity, info.Integrity) + + // Put a blocking rock to the north + w.Atlas.SetObject(info.Pos.Added(maths.Vector{X: 0, Y: 1}), Object{Type: roveapi.Object_RockLarge}) + + // Try and move and make sure we're blocked + newpos, err := w.TryMoveRover(name, roveapi.Bearing_North) + assert.NoError(t, err) + assert.Equal(t, info.Pos, newpos) + + // Check we're damaged + info, err = w.GetRover(name) + assert.NoError(t, err) + assert.Equal(t, info.MaximumIntegrity-1, info.Integrity) + + // Stash a repair object + w.Atlas.SetObject(info.Pos, Object{Type: roveapi.Object_RoverParts}) + obj, err := w.RoverStash(name) + assert.NoError(t, err) + assert.Equal(t, roveapi.Object_RoverParts, obj) + + // Enqueue the repair and tick + err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_repair}) + assert.NoError(t, err) + w.Tick() + + // Check we're repaired + info, err = w.GetRover(name) + assert.NoError(t, err) + assert.Equal(t, info.MaximumIntegrity, info.Integrity) + assert.Equal(t, 0, len(info.Inventory)) +} + +func TestCommand_Broadcast(t *testing.T) { + w := NewWorld(8) + name, err := w.SpawnRover("") + assert.NoError(t, err) + + // Enqueue the broadcast and tick + err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_broadcast, Data: []byte("ABC")}) + assert.NoError(t, err) + w.Tick() + + info, err := w.GetRover(name) + assert.NoError(t, err) + assert.Contains(t, info.Logs[len(info.Logs)-1].Text, "ABC") +} + +func TestCommand_Salvage(t *testing.T) { + w := NewWorld(8) + name, err := w.SpawnRover("") + assert.NoError(t, err) + + info, err := w.GetRover(name) + assert.NoError(t, err) + + w.Atlas.SetObject(info.Pos, Object{Type: roveapi.Object_RoverDormant}) + + // Enqueue the broadcast and tick + err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_salvage}) + assert.NoError(t, err) + w.Tick() + + // Check we now have some rover parts + info, err = w.GetRover(name) + assert.NoError(t, err) + assert.NotEmpty(t, info.Inventory) + for _, i := range info.Inventory { + assert.Equal(t, roveapi.Object_RoverParts, i.Type) } - err = world.WarpRover(a, pos) - assert.NoError(t, err, "Failed to set position for rover") + // Check the dormant rover is gone + _, obj := w.Atlas.QueryPosition(info.Pos) + assert.Equal(t, roveapi.Object_ObjectUnknown, obj.Type) +} - // Move to use up some charge - moveCommand := Command{Command: roveapi.CommandType_move, Bearing: roveapi.Bearing_North} - assert.NoError(t, world.Enqueue(a, moveCommand), "Failed to queue move command") +func TestCommand_Transfer(t *testing.T) { + w := NewWorld(8) + acc, err := w.Accountant.RegisterAccount("tmp") + assert.NoError(t, err) + nameA, err := w.SpawnRover(acc.Name) + assert.NoError(t, err) - // Tick the world - world.EnqueueAllIncoming() - world.ExecuteCommandQueues() + infoA, err := w.GetRover(nameA) + assert.NoError(t, err) - rover, _ := world.GetRover(a) - assert.Equal(t, rover.MaximumCharge-1, rover.Charge) + // Drop a dormant rover on the current position + infoB := DefaultRover() + infoB.Name = "abc" + infoB.Pos = infoA.Pos + data, err := json.Marshal(infoB) + assert.NoError(t, err) + w.Atlas.SetObject(infoA.Pos, Object{Type: roveapi.Object_RoverDormant, Data: data}) - chargeCommand := Command{Command: roveapi.CommandType_recharge} - assert.NoError(t, world.Enqueue(a, chargeCommand), "Failed to queue recharge command") + // Enqueue a transfer as well as a dud command + err = w.Enqueue(nameA, + &roveapi.Command{Command: roveapi.CommandType_transfer}, + &roveapi.Command{Command: roveapi.CommandType_broadcast, Data: []byte("xyz")}) + assert.NoError(t, err) + w.Tick() - // Tick the world - world.EnqueueAllIncoming() - world.ExecuteCommandQueues() + // Ensure both command queues are empty + assert.Empty(t, w.CommandQueue[nameA]) + assert.Empty(t, w.CommandQueue[infoB.Name]) - rover, _ = world.GetRover(a) - assert.Equal(t, rover.MaximumCharge, rover.Charge) + // Verify the account now controls the new rover + accountRover, err := w.Accountant.GetValue(acc.Name, "rover") + assert.NoError(t, err) + assert.Equal(t, infoB.Name, accountRover) + + // Verify the position now has a dormant rover + _, obj := w.Atlas.QueryPosition(infoA.Pos) + assert.Equal(t, roveapi.Object_RoverDormant, obj.Type) + + // Verify the stored data matches + var stored Rover + err = json.Unmarshal(obj.Data, &stored) + assert.NoError(t, err) + assert.Equal(t, infoA.Name, stored.Name) + + // Verify the new rover data matches what we put in + infoB2, err := w.GetRover(infoB.Name) + assert.NoError(t, err) + assert.Equal(t, infoB.Name, infoB2.Name) } + +func TestCommand_Wait(t *testing.T) { + w := NewWorld(8) + a, err := w.SpawnRover("") + assert.NoError(t, err) + + r, err := w.GetRover(a) + assert.NoError(t, err) + assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition) + + err = w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_wait, Repeat: 4}, &roveapi.Command{Command: roveapi.CommandType_toggle}) + assert.NoError(t, err) + + // Tick 5 times during the wait (1 normal execute + 4) + for i := 0; i < 5; i++ { + w.Tick() + + r, err = w.GetRover(a) + assert.NoError(t, err) + assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition) + } + + // One last tick to do the toggle + w.Tick() + + r, err = w.GetRover(a) + assert.NoError(t, err) + assert.Equal(t, roveapi.SailPosition_CatchingWind, r.SailPosition) +} + +func TestCommand_Upgrade(t *testing.T) { + w := NewWorld(8) + name, err := w.SpawnRover("") + assert.NoError(t, err) + rover, ok := w.Rovers[name] + assert.True(t, ok) + + // Try an invalid upgrade + err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_upgrade}) + assert.Error(t, err) + + // Try a valid command but without the parts + err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_upgrade, Upgrade: roveapi.RoverUpgrade_Capacity}) + assert.NoError(t, err) + + // Ensure nothing changed and we logged the attempt + pre := rover.Capacity + w.Tick() + assert.Equal(t, pre, rover.Capacity) + assert.Contains(t, rover.Logs[len(rover.Logs)-1].Text, "tried") + + // One non-part item + rover.Inventory = []Object{ + { + Type: roveapi.Object_RoverParts, + }, + { + Type: roveapi.Object_RoverParts, + }, + { + Type: roveapi.Object_RockSmall, + }, + { + Type: roveapi.Object_RoverParts, + }, + { + Type: roveapi.Object_RoverParts, + }, + { + Type: roveapi.Object_RoverParts, + }, + } + + // Try a valid command again + err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_upgrade, Upgrade: roveapi.RoverUpgrade_Capacity}) + assert.NoError(t, err) + + // Check that the capacity increases on the tick and all the parts are used + pre = rover.Capacity + w.Tick() + assert.Equal(t, pre+1, rover.Capacity) + assert.Equal(t, 1, len(rover.Inventory)) + assert.Equal(t, roveapi.Object_RockSmall, rover.Inventory[0].Type) +} diff --git a/pkg/atlas/objects.go b/pkg/rove/objects.go similarity index 82% rename from pkg/atlas/objects.go rename to pkg/rove/objects.go index e1b6730..df10453 100644 --- a/pkg/atlas/objects.go +++ b/pkg/rove/objects.go @@ -1,4 +1,4 @@ -package atlas +package rove import ( "github.com/mdiluz/rove/proto/roveapi" @@ -7,7 +7,10 @@ import ( // Object represents an object in the world type Object struct { // The type of the object - Type roveapi.Object `json:"type"` + Type roveapi.Object + + // Data is an internal type used for certain types of object + Data []byte } // IsBlocking checks if an object is a blocking object @@ -29,6 +32,7 @@ func (o *Object) IsBlocking() bool { func (o *Object) IsStashable() bool { var stashable = [...]roveapi.Object{ roveapi.Object_RockSmall, + roveapi.Object_RoverParts, } for _, t := range stashable { diff --git a/pkg/rove/rover.go b/pkg/rove/rover.go index 089b44a..bebde50 100644 --- a/pkg/rove/rover.go +++ b/pkg/rove/rover.go @@ -1,54 +1,89 @@ package rove import ( + "bufio" "fmt" "log" + "math/rand" + "os" "time" - "github.com/mdiluz/rove/pkg/atlas" + "github.com/google/uuid" "github.com/mdiluz/rove/pkg/maths" + "github.com/mdiluz/rove/proto/roveapi" +) + +const ( + maxLogEntries = 16 ) // RoverLogEntry describes a single log entry for the rover type RoverLogEntry struct { // Time is the timestamp of the entry - Time time.Time `json:"time"` + Time time.Time // Text contains the information in this log entry - Text string `json:"text"` + Text string } // Rover describes a single rover in the world type Rover struct { // Unique name of this rover - Name string `json:"name"` + Name string // Pos represents where this rover is in the world - Pos maths.Vector `json:"pos"` + Pos maths.Vector + + // Bearing is the current direction the rover is facing + Bearing roveapi.Bearing // Range represents the distance the unit's radar can see - Range int `json:"range"` + Range int // Inventory represents any items the rover is carrying - Inventory []atlas.Object `json:"inventory"` + Inventory []Object // Capacity is the maximum number of inventory items - Capacity int `json:"capacity"` + Capacity int // Integrity represents current rover health - Integrity int `json:"integrity"` + Integrity int // MaximumIntegrity is the full integrity of the rover - MaximumIntegrity int `json:"maximum-integrity"` + MaximumIntegrity int // Charge is the amount of energy the rover has - Charge int `json:"charge"` + Charge int // MaximumCharge is the maximum charge able to be stored - MaximumCharge int `json:"maximum-Charge"` + MaximumCharge int + + // SailPosition is the current position of the sails + SailPosition roveapi.SailPosition + + // Current number of ticks in this move, used for sailing speeds + MoveTicks int // Logs Stores log of information - Logs []RoverLogEntry `json:"logs"` + Logs []RoverLogEntry + + // The account that owns this rover + Owner string +} + +// DefaultRover returns a default rover object with default settings +func DefaultRover() *Rover { + return &Rover{ + Range: 10, + Integrity: 10, + MaximumIntegrity: 10, + Capacity: 10, + Charge: 10, + MaximumCharge: 10, + Bearing: roveapi.Bearing_North, + SailPosition: roveapi.SailPosition_SolarCharging, + Name: GenerateRoverName(), + } } // AddLogEntryf adds an entry to the rovers log @@ -61,4 +96,42 @@ func (r *Rover) AddLogEntryf(format string, args ...interface{}) { Text: text, }, ) + + // Limit the number of logs + if len(r.Logs) > maxLogEntries { + r.Logs = r.Logs[len(r.Logs)-maxLogEntries:] + } +} + +var wordsFile = os.Getenv("WORDS_FILE") +var roverWords []string + +// GenerateRoverName generates a new rover name +func GenerateRoverName() string { + + // Try and load the rover words file + if len(roverWords) == 0 { + // Try and load the words file + if file, err := os.Open(wordsFile); err != nil { + log.Printf("Couldn't read words file [%s], running without words: %s\n", wordsFile, err) + } else { + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + roverWords = append(roverWords, scanner.Text()) + } + if scanner.Err() != nil { + log.Printf("Failure during word file scan: %s\n", scanner.Err()) + } + } + } + + // Assign a random name if we have words + if len(roverWords) > 0 { + // Loop until we find a unique name + return fmt.Sprintf("%s-%s", roverWords[rand.Intn(len(roverWords))], roverWords[rand.Intn(len(roverWords))]) + } + + // Default to a unique string + return uuid.New().String() } diff --git a/pkg/rove/world.go b/pkg/rove/world.go index 7c313ba..d509d8d 100644 --- a/pkg/rove/world.go +++ b/pkg/rove/world.go @@ -1,104 +1,81 @@ package rove import ( - "bufio" + "encoding/json" "fmt" "log" "math/rand" - "os" "sync" - "github.com/google/uuid" - "github.com/mdiluz/rove/pkg/atlas" + "github.com/mdiluz/rove/pkg/accounts" "github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/proto/roveapi" ) +const ( + // ticksPerNormalMove defines the number of ticks it should take for a "normal" speed move + ticksPerNormalMove = 4 + + // upgradeCost is the cost in rover parts needed to upgrade a rover specification + upgradeCost = 5 +) + +// CommandStream is a list of commands to execute in order +type CommandStream []*roveapi.Command + // World describes a self contained universe and everything in it type World struct { + // TicksPerDay is the amount of ticks in a single day - TicksPerDay int `json:"ticks-per-day"` + TicksPerDay int // Current number of ticks from the start - CurrentTicks int `json:"current-ticks"` + CurrentTicks int // Rovers is a id->data map of all the rovers in the game - Rovers map[string]Rover `json:"rovers"` + Rovers map[string]*Rover // Atlas represends the world map of chunks and tiles - Atlas atlas.Atlas `json:"atlas"` + Atlas Atlas + + // Wind is the current wind direction + Wind roveapi.Bearing // Commands is the set of currently executing command streams per rover - CommandQueue map[string]CommandStream `json:"commands"` - // Incoming represents the set of commands to add to the queue at the end of the current tick - CommandIncoming map[string]CommandStream `json:"incoming"` + CommandQueue map[string]CommandStream + + // Accountant + Accountant accounts.Accountant // Mutex to lock around all world operations worldMutex sync.RWMutex // Mutex to lock around command operations cmdMutex sync.RWMutex - // Set of possible words to use for names - words []string } -var wordsFile = os.Getenv("WORDS_FILE") - // NewWorld creates a new world object func NewWorld(chunkSize int) *World { - - // Try and load the words file - var lines []string - if file, err := os.Open(wordsFile); err != nil { - log.Printf("Couldn't read words file [%s], running without words: %s\n", wordsFile, err) - } else { - defer file.Close() - scanner := bufio.NewScanner(file) - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - if scanner.Err() != nil { - log.Printf("Failure during word file scan: %s\n", scanner.Err()) - } - } - return &World{ - Rovers: make(map[string]Rover), - CommandQueue: make(map[string]CommandStream), - CommandIncoming: make(map[string]CommandStream), - Atlas: atlas.NewChunkAtlas(chunkSize), - words: lines, - TicksPerDay: 24, - CurrentTicks: 0, + Rovers: make(map[string]*Rover), + CommandQueue: make(map[string]CommandStream), + Atlas: NewChunkAtlas(chunkSize), + TicksPerDay: 24, + CurrentTicks: 0, + Accountant: accounts.NewSimpleAccountant(), + Wind: roveapi.Bearing_North, } } -// SpawnRover adds an rover to the game -func (w *World) SpawnRover() (string, error) { +// SpawnRover adds an rover to the game (without lock) +func (w *World) SpawnRover(account string) (string, error) { w.worldMutex.Lock() defer w.worldMutex.Unlock() // Initialise the rover - rover := Rover{ - Range: 4, - Integrity: 10, - MaximumIntegrity: 10, - Capacity: 10, - Charge: 10, - MaximumCharge: 10, - Name: uuid.New().String(), - } + rover := DefaultRover() - // Assign a random name if we have words - if len(w.words) > 0 { - for { - // Loop until we find a unique name - name := fmt.Sprintf("%s-%s", w.words[rand.Intn(len(w.words))], w.words[rand.Intn(len(w.words))]) - if _, ok := w.Rovers[name]; !ok { - rover.Name = name - break - } - } - } + // Assign the owner + rover.Owner = account // Spawn in a random place near the origin rover.Pos = maths.Vector{ @@ -124,7 +101,13 @@ func (w *World) SpawnRover() (string, error) { // Append the rover to the list w.Rovers[rover.Name] = rover - return rover.Name, nil + var err error + // Only assign if we've been given an account + if len(account) > 0 { + err = w.Accountant.AssignData(account, "rover", rover.Name) + } + + return rover.Name, err } // GetRover gets a specific rover by name @@ -136,7 +119,7 @@ func (w *World) GetRover(rover string) (Rover, error) { if !ok { return Rover{}, fmt.Errorf("Failed to find rover with name: %s", rover) } - return i, nil + return *i, nil } // RoverRecharge charges up a rover @@ -159,7 +142,6 @@ func (w *World) RoverRecharge(rover string) (int, error) { i.Charge++ i.AddLogEntryf("recharged to %d", i.Charge) } - w.Rovers[rover] = i return i.Charge, nil } @@ -194,7 +176,6 @@ func (w *World) RoverBroadcast(rover string, message []byte) (err error) { } i.AddLogEntryf("broadcasted %s", string(message)) - w.Rovers[rover] = i return } @@ -203,12 +184,26 @@ func (w *World) DestroyRover(rover string) error { w.worldMutex.Lock() defer w.worldMutex.Unlock() - _, ok := w.Rovers[rover] + r, ok := w.Rovers[rover] if !ok { return fmt.Errorf("no rover matching id") } + // Remove this rover from tracked rovers delete(w.Rovers, rover) + + r.Owner = "" + r.AddLogEntryf("rover destroyed") + + // Marshal the rover data + data, err := json.Marshal(r) + if err != nil { + return err + } + + // Place the dormant rover down + w.Atlas.SetObject(r.Pos, Object{Type: roveapi.Object_RoverDormant, Data: data}) + return nil } @@ -235,12 +230,11 @@ func (w *World) SetRoverPosition(rover string, pos maths.Vector) error { } i.Pos = pos - w.Rovers[rover] = i return nil } // RoverInventory returns the inventory of a requested rover -func (w *World) RoverInventory(rover string) ([]atlas.Object, error) { +func (w *World) RoverInventory(rover string) ([]Object, error) { w.worldMutex.RLock() defer w.worldMutex.RUnlock() @@ -272,12 +266,11 @@ func (w *World) WarpRover(rover string, pos maths.Vector) error { } i.Pos = pos - w.Rovers[rover] = i return nil } -// MoveRover attempts to move a rover in a specific direction -func (w *World) MoveRover(rover string, b roveapi.Bearing) (maths.Vector, error) { +// TryMoveRover attempts to move a rover in a specific direction +func (w *World) TryMoveRover(rover string, b roveapi.Bearing) (maths.Vector, error) { w.worldMutex.Lock() defer w.worldMutex.Unlock() @@ -286,12 +279,6 @@ func (w *World) MoveRover(rover string, b roveapi.Bearing) (maths.Vector, error) return maths.Vector{}, fmt.Errorf("no rover matching id") } - // Ensure the rover has energy - if i.Charge <= 0 { - return i.Pos, nil - } - i.Charge-- - // Try the new move position newPos := i.Pos.Added(maths.BearingToVector(b)) @@ -301,17 +288,11 @@ func (w *World) MoveRover(rover string, b roveapi.Bearing) (maths.Vector, error) i.AddLogEntryf("moved %s to %+v", b.String(), newPos) // Perform the move i.Pos = newPos - w.Rovers[rover] = i } else { // If it is a blocking tile, reduce the rover integrity i.AddLogEntryf("tried to move %s to %+v", b.String(), newPos) i.Integrity = i.Integrity - 1 i.AddLogEntryf("had a collision, new integrity %d", i.Integrity) - if i.Integrity == 0 { - // TODO: The rover needs to be left dormant with the player - } else { - w.Rovers[rover] = i - } } return i.Pos, nil @@ -329,11 +310,13 @@ func (w *World) RoverStash(rover string) (roveapi.Object, error) { // Can't pick up when full if len(r.Inventory) >= r.Capacity { + r.AddLogEntryf("tried to stash object but inventory was full") return roveapi.Object_ObjectUnknown, nil } // Ensure the rover has energy if r.Charge <= 0 { + r.AddLogEntryf("tried to stash object but had no charge") return roveapi.Object_ObjectUnknown, nil } r.Charge-- @@ -345,11 +328,237 @@ func (w *World) RoverStash(rover string) (roveapi.Object, error) { r.AddLogEntryf("stashed %c", obj.Type) r.Inventory = append(r.Inventory, obj) - w.Rovers[rover] = r - w.Atlas.SetObject(r.Pos, atlas.Object{Type: roveapi.Object_ObjectUnknown}) + w.Atlas.SetObject(r.Pos, Object{Type: roveapi.Object_ObjectUnknown}) return obj.Type, nil } +// RoverSalvage will salvage a rover for parts +func (w *World) RoverSalvage(rover string) (roveapi.Object, error) { + w.worldMutex.Lock() + defer w.worldMutex.Unlock() + + r, ok := w.Rovers[rover] + if !ok { + return roveapi.Object_ObjectUnknown, fmt.Errorf("no rover matching id") + } + + // Can't pick up when full + if len(r.Inventory) >= r.Capacity { + r.AddLogEntryf("tried to salvage dormant rover but inventory was full") + return roveapi.Object_ObjectUnknown, nil + } + + // Ensure the rover has energy + if r.Charge <= 0 { + r.AddLogEntryf("tried to salvage dormant rover but had no charge") + return roveapi.Object_ObjectUnknown, nil + } + r.Charge-- + + _, obj := w.Atlas.QueryPosition(r.Pos) + if obj.Type != roveapi.Object_RoverDormant { + r.AddLogEntryf("tried to salvage dormant rover but found no rover to salvage") + return roveapi.Object_ObjectUnknown, nil + } + + r.AddLogEntryf("salvaged dormant rover") + for i := 0; i < 5; i++ { + if len(r.Inventory) == r.Capacity { + break + } + r.Inventory = append(r.Inventory, Object{Type: roveapi.Object_RoverParts}) + } + w.Atlas.SetObject(r.Pos, Object{Type: roveapi.Object_ObjectUnknown}) + return obj.Type, nil +} + +// RoverTransfer will transfer rover control to dormant rover +func (w *World) RoverTransfer(rover string) (string, error) { + w.worldMutex.Lock() + defer w.worldMutex.Unlock() + + oldRover, ok := w.Rovers[rover] + if !ok { + return "", fmt.Errorf("no rover matching id") + } + + _, obj := w.Atlas.QueryPosition(oldRover.Pos) + if obj.Type != roveapi.Object_RoverDormant { + oldRover.AddLogEntryf("tried to transfer to dormant rover but found no rover") + return "", nil + } + + // Unmarshal the dormant rover + var newRover Rover + err := json.Unmarshal(obj.Data, &newRover) + if err != nil { + return "", err + } + + // Add logs + oldRover.AddLogEntryf("transferring to dormant rover %s", newRover.Name) + newRover.AddLogEntryf("transferred from rover %s", oldRover.Name) + + // Transfer the ownership + err = w.Accountant.AssignData(oldRover.Owner, "rover", newRover.Name) + if err != nil { + return "", err + } + newRover.Owner = oldRover.Owner + oldRover.Owner = "" + + // Place the old rover in the world + oldRoverData, err := json.Marshal(oldRover) + if err != nil { + return "", err + } + w.Atlas.SetObject(oldRover.Pos, Object{Type: roveapi.Object_RoverDormant, Data: oldRoverData}) + + // Swap the rovers in the tracking + w.Rovers[newRover.Name] = &newRover + delete(w.Rovers, oldRover.Name) + + // Clear the command queues for both rovers + delete(w.CommandQueue, oldRover.Name) + delete(w.CommandQueue, newRover.Name) + + return newRover.Name, nil +} + +// RoverToggle will toggle the sail position +func (w *World) RoverToggle(rover string) (roveapi.SailPosition, error) { + w.worldMutex.Lock() + defer w.worldMutex.Unlock() + + r, ok := w.Rovers[rover] + if !ok { + return roveapi.SailPosition_UnknownSailPosition, fmt.Errorf("no rover matching id") + } + + // Swap the sail position + switch r.SailPosition { + case roveapi.SailPosition_CatchingWind: + r.SailPosition = roveapi.SailPosition_SolarCharging + case roveapi.SailPosition_SolarCharging: + r.SailPosition = roveapi.SailPosition_CatchingWind + } + + // Reset the movement ticks + r.MoveTicks = 0 + + return r.SailPosition, nil +} + +// RoverUpgrade will try to upgrade the rover +func (w *World) RoverUpgrade(rover string, upgrade roveapi.RoverUpgrade) (int, error) { + w.worldMutex.Lock() + defer w.worldMutex.Unlock() + + r, ok := w.Rovers[rover] + if !ok { + return 0, fmt.Errorf("no rover matching id") + } + + cost := upgradeCost + num := 0 + for i := range r.Inventory { + if r.Inventory[i].Type == roveapi.Object_RoverParts { + num++ + } + } + + if num < cost { + r.AddLogEntryf("tried to upgrade but lacked rover parts") + return 0, nil + } + + // Apply the upgrade + var ret int + switch upgrade { + case roveapi.RoverUpgrade_Capacity: + r.Capacity++ + ret = r.Capacity + case roveapi.RoverUpgrade_Range: + r.Range++ + ret = r.Range + case roveapi.RoverUpgrade_MaximumCharge: + r.MaximumCharge++ + ret = r.MaximumCharge + case roveapi.RoverUpgrade_MaximumIntegrity: + r.MaximumIntegrity++ + ret = r.MaximumIntegrity + default: + return 0, fmt.Errorf("unknown upgrade: %s", upgrade) + } + + // Remove the cost in rover parts + var n []Object + for _, o := range r.Inventory { + if o.Type == roveapi.Object_RoverParts && cost > 0 { + cost-- + } else { + n = append(n, o) + } + } + // Assign back the inventory + r.Inventory = n + + r.AddLogEntryf("upgraded %s to %d", upgrade, ret) + + return ret, nil +} + +// RoverTurn will turn the rover +func (w *World) RoverTurn(rover string, bearing roveapi.Bearing) (roveapi.Bearing, error) { + w.worldMutex.Lock() + defer w.worldMutex.Unlock() + + r, ok := w.Rovers[rover] + if !ok { + return roveapi.Bearing_BearingUnknown, fmt.Errorf("no rover matching id") + } + + // Set the new bearing + r.Bearing = bearing + // Reset the movement ticks + r.MoveTicks = 0 + + return r.Bearing, nil +} + +// RoverRepair will turn the rover +func (w *World) RoverRepair(rover string) (int, error) { + w.worldMutex.Lock() + defer w.worldMutex.Unlock() + + r, ok := w.Rovers[rover] + if !ok { + return 0, fmt.Errorf("no rover matching id") + } + + // Can't repair past max + if r.Integrity >= r.MaximumIntegrity { + return r.Integrity, nil + } + + // Find rover parts in inventory + for i, o := range r.Inventory { + if o.Type == roveapi.Object_RoverParts { + + // Copy-erase from slice + r.Inventory[i] = r.Inventory[len(r.Inventory)-1] + r.Inventory = r.Inventory[:len(r.Inventory)-1] + + // Repair + r.Integrity = r.Integrity + 1 + r.AddLogEntryf("repaired self to %d", r.Integrity) + break + } + } + + return r.Integrity, nil +} + // RadarFromRover can be used to query what a rover can currently see func (w *World) RadarFromRover(rover string) (radar []roveapi.Tile, objs []roveapi.Object, err error) { w.worldMutex.RLock() @@ -409,10 +618,7 @@ func (w *World) RadarFromRover(rover string) (radar []roveapi.Tile, objs []rovea } // RoverCommands returns current commands for the given rover -func (w *World) RoverCommands(rover string) (incoming []Command, queued []Command) { - if c, ok := w.CommandIncoming[rover]; ok { - incoming = c - } +func (w *World) RoverCommands(rover string) (queued CommandStream) { if c, ok := w.CommandQueue[rover]; ok { queued = c } @@ -420,27 +626,34 @@ func (w *World) RoverCommands(rover string) (incoming []Command, queued []Comman } // Enqueue will queue the commands given -func (w *World) Enqueue(rover string, commands ...Command) error { +func (w *World) Enqueue(rover string, commands ...*roveapi.Command) error { // First validate the commands for _, c := range commands { switch c.Command { - case roveapi.CommandType_move: - if c.Bearing == roveapi.Bearing_BearingUnknown { - return fmt.Errorf("bearing must be valid") - } case roveapi.CommandType_broadcast: - if len(c.Message) > 3 { - return fmt.Errorf("too many characters in message (limit 3): %d", len(c.Message)) + if len(c.GetData()) > 3 { + return fmt.Errorf("too many characters in message (limit 3): %d", len(c.GetData())) } - for _, b := range c.Message { + for _, b := range c.GetData() { if b < 37 || b > 126 { return fmt.Errorf("invalid message character: %c", b) } } + case roveapi.CommandType_turn: + if c.GetBearing() == roveapi.Bearing_BearingUnknown { + return fmt.Errorf("turn command given unknown bearing") + } + case roveapi.CommandType_upgrade: + if c.GetUpgrade() == roveapi.RoverUpgrade_RoverUpgradeUnknown { + return fmt.Errorf("upgrade command given unknown upgrade") + } + case roveapi.CommandType_wait: + case roveapi.CommandType_toggle: case roveapi.CommandType_stash: case roveapi.CommandType_repair: - case roveapi.CommandType_recharge: + case roveapi.CommandType_salvage: + case roveapi.CommandType_transfer: // Nothing to verify default: return fmt.Errorf("unknown command: %s", c.Command) @@ -451,39 +664,31 @@ func (w *World) Enqueue(rover string, commands ...Command) error { w.cmdMutex.Lock() defer w.cmdMutex.Unlock() - // Override the incoming command set - w.CommandIncoming[rover] = commands + w.CommandQueue[rover] = commands return nil } -// EnqueueAllIncoming will enqueue the incoming commands -func (w *World) EnqueueAllIncoming() { - // Add any incoming commands from this tick and clear that queue - for id, incoming := range w.CommandIncoming { - commands := w.CommandQueue[id] - commands = append(commands, incoming...) - w.CommandQueue[id] = commands - } - w.CommandIncoming = make(map[string]CommandStream) -} - -// ExecuteCommandQueues will execute any commands in the current command queue -func (w *World) ExecuteCommandQueues() { +// Tick will execute any commands in the current command queue and tick the world +func (w *World) Tick() { w.cmdMutex.Lock() defer w.cmdMutex.Unlock() // Iterate through all the current commands for rover, cmds := range w.CommandQueue { if len(cmds) != 0 { - // Extract the first command in the queue - c := cmds[0] - w.CommandQueue[rover] = cmds[1:] // Execute the command - if err := w.ExecuteCommand(&c, rover); err != nil { + if done, err := w.ExecuteCommand(cmds[0], rover); err != nil { log.Println(err) // TODO: Report this error somehow + + } else if done { + // Extract the first command in the queue + // Only if the command queue still has entries (the command may have modified this queue) + if _, ok := w.CommandQueue[rover]; ok { + w.CommandQueue[rover] = cmds[1:] + } } } else { @@ -492,54 +697,115 @@ func (w *World) ExecuteCommandQueues() { } } - // Add any incoming commands from this tick and clear that queue - w.EnqueueAllIncoming() + // Move all the rovers based on current wind and sails + for n, r := range w.Rovers { + // Skip if we're not catching the wind + if r.SailPosition != roveapi.SailPosition_CatchingWind { + continue + } + + // Increment the current move ticks + r.MoveTicks++ + + // Get the difference between the two bearings + // Normalise, we don't care about clockwise/anticlockwise + diff := maths.Abs(int(w.Wind - r.Bearing)) + if diff > 4 { + diff = 8 - diff + } + + // Calculate the travel "ticks" + var ticksToMove int + switch diff { + case 0: + // Going with the wind, travel at base speed of once every 4 ticks + ticksToMove = ticksPerNormalMove + case 1: + // At a slight angle, we can go a little faster + ticksToMove = ticksPerNormalMove / 2 + case 2: + // Perpendicular to wind, max speed + ticksToMove = 1 + case 3: + // Heading at 45 degrees into the wind, back to min speed + ticksToMove = ticksPerNormalMove + case 4: + // Heading durectly into the wind, no movement at all + default: + log.Fatalf("bearing difference of %d should be impossible", diff) + } + + // If we've incremented over the current move ticks on the rover, we can try and make the move + if ticksToMove != 0 && r.MoveTicks >= ticksToMove { + _, err := w.TryMoveRover(n, r.Bearing) + if err != nil { + log.Println(err) + // TODO: Report this error somehow + } + + // Reset the move ticks + r.MoveTicks = 0 + } + } + + // Check all rover integrities + for _, r := range w.Rovers { + if r.Integrity <= 0 { + // The rover has died destroy it + err := w.DestroyRover(r.Name) + if err != nil { + log.Println(err) + // TODO: Report this error somehow + } + + // Spawn a new one for this account + _, err = w.SpawnRover(r.Owner) + if err != nil { + log.Println(err) + // TODO: Report this error somehow + } + } + } // Increment the current tick count w.CurrentTicks++ + + // Change the wind every day + if (w.CurrentTicks % w.TicksPerDay) == 0 { + w.Wind = roveapi.Bearing((rand.Int() % 8) + 1) // Random cardinal bearing + } } // ExecuteCommand will execute a single command -func (w *World) ExecuteCommand(c *Command, rover string) (err error) { - log.Printf("Executing command: %+v for %s\n", *c, rover) +func (w *World) ExecuteCommand(c *roveapi.Command, rover string) (done bool, err error) { + log.Printf("Executing command: %+v for %s\n", c.Command, rover) switch c.Command { - case roveapi.CommandType_move: - if _, err := w.MoveRover(rover, c.Bearing); err != nil { - return err - } - + case roveapi.CommandType_toggle: + _, err = w.RoverToggle(rover) case roveapi.CommandType_stash: - if _, err := w.RoverStash(rover); err != nil { - return err - } - + _, err = w.RoverStash(rover) case roveapi.CommandType_repair: - r, err := w.GetRover(rover) - if err != nil { - return err - } - // Consume an inventory item to repair if possible - if len(r.Inventory) > 0 && r.Integrity < r.MaximumIntegrity { - r.Inventory = r.Inventory[:len(r.Inventory)-1] - r.Integrity = r.Integrity + 1 - r.AddLogEntryf("repaired self to %d", r.Integrity) - w.Rovers[rover] = r - } - case roveapi.CommandType_recharge: - _, err := w.RoverRecharge(rover) - if err != nil { - return err - } + _, err = w.RoverRepair(rover) case roveapi.CommandType_broadcast: - if err := w.RoverBroadcast(rover, c.Message); err != nil { - return err - } + err = w.RoverBroadcast(rover, c.GetData()) + case roveapi.CommandType_turn: + _, err = w.RoverTurn(rover, c.GetBearing()) + case roveapi.CommandType_salvage: + _, err = w.RoverSalvage(rover) + case roveapi.CommandType_transfer: + _, err = w.RoverTransfer(rover) + case roveapi.CommandType_upgrade: + _, err = w.RoverUpgrade(rover, c.GetUpgrade()) + case roveapi.CommandType_wait: + // Nothing to do default: - return fmt.Errorf("unknown command: %s", c.Command) + return true, fmt.Errorf("unknown command: %s", c.Command) } - return + // Decrement the repeat number + c.Repeat-- + return c.Repeat < 0, err } // Daytime returns if it's currently daytime diff --git a/pkg/rove/world_test.go b/pkg/rove/world_test.go index 7cf2483..f085d89 100644 --- a/pkg/rove/world_test.go +++ b/pkg/rove/world_test.go @@ -3,7 +3,6 @@ package rove import ( "testing" - "github.com/mdiluz/rove/pkg/atlas" "github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/proto/roveapi" "github.com/stretchr/testify/assert" @@ -19,9 +18,9 @@ func TestNewWorld(t *testing.T) { func TestWorld_CreateRover(t *testing.T) { world := NewWorld(8) - a, err := world.SpawnRover() + a, err := world.SpawnRover("") assert.NoError(t, err) - b, err := world.SpawnRover() + b, err := world.SpawnRover("") assert.NoError(t, err) // Basic duplicate check @@ -34,7 +33,7 @@ func TestWorld_CreateRover(t *testing.T) { func TestWorld_GetRover(t *testing.T) { world := NewWorld(4) - a, err := world.SpawnRover() + a, err := world.SpawnRover("") assert.NoError(t, err) rover, err := world.GetRover(a) @@ -45,9 +44,9 @@ func TestWorld_GetRover(t *testing.T) { func TestWorld_DestroyRover(t *testing.T) { world := NewWorld(1) - a, err := world.SpawnRover() + a, err := world.SpawnRover("") assert.NoError(t, err) - b, err := world.SpawnRover() + b, err := world.SpawnRover("") assert.NoError(t, err) err = world.DestroyRover(a) @@ -63,7 +62,7 @@ func TestWorld_DestroyRover(t *testing.T) { func TestWorld_GetSetMovePosition(t *testing.T) { world := NewWorld(4) - a, err := world.SpawnRover() + a, err := world.SpawnRover("") assert.NoError(t, err) pos := maths.Vector{ @@ -79,49 +78,47 @@ func TestWorld_GetSetMovePosition(t *testing.T) { assert.Equal(t, pos, newPos, "Failed to correctly set position for rover") b := roveapi.Bearing_North - newPos, err = world.MoveRover(a, b) + newPos, err = world.TryMoveRover(a, b) assert.NoError(t, err, "Failed to set position for rover") pos.Add(maths.Vector{X: 0, Y: 1}) assert.Equal(t, pos, newPos, "Failed to correctly move position for rover") rover, err := world.GetRover(a) assert.NoError(t, err, "Failed to get rover information") - assert.Equal(t, rover.MaximumCharge-1, rover.Charge, "Rover should have lost charge for moving") assert.Contains(t, rover.Logs[len(rover.Logs)-1].Text, "moved", "Rover logs should contain the move") // Place a tile in front of the rover - world.Atlas.SetObject(maths.Vector{X: 0, Y: 2}, atlas.Object{Type: roveapi.Object_RockLarge}) - newPos, err = world.MoveRover(a, b) + world.Atlas.SetObject(maths.Vector{X: 0, Y: 2}, Object{Type: roveapi.Object_RockLarge}) + newPos, err = world.TryMoveRover(a, b) assert.NoError(t, err, "Failed to move rover") assert.Equal(t, pos, newPos, "Failed to correctly not move position for rover into wall") - - rover, err = world.GetRover(a) - assert.NoError(t, err, "Failed to get rover information") - assert.Equal(t, rover.MaximumCharge-2, rover.Charge, "Rover should have lost charge for move attempt") } func TestWorld_RadarFromRover(t *testing.T) { // Create world that should have visible walls on the radar world := NewWorld(2) - a, err := world.SpawnRover() + a, err := world.SpawnRover("") assert.NoError(t, err) - b, err := world.SpawnRover() + b, err := world.SpawnRover("") assert.NoError(t, err) // Warp the rovers into position bpos := maths.Vector{X: -3, Y: -3} + world.Atlas.SetObject(bpos, Object{Type: roveapi.Object_ObjectUnknown}) assert.NoError(t, world.WarpRover(b, bpos), "Failed to warp rover") + world.Atlas.SetObject(maths.Vector{X: 0, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown}) assert.NoError(t, world.WarpRover(a, maths.Vector{X: 0, Y: 0}), "Failed to warp rover") + r, err := world.GetRover(a) + assert.NoError(t, err) + radar, objs, err := world.RadarFromRover(a) assert.NoError(t, err, "Failed to get radar from rover") - fullRange := 4 + 4 + 1 + fullRange := r.Range + r.Range + 1 assert.Equal(t, fullRange*fullRange, len(radar), "Radar returned wrong length") assert.Equal(t, fullRange*fullRange, len(objs), "Radar returned wrong length") - // Test the expected values - assert.Equal(t, roveapi.Object_RoverLive, objs[1+fullRange]) - assert.Equal(t, roveapi.Object_RoverLive, objs[4+4*fullRange]) + // TODO: Verify the other rover is on the radar // Check the radar results are stable radar1, objs1, err := world.RadarFromRover(a) @@ -132,82 +129,11 @@ func TestWorld_RadarFromRover(t *testing.T) { assert.Equal(t, objs1, objs2) } -func TestWorld_RoverStash(t *testing.T) { - world := NewWorld(2) - a, err := world.SpawnRover() - assert.NoError(t, err) - - pos := maths.Vector{ - X: 0.0, - Y: 0.0, - } - - world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_ObjectUnknown}) - err = world.WarpRover(a, pos) - assert.NoError(t, err, "Failed to set position for rover") - - rover, err := world.GetRover(a) - assert.NoError(t, err, "Failed to get rover") - - for i := 0; i < rover.Capacity; i++ { - // Place an object - world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_RockSmall}) - - // Pick it up - o, err := world.RoverStash(a) - assert.NoError(t, err, "Failed to stash") - assert.Equal(t, roveapi.Object_RockSmall, o, "Failed to get correct object") - - // Check it's gone - _, obj := world.Atlas.QueryPosition(pos) - assert.Equal(t, roveapi.Object_ObjectUnknown, obj.Type, "Stash failed to remove object from atlas") - - // Check we have it - inv, err := world.RoverInventory(a) - assert.NoError(t, err, "Failed to get inventory") - assert.Equal(t, i+1, len(inv)) - assert.Equal(t, atlas.Object{Type: roveapi.Object_RockSmall}, inv[i]) - - // Check that this did reduce the charge - info, err := world.GetRover(a) - assert.NoError(t, err, "Failed to get rover") - assert.Equal(t, info.MaximumCharge-(i+1), info.Charge, "Rover lost charge for stash") - assert.Contains(t, info.Logs[len(info.Logs)-1].Text, "stashed", "Rover logs should contain the move") - } - - // Recharge the rover - for i := 0; i < rover.MaximumCharge; i++ { - _, err = world.RoverRecharge(a) - assert.NoError(t, err) - - } - - // Place an object - world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_RockSmall}) - - // Try to pick it up - o, err := world.RoverStash(a) - assert.NoError(t, err, "Failed to stash") - assert.Equal(t, roveapi.Object_ObjectUnknown, o, "Failed to get correct object") - - // Check it's still there - _, obj := world.Atlas.QueryPosition(pos) - assert.Equal(t, roveapi.Object_RockSmall, obj.Type, "Stash failed to remove object from atlas") - - // Check we don't have it - inv, err := world.RoverInventory(a) - assert.NoError(t, err, "Failed to get inventory") - assert.Equal(t, rover.Capacity, len(inv)) - - // Check that this didn't reduce the charge - info, err := world.GetRover(a) - assert.NoError(t, err, "Failed to get rover") - assert.Equal(t, info.MaximumCharge, info.Charge, "Rover lost charge for non-stash") -} - func TestWorld_RoverDamage(t *testing.T) { world := NewWorld(2) - a, err := world.SpawnRover() + acc, err := world.Accountant.RegisterAccount("tmp") + assert.NoError(t, err) + a, err := world.SpawnRover(acc.Name) assert.NoError(t, err) pos := maths.Vector{ @@ -215,15 +141,16 @@ func TestWorld_RoverDamage(t *testing.T) { Y: 0.0, } + world.Atlas.SetObject(pos, Object{Type: roveapi.Object_ObjectUnknown}) err = world.WarpRover(a, pos) assert.NoError(t, err, "Failed to set position for rover") info, err := world.GetRover(a) assert.NoError(t, err, "couldn't get rover info") - world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, atlas.Object{Type: roveapi.Object_RockLarge}) + world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, Object{Type: roveapi.Object_RockLarge}) - vec, err := world.MoveRover(a, roveapi.Bearing_North) + vec, err := world.TryMoveRover(a, roveapi.Bearing_North) assert.NoError(t, err, "Failed to move rover") assert.Equal(t, pos, vec, "Rover managed to move into large rock") @@ -231,102 +158,29 @@ func TestWorld_RoverDamage(t *testing.T) { assert.NoError(t, err, "couldn't get rover info") assert.Equal(t, info.Integrity-1, newinfo.Integrity, "rover should have lost integrity") assert.Contains(t, newinfo.Logs[len(newinfo.Logs)-1].Text, "collision", "Rover logs should contain the collision") -} -func TestWorld_RoverRepair(t *testing.T) { - world := NewWorld(2) - a, err := world.SpawnRover() - assert.NoError(t, err) - - pos := maths.Vector{ - X: 0.0, - Y: 0.0, + // Keep moving to damage the rover + for i := 0; i < info.Integrity-1; i++ { + vec, err := world.TryMoveRover(a, roveapi.Bearing_North) + assert.NoError(t, err, "Failed to move rover") + assert.Equal(t, pos, vec, "Rover managed to move into large rock") } - world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_ObjectUnknown}) + // Tick the world to check for rover deaths + world.Tick() - err = world.WarpRover(a, pos) - assert.NoError(t, err, "Failed to set position for rover") - - originalInfo, err := world.GetRover(a) - assert.NoError(t, err, "couldn't get rover info") - - // Pick up something to repair with - world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_RockSmall}) - o, err := world.RoverStash(a) - assert.NoError(t, err, "Failed to stash") - assert.Equal(t, roveapi.Object_RockSmall, o, "Failed to get correct object") - - world.Atlas.SetObject(maths.Vector{X: 0.0, Y: 1.0}, atlas.Object{Type: roveapi.Object_RockLarge}) - - // Try and bump into the rock - vec, err := world.MoveRover(a, roveapi.Bearing_North) - assert.NoError(t, err, "Failed to move rover") - assert.Equal(t, pos, vec, "Rover managed to move into large rock") - - newinfo, err := world.GetRover(a) - assert.NoError(t, err, "couldn't get rover info") - assert.Equal(t, originalInfo.Integrity-1, newinfo.Integrity, "rover should have lost integrity") - - err = world.ExecuteCommand(&Command{Command: roveapi.CommandType_repair}, a) - assert.NoError(t, err, "Failed to repair rover") - - newinfo, err = world.GetRover(a) - assert.NoError(t, err, "couldn't get rover info") - assert.Equal(t, originalInfo.Integrity, newinfo.Integrity, "rover should have gained integrity") - assert.Contains(t, newinfo.Logs[len(newinfo.Logs)-1].Text, "repair", "Rover logs should contain the repair") - - // Check again that it can't repair past the max - world.Atlas.SetObject(pos, atlas.Object{Type: roveapi.Object_RockSmall}) - o, err = world.RoverStash(a) - assert.NoError(t, err, "Failed to stash") - assert.Equal(t, roveapi.Object_RockSmall, o, "Failed to get correct object") - - err = world.ExecuteCommand(&Command{Command: roveapi.CommandType_repair}, a) - assert.NoError(t, err, "Failed to repair rover") - - newinfo, err = world.GetRover(a) - assert.NoError(t, err, "couldn't get rover info") - assert.Equal(t, originalInfo.Integrity, newinfo.Integrity, "rover should have kept the same integrity") -} - -func TestWorld_Charge(t *testing.T) { - world := NewWorld(4) - a, err := world.SpawnRover() - assert.NoError(t, err) - - // Get the rover information - rover, err := world.GetRover(a) - assert.NoError(t, err, "Failed to get rover information") - assert.Equal(t, rover.MaximumCharge, rover.Charge, "Rover should start with maximum charge") - - // Use up all the charge - for i := 0; i < rover.MaximumCharge; i++ { - // Get the initial position - initialPos, err := world.RoverPosition(a) - assert.NoError(t, err, "Failed to get position for rover") - - // Ensure the path ahead is empty - world.Atlas.SetTile(initialPos.Added(maths.BearingToVector(roveapi.Bearing_North)), roveapi.Tile_Rock) - world.Atlas.SetObject(initialPos.Added(maths.BearingToVector(roveapi.Bearing_North)), atlas.Object{Type: roveapi.Object_ObjectUnknown}) - - // Try and move north (along unblocked path) - newPos, err := world.MoveRover(a, roveapi.Bearing_North) - assert.NoError(t, err, "Failed to set position for rover") - assert.Equal(t, initialPos.Added(maths.BearingToVector(roveapi.Bearing_North)), newPos, "Failed to correctly move position for rover") - - // Ensure rover lost charge - rover, err := world.GetRover(a) - assert.NoError(t, err, "Failed to get rover information") - assert.Equal(t, rover.MaximumCharge-(i+1), rover.Charge, "Rover should have lost charge") - } + // Rover should have been destroyed now + _, err = world.GetRover(a) + assert.Error(t, err) + _, obj := world.Atlas.QueryPosition(info.Pos) + assert.Equal(t, roveapi.Object_RoverDormant, obj.Type) } func TestWorld_Daytime(t *testing.T) { world := NewWorld(1) - a, err := world.SpawnRover() + a, err := world.SpawnRover("") assert.NoError(t, err) // Remove rover charge @@ -342,7 +196,7 @@ func TestWorld_Daytime(t *testing.T) { // Loop for half the day for i := 0; i < world.TicksPerDay/2; i++ { assert.True(t, world.Daytime()) - world.ExecuteCommandQueues() + world.Tick() } // Remove rover charge again @@ -358,20 +212,22 @@ func TestWorld_Daytime(t *testing.T) { // Loop for half the day for i := 0; i < world.TicksPerDay/2; i++ { assert.False(t, world.Daytime()) - world.ExecuteCommandQueues() + world.Tick() } } func TestWorld_Broadcast(t *testing.T) { world := NewWorld(8) - a, err := world.SpawnRover() + a, err := world.SpawnRover("") assert.NoError(t, err) - b, err := world.SpawnRover() + b, err := world.SpawnRover("") assert.NoError(t, err) // Warp rovers near to eachother + world.Atlas.SetObject(maths.Vector{X: 0, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown}) + world.Atlas.SetObject(maths.Vector{X: 1, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown}) assert.NoError(t, world.WarpRover(a, maths.Vector{X: 0, Y: 0})) assert.NoError(t, world.WarpRover(b, maths.Vector{X: 1, Y: 0})) @@ -390,7 +246,7 @@ func TestWorld_Broadcast(t *testing.T) { assert.Contains(t, rb.Logs[len(rb.Logs)-1].Text, "ABC", "Rover A should have logged it's broadcast") // Warp B outside of the range of A - world.Atlas.SetObject(maths.Vector{X: ra.Range, Y: 0}, atlas.Object{Type: roveapi.Object_ObjectUnknown}) + world.Atlas.SetObject(maths.Vector{X: ra.Range, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown}) assert.NoError(t, world.WarpRover(b, maths.Vector{X: ra.Range, Y: 0})) // Broadcast from a again @@ -407,7 +263,7 @@ func TestWorld_Broadcast(t *testing.T) { assert.Contains(t, rb.Logs[len(rb.Logs)-1].Text, "XYZ", "Rover A should have logged it's broadcast") // Warp B outside of the range of A - world.Atlas.SetObject(maths.Vector{X: ra.Range + 1, Y: 0}, atlas.Object{Type: roveapi.Object_ObjectUnknown}) + world.Atlas.SetObject(maths.Vector{X: ra.Range + 1, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown}) assert.NoError(t, world.WarpRover(b, maths.Vector{X: ra.Range + 1, Y: 0})) // Broadcast from a again @@ -423,3 +279,81 @@ func TestWorld_Broadcast(t *testing.T) { assert.NoError(t, err) assert.Contains(t, rb.Logs[len(rb.Logs)-1].Text, "HJK", "Rover A should have logged it's broadcast") } + +func TestWorld_Sailing(t *testing.T) { + world := NewWorld(8) + world.Tick() // One initial tick to set the wind direction the first time + world.Wind = roveapi.Bearing_North // Set the wind direction to north + + name, err := world.SpawnRover("") + assert.NoError(t, err) + + // Warp the rover to 0,0 after clearing it + world.Atlas.SetObject(maths.Vector{X: 0, Y: 0}, Object{Type: roveapi.Object_ObjectUnknown}) + assert.NoError(t, world.WarpRover(name, maths.Vector{X: 0, Y: 0})) + + s, err := world.RoverToggle(name) + assert.NoError(t, err) + assert.Equal(t, roveapi.SailPosition_CatchingWind, s) + + b, err := world.RoverTurn(name, roveapi.Bearing_North) + assert.NoError(t, err) + assert.Equal(t, roveapi.Bearing_North, b) + + // Clear the space to the north + world.Atlas.SetObject(maths.Vector{X: 0, Y: 1}, Object{Type: roveapi.Object_ObjectUnknown}) + + // Tick the world and check we've moved not moved + world.Tick() + info, err := world.GetRover(name) + assert.NoError(t, err) + assert.Equal(t, maths.Vector{Y: 0}, info.Pos) + + // Loop a few more times + for i := 0; i < ticksPerNormalMove-2; i++ { + world.Tick() + info, err := world.GetRover(name) + assert.NoError(t, err) + assert.Equal(t, maths.Vector{Y: 0}, info.Pos) + } + + // Now check we've moved (after the TicksPerNormalMove number of ticks) + world.Tick() + info, err = world.GetRover(name) + assert.NoError(t, err) + assert.Equal(t, maths.Vector{Y: 1}, info.Pos) + + // Reset the world ticks back to stop any wind changes etc. + world.CurrentTicks = 1 + + // Face the rover south, into the wind + b, err = world.RoverTurn(name, roveapi.Bearing_South) + assert.NoError(t, err) + assert.Equal(t, roveapi.Bearing_South, b) + + // Tick a bunch, we should never move + for i := 0; i < ticksPerNormalMove*2; i++ { + world.Tick() + info, err := world.GetRover(name) + assert.NoError(t, err) + assert.Equal(t, maths.Vector{Y: 1}, info.Pos) + } + + // Reset the world ticks back to stop any wind changes etc. + world.CurrentTicks = 1 + world.Wind = roveapi.Bearing_SouthEast // Set up a south easternly wind + + // Turn the rover perpendicular + b, err = world.RoverTurn(name, roveapi.Bearing_NorthEast) + assert.NoError(t, err) + assert.Equal(t, roveapi.Bearing_NorthEast, b) + + // Clear a space + world.Atlas.SetObject(maths.Vector{X: 1, Y: 2}, Object{Type: roveapi.Object_ObjectUnknown}) + + // Now check we've moved immediately + world.Tick() + info, err = world.GetRover(name) + assert.NoError(t, err) + assert.Equal(t, maths.Vector{X: 1, Y: 2}, info.Pos) +} diff --git a/pkg/rove/worldgen.go b/pkg/rove/worldgen.go new file mode 100644 index 0000000..6cb425f --- /dev/null +++ b/pkg/rove/worldgen.go @@ -0,0 +1,70 @@ +package rove + +import ( + "github.com/mdiluz/rove/pkg/maths" + "github.com/mdiluz/rove/proto/roveapi" + "github.com/ojrac/opensimplex-go" +) + +// WorldGen describes a world gen algorythm +type WorldGen interface { + // GetTile generates a tile for a location + GetTile(v maths.Vector) roveapi.Tile + + // GetObject generates an object for a location + GetObject(v maths.Vector) Object +} + +// NoiseWorldGen returns a noise based world generator +type NoiseWorldGen struct { + // noise describes the noise function + noise opensimplex.Noise +} + +// NewNoiseWorldGen creates a new noise based world generator +func NewNoiseWorldGen(seed int64) WorldGen { + return &NoiseWorldGen{ + noise: opensimplex.New(seed), + } +} + +const ( + terrainNoiseScale = 15 + rockNoiseScale = 3 + partsNoiseScale = 2 +) + +// GetTile returns the chosen tile at a location +func (g *NoiseWorldGen) GetTile(v maths.Vector) roveapi.Tile { + t := g.noise.Eval2(float64(v.X)/terrainNoiseScale, float64(v.Y)/terrainNoiseScale) + switch { + case t > 0.5: + return roveapi.Tile_Gravel + case t > 0.05: + return roveapi.Tile_Sand + default: + return roveapi.Tile_Rock + } +} + +// GetObject returns the chosen object at a location +func (g *NoiseWorldGen) GetObject(v maths.Vector) (obj Object) { + r := g.noise.Eval2(float64(v.X)/rockNoiseScale, float64(v.Y)/rockNoiseScale) + switch { + // Prioritise rocks + case r > 0.6: + obj.Type = roveapi.Object_RockLarge + case r > 0.5: + obj.Type = roveapi.Object_RockSmall + + default: + // Otherwise, try some rover parts + p := g.noise.Eval2(float64(v.X)/partsNoiseScale, float64(v.Y)/partsNoiseScale) + switch { + case p > 0.7: + obj.Type = roveapi.Object_RoverParts + } + } + + return obj +} diff --git a/proto/roveapi/roveapi.pb.go b/proto/roveapi/roveapi.pb.go index e35c863..81a6cb5 100644 --- a/proto/roveapi/roveapi.pb.go +++ b/proto/roveapi/roveapi.pb.go @@ -39,35 +39,51 @@ type CommandType int32 const ( CommandType_none CommandType = 0 - // Move the rover in a direction, requires bearing - CommandType_move CommandType = 1 + // Waits before performing the next command + CommandType_wait CommandType = 1 + // Toggles the sails, either catching the wind, or charging from the sun + CommandType_toggle CommandType = 2 + // Turns the rover in the specified bearing (requires bearing) + CommandType_turn CommandType = 3 // Stashes item at current location in rover inventory - CommandType_stash CommandType = 2 + CommandType_stash CommandType = 4 // Repairs the rover using an inventory object - CommandType_repair CommandType = 3 - // Waits a tick to add more charge to the rover - CommandType_recharge CommandType = 4 - // Broadcasts a message to nearby rovers - CommandType_broadcast CommandType = 5 + CommandType_repair CommandType = 5 + // Broadcasts a message to nearby rovers (requires data) + CommandType_broadcast CommandType = 6 + // Salvages a neighboring dormant rover for parts + CommandType_salvage CommandType = 7 + // Transfers remote control into dormant rover + CommandType_transfer CommandType = 8 + // Upgrades a chosen rover specification using 5 rover parts + CommandType_upgrade CommandType = 9 ) // Enum value maps for CommandType. var ( CommandType_name = map[int32]string{ 0: "none", - 1: "move", - 2: "stash", - 3: "repair", - 4: "recharge", - 5: "broadcast", + 1: "wait", + 2: "toggle", + 3: "turn", + 4: "stash", + 5: "repair", + 6: "broadcast", + 7: "salvage", + 8: "transfer", + 9: "upgrade", } CommandType_value = map[string]int32{ "none": 0, - "move": 1, - "stash": 2, - "repair": 3, - "recharge": 4, - "broadcast": 5, + "wait": 1, + "toggle": 2, + "turn": 3, + "stash": 4, + "repair": 5, + "broadcast": 6, + "salvage": 7, + "transfer": 8, + "upgrade": 9, } ) @@ -98,15 +114,20 @@ func (CommandType) EnumDescriptor() ([]byte, []int) { return file_roveapi_roveapi_proto_rawDescGZIP(), []int{0} } +// Bearing represents a compass direction type Bearing int32 const ( // BearingUnknown an unknown invalid bearing Bearing_BearingUnknown Bearing = 0 Bearing_North Bearing = 1 - Bearing_East Bearing = 2 - Bearing_South Bearing = 3 - Bearing_West Bearing = 4 + Bearing_NorthEast Bearing = 2 + Bearing_East Bearing = 3 + Bearing_SouthEast Bearing = 4 + Bearing_South Bearing = 5 + Bearing_SouthWest Bearing = 6 + Bearing_West Bearing = 7 + Bearing_NorthWest Bearing = 8 ) // Enum value maps for Bearing. @@ -114,16 +135,24 @@ var ( Bearing_name = map[int32]string{ 0: "BearingUnknown", 1: "North", - 2: "East", - 3: "South", - 4: "West", + 2: "NorthEast", + 3: "East", + 4: "SouthEast", + 5: "South", + 6: "SouthWest", + 7: "West", + 8: "NorthWest", } Bearing_value = map[string]int32{ "BearingUnknown": 0, "North": 1, - "East": 2, - "South": 3, - "West": 4, + "NorthEast": 2, + "East": 3, + "SouthEast": 4, + "South": 5, + "SouthWest": 6, + "West": 7, + "NorthWest": 8, } ) @@ -154,6 +183,62 @@ func (Bearing) EnumDescriptor() ([]byte, []int) { return file_roveapi_roveapi_proto_rawDescGZIP(), []int{1} } +// Describes the type of upgrade +type RoverUpgrade int32 + +const ( + RoverUpgrade_RoverUpgradeUnknown RoverUpgrade = 0 + RoverUpgrade_Range RoverUpgrade = 1 + RoverUpgrade_Capacity RoverUpgrade = 2 + RoverUpgrade_MaximumIntegrity RoverUpgrade = 3 + RoverUpgrade_MaximumCharge RoverUpgrade = 4 +) + +// Enum value maps for RoverUpgrade. +var ( + RoverUpgrade_name = map[int32]string{ + 0: "RoverUpgradeUnknown", + 1: "Range", + 2: "Capacity", + 3: "MaximumIntegrity", + 4: "MaximumCharge", + } + RoverUpgrade_value = map[string]int32{ + "RoverUpgradeUnknown": 0, + "Range": 1, + "Capacity": 2, + "MaximumIntegrity": 3, + "MaximumCharge": 4, + } +) + +func (x RoverUpgrade) Enum() *RoverUpgrade { + p := new(RoverUpgrade) + *p = x + return p +} + +func (x RoverUpgrade) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RoverUpgrade) Descriptor() protoreflect.EnumDescriptor { + return file_roveapi_roveapi_proto_enumTypes[2].Descriptor() +} + +func (RoverUpgrade) Type() protoreflect.EnumType { + return &file_roveapi_roveapi_proto_enumTypes[2] +} + +func (x RoverUpgrade) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RoverUpgrade.Descriptor instead. +func (RoverUpgrade) EnumDescriptor() ([]byte, []int) { + return file_roveapi_roveapi_proto_rawDescGZIP(), []int{2} +} + // Types of objects type Object int32 @@ -162,10 +247,15 @@ const ( Object_ObjectUnknown Object = 0 // RoverLive represents a live rover Object_RoverLive Object = 1 + // RoverDormant describes a dormant rover + Object_RoverDormant Object = 2 // RockSmall is a small stashable rock - Object_RockSmall Object = 2 + Object_RockSmall Object = 3 // RockLarge is a large blocking rock - Object_RockLarge Object = 3 + Object_RockLarge Object = 4 + // RoverParts is one unit of rover parts, used for repairing and fixing the + // rover + Object_RoverParts Object = 5 ) // Enum value maps for Object. @@ -173,14 +263,18 @@ var ( Object_name = map[int32]string{ 0: "ObjectUnknown", 1: "RoverLive", - 2: "RockSmall", - 3: "RockLarge", + 2: "RoverDormant", + 3: "RockSmall", + 4: "RockLarge", + 5: "RoverParts", } Object_value = map[string]int32{ "ObjectUnknown": 0, "RoverLive": 1, - "RockSmall": 2, - "RockLarge": 3, + "RoverDormant": 2, + "RockSmall": 3, + "RockLarge": 4, + "RoverParts": 5, } ) @@ -195,11 +289,11 @@ func (x Object) String() string { } func (Object) Descriptor() protoreflect.EnumDescriptor { - return file_roveapi_roveapi_proto_enumTypes[2].Descriptor() + return file_roveapi_roveapi_proto_enumTypes[3].Descriptor() } func (Object) Type() protoreflect.EnumType { - return &file_roveapi_roveapi_proto_enumTypes[2] + return &file_roveapi_roveapi_proto_enumTypes[3] } func (x Object) Number() protoreflect.EnumNumber { @@ -208,7 +302,7 @@ func (x Object) Number() protoreflect.EnumNumber { // Deprecated: Use Object.Descriptor instead. func (Object) EnumDescriptor() ([]byte, []int) { - return file_roveapi_roveapi_proto_rawDescGZIP(), []int{2} + return file_roveapi_roveapi_proto_rawDescGZIP(), []int{3} } type Tile int32 @@ -251,11 +345,11 @@ func (x Tile) String() string { } func (Tile) Descriptor() protoreflect.EnumDescriptor { - return file_roveapi_roveapi_proto_enumTypes[3].Descriptor() + return file_roveapi_roveapi_proto_enumTypes[4].Descriptor() } func (Tile) Type() protoreflect.EnumType { - return &file_roveapi_roveapi_proto_enumTypes[3] + return &file_roveapi_roveapi_proto_enumTypes[4] } func (x Tile) Number() protoreflect.EnumNumber { @@ -264,7 +358,59 @@ func (x Tile) Number() protoreflect.EnumNumber { // Deprecated: Use Tile.Descriptor instead. func (Tile) EnumDescriptor() ([]byte, []int) { - return file_roveapi_roveapi_proto_rawDescGZIP(), []int{3} + return file_roveapi_roveapi_proto_rawDescGZIP(), []int{4} +} + +// SailPosition represents the position of the sola sail +type SailPosition int32 + +const ( + SailPosition_UnknownSailPosition SailPosition = 0 + // CatchingWind means the sail is catching the wind and moving the rover + SailPosition_CatchingWind SailPosition = 1 + // SolarCharging means the sail is facing the sun and charging + SailPosition_SolarCharging SailPosition = 2 +) + +// Enum value maps for SailPosition. +var ( + SailPosition_name = map[int32]string{ + 0: "UnknownSailPosition", + 1: "CatchingWind", + 2: "SolarCharging", + } + SailPosition_value = map[string]int32{ + "UnknownSailPosition": 0, + "CatchingWind": 1, + "SolarCharging": 2, + } +) + +func (x SailPosition) Enum() *SailPosition { + p := new(SailPosition) + *p = x + return p +} + +func (x SailPosition) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SailPosition) Descriptor() protoreflect.EnumDescriptor { + return file_roveapi_roveapi_proto_enumTypes[5].Descriptor() +} + +func (SailPosition) Type() protoreflect.EnumType { + return &file_roveapi_roveapi_proto_enumTypes[5] +} + +func (x SailPosition) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SailPosition.Descriptor instead. +func (SailPosition) EnumDescriptor() ([]byte, []int) { + return file_roveapi_roveapi_proto_rawDescGZIP(), []int{5} } // ServerStatusRequest is an empty placeholder @@ -555,10 +701,15 @@ type Command struct { // The command type Command CommandType `protobuf:"varint,1,opt,name=command,proto3,enum=roveapi.CommandType" json:"command,omitempty"` - // Types that are assignable to Data: - // *Command_Bearing - // *Command_Message - Data isCommand_Data `protobuf_oneof:"data"` + // The number of times to repeat the command after the first + Repeat int32 `protobuf:"varint,2,opt,name=repeat,proto3" json:"repeat,omitempty"` + // broadcast - a simple message, must be composed of up to 3 printable ASCII + // glyphs (32-126) + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + // move - the bearing for the rover to turn to + Bearing Bearing `protobuf:"varint,4,opt,name=bearing,proto3,enum=roveapi.Bearing" json:"bearing,omitempty"` + // upgrade - the upgrade to apply to the rover + Upgrade RoverUpgrade `protobuf:"varint,5,opt,name=upgrade,proto3,enum=roveapi.RoverUpgrade" json:"upgrade,omitempty"` } func (x *Command) Reset() { @@ -600,48 +751,34 @@ func (x *Command) GetCommand() CommandType { return CommandType_none } -func (m *Command) GetData() isCommand_Data { - if m != nil { - return m.Data +func (x *Command) GetRepeat() int32 { + if x != nil { + return x.Repeat + } + return 0 +} + +func (x *Command) GetData() []byte { + if x != nil { + return x.Data } return nil } func (x *Command) GetBearing() Bearing { - if x, ok := x.GetData().(*Command_Bearing); ok { + if x != nil { return x.Bearing } return Bearing_BearingUnknown } -func (x *Command) GetMessage() []byte { - if x, ok := x.GetData().(*Command_Message); ok { - return x.Message +func (x *Command) GetUpgrade() RoverUpgrade { + if x != nil { + return x.Upgrade } - return nil + return RoverUpgrade_RoverUpgradeUnknown } -type isCommand_Data interface { - isCommand_Data() -} - -type Command_Bearing struct { - // A bearing - // Used with MOVE - Bearing Bearing `protobuf:"varint,2,opt,name=bearing,proto3,enum=roveapi.Bearing,oneof"` -} - -type Command_Message struct { - // A simple message, must be composed of printable ASCII glyphs (32-126) - // maximum of three characters - // Used with BROADCAST - Message []byte `protobuf:"bytes,3,opt,name=message,proto3,oneof"` -} - -func (*Command_Bearing) isCommand_Data() {} - -func (*Command_Message) isCommand_Data() {} - // CommandRequest describes a set of commands to be requested for the rover type CommandRequest struct { state protoimpl.MessageState @@ -1019,42 +1156,267 @@ func (x *Vector) GetY() int32 { return 0 } -// StatusResponse is the response given to a status request -type StatusResponse struct { +type RoverSpecifications struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The name of the rover Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Position of the rover in world coordinates - Position *Vector `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` // The range of this rover's radar and broadcasting - Range int32 `protobuf:"varint,3,opt,name=range,proto3" json:"range,omitempty"` - // The items in the rover inventory - Inventory []byte `protobuf:"bytes,4,opt,name=inventory,proto3" json:"inventory,omitempty"` + Range int32 `protobuf:"varint,2,opt,name=range,proto3" json:"range,omitempty"` // The capacity of the inventory - Capacity int32 `protobuf:"varint,5,opt,name=capacity,proto3" json:"capacity,omitempty"` - // The current health of the rover - Integrity int32 `protobuf:"varint,6,opt,name=integrity,proto3" json:"integrity,omitempty"` + Capacity int32 `protobuf:"varint,3,opt,name=capacity,proto3" json:"capacity,omitempty"` // The maximum health of the rover - MaximumIntegrity int32 `protobuf:"varint,7,opt,name=maximumIntegrity,proto3" json:"maximumIntegrity,omitempty"` - // The energy stored in the rover - Charge int32 `protobuf:"varint,8,opt,name=charge,proto3" json:"charge,omitempty"` + MaximumIntegrity int32 `protobuf:"varint,4,opt,name=maximumIntegrity,proto3" json:"maximumIntegrity,omitempty"` // The max energy the rover can store - MaximumCharge int32 `protobuf:"varint,9,opt,name=maximumCharge,proto3" json:"maximumCharge,omitempty"` - // The set of currently incoming commands for this tick - IncomingCommands []*Command `protobuf:"bytes,10,rep,name=incomingCommands,proto3" json:"incomingCommands,omitempty"` + MaximumCharge int32 `protobuf:"varint,5,opt,name=maximumCharge,proto3" json:"maximumCharge,omitempty"` +} + +func (x *RoverSpecifications) Reset() { + *x = RoverSpecifications{} + if protoimpl.UnsafeEnabled { + mi := &file_roveapi_roveapi_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RoverSpecifications) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoverSpecifications) ProtoMessage() {} + +func (x *RoverSpecifications) ProtoReflect() protoreflect.Message { + mi := &file_roveapi_roveapi_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoverSpecifications.ProtoReflect.Descriptor instead. +func (*RoverSpecifications) Descriptor() ([]byte, []int) { + return file_roveapi_roveapi_proto_rawDescGZIP(), []int{13} +} + +func (x *RoverSpecifications) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RoverSpecifications) GetRange() int32 { + if x != nil { + return x.Range + } + return 0 +} + +func (x *RoverSpecifications) GetCapacity() int32 { + if x != nil { + return x.Capacity + } + return 0 +} + +func (x *RoverSpecifications) GetMaximumIntegrity() int32 { + if x != nil { + return x.MaximumIntegrity + } + return 0 +} + +func (x *RoverSpecifications) GetMaximumCharge() int32 { + if x != nil { + return x.MaximumCharge + } + return 0 +} + +type RoverStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The current direction of the rover + Bearing Bearing `protobuf:"varint,1,opt,name=bearing,proto3,enum=roveapi.Bearing" json:"bearing,omitempty"` + // The current position of the sails + SailPosition SailPosition `protobuf:"varint,2,opt,name=sailPosition,proto3,enum=roveapi.SailPosition" json:"sailPosition,omitempty"` + // The items in the rover inventory + Inventory []byte `protobuf:"bytes,3,opt,name=inventory,proto3" json:"inventory,omitempty"` + // The current health of the rover + Integrity int32 `protobuf:"varint,4,opt,name=integrity,proto3" json:"integrity,omitempty"` + // The energy stored in the rover + Charge int32 `protobuf:"varint,5,opt,name=charge,proto3" json:"charge,omitempty"` // The set of currently queued commands - QueuedCommands []*Command `protobuf:"bytes,11,rep,name=queuedCommands,proto3" json:"queuedCommands,omitempty"` + QueuedCommands []*Command `protobuf:"bytes,6,rep,name=queuedCommands,proto3" json:"queuedCommands,omitempty"` +} + +func (x *RoverStatus) Reset() { + *x = RoverStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_roveapi_roveapi_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RoverStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoverStatus) ProtoMessage() {} + +func (x *RoverStatus) ProtoReflect() protoreflect.Message { + mi := &file_roveapi_roveapi_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoverStatus.ProtoReflect.Descriptor instead. +func (*RoverStatus) Descriptor() ([]byte, []int) { + return file_roveapi_roveapi_proto_rawDescGZIP(), []int{14} +} + +func (x *RoverStatus) GetBearing() Bearing { + if x != nil { + return x.Bearing + } + return Bearing_BearingUnknown +} + +func (x *RoverStatus) GetSailPosition() SailPosition { + if x != nil { + return x.SailPosition + } + return SailPosition_UnknownSailPosition +} + +func (x *RoverStatus) GetInventory() []byte { + if x != nil { + return x.Inventory + } + return nil +} + +func (x *RoverStatus) GetIntegrity() int32 { + if x != nil { + return x.Integrity + } + return 0 +} + +func (x *RoverStatus) GetCharge() int32 { + if x != nil { + return x.Charge + } + return 0 +} + +func (x *RoverStatus) GetQueuedCommands() []*Command { + if x != nil { + return x.QueuedCommands + } + return nil +} + +type RoverReadings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Position of the rover in world coordinates + Position *Vector `protobuf:"bytes,1,opt,name=position,proto3" json:"position,omitempty"` + // The current wind direction + Wind Bearing `protobuf:"varint,2,opt,name=wind,proto3,enum=roveapi.Bearing" json:"wind,omitempty"` // The most recent logs - Logs []*Log `protobuf:"bytes,12,rep,name=logs,proto3" json:"logs,omitempty"` + Logs []*Log `protobuf:"bytes,3,rep,name=logs,proto3" json:"logs,omitempty"` +} + +func (x *RoverReadings) Reset() { + *x = RoverReadings{} + if protoimpl.UnsafeEnabled { + mi := &file_roveapi_roveapi_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RoverReadings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoverReadings) ProtoMessage() {} + +func (x *RoverReadings) ProtoReflect() protoreflect.Message { + mi := &file_roveapi_roveapi_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoverReadings.ProtoReflect.Descriptor instead. +func (*RoverReadings) Descriptor() ([]byte, []int) { + return file_roveapi_roveapi_proto_rawDescGZIP(), []int{15} +} + +func (x *RoverReadings) GetPosition() *Vector { + if x != nil { + return x.Position + } + return nil +} + +func (x *RoverReadings) GetWind() Bearing { + if x != nil { + return x.Wind + } + return Bearing_BearingUnknown +} + +func (x *RoverReadings) GetLogs() []*Log { + if x != nil { + return x.Logs + } + return nil +} + +// StatusResponse is the response given to a status request +type StatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The static rover information + Spec *RoverSpecifications `protobuf:"bytes,1,opt,name=spec,proto3" json:"spec,omitempty"` + // Current rover status + Status *RoverStatus `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + // Current rover readings + Readings *RoverReadings `protobuf:"bytes,3,opt,name=readings,proto3" json:"readings,omitempty"` } func (x *StatusResponse) Reset() { *x = StatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_roveapi_roveapi_proto_msgTypes[13] + mi := &file_roveapi_roveapi_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1067,7 +1429,7 @@ func (x *StatusResponse) String() string { func (*StatusResponse) ProtoMessage() {} func (x *StatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_roveapi_roveapi_proto_msgTypes[13] + mi := &file_roveapi_roveapi_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1080,89 +1442,26 @@ func (x *StatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead. func (*StatusResponse) Descriptor() ([]byte, []int) { - return file_roveapi_roveapi_proto_rawDescGZIP(), []int{13} + return file_roveapi_roveapi_proto_rawDescGZIP(), []int{16} } -func (x *StatusResponse) GetName() string { +func (x *StatusResponse) GetSpec() *RoverSpecifications { if x != nil { - return x.Name - } - return "" -} - -func (x *StatusResponse) GetPosition() *Vector { - if x != nil { - return x.Position + return x.Spec } return nil } -func (x *StatusResponse) GetRange() int32 { +func (x *StatusResponse) GetStatus() *RoverStatus { if x != nil { - return x.Range - } - return 0 -} - -func (x *StatusResponse) GetInventory() []byte { - if x != nil { - return x.Inventory + return x.Status } return nil } -func (x *StatusResponse) GetCapacity() int32 { +func (x *StatusResponse) GetReadings() *RoverReadings { if x != nil { - return x.Capacity - } - return 0 -} - -func (x *StatusResponse) GetIntegrity() int32 { - if x != nil { - return x.Integrity - } - return 0 -} - -func (x *StatusResponse) GetMaximumIntegrity() int32 { - if x != nil { - return x.MaximumIntegrity - } - return 0 -} - -func (x *StatusResponse) GetCharge() int32 { - if x != nil { - return x.Charge - } - return 0 -} - -func (x *StatusResponse) GetMaximumCharge() int32 { - if x != nil { - return x.MaximumCharge - } - return 0 -} - -func (x *StatusResponse) GetIncomingCommands() []*Command { - if x != nil { - return x.IncomingCommands - } - return nil -} - -func (x *StatusResponse) GetQueuedCommands() []*Command { - if x != nil { - return x.QueuedCommands - } - return nil -} - -func (x *StatusResponse) GetLogs() []*Log { - if x != nil { - return x.Logs + return x.Readings } return nil } @@ -1193,115 +1492,157 @@ var file_roveapi_roveapi_proto_rawDesc = []byte{ 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8b, 0x01, 0x0a, 0x07, 0x43, 0x6f, + 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc2, 0x01, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2c, 0x0a, 0x07, 0x62, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, - 0x2e, 0x42, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x07, 0x62, 0x65, 0x61, 0x72, - 0x69, 0x6e, 0x67, 0x12, 0x1a, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, - 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x6a, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, - 0x65, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, - 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0c, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, - 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0x75, 0x0a, 0x0d, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x74, 0x69, 0x6c, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, - 0x70, 0x69, 0x2e, 0x54, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x74, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x29, - 0x0a, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, - 0x0f, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x22, 0x3b, 0x0a, 0x0d, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, - 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2d, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x12, 0x0a, - 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x6d, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x24, 0x0a, 0x06, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, - 0x0c, 0x0a, 0x01, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x78, 0x12, 0x0c, 0x0a, - 0x01, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x79, 0x22, 0xc3, 0x03, 0x0a, 0x0e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x56, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x14, 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, - 0x72, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, - 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, - 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, - 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2a, 0x0a, - 0x10, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, - 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, - 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, - 0x72, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x68, 0x61, 0x72, 0x67, - 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x72, - 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, - 0x6d, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x3c, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, - 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x12, 0x38, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, - 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, - 0x0e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x12, - 0x20, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, - 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, - 0x73, 0x2a, 0x55, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x08, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x6d, 0x6f, - 0x76, 0x65, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x73, 0x68, 0x10, 0x02, 0x12, - 0x0a, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x61, 0x69, 0x72, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x72, - 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x62, 0x72, 0x6f, - 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x10, 0x05, 0x2a, 0x47, 0x0a, 0x07, 0x42, 0x65, 0x61, 0x72, - 0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x0e, 0x42, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x6e, - 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x4e, 0x6f, 0x72, 0x74, 0x68, - 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x61, 0x73, 0x74, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, - 0x53, 0x6f, 0x75, 0x74, 0x68, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x65, 0x73, 0x74, 0x10, - 0x04, 0x2a, 0x48, 0x0a, 0x06, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x11, 0x0a, 0x0d, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0d, - 0x0a, 0x09, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x4c, 0x69, 0x76, 0x65, 0x10, 0x01, 0x12, 0x0d, 0x0a, - 0x09, 0x52, 0x6f, 0x63, 0x6b, 0x53, 0x6d, 0x61, 0x6c, 0x6c, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, - 0x52, 0x6f, 0x63, 0x6b, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x10, 0x03, 0x2a, 0x37, 0x0a, 0x04, 0x54, - 0x69, 0x6c, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x69, 0x6c, 0x65, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, - 0x77, 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x6f, 0x63, 0x6b, 0x10, 0x01, 0x12, 0x0a, - 0x0a, 0x06, 0x47, 0x72, 0x61, 0x76, 0x65, 0x6c, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x61, - 0x6e, 0x64, 0x10, 0x03, 0x32, 0xcf, 0x02, 0x0a, 0x04, 0x52, 0x6f, 0x76, 0x65, 0x12, 0x4d, 0x0a, - 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x2e, - 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, - 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x08, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, - 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x3e, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x72, 0x6f, 0x76, - 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x38, 0x0a, 0x05, 0x52, 0x61, 0x64, 0x61, 0x72, 0x12, 0x15, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, - 0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x6f, - 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x64, 0x69, 0x6c, 0x75, 0x7a, 0x2f, 0x72, 0x6f, 0x76, 0x65, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x2a, 0x0a, 0x07, 0x62, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x65, 0x61, + 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x62, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x2f, 0x0a, + 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, + 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x70, + 0x67, 0x72, 0x61, 0x64, 0x65, 0x52, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x22, 0x6a, + 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x08, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x43, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, + 0x0c, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, + 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x75, 0x0a, 0x0d, 0x52, 0x61, 0x64, + 0x61, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x61, + 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x23, 0x0a, 0x05, 0x74, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, + 0x0d, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x69, 0x6c, 0x65, 0x52, 0x05, + 0x74, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, + 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x22, 0x3b, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2d, 0x0a, + 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x24, 0x0a, 0x06, + 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x0c, 0x0a, 0x01, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x01, 0x78, 0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x01, 0x79, 0x22, 0xad, 0x01, 0x0a, 0x13, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x70, 0x65, 0x63, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, + 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, + 0x12, 0x2a, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x49, 0x6e, 0x74, 0x65, 0x67, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x69, + 0x6d, 0x75, 0x6d, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x12, 0x24, 0x0a, 0x0d, + 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x72, + 0x67, 0x65, 0x22, 0x82, 0x02, 0x0a, 0x0b, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x07, 0x62, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x65, + 0x61, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x62, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x39, + 0x0a, 0x0c, 0x73, 0x61, 0x69, 0x6c, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, + 0x61, 0x69, 0x6c, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x61, 0x69, + 0x6c, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x76, + 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x69, 0x6e, + 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x67, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, + 0x67, 0x72, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x12, 0x38, 0x0a, + 0x0e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x0e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x43, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x22, 0x84, 0x01, 0x0a, 0x0d, 0x52, 0x6f, 0x76, 0x65, + 0x72, 0x52, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2b, 0x0a, 0x08, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x72, 0x6f, + 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x04, 0x77, 0x69, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x42, + 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x04, 0x77, 0x69, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x04, + 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x72, 0x6f, 0x76, + 0x65, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0xa4, + 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x30, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x53, + 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x04, 0x73, + 0x70, 0x65, 0x63, 0x12, 0x2c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x6f, + 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x32, 0x0a, 0x08, 0x72, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x6f, + 0x76, 0x65, 0x72, 0x52, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x72, 0x65, 0x61, + 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2a, 0x85, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, + 0x08, 0x0a, 0x04, 0x77, 0x61, 0x69, 0x74, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x74, 0x6f, 0x67, + 0x67, 0x6c, 0x65, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x74, 0x75, 0x72, 0x6e, 0x10, 0x03, 0x12, + 0x09, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x73, 0x68, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x72, 0x65, + 0x70, 0x61, 0x69, 0x72, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, + 0x61, 0x73, 0x74, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x73, 0x61, 0x6c, 0x76, 0x61, 0x67, 0x65, + 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x10, 0x08, + 0x12, 0x0b, 0x0a, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x10, 0x09, 0x2a, 0x83, 0x01, + 0x0a, 0x07, 0x42, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x0e, 0x42, 0x65, 0x61, + 0x72, 0x69, 0x6e, 0x67, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, + 0x05, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x6f, 0x72, 0x74, + 0x68, 0x45, 0x61, 0x73, 0x74, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x61, 0x73, 0x74, 0x10, + 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x45, 0x61, 0x73, 0x74, 0x10, 0x04, + 0x12, 0x09, 0x0a, 0x05, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x53, + 0x6f, 0x75, 0x74, 0x68, 0x57, 0x65, 0x73, 0x74, 0x10, 0x06, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x65, + 0x73, 0x74, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x57, 0x65, 0x73, + 0x74, 0x10, 0x08, 0x2a, 0x69, 0x0a, 0x0c, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x70, 0x67, 0x72, + 0x61, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x6f, 0x76, 0x65, 0x72, 0x55, 0x70, 0x67, 0x72, + 0x61, 0x64, 0x65, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x61, 0x70, 0x61, 0x63, + 0x69, 0x74, 0x79, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, + 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x4d, + 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x72, 0x67, 0x65, 0x10, 0x04, 0x2a, 0x6a, + 0x0a, 0x06, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x52, + 0x6f, 0x76, 0x65, 0x72, 0x4c, 0x69, 0x76, 0x65, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x52, 0x6f, + 0x76, 0x65, 0x72, 0x44, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x74, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, + 0x52, 0x6f, 0x63, 0x6b, 0x53, 0x6d, 0x61, 0x6c, 0x6c, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x52, + 0x6f, 0x63, 0x6b, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x6f, + 0x76, 0x65, 0x72, 0x50, 0x61, 0x72, 0x74, 0x73, 0x10, 0x05, 0x2a, 0x37, 0x0a, 0x04, 0x54, 0x69, + 0x6c, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x69, 0x6c, 0x65, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, + 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x6f, 0x63, 0x6b, 0x10, 0x01, 0x12, 0x0a, 0x0a, + 0x06, 0x47, 0x72, 0x61, 0x76, 0x65, 0x6c, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x61, 0x6e, + 0x64, 0x10, 0x03, 0x2a, 0x4c, 0x0a, 0x0c, 0x53, 0x61, 0x69, 0x6c, 0x50, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x53, 0x61, + 0x69, 0x6c, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, + 0x43, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x57, 0x69, 0x6e, 0x64, 0x10, 0x01, 0x12, 0x11, + 0x0a, 0x0d, 0x53, 0x6f, 0x6c, 0x61, 0x72, 0x43, 0x68, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x67, 0x10, + 0x02, 0x32, 0xcf, 0x02, 0x0a, 0x04, 0x52, 0x6f, 0x76, 0x65, 0x12, 0x4d, 0x0a, 0x0c, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x76, + 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, + 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x08, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x19, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x07, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, + 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x05, + 0x52, 0x61, 0x64, 0x61, 0x72, 0x12, 0x15, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, + 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, + 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x61, 0x64, 0x61, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x16, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x6f, 0x76, 0x65, 0x61, + 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x6d, 0x64, 0x69, 0x6c, 0x75, 0x7a, 0x2f, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x76, 0x65, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -1316,57 +1657,68 @@ func file_roveapi_roveapi_proto_rawDescGZIP() []byte { return file_roveapi_roveapi_proto_rawDescData } -var file_roveapi_roveapi_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_roveapi_roveapi_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_roveapi_roveapi_proto_enumTypes = make([]protoimpl.EnumInfo, 6) +var file_roveapi_roveapi_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_roveapi_roveapi_proto_goTypes = []interface{}{ (CommandType)(0), // 0: roveapi.CommandType (Bearing)(0), // 1: roveapi.Bearing - (Object)(0), // 2: roveapi.Object - (Tile)(0), // 3: roveapi.Tile - (*ServerStatusRequest)(nil), // 4: roveapi.ServerStatusRequest - (*ServerStatusResponse)(nil), // 5: roveapi.ServerStatusResponse - (*RegisterRequest)(nil), // 6: roveapi.RegisterRequest - (*Account)(nil), // 7: roveapi.Account - (*RegisterResponse)(nil), // 8: roveapi.RegisterResponse - (*Command)(nil), // 9: roveapi.Command - (*CommandRequest)(nil), // 10: roveapi.CommandRequest - (*CommandResponse)(nil), // 11: roveapi.CommandResponse - (*RadarRequest)(nil), // 12: roveapi.RadarRequest - (*RadarResponse)(nil), // 13: roveapi.RadarResponse - (*StatusRequest)(nil), // 14: roveapi.StatusRequest - (*Log)(nil), // 15: roveapi.Log - (*Vector)(nil), // 16: roveapi.Vector - (*StatusResponse)(nil), // 17: roveapi.StatusResponse + (RoverUpgrade)(0), // 2: roveapi.RoverUpgrade + (Object)(0), // 3: roveapi.Object + (Tile)(0), // 4: roveapi.Tile + (SailPosition)(0), // 5: roveapi.SailPosition + (*ServerStatusRequest)(nil), // 6: roveapi.ServerStatusRequest + (*ServerStatusResponse)(nil), // 7: roveapi.ServerStatusResponse + (*RegisterRequest)(nil), // 8: roveapi.RegisterRequest + (*Account)(nil), // 9: roveapi.Account + (*RegisterResponse)(nil), // 10: roveapi.RegisterResponse + (*Command)(nil), // 11: roveapi.Command + (*CommandRequest)(nil), // 12: roveapi.CommandRequest + (*CommandResponse)(nil), // 13: roveapi.CommandResponse + (*RadarRequest)(nil), // 14: roveapi.RadarRequest + (*RadarResponse)(nil), // 15: roveapi.RadarResponse + (*StatusRequest)(nil), // 16: roveapi.StatusRequest + (*Log)(nil), // 17: roveapi.Log + (*Vector)(nil), // 18: roveapi.Vector + (*RoverSpecifications)(nil), // 19: roveapi.RoverSpecifications + (*RoverStatus)(nil), // 20: roveapi.RoverStatus + (*RoverReadings)(nil), // 21: roveapi.RoverReadings + (*StatusResponse)(nil), // 22: roveapi.StatusResponse } var file_roveapi_roveapi_proto_depIdxs = []int32{ - 7, // 0: roveapi.RegisterResponse.account:type_name -> roveapi.Account + 9, // 0: roveapi.RegisterResponse.account:type_name -> roveapi.Account 0, // 1: roveapi.Command.command:type_name -> roveapi.CommandType 1, // 2: roveapi.Command.bearing:type_name -> roveapi.Bearing - 7, // 3: roveapi.CommandRequest.account:type_name -> roveapi.Account - 9, // 4: roveapi.CommandRequest.commands:type_name -> roveapi.Command - 7, // 5: roveapi.RadarRequest.account:type_name -> roveapi.Account - 3, // 6: roveapi.RadarResponse.tiles:type_name -> roveapi.Tile - 2, // 7: roveapi.RadarResponse.objects:type_name -> roveapi.Object - 7, // 8: roveapi.StatusRequest.account:type_name -> roveapi.Account - 16, // 9: roveapi.StatusResponse.position:type_name -> roveapi.Vector - 9, // 10: roveapi.StatusResponse.incomingCommands:type_name -> roveapi.Command - 9, // 11: roveapi.StatusResponse.queuedCommands:type_name -> roveapi.Command - 15, // 12: roveapi.StatusResponse.logs:type_name -> roveapi.Log - 4, // 13: roveapi.Rove.ServerStatus:input_type -> roveapi.ServerStatusRequest - 6, // 14: roveapi.Rove.Register:input_type -> roveapi.RegisterRequest - 10, // 15: roveapi.Rove.Command:input_type -> roveapi.CommandRequest - 12, // 16: roveapi.Rove.Radar:input_type -> roveapi.RadarRequest - 14, // 17: roveapi.Rove.Status:input_type -> roveapi.StatusRequest - 5, // 18: roveapi.Rove.ServerStatus:output_type -> roveapi.ServerStatusResponse - 8, // 19: roveapi.Rove.Register:output_type -> roveapi.RegisterResponse - 11, // 20: roveapi.Rove.Command:output_type -> roveapi.CommandResponse - 13, // 21: roveapi.Rove.Radar:output_type -> roveapi.RadarResponse - 17, // 22: roveapi.Rove.Status:output_type -> roveapi.StatusResponse - 18, // [18:23] is the sub-list for method output_type - 13, // [13:18] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name + 2, // 3: roveapi.Command.upgrade:type_name -> roveapi.RoverUpgrade + 9, // 4: roveapi.CommandRequest.account:type_name -> roveapi.Account + 11, // 5: roveapi.CommandRequest.commands:type_name -> roveapi.Command + 9, // 6: roveapi.RadarRequest.account:type_name -> roveapi.Account + 4, // 7: roveapi.RadarResponse.tiles:type_name -> roveapi.Tile + 3, // 8: roveapi.RadarResponse.objects:type_name -> roveapi.Object + 9, // 9: roveapi.StatusRequest.account:type_name -> roveapi.Account + 1, // 10: roveapi.RoverStatus.bearing:type_name -> roveapi.Bearing + 5, // 11: roveapi.RoverStatus.sailPosition:type_name -> roveapi.SailPosition + 11, // 12: roveapi.RoverStatus.queuedCommands:type_name -> roveapi.Command + 18, // 13: roveapi.RoverReadings.position:type_name -> roveapi.Vector + 1, // 14: roveapi.RoverReadings.wind:type_name -> roveapi.Bearing + 17, // 15: roveapi.RoverReadings.logs:type_name -> roveapi.Log + 19, // 16: roveapi.StatusResponse.spec:type_name -> roveapi.RoverSpecifications + 20, // 17: roveapi.StatusResponse.status:type_name -> roveapi.RoverStatus + 21, // 18: roveapi.StatusResponse.readings:type_name -> roveapi.RoverReadings + 6, // 19: roveapi.Rove.ServerStatus:input_type -> roveapi.ServerStatusRequest + 8, // 20: roveapi.Rove.Register:input_type -> roveapi.RegisterRequest + 12, // 21: roveapi.Rove.Command:input_type -> roveapi.CommandRequest + 14, // 22: roveapi.Rove.Radar:input_type -> roveapi.RadarRequest + 16, // 23: roveapi.Rove.Status:input_type -> roveapi.StatusRequest + 7, // 24: roveapi.Rove.ServerStatus:output_type -> roveapi.ServerStatusResponse + 10, // 25: roveapi.Rove.Register:output_type -> roveapi.RegisterResponse + 13, // 26: roveapi.Rove.Command:output_type -> roveapi.CommandResponse + 15, // 27: roveapi.Rove.Radar:output_type -> roveapi.RadarResponse + 22, // 28: roveapi.Rove.Status:output_type -> roveapi.StatusResponse + 24, // [24:29] is the sub-list for method output_type + 19, // [19:24] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_roveapi_roveapi_proto_init() } @@ -1532,6 +1884,42 @@ func file_roveapi_roveapi_proto_init() { } } file_roveapi_roveapi_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RoverSpecifications); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_roveapi_roveapi_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RoverStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_roveapi_roveapi_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RoverReadings); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_roveapi_roveapi_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StatusResponse); i { case 0: return &v.state @@ -1544,17 +1932,13 @@ func file_roveapi_roveapi_proto_init() { } } } - file_roveapi_roveapi_proto_msgTypes[5].OneofWrappers = []interface{}{ - (*Command_Bearing)(nil), - (*Command_Message)(nil), - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_roveapi_roveapi_proto_rawDesc, - NumEnums: 4, - NumMessages: 14, + NumEnums: 6, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/roveapi/roveapi.proto b/proto/roveapi/roveapi.proto index b43087b..72a1fc2 100644 --- a/proto/roveapi/roveapi.proto +++ b/proto/roveapi/roveapi.proto @@ -89,25 +89,47 @@ message RegisterResponse { // CommandType defines the type of a command to give to the rover enum CommandType { none = 0; - // Move the rover in a direction, requires bearing - move = 1; + // Waits before performing the next command + wait = 1; + // Toggles the sails, either catching the wind, or charging from the sun + toggle = 2; + // Turns the rover in the specified bearing (requires bearing) + turn = 3; // Stashes item at current location in rover inventory - stash = 2; + stash = 4; // Repairs the rover using an inventory object - repair = 3; - // Waits a tick to add more charge to the rover - recharge = 4; - // Broadcasts a message to nearby rovers - broadcast = 5; + repair = 5; + // Broadcasts a message to nearby rovers (requires data) + broadcast = 6; + // Salvages a neighboring dormant rover for parts + salvage = 7; + // Transfers remote control into dormant rover + transfer = 8; + // Upgrades a chosen rover specification using 5 rover parts + upgrade = 9; } +// Bearing represents a compass direction enum Bearing { // BearingUnknown an unknown invalid bearing BearingUnknown = 0; North = 1; - East = 2; - South = 3; - West = 4; + NorthEast = 2; + East = 3; + SouthEast = 4; + South = 5; + SouthWest = 6; + West = 7; + NorthWest = 8; +} + +// Describes the type of upgrade +enum RoverUpgrade { + RoverUpgradeUnknown = 0; + Range = 1; + Capacity = 2; + MaximumIntegrity = 3; + MaximumCharge = 4; } // Command is a single command for a rover @@ -115,16 +137,18 @@ message Command { // The command type CommandType command = 1; - oneof data { - // A bearing - // Used with MOVE - Bearing bearing = 2; + // The number of times to repeat the command after the first + int32 repeat = 2; - // A simple message, must be composed of printable ASCII glyphs (32-126) - // maximum of three characters - // Used with BROADCAST - bytes message = 3; - } + // broadcast - a simple message, must be composed of up to 3 printable ASCII + // glyphs (32-126) + bytes data = 3; + + // move - the bearing for the rover to turn to + Bearing bearing = 4; + + // upgrade - the upgrade to apply to the rover + RoverUpgrade upgrade = 5; } // CommandRequest describes a set of commands to be requested for the rover @@ -151,11 +175,18 @@ enum Object { // RoverLive represents a live rover RoverLive = 1; + // RoverDormant describes a dormant rover + RoverDormant = 2; + // RockSmall is a small stashable rock - RockSmall = 2; + RockSmall = 3; // RockLarge is a large blocking rock - RockLarge = 3; + RockLarge = 4; + + // RoverParts is one unit of rover parts, used for repairing and fixing the + // rover + RoverParts = 5; } enum Tile { @@ -216,41 +247,76 @@ message Vector { int32 y = 2; } -// StatusResponse is the response given to a status request -message StatusResponse { +// SailPosition represents the position of the sola sail +enum SailPosition { + UnknownSailPosition = 0; + + // CatchingWind means the sail is catching the wind and moving the rover + CatchingWind = 1; + + // SolarCharging means the sail is facing the sun and charging + SolarCharging = 2; +} + +message RoverSpecifications { + // The name of the rover string name = 1; - // Position of the rover in world coordinates - Vector position = 2; - // The range of this rover's radar and broadcasting - int32 range = 3; - - // The items in the rover inventory - bytes inventory = 4; + int32 range = 2; // The capacity of the inventory - int32 capacity = 5; - - // The current health of the rover - int32 integrity = 6; + int32 capacity = 3; // The maximum health of the rover - int32 maximumIntegrity = 7; - - // The energy stored in the rover - int32 charge = 8; + int32 maximumIntegrity = 4; // The max energy the rover can store - int32 maximumCharge = 9; + int32 maximumCharge = 5; +} - // The set of currently incoming commands for this tick - repeated Command incomingCommands = 10; +message RoverStatus { + + // The current direction of the rover + Bearing bearing = 1; + + // The current position of the sails + SailPosition sailPosition = 2; + + // The items in the rover inventory + bytes inventory = 3; + + // The current health of the rover + int32 integrity = 4; + + // The energy stored in the rover + int32 charge = 5; // The set of currently queued commands - repeated Command queuedCommands = 11; + repeated Command queuedCommands = 6; +} + +message RoverReadings { + // Position of the rover in world coordinates + Vector position = 1; + + // The current wind direction + Bearing wind = 2; // The most recent logs - repeated Log logs = 12; + repeated Log logs = 3; +} + +// StatusResponse is the response given to a status request +message StatusResponse { + + // The static rover information + RoverSpecifications spec = 1; + + // Current rover status + RoverStatus status = 2; + + // Current rover readings + RoverReadings readings = 3; } \ No newline at end of file