bedrocktool/utils/nbtconv/write.go

149 lines
3.9 KiB
Go

package nbtconv
import (
"bytes"
"encoding/gob"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/chunk"
"sort"
)
// WriteItem encodes an item stack into a map that can be encoded using NBT.
func WriteItem(s item.Stack, disk bool) map[string]any {
tag := make(map[string]any)
if nbt, ok := s.Item().(world.NBTer); ok {
for k, v := range nbt.EncodeNBT() {
tag[k] = v
}
}
writeAnvilCost(tag, s)
writeDamage(tag, s, disk)
writeDisplay(tag, s)
writeDragonflyData(tag, s)
writeEnchantments(tag, s)
data := make(map[string]any)
if disk {
writeItemStack(data, tag, s)
} else {
for k, v := range tag {
data[k] = v
}
}
return data
}
// WriteBlock encodes a world.Block into a map that can be encoded using NBT.
func WriteBlock(b world.Block) map[string]any {
name, properties := b.EncodeBlock()
return map[string]any{
"name": name,
"states": properties,
"version": chunk.CurrentBlockVersion,
}
}
// writeItemStack writes the name, metadata value, count and NBT of an item to a map ready for NBT encoding.
func writeItemStack(m, t map[string]any, s item.Stack) {
m["Name"], m["Damage"] = s.Item().EncodeItem()
if b, ok := s.Item().(world.Block); ok {
v := map[string]any{}
writeBlock(v, b)
m["Block"] = v
}
m["Count"] = byte(s.Count())
if len(t) > 0 {
m["tag"] = t
}
}
// writeBlock writes the name, properties and version of a block to a map ready for NBT encoding.
func writeBlock(m map[string]any, b world.Block) {
m["name"], m["states"] = b.EncodeBlock()
m["version"] = chunk.CurrentBlockVersion
}
// writeDragonflyData writes additional data associated with an item.Stack to a map for NBT encoding.
func writeDragonflyData(m map[string]any, s item.Stack) {
if v := s.Values(); len(v) != 0 {
buf := new(bytes.Buffer)
if err := gob.NewEncoder(buf).Encode(mapToSlice(v)); err != nil {
panic("error encoding item user data: " + err.Error())
}
m["dragonflyData"] = buf.Bytes()
}
}
// mapToSlice converts a map to a slice of the type mapValue and orders the slice by the keys in the map to ensure a
// deterministic order.
func mapToSlice(m map[string]any) []mapValue {
values := make([]mapValue, 0, len(m))
for k, v := range m {
values = append(values, mapValue{K: k, V: v})
}
sort.Slice(values, func(i, j int) bool {
return values[i].K < values[j].K
})
return values
}
// mapValue represents a value in a map. It is used to convert maps to a slice and order the slice before encoding to
// NBT to ensure a deterministic output.
type mapValue struct {
K string
V any
}
// writeEnchantments writes the enchantments of an item to a map for NBT encoding.
func writeEnchantments(m map[string]any, s item.Stack) {
if len(s.Enchantments()) != 0 {
var enchantments []map[string]any
for _, e := range s.Enchantments() {
if eType, ok := item.EnchantmentID(e.Type()); ok {
enchantments = append(enchantments, map[string]any{
"id": int16(eType),
"lvl": int16(e.Level()),
})
}
}
m["ench"] = enchantments
}
}
// writeDisplay writes the display name and lore of an item to a map for NBT encoding.
func writeDisplay(m map[string]any, s item.Stack) {
name, lore := s.CustomName(), s.Lore()
v := map[string]any{}
if name != "" {
v["Name"] = name
}
if len(lore) != 0 {
v["Lore"] = lore
}
if len(v) != 0 {
m["display"] = v
}
}
// writeDamage writes the damage to an item.Stack (either an int16 for disk or int32 for network) to a map for NBT
// encoding.
func writeDamage(m map[string]any, s item.Stack, disk bool) {
if v, ok := m["Damage"]; !ok || v.(int16) == 0 {
if _, ok := s.Item().(item.Durable); ok {
if disk {
m["Damage"] = int16(s.MaxDurability() - s.Durability())
} else {
m["Damage"] = int32(s.MaxDurability() - s.Durability())
}
}
}
}
// writeAnvilCost ...
func writeAnvilCost(m map[string]any, s item.Stack) {
if cost := s.AnvilCost(); cost > 0 {
m["RepairCost"] = int32(cost)
}
}