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  }