372 lines
13 KiB
Go
372 lines
13 KiB
Go
package proto
|
|
|
|
import (
|
|
"math"
|
|
|
|
"github.com/akmalfairuz/legacy-version/internal/typeconf"
|
|
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
|
)
|
|
|
|
func legacyCommandPermToNew(x byte) string {
|
|
switch x {
|
|
case protocol.CommandPermissionLevelAny:
|
|
return "any"
|
|
case protocol.CommandPermissionLevelGameDirectors:
|
|
return "gamedirectors"
|
|
case protocol.CommandPermissionLevelAdmin:
|
|
return "admin"
|
|
case protocol.CommandPermissionLevelHost:
|
|
return "host"
|
|
case protocol.CommandPermissionLevelOwner:
|
|
return "owner"
|
|
case protocol.CommandPermissionLevelInternal:
|
|
return "internal"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func newCommandPermToLegacy(x string) byte {
|
|
switch x {
|
|
case "any":
|
|
return protocol.CommandPermissionLevelAny
|
|
case "gamedirectors":
|
|
return protocol.CommandPermissionLevelGameDirectors
|
|
case "admin":
|
|
return protocol.CommandPermissionLevelAdmin
|
|
case "host":
|
|
return protocol.CommandPermissionLevelHost
|
|
case "owner":
|
|
return protocol.CommandPermissionLevelOwner
|
|
case "internal":
|
|
return protocol.CommandPermissionLevelInternal
|
|
default:
|
|
return protocol.CommandPermissionLevelAny
|
|
}
|
|
}
|
|
|
|
// Command holds the data that a command requires to be shown to a player client-side. The command is shown in
|
|
// the /help command and auto-completed using this data.
|
|
type Command struct {
|
|
// Name is the name of the command. The command may be executed using this name, and will be shown in the
|
|
// /help list with it. It currently seems that the client crashes if the Name contains uppercase letters.
|
|
Name string
|
|
// Description is the description of the command. It is shown in the /help list and when starting to write
|
|
// a command.
|
|
Description string
|
|
// Flags is a combination of flags not currently known. Leaving the Flags field empty appears to work.
|
|
Flags uint16
|
|
// PermissionLevel is the command permission level that the player required to execute this command. The
|
|
// field no longer seems to serve a purpose, as the client does not handle the execution of commands
|
|
// anymore: The permissions should be checked server-side.
|
|
PermissionLevel string
|
|
// AliasesOffset is the offset to a CommandEnum that holds the values that
|
|
// should be used as aliases for this command.
|
|
AliasesOffset uint32
|
|
// ChainedSubcommandOffsets is a slice of offsets that all point to a different ChainedSubcommand from the
|
|
// ChainedSubcommands slice in the AvailableCommands packet.
|
|
ChainedSubcommandOffsets []uint32
|
|
// Overloads is a list of command overloads that specify the ways in which a command may be executed. The
|
|
// overloads may be completely different.
|
|
Overloads []protocol.CommandOverload
|
|
}
|
|
|
|
func (c *Command) Marshal(r protocol.IO) {
|
|
r.String(&c.Name)
|
|
r.String(&c.Description)
|
|
r.Uint16(&c.Flags)
|
|
|
|
if IsProtoGTE(r, ID898) {
|
|
r.String(&c.PermissionLevel)
|
|
} else {
|
|
permLevel := byte(0)
|
|
r.Uint8(&permLevel)
|
|
c.PermissionLevel = "any"
|
|
}
|
|
r.Uint32(&c.AliasesOffset)
|
|
if IsProtoGTE(r, ID898) {
|
|
protocol.FuncSlice(r, &c.ChainedSubcommandOffsets, r.Uint32)
|
|
} else {
|
|
offsets := typeconf.SliceIntToSliceInt[uint32, uint16](c.ChainedSubcommandOffsets)
|
|
protocol.FuncSlice(r, &offsets, r.Uint16)
|
|
c.ChainedSubcommandOffsets = typeconf.SliceIntToSliceInt[uint16, uint32](offsets)
|
|
}
|
|
protocol.Slice(r, &c.Overloads)
|
|
}
|
|
|
|
func (c *Command) ToLatest() protocol.Command {
|
|
return protocol.Command{
|
|
Name: c.Name,
|
|
Description: c.Description,
|
|
Flags: c.Flags,
|
|
PermissionLevel: newCommandPermToLegacy(c.PermissionLevel),
|
|
AliasesOffset: c.AliasesOffset,
|
|
ChainedSubcommandOffsets: c.ChainedSubcommandOffsets,
|
|
Overloads: c.Overloads,
|
|
}
|
|
}
|
|
|
|
func (c *Command) FromLatest(latest protocol.Command) Command {
|
|
c.Name = latest.Name
|
|
c.Description = latest.Description
|
|
c.Flags = latest.Flags
|
|
c.PermissionLevel = legacyCommandPermToNew(latest.PermissionLevel)
|
|
c.AliasesOffset = latest.AliasesOffset
|
|
c.ChainedSubcommandOffsets = latest.ChainedSubcommandOffsets
|
|
c.Overloads = latest.Overloads
|
|
return *c
|
|
}
|
|
|
|
// CommandEnum represents an enum in a command usage. The enum typically has a type and a set of options that
|
|
// are valid. A value that is not one of the options results in a failure during execution.
|
|
type CommandEnum struct {
|
|
// Type is the type of the command enum. The type will show up in the command usage as the type of the
|
|
// argument if it has a certain amount of arguments, or when Options is set to true in the
|
|
// command holding the enum.
|
|
Type string
|
|
// ValueIndices holds a list of indices that point to the EnumValues slice in the
|
|
// AvailableCommandsPacket. These represent the options of the enum.
|
|
ValueIndices []uint32
|
|
}
|
|
|
|
// CommandEnumContext holds context required for encoding command enums.
|
|
type CommandEnumContext struct {
|
|
EnumValues []string
|
|
}
|
|
|
|
// Marshal encodes/decodes a CommandEnum.
|
|
func (ctx CommandEnumContext) Marshal(r protocol.IO, x *CommandEnum) {
|
|
r.String(&x.Type)
|
|
if IsProtoGTE(r, ID898) {
|
|
protocol.FuncSlice(r, &x.ValueIndices, r.Uint32)
|
|
} else {
|
|
protocol.FuncIOSlice(r, &x.ValueIndices, ctx.enumOption)
|
|
}
|
|
}
|
|
|
|
// enumOption writes/reads a command enum option as a byte/uint16/uint32,
|
|
// depending on the amount of enum values.
|
|
func (ctx CommandEnumContext) enumOption(r protocol.IO, opt *uint32) {
|
|
n := len(ctx.EnumValues)
|
|
switch {
|
|
case n <= math.MaxUint8:
|
|
val := byte(*opt)
|
|
r.Uint8(&val)
|
|
*opt = uint32(val)
|
|
case n <= math.MaxUint16:
|
|
val := uint16(*opt)
|
|
r.Uint16(&val)
|
|
*opt = uint32(val)
|
|
default:
|
|
r.Uint32(opt)
|
|
}
|
|
}
|
|
|
|
// ToLatest ...
|
|
func (ce *CommandEnum) ToLatest() protocol.CommandEnum {
|
|
return protocol.CommandEnum{
|
|
Type: ce.Type,
|
|
ValueIndices: ce.ValueIndices,
|
|
}
|
|
}
|
|
|
|
// FromLatest ...
|
|
func (ce *CommandEnum) FromLatest(latest protocol.CommandEnum) CommandEnum {
|
|
ce.Type = latest.Type
|
|
ce.ValueIndices = latest.ValueIndices
|
|
return *ce
|
|
}
|
|
|
|
// ChainedSubcommand represents a subcommand that can have chained commands, such as /execute which allows you to run
|
|
// another command as another entity or at a different position etc.
|
|
type ChainedSubcommand struct {
|
|
// Name is the name of the chained subcommand and shows up in the list as a regular subcommand enum.
|
|
Name string
|
|
// Values contains the index and parameter type of the chained subcommand.
|
|
Values []ChainedSubcommandValue
|
|
}
|
|
|
|
func (x *ChainedSubcommand) Marshal(r protocol.IO) {
|
|
r.String(&x.Name)
|
|
protocol.Slice(r, &x.Values)
|
|
}
|
|
|
|
// ToLatest ...
|
|
func (x *ChainedSubcommand) ToLatest() protocol.ChainedSubcommand {
|
|
values := make([]protocol.ChainedSubcommandValue, len(x.Values))
|
|
for i, v := range x.Values {
|
|
values[i] = v.ToLatest()
|
|
}
|
|
return protocol.ChainedSubcommand{
|
|
Name: x.Name,
|
|
Values: values,
|
|
}
|
|
}
|
|
|
|
// FromLatest ...
|
|
func (x *ChainedSubcommand) FromLatest(latest protocol.ChainedSubcommand) ChainedSubcommand {
|
|
x.Name = latest.Name
|
|
x.Values = make([]ChainedSubcommandValue, len(latest.Values))
|
|
for i, v := range latest.Values {
|
|
x.Values[i] = (&ChainedSubcommandValue{}).FromLatest(v)
|
|
}
|
|
return *x
|
|
}
|
|
|
|
// ChainedSubcommandValue represents the value for a chained subcommand argument.
|
|
type ChainedSubcommandValue struct {
|
|
// Index is the index of the argument in the ChainedSubcommandValues slice from the AvailableCommands packet. This is
|
|
// then used to set the type specified by the Value field below.
|
|
Index uint32
|
|
// Value is a combination of the flags above and specified the type of argument. Unlike regular parameter types,
|
|
// this should NOT contain any of the special flags (valid, enum, suffixed or soft enum) but only the basic types.
|
|
Value uint32
|
|
}
|
|
|
|
func (x *ChainedSubcommandValue) Marshal(r protocol.IO) {
|
|
if IsProtoGTE(r, ID898) {
|
|
r.Varuint32(&x.Index)
|
|
r.Varuint32(&x.Value)
|
|
} else {
|
|
v := uint16(x.Index)
|
|
r.Uint16(&v)
|
|
x.Index = uint32(v)
|
|
vv := uint16(x.Value)
|
|
r.Uint16(&vv)
|
|
x.Value = uint32(vv)
|
|
}
|
|
}
|
|
|
|
// ToLatest ...
|
|
func (x *ChainedSubcommandValue) ToLatest() protocol.ChainedSubcommandValue {
|
|
return protocol.ChainedSubcommandValue{
|
|
Index: x.Index,
|
|
Value: x.Value,
|
|
}
|
|
}
|
|
|
|
// FromLatest ...
|
|
func (x *ChainedSubcommandValue) FromLatest(latest protocol.ChainedSubcommandValue) ChainedSubcommandValue {
|
|
x.Index = latest.Index
|
|
x.Value = latest.Value
|
|
return *x
|
|
}
|
|
|
|
var (
|
|
commandOrigins = []string{
|
|
"player", // protocol.CommandOriginPlayer
|
|
"commandblock", // protocol.CommandOriginBlock
|
|
"minecartcommandblock", // protocol.CommandOriginMinecartBlock
|
|
"devconsole", // protocol.CommandOriginDevConsole
|
|
"test", // protocol.CommandOriginTest
|
|
"automationplayer", // protocol.CommandOriginAutomationPlayer
|
|
"clientautomation", // protocol.CommandOriginClientAutomation
|
|
"dedicatedserver", // protocol.CommandOriginDedicatedServer
|
|
"entity", // protocol.CommandOriginEntity
|
|
"virtual", // protocol.CommandOriginVirtual
|
|
"gameargument", // protocol.CommandOriginGameArgument
|
|
"entityserver", // protocol.CommandOriginEntityServer
|
|
"precompiled", // protocol.CommandOriginPrecompiled
|
|
"gamedirectorentityserver", // protocol.CommandOriginGameDirectorEntityServer
|
|
"scripting", // protocol.CommandOriginScript
|
|
"executecontext", // protocol.CommandOriginExecutor
|
|
}
|
|
originToLegacyMap = map[string]uint32{
|
|
"player": protocol.CommandOriginPlayer,
|
|
"commandblock": protocol.CommandOriginBlock,
|
|
"minecartcommandblock": protocol.CommandOriginMinecartBlock,
|
|
"devconsole": protocol.CommandOriginDevConsole,
|
|
"test": protocol.CommandOriginTest,
|
|
"automationplayer": protocol.CommandOriginAutomationPlayer,
|
|
"clientautomation": protocol.CommandOriginClientAutomation,
|
|
"dedicatedserver": protocol.CommandOriginDedicatedServer,
|
|
"entity": protocol.CommandOriginEntity,
|
|
"virtual": protocol.CommandOriginVirtual,
|
|
"gameargument": protocol.CommandOriginGameArgument,
|
|
"entityserver": protocol.CommandOriginEntityServer,
|
|
"precompiled": protocol.CommandOriginPrecompiled,
|
|
"gamedirectorentityserver": protocol.CommandOriginGameDirectorEntityServer,
|
|
"scripting": protocol.CommandOriginScript,
|
|
"executecontext": protocol.CommandOriginExecutor,
|
|
}
|
|
)
|
|
|
|
func commandOriginFromLegacy(legacy uint32) string {
|
|
if int(legacy) < len(commandOrigins) {
|
|
return commandOrigins[legacy]
|
|
}
|
|
return "player" // default to protocol.CommandOriginPlayer
|
|
}
|
|
|
|
func commandOriginToLegacy(origin string) uint32 {
|
|
legacy, ok := originToLegacyMap[origin]
|
|
if ok {
|
|
return legacy
|
|
}
|
|
return protocol.CommandOriginPlayer
|
|
}
|
|
|
|
// CommandOriginData reads/writes a CommandOrigin x using IO r.
|
|
func CommandOriginData(r protocol.IO, x *protocol.CommandOrigin) {
|
|
originV898 := commandOriginFromLegacy(x.Origin)
|
|
if IsProtoGTE(r, ID898) {
|
|
r.String(&originV898)
|
|
x.Origin = commandOriginToLegacy(originV898)
|
|
} else {
|
|
r.Varuint32(&x.Origin)
|
|
}
|
|
r.UUID(&x.UUID)
|
|
r.String(&x.RequestID)
|
|
if IsProtoGTE(r, ID898) {
|
|
r.Int64(&x.PlayerUniqueID)
|
|
} else if x.Origin == protocol.CommandOriginDevConsole || x.Origin == protocol.CommandOriginTest {
|
|
r.Varint64(&x.PlayerUniqueID)
|
|
}
|
|
}
|
|
|
|
// CommandOutputMessage represents a message sent by a command that holds the output of one of the commands
|
|
// executed.
|
|
type CommandOutputMessage struct {
|
|
// Success indicates if the output message was one of a successful command execution. If set to true, the
|
|
// output message is by default coloured white, whereas if set to false, the message is by default
|
|
// coloured red.
|
|
Success bool
|
|
// Message is the message that is sent to the client in the chat window. It may either be simply a
|
|
// message or a translated built-in string like 'commands.tp.success.coordinates', combined with specific
|
|
// parameters below.
|
|
Message string
|
|
// Parameters is a list of parameters that serve to supply the message sent with additional information,
|
|
// such as the position that a player was teleported to or the effect that was applied to an entity.
|
|
// These parameters only apply for the Minecraft built-in command output.
|
|
Parameters []string
|
|
}
|
|
|
|
// Marshal encodes/decodes a CommandOutputMessage.
|
|
func (x *CommandOutputMessage) Marshal(r protocol.IO) {
|
|
if IsProtoLT(r, ID898) {
|
|
r.Bool(&x.Success)
|
|
}
|
|
r.String(&x.Message)
|
|
if IsProtoGTE(r, ID898) {
|
|
r.Bool(&x.Success)
|
|
}
|
|
protocol.FuncSlice(r, &x.Parameters, r.String)
|
|
}
|
|
|
|
// ToLatest ...
|
|
func (x *CommandOutputMessage) ToLatest() protocol.CommandOutputMessage {
|
|
return protocol.CommandOutputMessage{
|
|
Success: x.Success,
|
|
Message: x.Message,
|
|
Parameters: x.Parameters,
|
|
}
|
|
}
|
|
|
|
// FromLatest ...
|
|
func (x *CommandOutputMessage) FromLatest(latest protocol.CommandOutputMessage) CommandOutputMessage {
|
|
x.Success = latest.Success
|
|
x.Message = latest.Message
|
|
x.Parameters = latest.Parameters
|
|
return *x
|
|
}
|