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 }