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,24 @@
|
||||
package nbtconv
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// Int32FromRGBA converts a color.RGBA into an int32. These int32s are present in, for example, signs.
|
||||
func Int32FromRGBA(x color.RGBA) int32 {
|
||||
if x.R == 0 && x.G == 0 && x.B == 0 {
|
||||
// Default to black colour. The default (0x000000) is a transparent colour. Text with this colour will not show
|
||||
// up on the sign.
|
||||
return int32(-0x1000000)
|
||||
}
|
||||
return int32(binary.BigEndian.Uint32([]byte{x.A, x.R, x.G, x.B}))
|
||||
}
|
||||
|
||||
// RGBAFromInt32 converts an int32 into a color.RGBA. These int32s are present in, for example, signs.
|
||||
func RGBAFromInt32(x int32) color.RGBA {
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(x))
|
||||
|
||||
return color.RGBA{A: b[0], R: b[1], G: b[2], B: b[3]}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package nbtconv
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/item/inventory"
|
||||
)
|
||||
|
||||
// InvFromNBT decodes the data of an NBT slice into the inventory passed.
|
||||
func InvFromNBT(inv *inventory.Inventory, items []any) {
|
||||
for _, itemData := range items {
|
||||
data, _ := itemData.(map[string]any)
|
||||
it := Item(data, nil)
|
||||
if it.Empty() {
|
||||
continue
|
||||
}
|
||||
_ = inv.SetItem(int(Uint8(data, "Slot")), it)
|
||||
}
|
||||
}
|
||||
|
||||
// InvToNBT encodes an inventory to a data slice which may be encoded as NBT.
|
||||
func InvToNBT(inv *inventory.Inventory) []map[string]any {
|
||||
var items []map[string]any
|
||||
for index, i := range inv.Slots() {
|
||||
if i.Empty() {
|
||||
continue
|
||||
}
|
||||
data := WriteItem(i, true)
|
||||
data["Slot"] = byte(index)
|
||||
items = append(items, data)
|
||||
}
|
||||
return items
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package nbtconv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"time"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// Bool reads a uint8 value from a map at key k and returns true if it equals 1.
|
||||
func Bool(m map[string]any, k string) bool {
|
||||
return Uint8(m, k) == 1
|
||||
}
|
||||
|
||||
// Uint8 reads a uint8 value from a map at key k.
|
||||
func Uint8(m map[string]any, k string) uint8 {
|
||||
v, _ := m[k].(uint8)
|
||||
return v
|
||||
}
|
||||
|
||||
// String reads a string value from a map at key k.
|
||||
func String(m map[string]any, k string) string {
|
||||
v, _ := m[k].(string)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int16 reads an int16 value from a map at key k.
|
||||
func Int16(m map[string]any, k string) int16 {
|
||||
v, _ := m[k].(int16)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int32 reads an int32 value from a map at key k.
|
||||
func Int32(m map[string]any, k string) int32 {
|
||||
v, _ := m[k].(int32)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int64 reads an int16 value from a map at key k.
|
||||
func Int64(m map[string]any, k string) int64 {
|
||||
v, _ := m[k].(int64)
|
||||
return v
|
||||
}
|
||||
|
||||
// TickDuration reads a uint8/int16/in32 value from a map at key k and converts
|
||||
// it from ticks to a time.Duration.
|
||||
func TickDuration[T constraints.Integer](m map[string]any, k string) time.Duration {
|
||||
var v time.Duration
|
||||
switch any(*new(T)).(type) {
|
||||
case uint8:
|
||||
v = time.Duration(Uint8(m, k))
|
||||
case int16:
|
||||
v = time.Duration(Int16(m, k))
|
||||
case int32:
|
||||
v = time.Duration(Int32(m, k))
|
||||
default:
|
||||
panic("invalid tick duration value type")
|
||||
}
|
||||
return v * time.Millisecond * 50
|
||||
}
|
||||
|
||||
// Float32 reads a float32 value from a map at key k.
|
||||
func Float32(m map[string]any, k string) float32 {
|
||||
v, _ := m[k].(float32)
|
||||
return v
|
||||
}
|
||||
|
||||
// Rotation reads a cube.Rotation from the map passed.
|
||||
func Rotation(m map[string]any) cube.Rotation {
|
||||
return cube.Rotation{float64(Float32(m, "Yaw")), float64(Float32(m, "Pitch"))}
|
||||
}
|
||||
|
||||
// Float64 reads a float64 value from a map at key k.
|
||||
func Float64(m map[string]any, k string) float64 {
|
||||
v, _ := m[k].(float64)
|
||||
return v
|
||||
}
|
||||
|
||||
// Slice reads a []any value from a map at key k.
|
||||
func Slice(m map[string]any, k string) []any {
|
||||
v, _ := m[k].([]any)
|
||||
return v
|
||||
}
|
||||
|
||||
// Vec3 converts x, y and z values in an NBT map to an mgl64.Vec3.
|
||||
func Vec3(x map[string]any, k string) mgl64.Vec3 {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
var v mgl64.Vec3
|
||||
for index, f := range i {
|
||||
f32, _ := f.(float32)
|
||||
v[index] = float64(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]float32); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
return mgl64.Vec3{float64(i[0]), float64(i[1]), float64(i[2])}
|
||||
}
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
|
||||
// Vec3ToFloat32Slice converts an mgl64.Vec3 to a []float32 with 3 elements.
|
||||
func Vec3ToFloat32Slice(x mgl64.Vec3) []float32 {
|
||||
return []float32{float32(x[0]), float32(x[1]), float32(x[2])}
|
||||
}
|
||||
|
||||
// Pos converts x, y and z values in an NBT map to a cube.Pos.
|
||||
func Pos(x map[string]any, k string) cube.Pos {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
var v cube.Pos
|
||||
for index, f := range i {
|
||||
f32, _ := f.(int32)
|
||||
v[index] = int(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]int32); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
return cube.Pos{int(i[0]), int(i[1]), int(i[2])}
|
||||
}
|
||||
return cube.Pos{}
|
||||
}
|
||||
|
||||
// PosToInt32Slice converts a cube.Pos to a []int32 with 3 elements.
|
||||
func PosToInt32Slice(x cube.Pos) []int32 {
|
||||
return []int32{int32(x[0]), int32(x[1]), int32(x[2])}
|
||||
}
|
||||
|
||||
// MapItem converts an item's name, count, damage (and properties when it is a block) in a map obtained by decoding NBT
|
||||
// to a world.Item.
|
||||
func MapItem(x map[string]any, k string) item.Stack {
|
||||
if m, ok := x[k].(map[string]any); ok {
|
||||
return Item(m, nil)
|
||||
}
|
||||
return item.Stack{}
|
||||
}
|
||||
|
||||
// Item decodes the data of an item into an item stack.
|
||||
func Item(data map[string]any, s *item.Stack) item.Stack {
|
||||
disk, tag := s == nil, data
|
||||
if disk {
|
||||
t, ok := data["tag"].(map[string]any)
|
||||
if !ok {
|
||||
t = map[string]any{}
|
||||
}
|
||||
tag = t
|
||||
|
||||
a := readItemStack(data, tag)
|
||||
s = &a
|
||||
}
|
||||
|
||||
readAnvilCost(tag, s)
|
||||
readDamage(tag, s, disk)
|
||||
readDisplay(tag, s)
|
||||
readDragonflyData(tag, s)
|
||||
readEnchantments(tag, s)
|
||||
readUnbreakable(tag, s)
|
||||
return *s
|
||||
}
|
||||
|
||||
// Block decodes the data of a block into a world.Block.
|
||||
func Block(m map[string]any, k string) world.Block {
|
||||
if mk, ok := m[k].(map[string]any); ok {
|
||||
name, _ := mk["name"].(string)
|
||||
properties, _ := mk["states"].(map[string]any)
|
||||
b, _ := world.BlockByName(name, properties)
|
||||
return b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readItemStack reads an item.Stack from the NBT in the map passed.
|
||||
func readItemStack(m, t map[string]any) item.Stack {
|
||||
var it world.Item
|
||||
if blockItem, ok := Block(m, "Block").(world.Item); ok {
|
||||
it = blockItem
|
||||
}
|
||||
if v, ok := world.ItemByName(String(m, "Name"), Int16(m, "Damage")); ok {
|
||||
it = v
|
||||
}
|
||||
if it == nil {
|
||||
return item.Stack{}
|
||||
}
|
||||
if n, ok := it.(world.NBTer); ok {
|
||||
it = n.DecodeNBT(t).(world.Item)
|
||||
}
|
||||
return item.NewStack(it, int(Uint8(m, "Count")))
|
||||
}
|
||||
|
||||
// readDamage reads the damage value stored in the NBT with the Damage tag and saves it to the item.Stack passed.
|
||||
func readDamage(m map[string]any, s *item.Stack, disk bool) {
|
||||
if disk {
|
||||
*s = s.Damage(int(Int16(m, "Damage")))
|
||||
return
|
||||
}
|
||||
*s = s.Damage(int(Int32(m, "Damage")))
|
||||
}
|
||||
|
||||
// readAnvilCost ...
|
||||
func readAnvilCost(m map[string]any, s *item.Stack) {
|
||||
*s = s.WithAnvilCost(int(Int32(m, "RepairCost")))
|
||||
}
|
||||
|
||||
// readEnchantments reads the enchantments stored in the ench tag of the NBT passed and stores it into an item.Stack.
|
||||
func readEnchantments(m map[string]any, s *item.Stack) {
|
||||
enchantments, ok := m["ench"].([]map[string]any)
|
||||
if !ok {
|
||||
for _, e := range Slice(m, "ench") {
|
||||
if v, ok := e.(map[string]any); ok {
|
||||
enchantments = append(enchantments, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ench := range enchantments {
|
||||
if t, ok := item.EnchantmentByID(int(Int16(ench, "id"))); ok {
|
||||
*s = s.WithForcedEnchantments(item.NewEnchantment(t, int(Int16(ench, "lvl"))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readDisplay reads the display data present in the display field in the NBT. It includes a custom name of the item
|
||||
// and the lore.
|
||||
func readDisplay(m map[string]any, s *item.Stack) {
|
||||
if display, ok := m["display"].(map[string]any); ok {
|
||||
if name, ok := display["Name"].(string); ok {
|
||||
// Only add the custom name if actually set.
|
||||
*s = s.WithCustomName(name)
|
||||
}
|
||||
if lore, ok := display["Lore"].([]string); ok {
|
||||
*s = s.WithLore(lore...)
|
||||
} else if lore, ok := display["Lore"].([]any); ok {
|
||||
loreLines := make([]string, 0, len(lore))
|
||||
for _, l := range lore {
|
||||
loreLines = append(loreLines, l.(string))
|
||||
}
|
||||
*s = s.WithLore(loreLines...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readDragonflyData reads data written to the dragonflyData field in the NBT of an item and adds it to the item.Stack
|
||||
// passed.
|
||||
func readDragonflyData(m map[string]any, s *item.Stack) {
|
||||
if customData, ok := m["dragonflyData"]; ok {
|
||||
d, ok := customData.([]byte)
|
||||
if !ok {
|
||||
if itf, ok := customData.([]any); ok {
|
||||
for _, v := range itf {
|
||||
b, _ := v.(byte)
|
||||
d = append(d, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
var values []mapValue
|
||||
if err := gob.NewDecoder(bytes.NewBuffer(d)).Decode(&values); err != nil {
|
||||
panic("error decoding item user data: " + err.Error())
|
||||
}
|
||||
for _, val := range values {
|
||||
*s = s.WithValue(val.K, val.V)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readUnbreakable reads the unbreakable value stored in the NBT with the Unbreakable tag and saves it to the item.Stack
|
||||
// passed.
|
||||
func readUnbreakable(m map[string]any, s *item.Stack) {
|
||||
if Bool(m, "Unbreakable") {
|
||||
*s = s.AsUnbreakable()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
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 s.Empty() {
|
||||
return tag
|
||||
}
|
||||
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)
|
||||
writeUnbreakable(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)
|
||||
}
|
||||
}
|
||||
|
||||
// writeUnbreakable writes the unbreakable tag to an item stack if it is unbreakable.
|
||||
func writeUnbreakable(m map[string]any, s item.Stack) {
|
||||
if s.Unbreakable() {
|
||||
m["Unbreakable"] = byte(1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user