147 lines
4.4 KiB
Go
147 lines
4.4 KiB
Go
|
|
package entity
|
||
|
|
|
||
|
|
import (
|
||
|
|
"github.com/df-mc/dragonfly/server/block/cube/trace"
|
||
|
|
"github.com/df-mc/dragonfly/server/item"
|
||
|
|
"github.com/df-mc/dragonfly/server/world"
|
||
|
|
"github.com/df-mc/dragonfly/server/world/sound"
|
||
|
|
"iter"
|
||
|
|
"math"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// FireworkBehaviourConfig holds optional parameters for a FireworkBehaviour.
|
||
|
|
type FireworkBehaviourConfig struct {
|
||
|
|
Firework item.Firework
|
||
|
|
Owner *world.EntityHandle
|
||
|
|
// ExistenceDuration is the duration that an entity with this behaviour
|
||
|
|
// should last. Once this time expires, the entity is closed. If
|
||
|
|
// ExistenceDuration is 0, the entity will never expire automatically.
|
||
|
|
ExistenceDuration time.Duration
|
||
|
|
// SidewaysVelocityMultiplier is a value that the sideways velocity (X/Z) is
|
||
|
|
// multiplied with every tick. For normal fireworks this is 1.15.
|
||
|
|
SidewaysVelocityMultiplier float64
|
||
|
|
// UpwardsAcceleration is a value added to the firework's velocity every
|
||
|
|
// tick. For normal fireworks, this is 0.04.
|
||
|
|
UpwardsAcceleration float64
|
||
|
|
// Attached specifies if the firework is attached to its owner. If true,
|
||
|
|
// the firework will boost the speed of the owner while flying.
|
||
|
|
Attached bool
|
||
|
|
}
|
||
|
|
|
||
|
|
func (conf FireworkBehaviourConfig) Apply(data *world.EntityData) {
|
||
|
|
data.Data = conf.New()
|
||
|
|
}
|
||
|
|
|
||
|
|
// New creates a FireworkBehaviour for an fw and owner using the optional
|
||
|
|
// parameters in conf.
|
||
|
|
func (conf FireworkBehaviourConfig) New() *FireworkBehaviour {
|
||
|
|
b := &FireworkBehaviour{conf: conf}
|
||
|
|
b.passive = PassiveBehaviourConfig{
|
||
|
|
ExistenceDuration: conf.ExistenceDuration,
|
||
|
|
Expire: b.explode,
|
||
|
|
Tick: b.tick,
|
||
|
|
}.New()
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
// FireworkBehaviour implements Behaviour for a firework entity.
|
||
|
|
type FireworkBehaviour struct {
|
||
|
|
conf FireworkBehaviourConfig
|
||
|
|
passive *PassiveBehaviour
|
||
|
|
}
|
||
|
|
|
||
|
|
// Firework returns the underlying item.Firework of the FireworkBehaviour.
|
||
|
|
func (f *FireworkBehaviour) Firework() item.Firework {
|
||
|
|
return f.conf.Firework
|
||
|
|
}
|
||
|
|
|
||
|
|
// Attached specifies if the firework is attached to its owner.
|
||
|
|
func (f *FireworkBehaviour) Attached() bool {
|
||
|
|
return f.conf.Attached
|
||
|
|
}
|
||
|
|
|
||
|
|
// Owner returns the world.Entity that launched the firework.
|
||
|
|
func (f *FireworkBehaviour) Owner() *world.EntityHandle {
|
||
|
|
return f.conf.Owner
|
||
|
|
}
|
||
|
|
|
||
|
|
// Tick moves the firework and makes it explode when it reaches its maximum
|
||
|
|
// duration.
|
||
|
|
func (f *FireworkBehaviour) Tick(e *Ent, tx *world.Tx) *Movement {
|
||
|
|
return f.passive.Tick(e, tx)
|
||
|
|
}
|
||
|
|
|
||
|
|
// tick ticks the entity, updating its velocity either with a constant factor
|
||
|
|
// or based on the owner's position and velocity if attached.
|
||
|
|
func (f *FireworkBehaviour) tick(e *Ent, tx *world.Tx) {
|
||
|
|
owner, ok := f.conf.Owner.Entity(tx)
|
||
|
|
if f.conf.Attached && ok {
|
||
|
|
// The client will propel itself to match the firework's velocity since
|
||
|
|
// we set the appropriate metadata.
|
||
|
|
e.data.Pos = owner.Position()
|
||
|
|
} else {
|
||
|
|
e.data.Vel[0] *= f.conf.SidewaysVelocityMultiplier
|
||
|
|
e.data.Vel[1] += f.conf.UpwardsAcceleration
|
||
|
|
e.data.Vel[2] *= f.conf.SidewaysVelocityMultiplier
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// explode causes an explosion at the position of the firework, spawning
|
||
|
|
// particles and damaging nearby entities.
|
||
|
|
func (f *FireworkBehaviour) explode(e *Ent, tx *world.Tx) {
|
||
|
|
owner, _ := f.conf.Owner.Entity(tx)
|
||
|
|
pos, explosions := e.Position(), f.conf.Firework.Explosions
|
||
|
|
|
||
|
|
for _, v := range tx.Viewers(pos) {
|
||
|
|
v.ViewEntityAction(e, FireworkExplosionAction{})
|
||
|
|
}
|
||
|
|
for _, explosion := range explosions {
|
||
|
|
if explosion.Shape == item.FireworkShapeHugeSphere() {
|
||
|
|
tx.PlaySound(pos, sound.FireworkHugeBlast{})
|
||
|
|
} else {
|
||
|
|
tx.PlaySound(pos, sound.FireworkBlast{})
|
||
|
|
}
|
||
|
|
if explosion.Twinkle {
|
||
|
|
tx.PlaySound(pos, sound.FireworkTwinkle{})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(explosions) == 0 {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
force := float64(len(explosions)*2) + 5.0
|
||
|
|
for victim := range filterLiving(tx.EntitiesWithin(e.H().Type().BBox(e).Translate(pos).Grow(5.25))) {
|
||
|
|
tpos := victim.Position()
|
||
|
|
dist := pos.Sub(tpos).Len()
|
||
|
|
if dist > 5.0 {
|
||
|
|
// The maximum distance allowed is 5.0 blocks.
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
dmg := force * math.Sqrt((5.0-dist)/5.0)
|
||
|
|
src := ProjectileDamageSource{Owner: owner, Projectile: e}
|
||
|
|
|
||
|
|
if pos == tpos {
|
||
|
|
victim.(Living).Hurt(dmg, src)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if _, ok := trace.Perform(pos, tpos, tx, victim.H().Type().BBox(victim).Grow(0.3), nil); ok {
|
||
|
|
victim.(Living).Hurt(dmg, src)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func filterLiving(seq iter.Seq[world.Entity]) iter.Seq[world.Entity] {
|
||
|
|
return func(yield func(world.Entity) bool) {
|
||
|
|
for e := range seq {
|
||
|
|
if _, living := e.(Living); !living {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if !yield(e) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|