up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"time"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/block/cube/trace"
|
||||
"github.com/df-mc/dragonfly/server/event"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/particle"
|
||||
"github.com/df-mc/dragonfly/server/world/sound"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
)
|
||||
|
||||
// ExplosionConfig is the configuration for an explosion. The world, position, size, sound, particle, and more can all
|
||||
// be configured through this configuration.
|
||||
type ExplosionConfig struct {
|
||||
// Size is the size of the explosion, it is effectively the radius which entities/blocks will be affected within.
|
||||
Size float64
|
||||
// RandSource is the source to use for the explosion "randomness". If set
|
||||
// to nil, RandSource defaults to a `rand.PCG`source seeded with
|
||||
// `time.Now().UnixNano()`.
|
||||
RandSource rand.Source
|
||||
// SpawnFire will cause the explosion to randomly start fires in 1/3 of all destroyed air blocks that are
|
||||
// above opaque blocks.
|
||||
SpawnFire bool
|
||||
// ItemDropChance specifies how item drops should be handled. By default,
|
||||
// the item drop chance is 1/Size. If negative, no items will be dropped by
|
||||
// the explosion. If set to 1 or higher, all items are dropped.
|
||||
ItemDropChance float64
|
||||
|
||||
// Sound is the sound to play when the explosion is created. If set to nil, this will default to the sound of a
|
||||
// regular explosion.
|
||||
Sound world.Sound
|
||||
// Particle is the particle to spawn when the explosion is created. If set to nil, this will default to the particle
|
||||
// of a regular huge explosion.
|
||||
Particle world.Particle
|
||||
}
|
||||
|
||||
// ExplodableEntity represents an entity that can be exploded.
|
||||
type ExplodableEntity interface {
|
||||
// Explode is called when an explosion occurs. The entity can then react to the explosion using the configuration
|
||||
// and impact provided.
|
||||
Explode(explosionPos mgl64.Vec3, impact float64, c ExplosionConfig)
|
||||
}
|
||||
|
||||
// Explodable represents a block that can be exploded.
|
||||
type Explodable interface {
|
||||
// Explode is called when an explosion occurs. The block can react to the explosion using the configuration passed.
|
||||
Explode(explosionPos mgl64.Vec3, pos cube.Pos, tx *world.Tx, c ExplosionConfig)
|
||||
}
|
||||
|
||||
// rays ...
|
||||
var rays = make([]mgl64.Vec3, 0, 1352)
|
||||
|
||||
// init ...
|
||||
func init() {
|
||||
for x := 0.0; x < 16; x++ {
|
||||
for y := 0.0; y < 16; y++ {
|
||||
for z := 0.0; z < 16; z++ {
|
||||
if x != 0 && x != 15 && y != 0 && y != 15 && z != 0 && z != 15 {
|
||||
continue
|
||||
}
|
||||
rays = append(rays, mgl64.Vec3{x/15*2 - 1, y/15*2 - 1, z/15*2 - 1}.Normalize().Mul(0.3))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Explode performs the explosion as specified by the configuration.
|
||||
func (c ExplosionConfig) Explode(tx *world.Tx, explosionPos mgl64.Vec3) {
|
||||
if c.Sound == nil {
|
||||
c.Sound = sound.Explosion{}
|
||||
}
|
||||
if c.Particle == nil {
|
||||
c.Particle = particle.HugeExplosion{}
|
||||
}
|
||||
if c.RandSource == nil {
|
||||
t := uint64(time.Now().UnixNano())
|
||||
c.RandSource = rand.NewPCG(t, t)
|
||||
}
|
||||
if c.Size == 0 {
|
||||
c.Size = 4
|
||||
}
|
||||
if c.ItemDropChance == 0 {
|
||||
c.ItemDropChance = 1.0 / c.Size
|
||||
}
|
||||
|
||||
r, d := rand.New(c.RandSource), c.Size*2
|
||||
box := cube.Box(
|
||||
math.Floor(explosionPos[0]-d-1),
|
||||
math.Floor(explosionPos[1]-d-1),
|
||||
math.Floor(explosionPos[2]-d-1),
|
||||
math.Ceil(explosionPos[0]+d+1),
|
||||
math.Ceil(explosionPos[1]+d+1),
|
||||
math.Ceil(explosionPos[2]+d+1),
|
||||
)
|
||||
|
||||
affectedEntities := make([]world.Entity, 0, 32)
|
||||
for e := range tx.EntitiesWithin(box.Grow(2)) {
|
||||
pos := e.Position()
|
||||
dist := pos.Sub(explosionPos).Len()
|
||||
if dist > d || dist == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
affectedEntities = append(affectedEntities, e)
|
||||
}
|
||||
|
||||
affectedBlocks := make([]cube.Pos, 0, 32)
|
||||
for _, ray := range rays {
|
||||
pos := explosionPos
|
||||
for blastForce := c.Size * (0.7 + r.Float64()*0.6); blastForce > 0.0; blastForce -= 0.225 {
|
||||
current := cube.PosFromVec3(pos)
|
||||
currentBlock := tx.Block(current)
|
||||
|
||||
resistance := 0.0
|
||||
if l, ok := tx.Liquid(current); ok {
|
||||
resistance = l.BlastResistance()
|
||||
} else if i, ok := currentBlock.(Breakable); ok {
|
||||
resistance = i.BreakInfo().BlastResistance
|
||||
} else if _, ok = currentBlock.(Air); !ok {
|
||||
// Completely stop the ray if the current block is not air and unbreakable.
|
||||
break
|
||||
}
|
||||
|
||||
pos = pos.Add(ray)
|
||||
if blastForce -= (resistance/5 + 0.3) * 0.3; blastForce > 0 {
|
||||
affectedBlocks = append(affectedBlocks, current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx := event.C(tx)
|
||||
spawnFire := c.SpawnFire
|
||||
itemDropChance := c.ItemDropChance
|
||||
if tx.World().Handler().HandleExplosion(ctx, explosionPos, &affectedEntities, &affectedBlocks, &itemDropChance, &spawnFire); ctx.Cancelled() {
|
||||
return
|
||||
}
|
||||
|
||||
for _, e := range affectedEntities {
|
||||
if explodable, ok := e.(ExplodableEntity); ok {
|
||||
impact := (1 - e.Position().Sub(explosionPos).Len()/d) * exposure(tx, explosionPos, e)
|
||||
explodable.Explode(explosionPos, impact, c)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pos := range affectedBlocks {
|
||||
bl := tx.Block(pos)
|
||||
if explodable, ok := bl.(Explodable); ok {
|
||||
explodable.Explode(explosionPos, pos, tx, c)
|
||||
} else if breakable, ok := bl.(Breakable); ok {
|
||||
// Clear the block first so break handlers see the post-break world, this is required by things such as redstone updates.
|
||||
tx.SetBlock(pos, nil, nil)
|
||||
breakHandler := breakable.BreakInfo().BreakHandler
|
||||
if breakHandler != nil {
|
||||
breakHandler(pos, tx, nil)
|
||||
}
|
||||
if itemDropChance > r.Float64() {
|
||||
for _, drop := range breakable.BreakInfo().Drops(item.ToolNone{}, nil) {
|
||||
dropItem(tx, drop, pos.Vec3Centre())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if spawnFire {
|
||||
for _, pos := range affectedBlocks {
|
||||
if r.IntN(3) == 0 {
|
||||
if _, ok := tx.Block(pos).(Air); ok && tx.Block(pos.Side(cube.FaceDown)).Model().FaceSolid(pos, cube.FaceUp, tx) {
|
||||
Fire{}.Start(tx, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx.AddParticle(explosionPos, c.Particle)
|
||||
tx.PlaySound(explosionPos, c.Sound)
|
||||
}
|
||||
|
||||
// exposure returns the exposure of an explosion to an entity, used to calculate the impact of an explosion.
|
||||
func exposure(tx *world.Tx, origin mgl64.Vec3, e world.Entity) float64 {
|
||||
pos := e.Position()
|
||||
box := e.H().Type().BBox(e).Translate(pos)
|
||||
|
||||
boxMin, boxMax := box.Min(), box.Max()
|
||||
diff := boxMax.Sub(boxMin).Mul(2.0).Add(mgl64.Vec3{1, 1, 1})
|
||||
|
||||
step := mgl64.Vec3{1.0 / diff[0], 1.0 / diff[1], 1.0 / diff[2]}
|
||||
if step[0] < 0.0 || step[1] < 0.0 || step[2] < 0.0 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
xOffset := (1.0 - math.Floor(diff[0])/diff[0]) / 2.0
|
||||
zOffset := (1.0 - math.Floor(diff[2])/diff[2]) / 2.0
|
||||
|
||||
var checks, misses float64
|
||||
for x := 0.0; x <= 1.0; x += step[0] {
|
||||
for y := 0.0; y <= 1.0; y += step[1] {
|
||||
for z := 0.0; z <= 1.0; z += step[2] {
|
||||
point := mgl64.Vec3{
|
||||
lerp(x, boxMin[0], boxMax[0]) + xOffset,
|
||||
lerp(y, boxMin[1], boxMax[1]),
|
||||
lerp(z, boxMin[2], boxMax[2]) + zOffset,
|
||||
}
|
||||
var collided bool
|
||||
trace.TraverseBlocks(origin, point, func(pos cube.Pos) (cont bool) {
|
||||
_, collided = trace.BlockIntercept(pos, tx, tx.Block(pos), origin, point)
|
||||
return !collided
|
||||
})
|
||||
|
||||
if !collided {
|
||||
misses++
|
||||
}
|
||||
checks++
|
||||
}
|
||||
}
|
||||
}
|
||||
return misses / checks
|
||||
}
|
||||
|
||||
// lerp returns the linear interpolation between a and b at t.
|
||||
func lerp(a, b, t float64) float64 {
|
||||
return b + a*(t-b)
|
||||
}
|
||||
Reference in New Issue
Block a user