github.com/df-mc/dragonfly@v0.9.13/server/block/explosion.go (about) 1 package block 2 3 import ( 4 "github.com/df-mc/dragonfly/server/block/cube" 5 "github.com/df-mc/dragonfly/server/block/cube/trace" 6 "github.com/df-mc/dragonfly/server/item" 7 "github.com/df-mc/dragonfly/server/world" 8 "github.com/df-mc/dragonfly/server/world/particle" 9 "github.com/df-mc/dragonfly/server/world/sound" 10 "github.com/go-gl/mathgl/mgl64" 11 "math" 12 "math/rand" 13 "time" 14 ) 15 16 // ExplosionConfig is the configuration for an explosion. The world, position, size, sound, particle, and more can all 17 // be configured through this configuration. 18 type ExplosionConfig struct { 19 // Size is the size of the explosion, it is effectively the radius which entities/blocks will be affected within. 20 Size float64 21 // Rand is the source to use for the explosion "randomness". 22 Rand rand.Source 23 // SpawnFire will cause the explosion to randomly start fires in 1/3 of all destroyed air blocks that are 24 // above opaque blocks. 25 SpawnFire bool 26 // DisableItemDrops, when set to true, will prevent any item entities from dropping as a result of blocks being 27 // destroyed. 28 DisableItemDrops bool 29 30 // Sound is the sound to play when the explosion is created. If set to nil, this will default to the sound of a 31 // regular explosion. 32 Sound world.Sound 33 // Particle is the particle to spawn when the explosion is created. If set to nil, this will default to the particle 34 // of a regular huge explosion. 35 Particle world.Particle 36 } 37 38 // ExplodableEntity represents an entity that can be exploded. 39 type ExplodableEntity interface { 40 // Explode is called when an explosion occurs. The entity can then react to the explosion using the configuration 41 // and impact provided. 42 Explode(explosionPos mgl64.Vec3, impact float64, c ExplosionConfig) 43 } 44 45 // Explodable represents a block that can be exploded. 46 type Explodable interface { 47 // Explode is called when an explosion occurs. The block can react to the explosion using the configuration passed. 48 Explode(explosionPos mgl64.Vec3, pos cube.Pos, w *world.World, c ExplosionConfig) 49 } 50 51 // rays ... 52 var rays = make([]mgl64.Vec3, 0, 1352) 53 54 // init ... 55 func init() { 56 for x := 0.0; x < 16; x++ { 57 for y := 0.0; y < 16; y++ { 58 for z := 0.0; z < 16; z++ { 59 if x != 0 && x != 15 && y != 0 && y != 15 && z != 0 && z != 15 { 60 continue 61 } 62 rays = append(rays, mgl64.Vec3{x/15*2 - 1, y/15*2 - 1, z/15*2 - 1}.Normalize().Mul(0.3)) 63 } 64 } 65 } 66 } 67 68 // Explode performs the explosion as specified by the configuration. 69 func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) { 70 if c.Sound == nil { 71 c.Sound = sound.Explosion{} 72 } 73 if c.Particle == nil { 74 c.Particle = particle.HugeExplosion{} 75 } 76 if c.Rand == nil { 77 c.Rand = rand.NewSource(time.Now().UnixNano()) 78 } 79 if c.Size == 0 { 80 c.Size = 4 81 } 82 83 r, d := rand.New(c.Rand), c.Size*2 84 box := cube.Box( 85 math.Floor(explosionPos[0]-d-1), 86 math.Floor(explosionPos[1]-d-1), 87 math.Floor(explosionPos[2]-d-1), 88 math.Ceil(explosionPos[0]+d+1), 89 math.Ceil(explosionPos[1]+d+1), 90 math.Ceil(explosionPos[2]+d+1), 91 ) 92 93 for _, e := range w.EntitiesWithin(box.Grow(2), nil) { 94 pos := e.Position() 95 if !e.Type().BBox(e).Translate(pos).IntersectsWith(box) { 96 continue 97 } 98 dist := pos.Sub(pos).Len() 99 if dist >= d { 100 continue 101 } 102 if explodable, ok := e.(ExplodableEntity); ok { 103 impact := (1 - dist/d) * exposure(pos, e) 104 explodable.Explode(explosionPos, impact, c) 105 } 106 } 107 108 affectedBlocks := make([]cube.Pos, 0, 32) 109 for _, ray := range rays { 110 pos := explosionPos 111 for blastForce := c.Size * (0.7 + r.Float64()*0.6); blastForce > 0.0; blastForce -= 0.225 { 112 current := cube.PosFromVec3(pos) 113 currentBlock := w.Block(current) 114 115 resistance := 0.0 116 if l, ok := w.Liquid(current); ok { 117 resistance = l.BlastResistance() 118 } else if i, ok := currentBlock.(Breakable); ok { 119 resistance = i.BreakInfo().BlastResistance 120 } else if _, ok = currentBlock.(Air); !ok { 121 // Completely stop the ray if the current block is not air and unbreakable. 122 break 123 } 124 125 pos = pos.Add(ray) 126 if blastForce -= (resistance/5 + 0.3) * 0.3; blastForce > 0 { 127 affectedBlocks = append(affectedBlocks, current) 128 } 129 } 130 } 131 for _, pos := range affectedBlocks { 132 bl := w.Block(pos) 133 if explodable, ok := bl.(Explodable); ok { 134 explodable.Explode(explosionPos, pos, w, c) 135 } else if breakable, ok := bl.(Breakable); ok { 136 w.SetBlock(pos, nil, nil) 137 if !c.DisableItemDrops && 1/c.Size > r.Float64() { 138 for _, drop := range breakable.BreakInfo().Drops(item.ToolNone{}, nil) { 139 dropItem(w, drop, pos.Vec3Centre()) 140 } 141 } 142 } 143 } 144 if c.SpawnFire { 145 for _, pos := range affectedBlocks { 146 if r.Intn(3) == 0 { 147 if _, ok := w.Block(pos).(Air); ok && w.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos, cube.FaceUp, w) { 148 w.SetBlock(pos, Fire{}, nil) 149 } 150 } 151 } 152 } 153 154 w.AddParticle(explosionPos, c.Particle) 155 w.PlaySound(explosionPos, c.Sound) 156 } 157 158 // exposure returns the exposure of an explosion to an entity, used to calculate the impact of an explosion. 159 func exposure(origin mgl64.Vec3, e world.Entity) float64 { 160 w := e.World() 161 pos := e.Position() 162 box := e.Type().BBox(e).Translate(pos) 163 164 boxMin, boxMax := box.Min(), box.Max() 165 diff := boxMax.Sub(boxMin).Mul(2.0).Add(mgl64.Vec3{1, 1, 1}) 166 167 step := mgl64.Vec3{1.0 / diff[0], 1.0 / diff[1], 1.0 / diff[2]} 168 if step[0] < 0.0 || step[1] < 0.0 || step[2] < 0.0 { 169 return 0.0 170 } 171 172 xOffset := (1.0 - math.Floor(diff[0])/diff[0]) / 2.0 173 zOffset := (1.0 - math.Floor(diff[2])/diff[2]) / 2.0 174 175 var checks, misses int 176 for x := 0.0; x <= 1.0; x += step[0] { 177 for y := 0.0; y <= 1.0; y += step[1] { 178 for z := 0.0; z <= 1.0; z += step[2] { 179 point := mgl64.Vec3{ 180 lerp(x, boxMin[0], boxMax[0]) + xOffset, 181 lerp(y, boxMin[1], boxMax[1]), 182 lerp(z, boxMin[2], boxMax[2]) + zOffset, 183 } 184 185 var collided bool 186 trace.TraverseBlocks(origin, point, func(pos cube.Pos) (con bool) { 187 _, air := w.Block(pos).(Air) 188 collided = !air 189 return air 190 }) 191 if !collided { 192 misses++ 193 } 194 checks++ 195 } 196 } 197 } 198 return float64(misses) / float64(checks) 199 } 200 201 // lerp returns the linear interpolation between a and b at t. 202 func lerp(a, b, t float64) float64 { 203 return b + a*(t-b) 204 }