Files
mc/server/entity/lightning.go
T

126 lines
3.9 KiB
Go
Raw Permalink Normal View History

2026-07-09 08:33:57 +08:00
package entity
import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/sound"
"github.com/go-gl/mathgl/mgl64"
"math/rand/v2"
"time"
)
// NewLightning creates a lightning entity. The lightning entity will be
// positioned at the position passed. Lightning is a lethal element to
// thunderstorms. Lightning momentarily increases the skylight's brightness to
// slightly greater than full daylight.
func NewLightning(opts world.EntitySpawnOpts) *world.EntityHandle {
return NewLightningWithDamage(opts, 5, true, time.Second*8)
}
// NewLightningWithDamage creates a new lightning entities using the damage and
// fire properties passed.
func NewLightningWithDamage(opts world.EntitySpawnOpts, dmg float64, blockFire bool, entityFireDuration time.Duration) *world.EntityHandle {
conf := lightningConf
conf.Tick = (&lightningState{
Damage: dmg,
EntityFireDuration: entityFireDuration,
BlockFire: blockFire,
state: 2,
lifetime: rand.IntN(4) + 1,
}).tick
return opts.New(LightningType, conf)
}
var lightningConf = StationaryBehaviourConfig{SpawnSounds: []world.Sound{sound.Explosion{}, sound.Thunder{}}, ExistenceDuration: time.Second}
// lightningState holds the state of a lightning entity.
type lightningState struct {
Damage float64
EntityFireDuration time.Duration
BlockFire bool
state, lifetime int
}
// tick carries out lightning logic, dealing damage and setting blocks/entities
// on fire when appropriate.
func (s *lightningState) tick(e *Ent, tx *world.Tx) {
pos := e.Position()
if s.state--; s.state < 0 {
if s.lifetime == 0 {
_ = e.Close()
} else if s.state < -rand.IntN(10) {
s.lifetime--
s.state = 1
if s.BlockFire && tx.World().Difficulty().FireSpreadIncrease() >= 10 {
s.spreadFire(tx, cube.PosFromVec3(pos))
}
}
}
if s.state > 0 {
s.dealDamage(e, tx)
}
}
// dealDamage deals damage to all entities around the lightning and sets them
// on fire.
func (s *lightningState) dealDamage(e *Ent, tx *world.Tx) {
pos := e.Position()
bb := e.H().Type().BBox(e).GrowVec3(mgl64.Vec3{3, 6, 3}).Translate(pos.Add(mgl64.Vec3{0, 3}))
for e := range tx.EntitiesWithin(bb) {
// Only damage entities that weren't already dead.
if l, ok := e.(Living); ok && l.Health() > 0 {
if s.Damage > 0 {
l.Hurt(s.Damage, LightningDamageSource{})
}
if f, ok := e.(Flammable); ok && f.OnFireDuration() < s.EntityFireDuration {
f.SetOnFire(s.EntityFireDuration)
}
}
}
}
// spreadFire attempts to place fire at the position of the lightning and does
// 4 additional attempts to spread it around that position.
func (s *lightningState) spreadFire(tx *world.Tx, pos cube.Pos) {
s.fire().Start(tx, pos)
for i := 0; i < 4; i++ {
pos.Add(cube.Pos{rand.IntN(3) - 1, rand.IntN(3) - 1, rand.IntN(3) - 1})
s.fire().Start(tx, pos)
}
}
// fire returns a fire block.
func (s *lightningState) fire() interface {
Start(tx *world.Tx, pos cube.Pos)
} {
return fire().(interface {
Start(tx *world.Tx, pos cube.Pos)
})
}
// fire returns a fire block.
func fire() world.Block {
f, ok := world.BlockByName("minecraft:fire", map[string]any{"age": int32(0)})
if !ok {
panic("could not find fire block")
}
return f
}
// LightningType is a world.EntityType implementation for Lightning.
var LightningType lightningType
type lightningType struct{}
func (t lightningType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity {
return &Ent{tx: tx, handle: handle, data: data}
}
func (t lightningType) DecodeNBT(_ map[string]any, data *world.EntityData) {
data.Data = lightningConf.New()
}
func (t lightningType) EncodeNBT(*world.EntityData) map[string]any { return nil }
func (lightningType) EncodeEntity() string { return "minecraft:lightning_bolt" }
func (lightningType) BBox(world.Entity) cube.BBox { return cube.BBox{} }