diff --git a/cmd/blockhash/main.go b/cmd/blockhash/main.go index 8244acfdf..9718ca27a 100644 --- a/cmd/blockhash/main.go +++ b/cmd/blockhash/main.go @@ -249,7 +249,8 @@ func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[ case "CoralType", "SkullType": return "uint64(" + s + ".Uint8())", 3 case "AnvilType", "SandstoneType", "PrismarineType", "StoneBricksType", "NetherBricksType", "FroglightType", - "WallConnectionType", "BlackstoneType", "DeepslateType", "TallGrassType", "CopperType", "OxidationType": + "WallConnectionType", "BlackstoneType", "DeepslateType", "TallGrassType", "CopperType", "OxidationType", + "ShulkerBoxType": return "uint64(" + s + ".Uint8())", 2 case "OreType", "FireType", "DoubleTallGrassType": return "uint64(" + s + ".Uint8())", 1 diff --git a/server/block/cube/face.go b/server/block/cube/face.go index d08286b64..4c1681e73 100644 --- a/server/block/cube/face.go +++ b/server/block/cube/face.go @@ -1,5 +1,7 @@ package cube +import "github.com/go-gl/mathgl/mgl64" + const ( // FaceDown represents the bottom face of a block. FaceDown Face = iota @@ -89,6 +91,25 @@ func (f Face) RotateLeft() Face { return f } +// Offset returns the position offset of the Face. +func (f Face) Offset() mgl64.Vec3 { + switch f { + case FaceUp: + return mgl64.Vec3{0, 1, 0} + case FaceDown: + return mgl64.Vec3{0, -1, 0} + case FaceNorth: + return mgl64.Vec3{0, 0, -1} + case FaceSouth: + return mgl64.Vec3{0, 0, 1} + case FaceWest: + return mgl64.Vec3{-1, -0, 0} + case FaceEast: + return mgl64.Vec3{1, 0, 0} + } + panic("invalid face") +} + // String returns the Face as a string. func (f Face) String() string { switch f { diff --git a/server/block/hash.go b/server/block/hash.go index cfc93e48d..fb8c1f4d7 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -160,6 +160,7 @@ const ( hashSeaPickle hashShortGrass hashShroomlight + hashShulkerBox hashSign hashSkull hashSlab @@ -825,6 +826,10 @@ func (Shroomlight) Hash() (uint64, uint64) { return hashShroomlight, 0 } +func (s ShulkerBox) Hash() (uint64, uint64) { + return hashShulkerBox, uint64(s.Type.Uint8()) +} + func (s Sign) Hash() (uint64, uint64) { return hashSign, uint64(s.Wood.Uint8()) | uint64(s.Attach.Uint8())<<4 } diff --git a/server/block/model/shulker.go b/server/block/model/shulker.go new file mode 100644 index 000000000..d920c8bcb --- /dev/null +++ b/server/block/model/shulker.go @@ -0,0 +1,35 @@ +package model + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" +) + +// Shulker is the model of a shulker. It depends on the opening/closing progress of the shulker block. +type Shulker struct { + // Facing is the face that the shulker faces. + Facing cube.Face + // Progress is the opening/closing progress of the shulker. + Progress int32 +} + +// BBox returns a BBox that depends on the opening/closing progress of the shulker. +func (s Shulker) BBox(cube.Pos, world.BlockSource) []cube.BBox { + peak := ShulkerPhysicalPeak(s.Progress) + // Adds peak to the top and subtracts peak from the bottom. (according to BDS) + bbox := full + bbox = bbox.ExtendTowards(s.Facing, peak).ExtendTowards(s.Facing.Opposite(), -peak) + return []cube.BBox{bbox} +} + +// ShulkerPhysicalPeak returns the peak of which the shulker reaches in its current progress +func ShulkerPhysicalPeak(progress int32) float64 { + fp := float64(progress) / 10.0 + openness := 1.0 - fp + return (1.0 - openness*openness*openness) * 0.5 +} + +// FaceSolid always returns false. +func (Shulker) FaceSolid(cube.Pos, cube.Face, world.BlockSource) bool { + return false +} diff --git a/server/block/register.go b/server/block/register.go index ecec82902..8e9c74edd 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -215,6 +215,7 @@ func init() { registerAll(allCopperLanterns()) registerAll(allCopperTorches()) registerAll(allCopperTrapdoors()) + registerAll(allShulkerBoxes()) } func init() { @@ -489,6 +490,10 @@ func init() { world.RegisterItem(Copper{Type: c, Oxidation: o, Waxed: true}) } } + + for _, t := range ShulkerBoxTypes() { + world.RegisterItem(ShulkerBox{Type: t}) + } } func registerAll(blocks []world.Block) { diff --git a/server/block/shulker_box.go b/server/block/shulker_box.go new file mode 100644 index 000000000..cd993ea16 --- /dev/null +++ b/server/block/shulker_box.go @@ -0,0 +1,322 @@ +package block + +import ( + "fmt" + "math/rand/v2" + "strings" + "sync" + "sync/atomic" + + "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/item/inventory" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" +) + +const ( + StateClosed = iota + StateOpening + StateOpened + StateClosing +) + +// ShulkerBox is a dye-able block that stores items. Unlike other blocks, it keeps its contents when broken. +type ShulkerBox struct { + transparent + sourceWaterDisplacer + // Type is the type of shulker box of the block. + Type ShulkerBoxType + // Facing is the direction that the shulker box is facing. + Facing cube.Face + // CustomName is the custom name of the shulker box. This name is displayed when the shulker box is opened, and may + // include colour codes. + CustomName string + + inventory *inventory.Inventory + viewerMu *sync.RWMutex + viewers map[ContainerViewer]struct{} + // progress is the openness of the shulker box opening or closing. It is a float between 0 and 1. + progress *atomic.Int32 + // prevProgress is the previous tick's progress, used to calculate push delta. + prevProgress *atomic.Int32 + // animationStatus is the current openness state of the shulker box (whether its opened, closing, etc.). + animationStatus *atomic.Int32 +} + +// NewShulkerBox creates a new initialised shulker box. The inventory is properly initialised. +func NewShulkerBox() ShulkerBox { + s := ShulkerBox{ + viewerMu: new(sync.RWMutex), + viewers: make(map[ContainerViewer]struct{}, 1), + progress: new(atomic.Int32), + prevProgress: new(atomic.Int32), + animationStatus: new(atomic.Int32), + } + + s.inventory = inventory.New(27, func(slot int, _, after item.Stack) { + s.viewerMu.RLock() + defer s.viewerMu.RUnlock() + for viewer := range s.viewers { + // A shulker box inventory can't store shulker boxes, this is mostly handled by the client. + if _, ok := after.Item().(ShulkerBox); !ok { + viewer.ViewSlotChange(slot, after) + } + } + }) + + return s +} + +// Model ... +func (s ShulkerBox) Model() world.BlockModel { + return model.Shulker{Facing: s.Facing, Progress: s.progress.Load()} +} + +// WithName returns the shulker box after applying a specific name to the block. +func (s ShulkerBox) WithName(a ...any) world.Item { + s.CustomName = strings.TrimSuffix(fmt.Sprintln(a...), "\n") + return s +} + +// AddViewer adds a viewer to the shulker box, so that it is updated whenever the inventory of the shulker box is changed. +func (s ShulkerBox) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { + s.viewerMu.Lock() + defer s.viewerMu.Unlock() + if len(s.viewers) == 0 { + s.open(tx, pos) + } + + s.viewers[v] = struct{}{} +} + +// RemoveViewer removes a viewer from the shulker box, so that slot updates in the inventory are no longer sent to it. +func (s ShulkerBox) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) { + s.viewerMu.Lock() + defer s.viewerMu.Unlock() + if len(s.viewers) == 0 { + return + } + delete(s.viewers, v) + if len(s.viewers) == 0 { + s.close(tx, pos) + } +} + +// Inventory returns the inventory of the shulker box. +func (s ShulkerBox) Inventory(*world.Tx, cube.Pos) *inventory.Inventory { + return s.inventory +} + +// Activate ... +func (s ShulkerBox) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool { + if opener, ok := u.(ContainerOpener); ok { + if d, ok := tx.Block(pos.Side(s.Facing)).(LightDiffuser); ok && d.LightDiffusionLevel() <= 2 { + opener.OpenBlockContainer(pos, tx) + } + return true + } + return false +} + +// UseOnBlock ... +func (s ShulkerBox) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, s) + if !used { + return + } + + if s.inventory == nil { + typ, customName := s.Type, s.CustomName + //noinspection GoAssignmentToReceiver + s = NewShulkerBox() + s.Type, s.CustomName = typ, customName + } + + s.Facing = face + place(tx, pos, s, user, ctx) + return placed(ctx) +} + +// open opens the shulker box, displaying the animation and playing a sound. +func (s ShulkerBox) open(tx *world.Tx, pos cube.Pos) { + s.animationStatus.Store(StateOpening) + for _, v := range tx.Viewers(pos.Vec3()) { + v.ViewBlockAction(pos, OpenAction{}) + } + tx.PlaySound(pos.Vec3Centre(), sound.ShulkerBoxOpen{}) + tx.ScheduleBlockUpdate(pos, s, 0) +} + +// close closes the shulker box, displaying the animation and playing a sound. +func (s ShulkerBox) close(tx *world.Tx, pos cube.Pos) { + s.animationStatus.Store(StateClosing) + for _, v := range tx.Viewers(pos.Vec3()) { + v.ViewBlockAction(pos, CloseAction{}) + } + tx.ScheduleBlockUpdate(pos, s, 0) +} + +// ScheduledTick ... +func (s ShulkerBox) ScheduledTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { + switch s.animationStatus.Load() { + case StateClosed: + s.progress.Store(0) + case StateOpening: + s.prevProgress.Store(s.progress.Load()) + s.progress.Add(1) + + s.pushEntities(pos, tx) + if s.progress.Load() >= 10 { + s.progress.Store(10) + s.animationStatus.Store(StateOpened) + } + tx.ScheduleBlockUpdate(pos, s, 0) + case StateOpened: + s.progress.Store(10) + case StateClosing: + s.progress.Add(-1) + if s.progress.Load() <= 0 { + tx.PlaySound(pos.Vec3Centre(), sound.ShulkerBoxClose{}) + s.progress.Store(0) + s.animationStatus.Store(StateClosed) + } + tx.ScheduleBlockUpdate(pos, s, 0) + } +} + +// pushEntities pushes all entities touching the shulker box lid during opening. +func (s ShulkerBox) pushEntities(pos cube.Pos, tx *world.Tx) { + shulkerBBoxes := s.Model().BBox(pos, tx) + if len(shulkerBBoxes) == 0 { + return + } + searchBox := shulkerBBoxes[0].Translate(pos.Vec3()).Grow(0.35) + for e := range tx.EntitiesWithin(searchBox) { + s.push(pos, tx, e) + } +} + +// push pushes entities when the shulker box lid is opening. +func (s ShulkerBox) push(pos cube.Pos, tx *world.Tx, e world.Entity) { + if s.animationStatus.Load() != StateOpening { + return + } + living, ok := e.(interface { + MoveDelta(deltaPos mgl64.Vec3, deltaYaw, deltaPitch float64) + }) + if !ok { + return + } + shulkerBBoxes := s.Model().BBox(pos, tx) + if len(shulkerBBoxes) == 0 { + return + } + shulkerBBox := shulkerBBoxes[0].Translate(pos.Vec3()) + entityBBox := e.H().Type().BBox(e).Translate(e.Position()) + if !shulkerBBox.IntersectsWith(entityBBox) { + return + } + + offset := s.Facing.Offset() + entityPos := e.Position() + + if offset.Y() > 0 { + targetY := shulkerBBox.Max().Y() + if entityPos.Y() < targetY { + living.MoveDelta(mgl64.Vec3{0, targetY - entityPos.Y(), 0}, 0, 0) + } + return + } + + if offset.Y() != 0 { + return + } + + halfW := entityBBox.Width() / 2 + halfL := entityBBox.Length() / 2 + switch { + case offset.X() > 0: + target := shulkerBBox.Max().X() + halfW + if x := entityPos.X(); x < target { + living.MoveDelta(mgl64.Vec3{target - x, 0, 0}, 0, 0) + } + case offset.X() < 0: + target := shulkerBBox.Min().X() - halfW + if x := entityPos.X(); x > target { + living.MoveDelta(mgl64.Vec3{target - x, 0, 0}, 0, 0) + } + case offset.Z() > 0: + target := shulkerBBox.Max().Z() + halfL + if z := entityPos.Z(); z < target { + living.MoveDelta(mgl64.Vec3{0, 0, target - z}, 0, 0) + } + case offset.Z() < 0: + target := shulkerBBox.Min().Z() - halfL + if z := entityPos.Z(); z > target { + living.MoveDelta(mgl64.Vec3{0, 0, target - z}, 0, 0) + } + } +} + +// BreakInfo ... +func (s ShulkerBox) BreakInfo() BreakInfo { + return newBreakInfo(2, alwaysHarvestable, pickaxeEffective, oneOf(s)) +} + +// MaxCount always returns 1. +func (s ShulkerBox) MaxCount() int { + return 1 +} + +// EncodeBlock ... +func (s ShulkerBox) EncodeBlock() (name string, properties map[string]any) { + return "minecraft:" + s.Type.String(), nil +} + +// EncodeItem ... +func (s ShulkerBox) EncodeItem() (id string, meta int16) { + return "minecraft:" + s.Type.String(), 0 +} + +// DecodeNBT ... +func (s ShulkerBox) DecodeNBT(data map[string]any) any { + typ := s.Type + //noinspection GoAssignmentToReceiver + s = NewShulkerBox() + s.Type = typ + nbtconv.InvFromNBT(s.inventory, nbtconv.Slice(data, "Items")) + s.Facing = cube.Face(nbtconv.Uint8(data, "facing")) + s.CustomName = nbtconv.String(data, "CustomName") + return s +} + +// EncodeNBT .. +func (s ShulkerBox) EncodeNBT() map[string]any { + if s.inventory == nil { + typ, facing, customName := s.Type, s.Facing, s.CustomName + //noinspection GoAssignmentToReceiver + s = NewShulkerBox() + s.Type, s.Facing, s.CustomName = typ, facing, customName + } + m := map[string]any{ + "Items": nbtconv.InvToNBT(s.inventory), + "id": "ShulkerBox", + "facing": uint8(s.Facing), + } + if s.CustomName != "" { + m["CustomName"] = s.CustomName + } + return m +} + +// allShulkerBoxes ... +func allShulkerBoxes() (boxes []world.Block) { + for _, t := range ShulkerBoxTypes() { + boxes = append(boxes, ShulkerBox{Type: t}) + } + return +} diff --git a/server/block/shulker_box_type.go b/server/block/shulker_box_type.go new file mode 100644 index 000000000..9c96c20c4 --- /dev/null +++ b/server/block/shulker_box_type.go @@ -0,0 +1,203 @@ +package block + +// ShulkerBoxType represents a type of shulker box. +type ShulkerBoxType struct { + shulkerBox +} + +type shulkerBox uint8 + +// NormalShulkerBox is the normal variant of the shulker box. +func NormalShulkerBox() ShulkerBoxType { + return ShulkerBoxType{0} +} + +// WhiteShulkerBox is the white variant of the shulker box. +func WhiteShulkerBox() ShulkerBoxType { + return ShulkerBoxType{1} +} + +// OrangeShulkerBox is the orange variant of the shulker box. +func OrangeShulkerBox() ShulkerBoxType { + return ShulkerBoxType{2} +} + +// MagentaShulkerBox is the magenta variant of the shulker box. +func MagentaShulkerBox() ShulkerBoxType { + return ShulkerBoxType{3} +} + +// LightBlueShulkerBox is the light blue variant of the shulker box. +func LightBlueShulkerBox() ShulkerBoxType { + return ShulkerBoxType{4} +} + +// YellowShulkerBox is the yellow variant of the shulker box. +func YellowShulkerBox() ShulkerBoxType { + return ShulkerBoxType{5} +} + +// LimeShulkerBox is the lime variant of the shulker box. +func LimeShulkerBox() ShulkerBoxType { + return ShulkerBoxType{6} +} + +// PinkShulkerBox is the pink variant of the shulker box. +func PinkShulkerBox() ShulkerBoxType { + return ShulkerBoxType{7} +} + +// GrayShulkerBox is the gray variant of the shulker box. +func GrayShulkerBox() ShulkerBoxType { + return ShulkerBoxType{8} +} + +// LightGrayShulkerBox is the light gray variant of the shulker box. +func LightGrayShulkerBox() ShulkerBoxType { + return ShulkerBoxType{9} +} + +// CyanShulkerBox is the cyan variant of the shulker box. +func CyanShulkerBox() ShulkerBoxType { + return ShulkerBoxType{10} +} + +// PurpleShulkerBox is the purple variant of the shulker box. +func PurpleShulkerBox() ShulkerBoxType { + return ShulkerBoxType{11} +} + +// BlueShulkerBox is the blue variant of the shulker box. +func BlueShulkerBox() ShulkerBoxType { + return ShulkerBoxType{12} +} + +// BrownShulkerBox is the brown variant of the shulker box. +func BrownShulkerBox() ShulkerBoxType { + return ShulkerBoxType{13} +} + +// GreenShulkerBox is the green variant of the shulker box. +func GreenShulkerBox() ShulkerBoxType { + return ShulkerBoxType{14} +} + +// RedShulkerBox is the red variant of the shulker box. +func RedShulkerBox() ShulkerBoxType { + return ShulkerBoxType{15} +} + +// BlackShulkerBox is the black variant of the shulker box. +func BlackShulkerBox() ShulkerBoxType { + return ShulkerBoxType{16} +} + +// Uint8 returns the shulker box type as a uint8. +func (s shulkerBox) Uint8() uint8 { + return uint8(s) +} + +// Name ... +func (s shulkerBox) Name() string { + switch s { + case 0: + return "Shulker Box" + case 1: + return "White Shulker Box" + case 2: + return "Orange Shulker Box" + case 3: + return "Magenta Shulker Box" + case 4: + return "Light Blue Shulker Box" + case 5: + return "Yellow Shulker Box" + case 6: + return "Lime Shulker Box" + case 7: + return "Pink Shulker Box" + case 8: + return "Gray Shulker Box" + case 9: + return "Light Gray Shulker Box" + case 10: + return "Cyan Shulker Box" + case 11: + return "Purple Shulker Box" + case 12: + return "Blue Shulker Box" + case 13: + return "Brown Shulker Box" + case 14: + return "Green Shulker Box" + case 15: + return "Red Shulker Box" + case 16: + return "Black Shulker Box" + } + panic("unknown skulker box type") +} + +// String ... +func (s shulkerBox) String() string { + switch s { + case 0: + return "undyed_shulker_box" + case 1: + return "white_shulker_box" + case 2: + return "orange_shulker_box" + case 3: + return "magenta_shulker_box" + case 4: + return "light_blue_shulker_box" + case 5: + return "yellow_shulker_box" + case 6: + return "lime_shulker_box" + case 7: + return "pink_shulker_box" + case 8: + return "gray_shulker_box" + case 9: + return "light_gray_shulker_box" + case 10: + return "cyan_shulker_box" + case 11: + return "purple_shulker_box" + case 12: + return "blue_shulker_box" + case 13: + return "brown_shulker_box" + case 14: + return "green_shulker_box" + case 15: + return "red_shulker_box" + case 16: + return "black_shulker_box" + } + panic("unknown shulker box type") +} + +// ShulkerBoxTypes returns all shulker box types. +func ShulkerBoxTypes() []ShulkerBoxType { + return []ShulkerBoxType{ + NormalShulkerBox(), + WhiteShulkerBox(), + OrangeShulkerBox(), + MagentaShulkerBox(), + LightBlueShulkerBox(), + YellowShulkerBox(), + LimeShulkerBox(), + PinkShulkerBox(), + GrayShulkerBox(), + LightGrayShulkerBox(), + CyanShulkerBox(), + PurpleShulkerBox(), + BlueShulkerBox(), + BrownShulkerBox(), + GreenShulkerBox(), + RedShulkerBox(), + BlackShulkerBox(), + } +} diff --git a/server/internal/nbtconv/item.go b/server/internal/nbtconv/item.go index 1443ddc16..2a3886187 100644 --- a/server/internal/nbtconv/item.go +++ b/server/internal/nbtconv/item.go @@ -17,8 +17,8 @@ func InvFromNBT(inv *inventory.Inventory, items []any) { } // 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 +func InvToNBT(inv *inventory.Inventory) []any { + var items []any for index, i := range inv.Slots() { if i.Empty() { continue diff --git a/server/player/player.go b/server/player/player.go index 0c2f8d363..ea1bd1a85 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -10,9 +10,6 @@ import ( "sync" "time" - "github.com/df-mc/dragonfly/server/player/debug" - "github.com/df-mc/dragonfly/server/player/hud" - "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/model" @@ -25,8 +22,10 @@ import ( "github.com/df-mc/dragonfly/server/item/inventory" "github.com/df-mc/dragonfly/server/player/bossbar" "github.com/df-mc/dragonfly/server/player/chat" + "github.com/df-mc/dragonfly/server/player/debug" "github.com/df-mc/dragonfly/server/player/dialogue" "github.com/df-mc/dragonfly/server/player/form" + "github.com/df-mc/dragonfly/server/player/hud" "github.com/df-mc/dragonfly/server/player/scoreboard" "github.com/df-mc/dragonfly/server/player/skin" "github.com/df-mc/dragonfly/server/player/title" @@ -2150,6 +2149,26 @@ func (p *Player) Move(deltaPos mgl64.Vec3, deltaYaw, deltaPitch float64) { } } +// MoveDelta ... +func (p *Player) MoveDelta(deltaPos mgl64.Vec3, deltaYaw, deltaPitch float64) { + var ( + pos = p.Position() + res, resRot = pos.Add(deltaPos), p.Rotation().Add(cube.Rotation{deltaYaw, deltaPitch}) + ) + + for _, v := range p.viewers() { + v.ViewEntityDelta(p, res, resRot) + } + + p.data.Pos = res + p.data.Rot = resRot + if deltaPos.Len() <= 3 { + // Only update velocity if the player is not moving too fast to prevent potential OOMs. + p.data.Vel = deltaPos + p.checkBlockCollisions(deltaPos) + } +} + // Position returns the current position of the player. It may be changed as the player moves or is moved // around the world. func (p *Player) Position() mgl64.Vec3 { diff --git a/server/session/controllable.go b/server/session/controllable.go index 524c5dbb0..ed726f177 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -38,6 +38,7 @@ type Controllable interface { SetHeldSlot(slot int) error Move(deltaPos mgl64.Vec3, deltaYaw, deltaPitch float64) + MoveDelta(deltaPos mgl64.Vec3, deltaYaw, deltaPitch float64) Speed() float64 FlightSpeed() float64 diff --git a/server/session/player.go b/server/session/player.go index 1eba07add..21edc7d7e 100644 --- a/server/session/player.go +++ b/server/session/player.go @@ -274,6 +274,10 @@ func (s *Session) invByID(id int32, tx *world.Tx) (*inventory.Inventory, bool) { switch id { case protocol.ContainerLevelEntity: return s.openedWindow.Load(), true + case protocol.ContainerShulkerBox: + if _, shulkerbox := tx.Block(*s.openedPos.Load()).(block.ShulkerBox); shulkerbox { + return s.openedWindow.Load(), true + } case protocol.ContainerBarrel: if _, barrel := tx.Block(*s.openedPos.Load()).(block.Barrel); barrel { return s.openedWindow.Load(), true diff --git a/server/session/world.go b/server/session/world.go index 6090d7e46..316986e5a 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -207,6 +207,39 @@ func (s *Session) ViewEntityMovement(e world.Entity, pos mgl64.Vec3, rot cube.Ro }) } +// ViewEntityDelta ... +func (s *Session) ViewEntityDelta(e world.Entity, pos mgl64.Vec3, rot cube.Rotation) { + if s.entityHidden(e) { + return + } + + flags := uint16(0) + if !mgl64.FloatEqual(pos.X(), 0) { + flags |= packet.MoveActorDeltaFlagHasX + } + if !mgl64.FloatEqual(pos.Y(), 0) { + flags |= packet.MoveActorDeltaFlagHasY + } + if !mgl64.FloatEqual(pos.Z(), 0) { + flags |= packet.MoveActorDeltaFlagHasZ + } + if !mgl64.FloatEqual(rot.Pitch(), 0) { + flags |= packet.MoveActorDeltaFlagHasRotX + } + if !mgl64.FloatEqual(rot.Yaw(), 0) { + flags |= packet.MoveActorDeltaFlagHasRotY + } + + flags |= packet.MoveActorDeltaFlagForceMove + + s.writePacket(&packet.MoveActorDelta{ + Flags: flags, + EntityRuntimeID: s.entityRuntimeID(e), + Position: vec64To32(pos.Add(entityOffset(e))), + Rotation: vec64To32(mgl64.Vec3{rot.Pitch(), rot.Yaw(), rot.Yaw()}), + }) +} + // ViewEntityVelocity ... func (s *Session) ViewEntityVelocity(e world.Entity, velocity mgl64.Vec3) { if s.entityHidden(e) { @@ -829,6 +862,11 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) EventType: packet.LevelEventSoundTotemUsed, Position: vec64To32(pos), }) + return + case sound.ShulkerBoxClose: + pk.SoundType = packet.SoundEventShulkerBoxClosed + case sound.ShulkerBoxOpen: + pk.SoundType = packet.SoundEventShulkerBoxOpen case sound.DecoratedPotInserted: s.writePacket(&packet.PlaySound{ SoundName: "block.decorated_pot.insert", diff --git a/server/world/sound/block.go b/server/world/sound/block.go index e87c2b7ab..34bc9eb38 100644 --- a/server/world/sound/block.go +++ b/server/world/sound/block.go @@ -60,6 +60,12 @@ type BarrelClose struct{ sound } // Deny is a sound played when a block is placed or broken above a 'Deny' block from Education edition. type Deny struct{ sound } +// ShulkerBoxOpen is a sound played when a shulker box is opened. +type ShulkerBoxOpen struct{ sound } + +// ShulkerBoxClose is a sound played when a shulker box is closed. +type ShulkerBoxClose struct{ sound } + // DoorOpen is a sound played when a door is opened. type DoorOpen struct { // Block is the block which is being opened, for which a sound should be played. The sound played depends on the diff --git a/server/world/viewer.go b/server/world/viewer.go index 799017920..d47efc581 100644 --- a/server/world/viewer.go +++ b/server/world/viewer.go @@ -24,6 +24,9 @@ type Viewer interface { // ViewEntityMovement views the movement of an Entity. The Entity is moved with a delta position, yaw and // pitch, which, when applied to the respective values of the Entity, will result in the final values. ViewEntityMovement(e Entity, pos mgl64.Vec3, rot cube.Rotation, onGround bool) + // ViewEntityDelta views the movement of an Entity to an absolute position. The pos parameter is the + // absolute target position (not a delta), and rot is the target rotation. + ViewEntityDelta(e Entity, pos mgl64.Vec3, rot cube.Rotation) // ViewEntityVelocity views the velocity of an Entity. It is called right before a call to // ViewEntityMovement so that the Viewer may interpolate the movement itself. ViewEntityVelocity(e Entity, vel mgl64.Vec3) @@ -86,6 +89,7 @@ func (NopViewer) ViewEntity(Entity) func (NopViewer) HideEntity(Entity) {} func (NopViewer) ViewEntityGameMode(Entity) {} func (NopViewer) ViewEntityMovement(Entity, mgl64.Vec3, cube.Rotation, bool) {} +func (NopViewer) ViewEntityDelta(Entity, mgl64.Vec3, cube.Rotation) {} func (NopViewer) ViewEntityVelocity(Entity, mgl64.Vec3) {} func (NopViewer) ViewEntityTeleport(Entity, mgl64.Vec3) {} func (NopViewer) ViewChunk(ChunkPos, Dimension, map[cube.Pos]Block, *chunk.Chunk) {}