github.com/df-mc/dragonfly@v0.9.13/server/block/sponge.go (about) 1 package block 2 3 import ( 4 "github.com/df-mc/dragonfly/server/block/cube" 5 "github.com/df-mc/dragonfly/server/item" 6 "github.com/df-mc/dragonfly/server/world" 7 "github.com/df-mc/dragonfly/server/world/particle" 8 "github.com/go-gl/mathgl/mgl64" 9 ) 10 11 // Sponge is a block that can be used to remove water around itself when placed, turning into a wet sponge in the 12 // process. 13 type Sponge struct { 14 solid 15 16 // Wet specifies whether the dry or the wet variant of the block is used. 17 Wet bool 18 } 19 20 // BreakInfo ... 21 func (s Sponge) BreakInfo() BreakInfo { 22 return newBreakInfo(0.6, alwaysHarvestable, nothingEffective, oneOf(s)) 23 } 24 25 // SmeltInfo ... 26 func (s Sponge) SmeltInfo() item.SmeltInfo { 27 if s.Wet { 28 return newSmeltInfo(item.NewStack(Sponge{}, 1), 0.15) 29 } 30 return item.SmeltInfo{} 31 } 32 33 // EncodeItem ... 34 func (s Sponge) EncodeItem() (name string, meta int16) { 35 if s.Wet { 36 meta = 1 37 } 38 return "minecraft:sponge", meta 39 } 40 41 // EncodeBlock ... 42 func (s Sponge) EncodeBlock() (string, map[string]any) { 43 if s.Wet { 44 return "minecraft:sponge", map[string]any{"sponge_type": "wet"} 45 } 46 return "minecraft:sponge", map[string]any{"sponge_type": "dry"} 47 } 48 49 // UseOnBlock places the sponge, absorbs nearby water if it's still dry and flags it as wet if any water has been 50 // absorbed. 51 func (s Sponge) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { 52 var particles = false 53 pos, _, used = firstReplaceable(w, pos, face, s) 54 if !used { 55 return 56 } 57 58 // Check if the Sponge is placed in the Nether and if so, turn it into a normal Sponge instantly. 59 if w.Dimension().WaterEvaporates() && s.Wet { 60 s.Wet = false 61 particles = true 62 } 63 64 place(w, pos, s, user, ctx) 65 if particles && placed(ctx) { 66 w.AddParticle(pos.Side(cube.FaceUp).Vec3(), particle.Evaporate{}) 67 } 68 return placed(ctx) 69 } 70 71 // NeighbourUpdateTick checks for nearby water flow. If water could be found and the sponge is dry, it will absorb the 72 // water and be flagged as wet. 73 func (s Sponge) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { 74 // The sponge is dry, so it can absorb nearby water. 75 if !s.Wet { 76 if s.absorbWater(pos, w) > 0 { 77 // Water has been absorbed, so we flag the sponge as wet. 78 s.setWet(pos, w) 79 } 80 } 81 } 82 83 // setWet flags a sponge as wet. It replaces the block at pos by a wet sponge block and displays a block break 84 // particle at the sponge's position with an offset of 0.5 on each axis. 85 func (s Sponge) setWet(pos cube.Pos, w *world.World) { 86 s.Wet = true 87 w.SetBlock(pos, s, nil) 88 w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: Water{Depth: 1}}) 89 } 90 91 // absorbWater replaces water blocks near the sponge by air out to a taxicab geometry of 7 in all directions. 92 // The maximum for absorbed blocks is 65. 93 // The returned int specifies the amount of replaced water blocks. 94 func (s Sponge) absorbWater(pos cube.Pos, w *world.World) int { 95 // distanceToSponge binds a world.Pos to its distance from the sponge's position. 96 type distanceToSponge struct { 97 block cube.Pos 98 distance int32 99 } 100 101 queue := make([]distanceToSponge, 0) 102 queue = append(queue, distanceToSponge{pos, 0}) 103 104 // A sponge can only absorb up to 65 water blocks. 105 replaced := 0 106 for replaced < 65 { 107 if len(queue) == 0 { 108 break 109 } 110 111 // Pop the next distanceToSponge entry from the queue. 112 next := queue[0] 113 queue = queue[1:] 114 115 next.block.Neighbours(func(neighbour cube.Pos) { 116 liquid, found := w.Liquid(neighbour) 117 if found { 118 if _, isWater := liquid.(Water); isWater { 119 w.SetLiquid(neighbour, nil) 120 replaced++ 121 if next.distance < 7 { 122 queue = append(queue, distanceToSponge{neighbour, next.distance + 1}) 123 } 124 } 125 } 126 }, w.Range()) 127 } 128 129 return replaced 130 }