github.com/df-mc/dragonfly@v0.9.13/server/block/wall.go (about)

     1  package block
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/df-mc/dragonfly/server/block/cube"
     6  	"github.com/df-mc/dragonfly/server/block/model"
     7  	"github.com/df-mc/dragonfly/server/internal/sliceutil"
     8  	"github.com/df-mc/dragonfly/server/item"
     9  	"github.com/df-mc/dragonfly/server/world"
    10  	"github.com/go-gl/mathgl/mgl64"
    11  	"math"
    12  )
    13  
    14  // Wall is a block similar to fences that prevents players from jumping over and is thinner than the usual block. It is
    15  // available for many blocks and all types connect together as if they were the same type.
    16  type Wall struct {
    17  	transparent
    18  	sourceWaterDisplacer
    19  
    20  	// Block is the block to use for the type of wall.
    21  	Block world.Block
    22  	// NorthConnection is the type of connection in the north direction of the post.
    23  	NorthConnection WallConnectionType
    24  	// EastConnection is the type of connection in the east direction of the post.
    25  	EastConnection WallConnectionType
    26  	// SouthConnection is the type of connection in the south direction of the post.
    27  	SouthConnection WallConnectionType
    28  	// WestConnection is the type of connection in the west direction of the post.
    29  	WestConnection WallConnectionType
    30  	// Post is if the wall is extended to the full height of a block or not.
    31  	Post bool
    32  }
    33  
    34  // EncodeItem ...
    35  func (w Wall) EncodeItem() (string, int16) {
    36  	name, meta := encodeWallBlock(w.Block)
    37  	if meta == 0 {
    38  		return "minecraft:" + name + "_wall", 0
    39  	}
    40  	return "minecraft:cobblestone_wall", meta
    41  }
    42  
    43  // EncodeBlock ...
    44  func (w Wall) EncodeBlock() (string, map[string]any) {
    45  	properties := map[string]any{
    46  		"wall_connection_type_north": w.NorthConnection.String(),
    47  		"wall_connection_type_east":  w.EastConnection.String(),
    48  		"wall_connection_type_south": w.SouthConnection.String(),
    49  		"wall_connection_type_west":  w.WestConnection.String(),
    50  		"wall_post_bit":              boolByte(w.Post),
    51  	}
    52  	name, meta := encodeWallBlock(w.Block)
    53  	if meta > 0 || name == "cobblestone" {
    54  		properties["wall_block_type"] = name
    55  		name = "cobblestone"
    56  	}
    57  	return "minecraft:" + name + "_wall", properties
    58  }
    59  
    60  // Model ...
    61  func (w Wall) Model() world.BlockModel {
    62  	return model.Wall{
    63  		NorthConnection: w.NorthConnection.Height(),
    64  		EastConnection:  w.EastConnection.Height(),
    65  		SouthConnection: w.SouthConnection.Height(),
    66  		WestConnection:  w.WestConnection.Height(),
    67  		Post:            w.Post,
    68  	}
    69  }
    70  
    71  // BreakInfo ...
    72  func (w Wall) BreakInfo() BreakInfo {
    73  	breakable, ok := w.Block.(Breakable)
    74  	if !ok {
    75  		panic("wall block is not breakable")
    76  	}
    77  	blastResistance := breakable.BreakInfo().BlastResistance
    78  	switch w.Block.(type) {
    79  	case MudBricks:
    80  		blastResistance = 30
    81  	}
    82  	return newBreakInfo(breakable.BreakInfo().Hardness, pickaxeHarvestable, pickaxeEffective, oneOf(w)).withBlastResistance(blastResistance)
    83  }
    84  
    85  // NeighbourUpdateTick ...
    86  func (w Wall) NeighbourUpdateTick(pos, _ cube.Pos, wo *world.World) {
    87  	w, connectionsUpdated := w.calculateConnections(wo, pos)
    88  	w, postUpdated := w.calculatePost(wo, pos)
    89  	if connectionsUpdated || postUpdated {
    90  		wo.SetBlock(pos, w, nil)
    91  	}
    92  }
    93  
    94  // UseOnBlock ...
    95  func (w Wall) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, wo *world.World, user item.User, ctx *item.UseContext) (used bool) {
    96  	pos, _, used = firstReplaceable(wo, pos, face, w)
    97  	if !used {
    98  		return
    99  	}
   100  	w, _ = w.calculateConnections(wo, pos)
   101  	w, _ = w.calculatePost(wo, pos)
   102  	place(wo, pos, w, user, ctx)
   103  	return placed(ctx)
   104  }
   105  
   106  // SideClosed ...
   107  func (Wall) SideClosed(cube.Pos, cube.Pos, *world.World) bool {
   108  	return false
   109  }
   110  
   111  // ConnectionType returns the connection type of the wall in the given direction.
   112  func (w Wall) ConnectionType(direction cube.Direction) WallConnectionType {
   113  	switch direction {
   114  	case cube.North:
   115  		return w.NorthConnection
   116  	case cube.East:
   117  		return w.EastConnection
   118  	case cube.South:
   119  		return w.SouthConnection
   120  	case cube.West:
   121  		return w.WestConnection
   122  	}
   123  	panic("unknown direction")
   124  }
   125  
   126  // WithConnectionType returns the wall with the given connection type in the given direction.
   127  func (w Wall) WithConnectionType(direction cube.Direction, connection WallConnectionType) Wall {
   128  	switch direction {
   129  	case cube.North:
   130  		w.NorthConnection = connection
   131  	case cube.East:
   132  		w.EastConnection = connection
   133  	case cube.South:
   134  		w.SouthConnection = connection
   135  	case cube.West:
   136  		w.WestConnection = connection
   137  	}
   138  	return w
   139  }
   140  
   141  // calculateConnections calculates the correct connections for the wall at a given position in a world. The updated wall
   142  // is returned and a bool to determine if any changes were made.
   143  func (w Wall) calculateConnections(wo *world.World, pos cube.Pos) (Wall, bool) {
   144  	var updated bool
   145  	abovePos := pos.Add(cube.Pos{0, 1, 0})
   146  	above := wo.Block(abovePos)
   147  	for _, face := range cube.HorizontalFaces() {
   148  		sidePos := pos.Side(face)
   149  		side := wo.Block(sidePos)
   150  		// A wall can only connect to a block if the side is solid, with the only exception being thin blocks (such as
   151  		// glass panes and iron bars) as well as the sides of fence gates.
   152  		connected := side.Model().FaceSolid(sidePos, face.Opposite(), wo)
   153  		if !connected {
   154  			if _, ok := wo.Block(sidePos).(Wall); ok {
   155  				connected = true
   156  			} else if gate, ok := wo.Block(sidePos).(WoodFenceGate); ok {
   157  				connected = gate.Facing.Face().Axis() != face.Axis()
   158  			} else if _, ok := wo.Block(sidePos).Model().(model.Thin); ok {
   159  				connected = true
   160  			}
   161  		}
   162  		var connectionType WallConnectionType
   163  		if connected {
   164  			// If the wall is connected to the side, it has the possibility of having a tall connection. This is
   165  			//calculated by checking for any overlapping blocks in the area of the connection.
   166  			connectionType = ShortWallConnection()
   167  			boxes := above.Model().BBox(abovePos, wo)
   168  			for _, bb := range boxes {
   169  				if bb.Min().Y() == 0 {
   170  					xOverlap := bb.Min().X() < 0.75 && bb.Max().X() > 0.25
   171  					zOverlap := bb.Min().Z() < 0.75 && bb.Max().Z() > 0.25
   172  					var tall bool
   173  					switch face {
   174  					case cube.FaceNorth:
   175  						tall = xOverlap && bb.Max().Z() > 0.75
   176  					case cube.FaceEast:
   177  						tall = bb.Min().X() < 0.25 && zOverlap
   178  					case cube.FaceSouth:
   179  						tall = xOverlap && bb.Min().Z() < 0.25
   180  					case cube.FaceWest:
   181  						tall = bb.Max().X() > 0.75 && zOverlap
   182  					}
   183  					if tall {
   184  						connectionType = TallWallConnection()
   185  						break
   186  					}
   187  				}
   188  			}
   189  
   190  		}
   191  		if w.ConnectionType(face.Direction()) != connectionType {
   192  			updated = true
   193  			w = w.WithConnectionType(face.Direction(), connectionType)
   194  		}
   195  	}
   196  	return w, updated
   197  }
   198  
   199  // calculatePost calculates the correct post bit for the wall at a given position in a world. The updated wall is
   200  // returned and a bool to determine if any changes were made.
   201  func (w Wall) calculatePost(wo *world.World, pos cube.Pos) (Wall, bool) {
   202  	var updated bool
   203  	abovePos := pos.Add(cube.Pos{0, 1, 0})
   204  	above := wo.Block(abovePos)
   205  	connections := len(sliceutil.Filter(cube.HorizontalFaces(), func(face cube.Face) bool {
   206  		return w.ConnectionType(face.Direction()) != NoWallConnection()
   207  	}))
   208  	var post bool
   209  	switch above := above.(type) {
   210  	case Lantern:
   211  		// Lanterns only make a wall become a post when placed on the wall and not hanging from above.
   212  		post = !above.Hanging
   213  	case Sign:
   214  		// Signs only make a wall become a post when placed on the wall and not placed on the side of a block.
   215  		post = !above.Attach.hanging
   216  	case Torch:
   217  		// Torches only make a wall become a post when placed on the wall and not placed on the side of a block.
   218  		post = above.Facing == cube.FaceDown
   219  	case WoodTrapdoor:
   220  		// Trapdoors only make a wall become a post when they are opened and not closed and above a connection.
   221  		if above.Open {
   222  			switch above.Facing {
   223  			case cube.North:
   224  				post = w.NorthConnection != NoWallConnection()
   225  			case cube.East:
   226  				post = w.EastConnection != NoWallConnection()
   227  			case cube.South:
   228  				post = w.SouthConnection != NoWallConnection()
   229  			case cube.West:
   230  				post = w.WestConnection != NoWallConnection()
   231  			}
   232  		}
   233  	case Wall:
   234  		// A wall only make a wall become a post if it is a post itself.
   235  		post = above.Post
   236  	}
   237  	if !post {
   238  		// If a wall has two connections that are in different axis then it becomes a post regardless of the above block.
   239  		post = connections < 2
   240  		if connections >= 2 {
   241  			if w.NorthConnection != NoWallConnection() && w.SouthConnection != NoWallConnection() {
   242  				post = w.EastConnection != NoWallConnection() || w.WestConnection != NoWallConnection()
   243  			} else if w.EastConnection != NoWallConnection() && w.WestConnection != NoWallConnection() {
   244  				post = w.NorthConnection != NoWallConnection() || w.SouthConnection != NoWallConnection()
   245  			} else {
   246  				post = true
   247  			}
   248  		}
   249  	}
   250  	if w.Post != post {
   251  		updated = true
   252  		w.Post = post
   253  	}
   254  	return w, updated
   255  }
   256  
   257  // allWalls returns a list of all wall types.
   258  func allWalls() (walls []world.Block) {
   259  	for _, block := range WallBlocks() {
   260  		if block.Hash() > math.MaxUint16 {
   261  			name, _ := block.EncodeBlock()
   262  			panic(fmt.Errorf("hash of block %s exceeds 16 bytes", name))
   263  		}
   264  		for _, north := range WallConnectionTypes() {
   265  			for _, east := range WallConnectionTypes() {
   266  				for _, south := range WallConnectionTypes() {
   267  					for _, west := range WallConnectionTypes() {
   268  						walls = append(walls, Wall{
   269  							Block:           block,
   270  							NorthConnection: north,
   271  							EastConnection:  east,
   272  							SouthConnection: south,
   273  							WestConnection:  west,
   274  							Post:            false,
   275  						})
   276  						walls = append(walls, Wall{
   277  							Block:           block,
   278  							NorthConnection: north,
   279  							EastConnection:  east,
   280  							SouthConnection: south,
   281  							WestConnection:  west,
   282  							Post:            true,
   283  						})
   284  					}
   285  				}
   286  			}
   287  		}
   288  	}
   289  	return
   290  }