26ed99fda6
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
170 lines
4.8 KiB
Go
170 lines
4.8 KiB
Go
package block
|
|
|
|
import (
|
|
"github.com/df-mc/dragonfly/server/block/cube"
|
|
"github.com/df-mc/dragonfly/server/block/model"
|
|
"github.com/df-mc/dragonfly/server/internal/nbtconv"
|
|
"github.com/df-mc/dragonfly/server/item"
|
|
"github.com/df-mc/dragonfly/server/world"
|
|
"github.com/df-mc/dragonfly/server/world/sound"
|
|
"github.com/go-gl/mathgl/mgl64"
|
|
"math/rand/v2"
|
|
)
|
|
|
|
// ItemFrame is a block entity that displays the item or block that is inside it.
|
|
type ItemFrame struct {
|
|
empty
|
|
transparent
|
|
sourceWaterDisplacer
|
|
|
|
// Facing is the direction from the frame to the block.
|
|
Facing cube.Face
|
|
// Item is the item that is displayed inside the frame.
|
|
Item item.Stack
|
|
// Rotations is the number of rotations for the item in the frame. Each rotation is 45 degrees, with the exception
|
|
// being maps having 90 degree rotations.
|
|
Rotations int
|
|
// DropChance is the chance of the item dropping when the frame is broken. In vanilla, this is always 1.0.
|
|
DropChance float64
|
|
// Glowing makes the frame the glowing variant.
|
|
Glowing bool
|
|
}
|
|
|
|
// Activate ...
|
|
func (i ItemFrame) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool {
|
|
if !i.Item.Empty() {
|
|
// TODO: Item frames with maps can only be rotated four times.
|
|
i.Rotations = (i.Rotations + 1) % 8
|
|
tx.PlaySound(pos.Vec3Centre(), sound.ItemFrameRotate{})
|
|
} else if held, _ := u.HeldItems(); !held.Empty() {
|
|
i.Item = held.Grow(-held.Count() + 1)
|
|
// TODO: When maps are implemented, check the item is a map, and if so, display the large version of the frame.
|
|
ctx.SubtractFromCount(1)
|
|
tx.PlaySound(pos.Vec3Centre(), sound.ItemAdd{})
|
|
} else {
|
|
return true
|
|
}
|
|
|
|
tx.SetBlock(pos, i, nil)
|
|
return true
|
|
}
|
|
|
|
// Punch ...
|
|
func (i ItemFrame) Punch(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User) {
|
|
if i.Item.Empty() {
|
|
return
|
|
}
|
|
|
|
if g, ok := u.(interface {
|
|
GameMode() world.GameMode
|
|
}); ok {
|
|
if rand.Float64() <= i.DropChance && !g.GameMode().CreativeInventory() {
|
|
dropItem(tx, i.Item, pos.Vec3Centre())
|
|
}
|
|
}
|
|
i.Item, i.Rotations = item.Stack{}, 0
|
|
tx.PlaySound(pos.Vec3Centre(), sound.ItemFrameRemove{})
|
|
tx.SetBlock(pos, i, nil)
|
|
}
|
|
|
|
// UseOnBlock ...
|
|
func (i ItemFrame) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool {
|
|
pos, face, used := firstReplaceable(tx, pos, face, i)
|
|
if !used {
|
|
return false
|
|
}
|
|
if _, ok := tx.Block(pos.Side(face.Opposite())).Model().(model.Empty); ok {
|
|
// TODO: Allow exceptions for pressure plates.
|
|
return false
|
|
}
|
|
i.Facing = face.Opposite()
|
|
i.DropChance = 1.0
|
|
|
|
place(tx, pos, i, user, ctx)
|
|
return placed(ctx)
|
|
}
|
|
|
|
// BreakInfo ...
|
|
func (i ItemFrame) BreakInfo() BreakInfo {
|
|
return newBreakInfo(0.25, alwaysHarvestable, nothingEffective, oneOf(ItemFrame{Glowing: i.Glowing})).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) {
|
|
if !i.Item.Empty() {
|
|
dropItem(tx, i.Item, pos.Vec3Centre())
|
|
}
|
|
})
|
|
}
|
|
|
|
// EncodeItem ...
|
|
func (i ItemFrame) EncodeItem() (name string, meta int16) {
|
|
if i.Glowing {
|
|
return "minecraft:glow_frame", 0
|
|
}
|
|
return "minecraft:frame", 0
|
|
}
|
|
|
|
// EncodeBlock ...
|
|
func (i ItemFrame) EncodeBlock() (name string, properties map[string]any) {
|
|
name = "minecraft:frame"
|
|
if i.Glowing {
|
|
name = "minecraft:glow_frame"
|
|
}
|
|
return name, map[string]any{
|
|
"facing_direction": int32(i.Facing.Opposite()),
|
|
"item_frame_map_bit": uint8(0), // TODO: When maps are added, set this to true if the item is a map.
|
|
"item_frame_photo_bit": uint8(0), // Only implemented in Education Edition.
|
|
}
|
|
}
|
|
|
|
// DecodeNBT ...
|
|
func (i ItemFrame) DecodeNBT(data map[string]any) any {
|
|
i.DropChance = float64(nbtconv.Float32(data, "ItemDropChance"))
|
|
i.Rotations = int(nbtconv.Uint8(data, "ItemRotation"))
|
|
i.Item = nbtconv.MapItem(data, "Item")
|
|
return i
|
|
}
|
|
|
|
// EncodeNBT ...
|
|
func (i ItemFrame) EncodeNBT() map[string]any {
|
|
m := map[string]any{
|
|
"ItemDropChance": float32(i.DropChance),
|
|
"ItemRotation": uint8(i.Rotations),
|
|
"id": "ItemFrame",
|
|
}
|
|
if i.Glowing {
|
|
m["id"] = "GlowItemFrame"
|
|
}
|
|
if !i.Item.Empty() {
|
|
m["Item"] = nbtconv.WriteItem(i.Item, true)
|
|
}
|
|
return m
|
|
}
|
|
|
|
// Pick returns the item that is picked when the block is picked.
|
|
func (i ItemFrame) Pick() item.Stack {
|
|
if i.Item.Empty() {
|
|
return item.NewStack(ItemFrame{Glowing: i.Glowing}, 1)
|
|
}
|
|
return i.Item.Grow(-i.Item.Count() + 1)
|
|
}
|
|
|
|
// SideClosed ...
|
|
func (ItemFrame) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool {
|
|
return false
|
|
}
|
|
|
|
// NeighbourUpdateTick ...
|
|
func (i ItemFrame) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) {
|
|
if _, ok := tx.Block(pos.Side(i.Facing)).Model().(model.Empty); ok {
|
|
// TODO: Allow exceptions for pressure plates.
|
|
breakBlock(i, pos, tx)
|
|
}
|
|
}
|
|
|
|
// allItemFrames ...
|
|
func allItemFrames() (frames []world.Block) {
|
|
for _, f := range cube.Faces() {
|
|
frames = append(frames, ItemFrame{Facing: f, Glowing: true})
|
|
frames = append(frames, ItemFrame{Facing: f, Glowing: false})
|
|
}
|
|
return
|
|
}
|