Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/blockhash/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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", "LeavesType", "FlowerType", "DoubleFlowerType", "Colour":
Expand Down
47 changes: 47 additions & 0 deletions server/block/hanging_attachment.go
Original file line number Diff line number Diff line change
@@ -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()
}
238 changes: 238 additions & 0 deletions server/block/hanging_sign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
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
// 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.
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.Attach.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
}

rotation := user.Rotation()
if supportsAttachedCeilingHangingSign(support) || sneaking(user) {
h.Attach = AttachedCeilingHangingAttachment(rotation.Orientation().Opposite())
} else {
h.Attach = CeilingHangingAttachment(rotation.Direction().Opposite())
}
default:
h.Attach = WallHangingAttachment(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.Attach.ceiling {
supportPos := pos.Side(cube.FaceUp)
if !supportsCeilingHangingSign(tx.Block(supportPos), supportPos, tx) {
breakBlock(h, pos, tx)
}
return
}

supportFace := h.Attach.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) {
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": facing,
"ground_sign_direction": ground,
"hanging": boolByte(h.Attach.ceiling),
}
}

// 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
}

// 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, 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, Attach: AttachedCeilingHangingAttachment(o)})
}
}
return
}
5 changes: 5 additions & 0 deletions server/block/hash.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions server/block/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ func init() {
registerAll(allFurnaces())
registerAll(allGlazedTerracotta())
registerAll(allGrindstones())
registerAll(allHangingSigns())
registerAll(allHayBales())
registerAll(allHoppers())
registerAll(allItemFrames())
Expand Down Expand Up @@ -418,6 +419,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})
Expand Down
39 changes: 27 additions & 12 deletions server/player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -2914,34 +2914,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
}

Expand Down
Loading
Loading