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  }