diff --git a/server/cmd/argument.go b/server/cmd/argument.go
index 1d97b0e42..cd3939823 100644
--- a/server/cmd/argument.go
+++ b/server/cmd/argument.go
@@ -1,10 +1,8 @@
package cmd
import (
- "errors"
"fmt"
"github.com/df-mc/dragonfly/server/internal/sliceutil"
- "github.com/df-mc/dragonfly/server/player/chat"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
"math/rand"
@@ -21,18 +19,24 @@ type Line struct {
args []string
seen []string
src Source
+ cmd Command
}
// SyntaxError returns a translated syntax error.
func (line *Line) SyntaxError() error {
if len(line.args) == 0 {
- return chat.MessageCommandSyntax.F(line.seen, "", "")
+ return MessageSyntax.F(strings.Join(line.seen, " "), "", "")
}
next := strings.Join(line.args[1:], " ")
if next != "" {
next = " " + next
}
- return chat.MessageCommandSyntax.F(strings.Join(line.seen, " ")+" ", line.args[0], next)
+ return MessageSyntax.F(strings.Join(line.seen, " ")+" ", line.args[0], next)
+}
+
+// UsageError returns a translated usage error.
+func (line *Line) UsageError() error {
+ return MessageUsage.F(line.cmd.Usage())
}
// Next reads the next argument from the command line and returns it. If there were no more arguments to
@@ -137,19 +141,15 @@ func (p parser) parseArgument(line *Line, v reflect.Value, optional bool, name s
return err, err == nil
}
-// ErrInsufficientArgs is returned by argument parsing functions if it does not have sufficient arguments
-// passed and is not optional.
-var ErrInsufficientArgs = errors.New("not enough arguments for command")
-
// int ...
func (p parser) int(line *Line, v reflect.Value) error {
arg, ok := line.Next()
if !ok {
- return line.SyntaxError()
+ return line.UsageError()
}
value, err := strconv.ParseInt(arg, 10, v.Type().Bits())
if err != nil {
- return line.SyntaxError()
+ return MessageNumberInvalid.F(arg)
}
v.SetInt(value)
return nil
@@ -159,11 +159,11 @@ func (p parser) int(line *Line, v reflect.Value) error {
func (p parser) uint(line *Line, v reflect.Value) error {
arg, ok := line.Next()
if !ok {
- return line.SyntaxError()
+ return line.UsageError()
}
value, err := strconv.ParseUint(arg, 10, v.Type().Bits())
if err != nil {
- return line.SyntaxError()
+ return MessageNumberInvalid.F(arg)
}
v.SetUint(value)
return nil
@@ -173,11 +173,11 @@ func (p parser) uint(line *Line, v reflect.Value) error {
func (p parser) float(line *Line, v reflect.Value) error {
arg, ok := line.Next()
if !ok {
- return line.SyntaxError()
+ return line.UsageError()
}
value, err := strconv.ParseFloat(arg, v.Type().Bits())
if err != nil {
- return line.SyntaxError()
+ return MessageNumberInvalid.F(arg)
}
v.SetFloat(value)
return nil
@@ -187,7 +187,7 @@ func (p parser) float(line *Line, v reflect.Value) error {
func (p parser) string(line *Line, v reflect.Value) error {
arg, ok := line.Next()
if !ok {
- return line.SyntaxError()
+ return line.UsageError()
}
v.SetString(arg)
return nil
@@ -197,11 +197,11 @@ func (p parser) string(line *Line, v reflect.Value) error {
func (p parser) bool(line *Line, v reflect.Value) error {
arg, ok := line.Next()
if !ok {
- return line.SyntaxError()
+ return line.UsageError()
}
value, err := strconv.ParseBool(arg)
if err != nil {
- return line.SyntaxError()
+ return MessageBooleanInvalid.F(arg)
}
v.SetBool(value)
return nil
@@ -211,14 +211,14 @@ func (p parser) bool(line *Line, v reflect.Value) error {
func (p parser) enum(line *Line, val reflect.Value, v Enum, source Source) error {
arg, ok := line.Next()
if !ok {
- return line.SyntaxError()
+ return line.UsageError()
}
opts := v.Options(source)
ind := slices.IndexFunc(opts, func(s string) bool {
return strings.EqualFold(s, arg)
})
if ind < 0 {
- return line.SyntaxError()
+ return MessageParameterInvalid.F(arg)
}
val.SetString(opts[ind])
return nil
@@ -228,12 +228,12 @@ func (p parser) enum(line *Line, val reflect.Value, v Enum, source Source) error
func (p parser) sub(line *Line, name string) error {
arg, ok := line.Next()
if !ok {
- return line.SyntaxError()
+ return line.UsageError()
}
if strings.EqualFold(name, arg) {
return nil
}
- return line.SyntaxError()
+ return MessageParameterInvalid.F(arg)
}
// vec3 ...
@@ -262,7 +262,7 @@ func (p parser) targets(line *Line, v reflect.Value, tx *world.Tx) error {
return err
}
if len(targets) == 0 {
- return chat.MessageCommandNoTargets.F()
+ return MessageNoTargets.F()
}
v.Set(reflect.ValueOf(targets))
return nil
@@ -273,9 +273,9 @@ func (p parser) parseTargets(line *Line, tx *world.Tx) ([]Target, error) {
entities, players := targets(tx)
first, ok := line.Next()
if !ok {
- return nil, line.SyntaxError()
+ return nil, line.UsageError()
}
- switch first {
+ switch first[:2] {
case "@p":
pos := line.src.Position()
playerDistances := make([]float64, len(players))
@@ -301,27 +301,27 @@ func (p parser) parseTargets(line *Line, tx *world.Tx) ([]Target, error) {
}
return []Target{players[rand.Intn(len(players))]}, nil
default:
- target, ok := p.parsePlayer(line, players)
- if ok {
- return []Target{target}, nil
+ target, err := p.parsePlayer(line, players)
+ if err != nil {
+ return nil, err
}
- return nil, nil
+ return []Target{target}, nil
}
}
// parsePlayer parses one Player from the Line, consuming multiple arguments
// from Line if necessary.
-func (p parser) parsePlayer(line *Line, players []NamedTarget) (Target, bool) {
+func (p parser) parsePlayer(line *Line, players []NamedTarget) (Target, error) {
name := ""
for i := 0; i < line.Len(); i++ {
name += line.args[0]
if ind := slices.IndexFunc(players, func(target NamedTarget) bool {
return strings.EqualFold(target.Name(), name)
}); ind != -1 {
- return players[ind], true
+ return players[ind], nil
}
name += " "
line.RemoveNext()
}
- return nil, false
+ return nil, MessagePlayerNotFound.F()
}
diff --git a/server/cmd/command.go b/server/cmd/command.go
index a7d46a374..249338912 100644
--- a/server/cmd/command.go
+++ b/server/cmd/command.go
@@ -3,7 +3,6 @@ package cmd
import (
"encoding/csv"
"fmt"
- "github.com/df-mc/dragonfly/server/player/chat"
"github.com/df-mc/dragonfly/server/world"
"go/ast"
"reflect"
@@ -131,7 +130,7 @@ func (cmd Command) Execute(args string, source Source, tx *world.Tx) {
defer source.SendCommandOutput(output)
var leastErroneous error
- leastArgsLeft := len(strings.Split(args, " "))
+ var leastArgsLeft *Line
for _, v := range cmd.v {
cp := reflect.New(v.Type())
@@ -150,15 +149,18 @@ func (cmd Command) Execute(args string, source Source, tx *world.Tx) {
}
continue
}
- if line.Len() <= leastArgsLeft {
+ if leastArgsLeft == nil || line.Len() <= leastArgsLeft.Len() {
// If the line had less (or equal) arguments left than the previous lowest, we update the error,
// so that we can return an error that applies for the most successful Runnable.
leastErroneous = err
- leastArgsLeft = line.Len()
+ leastArgsLeft = line
}
}
- // No working Runnable found for the arguments passed. We add the most applicable error to the output and
- // stop there.
+ // No working Runnable found for the arguments passed. We add the most
+ // applicable error to the output and stop there.
+ if leastArgsLeft != nil {
+ output.Error(leastArgsLeft.SyntaxError())
+ }
output.Error(leastErroneous)
}
@@ -222,7 +224,7 @@ func (cmd Command) String() string {
// leftover command line.
func (cmd Command) executeRunnable(v reflect.Value, args string, source Source, output *Output, tx *world.Tx) (*Line, error) {
if a, ok := v.Interface().(Allower); ok && !a.Allow(source) {
- return nil, chat.MessageCommandUnknown.F(cmd.name)
+ return nil, MessageUnknown.F(cmd.name)
}
var argFrags []string
@@ -234,12 +236,12 @@ func (cmd Command) executeRunnable(v reflect.Value, args string, source Source,
// When LazyQuotes is enabled, this really never appears to return
// an error when we read only one line. Just in case it does though,
// we return the command usage.
- return nil, chat.MessageCommandUsage.F(cmd.Usage())
+ return nil, MessageUsage.F(cmd.Usage())
}
argFrags = record
}
parser := parser{}
- arguments := &Line{args: argFrags, src: source, seen: []string{"/" + cmd.name}}
+ arguments := &Line{args: argFrags, src: source, seen: []string{"/" + cmd.name}, cmd: cmd}
// We iterate over all the fields of the struct: Each of the fields will have an argument parsed to
// produce its value.
@@ -265,7 +267,7 @@ func (cmd Command) executeRunnable(v reflect.Value, args string, source Source,
}
}
if arguments.Len() != 0 {
- return arguments, arguments.SyntaxError()
+ return arguments, arguments.UsageError()
}
v.Interface().(Runnable).Run(source, output, tx)
diff --git a/server/cmd/output.go b/server/cmd/output.go
index cf5bed416..7e9b4f343 100644
--- a/server/cmd/output.go
+++ b/server/cmd/output.go
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"github.com/df-mc/dragonfly/server/player/chat"
+ "golang.org/x/text/language"
)
// Output holds the output of a command execution. It holds success messages
@@ -78,3 +79,17 @@ func (o *Output) MessageCount() int {
type stringer string
func (s stringer) String() string { return string(s) }
+
+var MessageSyntax = chat.Translate(str("%commands.generic.syntax"), 3, `Syntax error: unexpected value: at "%v>>%v<<%v"`).Enc("%v")
+var MessageUsage = chat.Translate(str("%commands.generic.usage"), 1, `Usage: %v`).Enc("%v")
+var MessageUnknown = chat.Translate(str("%commands.generic.unknown"), 1, `Unknown command: "%v": Please check that the command exists and that you have permission to use it.`).Enc("%v")
+var MessageNoTargets = chat.Translate(str("%commands.generic.noTargetMatch"), 0, `No targets matched selector`).Enc("%v")
+var MessageNumberInvalid = chat.Translate(str("%commands.generic.num.invalid"), 1, `'%v' is not a valid number`).Enc("> %v")
+var MessageBooleanInvalid = chat.Translate(str("%commands.generic.boolean.invalid"), 1, `'%v' is not true or false`).Enc("> %v")
+var MessagePlayerNotFound = chat.Translate(str("%commands.generic.player.notFound"), 0, `That player cannot be found`).Enc("> %v")
+var MessageParameterInvalid = chat.Translate(str("%commands.generic.parameter.invalid"), 1, `'%v' is not a valid parameter`).Enc("> %v")
+
+type str string
+
+// Resolve returns the translation identifier as a string.
+func (s str) Resolve(language.Tag) string { return string(s) }
diff --git a/server/player/chat/translate.go b/server/player/chat/translate.go
index 4c016cdd2..5ecb66ee1 100644
--- a/server/player/chat/translate.go
+++ b/server/player/chat/translate.go
@@ -12,11 +12,6 @@ var MessageJoin = Translate(str("%multiplayer.player.joined"), 1, `%v joined the
var MessageQuit = Translate(str("%multiplayer.player.left"), 1, `%v left the game`).Enc("%v")
var MessageServerDisconnect = Translate(str("%disconnect.disconnected"), 0, `Disconnected by Server`).Enc("%v")
-var MessageCommandSyntax = Translate(str("%commands.generic.syntax"), 3, `Syntax error: unexpected value: at "%v>>%v<<%v"`)
-var MessageCommandUsage = Translate(str("%commands.generic.usage"), 1, `Usage: %v`)
-var MessageCommandUnknown = Translate(str("%commands.generic.unknown"), 1, `Unknown command: "%v": Please check that the command exists and that you have permission to use it.`)
-var MessageCommandNoTargets = Translate(str("%commands.generic.noTargetMatch"), 0, `No targets matched selector`)
-
type str string
// Resolve returns the translation identifier as a string.
diff --git a/server/player/player.go b/server/player/player.go
index 297909ab3..398a3927f 100644
--- a/server/player/player.go
+++ b/server/player/player.go
@@ -332,7 +332,7 @@ func (p *Player) ExecuteCommand(commandLine string) {
command, ok := cmd.ByAlias(args[0][1:])
if !ok {
o := &cmd.Output{}
- o.Errort(chat.MessageCommandUnknown, args[0])
+ o.Errort(cmd.MessageUnknown, args[0])
p.SendCommandOutput(o)
return
}
diff --git a/server/world/entity.go b/server/world/entity.go
index 69c2b7377..aa15e4d10 100644
--- a/server/world/entity.go
+++ b/server/world/entity.go
@@ -149,7 +149,7 @@ func (e *EntityHandle) Close() error {
// run the transaction function once it is. If the Entity is closed before
// ExecWorld is called, ExecWorld will return false immediately without running
// the transaction function.
-func (e *EntityHandle) ExecWorld(f func(tx *Tx, e Entity)) (run bool) {
+func (e *EntityHandle) ExecWorld(f func(tx *Tx, e Entity)) bool {
return e.execWorld(f, false)
}