github.com/df-mc/dragonfly@v0.9.13/server/internal/nbtconv/write.go (about) 1 package nbtconv 2 3 import ( 4 "bytes" 5 "encoding/gob" 6 "github.com/df-mc/dragonfly/server/item" 7 "github.com/df-mc/dragonfly/server/world" 8 "github.com/df-mc/dragonfly/server/world/chunk" 9 "sort" 10 ) 11 12 // WriteItem encodes an item stack into a map that can be encoded using NBT. 13 func WriteItem(s item.Stack, disk bool) map[string]any { 14 tag := make(map[string]any) 15 if nbt, ok := s.Item().(world.NBTer); ok { 16 for k, v := range nbt.EncodeNBT() { 17 tag[k] = v 18 } 19 } 20 writeAnvilCost(tag, s) 21 writeDamage(tag, s, disk) 22 writeDisplay(tag, s) 23 writeDragonflyData(tag, s) 24 writeEnchantments(tag, s) 25 26 data := make(map[string]any) 27 if disk { 28 writeItemStack(data, tag, s) 29 } else { 30 for k, v := range tag { 31 data[k] = v 32 } 33 } 34 return data 35 } 36 37 // WriteBlock encodes a world.Block into a map that can be encoded using NBT. 38 func WriteBlock(b world.Block) map[string]any { 39 name, properties := b.EncodeBlock() 40 return map[string]any{ 41 "name": name, 42 "states": properties, 43 "version": chunk.CurrentBlockVersion, 44 } 45 } 46 47 // writeItemStack writes the name, metadata value, count and NBT of an item to a map ready for NBT encoding. 48 func writeItemStack(m, t map[string]any, s item.Stack) { 49 m["Name"], m["Damage"] = s.Item().EncodeItem() 50 if b, ok := s.Item().(world.Block); ok { 51 v := map[string]any{} 52 writeBlock(v, b) 53 m["Block"] = v 54 } 55 m["Count"] = byte(s.Count()) 56 if len(t) > 0 { 57 m["tag"] = t 58 } 59 } 60 61 // writeBlock writes the name, properties and version of a block to a map ready for NBT encoding. 62 func writeBlock(m map[string]any, b world.Block) { 63 m["name"], m["states"] = b.EncodeBlock() 64 m["version"] = chunk.CurrentBlockVersion 65 } 66 67 // writeDragonflyData writes additional data associated with an item.Stack to a map for NBT encoding. 68 func writeDragonflyData(m map[string]any, s item.Stack) { 69 if v := s.Values(); len(v) != 0 { 70 buf := new(bytes.Buffer) 71 if err := gob.NewEncoder(buf).Encode(mapToSlice(v)); err != nil { 72 panic("error encoding item user data: " + err.Error()) 73 } 74 m["dragonflyData"] = buf.Bytes() 75 } 76 } 77 78 // mapToSlice converts a map to a slice of the type mapValue and orders the slice by the keys in the map to ensure a 79 // deterministic order. 80 func mapToSlice(m map[string]any) []mapValue { 81 values := make([]mapValue, 0, len(m)) 82 for k, v := range m { 83 values = append(values, mapValue{K: k, V: v}) 84 } 85 sort.Slice(values, func(i, j int) bool { 86 return values[i].K < values[j].K 87 }) 88 return values 89 } 90 91 // mapValue represents a value in a map. It is used to convert maps to a slice and order the slice before encoding to 92 // NBT to ensure a deterministic output. 93 type mapValue struct { 94 K string 95 V any 96 } 97 98 // writeEnchantments writes the enchantments of an item to a map for NBT encoding. 99 func writeEnchantments(m map[string]any, s item.Stack) { 100 if len(s.Enchantments()) != 0 { 101 var enchantments []map[string]any 102 for _, e := range s.Enchantments() { 103 if eType, ok := item.EnchantmentID(e.Type()); ok { 104 enchantments = append(enchantments, map[string]any{ 105 "id": int16(eType), 106 "lvl": int16(e.Level()), 107 }) 108 } 109 } 110 m["ench"] = enchantments 111 } 112 } 113 114 // writeDisplay writes the display name and lore of an item to a map for NBT encoding. 115 func writeDisplay(m map[string]any, s item.Stack) { 116 name, lore := s.CustomName(), s.Lore() 117 v := map[string]any{} 118 if name != "" { 119 v["Name"] = name 120 } 121 if len(lore) != 0 { 122 v["Lore"] = lore 123 } 124 if len(v) != 0 { 125 m["display"] = v 126 } 127 } 128 129 // writeDamage writes the damage to an item.Stack (either an int16 for disk or int32 for network) to a map for NBT 130 // encoding. 131 func writeDamage(m map[string]any, s item.Stack, disk bool) { 132 if v, ok := m["Damage"]; !ok || v.(int16) == 0 { 133 if _, ok := s.Item().(item.Durable); ok { 134 if disk { 135 m["Damage"] = int16(s.MaxDurability() - s.Durability()) 136 } else { 137 m["Damage"] = int32(s.MaxDurability() - s.Durability()) 138 } 139 } 140 } 141 } 142 143 // writeAnvilCost ... 144 func writeAnvilCost(m map[string]any, s item.Stack) { 145 if cost := s.AnvilCost(); cost > 0 { 146 m["RepairCost"] = int32(cost) 147 } 148 }