Files
mc/server/entity/area_effect_cloud_behaviour.go
TarnaWijaya 26ed99fda6
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
up3
2026-07-09 08:33:57 +08:00

183 lines
5.2 KiB
Go

package entity
import (
"github.com/df-mc/dragonfly/server/entity/effect"
"github.com/df-mc/dragonfly/server/item/potion"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
"iter"
"time"
)
// AreaEffectCloudBehaviourConfig contains optional parameters for an area
// effect cloud entity.
type AreaEffectCloudBehaviourConfig struct {
Potion potion.Potion
// Radius specifies the initial radius of the cloud. Defaults to 3.0.
Radius float64
// RadiusUseGrowth is the value that is added to the radius every time the
// effect cloud is used/consumed. This is typically a negative value. (-0.5)
RadiusUseGrowth float64
// RadiusTickGrowth is the value added to the radius every tick. This is
// typically a negative value. (-0.005)
RadiusTickGrowth float64
// Duration specifies the initial duration of the cloud. Defaults to 30s.
Duration time.Duration
// DurationUseGrowth is the duration that is added to the effect cloud every
// time it is used/consumed. This is 0 in normal situations.
DurationUseGrowth time.Duration
// ReapplicationDelay specifies the delay with which the effects from the
// cloud can be re-applied to users.
ReapplicationDelay time.Duration
}
func (conf AreaEffectCloudBehaviourConfig) Apply(data *world.EntityData) {
data.Data = conf.New()
}
// New creates an AreaEffectCloudBehaviour using the parameter in conf and t.
func (conf AreaEffectCloudBehaviourConfig) New() *AreaEffectCloudBehaviour {
if conf.Radius == 0 {
conf.Radius = 3.0
}
if conf.Duration == 0 {
conf.Duration = time.Second * 30
}
stationary := StationaryBehaviourConfig{ExistenceDuration: conf.Duration}
return &AreaEffectCloudBehaviour{
conf: conf,
stationary: stationary.New(),
duration: conf.Duration,
radius: conf.Radius,
targets: make(map[*world.EntityHandle]time.Duration),
}
}
// AreaEffectCloudBehaviour is the cloud that is created when: lingering
// potions are thrown; creepers with potion effects explode; dragon fireballs
// hit the ground.
type AreaEffectCloudBehaviour struct {
conf AreaEffectCloudBehaviourConfig
stationary *StationaryBehaviour
duration time.Duration
radius float64
targets map[*world.EntityHandle]time.Duration
}
// Radius returns the current radius of the area effect cloud.
func (a *AreaEffectCloudBehaviour) Radius() float64 {
return a.radius
}
// Effects returns the effects the area effect cloud provides.
func (a *AreaEffectCloudBehaviour) Effects() []effect.Effect {
return a.conf.Potion.Effects()
}
// Tick ...
func (a *AreaEffectCloudBehaviour) Tick(e *Ent, tx *world.Tx) *Movement {
a.stationary.Tick(e, tx)
if a.stationary.close || e.Age() < time.Second/2 {
// The cloud lives for at least half a second before it may begin
// spreading effects and growing/shrinking.
return nil
}
pos := e.Position()
if a.subtractTickRadius() {
for _, v := range tx.Viewers(pos) {
v.ViewEntityState(e)
}
}
if int16(e.Age()/(time.Second*20))%10 != 0 {
// Area effect clouds only trigger updates every ten ticks.
return nil
}
for target, expiration := range a.targets {
if e.Age() >= expiration {
delete(a.targets, target)
}
}
if a.applyEffects(pos, e, a.filter(tx.EntitiesWithin(e.H().Type().BBox(e).Translate(pos)))) {
for _, v := range tx.Viewers(pos) {
v.ViewEntityState(e)
}
}
return nil
}
func (a *AreaEffectCloudBehaviour) filter(seq iter.Seq[world.Entity]) iter.Seq[world.Entity] {
return func(yield func(world.Entity) bool) {
for e := range seq {
_, target := a.targets[e.H()]
_, living := e.(Living)
if !living || target {
continue
}
if !yield(e) {
return
}
}
}
}
// applyEffects applies the effects of an area effect cloud at pos to all
// entities passed if they were within the radius and don't have an active
// cooldown period.
func (a *AreaEffectCloudBehaviour) applyEffects(pos mgl64.Vec3, ent *Ent, entities iter.Seq[world.Entity]) bool {
var update bool
for e := range entities {
delta := e.Position().Sub(pos)
delta[1] = 0
if delta.Len() <= a.radius {
l := e.(Living)
for _, eff := range a.Effects() {
if lasting, ok := eff.Type().(effect.LastingType); ok {
l.AddEffect(effect.New(lasting, eff.Level(), eff.Duration()/4))
continue
}
l.AddEffect(eff)
}
a.targets[e.H()] = ent.Age() + a.conf.ReapplicationDelay
a.subtractUseDuration()
a.subtractUseRadius()
update = true
}
}
return update
}
// subtractTickRadius grows the cloud's radius by the radiusTickGrowth value. If the
// radius goes under 1/2, it will close the entity.
func (a *AreaEffectCloudBehaviour) subtractTickRadius() bool {
a.radius += a.conf.RadiusTickGrowth
if a.radius < 0.5 {
a.stationary.close = true
}
return a.conf.RadiusTickGrowth != 0
}
// subtractUseDuration grows duration by the durationUseGrowth factor. If duration
// goes under zero, it will close the entity.
func (a *AreaEffectCloudBehaviour) subtractUseDuration() {
a.duration += a.conf.DurationUseGrowth
if a.duration <= 0 {
a.stationary.close = true
}
}
// subtractUseRadius grows radius by the radiusUseGrowth factor. If radius goes
// under 1/2, it will close the entity.
func (a *AreaEffectCloudBehaviour) subtractUseRadius() {
a.radius += a.conf.RadiusUseGrowth
if a.radius <= 0.5 {
a.stationary.close = true
}
}