177 lines
6.0 KiB
Go
177 lines
6.0 KiB
Go
|
|
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
|
||
|
|
}
|