up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/cmd"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// SendCommandOutput sends the output of a command to the player. It will be shown to the caller of the
|
||||
// command, which might be the player or a websocket server.
|
||||
func (s *Session) SendCommandOutput(output *cmd.Output, l language.Tag) {
|
||||
if s == Nop {
|
||||
return
|
||||
}
|
||||
messages := make([]protocol.CommandOutputMessage, 0, output.MessageCount()+output.ErrorCount())
|
||||
for _, message := range output.Messages() {
|
||||
om := protocol.CommandOutputMessage{Success: true, Message: message.String()}
|
||||
if t, ok := message.(translation); ok {
|
||||
om.Message, om.Parameters = t.Resolve(l), t.Params(l)
|
||||
}
|
||||
messages = append(messages, om)
|
||||
}
|
||||
for _, err := range output.Errors() {
|
||||
om := protocol.CommandOutputMessage{Message: err.Error()}
|
||||
if t, ok := err.(translation); ok {
|
||||
om.Message, om.Parameters = t.Resolve(l), t.Params(l)
|
||||
}
|
||||
messages = append(messages, om)
|
||||
}
|
||||
|
||||
s.writePacket(&packet.CommandOutput{
|
||||
CommandOrigin: s.handlers[packet.IDCommandRequest].(*CommandRequestHandler).origin,
|
||||
OutputType: packet.CommandOutputTypeAllOutput,
|
||||
SuccessCount: uint32(output.MessageCount()),
|
||||
OutputMessages: messages,
|
||||
})
|
||||
}
|
||||
|
||||
type translation interface {
|
||||
Resolve(l language.Tag) string
|
||||
Params(l language.Tag) []string
|
||||
}
|
||||
|
||||
// BuildAvailableCommands builds an AvailableCommands packet and the runnable command map for the Source
|
||||
// passed. The input map may contain aliases. It returns the AvailableCommands packet and the runnable command map
|
||||
// for the commands that the Source can execute.
|
||||
func BuildAvailableCommands(
|
||||
commands map[string]cmd.Command,
|
||||
src cmd.Source,
|
||||
softEnums map[string]struct{},
|
||||
) (*packet.AvailableCommands, map[string]map[int]cmd.Runnable) {
|
||||
m := make(map[string]map[int]cmd.Runnable, len(commands))
|
||||
|
||||
pk := &packet.AvailableCommands{}
|
||||
|
||||
var enums []commandEnum
|
||||
enumIndices := map[string]uint32{}
|
||||
|
||||
var dynamicEnums []commandEnum
|
||||
dynamicEnumIndices := map[string]uint32{}
|
||||
|
||||
suffixIndices := map[string]uint32{}
|
||||
|
||||
for alias, c := range commands {
|
||||
if c.Name() != alias {
|
||||
// Don't add duplicate entries for aliases.
|
||||
continue
|
||||
}
|
||||
|
||||
if run := c.Runnables(src); len(run) > 0 {
|
||||
m[alias] = run
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
params := c.Params(src)
|
||||
overloads := make([]protocol.CommandOverload, len(params))
|
||||
|
||||
aliasesIndex := uint32(math.MaxUint32)
|
||||
if len(c.Aliases()) > 0 {
|
||||
aliasesIndex = uint32(len(enumIndices))
|
||||
enumIndices[c.Name()+"Aliases"] = aliasesIndex
|
||||
enums = append(enums, commandEnum{Type: c.Name() + "Aliases", Options: c.Aliases()})
|
||||
}
|
||||
|
||||
for i, params := range params {
|
||||
for _, paramInfo := range params {
|
||||
t, enum := valueToParamType(paramInfo, src)
|
||||
suffix := paramInfo.Suffix
|
||||
|
||||
opt := byte(0)
|
||||
if suffix != "" {
|
||||
index, ok := suffixIndices[suffix]
|
||||
if !ok {
|
||||
index = uint32(len(pk.Suffixes))
|
||||
suffixIndices[suffix] = index
|
||||
pk.Suffixes = append(pk.Suffixes, suffix)
|
||||
}
|
||||
t = protocol.CommandArgSuffixed | index
|
||||
} else {
|
||||
t |= protocol.CommandArgValid
|
||||
if len(enum.Options) > 0 || enum.Type != "" {
|
||||
_, dynamic := softEnums[enum.Type]
|
||||
if !dynamic {
|
||||
index, ok := enumIndices[enum.Type]
|
||||
if !ok {
|
||||
index = uint32(len(enums))
|
||||
enumIndices[enum.Type] = index
|
||||
enums = append(enums, enum)
|
||||
}
|
||||
t |= protocol.CommandArgEnum | index
|
||||
} else {
|
||||
index, ok := dynamicEnumIndices[enum.Type]
|
||||
if !ok {
|
||||
index = uint32(len(dynamicEnums))
|
||||
dynamicEnumIndices[enum.Type] = index
|
||||
dynamicEnums = append(dynamicEnums, enum)
|
||||
}
|
||||
t |= protocol.CommandArgSoftEnum | index
|
||||
}
|
||||
}
|
||||
}
|
||||
overloads[i].Parameters = append(overloads[i].Parameters, protocol.CommandParameter{
|
||||
Name: paramInfo.Name,
|
||||
Type: t,
|
||||
Optional: paramInfo.Optional,
|
||||
Options: opt,
|
||||
})
|
||||
}
|
||||
}
|
||||
pk.Commands = append(pk.Commands, protocol.Command{
|
||||
Name: c.Name(),
|
||||
Description: c.Description(),
|
||||
AliasesOffset: aliasesIndex,
|
||||
PermissionLevel: protocol.CommandPermissionLevelAny,
|
||||
Overloads: overloads,
|
||||
})
|
||||
}
|
||||
|
||||
pk.DynamicEnums = make([]protocol.DynamicEnum, 0, len(dynamicEnums))
|
||||
for _, e := range dynamicEnums {
|
||||
pk.DynamicEnums = append(pk.DynamicEnums, protocol.DynamicEnum{Type: e.Type, Values: e.Options})
|
||||
}
|
||||
|
||||
enumValueIndices := make(map[string]uint32, len(enums)*3)
|
||||
pk.EnumValues = make([]string, 0, len(enumValueIndices))
|
||||
|
||||
pk.Enums = make([]protocol.CommandEnum, 0, len(enums))
|
||||
for _, enum := range enums {
|
||||
protoEnum := protocol.CommandEnum{Type: enum.Type}
|
||||
for _, opt := range enum.Options {
|
||||
index, ok := enumValueIndices[opt]
|
||||
if !ok {
|
||||
index = uint32(len(pk.EnumValues))
|
||||
enumValueIndices[opt] = index
|
||||
pk.EnumValues = append(pk.EnumValues, opt)
|
||||
}
|
||||
protoEnum.ValueIndices = append(protoEnum.ValueIndices, index)
|
||||
}
|
||||
pk.Enums = append(pk.Enums, protoEnum)
|
||||
}
|
||||
return pk, m
|
||||
}
|
||||
|
||||
// sendAvailableCommands sends all available commands of the server. Once sent, they will be visible in the
|
||||
// /help list and will be auto-completed.
|
||||
func (s *Session) sendAvailableCommands(co Controllable, softEnums map[string]struct{}) map[string]map[int]cmd.Runnable {
|
||||
commands := cmd.Commands()
|
||||
pk, m := BuildAvailableCommands(commands, co, softEnums)
|
||||
s.writePacket(pk)
|
||||
return m
|
||||
}
|
||||
|
||||
type commandEnum struct {
|
||||
Type string
|
||||
Options []string
|
||||
}
|
||||
|
||||
// valueToParamType finds the command argument type of the value passed and returns it, in addition to creating
|
||||
// an enum if applicable.
|
||||
func valueToParamType(i cmd.ParamInfo, source cmd.Source) (t uint32, enum commandEnum) {
|
||||
switch i.Value.(type) {
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
return protocol.CommandArgTypeInt, enum
|
||||
case float32, float64:
|
||||
return protocol.CommandArgTypeFloat, enum
|
||||
case string:
|
||||
return protocol.CommandArgTypeString, enum
|
||||
case cmd.Varargs:
|
||||
return protocol.CommandArgTypeRawText, enum
|
||||
case cmd.Target, []cmd.Target:
|
||||
return protocol.CommandArgTypeTarget, enum
|
||||
case bool:
|
||||
return 0, commandEnum{
|
||||
Type: "Boolean",
|
||||
Options: []string{"true", "false"},
|
||||
}
|
||||
case mgl64.Vec3:
|
||||
return protocol.CommandArgTypePosition, enum
|
||||
case cmd.SubCommand:
|
||||
return 0, commandEnum{
|
||||
Type: "SubCommand" + i.Name,
|
||||
Options: []string{i.Name},
|
||||
}
|
||||
}
|
||||
if enum, ok := i.Value.(cmd.Enum); ok {
|
||||
return 0, commandEnum{
|
||||
Type: enum.Type(),
|
||||
Options: enum.Options(source),
|
||||
}
|
||||
}
|
||||
return protocol.CommandArgTypeValue, enum
|
||||
}
|
||||
|
||||
// resendCommands resends all commands that a Session has access to if the map of runnable commands passed does not
|
||||
// match with the commands that the Session is currently allowed to execute.
|
||||
// True is returned if the commands were resent.
|
||||
func (s *Session) resendCommands(
|
||||
before map[string]map[int]cmd.Runnable,
|
||||
co Controllable,
|
||||
softEnums map[string]struct{},
|
||||
) (map[string]map[int]cmd.Runnable, bool) {
|
||||
commands := cmd.Commands()
|
||||
m := make(map[string]map[int]cmd.Runnable, len(commands))
|
||||
|
||||
for alias, c := range commands {
|
||||
if c.Name() == alias {
|
||||
if run := c.Runnables(co); len(run) > 0 {
|
||||
m[alias] = run
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(before) != len(m) {
|
||||
return s.sendAvailableCommands(co, softEnums), true
|
||||
}
|
||||
// First check for commands that were newly added.
|
||||
for name, r := range m {
|
||||
for k := range r {
|
||||
if _, ok := before[name][k]; !ok {
|
||||
return s.sendAvailableCommands(co, softEnums), true
|
||||
}
|
||||
}
|
||||
}
|
||||
// Then check for commands that a player could execute before, but no longer can.
|
||||
for name, r := range before {
|
||||
for k := range r {
|
||||
if _, ok := m[name][k]; !ok {
|
||||
return s.sendAvailableCommands(co, softEnums), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, false
|
||||
}
|
||||
|
||||
// enums returns a map of all enums exposed to the Session and records the values those enums currently hold.
|
||||
func (s *Session) enums(co Controllable) (map[string]cmd.Enum, map[string][]string) {
|
||||
enums, enumValues := make(map[string]cmd.Enum), make(map[string][]string)
|
||||
for alias, c := range cmd.Commands() {
|
||||
if c.Name() == alias {
|
||||
for _, params := range c.Params(co) {
|
||||
for _, paramInfo := range params {
|
||||
if enum, ok := paramInfo.Value.(cmd.Enum); ok {
|
||||
enums[enum.Type()] = enum
|
||||
enumValues[enum.Type()] = enum.Options(co)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return enums, enumValues
|
||||
}
|
||||
|
||||
// resendEnums checks the options of the enums passed against the values that were previously recorded. If they do not
|
||||
// match, and the enum is in softEnums, the enum is resent via UpdateSoftEnum. If the enum is not yet in softEnums,
|
||||
// it is added and the full AvailableCommands packet is resent.
|
||||
func (s *Session) resendEnums(
|
||||
enums map[string]cmd.Enum,
|
||||
before map[string][]string,
|
||||
softEnums map[string]struct{},
|
||||
r map[string]map[int]cmd.Runnable,
|
||||
c Controllable,
|
||||
) map[string]map[int]cmd.Runnable {
|
||||
for name, enum := range enums {
|
||||
valuesBefore := before[name]
|
||||
values := enum.Options(c)
|
||||
before[name] = values
|
||||
|
||||
changed := false
|
||||
if len(valuesBefore) != len(values) {
|
||||
changed = true
|
||||
} else {
|
||||
for k, v := range values {
|
||||
if valuesBefore[k] != v {
|
||||
changed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, dynamic := softEnums[name]; dynamic {
|
||||
s.writePacket(&packet.UpdateSoftEnum{EnumType: name, Options: values, ActionType: packet.SoftEnumActionSet})
|
||||
} else {
|
||||
softEnums[name] = struct{}{}
|
||||
r = s.sendAvailableCommands(c, softEnums)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
Reference in New Issue
Block a user