From 93e60005038122f923da48bd7039f29c44b30f50 Mon Sep 17 00:00:00 2001 From: AkmalFairuz Date: Sat, 21 Mar 2026 00:18:49 +0700 Subject: [PATCH 1/3] Implement Hanging Sign --- cmd/blockhash/main.go | 2 + server/block/hanging_sign.go | 248 +++++++++++++++++++++ server/block/hash.go | 5 + server/block/register.go | 2 + server/player/player.go | 39 +++- server/session/handler_block_actor_data.go | 22 +- 6 files changed, 300 insertions(+), 18 deletions(-) create mode 100644 server/block/hanging_sign.go diff --git a/cmd/blockhash/main.go b/cmd/blockhash/main.go index 25d90a133..b56279dad 100644 --- a/cmd/blockhash/main.go +++ b/cmd/blockhash/main.go @@ -258,6 +258,8 @@ func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[ return "uint64(" + s + ".Uint8())", 1 case "Direction", "Axis": return "uint64(" + s + ")", 2 + case "Orientation": + return "uint64(" + s + ")", 4 case "Face": return "uint64(" + s + ")", 3 default: diff --git a/server/block/hanging_sign.go b/server/block/hanging_sign.go new file mode 100644 index 000000000..609c5392b --- /dev/null +++ b/server/block/hanging_sign.go @@ -0,0 +1,248 @@ +package block + +import ( + "time" + + "github.com/df-mc/dragonfly/server/block/cube" + "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" +) + +// HangingSign is a decorative sign block that may be attached to the side of a block or hung from its underside. +type HangingSign struct { + transparent + empty + bass + sourceWaterDisplacer + + // Wood is the type of wood of the hanging sign. + Wood WoodType + // Facing is the horizontal facing of the hanging sign when it is not using the 16-direction ceiling variant. + Facing cube.Direction + // Orientation is the 16-direction rotation used by ceiling hanging signs with Attached set to true. + Orientation cube.Orientation + // Hanging specifies if the sign is hung from the underside of a block. If false, it is mounted to the side of a block. + Hanging bool + // Attached specifies if a ceiling-hanging sign uses the attached chain variant. + Attached bool + // Waxed specifies if the sign can no longer be edited. + Waxed bool + // Front is the text on the front side of the sign. + Front SignText + // Back is the text on the back side of the sign. + Back SignText +} + +// SideClosed reports that no face of a hanging sign fully closes off an adjacent block face. +func (h HangingSign) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// MaxCount returns the maximum number of hanging signs that may be stacked in one inventory slot. +func (h HangingSign) MaxCount() int { + return 16 +} + +// FlammabilityInfo returns the flammability properties of the hanging sign. +func (h HangingSign) FlammabilityInfo() FlammabilityInfo { + return newFlammabilityInfo(0, 0, true) +} + +// FuelInfo returns the furnace fuel properties of the hanging sign. +func (h HangingSign) FuelInfo() item.FuelInfo { + if !h.Wood.Flammable() { + return item.FuelInfo{} + } + return newFuelInfo(time.Second * 10) +} + +// EncodeItem encodes the hanging sign item name. +func (h HangingSign) EncodeItem() (name string, meta int16) { + return "minecraft:" + h.Wood.String() + "_hanging_sign", 0 +} + +// BreakInfo returns the breaking properties of the hanging sign. +func (h HangingSign) BreakInfo() BreakInfo { + return newBreakInfo(1, alwaysHarvestable, axeEffective, oneOf(HangingSign{Wood: h.Wood})) +} + +// Dye dyes the HangingSign, changing its base colour to that of the colour passed. +func (h HangingSign) Dye(pos cube.Pos, userPos mgl64.Vec3, c item.Colour) (world.Block, bool) { + if h.EditingFrontSide(pos, userPos) { + if h.Front.BaseColour == c.SignRGBA() { + return h, false + } + h.Front.BaseColour = c.SignRGBA() + } else { + if h.Back.BaseColour == c.SignRGBA() { + return h, false + } + h.Back.BaseColour = c.SignRGBA() + } + return h, true +} + +// Ink inks the sign either glowing or non-glowing. +func (h HangingSign) Ink(pos cube.Pos, userPos mgl64.Vec3, glowing bool) (world.Block, bool) { + if h.EditingFrontSide(pos, userPos) { + if h.Front.Glowing == glowing { + return h, false + } + h.Front.Glowing = glowing + } else { + if h.Back.Glowing == glowing { + return h, false + } + h.Back.Glowing = glowing + } + return h, true +} + +// Wax waxes a sign to prevent it from further editing. +func (h HangingSign) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { + if h.Waxed { + return h, false + } + h.Waxed = true + return h, true +} + +// Activate opens the sign editor when the hanging sign is editable or plays the waxed interaction sound otherwise. +func (h HangingSign) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool { + if editor, ok := u.(SignEditor); ok && !h.Waxed { + editor.OpenSign(pos, h.EditingFrontSide(pos, u.Position())) + } else if h.Waxed { + tx.PlaySound(pos.Vec3(), sound.WaxedSignFailedInteraction{}) + } + return true +} + +// EditingFrontSide returns if the user is editing the front side of the sign based on their position relative to the sign. +func (h HangingSign) EditingFrontSide(pos cube.Pos, userPos mgl64.Vec3) bool { + return userPos.Sub(pos.Vec3Centre()).Dot(h.rotation().Vec3()) > 0 +} + +// UseOnBlock places the hanging sign either on the side of a block or underneath a supporting block. +func (h HangingSign) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, h) + if !used || face == cube.FaceUp { + return false + } + switch face { + case cube.FaceDown: + supportPos := pos.Side(cube.FaceUp) + support := tx.Block(supportPos) + if !supportsCeilingHangingSign(support, supportPos, tx) { + return false + } + + h.Hanging = true + rotation := user.Rotation() + if supportsAttachedCeilingHangingSign(support) || sneaking(user) { + h.Attached = true + h.Orientation = rotation.Orientation().Opposite() + } else { + h.Facing = rotation.Direction().Opposite() + } + default: + h.Facing = face.Opposite().Direction().RotateRight() + } + + place(tx, pos, h, user, ctx) + if editor, ok := user.(SignEditor); ok { + editor.OpenSign(pos, true) + } + return placed(ctx) +} + +// NeighbourUpdateTick breaks the hanging sign when the block supporting it is no longer valid. +func (h HangingSign) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if h.Hanging { + supportPos := pos.Side(cube.FaceUp) + if !supportsCeilingHangingSign(tx.Block(supportPos), supportPos, tx) { + breakBlock(h, pos, tx) + } + return + } + + supportFace := h.Facing.RotateLeft().Face() + supportPos := pos.Side(supportFace) + if !tx.Block(supportPos).Model().FaceSolid(supportPos, supportFace.Opposite(), tx) { + breakBlock(h, pos, tx) + } +} + +// EncodeBlock encodes the Bedrock block state of the hanging sign. +func (h HangingSign) EncodeBlock() (name string, properties map[string]any) { + return "minecraft:" + h.Wood.String() + "_hanging_sign", map[string]any{ + "attached_bit": boolByte(h.Attached), + "facing_direction": int32(h.Facing.Face()), + "ground_sign_direction": int32(h.Orientation), + "hanging": boolByte(h.Hanging), + } +} + +// DecodeNBT decodes block actor data for the hanging sign using the same text format as regular signs. +func (h HangingSign) DecodeNBT(data map[string]any) any { + s := Sign{Front: h.Front, Back: h.Back, Waxed: h.Waxed} + s = s.DecodeNBT(data).(Sign) + h.Front, h.Back, h.Waxed = s.Front, s.Back, s.Waxed + return h +} + +// EncodeNBT encodes block actor data for the hanging sign. +func (h HangingSign) EncodeNBT() map[string]any { + nbt := Sign{Front: h.Front, Back: h.Back, Waxed: h.Waxed}.EncodeNBT() + nbt["id"] = "HangingSign" + return nbt +} + +// rotation returns the effective front-facing rotation of the hanging sign for side selection and editing. +func (h HangingSign) rotation() cube.Rotation { + if h.Hanging && h.Attached { + return StandingAttachment(h.Orientation).Rotation() + } + return WallAttachment(h.Facing).Rotation() +} + +// supportsCeilingHangingSign reports whether the block above can support a ceiling-hanging sign. +func supportsCeilingHangingSign(b world.Block, pos cube.Pos, tx *world.Tx) bool { + if supportsAttachedCeilingHangingSign(b) { + return true + } + return b.Model().FaceSolid(pos, cube.FaceDown, tx) +} + +// supportsAttachedCeilingHangingSign reports whether the block above supports the attached chain variant. +func supportsAttachedCeilingHangingSign(b world.Block) bool { + switch c := b.(type) { + case IronChain: + return c.Axis == cube.Y + case CopperChain: + return c.Axis == cube.Y + default: + return false + } +} + +// sneaking reports whether the user is currently sneaking. +func sneaking(u item.User) bool { + s, ok := u.(interface{ Sneaking() bool }) + return ok && s.Sneaking() +} + +// allHangingSigns returns all registered hanging sign permutations. +func allHangingSigns() (signs []world.Block) { + for _, w := range WoodTypes() { + for _, d := range cube.Directions() { + signs = append(signs, HangingSign{Wood: w, Facing: d}) + signs = append(signs, HangingSign{Wood: w, Facing: d, Hanging: true}) + } + for o := cube.Orientation(0); o <= 15; o++ { + signs = append(signs, HangingSign{Wood: w, Orientation: o, Hanging: true, Attached: true}) + } + } + return +} diff --git a/server/block/hash.go b/server/block/hash.go index 0d6223950..e403dbc0b 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -93,6 +93,7 @@ const ( hashGrass hashGravel hashGrindstone + hashHangingSign hashHayBale hashHoneycomb hashHopper @@ -560,6 +561,10 @@ func (g Grindstone) Hash() (uint64, uint64) { return hashGrindstone, uint64(g.Attach.Uint8()) | uint64(g.Facing)<<2 } +func (h HangingSign) Hash() (uint64, uint64) { + return hashHangingSign, uint64(h.Wood.Uint8()) | uint64(h.Facing)<<4 | uint64(h.Orientation)<<6 | uint64(boolByte(h.Hanging))<<10 | uint64(boolByte(h.Attached))<<11 +} + func (h HayBale) Hash() (uint64, uint64) { return hashHayBale, uint64(h.Axis) } diff --git a/server/block/register.go b/server/block/register.go index 5b54b0f45..3c774fd54 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -162,6 +162,7 @@ func init() { registerAll(allFurnaces()) registerAll(allGlazedTerracotta()) registerAll(allGrindstones()) + registerAll(allHangingSigns()) registerAll(allHayBales()) registerAll(allHoppers()) registerAll(allItemFrames()) @@ -415,6 +416,7 @@ func init() { } world.RegisterItem(Log{Wood: w, Stripped: true}) world.RegisterItem(Log{Wood: w}) + world.RegisterItem(HangingSign{Wood: w}) world.RegisterItem(Planks{Wood: w}) world.RegisterItem(Sign{Wood: w}) world.RegisterItem(WoodDoor{Wood: w}) diff --git a/server/player/player.go b/server/player/player.go index 8d53f4845..29222668e 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2886,34 +2886,49 @@ func (p *Player) OpenSign(pos cube.Pos, frontSide bool) { // EditSign edits the sign at the cube.Pos passed and writes the text passed to a sign at that position. If no sign is // present, an error is returned. func (p *Player) EditSign(pos cube.Pos, frontText, backText string) error { - sign, ok := p.tx.Block(pos).(block.Sign) - if !ok { + switch sign := p.tx.Block(pos).(type) { + case block.Sign: + return p.editSign(pos, frontText, backText, sign.Front, sign.Back, sign.Waxed, func(front, back block.SignText) world.Block { + sign.Front = front + sign.Back = back + return sign + }) + case block.HangingSign: + return p.editSign(pos, frontText, backText, sign.Front, sign.Back, sign.Waxed, func(front, back block.SignText) world.Block { + sign.Front = front + sign.Back = back + return sign + }) + default: return fmt.Errorf("edit sign: no sign at position %v", pos) } +} - if sign.Waxed { +// editSign applies validated sign text changes to either a regular sign or a hanging sign. +func (p *Player) editSign(pos cube.Pos, frontText, backText string, front, back block.SignText, waxed bool, update func(front, back block.SignText) world.Block) error { + if waxed { return nil - } else if frontText == sign.Front.Text && backText == sign.Back.Text { + } else if frontText == front.Text && backText == back.Text { return nil } ctx := event.C(p) - if frontText != sign.Front.Text { - if p.Handler().HandleSignEdit(ctx, pos, true, sign.Front.Text, frontText); ctx.Cancelled() { + if frontText != front.Text { + if p.Handler().HandleSignEdit(ctx, pos, true, front.Text, frontText); ctx.Cancelled() { p.resendNearbyBlock(pos) return nil } - sign.Front.Text = frontText - sign.Front.Owner = p.XUID() + front.Text = frontText + front.Owner = p.XUID() } else { - if p.Handler().HandleSignEdit(ctx, pos, false, sign.Back.Text, backText); ctx.Cancelled() { + if p.Handler().HandleSignEdit(ctx, pos, false, back.Text, backText); ctx.Cancelled() { p.resendNearbyBlock(pos) return nil } - sign.Back.Text = backText - sign.Back.Owner = p.XUID() + back.Text = backText + back.Owner = p.XUID() } - p.tx.SetBlock(pos, sign, nil) + p.tx.SetBlock(pos, update(front, back), nil) return nil } diff --git a/server/session/handler_block_actor_data.go b/server/session/handler_block_actor_data.go index ae0ea70e4..c8b4782c2 100644 --- a/server/session/handler_block_actor_data.go +++ b/server/session/handler_block_actor_data.go @@ -26,18 +26,28 @@ func (b BlockActorDataHandler) Handle(p packet.Packet, s *Session, tx *world.Tx, } switch id { case "Sign": - return b.handleSign(pk, pos, s, tx, c) + return b.handleSign(pk, pos, s, tx, c, false) + case "HangingSign": + return b.handleSign(pk, pos, s, tx, c, true) } return fmt.Errorf("unhandled block actor data ID %v", id) } return fmt.Errorf("block actor data without 'id' tag: %v", pk.NBTData) } -// handleSign handles the BlockActorData packet sent when editing a sign. -func (b BlockActorDataHandler) handleSign(pk *packet.BlockActorData, pos cube.Pos, s *Session, tx *world.Tx, co Controllable) error { - if _, ok := tx.Block(pos).(block.Sign); !ok { - s.conf.Log.Debug("no sign at position of sign block actor data", "pos", pos.String()) - return nil +// handleSign handles the BlockActorData packet sent when editing a sign and validates the expected sign variant. +func (b BlockActorDataHandler) handleSign(pk *packet.BlockActorData, pos cube.Pos, s *Session, tx *world.Tx, co Controllable, hanging bool) error { + bl := tx.Block(pos) + if hanging { + if _, ok := bl.(block.HangingSign); !ok { + s.conf.Log.Debug("no hanging sign at position of sign block actor data", "pos", pos.String()) + return nil + } + } else { + if _, ok := bl.(block.Sign); !ok { + s.conf.Log.Debug("no sign at position of sign block actor data", "pos", pos.String()) + return nil + } } frontText, err := b.textFromNBTData(pk.NBTData, true) From f913bdb89337d737ee138ec8dc1c65ef4acabdc8 Mon Sep 17 00:00:00 2001 From: AkmalFairuz Date: Sat, 21 Mar 2026 06:08:01 +0700 Subject: [PATCH 2/3] Refactor HangingSign structure to use HangingAttachment for improved mounting logic --- cmd/blockhash/main.go | 2 ++ server/block/hanging_attachment.go | 47 ++++++++++++++++++++++++++++++ server/block/hanging_sign.go | 46 ++++++++++------------------- server/block/hash.go | 2 +- 4 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 server/block/hanging_attachment.go diff --git a/cmd/blockhash/main.go b/cmd/blockhash/main.go index b56279dad..8867dd538 100644 --- a/cmd/blockhash/main.go +++ b/cmd/blockhash/main.go @@ -244,6 +244,8 @@ func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[ return "uint64(" + s + ".FaceUint8())", 3 } return "uint64(" + s + ".Uint8())", 5 + case "HangingAttachment": + return "uint64(" + s + ".Uint8())", 5 case "GrindstoneAttachment": return "uint64(" + s + ".Uint8())", 2 case "WoodType", "FlowerType", "DoubleFlowerType", "Colour": diff --git a/server/block/hanging_attachment.go b/server/block/hanging_attachment.go new file mode 100644 index 000000000..84017c402 --- /dev/null +++ b/server/block/hanging_attachment.go @@ -0,0 +1,47 @@ +package block + +import "github.com/df-mc/dragonfly/server/block/cube" + +// HangingAttachment describes how a hanging sign is attached. It may be mounted to the side of a block, hang +// underneath a block by chains, or be attached underneath a block with the 16-direction ceiling variant. +type HangingAttachment struct { + ceiling bool + attached bool + facing cube.Direction + o cube.Orientation +} + +// WallHangingAttachment returns a HangingAttachment for a hanging sign mounted to the side of a block. +func WallHangingAttachment(facing cube.Direction) HangingAttachment { + return HangingAttachment{facing: facing} +} + +// CeilingHangingAttachment returns a HangingAttachment for a hanging sign hanging underneath a block. +func CeilingHangingAttachment(facing cube.Direction) HangingAttachment { + return HangingAttachment{ceiling: true, facing: facing} +} + +// AttachedCeilingHangingAttachment returns a HangingAttachment for a hanging sign attached underneath a block using +// the 16-direction ceiling variant. +func AttachedCeilingHangingAttachment(o cube.Orientation) HangingAttachment { + return HangingAttachment{ceiling: true, attached: true, o: o} +} + +// Uint8 returns the HangingAttachment as a uint8. +func (a HangingAttachment) Uint8() uint8 { + if !a.ceiling { + return uint8(a.facing) + } + if !a.attached { + return 4 | uint8(a.facing) + } + return 8 | uint8(a.o) +} + +// Rotation returns the rotation of the HangingAttachment. +func (a HangingAttachment) Rotation() cube.Rotation { + if a.attached { + return cube.Rotation{a.o.Yaw()} + } + return WallAttachment(a.facing).Rotation() +} diff --git a/server/block/hanging_sign.go b/server/block/hanging_sign.go index 609c5392b..b14c5b0af 100644 --- a/server/block/hanging_sign.go +++ b/server/block/hanging_sign.go @@ -19,14 +19,8 @@ type HangingSign struct { // Wood is the type of wood of the hanging sign. Wood WoodType - // Facing is the horizontal facing of the hanging sign when it is not using the 16-direction ceiling variant. - Facing cube.Direction - // Orientation is the 16-direction rotation used by ceiling hanging signs with Attached set to true. - Orientation cube.Orientation - // Hanging specifies if the sign is hung from the underside of a block. If false, it is mounted to the side of a block. - Hanging bool - // Attached specifies if a ceiling-hanging sign uses the attached chain variant. - Attached bool + // Attach describes how the hanging sign is mounted. + Attach HangingAttachment // Waxed specifies if the sign can no longer be edited. Waxed bool // Front is the text on the front side of the sign. @@ -121,7 +115,7 @@ func (h HangingSign) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.Us // EditingFrontSide returns if the user is editing the front side of the sign based on their position relative to the sign. func (h HangingSign) EditingFrontSide(pos cube.Pos, userPos mgl64.Vec3) bool { - return userPos.Sub(pos.Vec3Centre()).Dot(h.rotation().Vec3()) > 0 + return userPos.Sub(pos.Vec3Centre()).Dot(h.Attach.Rotation().Vec3()) > 0 } // UseOnBlock places the hanging sign either on the side of a block or underneath a supporting block. @@ -138,16 +132,14 @@ func (h HangingSign) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx * return false } - h.Hanging = true rotation := user.Rotation() if supportsAttachedCeilingHangingSign(support) || sneaking(user) { - h.Attached = true - h.Orientation = rotation.Orientation().Opposite() + h.Attach = AttachedCeilingHangingAttachment(rotation.Orientation().Opposite()) } else { - h.Facing = rotation.Direction().Opposite() + h.Attach = CeilingHangingAttachment(rotation.Direction().Opposite()) } default: - h.Facing = face.Opposite().Direction().RotateRight() + h.Attach = WallHangingAttachment(face.Opposite().Direction().RotateRight()) } place(tx, pos, h, user, ctx) @@ -159,7 +151,7 @@ func (h HangingSign) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx * // NeighbourUpdateTick breaks the hanging sign when the block supporting it is no longer valid. func (h HangingSign) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { - if h.Hanging { + if h.Attach.ceiling { supportPos := pos.Side(cube.FaceUp) if !supportsCeilingHangingSign(tx.Block(supportPos), supportPos, tx) { breakBlock(h, pos, tx) @@ -167,7 +159,7 @@ func (h HangingSign) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { return } - supportFace := h.Facing.RotateLeft().Face() + supportFace := h.Attach.facing.RotateLeft().Face() supportPos := pos.Side(supportFace) if !tx.Block(supportPos).Model().FaceSolid(supportPos, supportFace.Opposite(), tx) { breakBlock(h, pos, tx) @@ -177,10 +169,10 @@ func (h HangingSign) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { // EncodeBlock encodes the Bedrock block state of the hanging sign. func (h HangingSign) EncodeBlock() (name string, properties map[string]any) { return "minecraft:" + h.Wood.String() + "_hanging_sign", map[string]any{ - "attached_bit": boolByte(h.Attached), - "facing_direction": int32(h.Facing.Face()), - "ground_sign_direction": int32(h.Orientation), - "hanging": boolByte(h.Hanging), + "attached_bit": boolByte(h.Attach.attached), + "facing_direction": int32(h.Attach.facing.Face()), + "ground_sign_direction": int32(h.Attach.o), + "hanging": boolByte(h.Attach.ceiling), } } @@ -199,14 +191,6 @@ func (h HangingSign) EncodeNBT() map[string]any { return nbt } -// rotation returns the effective front-facing rotation of the hanging sign for side selection and editing. -func (h HangingSign) rotation() cube.Rotation { - if h.Hanging && h.Attached { - return StandingAttachment(h.Orientation).Rotation() - } - return WallAttachment(h.Facing).Rotation() -} - // supportsCeilingHangingSign reports whether the block above can support a ceiling-hanging sign. func supportsCeilingHangingSign(b world.Block, pos cube.Pos, tx *world.Tx) bool { if supportsAttachedCeilingHangingSign(b) { @@ -237,11 +221,11 @@ func sneaking(u item.User) bool { func allHangingSigns() (signs []world.Block) { for _, w := range WoodTypes() { for _, d := range cube.Directions() { - signs = append(signs, HangingSign{Wood: w, Facing: d}) - signs = append(signs, HangingSign{Wood: w, Facing: d, Hanging: true}) + signs = append(signs, HangingSign{Wood: w, Attach: WallHangingAttachment(d)}) + signs = append(signs, HangingSign{Wood: w, Attach: CeilingHangingAttachment(d)}) } for o := cube.Orientation(0); o <= 15; o++ { - signs = append(signs, HangingSign{Wood: w, Orientation: o, Hanging: true, Attached: true}) + signs = append(signs, HangingSign{Wood: w, Attach: AttachedCeilingHangingAttachment(o)}) } } return diff --git a/server/block/hash.go b/server/block/hash.go index e403dbc0b..8c8879627 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -562,7 +562,7 @@ func (g Grindstone) Hash() (uint64, uint64) { } func (h HangingSign) Hash() (uint64, uint64) { - return hashHangingSign, uint64(h.Wood.Uint8()) | uint64(h.Facing)<<4 | uint64(h.Orientation)<<6 | uint64(boolByte(h.Hanging))<<10 | uint64(boolByte(h.Attached))<<11 + return hashHangingSign, uint64(h.Wood.Uint8()) | uint64(h.Attach.Uint8())<<4 } func (h HayBale) Hash() (uint64, uint64) { From 30e0319d924ec976c6d35dcec58b997f7d1e5663 Mon Sep 17 00:00:00 2001 From: AkmalFairuz Date: Sat, 21 Mar 2026 06:20:36 +0700 Subject: [PATCH 3/3] Refactor EncodeBlock method in HangingSign to improve direction handling --- cmd/blockhash/main.go | 2 -- server/block/hanging_sign.go | 10 ++++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cmd/blockhash/main.go b/cmd/blockhash/main.go index 8867dd538..59ab178e1 100644 --- a/cmd/blockhash/main.go +++ b/cmd/blockhash/main.go @@ -260,8 +260,6 @@ func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[ return "uint64(" + s + ".Uint8())", 1 case "Direction", "Axis": return "uint64(" + s + ")", 2 - case "Orientation": - return "uint64(" + s + ")", 4 case "Face": return "uint64(" + s + ")", 3 default: diff --git a/server/block/hanging_sign.go b/server/block/hanging_sign.go index b14c5b0af..83a9f81ef 100644 --- a/server/block/hanging_sign.go +++ b/server/block/hanging_sign.go @@ -168,10 +168,16 @@ func (h HangingSign) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { // EncodeBlock encodes the Bedrock block state of the hanging sign. func (h HangingSign) EncodeBlock() (name string, properties map[string]any) { + var facing, ground int32 + if h.Attach.attached { + ground = int32(h.Attach.o) + } else { + facing = int32(h.Attach.facing.Face()) + } return "minecraft:" + h.Wood.String() + "_hanging_sign", map[string]any{ "attached_bit": boolByte(h.Attach.attached), - "facing_direction": int32(h.Attach.facing.Face()), - "ground_sign_direction": int32(h.Attach.o), + "facing_direction": facing, + "ground_sign_direction": ground, "hanging": boolByte(h.Attach.ceiling), } }