github.com/df-mc/dragonfly@v0.9.13/server/block/fire.go (about) 1 package block 2 3 //lint:file-ignore ST1022 Exported variables in this package have compiler directives. These variables are not otherwise exposed to users. 4 5 import ( 6 "github.com/df-mc/dragonfly/server/block/cube" 7 "github.com/df-mc/dragonfly/server/event" 8 "github.com/df-mc/dragonfly/server/item" 9 "github.com/df-mc/dragonfly/server/item/enchantment" 10 "github.com/df-mc/dragonfly/server/world" 11 "math/rand" 12 "time" 13 ) 14 15 // Fire is a non-solid block that can spread to nearby flammable blocks. 16 type Fire struct { 17 replaceable 18 transparent 19 empty 20 21 // Type is the type of fire. 22 Type FireType 23 // Age affects how fire extinguishes. Newly placed fire starts at 0 and the value has a 1/3 chance of incrementing 24 // each block tick. 25 Age int 26 } 27 28 // flammableBlock returns true if a block is flammable. 29 func flammableBlock(block world.Block) bool { 30 flammable, ok := block.(Flammable) 31 return ok && flammable.FlammabilityInfo().Encouragement > 0 32 } 33 34 // neighboursFlammable returns true if one a block adjacent to the passed position is flammable. 35 func neighboursFlammable(pos cube.Pos, w *world.World) bool { 36 for _, i := range cube.Faces() { 37 if flammableBlock(w.Block(pos.Side(i))) { 38 return true 39 } 40 } 41 return false 42 } 43 44 // max ... 45 func max(a, b int) int { 46 if a > b { 47 return a 48 } 49 return b 50 } 51 52 // infinitelyBurning returns true if fire can infinitely burn at the specified position. 53 func infinitelyBurning(pos cube.Pos, w *world.World) bool { 54 switch block := w.Block(pos.Side(cube.FaceDown)).(type) { 55 // TODO: Magma Block 56 case Netherrack: 57 return true 58 case Bedrock: 59 return block.InfiniteBurning 60 } 61 return false 62 } 63 64 // burn attempts to burn a block. 65 func (f Fire) burn(from, to cube.Pos, w *world.World, r *rand.Rand, chanceBound int) { 66 if flammable, ok := w.Block(to).(Flammable); ok && r.Intn(chanceBound) < flammable.FlammabilityInfo().Flammability { 67 if r.Intn(f.Age+10) < 5 && !rainingAround(to, w) { 68 f.spread(from, to, w, r) 69 return 70 } 71 if t, ok := flammable.(TNT); ok { 72 t.Ignite(to, w) 73 return 74 } 75 w.SetBlock(to, nil, nil) 76 } 77 } 78 79 // rainingAround checks if it is raining either at the cube.Pos passed or at any of its horizontal neighbours. 80 func rainingAround(pos cube.Pos, w *world.World) bool { 81 raining := w.RainingAt(pos) 82 for _, face := range cube.HorizontalFaces() { 83 if raining { 84 break 85 } 86 raining = w.RainingAt(pos.Side(face)) 87 } 88 return raining 89 } 90 91 // tick ... 92 func (f Fire) tick(pos cube.Pos, w *world.World, r *rand.Rand) { 93 if f.Type == SoulFire() { 94 return 95 } 96 infinitelyBurns := infinitelyBurning(pos, w) 97 if !infinitelyBurns && (20+f.Age*3) > r.Intn(100) && rainingAround(pos, w) { 98 // Fire is extinguished by the rain. 99 w.SetBlock(pos, nil, nil) 100 return 101 } 102 103 if f.Age < 15 && r.Intn(3) == 0 { 104 f.Age++ 105 w.SetBlock(pos, f, nil) 106 } 107 108 w.ScheduleBlockUpdate(pos, time.Duration(30+r.Intn(10))*time.Second/20) 109 110 if !infinitelyBurns { 111 _, waterBelow := w.Block(pos.Side(cube.FaceDown)).(Water) 112 if waterBelow { 113 w.SetBlock(pos, nil, nil) 114 return 115 } 116 if !neighboursFlammable(pos, w) { 117 if !w.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos, cube.FaceUp, w) || f.Age > 3 { 118 w.SetBlock(pos, nil, nil) 119 } 120 return 121 } 122 if !flammableBlock(w.Block(pos.Side(cube.FaceDown))) && f.Age == 15 && r.Intn(4) == 0 { 123 w.SetBlock(pos, nil, nil) 124 return 125 } 126 } 127 128 humid := w.Biome(pos).Rainfall() > 0.85 129 130 s := 0 131 if humid { 132 s = 50 133 } 134 for _, face := range cube.Faces() { 135 if face == cube.FaceUp || face == cube.FaceDown { 136 f.burn(pos, pos.Side(face), w, r, 300-s) 137 } else { 138 f.burn(pos, pos.Side(face), w, r, 250-s) 139 } 140 } 141 142 for y := -1; y <= 4; y++ { 143 randomBound := 100 144 if y > 1 { 145 randomBound += (y - 1) * 100 146 } 147 148 for x := -1; x <= 1; x++ { 149 for z := -1; z <= 1; z++ { 150 if x == 0 && y == 0 && z == 0 { 151 continue 152 } 153 blockPos := pos.Add(cube.Pos{x, y, z}) 154 block := w.Block(blockPos) 155 if _, ok := block.(Air); !ok { 156 continue 157 } 158 159 encouragement := 0 160 blockPos.Neighbours(func(neighbour cube.Pos) { 161 if flammable, ok := w.Block(neighbour).(Flammable); ok { 162 encouragement = max(encouragement, flammable.FlammabilityInfo().Encouragement) 163 } 164 }, w.Range()) 165 if encouragement <= 0 { 166 continue 167 } 168 169 maxChance := (encouragement + 40 + w.Difficulty().FireSpreadIncrease()) / (f.Age + 30) 170 if humid { 171 maxChance /= 2 172 } 173 174 if maxChance > 0 && r.Intn(randomBound) <= maxChance && !rainingAround(blockPos, w) { 175 f.spread(pos, blockPos, w, r) 176 } 177 } 178 } 179 } 180 } 181 182 // spread attempts to spread fire from a cube.Pos to another. If the block burn or fire spreading events are cancelled, 183 // this might end up not happening. 184 func (f Fire) spread(from, to cube.Pos, w *world.World, r *rand.Rand) { 185 if _, air := w.Block(to).(Air); !air { 186 ctx := event.C() 187 if w.Handler().HandleBlockBurn(ctx, to); ctx.Cancelled() { 188 return 189 } 190 } 191 ctx := event.C() 192 if w.Handler().HandleFireSpread(ctx, from, to); ctx.Cancelled() { 193 return 194 } 195 w.SetBlock(to, Fire{Type: f.Type, Age: min(15, f.Age+r.Intn(5)/4)}, nil) 196 w.ScheduleBlockUpdate(to, time.Duration(30+r.Intn(10))*time.Second/20) 197 } 198 199 // EntityInside ... 200 func (f Fire) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) { 201 if flammable, ok := e.(flammableEntity); ok { 202 if l, ok := e.(livingEntity); ok && !l.AttackImmune() { 203 l.Hurt(f.Type.Damage(), FireDamageSource{}) 204 } 205 if flammable.OnFireDuration() < time.Second*8 { 206 flammable.SetOnFire(8 * time.Second) 207 } 208 } 209 } 210 211 // ScheduledTick ... 212 func (f Fire) ScheduledTick(pos cube.Pos, w *world.World, r *rand.Rand) { 213 f.tick(pos, w, r) 214 } 215 216 // RandomTick ... 217 func (f Fire) RandomTick(pos cube.Pos, w *world.World, r *rand.Rand) { 218 f.tick(pos, w, r) 219 } 220 221 // NeighbourUpdateTick ... 222 func (f Fire) NeighbourUpdateTick(pos, neighbour cube.Pos, w *world.World) { 223 below := w.Block(pos.Side(cube.FaceDown)) 224 if diffuser, ok := below.(LightDiffuser); (ok && diffuser.LightDiffusionLevel() != 15) && (!neighboursFlammable(pos, w) || f.Type == SoulFire()) { 225 w.SetBlock(pos, nil, nil) 226 return 227 } 228 switch below.(type) { 229 case SoulSand, SoulSoil: 230 f.Type = SoulFire() 231 w.SetBlock(pos, f, nil) 232 case Water: 233 if neighbour == pos { 234 w.SetBlock(pos, nil, nil) 235 } 236 default: 237 if f.Type == SoulFire() { 238 w.SetBlock(pos, nil, nil) 239 return 240 } 241 } 242 } 243 244 // HasLiquidDrops ... 245 func (f Fire) HasLiquidDrops() bool { 246 return false 247 } 248 249 // LightEmissionLevel ... 250 func (f Fire) LightEmissionLevel() uint8 { 251 return f.Type.LightLevel() 252 } 253 254 // EncodeBlock ... 255 func (f Fire) EncodeBlock() (name string, properties map[string]any) { 256 switch f.Type { 257 case NormalFire(): 258 return "minecraft:fire", map[string]any{"age": int32(f.Age)} 259 case SoulFire(): 260 return "minecraft:soul_fire", map[string]any{"age": int32(f.Age)} 261 } 262 panic("unknown fire type") 263 } 264 265 // Start starts a fire at a position in the world. The position passed must be either air or tall grass and conditions 266 // for a fire to be present must be present. 267 func (f Fire) Start(w *world.World, pos cube.Pos) { 268 b := w.Block(pos) 269 _, air := b.(Air) 270 _, tallGrass := b.(TallGrass) 271 if air || tallGrass { 272 below := w.Block(pos.Side(cube.FaceDown)) 273 if below.Model().FaceSolid(pos, cube.FaceUp, w) || neighboursFlammable(pos, w) { 274 w.SetBlock(pos, Fire{}, nil) 275 } 276 } 277 } 278 279 // allFire ... 280 func allFire() (b []world.Block) { 281 for i := 0; i < 16; i++ { 282 b = append(b, Fire{Age: i, Type: NormalFire()}) 283 b = append(b, Fire{Age: i, Type: SoulFire()}) 284 } 285 return 286 } 287 288 // FireDamageSource is used for damage caused by being in fire. 289 type FireDamageSource struct{} 290 291 func (FireDamageSource) ReducedByResistance() bool { return true } 292 func (FireDamageSource) ReducedByArmour() bool { return true } 293 func (FireDamageSource) Fire() bool { return true } 294 func (FireDamageSource) AffectedByEnchantment(e item.EnchantmentType) bool { 295 _, prot := e.(enchantment.FireProtection) 296 return prot 297 }