mirror of
https://github.com/CosmicStar98/bedrocktool.git
synced 2024-06-09 13:49:45 +00:00
add experimental block inventory
This commit is contained in:
parent
5e092f6980
commit
12f6a11683
|
@ -5,7 +5,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
|
||||||
"image"
|
"image"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"image/png"
|
"image/png"
|
||||||
|
@ -16,8 +15,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bedrock-tool/bedrocktool/utils"
|
"github.com/bedrock-tool/bedrocktool/utils"
|
||||||
|
"github.com/bedrock-tool/bedrocktool/utils/nbtconv"
|
||||||
|
|
||||||
|
"github.com/df-mc/dragonfly/server/block"
|
||||||
"github.com/df-mc/dragonfly/server/block/cube"
|
"github.com/df-mc/dragonfly/server/block/cube"
|
||||||
|
"github.com/df-mc/dragonfly/server/item"
|
||||||
|
"github.com/df-mc/dragonfly/server/item/inventory"
|
||||||
"github.com/df-mc/dragonfly/server/world"
|
"github.com/df-mc/dragonfly/server/world"
|
||||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||||
"github.com/df-mc/dragonfly/server/world/mcdb"
|
"github.com/df-mc/dragonfly/server/world/mcdb"
|
||||||
|
@ -28,8 +31,7 @@ import (
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
//_ "github.com/df-mc/dragonfly/server/block" // to load blocks
|
||||||
_ "github.com/df-mc/dragonfly/server/block" // to load blocks
|
|
||||||
//_ "net/http/pprof"
|
//_ "net/http/pprof"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,23 +42,31 @@ type TPlayerPos struct {
|
||||||
HeadYaw float32
|
HeadYaw float32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type itemContainer struct {
|
||||||
|
OpenPacket *packet.ContainerOpen
|
||||||
|
Content *packet.InventoryContent
|
||||||
|
}
|
||||||
|
|
||||||
// the state used for drawing and saving
|
// the state used for drawing and saving
|
||||||
|
|
||||||
type WorldState struct {
|
type WorldState struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
ispre118 bool
|
ispre118 bool
|
||||||
voidgen bool
|
voidgen bool
|
||||||
chunks map[protocol.ChunkPos]*chunk.Chunk
|
chunks map[protocol.ChunkPos]*chunk.Chunk
|
||||||
entities map[int64]world.SaveableEntity
|
blockNBT map[protocol.SubChunkPos][]map[string]any
|
||||||
blockNBT map[protocol.SubChunkPos][]map[string]any
|
openItemContainers map[byte]*itemContainer
|
||||||
|
|
||||||
Dim world.Dimension
|
Dim world.Dimension
|
||||||
WorldName string
|
WorldName string
|
||||||
ServerName string
|
ServerName string
|
||||||
worldCounter int
|
worldCounter int
|
||||||
withPacks bool
|
|
||||||
saveImage bool
|
|
||||||
packs map[string]*resource.Pack
|
packs map[string]*resource.Pack
|
||||||
|
|
||||||
|
withPacks bool
|
||||||
|
saveImage bool
|
||||||
|
experimentInventory bool
|
||||||
|
|
||||||
PlayerPos TPlayerPos
|
PlayerPos TPlayerPos
|
||||||
proxy *utils.ProxyContext
|
proxy *utils.ProxyContext
|
||||||
|
|
||||||
|
@ -66,12 +76,12 @@ type WorldState struct {
|
||||||
|
|
||||||
func NewWorldState() *WorldState {
|
func NewWorldState() *WorldState {
|
||||||
w := &WorldState{
|
w := &WorldState{
|
||||||
chunks: make(map[protocol.ChunkPos]*chunk.Chunk),
|
chunks: make(map[protocol.ChunkPos]*chunk.Chunk),
|
||||||
blockNBT: make(map[protocol.SubChunkPos][]map[string]any),
|
blockNBT: make(map[protocol.SubChunkPos][]map[string]any),
|
||||||
entities: make(map[int64]world.SaveableEntity),
|
openItemContainers: make(map[byte]*itemContainer),
|
||||||
Dim: nil,
|
Dim: nil,
|
||||||
WorldName: "world",
|
WorldName: "world",
|
||||||
PlayerPos: TPlayerPos{},
|
PlayerPos: TPlayerPos{},
|
||||||
}
|
}
|
||||||
w.ui = NewMapUI(w)
|
w.ui = NewMapUI(w)
|
||||||
return w
|
return w
|
||||||
|
@ -97,18 +107,15 @@ func init() {
|
||||||
Offset_table[i] = protocol.SubChunkOffset{0, int8(i), 0}
|
Offset_table[i] = protocol.SubChunkOffset{0, int8(i), 0}
|
||||||
}
|
}
|
||||||
draw.Draw(black_16x16, image.Rect(0, 0, 16, 16), image.Black, image.Point{}, draw.Src)
|
draw.Draw(black_16x16, image.Rect(0, 0, 16, 16), image.Black, image.Point{}, draw.Src)
|
||||||
cs := crc32.ChecksumIEEE([]byte(utils.A))
|
|
||||||
if cs != 0x9747c04f {
|
|
||||||
utils.A += "T" + "A" + "M" + "P" + "E" + "R" + "E" + "D"
|
|
||||||
}
|
|
||||||
utils.RegisterCommand(&WorldCMD{})
|
utils.RegisterCommand(&WorldCMD{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorldCMD struct {
|
type WorldCMD struct {
|
||||||
Address string
|
Address string
|
||||||
packs bool
|
packs bool
|
||||||
enableVoid bool
|
enableVoid bool
|
||||||
saveImage bool
|
saveImage bool
|
||||||
|
experimentInventory bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*WorldCMD) Name() string { return "worlds" }
|
func (*WorldCMD) Name() string { return "worlds" }
|
||||||
|
@ -119,6 +126,7 @@ func (p *WorldCMD) SetFlags(f *flag.FlagSet) {
|
||||||
f.BoolVar(&p.packs, "packs", false, "save resourcepacks to the worlds")
|
f.BoolVar(&p.packs, "packs", false, "save resourcepacks to the worlds")
|
||||||
f.BoolVar(&p.enableVoid, "void", true, "if false, saves with default flat generator")
|
f.BoolVar(&p.enableVoid, "void", true, "if false, saves with default flat generator")
|
||||||
f.BoolVar(&p.saveImage, "image", false, "saves an png of the map at the end")
|
f.BoolVar(&p.saveImage, "image", false, "saves an png of the map at the end")
|
||||||
|
f.BoolVar(&p.experimentInventory, "inv", false, "enable experimental block inventory saving")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WorldCMD) Usage() string {
|
func (c *WorldCMD) Usage() string {
|
||||||
|
@ -143,6 +151,7 @@ func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{
|
||||||
w.ServerName = hostname
|
w.ServerName = hostname
|
||||||
w.withPacks = c.packs
|
w.withPacks = c.packs
|
||||||
w.saveImage = c.saveImage
|
w.saveImage = c.saveImage
|
||||||
|
w.experimentInventory = c.experimentInventory
|
||||||
w.ctx = ctx
|
w.ctx = ctx
|
||||||
|
|
||||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||||
|
@ -559,6 +568,29 @@ func (w *WorldState) ProcessPacketClient(pk packet.Packet) (packet.Packet, bool)
|
||||||
return pk, forward
|
return pk, forward
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stackToItem converts a network ItemStack representation back to an item.Stack.
|
||||||
|
func stackToItem(it protocol.ItemStack) item.Stack {
|
||||||
|
t, ok := world.ItemByRuntimeID(it.NetworkID, int16(it.MetadataValue))
|
||||||
|
if !ok {
|
||||||
|
t = block.Air{}
|
||||||
|
}
|
||||||
|
if it.BlockRuntimeID > 0 {
|
||||||
|
// It shouldn't matter if it (for whatever reason) wasn't able to get the block runtime ID,
|
||||||
|
// since on the next line, we assert that the block is an item. If it didn't succeed, it'll
|
||||||
|
// return air anyway.
|
||||||
|
b, _ := world.BlockByRuntimeID(uint32(it.BlockRuntimeID))
|
||||||
|
if t, ok = b.(world.Item); !ok {
|
||||||
|
t = block.Air{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//noinspection SpellCheckingInspection
|
||||||
|
if nbter, ok := t.(world.NBTer); ok && len(it.NBTData) != 0 {
|
||||||
|
t = nbter.DecodeNBT(it.NBTData).(world.Item)
|
||||||
|
}
|
||||||
|
s := item.NewStack(t, int(it.Count))
|
||||||
|
return nbtconv.ReadItem(it.NBTData, &s)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WorldState) ProcessPacketServer(pk packet.Packet) (packet.Packet, bool) {
|
func (w *WorldState) ProcessPacketServer(pk packet.Packet) (packet.Packet, bool) {
|
||||||
switch pk := pk.(type) {
|
switch pk := pk.(type) {
|
||||||
case *packet.ChangeDimension:
|
case *packet.ChangeDimension:
|
||||||
|
@ -568,6 +600,77 @@ func (w *WorldState) ProcessPacketServer(pk packet.Packet) (packet.Packet, bool)
|
||||||
w.proxy.SendPopup(fmt.Sprintf("%d chunks loaded\nname: %s", len(w.chunks), w.WorldName))
|
w.proxy.SendPopup(fmt.Sprintf("%d chunks loaded\nname: %s", len(w.chunks), w.WorldName))
|
||||||
case *packet.SubChunk:
|
case *packet.SubChunk:
|
||||||
w.ProcessSubChunk(pk)
|
w.ProcessSubChunk(pk)
|
||||||
|
case *packet.ContainerOpen:
|
||||||
|
if w.experimentInventory {
|
||||||
|
// add to open containers
|
||||||
|
existing, ok := w.openItemContainers[pk.WindowID]
|
||||||
|
if !ok {
|
||||||
|
existing = &itemContainer{}
|
||||||
|
}
|
||||||
|
w.openItemContainers[pk.WindowID] = &itemContainer{
|
||||||
|
OpenPacket: pk,
|
||||||
|
Content: existing.Content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *packet.InventoryContent:
|
||||||
|
if w.experimentInventory {
|
||||||
|
// save content
|
||||||
|
fmt.Printf("WindowID: %d\n", pk.WindowID)
|
||||||
|
existing, ok := w.openItemContainers[byte(pk.WindowID)]
|
||||||
|
if !ok {
|
||||||
|
if pk.WindowID == 0x0 { // inventory
|
||||||
|
w.openItemContainers[byte(pk.WindowID)] = &itemContainer{
|
||||||
|
Content: pk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
existing.Content = pk
|
||||||
|
}
|
||||||
|
case *packet.ContainerClose:
|
||||||
|
if w.experimentInventory {
|
||||||
|
switch pk.WindowID {
|
||||||
|
case protocol.WindowIDArmour: // todo handle
|
||||||
|
case protocol.WindowIDOffHand: // todo handle
|
||||||
|
case protocol.WindowIDUI:
|
||||||
|
case protocol.WindowIDInventory: // todo handle
|
||||||
|
default:
|
||||||
|
// find container info
|
||||||
|
existing, ok := w.openItemContainers[byte(pk.WindowID)]
|
||||||
|
if !ok {
|
||||||
|
logrus.Warn("Closed window that wasnt open")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if existing.Content == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := existing.OpenPacket.ContainerPosition
|
||||||
|
cp := protocol.SubChunkPos{pos.X() << 4, pos.Z() << 4}
|
||||||
|
|
||||||
|
// create inventory
|
||||||
|
inv := inventory.New(len(existing.Content.Content), nil)
|
||||||
|
for i, c := range existing.Content.Content {
|
||||||
|
item := stackToItem(c.Stack)
|
||||||
|
inv.SetItem(i, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// put into subchunk
|
||||||
|
nbts := w.blockNBT[cp]
|
||||||
|
for i, v := range nbts {
|
||||||
|
nbt_pos := protocol.BlockPos{v["x"].(int32), v["y"].(int32), v["z"].(int32)}
|
||||||
|
if nbt_pos == pos {
|
||||||
|
w.blockNBT[cp][i]["Items"] = nbtconv.InvToNBT(inv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.proxy.SendMessage("Saved Block Inventory")
|
||||||
|
|
||||||
|
// remove it again
|
||||||
|
delete(w.openItemContainers, byte(pk.WindowID))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return pk, true
|
return pk, true
|
||||||
}
|
}
|
||||||
|
|
24
utils/nbtconv/colour.go
Normal file
24
utils/nbtconv/colour.go
Normal file
|
@ -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]}
|
||||||
|
}
|
31
utils/nbtconv/item.go
Normal file
31
utils/nbtconv/item.go
Normal file
|
@ -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 := ReadItem(data, nil)
|
||||||
|
if it.Empty() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_ = inv.SetItem(int(Map[byte](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
|
||||||
|
}
|
89
utils/nbtconv/mapread.go
Normal file
89
utils/nbtconv/mapread.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package nbtconv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map reads a value of the type T from the map passed. Map never panics. If the key was not found in the map
|
||||||
|
// or if the value was of a different type, the default value of type T is returned.
|
||||||
|
func Map[T any](m map[string]any, key string) T {
|
||||||
|
v, _ := m[key].(T)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapVec3 converts x, y and z values in an NBT map to an mgl64.Vec3.
|
||||||
|
func MapVec3(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])}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapPos converts x, y and z values in an NBT map to a cube.Pos.
|
||||||
|
func MapPos(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])}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapBlock converts a block's name and properties in a map obtained by decoding NBT to a world.Block.
|
||||||
|
func MapBlock(x map[string]any, k string) world.Block {
|
||||||
|
if m, ok := x[k].(map[string]any); ok {
|
||||||
|
return ReadBlock(m)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
s := readItemStack(m)
|
||||||
|
readDamage(m, &s, true)
|
||||||
|
readEnchantments(m, &s)
|
||||||
|
readDisplay(m, &s)
|
||||||
|
readDragonflyData(m, &s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return item.Stack{}
|
||||||
|
}
|
123
utils/nbtconv/read.go
Normal file
123
utils/nbtconv/read.go
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package nbtconv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"github.com/df-mc/dragonfly/server/item"
|
||||||
|
"github.com/df-mc/dragonfly/server/world"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadItem decodes the data of an item into an item stack.
|
||||||
|
func ReadItem(data map[string]any, s *item.Stack) item.Stack {
|
||||||
|
disk := s == nil
|
||||||
|
if disk {
|
||||||
|
a := readItemStack(data)
|
||||||
|
s = &a
|
||||||
|
}
|
||||||
|
readDamage(data, s, disk)
|
||||||
|
readAnvilCost(data, s)
|
||||||
|
readDisplay(data, s)
|
||||||
|
readEnchantments(data, s)
|
||||||
|
readDragonflyData(data, s)
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadBlock decodes the data of a block into a world.Block.
|
||||||
|
func ReadBlock(m map[string]any) world.Block {
|
||||||
|
name, _ := m["name"].(string)
|
||||||
|
properties, _ := m["states"].(map[string]any)
|
||||||
|
b, _ := world.BlockByName(name, properties)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// readItemStack reads an item.Stack from the NBT in the map passed.
|
||||||
|
func readItemStack(m map[string]any) item.Stack {
|
||||||
|
var it world.Item
|
||||||
|
if blockItem, ok := MapBlock(m, "Block").(world.Item); ok {
|
||||||
|
it = blockItem
|
||||||
|
}
|
||||||
|
if v, ok := world.ItemByName(Map[string](m, "Name"), Map[int16](m, "Damage")); ok {
|
||||||
|
it = v
|
||||||
|
}
|
||||||
|
if it == nil {
|
||||||
|
return item.Stack{}
|
||||||
|
}
|
||||||
|
if n, ok := it.(world.NBTer); ok {
|
||||||
|
it = n.DecodeNBT(m).(world.Item)
|
||||||
|
}
|
||||||
|
return item.NewStack(it, int(Map[byte](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(Map[int16](m, "Damage")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*s = s.Damage(int(Map[int32](m, "Damage")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readAnvilCost ...
|
||||||
|
func readAnvilCost(m map[string]any, s *item.Stack) {
|
||||||
|
*s = s.WithAnvilCost(int(Map[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 Map[[]any](m, "ench") {
|
||||||
|
if v, ok := e.(map[string]any); ok {
|
||||||
|
enchantments = append(enchantments, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ench := range enchantments {
|
||||||
|
if t, ok := item.EnchantmentByID(int(Map[int16](ench, "id"))); ok {
|
||||||
|
*s = s.WithEnchantments(item.NewEnchantment(t, int(Map[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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
139
utils/nbtconv/write.go
Normal file
139
utils/nbtconv/write.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
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 {
|
||||||
|
m := make(map[string]any)
|
||||||
|
if nbt, ok := s.Item().(world.NBTer); ok {
|
||||||
|
for k, v := range nbt.EncodeNBT() {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if disk {
|
||||||
|
writeItemStack(m, s)
|
||||||
|
}
|
||||||
|
writeDamage(m, s, disk)
|
||||||
|
writeAnvilCost(m, s)
|
||||||
|
writeDisplay(m, s)
|
||||||
|
writeEnchantments(m, s)
|
||||||
|
writeDragonflyData(m, s)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user