Files
mc/server/block/wall.go
T

281 lines
9.0 KiB
Go
Raw Normal View History

2026-07-09 08:33:57 +08:00
package block
import (
"fmt"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/block/model"
"github.com/df-mc/dragonfly/server/internal/sliceutil"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
"math"
)
// Wall is a block similar to fences that prevents players from jumping over and is thinner than the usual block. It is
// available for many blocks and all types connect together as if they were the same type.
type Wall struct {
transparent
sourceWaterDisplacer
// Block is the block to use for the type of wall.
Block world.Block
// NorthConnection is the type of connection in the north direction of the post.
NorthConnection WallConnectionType
// EastConnection is the type of connection in the east direction of the post.
EastConnection WallConnectionType
// SouthConnection is the type of connection in the south direction of the post.
SouthConnection WallConnectionType
// WestConnection is the type of connection in the west direction of the post.
WestConnection WallConnectionType
// Post is if the wall is extended to the full height of a block or not.
Post bool
}
// EncodeItem ...
func (w Wall) EncodeItem() (string, int16) {
name := encodeWallBlock(w.Block)
return "minecraft:" + name + "_wall", 0
}
// EncodeBlock ...
func (w Wall) EncodeBlock() (string, map[string]any) {
properties := map[string]any{
"wall_connection_type_north": w.NorthConnection.String(),
"wall_connection_type_east": w.EastConnection.String(),
"wall_connection_type_south": w.SouthConnection.String(),
"wall_connection_type_west": w.WestConnection.String(),
"wall_post_bit": boolByte(w.Post),
}
name := encodeWallBlock(w.Block)
return "minecraft:" + name + "_wall", properties
}
// Model ...
func (w Wall) Model() world.BlockModel {
return model.Wall{
NorthConnection: w.NorthConnection.Height(),
EastConnection: w.EastConnection.Height(),
SouthConnection: w.SouthConnection.Height(),
WestConnection: w.WestConnection.Height(),
Post: w.Post,
}
}
// BreakInfo ...
func (w Wall) BreakInfo() BreakInfo {
breakable, ok := w.Block.(Breakable)
if !ok {
panic("wall block is not breakable")
}
return newBreakInfo(breakable.BreakInfo().Hardness, pickaxeHarvestable, pickaxeEffective, oneOf(w)).withBlastResistance(breakable.BreakInfo().BlastResistance)
}
// NeighbourUpdateTick ...
func (w Wall) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) {
w, connectionsUpdated := w.calculateConnections(tx, pos)
w, postUpdated := w.calculatePost(tx, pos)
if connectionsUpdated || postUpdated {
tx.SetBlock(pos, w, nil)
}
}
// UseOnBlock ...
func (w Wall) 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, w)
if !used {
return
}
w, _ = w.calculateConnections(tx, pos)
w, _ = w.calculatePost(tx, pos)
place(tx, pos, w, user, ctx)
return placed(ctx)
}
// SideClosed ...
func (Wall) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool {
return false
}
// ConnectionType returns the connection type of the wall in the given direction.
func (w Wall) ConnectionType(direction cube.Direction) WallConnectionType {
switch direction {
case cube.North:
return w.NorthConnection
case cube.East:
return w.EastConnection
case cube.South:
return w.SouthConnection
case cube.West:
return w.WestConnection
}
panic("unknown direction")
}
// WithConnectionType returns the wall with the given connection type in the given direction.
func (w Wall) WithConnectionType(direction cube.Direction, connection WallConnectionType) Wall {
switch direction {
case cube.North:
w.NorthConnection = connection
case cube.East:
w.EastConnection = connection
case cube.South:
w.SouthConnection = connection
case cube.West:
w.WestConnection = connection
}
return w
}
// calculateConnections calculates the correct connections for the wall at a given position in a world. The updated wall
// is returned and a bool to determine if any changes were made.
func (w Wall) calculateConnections(tx *world.Tx, pos cube.Pos) (Wall, bool) {
var updated bool
abovePos := pos.Add(cube.Pos{0, 1, 0})
above := tx.Block(abovePos)
for _, face := range cube.HorizontalFaces() {
sidePos := pos.Side(face)
side := tx.Block(sidePos)
// A wall can only connect to a block if the side is solid, with the only exception being thin blocks (such as
// glass panes and iron bars) as well as the sides of fence gates.
connected := side.Model().FaceSolid(sidePos, face.Opposite(), tx)
if !connected {
if _, ok := tx.Block(sidePos).(Wall); ok {
connected = true
} else if gate, ok := tx.Block(sidePos).(WoodFenceGate); ok {
connected = gate.Facing.Face().Axis() != face.Axis()
} else if _, ok := tx.Block(sidePos).Model().(model.Thin); ok {
connected = true
}
}
var connectionType WallConnectionType
if connected {
// If the wall is connected to the side, it has the possibility of having a tall connection. This is
// calculated by checking for any overlapping blocks in the area of the connection.
connectionType = ShortWallConnection()
boxes := above.Model().BBox(abovePos, tx)
for _, bb := range boxes {
if bb.Min().Y() == 0 {
xOverlap := bb.Min().X() < 0.75 && bb.Max().X() > 0.25
zOverlap := bb.Min().Z() < 0.75 && bb.Max().Z() > 0.25
var tall bool
switch face {
case cube.FaceNorth:
tall = xOverlap && bb.Max().Z() > 0.75
case cube.FaceEast:
tall = bb.Min().X() < 0.25 && zOverlap
case cube.FaceSouth:
tall = xOverlap && bb.Min().Z() < 0.25
case cube.FaceWest:
tall = bb.Max().X() > 0.75 && zOverlap
}
if tall {
connectionType = TallWallConnection()
break
}
}
}
}
if w.ConnectionType(face.Direction()) != connectionType {
updated = true
w = w.WithConnectionType(face.Direction(), connectionType)
}
}
return w, updated
}
// calculatePost calculates the correct post bit for the wall at a given position in a world. The updated wall is
// returned and a bool to determine if any changes were made.
func (w Wall) calculatePost(tx *world.Tx, pos cube.Pos) (Wall, bool) {
var updated bool
abovePos := pos.Add(cube.Pos{0, 1, 0})
above := tx.Block(abovePos)
connections := len(sliceutil.Filter(cube.HorizontalFaces(), func(face cube.Face) bool {
return w.ConnectionType(face.Direction()) != NoWallConnection()
}))
var post bool
switch above := above.(type) {
case Lantern:
// Lanterns only make a wall become a post when placed on the wall and not hanging from above.
post = !above.Hanging
case Sign:
// Signs only make a wall become a post when placed on the wall and not placed on the side of a block.
post = !above.Attach.hanging
case Torch:
// Torches only make a wall become a post when placed on the wall and not placed on the side of a block.
post = above.Facing == cube.FaceDown
case WoodTrapdoor:
// Trapdoors only make a wall become a post when they are opened and not closed and above a connection.
if above.Open {
switch above.Facing {
case cube.North:
post = w.NorthConnection != NoWallConnection()
case cube.East:
post = w.EastConnection != NoWallConnection()
case cube.South:
post = w.SouthConnection != NoWallConnection()
case cube.West:
post = w.WestConnection != NoWallConnection()
}
}
case Wall:
// A wall only make a wall become a post if it is a post itself.
post = above.Post
}
if !post {
// If a wall has two connections that are in different axis then it becomes a post regardless of the above block.
if connections < 2 {
post = true
} else {
switch {
case w.NorthConnection != NoWallConnection() && w.SouthConnection != NoWallConnection():
post = w.EastConnection != NoWallConnection() || w.WestConnection != NoWallConnection()
case w.EastConnection != NoWallConnection() && w.WestConnection != NoWallConnection():
post = w.NorthConnection != NoWallConnection() || w.SouthConnection != NoWallConnection()
default:
post = true
}
}
}
if w.Post != post {
updated = true
w.Post = post
}
return w, updated
}
// allWalls returns a list of all wall types.
func allWalls() (walls []world.Block) {
for _, block := range WallBlocks() {
if _, hash := block.Hash(); hash > math.MaxUint16 {
name, _ := block.EncodeBlock()
panic(fmt.Errorf("hash of block %s exceeds 16 bytes", name))
}
for _, north := range WallConnectionTypes() {
for _, east := range WallConnectionTypes() {
for _, south := range WallConnectionTypes() {
for _, west := range WallConnectionTypes() {
walls = append(walls, Wall{
Block: block,
NorthConnection: north,
EastConnection: east,
SouthConnection: south,
WestConnection: west,
Post: false,
})
walls = append(walls, Wall{
Block: block,
NorthConnection: north,
EastConnection: east,
SouthConnection: south,
WestConnection: west,
Post: true,
})
}
}
}
}
}
return
}