Files

177 lines
6.0 KiB
Go
Raw Permalink Normal View History

2026-07-09 08:33:57 +08:00
package creative
import (
_ "embed"
"fmt"
"github.com/df-mc/dragonfly/server/internal/nbtconv"
// The following four imports are essential for this package: They make sure this package is loaded after
// all these imports. This ensures that all blocks and items are registered before the creative items are
// registered in the init function in this package.
_ "github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"github.com/sandertv/gophertunnel/minecraft/nbt"
)
// Item represents a registered item in the creative inventory. It holds a stack of the item and a group that
// the item is part of.
type Item struct {
// Stack is the stack of the item that is registered in the creative inventory.
Stack item.Stack
// Group is the name of the group that the item is part of. If two groups are registered with the same
// name, the item will always reside in the first group that was registered.
Group string
}
// Group represents a group of items in the creative inventory. Each group has a category, a name and an icon.
// If either the name or icon is empty, the group is considered an 'anonymous group' and will not group its
// contents together in the creative inventory.
type Group struct {
// Category is the category of the group. It determines the tab in which the group will be displayed in the
// creative inventory.
Category Category
// Name is the localised name of the group, i.e. "itemGroup.name.planks".
Name string
// Icon is the item that will be displayed as the icon of the group in the creative inventory.
Icon item.Stack
}
// Groups returns a list with all groups that have been registered as a creative group. These groups will be
// accessible by players in-game who have creative mode enabled.
func Groups() []Group {
return creativeGroups
}
// RegisterGroup registers a group as a creative group, exposing it in the creative inventory. It can then
// be referenced using its name when calling RegisterItem.
func RegisterGroup(group Group) {
creativeGroups = append(creativeGroups, group)
}
// Items returns a list with all items that have been registered as a creative item. These items will
// be accessible by players in-game who have creative mode enabled.
func Items() []Item {
return creativeItemStacks
}
// RegisterItem registers an item as a creative item, exposing it in the creative inventory.
func RegisterItem(item Item) {
creativeItemStacks = append(creativeItemStacks, item)
}
var (
//go:embed creative_items.nbt
creativeItemData []byte
// creativeGroups holds a list of all groups that were registered to the creative inventory using
// RegisterGroup.
creativeGroups []Group
// creativeItemStacks holds a list of all item stacks that were registered to the creative inventory using
// RegisterItem.
creativeItemStacks []Item
)
// creativeGroupEntry holds data of a creative group as present in the creative inventory.
type creativeGroupEntry struct {
Category int32 `nbt:"category"`
Name string `nbt:"name"`
Icon creativeItemEntry `nbt:"icon"`
}
// creativeItemEntry holds data of a creative item as present in the creative inventory.
type creativeItemEntry struct {
Name string `nbt:"name"`
Meta int16 `nbt:"meta"`
NBT map[string]any `nbt:"nbt,omitempty"`
BlockProperties map[string]any `nbt:"block_properties,omitempty"`
GroupIndex int32 `nbt:"group_index,omitempty"`
}
// registerCreativeItems initialises the creative items, registering all creative items that have also been registered as
// normal items and are present in vanilla.
// noinspection GoUnusedFunction
//
//lint:ignore U1000 Function is used through compiler directives.
func registerCreativeItems() {
var m struct {
Groups []creativeGroupEntry `nbt:"groups"`
Items []creativeItemEntry `nbt:"items"`
}
if err := nbt.Unmarshal(creativeItemData, &m); err != nil {
panic(err)
}
for i, group := range m.Groups {
name := group.Name
if name == "" {
name = fmt.Sprint("anon", i)
}
st, _ := itemStackFromEntry(group.Icon)
c := Category{category(group.Category)}
RegisterGroup(Group{Category: c, Name: name, Icon: st})
}
for _, data := range m.Items {
if data.GroupIndex >= int32(len(creativeGroups)) {
panic(fmt.Errorf("invalid group index %v for item %v", data.GroupIndex, data.Name))
}
st, ok := itemStackFromEntry(data)
if !ok {
continue
}
RegisterItem(Item{st, creativeGroups[data.GroupIndex].Name})
}
}
func itemStackFromEntry(data creativeItemEntry) (item.Stack, bool) {
var (
it world.Item
ok bool
)
if len(data.BlockProperties) > 0 {
// Item with a block, try parsing the block, then try asserting that to an item. Blocks no longer
// have their metadata sent, but we still need to get that metadata in order to be able to register
// different block states as different items.
if b, ok := world.BlockByName(data.Name, data.BlockProperties); ok {
if it, ok = b.(world.Item); !ok {
return item.Stack{}, false
}
}
} else {
if it, ok = world.ItemByName(data.Name, data.Meta); !ok {
// The item wasn't registered, so don't register it as a creative item.
return item.Stack{}, false
}
if _, resultingMeta := it.EncodeItem(); resultingMeta != data.Meta {
// We found an item registered with that ID and a meta of 0, but we only need items with strictly
// the same meta here.
return item.Stack{}, false
}
}
if n, ok := it.(world.NBTer); ok {
if len(data.NBT) > 0 {
it = n.DecodeNBT(data.NBT).(world.Item)
}
}
st := item.NewStack(it, 1)
if len(data.NBT) > 0 {
var invalid bool
for _, e := range nbtconv.Slice(data.NBT, "ench") {
if v, ok := e.(map[string]any); ok {
t, ok := item.EnchantmentByID(int(nbtconv.Int16(v, "id")))
if !ok {
invalid = true
break
}
st = st.WithEnchantments(item.NewEnchantment(t, int(nbtconv.Int16(v, "lvl"))))
}
}
if invalid {
// Invalid enchantment, skip this item.
return item.Stack{}, false
}
}
return st, true
}