Skip to content

Commit

Permalink
packet/available_commands.go: Symmetrical encode/decode. (Very ugly!)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sandertv committed Mar 23, 2023
1 parent 96d2399 commit 3492077
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 144 deletions.
91 changes: 91 additions & 0 deletions minecraft/protocol/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,94 @@ func (x *CommandOutputMessage) Marshal(r IO) {
r.String(&x.Message)
FuncSlice(r, &x.Parameters, r.String)
}

// enumValues runs through all commands set to the packet and collects enum values and a map of indices
// indexed with the enum values.
func enumValues(commands []Command) (values []string, indices map[string]int) {
indices = make(map[string]int)

for _, command := range commands {
for _, alias := range command.Aliases {
if _, ok := indices[alias]; !ok {
indices[alias] = len(values)
values = append(values, alias)
}
}
for _, overload := range command.Overloads {
for _, parameter := range overload.Parameters {
for _, option := range parameter.Enum.Options {
if _, ok := indices[option]; !ok {
indices[option] = len(values)
values = append(values, option)
}
}
}
}
}
return
}

// suffixes runs through all commands set to the packet and collects suffixes that the parameters of the
// commands may have. It returns the suffixes and a map indexed by the suffixes.
func suffixes(commands []Command) (suffixes []string, indices map[string]int) {
indices = make(map[string]int)

for _, command := range commands {
for _, overload := range command.Overloads {
for _, parameter := range overload.Parameters {
if parameter.Suffix != "" {
if _, ok := indices[parameter.Suffix]; !ok {
indices[parameter.Suffix] = len(suffixes)
suffixes = append(suffixes, parameter.Suffix)
}
}
}
}
}
return
}

// enums runs through all commands set to the packet and collects enums that the parameters of the commands
// may have. It returns the enums and a map indexed by the enums and their offsets in the slice.
func enums(commands []Command) (enums []CommandEnum, indices map[string]int) {
indices = make(map[string]int)

for _, command := range commands {
if len(command.Aliases) > 0 {
aliasEnum := CommandEnum{Type: command.Name + "Aliases", Options: command.Aliases}
indices[command.Name+"Aliases"] = len(enums)
enums = append(enums, aliasEnum)
}
for _, overload := range command.Overloads {
for _, parameter := range overload.Parameters {
if len(parameter.Enum.Options) != 0 && !parameter.Enum.Dynamic {
if _, ok := indices[parameter.Enum.Type]; !ok {
indices[parameter.Enum.Type] = len(enums)
enums = append(enums, parameter.Enum)
}
}
}
}
}
return
}

// dynamicEnums runs through all commands set to the packet and collects dynamic enums set as parameters of
// commands. These dynamic enums may be updated over the course of the game and are written separately.
func dynamicEnums(commands []Command) (enums []CommandEnum, indices map[string]int) {
indices = make(map[string]int)

for _, command := range commands {
for _, overload := range command.Overloads {
for _, parameter := range overload.Parameters {
if parameter.Enum.Dynamic {
if _, ok := indices[parameter.Enum.Type]; !ok {
indices[parameter.Enum.Type] = len(enums)
enums = append(enums, parameter.Enum)
}
}
}
}
}
return
}
1 change: 1 addition & 0 deletions minecraft/protocol/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type IO interface {
PlayerInventoryAction(x *UseItemTransactionData)
GameRule(x *GameRule)
AbilityValue(x *any)
Commands(commands *[]Command, constraints *[]CommandEnumConstraint)

ShieldID() int32
UnknownEnumOption(value any, enum string)
Expand Down
148 changes: 4 additions & 144 deletions minecraft/protocol/packet/available_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,154 +23,14 @@ func (*AvailableCommands) ID() uint32 {

// Marshal ...
func (pk *AvailableCommands) Marshal(w *protocol.Writer) {
values, valueIndices := pk.enumValues()
suffixes, suffixIndices := pk.suffixes()
enums, enumIndices := pk.enums()
dynamicEnums, dynamicEnumIndices := pk.dynamicEnums()

ctx := protocol.AvailableCommandsContext{
EnumIndices: enumIndices,
EnumValueIndices: valueIndices,
SuffixIndices: suffixIndices,
DynamicEnumIndices: dynamicEnumIndices,
}

// Start by writing all enum values and suffixes to the buffer.
protocol.FuncSlice(w, &values, w.String)
protocol.FuncSlice(w, &suffixes, w.String)

// After that all actual enums, which point to enum values rather than directly writing strings.
protocol.FuncIOSlice(w, &enums, ctx.WriteEnum)

// Finally we write the command data which includes all usages of the commands.
protocol.FuncIOSlice(w, &pk.Commands, ctx.WriteCommandData)

// Soft enums follow, which may be changed after sending this packet.
protocol.Slice(w, &dynamicEnums)

protocol.FuncIOSlice(w, &pk.Constraints, ctx.WriteEnumConstraint)
pk.marshal(w)
}

// Unmarshal ...
func (pk *AvailableCommands) Unmarshal(r *protocol.Reader) {
var ctx protocol.AvailableCommandsContext

// First we read all the enum values and suffixes.
protocol.FuncSlice(r, &ctx.EnumValues, r.String)
protocol.FuncSlice(r, &ctx.Suffixes, r.String)

// After that we create all enums, which are composed of pointers to the enum values above.
protocol.FuncIOSlice(r, &ctx.Enums, ctx.Enum)

// We read all the commands, which will have their enums and suffixes set automatically. We don't yet set
// the dynamic enums as we haven't read them yet.
protocol.FuncIOSlice(r, &pk.Commands, ctx.CommandData)

// We first read all soft enums of the packet.
protocol.Slice(r, &ctx.DynamicEnums)

protocol.FuncIOSlice(r, &pk.Constraints, ctx.EnumConstraint)

// After we've read all soft enums, we need to match them with the values that are set in the commands
// that we read before.
for i, command := range pk.Commands {
for j, overload := range command.Overloads {
for k, param := range overload.Parameters {
if param.Type&protocol.CommandArgSoftEnum != 0 {
pk.Commands[i].Overloads[j].Parameters[k].Enum = ctx.DynamicEnums[param.Type&0xffff]
}
}
}
}
}

// enumValues runs through all commands set to the packet and collects enum values and a map of indices
// indexed with the enum values.
func (pk *AvailableCommands) enumValues() (values []string, indices map[string]int) {
indices = make(map[string]int)

for _, command := range pk.Commands {
for _, alias := range command.Aliases {
if _, ok := indices[alias]; !ok {
indices[alias] = len(values)
values = append(values, alias)
}
}
for _, overload := range command.Overloads {
for _, parameter := range overload.Parameters {
for _, option := range parameter.Enum.Options {
if _, ok := indices[option]; !ok {
indices[option] = len(values)
values = append(values, option)
}
}
}
}
}
return
pk.marshal(r)
}

// suffixes runs through all commands set to the packet and collects suffixes that the parameters of the
// commands may have. It returns the suffixes and a map indexed by the suffixes.
func (pk *AvailableCommands) suffixes() (suffixes []string, indices map[string]int) {
indices = make(map[string]int)

for _, command := range pk.Commands {
for _, overload := range command.Overloads {
for _, parameter := range overload.Parameters {
if parameter.Suffix != "" {
if _, ok := indices[parameter.Suffix]; !ok {
indices[parameter.Suffix] = len(suffixes)
suffixes = append(suffixes, parameter.Suffix)
}
}
}
}
}
return
}

// enums runs through all commands set to the packet and collects enums that the parameters of the commands
// may have. It returns the enums and a map indexed by the enums and their offsets in the slice.
func (pk *AvailableCommands) enums() (enums []protocol.CommandEnum, indices map[string]int) {
indices = make(map[string]int)

for _, command := range pk.Commands {
if len(command.Aliases) > 0 {
aliasEnum := protocol.CommandEnum{Type: command.Name + "Aliases", Options: command.Aliases}
indices[command.Name+"Aliases"] = len(enums)
enums = append(enums, aliasEnum)
}
for _, overload := range command.Overloads {
for _, parameter := range overload.Parameters {
if len(parameter.Enum.Options) != 0 && !parameter.Enum.Dynamic {
if _, ok := indices[parameter.Enum.Type]; !ok {
indices[parameter.Enum.Type] = len(enums)
enums = append(enums, parameter.Enum)
}
}
}
}
}
return
}

// dynamicEnums runs through all commands set to the packet and collects dynamic enums set as parameters of
// commands. These dynamic enums may be updated over the course of the game and are written separately.
func (pk *AvailableCommands) dynamicEnums() (enums []protocol.CommandEnum, indices map[string]int) {
indices = make(map[string]int)

for _, command := range pk.Commands {
for _, overload := range command.Overloads {
for _, parameter := range overload.Parameters {
if parameter.Enum.Dynamic {
if _, ok := indices[parameter.Enum.Type]; !ok {
indices[parameter.Enum.Type] = len(enums)
enums = append(enums, parameter.Enum)
}
}
}
}
}
return
func (pk *AvailableCommands) marshal(r protocol.IO) {
r.Commands(&pk.Commands, &pk.Constraints)
}
33 changes: 33 additions & 0 deletions minecraft/protocol/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,39 @@ func (r *Reader) AbilityValue(x *any) {
}
}

// Commands reads a Command slice and its constraints from a reader.
func (r *Reader) Commands(commands *[]Command, constraints *[]CommandEnumConstraint) {
var ctx AvailableCommandsContext

// First we read all the enum values and suffixes.
FuncSlice(r, &ctx.EnumValues, r.String)
FuncSlice(r, &ctx.Suffixes, r.String)

// After that we create all enums, which are composed of pointers to the enum values above.
FuncIOSlice(r, &ctx.Enums, ctx.Enum)

// We read all the commands, which will have their enums and suffixes set automatically. We don't yet set
// the dynamic enums as we haven't read them yet.
FuncIOSlice(r, commands, ctx.CommandData)

// We first read all soft enums of the packet.
Slice(r, &ctx.DynamicEnums)

// After we've read all soft enums, we need to match them with the values that are set in the commands
// that we read before.
for i, command := range *commands {
for j, overload := range command.Overloads {
for k, param := range overload.Parameters {
if param.Type&CommandArgSoftEnum != 0 {
(*commands)[i].Overloads[j].Parameters[k].Enum = ctx.DynamicEnums[param.Type&0xffff]
}
}
}
}

FuncIOSlice(r, constraints, ctx.EnumConstraint)
}

// LimitUint32 checks if the value passed is lower than the limit passed. If not, the Reader panics.
func (r *Reader) LimitUint32(value uint32, max uint32) {
if max == math.MaxUint32 {
Expand Down
30 changes: 30 additions & 0 deletions minecraft/protocol/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,36 @@ func (w *Writer) AbilityValue(x *any) {
}
}

// Commands writes a Command slice and its constraints to a writer.
func (w *Writer) Commands(commands *[]Command, constraints *[]CommandEnumConstraint) {
values, valueIndices := enumValues(*commands)
suffixes, suffixIndices := suffixes(*commands)
enums, enumIndices := enums(*commands)
dynamicEnums, dynamicEnumIndices := dynamicEnums(*commands)

ctx := AvailableCommandsContext{
EnumIndices: enumIndices,
EnumValueIndices: valueIndices,
SuffixIndices: suffixIndices,
DynamicEnumIndices: dynamicEnumIndices,
}

// Start by writing all enum values and suffixes to the buffer.
FuncSlice(w, &values, w.String)
FuncSlice(w, &suffixes, w.String)

// After that all actual enums, which point to enum values rather than directly writing strings.
FuncIOSlice(w, &enums, ctx.WriteEnum)

// Finally we write the command data which includes all usages of the commands.
FuncIOSlice(w, commands, ctx.WriteCommandData)

// Soft enums follow, which may be changed after sending this packet.
Slice(w, &dynamicEnums)

FuncIOSlice(w, constraints, ctx.WriteEnumConstraint)
}

// Varint64 writes an int64 as 1-10 bytes to the underlying buffer.
func (w *Writer) Varint64(x *int64) {
u := *x
Expand Down

0 comments on commit 3492077

Please sign in to comment.