From 786d930b0b4567a578450a943fa51a68a9b11f50 Mon Sep 17 00:00:00 2001 From: Sandertv Date: Tue, 29 Oct 2024 11:53:10 +0100 Subject: [PATCH] Mostly finished un-breaking entities. --- server/block/block.go | 6 +- server/block/fire.go | 2 +- server/block/tnt.go | 20 +++--- server/entity/area_effect_cloud.go | 44 +++++++------- server/entity/area_effect_cloud_behaviour.go | 33 +++++----- server/entity/bottle_of_enchanting.go | 27 ++++----- server/entity/effect.go | 47 +++----------- server/entity/egg.go | 27 +++------ server/entity/ender_pearl.go | 27 ++++----- server/entity/experience.go | 32 +++------- server/entity/experience_orb.go | 36 +++++------ server/entity/experience_orb_behaviour.go | 4 ++ server/entity/falling_block.go | 38 +++++------- server/entity/falling_block_behaviour.go | 17 +++++- server/entity/firework.go | 55 ++++++++--------- server/entity/firework_behaviour.go | 40 ++++++------ server/entity/health.go | 25 +------- server/entity/item.go | 44 +++++++------- server/entity/item_behaviour.go | 22 ++++--- server/entity/lightning.go | 12 ++-- server/entity/movement.go | 4 +- server/entity/passive.go | 13 ++-- server/entity/projectile.go | 27 +++------ server/entity/register.go | 64 ++++++-------------- server/entity/snowball.go | 27 ++++----- server/entity/splash_potion.go | 32 +++++----- server/entity/splashable.go | 2 +- server/entity/stationary.go | 1 - server/entity/text.go | 23 +++---- server/entity/tnt.go | 43 ++++++------- server/item/bottle_of_enchanting.go | 3 +- server/item/bow.go | 4 +- server/item/egg.go | 3 +- server/item/ender_pearl.go | 3 +- server/item/firework.go | 6 +- server/item/lingering_potion.go | 3 +- server/item/snowball.go | 3 +- server/item/splash_potion.go | 3 +- server/session/handler_grindstone.go | 2 - server/session/handler_item_stack_request.go | 3 - server/world/entity.go | 21 ++++--- server/world/tx.go | 4 +- server/world/weather.go | 2 +- server/world/world.go | 5 +- 44 files changed, 366 insertions(+), 493 deletions(-) diff --git a/server/block/block.go b/server/block/block.go index c69f1553b..1c79ddd78 100644 --- a/server/block/block.go +++ b/server/block/block.go @@ -216,7 +216,8 @@ func (g gravityAffected) fall(b world.Block, pos cube.Pos, tx *world.Tx) { _, liquid := tx.Liquid(pos.Side(cube.FaceDown)) if air || liquid { tx.SetBlock(pos, nil, nil) - tx.AddEntity(tx.World().EntityRegistry().Config().FallingBlock(b, pos.Vec3Centre())) + opts := world.EntitySpawnOpts{Position: pos.Vec3Centre()} + tx.AddEntity(tx.World().EntityRegistry().Config().FallingBlock(opts, b)) } } @@ -271,7 +272,8 @@ type flammableEntity interface { // dropItem ... func dropItem(tx *world.Tx, it item.Stack, pos mgl64.Vec3) { create := tx.World().EntityRegistry().Config().Item - tx.AddEntity(create(it, pos, mgl64.Vec3{rand.Float64()*0.2 - 0.1, 0.2, rand.Float64()*0.2 - 0.1})) + opts := world.EntitySpawnOpts{Position: pos, Velocity: mgl64.Vec3{rand.Float64()*0.2 - 0.1, 0.2, rand.Float64()*0.2 - 0.1}} + tx.AddEntity(create(opts, it)) } // bass is a struct that may be embedded for blocks that create a bass sound. diff --git a/server/block/fire.go b/server/block/fire.go index e1a5f45ba..906c2ab65 100644 --- a/server/block/fire.go +++ b/server/block/fire.go @@ -61,7 +61,7 @@ func (f Fire) burn(from, to cube.Pos, tx *world.Tx, r *rand.Rand, chanceBound in return } if t, ok := flammable.(TNT); ok { - t.Ignite(to, tx, nil) + t.Ignite(to, tx) return } tx.SetBlock(to, nil, nil) diff --git a/server/block/tnt.go b/server/block/tnt.go index 8a222358f..ec65bb376 100644 --- a/server/block/tnt.go +++ b/server/block/tnt.go @@ -21,7 +21,7 @@ type TNT struct { func (t TNT) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { held, _ := u.HeldItems() if _, ok := held.Enchantment(enchantment.FireAspect{}); ok { - t.Ignite(pos, tx, u) + t.Ignite(pos, tx) ctx.DamageItem(1) return true } @@ -29,21 +29,14 @@ func (t TNT) Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item. } // Ignite ... -func (t TNT) Ignite(pos cube.Pos, tx *world.Tx, igniter world.Entity) bool { - t.igniter = igniter - spawnTnt(pos, tx, time.Second*4, t.igniter) +func (t TNT) Ignite(pos cube.Pos, tx *world.Tx) bool { + spawnTnt(pos, tx, time.Second*4) return true } -// Igniter returns the entity that ignited the TNT. -// It is nil if ignited by a world source like fire. -func (t TNT) Igniter() world.Entity { - return t.igniter -} - // Explode ... func (t TNT) Explode(explosionPos mgl64.Vec3, pos cube.Pos, tx *world.Tx, c ExplosionConfig) { - spawnTnt(pos, tx, time.Second/2+time.Duration(rand.Intn(int(time.Second+time.Second/2))), t.igniter) + spawnTnt(pos, tx, time.Second/2+time.Duration(rand.Intn(int(time.Second+time.Second/2)))) } // BreakInfo ... @@ -67,8 +60,9 @@ func (t TNT) EncodeBlock() (name string, properties map[string]interface{}) { } // spawnTnt creates a new TNT entity at the given position with the given fuse duration. -func spawnTnt(pos cube.Pos, tx *world.Tx, fuse time.Duration, igniter world.Entity) { +func spawnTnt(pos cube.Pos, tx *world.Tx, fuse time.Duration) { tx.PlaySound(pos.Vec3Centre(), sound.TNT{}) tx.SetBlock(pos, nil, nil) - tx.AddEntity(tx.World().EntityRegistry().Config().TNT(pos.Vec3Centre(), fuse, igniter)) + opts := world.EntitySpawnOpts{Position: pos.Vec3Centre()} + tx.AddEntity(tx.World().EntityRegistry().Config().TNT(opts, fuse)) } diff --git a/server/entity/area_effect_cloud.go b/server/entity/area_effect_cloud.go index a1d06e784..4909f6a7e 100644 --- a/server/entity/area_effect_cloud.go +++ b/server/entity/area_effect_cloud.go @@ -6,20 +6,20 @@ import ( "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/item/potion" "github.com/df-mc/dragonfly/server/world" - "github.com/go-gl/mathgl/mgl64" "time" ) // NewAreaEffectCloud creates a new area effect cloud entity and returns it. -func NewAreaEffectCloud(pos mgl64.Vec3, p potion.Potion) *Ent { +func NewAreaEffectCloud(opts world.EntitySpawnOpts, p potion.Potion) *world.EntityHandle { config := areaEffectCloudConf + config.Potion = p for _, e := range p.Effects() { if _, ok := e.Type().(effect.LastingType); !ok { config.ReapplicationDelay = 0 break } } - return Config{Behaviour: config.New(p)}.New(AreaEffectCloudType{}, pos) + return opts.New(AreaEffectCloudType{}, config) } var areaEffectCloudConf = AreaEffectCloudBehaviourConfig{ @@ -29,8 +29,9 @@ var areaEffectCloudConf = AreaEffectCloudBehaviourConfig{ } // NewAreaEffectCloudWith ... -func NewAreaEffectCloudWith(pos mgl64.Vec3, t potion.Potion, duration, reapplicationDelay, durationOnUse time.Duration, radius, radiusOnUse, radiusGrowth float64) *Ent { +func NewAreaEffectCloudWith(opts world.EntitySpawnOpts, t potion.Potion, duration, reapplicationDelay, durationOnUse time.Duration, radius, radiusOnUse, radiusGrowth float64) *world.EntityHandle { config := AreaEffectCloudBehaviourConfig{ + Potion: t, Radius: radius, RadiusUseGrowth: radiusOnUse, RadiusTickGrowth: radiusGrowth, @@ -38,42 +39,43 @@ func NewAreaEffectCloudWith(pos mgl64.Vec3, t potion.Potion, duration, reapplica DurationUseGrowth: durationOnUse, ReapplicationDelay: reapplicationDelay, } - return Config{Behaviour: config.New(t)}.New(AreaEffectCloudType{}, pos) + return opts.New(AreaEffectCloudType{}, config) } // AreaEffectCloudType is a world.EntityType implementation for AreaEffectCloud. type AreaEffectCloudType struct{} +func (t AreaEffectCloudType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + func (AreaEffectCloudType) EncodeEntity() string { return "minecraft:area_effect_cloud" } func (AreaEffectCloudType) BBox(e world.Entity) cube.BBox { r := e.(*Ent).Behaviour().(*AreaEffectCloudBehaviour).Radius() return cube.Box(-r, 0, -r, r, 0.5, r) } -func (AreaEffectCloudType) DecodeNBT(m map[string]any) world.Entity { - return NewAreaEffectCloudWith( - nbtconv.Vec3(m, "Pos"), - potion.From(nbtconv.Int32(m, "PotionId")), - nbtconv.TickDuration[int32](m, "Duration"), - nbtconv.TickDuration[int32](m, "ReapplicationDelay"), - nbtconv.TickDuration[int32](m, "DurationOnUse"), - float64(nbtconv.Float32(m, "Radius")), - float64(nbtconv.Float32(m, "RadiusOnUse")), - float64(nbtconv.Float32(m, "RadiusPerTick")), - ) +func (AreaEffectCloudType) DecodeNBT(m map[string]any, data *world.EntityData) { + data.Data = AreaEffectCloudBehaviourConfig{ + Potion: potion.From(nbtconv.Int32(m, "PotionId")), + Radius: float64(nbtconv.Float32(m, "Radius")), + RadiusUseGrowth: float64(nbtconv.Float32(m, "RadiusOnUse")), + RadiusTickGrowth: float64(nbtconv.Float32(m, "RadiusPerTick")), + Duration: nbtconv.TickDuration[int32](m, "Duration"), + DurationUseGrowth: nbtconv.TickDuration[int32](m, "ReapplicationDelay"), + ReapplicationDelay: nbtconv.TickDuration[int32](m, "DurationOnUse"), + }.New() } -func (AreaEffectCloudType) EncodeNBT(e world.Entity) map[string]any { - ent := e.(*Ent) - a := ent.Behaviour().(*AreaEffectCloudBehaviour) +func (AreaEffectCloudType) EncodeNBT(data *world.EntityData) map[string]any { + a := data.Data.(*AreaEffectCloudBehaviour) return map[string]any{ - "Pos": nbtconv.Vec3ToFloat32Slice(ent.Position()), + "PotionId": int32(a.conf.Potion.Uint8()), "ReapplicationDelay": int32(a.conf.ReapplicationDelay), "RadiusPerTick": float32(a.conf.RadiusTickGrowth), "RadiusOnUse": float32(a.conf.RadiusUseGrowth), "DurationOnUse": int32(a.conf.DurationUseGrowth), "Radius": float32(a.radius), "Duration": int32(a.duration), - "PotionId": int32(a.t.Uint8()), } } diff --git a/server/entity/area_effect_cloud_behaviour.go b/server/entity/area_effect_cloud_behaviour.go index 28b48fd84..b90dcc721 100644 --- a/server/entity/area_effect_cloud_behaviour.go +++ b/server/entity/area_effect_cloud_behaviour.go @@ -11,6 +11,7 @@ import ( // 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 @@ -29,24 +30,25 @@ type AreaEffectCloudBehaviourConfig struct { 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(t potion.Potion) *AreaEffectCloudBehaviour { +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, - } + stationary := StationaryBehaviourConfig{ExistenceDuration: conf.Duration} return &AreaEffectCloudBehaviour{ conf: conf, stationary: stationary.New(), duration: conf.Duration, radius: conf.Radius, targets: make(map[world.Entity]time.Duration), - t: t, } } @@ -55,7 +57,6 @@ func (conf AreaEffectCloudBehaviourConfig) New(t potion.Potion) *AreaEffectCloud // hit the ground. type AreaEffectCloudBehaviour struct { conf AreaEffectCloudBehaviourConfig - t potion.Potion stationary *StationaryBehaviour @@ -71,13 +72,13 @@ func (a *AreaEffectCloudBehaviour) Radius() float64 { // Effects returns the effects the area effect cloud provides. func (a *AreaEffectCloudBehaviour) Effects() []effect.Effect { - return a.t.Effects() + 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 || a.stationary.age < 10 { + 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 @@ -90,13 +91,13 @@ func (a *AreaEffectCloudBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { } } - if a.stationary.age%10 != 0 { + 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 a.stationary.age >= expiration { + if e.Age() >= expiration { delete(a.targets, target) } } @@ -117,17 +118,14 @@ func (a *AreaEffectCloudBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { // 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, e *Ent, entities []world.Entity) bool { - e.mu.Lock() - defer e.mu.Unlock() - +func (a *AreaEffectCloudBehaviour) applyEffects(pos mgl64.Vec3, ent *Ent, entities []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.t.Effects() { + for _, eff := range a.Effects() { if lasting, ok := eff.Type().(effect.LastingType); ok { l.AddEffect(effect.New(lasting, eff.Level(), eff.Duration()/4)) continue @@ -135,7 +133,7 @@ func (a *AreaEffectCloudBehaviour) applyEffects(pos mgl64.Vec3, e *Ent, entities l.AddEffect(eff) } - a.targets[e] = a.stationary.age + a.conf.ReapplicationDelay + a.targets[e] = ent.Age() + a.conf.ReapplicationDelay a.subtractUseDuration() a.subtractUseRadius() @@ -148,9 +146,6 @@ func (a *AreaEffectCloudBehaviour) applyEffects(pos mgl64.Vec3, e *Ent, entities // 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(e *Ent) bool { - e.mu.Lock() - defer e.mu.Unlock() - a.radius += a.conf.RadiusTickGrowth if a.radius < 0.5 { a.stationary.close = true diff --git a/server/entity/bottle_of_enchanting.go b/server/entity/bottle_of_enchanting.go index 9630701ee..9da29b8a3 100644 --- a/server/entity/bottle_of_enchanting.go +++ b/server/entity/bottle_of_enchanting.go @@ -3,17 +3,17 @@ package entity import ( "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/cube/trace" - "github.com/df-mc/dragonfly/server/internal/nbtconv" "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" "math/rand" ) // NewBottleOfEnchanting ... -func NewBottleOfEnchanting(pos mgl64.Vec3, owner world.Entity) *Ent { - return Config{Behaviour: bottleOfEnchantingConf.New()}.New(BottleOfEnchantingType{}, pos) +func NewBottleOfEnchanting(opts world.EntitySpawnOpts, owner world.Entity) *world.EntityHandle { + conf := bottleOfEnchantingConf + conf.Owner = owner + return opts.New(BottleOfEnchantingType{}, conf) } var bottleOfEnchantingConf = ProjectileBehaviourConfig{ @@ -29,7 +29,6 @@ var bottleOfEnchantingConf = ProjectileBehaviourConfig{ // a trace.Result. func spawnExperience(e *Ent, tx *world.Tx, target trace.Result) { for _, orb := range NewExperienceOrbs(target.Position(), rand.Intn(9)+3) { - orb.SetVelocity(mgl64.Vec3{(rand.Float64()*0.2 - 0.1) * 2, rand.Float64() * 0.4, (rand.Float64()*0.2 - 0.1) * 2}) tx.AddEntity(orb) } } @@ -37,6 +36,10 @@ func spawnExperience(e *Ent, tx *world.Tx, target trace.Result) { // BottleOfEnchantingType is a world.EntityType for BottleOfEnchanting. type BottleOfEnchantingType struct{} +func (t BottleOfEnchantingType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + // Glint returns true if the bottle should render with glint. It always returns // true for bottles of enchanting. func (BottleOfEnchantingType) Glint() bool { @@ -49,16 +52,10 @@ func (BottleOfEnchantingType) BBox(world.Entity) cube.BBox { return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125) } -func (BottleOfEnchantingType) DecodeNBT(m map[string]any) world.Entity { - b := NewBottleOfEnchanting(nbtconv.Vec3(m, "Pos"), nil) - b.vel = nbtconv.Vec3(m, "Motion") - return b +func (BottleOfEnchantingType) DecodeNBT(_ map[string]any, data *world.EntityData) { + data.Data = bottleOfEnchantingConf.New() } -func (BottleOfEnchantingType) EncodeNBT(e world.Entity) map[string]any { - b := e.(*Ent) - return map[string]any{ - "Pos": nbtconv.Vec3ToFloat32Slice(b.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(b.Velocity()), - } +func (BottleOfEnchantingType) EncodeNBT(data *world.EntityData) map[string]any { + return nil } diff --git a/server/entity/effect.go b/server/entity/effect.go index ba3d9d6dd..705cda41c 100644 --- a/server/entity/effect.go +++ b/server/entity/effect.go @@ -4,14 +4,13 @@ import ( "fmt" "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/world" + "golang.org/x/exp/maps" "reflect" - "sync" ) // EffectManager manages the effects of an entity. The effect manager will only store effects that last for // a specific duration. Instant effects are applied instantly and not stored. type EffectManager struct { - mu sync.Mutex effects map[reflect.Type]effect.Effect } @@ -41,21 +40,17 @@ func (m *EffectManager) Add(e effect.Effect, entity Living) effect.Effect { } typ := reflect.TypeOf(e.Type()) - m.mu.Lock() existing, ok := m.effects[typ] if !ok { m.effects[typ] = e - m.mu.Unlock() t.Start(entity, lvl) return e } if existing.Level() > lvl || (existing.Level() == lvl && existing.Duration() > dur) { - m.mu.Unlock() return existing } m.effects[typ] = e - m.mu.Unlock() existing.Type().(effect.LastingType).End(entity, existing.Level()) t.Start(entity, lvl) @@ -65,66 +60,44 @@ func (m *EffectManager) Add(e effect.Effect, entity Living) effect.Effect { // Remove removes any Effect present in the EffectManager with the type of the effect passed. func (m *EffectManager) Remove(e effect.Type, entity Living) { t := reflect.TypeOf(e) - - m.mu.Lock() - existing, ok := m.effects[t] - delete(m.effects, t) - m.mu.Unlock() - - if ok { + if existing, ok := m.effects[t]; ok { existing.Type().(effect.LastingType).End(entity, existing.Level()) } + delete(m.effects, t) + } // Effect returns the effect instance and true if the entity has the effect. If not found, it will return an empty // effect instance and false. func (m *EffectManager) Effect(e effect.Type) (effect.Effect, bool) { - m.mu.Lock() existing, ok := m.effects[reflect.TypeOf(e)] - m.mu.Unlock() return existing, ok } // Effects returns a list of all effects currently present in the effect manager. This will never include // effects that have expired. func (m *EffectManager) Effects() []effect.Effect { - m.mu.Lock() - defer m.mu.Unlock() - - e := make([]effect.Effect, 0, len(m.effects)) - for _, eff := range m.effects { - e = append(e, eff) - } - return e + return maps.Values(m.effects) } // Tick ticks the EffectManager, applying all of its effects to the Living entity passed when applicable and // removing expired effects. func (m *EffectManager) Tick(entity Living, tx *world.Tx) { - m.mu.Lock() - e := make([]effect.Effect, 0, len(m.effects)) - var toEnd []effect.Effect + update := false for i, eff := range m.effects { if m.expired(eff) { delete(m.effects, i) - toEnd = append(toEnd, eff) + eff.Type().(effect.LastingType).End(entity, eff.Level()) + update = true continue } eff = eff.TickDuration() - e = append(e, eff) - m.effects[i] = eff - } - m.mu.Unlock() - - for _, eff := range e { eff.Type().Apply(entity, eff.Level(), eff.Duration()) - } - for _, eff := range toEnd { - eff.Type().(effect.LastingType).End(entity, eff.Level()) + m.effects[i] = eff } - if len(toEnd) > 0 { + if update { for _, v := range tx.Viewers(entity.Position()) { v.ViewEntityState(entity) } diff --git a/server/entity/egg.go b/server/entity/egg.go index 340d9c7ab..9e45823e8 100644 --- a/server/entity/egg.go +++ b/server/entity/egg.go @@ -2,16 +2,16 @@ package entity import ( "github.com/df-mc/dragonfly/server/block/cube" - "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/particle" - "github.com/go-gl/mathgl/mgl64" ) // NewEgg creates an Egg entity. Egg is as a throwable entity that can be used // to spawn chicks. -func NewEgg(pos mgl64.Vec3, owner world.Entity) *Ent { - return Config{Behaviour: eggConf.New()}.New(EggType{}, pos) +func NewEgg(opts world.EntitySpawnOpts, owner world.Entity) *world.EntityHandle { + conf := eggConf + conf.Owner = owner + return opts.New(EggType{}, conf) } // TODO: Spawn chicken(e) 12.5% of the time. @@ -25,21 +25,14 @@ var eggConf = ProjectileBehaviourConfig{ // EggType is a world.EntityType implementation for Egg. type EggType struct{} +func (t EggType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + func (EggType) EncodeEntity() string { return "minecraft:egg" } func (EggType) BBox(world.Entity) cube.BBox { return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125) } -func (EggType) DecodeNBT(m map[string]any) world.Entity { - egg := NewEgg(nbtconv.Vec3(m, "Pos"), nil) - egg.vel = nbtconv.Vec3(m, "Motion") - return egg -} - -func (EggType) EncodeNBT(e world.Entity) map[string]any { - egg := e.(*Ent) - return map[string]any{ - "Pos": nbtconv.Vec3ToFloat32Slice(egg.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(egg.Velocity()), - } -} +func (EggType) DecodeNBT(m map[string]any, data *world.EntityData) { data.Data = eggConf.New() } +func (EggType) EncodeNBT(data *world.EntityData) map[string]any { return nil } diff --git a/server/entity/ender_pearl.go b/server/entity/ender_pearl.go index 42e713fe0..556b303ac 100644 --- a/server/entity/ender_pearl.go +++ b/server/entity/ender_pearl.go @@ -3,7 +3,6 @@ package entity import ( "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/cube/trace" - "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/particle" "github.com/df-mc/dragonfly/server/world/sound" @@ -12,8 +11,10 @@ import ( // NewEnderPearl creates an EnderPearl entity. EnderPearl is a smooth, greenish- // blue item used to teleport. -func NewEnderPearl(pos mgl64.Vec3, owner world.Entity) *Ent { - return Config{Behaviour: enderPearlConf.New()}.New(EnderPearlType{}, pos) +func NewEnderPearl(opts world.EntitySpawnOpts, owner world.Entity) *world.EntityHandle { + conf := enderPearlConf + conf.Owner = owner + return opts.New(EnderPearlType{}, conf) } var enderPearlConf = ProjectileBehaviourConfig{ @@ -43,21 +44,15 @@ func teleport(e *Ent, tx *world.Tx, target trace.Result) { // EnderPearlType is a world.EntityType implementation for EnderPearl. type EnderPearlType struct{} +func (t EnderPearlType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + func (EnderPearlType) EncodeEntity() string { return "minecraft:ender_pearl" } func (EnderPearlType) BBox(world.Entity) cube.BBox { return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125) } - -func (EnderPearlType) DecodeNBT(m map[string]any) world.Entity { - ep := NewEnderPearl(nbtconv.Vec3(m, "Pos"), nil) - ep.vel = nbtconv.Vec3(m, "Motion") - return ep -} - -func (EnderPearlType) EncodeNBT(e world.Entity) map[string]any { - ep := e.(*Ent) - return map[string]any{ - "Pos": nbtconv.Vec3ToFloat32Slice(ep.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(ep.Velocity()), - } +func (EnderPearlType) DecodeNBT(_ map[string]any, data *world.EntityData) { + data.Data = enderPearlConf.New() } +func (EnderPearlType) EncodeNBT(*world.EntityData) map[string]any { return nil } diff --git a/server/entity/experience.go b/server/entity/experience.go index 5b45354e3..7fc36b8a7 100644 --- a/server/entity/experience.go +++ b/server/entity/experience.go @@ -3,15 +3,13 @@ package entity import ( "fmt" "math" - "sync" ) // ExperienceManager manages experience and levels for entities, and provides functions to add, remove, and calculate // experience needed for upcoming levels. type ExperienceManager struct { - mu sync.RWMutex experience int - d float64 + display float64 } // NewExperienceManager returns a new ExperienceManager with no experience. @@ -21,33 +19,25 @@ func NewExperienceManager() *ExperienceManager { // Experience returns the amount of experience the manager currently has. func (e *ExperienceManager) Experience() int { - e.mu.RLock() - defer e.mu.RUnlock() return e.experience } // Add adds experience to the total experience and recalculates the level and progress if necessary. Passing a negative // value is valid. If the new experience would otherwise drop below 0, it is set to 0. func (e *ExperienceManager) Add(amount int) (level int, progress float64) { - e.mu.Lock() - defer e.mu.Unlock() - e.experience += amount - if e.experience < 0 { - e.experience = 0 - e.d = 0 + if e.experience += amount; e.experience < 0 { + e.Reset() } return progressFromExperience(e.total()) } // total returns the total amount of experience including the extra decimals provided for more accuracy. func (e *ExperienceManager) total() float64 { - return float64(e.experience) + e.d + return float64(e.experience) + e.display } // Level returns the current experience level. func (e *ExperienceManager) Level() int { - e.mu.RLock() - defer e.mu.RUnlock() level, _ := progressFromExperience(e.total()) return level } @@ -55,18 +45,14 @@ func (e *ExperienceManager) Level() int { // SetLevel sets the level of the manager. func (e *ExperienceManager) SetLevel(level int) { if level < 0 || level > math.MaxInt32 { - panic(fmt.Sprintf("level must be between 0 and 2,147,483,647, got %d", level)) + panic(fmt.Sprintf("level must be between 0 and 2,147,483,647, got %display", level)) } - e.mu.Lock() - defer e.mu.Unlock() _, progress := progressFromExperience(e.total()) e.experience = experienceForLevels(level) + int(float64(experienceForLevel(level))*progress) } // Progress returns the progress towards the next level. func (e *ExperienceManager) Progress() float64 { - e.mu.RLock() - defer e.mu.RUnlock() _, progress := progressFromExperience(e.total()) return progress } @@ -76,19 +62,15 @@ func (e *ExperienceManager) SetProgress(progress float64) { if progress < 0 || progress > 1 { panic(fmt.Sprintf("progress must be between 0 and 1, got %f", progress)) } - e.mu.Lock() - defer e.mu.Unlock() currentLevel, _ := progressFromExperience(e.total()) progressExp := float64(experienceForLevel(currentLevel)) * progress e.experience = experienceForLevels(currentLevel) + int(progressExp) - e.d = progressExp - math.Trunc(progressExp) + e.display = progressExp - math.Trunc(progressExp) } // Reset resets the total experience, level, and progress of the manager to zero. func (e *ExperienceManager) Reset() { - e.mu.Lock() - defer e.mu.Unlock() - e.experience, e.d = 0, 0 + e.experience, e.display = 0, 0 } // progressFromExperience returns the level and progress from the total experience given. diff --git a/server/entity/experience_orb.go b/server/entity/experience_orb.go index fc24b4b69..6c23772e4 100644 --- a/server/entity/experience_orb.go +++ b/server/entity/experience_orb.go @@ -5,8 +5,8 @@ import ( "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" + "math/rand" "slices" - "time" ) // orbSplitSizes contains split sizes used for dropping experience orbs. @@ -14,23 +14,26 @@ var orbSplitSizes = []int{2477, 1237, 617, 307, 149, 73, 37, 17, 7, 3, 1} // NewExperienceOrbs takes in a position and an amount and automatically splits the amount into multiple orbs, returning // a slice of the created orbs. -func NewExperienceOrbs(pos mgl64.Vec3, amount int) (orbs []*Ent) { +func NewExperienceOrbs(pos mgl64.Vec3, amount int) (orbs []*world.EntityHandle) { for amount > 0 { size := orbSplitSizes[slices.IndexFunc(orbSplitSizes, func(value int) bool { return amount >= value })] - orbs = append(orbs, NewExperienceOrb(pos, size)) + orbs = append(orbs, NewExperienceOrb(world.EntitySpawnOpts{Position: pos}, size)) amount -= size } return } // NewExperienceOrb creates a new experience orb and returns it. -func NewExperienceOrb(pos mgl64.Vec3, xp int) *Ent { +func NewExperienceOrb(opts world.EntitySpawnOpts, xp int) *world.EntityHandle { conf := experienceOrbConf conf.Experience = xp - return Config{Behaviour: conf.New()}.New(ExperienceOrbType{}, pos) + if opts.Velocity.Len() == 0 { + opts.Velocity = mgl64.Vec3{(rand.Float64()*0.2 - 0.1) * 2, rand.Float64() * 0.4, (rand.Float64()*0.2 - 0.1) * 2} + } + return opts.New(ExperienceOrbType{}, conf) } var experienceOrbConf = ExperienceOrbBehaviourConfig{ @@ -41,24 +44,21 @@ var experienceOrbConf = ExperienceOrbBehaviourConfig{ // ExperienceOrbType is a world.EntityType implementation for ExperienceOrb. type ExperienceOrbType struct{} +func (t ExperienceOrbType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + func (ExperienceOrbType) EncodeEntity() string { return "minecraft:xp_orb" } func (ExperienceOrbType) BBox(world.Entity) cube.BBox { return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125) } -func (ExperienceOrbType) DecodeNBT(m map[string]any) world.Entity { - o := NewExperienceOrb(nbtconv.Vec3(m, "Pos"), int(nbtconv.Int32(m, "Value"))) - o.vel = nbtconv.Vec3(m, "Motion") - o.age = time.Duration(nbtconv.Int16(m, "Age")) * (time.Second / 20) - return o +func (ExperienceOrbType) DecodeNBT(m map[string]any, data *world.EntityData) { + conf := experienceOrbConf + conf.Experience = int(nbtconv.Int32(m, "Value")) + data.Data = conf.New() } -func (ExperienceOrbType) EncodeNBT(e world.Entity) map[string]any { - orb := e.(*Ent) - return map[string]any{ - "Age": int16(orb.Age() / (time.Second * 20)), - "Value": int32(orb.Behaviour().(*ExperienceOrbBehaviour).Experience()), - "Pos": nbtconv.Vec3ToFloat32Slice(orb.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(orb.Velocity()), - } +func (ExperienceOrbType) EncodeNBT(data *world.EntityData) map[string]any { + return map[string]any{"Value": int32(data.Data.(*ExperienceOrbBehaviour).Experience())} } diff --git a/server/entity/experience_orb_behaviour.go b/server/entity/experience_orb_behaviour.go index fc2f14934..3a1f5447a 100644 --- a/server/entity/experience_orb_behaviour.go +++ b/server/entity/experience_orb_behaviour.go @@ -23,6 +23,10 @@ type ExperienceOrbBehaviourConfig struct { Experience int } +func (conf ExperienceOrbBehaviourConfig) Apply(data *world.EntityData) { + data.Data = conf.New() +} + // New creates an ExperienceOrbBehaviour using the parameters in conf. func (conf ExperienceOrbBehaviourConfig) New() *ExperienceOrbBehaviour { if conf.Experience == 0 { diff --git a/server/entity/falling_block.go b/server/entity/falling_block.go index 55bebd763..2e5052fc1 100644 --- a/server/entity/falling_block.go +++ b/server/entity/falling_block.go @@ -4,13 +4,13 @@ import ( "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/world" - "github.com/go-gl/mathgl/mgl64" - "math/rand" ) // NewFallingBlock creates a new FallingBlock entity. -func NewFallingBlock(block world.Block, pos mgl64.Vec3) *Ent { - return Config{Behaviour: fallingBlockConf.New(block)}.New(FallingBlockType{}, pos) +func NewFallingBlock(opts world.EntitySpawnOpts, block world.Block) *world.EntityHandle { + conf := fallingBlockConf + conf.Block = block + return opts.New(FallingBlockType{}, conf) } var fallingBlockConf = FallingBlockBehaviourConfig{ @@ -21,31 +21,23 @@ var fallingBlockConf = FallingBlockBehaviourConfig{ // FallingBlockType is a world.EntityType implementation for FallingBlock. type FallingBlockType struct{} +func (t FallingBlockType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} func (FallingBlockType) EncodeEntity() string { return "minecraft:falling_block" } func (FallingBlockType) NetworkOffset() float64 { return 0.49 } func (FallingBlockType) BBox(world.Entity) cube.BBox { return cube.Box(-0.49, 0, -0.49, 0.49, 0.98, 0.49) } -func (FallingBlockType) DecodeNBT(m map[string]any) world.Entity { - b := nbtconv.Block(m, "FallingBlock") - if b == nil { - return nil - } - n := NewFallingBlock(b, nbtconv.Vec3(m, "Pos")) - n.SetVelocity(nbtconv.Vec3(m, "Motion")) - n.Behaviour().(*FallingBlockBehaviour).passive.fallDistance = nbtconv.Float64(m, "FallDistance") - return n +func (FallingBlockType) DecodeNBT(m map[string]any, data *world.EntityData) { + conf := fallingBlockConf + conf.Block = nbtconv.Block(m, "FallingBlock") + conf.DistanceFallen = nbtconv.Float64(m, "FallDistance") + data.Data = conf.New() } -func (FallingBlockType) EncodeNBT(e world.Entity) map[string]any { - f := e.(*Ent) - b := f.Behaviour().(*FallingBlockBehaviour) - return map[string]any{ - "UniqueID": -rand.Int63(), - "FallDistance": b.passive.fallDistance, - "Pos": nbtconv.Vec3ToFloat32Slice(f.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(f.Velocity()), - "FallingBlock": nbtconv.WriteBlock(b.block), - } +func (FallingBlockType) EncodeNBT(data *world.EntityData) map[string]any { + b := data.Data.(*FallingBlockBehaviour) + return map[string]any{"FallDistance": b.passive.fallDistance, "FallingBlock": nbtconv.WriteBlock(b.block)} } diff --git a/server/entity/falling_block_behaviour.go b/server/entity/falling_block_behaviour.go index 17554aa38..fcab2752b 100644 --- a/server/entity/falling_block_behaviour.go +++ b/server/entity/falling_block_behaviour.go @@ -13,22 +13,32 @@ import ( // FallingBlockBehaviourConfig holds optional parameters for // FallingBlockBehaviour. type FallingBlockBehaviourConfig struct { + Block world.Block // Gravity is the amount of Y velocity subtracted every tick. Gravity float64 // Drag is used to reduce all axes of the velocity every tick. Velocity is // multiplied with (1-Drag) every tick. Drag float64 + // DistanceFallen specifies how far the falling block has already fallen. + // Blocks that damage entities on impact, like anvils, deal increased damage + // based on the distance fallen. + DistanceFallen float64 +} + +func (conf FallingBlockBehaviourConfig) Apply(data *world.EntityData) { + data.Data = conf.New() } // New creates a FallingBlockBehaviour using the optional parameters in conf and // a block type. -func (conf FallingBlockBehaviourConfig) New(b world.Block) *FallingBlockBehaviour { - behaviour := &FallingBlockBehaviour{block: b} +func (conf FallingBlockBehaviourConfig) New() *FallingBlockBehaviour { + behaviour := &FallingBlockBehaviour{block: conf.Block} behaviour.passive = PassiveBehaviourConfig{ Gravity: conf.Gravity, Drag: conf.Drag, Tick: behaviour.tick, }.New() + behaviour.passive.fallDistance = conf.DistanceFallen return behaviour } @@ -75,7 +85,8 @@ func (f *FallingBlockBehaviour) solidify(e *Ent, pos mgl64.Vec3, tx *world.Tx) { if r, ok := tx.Block(bpos).(replaceable); ok && r.ReplaceableBy(f.block) { tx.SetBlock(bpos, f.block, nil) } else if i, ok := f.block.(world.Item); ok { - tx.AddEntity(NewItem(item.NewStack(i, 1), bpos.Vec3Middle())) + opts := world.EntitySpawnOpts{Position: bpos.Vec3Middle()} + tx.AddEntity(NewItem(opts, item.NewStack(i, 1))) } } diff --git a/server/entity/firework.go b/server/entity/firework.go index 24235cb43..5d18d0c7e 100644 --- a/server/entity/firework.go +++ b/server/entity/firework.go @@ -5,53 +5,48 @@ import ( "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" - "github.com/go-gl/mathgl/mgl64" ) // NewFirework creates a firework entity. Firework is an item (and entity) used // for creating decorative explosions, boosting when flying with elytra, and // loading into a crossbow as ammunition. -func NewFirework(pos mgl64.Vec3, rot cube.Rotation, firework item.Firework) *Ent { - return NewFireworkAttached(pos, rot, firework, nil, false) +func NewFirework(opts world.EntitySpawnOpts, firework item.Firework) *world.EntityHandle { + return NewFireworkAttached(opts, firework, nil, false) } // NewFireworkAttached creates a firework entity with an owner that the firework // may be attached to. -func NewFireworkAttached(pos mgl64.Vec3, rot cube.Rotation, firework item.Firework, owner world.Entity, attached bool) *Ent { - e := Config{Behaviour: FireworkBehaviourConfig{ - ExistenceDuration: firework.RandomisedDuration(), - SidewaysVelocityMultiplier: 1.15, - UpwardsAcceleration: 0.04, - Attached: attached, - }.New(firework, owner)}.New(FireworkType{}, pos) - e.rot = rot - return e +func NewFireworkAttached(opts world.EntitySpawnOpts, firework item.Firework, owner world.Entity, attached bool) *world.EntityHandle { + conf := fireworkConf + conf.ExistenceDuration = firework.RandomisedDuration() + conf.Attached = attached + conf.Owner = owner + return world.NewEntity(FireworkType{}, conf) +} + +var fireworkConf = FireworkBehaviourConfig{ + SidewaysVelocityMultiplier: 1.15, + UpwardsAcceleration: 0.04, } // FireworkType is a world.EntityType implementation for Firework. type FireworkType struct{} +func (t FireworkType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + func (FireworkType) EncodeEntity() string { return "minecraft:fireworks_rocket" } func (FireworkType) BBox(world.Entity) cube.BBox { return cube.BBox{} } -func (FireworkType) DecodeNBT(m map[string]any) world.Entity { - f := NewFirework( - nbtconv.Vec3(m, "Pos"), - nbtconv.Rotation(m), - nbtconv.MapItem(m, "Item").Item().(item.Firework), - ) - f.vel = nbtconv.Vec3(m, "Motion") - return f +func (FireworkType) DecodeNBT(m map[string]any, data *world.EntityData) { + conf := fireworkConf + conf.Firework = nbtconv.MapItem(m, "Item").Item().(item.Firework) + conf.ExistenceDuration = conf.Firework.RandomisedDuration() + + data.Data = conf.New() } -func (FireworkType) EncodeNBT(e world.Entity) map[string]any { - f := e.(*Ent) - yaw, pitch := f.Rotation().Elem() - return map[string]any{ - "Item": nbtconv.WriteItem(item.NewStack(f.Behaviour().(*FireworkBehaviour).Firework(), 1), true), - "Pos": nbtconv.Vec3ToFloat32Slice(f.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(f.Velocity()), - "Yaw": float32(yaw), - "Pitch": float32(pitch), - } +func (FireworkType) EncodeNBT(data *world.EntityData) map[string]any { + return map[string]any{"Item": nbtconv.WriteItem(item.NewStack(data.Data.(*FireworkBehaviour).Firework(), 1), true)} } diff --git a/server/entity/firework_behaviour.go b/server/entity/firework_behaviour.go index 3e258c8eb..a40cb8fb0 100644 --- a/server/entity/firework_behaviour.go +++ b/server/entity/firework_behaviour.go @@ -12,6 +12,8 @@ import ( // FireworkBehaviourConfig holds optional parameters for a FireworkBehaviour. type FireworkBehaviourConfig struct { + Firework item.Firework + Owner world.Entity // 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. @@ -27,10 +29,14 @@ type FireworkBehaviourConfig struct { 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(fw item.Firework, owner world.Entity) *FireworkBehaviour { - b := &FireworkBehaviour{conf: conf, firework: fw, owner: owner} +func (conf FireworkBehaviourConfig) New() *FireworkBehaviour { + b := &FireworkBehaviour{conf: conf} b.passive = PassiveBehaviourConfig{ ExistenceDuration: conf.ExistenceDuration, Expire: b.explode, @@ -41,17 +47,13 @@ func (conf FireworkBehaviourConfig) New(fw item.Firework, owner world.Entity) *F // FireworkBehaviour implements Behaviour for a firework entity. type FireworkBehaviour struct { - conf FireworkBehaviourConfig - + conf FireworkBehaviourConfig passive *PassiveBehaviour - - firework item.Firework - owner world.Entity } // Firework returns the underlying item.Firework of the FireworkBehaviour. func (f *FireworkBehaviour) Firework() item.Firework { - return f.firework + return f.conf.Firework } // Attached specifies if the firework is attached to its owner. @@ -61,7 +63,7 @@ func (f *FireworkBehaviour) Attached() bool { // Owner returns the world.Entity that launched the firework. func (f *FireworkBehaviour) Owner() world.Entity { - return f.owner + return f.conf.Owner } // Tick moves the firework and makes it explode when it reaches its maximum @@ -74,32 +76,30 @@ func (f *FireworkBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { // or based on the owner's position and velocity if attached. func (f *FireworkBehaviour) tick(e *Ent, _ *world.Tx) { var ownerVel mgl64.Vec3 - if o, ok := f.owner.(interface { + if o, ok := f.conf.Owner.(interface { Velocity() mgl64.Vec3 }); ok { ownerVel = o.Velocity() } - e.mu.Lock() - defer e.mu.Unlock() if f.conf.Attached { - dV := f.owner.Rotation().Vec3() + dV := f.conf.Owner.Rotation().Vec3() // The client will propel itself to match the firework's velocity since // we set the appropriate metadata. - e.pos = f.owner.Position() - e.vel = e.vel.Add(ownerVel.Add(dV.Mul(0.1).Add(dV.Mul(1.5).Sub(ownerVel).Mul(0.5)))) + e.data.Pos = f.conf.Owner.Position() + e.data.Vel = e.data.Vel.Add(ownerVel.Add(dV.Mul(0.1).Add(dV.Mul(1.5).Sub(ownerVel).Mul(0.5)))) } else { - e.vel[0] *= f.conf.SidewaysVelocityMultiplier - e.vel[1] += f.conf.UpwardsAcceleration - e.vel[2] *= f.conf.SidewaysVelocityMultiplier + 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) { - pos, explosions := e.Position(), f.firework.Explosions + pos, explosions := e.Position(), f.conf.Firework.Explosions for _, v := range tx.Viewers(pos) { v.ViewEntityAction(e, FireworkExplosionAction{}) @@ -132,7 +132,7 @@ func (f *FireworkBehaviour) explode(e *Ent, tx *world.Tx) { continue } dmg := force * math.Sqrt((5.0-dist)/5.0) - src := ProjectileDamageSource{Owner: f.owner, Projectile: e} + src := ProjectileDamageSource{Owner: f.conf.Owner, Projectile: e} if pos == tpos { e.(Living).Hurt(dmg, src) diff --git a/server/entity/health.go b/server/entity/health.go index 7198e12fa..afd54c576 100644 --- a/server/entity/health.go +++ b/server/entity/health.go @@ -1,10 +1,7 @@ package entity -import "sync" - // HealthManager handles the health of an entity. type HealthManager struct { - mu sync.RWMutex health float64 max float64 } @@ -19,8 +16,6 @@ func NewHealthManager(health, max float64) *HealthManager { // Health returns the current health of an entity. func (m *HealthManager) Health() float64 { - m.mu.RLock() - defer m.mu.RUnlock() return m.health } @@ -28,23 +23,11 @@ func (m *HealthManager) Health() float64 { // exceeds the max, health will be set to the max. If the health is instead negative and results in a health // lower than 0, the final health will be 0. func (m *HealthManager) AddHealth(health float64) { - m.mu.Lock() - defer m.mu.Unlock() - - l := m.health + health - if l < 0 { - l = 0 - } else if l > m.max { - l = m.max - } - m.health = l + m.health = max(min(m.health+health, m.max), 0) } // MaxHealth returns the maximum health of the entity. func (m *HealthManager) MaxHealth() float64 { - m.mu.RLock() - defer m.mu.RUnlock() - return m.max } @@ -54,10 +37,6 @@ func (m *HealthManager) SetMaxHealth(max float64) { if max <= 0 { max = 1 } - m.mu.Lock() - defer m.mu.Unlock() m.max = max - if m.health > max { - m.health = max - } + m.health = min(m.health, max) } diff --git a/server/entity/item.go b/server/entity/item.go index 3391e8213..339b6274f 100644 --- a/server/entity/item.go +++ b/server/entity/item.go @@ -5,7 +5,6 @@ import ( "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" - "github.com/go-gl/mathgl/mgl64" "time" ) @@ -13,17 +12,20 @@ import ( // entity will be positioned at the position passed. If the stack's count // exceeds its max count, the count of the stack will be changed to the // maximum. -func NewItem(i item.Stack, pos mgl64.Vec3) *Ent { - return Config{Behaviour: itemConf.New(i)}.New(ItemType{}, pos) +func NewItem(opts world.EntitySpawnOpts, i item.Stack) *world.EntityHandle { + conf := itemConf + conf.Item = i + return opts.New(ItemType{}, conf) } // NewItemPickupDelay creates a new item entity containing item stack i. A // delay may be specified which defines for how long the item stack cannot be // picked up from the ground. -func NewItemPickupDelay(i item.Stack, pos mgl64.Vec3, delay time.Duration) *Ent { - config := itemConf - config.PickupDelay = delay - return Config{Behaviour: config.New(i)}.New(ItemType{}, pos) +func NewItemPickupDelay(opts world.EntitySpawnOpts, i item.Stack, delay time.Duration) *world.EntityHandle { + conf := itemConf + conf.Item = i + conf.PickupDelay = delay + return opts.New(ItemType{}, conf) } var itemConf = ItemBehaviourConfig{ @@ -34,33 +36,29 @@ var itemConf = ItemBehaviourConfig{ // ItemType is a world.EntityType implementation for Item. type ItemType struct{} +func (t ItemType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + func (ItemType) EncodeEntity() string { return "minecraft:item" } func (ItemType) NetworkOffset() float64 { return 0.125 } func (ItemType) BBox(world.Entity) cube.BBox { return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125) } -func (ItemType) DecodeNBT(m map[string]any) world.Entity { - i := nbtconv.MapItem(m, "Item") - if i.Empty() { - return nil - } - n := NewItem(i, nbtconv.Vec3(m, "Pos")) - n.SetVelocity(nbtconv.Vec3(m, "Motion")) - n.age = time.Duration(nbtconv.Int16(m, "Age")) * (time.Second / 20) - n.Behaviour().(*ItemBehaviour).pickupDelay = time.Duration(nbtconv.Int64(m, "PickupDelay")) * (time.Second / 20) - return n +func (ItemType) DecodeNBT(m map[string]any, data *world.EntityData) { + conf := itemConf + conf.Item = nbtconv.MapItem(m, "Item") + conf.PickupDelay = time.Duration(nbtconv.Int64(m, "PickupDelay")) * (time.Second / 20) + + data.Data = conf.New() } -func (ItemType) EncodeNBT(e world.Entity) map[string]any { - it := e.(*Ent) - b := it.Behaviour().(*ItemBehaviour) +func (ItemType) EncodeNBT(data *world.EntityData) map[string]any { + b := data.Data.(*ItemBehaviour) return map[string]any{ "Health": int16(5), - "Age": int16(it.Age() / (time.Second * 20)), "PickupDelay": int64(b.pickupDelay / (time.Second * 20)), - "Pos": nbtconv.Vec3ToFloat32Slice(it.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(it.Velocity()), "Item": nbtconv.WriteItem(b.Item(), true), } } diff --git a/server/entity/item_behaviour.go b/server/entity/item_behaviour.go index eee651001..7d1455ecd 100644 --- a/server/entity/item_behaviour.go +++ b/server/entity/item_behaviour.go @@ -13,6 +13,7 @@ import ( // ItemBehaviourConfig holds optional parameters for an ItemBehaviour. type ItemBehaviourConfig struct { + Item item.Stack // Gravity is the amount of Y velocity subtracted every tick. Gravity float64 // Drag is used to reduce all axes of the velocity every tick. Velocity is @@ -26,8 +27,13 @@ type ItemBehaviourConfig struct { PickupDelay time.Duration } +func (conf ItemBehaviourConfig) Apply(data *world.EntityData) { + data.Data = conf.New() +} + // New creates an ItemBehaviour using i and the optional parameters in conf. -func (conf ItemBehaviourConfig) New(i item.Stack) *ItemBehaviour { +func (conf ItemBehaviourConfig) New() *ItemBehaviour { + i := conf.Item if i.Count() > i.MaxCount() { i = i.Grow(i.MaxCount() - i.Count()) } @@ -79,7 +85,8 @@ func (i *ItemBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { } // This is only reached if part of the item stack was collected into the hopper. - tx.AddEntity(NewItem(i.Item().Grow(-addedCount), pos.Vec3Centre())) + opts := world.EntitySpawnOpts{Position: pos.Vec3Centre()} + tx.AddEntity(NewItem(opts, i.Item().Grow(-addedCount))) } _ = e.Close() @@ -137,14 +144,9 @@ func (i *ItemBehaviour) merge(e *Ent, other *Ent, tx *world.Tx) bool { } a, b := otherBehaviour.i.AddStack(i.i) - newA := NewItem(a, other.Position()) - newA.SetVelocity(other.Velocity()) - tx.AddEntity(newA) - + tx.AddEntity(NewItem(world.EntitySpawnOpts{Position: other.Position(), Velocity: other.Velocity()}, a)) if !b.Empty() { - newB := NewItem(b, pos) - newB.SetVelocity(e.Velocity()) - tx.AddEntity(newB) + tx.AddEntity(NewItem(world.EntitySpawnOpts{Position: pos, Velocity: e.Velocity()}, b)) } _ = e.Close() _ = other.Close() @@ -169,7 +171,7 @@ func (i *ItemBehaviour) collect(e *Ent, collector Collector, tx *world.Tx) { } // Create a new item entity and shrink it by the amount of items that the // collector collected. - tx.AddEntity(NewItem(i.i.Grow(-n), pos)) + tx.AddEntity(NewItem(world.EntitySpawnOpts{Position: pos}, i.i.Grow(-n))) _ = e.Close() } diff --git a/server/entity/lightning.go b/server/entity/lightning.go index 1d41354e9..b67118f78 100644 --- a/server/entity/lightning.go +++ b/server/entity/lightning.go @@ -31,7 +31,7 @@ func NewLightningWithDamage(opts world.EntitySpawnOpts, dmg float64, blockFire b return opts.New(LightningType{}, conf) } -var lightningConf = StationaryBehaviourConfig{SpawnSounds: []world.Sound{sound.Explosion{}, sound.Thunder{}}} +var lightningConf = StationaryBehaviourConfig{SpawnSounds: []world.Sound{sound.Explosion{}, sound.Thunder{}}, ExistenceDuration: time.Second} // lightningState holds the state of a lightning entity. type lightningState struct { @@ -115,7 +115,9 @@ 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, *world.EntityData) {} -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{} } +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{} } diff --git a/server/entity/movement.go b/server/entity/movement.go index 070bb6d93..744e814c7 100644 --- a/server/entity/movement.go +++ b/server/entity/movement.go @@ -124,7 +124,7 @@ func (c *MovementComputer) checkCollision(tx *world.Tx, e world.Entity, pos, vel // Entities only ever have a single bounding box. entityBBox := e.Type().BBox(e).Translate(pos) - blocks := blockBBoxsAround(tx, e, entityBBox.Extend(vel)) + blocks := blockBBoxsAround(tx, entityBBox.Extend(vel)) if !mgl64.FloatEqualThreshold(deltaY, 0, epsilon) { // First we move the entity BBox on the Y axis. @@ -170,7 +170,7 @@ func (c *MovementComputer) checkCollision(tx *world.Tx, e world.Entity, pos, vel // blockBBoxsAround returns all blocks around the entity passed, using the BBox passed to make a prediction of // what blocks need to have their BBox returned. -func blockBBoxsAround(tx *world.Tx, e world.Entity, box cube.BBox) []cube.BBox { +func blockBBoxsAround(tx *world.Tx, box cube.BBox) []cube.BBox { grown := box.Grow(0.25) min, max := grown.Min(), grown.Max() minX, minY, minZ := int(math.Floor(min[0])), int(math.Floor(min[1])), int(math.Floor(min[2])) diff --git a/server/entity/passive.go b/server/entity/passive.go index 5120f366e..d1d8b500a 100644 --- a/server/entity/passive.go +++ b/server/entity/passive.go @@ -27,6 +27,10 @@ type PassiveBehaviourConfig struct { Tick func(e *Ent, tx *world.Tx) } +func (conf PassiveBehaviourConfig) Apply(data *world.EntityData) { + data.Data = conf.New() +} + // New creates a PassiveBehaviour using the parameters in conf. func (conf PassiveBehaviourConfig) New() *PassiveBehaviour { if conf.ExistenceDuration == 0 { @@ -54,7 +58,7 @@ type PassiveBehaviour struct { // Explode adds velocity to a passive entity to blast it away from the // explosion's source. func (p *PassiveBehaviour) Explode(e *Ent, src mgl64.Vec3, impact float64, _ block.ExplosionConfig) { - e.vel = e.vel.Add(e.pos.Sub(src).Normalize().Mul(impact)) + e.data.Vel = e.data.Vel.Add(e.data.Pos.Sub(src).Normalize().Mul(impact)) } // Fuse returns the leftover time until PassiveBehaviourConfig.Expire is called, @@ -73,13 +77,10 @@ func (p *PassiveBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { _ = e.Close() return nil } - e.mu.Lock() - - m := p.mc.TickMovement(e, e.pos, e.vel, e.rot, tx) - e.pos, e.vel = m.pos, m.vel + m := p.mc.TickMovement(e, e.data.Pos, e.data.Vel, e.data.Rot, tx) + e.data.Pos, e.data.Vel = m.pos, m.vel p.fallDistance = math.Max(p.fallDistance-m.dvel[1], 0) - e.mu.Unlock() p.fuse = p.conf.ExistenceDuration - e.Age() diff --git a/server/entity/projectile.go b/server/entity/projectile.go index 6c655494c..057310702 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -117,7 +117,7 @@ func (lt *ProjectileBehaviour) Owner() world.Entity { // Explode adds velocity to a projectile to blast it away from the explosion's // source. func (lt *ProjectileBehaviour) Explode(e *Ent, src mgl64.Vec3, impact float64, _ block.ExplosionConfig) { - e.vel = e.vel.Add(e.pos.Sub(src).Normalize().Mul(impact)) + e.data.Vel = e.Velocity().Add(e.Position().Sub(src).Normalize().Mul(impact)) } // Potion returns the potion.Potion that is applied to an entity if hit by the @@ -141,21 +141,17 @@ func (lt *ProjectileBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { return nil } - e.mu.Lock() if lt.collided && lt.tickAttached(e, tx) { - e.mu.Unlock() - if lt.ageCollided > 1200 { lt.close = true } return nil } - before, vel := e.pos, e.vel + before, vel := e.Position(), e.Velocity() m, result := lt.tickMovement(e, tx) - e.pos, e.vel = m.pos, m.vel + e.data.Pos, e.data.Vel = m.pos, m.vel lt.collisionPos, lt.collided, lt.ageCollided = cube.Pos{}, false, 0 - e.mu.Unlock() if result == nil { return m @@ -176,7 +172,7 @@ func (lt *ProjectileBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { case trace.BlockResult: bpos := r.BlockPosition() if t, ok := tx.Block(bpos).(block.TNT); ok && e.OnFireDuration() > 0 { - t.Ignite(bpos, tx, e) + t.Ignite(bpos, tx) } if lt.conf.SurviveBlockCollision { lt.hitBlockSurviving(e, r, m, tx) @@ -195,7 +191,7 @@ func (lt *ProjectileBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { // projectile is still attached to a block and if it can be picked up. func (lt *ProjectileBehaviour) tickAttached(e *Ent, tx *world.Tx) bool { boxes := tx.Block(lt.collisionPos).Model().BBox(lt.collisionPos, tx) - box := e.Type().BBox(e).Translate(e.pos) + box := e.Type().BBox(e).Translate(e.Position()) for _, bb := range boxes { if box.IntersectsWith(bb.Translate(lt.collisionPos.Vec3()).Grow(0.05)) { @@ -212,7 +208,7 @@ func (lt *ProjectileBehaviour) tickAttached(e *Ent, tx *world.Tx) bool { // tryPickup checks for nearby projectile collectors and closes the entity if // one was found. func (lt *ProjectileBehaviour) tryPickup(e *Ent, tx *world.Tx) { - translated := e.Type().BBox(e).Translate(e.pos) + translated := e.Type().BBox(e).Translate(e.Position()) grown := translated.GrowVec3(mgl64.Vec3{1, 0.5, 1}) ignore := func(other world.Entity) bool { return e == other @@ -227,7 +223,7 @@ func (lt *ProjectileBehaviour) tryPickup(e *Ent, tx *world.Tx) { } // A collector was within range to pick up the entity. lt.close = true - for _, viewer := range tx.Viewers(e.pos) { + for _, viewer := range tx.Viewers(e.Position()) { viewer.ViewEntityAction(e, PickedUpAction{Collector: collector}) } if lt.conf.PickupItem.Empty() { @@ -242,16 +238,14 @@ func (lt *ProjectileBehaviour) tryPickup(e *Ent, tx *world.Tx) { // projectile collides with a block. If the resulting velocity is roughly 0, // it sets the projectile as having collided with the block. func (lt *ProjectileBehaviour) hitBlockSurviving(e *Ent, r trace.BlockResult, m *Movement, tx *world.Tx) { - e.mu.Lock() // Create an epsilon for deciding if the projectile has slowed down enough // for us to consider it as having collided for the final time. We take the // square root because FloatEqualThreshold squares it, which is not what we // want. eps := math.Sqrt(0.1 * (1 - lt.conf.BlockCollisionVelocityMultiplier)) - if mgl64.FloatEqualThreshold(e.vel.Len(), 0, eps) { - e.vel = mgl64.Vec3{} + if mgl64.FloatEqualThreshold(e.Velocity().Len(), 0, eps) { + e.SetVelocity(mgl64.Vec3{}) lt.collisionPos, lt.collided = r.BlockPosition(), true - e.mu.Unlock() for _, v := range tx.Viewers(m.pos) { v.ViewEntityAction(e, ArrowShakeAction{Duration: time.Millisecond * 350}) @@ -259,7 +253,6 @@ func (lt *ProjectileBehaviour) hitBlockSurviving(e *Ent, r trace.BlockResult, m } return } - e.mu.Unlock() } // hitEntity is called when a projectile hits a Living. It deals damage to the @@ -287,7 +280,7 @@ func (lt *ProjectileBehaviour) hitEntity(l Living, e *Ent, origin, vel mgl64.Vec // rotation of the projectile based on its velocity and updates the velocity // based on gravity and drag. func (lt *ProjectileBehaviour) tickMovement(e *Ent, tx *world.Tx) (*Movement, trace.Result) { - pos, vel := e.pos, e.vel + pos, vel := e.Position(), e.Velocity() viewers := tx.Viewers(pos) velBefore := vel diff --git a/server/entity/register.go b/server/entity/register.go index 4e7e76d68..5bb000f0f 100644 --- a/server/entity/register.go +++ b/server/entity/register.go @@ -1,14 +1,10 @@ package entity import ( - "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/item/enchantment" "github.com/df-mc/dragonfly/server/item/potion" "github.com/df-mc/dragonfly/server/world" - "github.com/go-gl/mathgl/mgl64" - "golang.org/x/tools/go/cfg" - "time" ) // DefaultRegistry is a world.EntityRegistry that registers all default entities @@ -32,60 +28,34 @@ var DefaultRegistry = conf.New([]world.EntityType{ }) var conf = world.EntityRegistryConfig{ - Item: func(opts world.EntitySpawnOpts, it any) *world.EntityHandle { - i := NewItem(it.(item.Stack), pos) - i.vel = vel - return i + TNT: NewTNT, + Egg: NewEgg, + Snowball: NewSnowball, + BottleOfEnchanting: NewBottleOfEnchanting, + EnderPearl: NewEnderPearl, + FallingBlock: NewFallingBlock, + Lightning: NewLightning, + Firework: func(opts world.EntitySpawnOpts, firework world.Item, owner world.Entity, attached bool) *world.EntityHandle { + return NewFireworkAttached(opts, firework.(item.Firework), owner, attached) }, - FallingBlock: func(bl world.Block, pos mgl64.Vec3) *world.EntityHandle { - return NewFallingBlock(bl, pos) + Item: func(opts world.EntitySpawnOpts, it any) *world.EntityHandle { + return NewItem(opts, it.(item.Stack)) }, - TNT: func(pos mgl64.Vec3, fuse time.Duration, igniter world.Entity) *world.EntityHandle { - return NewTNT(pos, fuse, igniter) + LingeringPotion: func(opts world.EntitySpawnOpts, t any, owner world.Entity) *world.EntityHandle { + return NewLingeringPotion(opts, t.(potion.Potion), owner) }, - BottleOfEnchanting: func(pos, vel mgl64.Vec3, owner world.Entity) *world.EntityHandle { - b := NewBottleOfEnchanting(pos, owner) - b.vel = vel - return b + SplashPotion: func(opts world.EntitySpawnOpts, t any, owner world.Entity) *world.EntityHandle { + return NewSplashPotion(opts, t.(potion.Potion), owner) }, Arrow: func(opts world.EntitySpawnOpts, damage float64, owner world.Entity, critical, disallowPickup, obtainArrowOnPickup bool, punchLevel int, tip any) *world.EntityHandle { conf := arrowConf - conf.Damage = damage - conf.Potion = tip.(potion.Potion) - conf.Owner = owner + conf.Damage, conf.Potion, conf.Owner = damage, tip.(potion.Potion), owner conf.KnockBackForceAddend = float64(punchLevel) * (enchantment.Punch{}).KnockBackMultiplier() conf.DisablePickup = disallowPickup if obtainArrowOnPickup { conf.PickupItem = item.NewStack(item.Arrow{Tip: tip.(potion.Potion)}, 1) } conf.Critical = critical - return cfg.New(ArrowType{}, conf) - }, - Egg: func(pos, vel mgl64.Vec3, owner world.Entity) *world.EntityHandle { - e := NewEgg(pos, owner) - e.vel = vel - return e - }, - EnderPearl: func(pos, vel mgl64.Vec3, owner world.Entity) *world.EntityHandle { - e := NewEnderPearl(pos, owner) - e.vel = vel - return e - }, - Firework: func(pos mgl64.Vec3, rot cube.Rotation, attached bool, firework world.Item, owner world.Entity) *world.EntityHandle { - return NewFireworkAttached(pos, rot, firework.(item.Firework), owner, attached) - }, - LingeringPotion: func(opts world.EntitySpawnOpts, t any, owner world.Entity) *world.EntityHandle { - return NewLingeringPotion(opts, t.(potion.Potion), owner) - }, - Snowball: func(pos, vel mgl64.Vec3, owner world.Entity) *world.EntityHandle { - s := NewSnowball(pos, owner) - s.vel = vel - return s - }, - SplashPotion: func(pos, vel mgl64.Vec3, t any, owner world.Entity) *world.EntityHandle { - p := NewSplashPotion(pos, owner, t.(potion.Potion)) - p.vel = vel - return p + return opts.New(ArrowType{}, conf) }, - Lightning: NewLightning, } diff --git a/server/entity/snowball.go b/server/entity/snowball.go index 3af2240a0..7ba7fab54 100644 --- a/server/entity/snowball.go +++ b/server/entity/snowball.go @@ -2,15 +2,15 @@ package entity import ( "github.com/df-mc/dragonfly/server/block/cube" - "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/particle" - "github.com/go-gl/mathgl/mgl64" ) // NewSnowball creates a snowball entity at a position with an owner entity. -func NewSnowball(pos mgl64.Vec3, owner world.Entity) *Ent { - return Config{Behaviour: snowballConf.New()}.New(SnowballType{}, pos) +func NewSnowball(opts world.EntitySpawnOpts, owner world.Entity) *world.EntityHandle { + conf := snowballConf + conf.Owner = owner + return opts.New(SnowballType{}, conf) } var snowballConf = ProjectileBehaviourConfig{ @@ -23,21 +23,16 @@ var snowballConf = ProjectileBehaviourConfig{ // SnowballType is a world.EntityType implementation for snowballs. type SnowballType struct{} +func (t SnowballType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + func (SnowballType) EncodeEntity() string { return "minecraft:snowball" } func (SnowballType) BBox(world.Entity) cube.BBox { return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125) } -func (SnowballType) DecodeNBT(m map[string]any) world.Entity { - s := NewSnowball(nbtconv.Vec3(m, "Pos"), nil) - s.vel = nbtconv.Vec3(m, "Motion") - return s -} - -func (SnowballType) EncodeNBT(e world.Entity) map[string]any { - s := e.(*Ent) - return map[string]any{ - "Pos": nbtconv.Vec3ToFloat32Slice(s.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(s.Velocity()), - } +func (SnowballType) DecodeNBT(_ map[string]any, data *world.EntityData) { + data.Data = snowballConf.New() } +func (SnowballType) EncodeNBT(*world.EntityData) map[string]any { return nil } diff --git a/server/entity/splash_potion.go b/server/entity/splash_potion.go index 68354f2b2..8c14ff6f3 100644 --- a/server/entity/splash_potion.go +++ b/server/entity/splash_potion.go @@ -8,19 +8,20 @@ import ( "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" ) // NewSplashPotion creates a splash potion. SplashPotion is an item that grants // effects when thrown. -func NewSplashPotion(pos mgl64.Vec3, owner world.Entity, t potion.Potion) *Ent { +func NewSplashPotion(opts world.EntitySpawnOpts, t potion.Potion, owner world.Entity) *world.EntityHandle { colour, _ := effect.ResultingColour(t.Effects()) conf := splashPotionConf conf.Potion = t conf.Particle = particle.Splash{Colour: colour} conf.Hit = potionSplash(1, t, false) - return Config{Behaviour: conf.New()}.New(SplashPotionType{}, pos) + conf.Owner = owner + + return opts.New(SplashPotionType{}, conf) } var splashPotionConf = ProjectileBehaviourConfig{ @@ -33,22 +34,25 @@ var splashPotionConf = ProjectileBehaviourConfig{ // SplashPotionType is a world.EntityType implementation for SplashPotion. type SplashPotionType struct{} +func (t SplashPotionType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + func (SplashPotionType) EncodeEntity() string { return "minecraft:splash_potion" } func (SplashPotionType) BBox(world.Entity) cube.BBox { return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125) } -func (SplashPotionType) DecodeNBT(m map[string]any) world.Entity { - pot := NewSplashPotion(nbtconv.Vec3(m, "Pos"), nil, potion.From(nbtconv.Int32(m, "PotionId"))) - pot.vel = nbtconv.Vec3(m, "Motion") - return pot +func (SplashPotionType) DecodeNBT(m map[string]any, data *world.EntityData) { + conf := splashPotionConf + conf.Potion = potion.From(nbtconv.Int32(m, "PotionId")) + colour, _ := effect.ResultingColour(conf.Potion.Effects()) + conf.Particle = particle.Splash{Colour: colour} + conf.Hit = potionSplash(1, conf.Potion, false) + + data.Data = conf.New() } -func (SplashPotionType) EncodeNBT(e world.Entity) map[string]any { - pot := e.(*Ent) - return map[string]any{ - "Pos": nbtconv.Vec3ToFloat32Slice(pot.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(pot.Velocity()), - "PotionId": int32(pot.Behaviour().(*ProjectileBehaviour).conf.Potion.Uint8()), - } +func (SplashPotionType) EncodeNBT(data *world.EntityData) map[string]any { + return map[string]any{"PotionId": int32(data.Data.(*ProjectileBehaviour).conf.Potion.Uint8())} } diff --git a/server/entity/splashable.go b/server/entity/splashable.go index 986eafffc..dde17aaf3 100644 --- a/server/entity/splashable.go +++ b/server/entity/splashable.go @@ -101,7 +101,7 @@ func potionSplash(durMul float64, pot potion.Potion, linger bool) func(e *Ent, t } } if linger { - tx.AddEntity(NewAreaEffectCloud(pos, pot)) + tx.AddEntity(NewAreaEffectCloud(world.EntitySpawnOpts{Position: pos}, pot)) } } } diff --git a/server/entity/stationary.go b/server/entity/stationary.go index f25c545dc..3aaecd18c 100644 --- a/server/entity/stationary.go +++ b/server/entity/stationary.go @@ -39,7 +39,6 @@ func (conf StationaryBehaviourConfig) New() *StationaryBehaviour { // such entities will not move them. type StationaryBehaviour struct { conf StationaryBehaviourConfig - age time.Duration close bool } diff --git a/server/entity/text.go b/server/entity/text.go index c5c0a480a..147082ca6 100644 --- a/server/entity/text.go +++ b/server/entity/text.go @@ -2,16 +2,13 @@ package entity import ( "github.com/df-mc/dragonfly/server/block/cube" - "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" ) // NewText creates and returns a new Text entity with the text and position provided. -func NewText(text string, pos mgl64.Vec3) *Ent { - e := Config{Behaviour: textConf.New()}.New(TextType{}, pos) - e.SetNameTag(text) - return e +func NewText(text string, pos mgl64.Vec3) *world.EntityHandle { + return world.EntitySpawnOpts{Position: pos, NameTag: text}.New(TextType{}, textConf) } var textConf StationaryBehaviourConfig @@ -19,18 +16,12 @@ var textConf StationaryBehaviourConfig // TextType is a world.EntityType implementation for Text. type TextType struct{} +func (t TextType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} func (TextType) EncodeEntity() string { return "dragonfly:text" } func (TextType) BBox(world.Entity) cube.BBox { return cube.BBox{} } func (TextType) NetworkEncodeEntity() string { return "minecraft:falling_block" } -func (TextType) DecodeNBT(m map[string]any) world.Entity { - return NewText(nbtconv.String(m, "Text"), nbtconv.Vec3(m, "Pos")) -} - -func (TextType) EncodeNBT(e world.Entity) map[string]any { - t := e.(*Ent) - return map[string]any{ - "Pos": nbtconv.Vec3ToFloat32Slice(t.Position()), - "Text": t.NameTag(), - } -} +func (TextType) DecodeNBT(_ map[string]any, data *world.EntityData) { data.Data = textConf.New() } +func (TextType) EncodeNBT(data *world.EntityData) map[string]any { return nil } diff --git a/server/entity/tnt.go b/server/entity/tnt.go index f5a11aacc..9a3eeb804 100644 --- a/server/entity/tnt.go +++ b/server/entity/tnt.go @@ -12,14 +12,14 @@ import ( ) // NewTNT creates a new primed TNT entity. -func NewTNT(pos mgl64.Vec3, fuse time.Duration, igniter world.Entity) *Ent { - config := tntConf - config.ExistenceDuration = fuse - ent := Config{Behaviour: config.New()}.New(TNTType{igniter: igniter}, pos) - - angle := rand.Float64() * math.Pi * 2 - ent.vel = mgl64.Vec3{-math.Sin(angle) * 0.02, 0.1, -math.Cos(angle) * 0.02} - return ent +func NewTNT(opts world.EntitySpawnOpts, fuse time.Duration) *world.EntityHandle { + conf := tntConf + conf.ExistenceDuration = fuse + if opts.Velocity.Len() == 0 { + angle := rand.Float64() * math.Pi * 2 + opts.Velocity = mgl64.Vec3{-math.Sin(angle) * 0.02, 0.1, -math.Cos(angle) * 0.02} + } + return opts.New(TNTType{}, conf) } var tntConf = PassiveBehaviourConfig{ @@ -35,13 +35,11 @@ func explodeTNT(e *Ent, tx *world.Tx) { } // TNTType is a world.EntityType implementation for TNT. -type TNTType struct { - igniter world.Entity -} +type TNTType struct{} -// Igniter returns the entity that ignited the TNT. -// It is nil if ignited by a world source like fire. -func (t TNTType) Igniter() world.Entity { return t.igniter } +func (t TNTType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} func (TNTType) EncodeEntity() string { return "minecraft:tnt" } func (TNTType) NetworkOffset() float64 { return 0.49 } @@ -49,17 +47,12 @@ func (TNTType) BBox(world.Entity) cube.BBox { return cube.Box(-0.49, 0, -0.49, 0.49, 0.98, 0.49) } -func (t TNTType) DecodeNBT(m map[string]any) world.Entity { - tnt := NewTNT(nbtconv.Vec3(m, "Pos"), nbtconv.TickDuration[uint8](m, "Fuse"), t.igniter) - tnt.vel = nbtconv.Vec3(m, "Motion") - return tnt +func (t TNTType) DecodeNBT(m map[string]any, data *world.EntityData) { + conf := tntConf + conf.ExistenceDuration = nbtconv.TickDuration[uint8](m, "Fuse") + data.Data = conf.New() } -func (TNTType) EncodeNBT(e world.Entity) map[string]any { - t := e.(*Ent) - return map[string]any{ - "Pos": nbtconv.Vec3ToFloat32Slice(t.Position()), - "Motion": nbtconv.Vec3ToFloat32Slice(t.Velocity()), - "Fuse": uint8(t.Behaviour().(*PassiveBehaviour).Fuse().Milliseconds() / 50), - } +func (TNTType) EncodeNBT(data *world.EntityData) map[string]any { + return map[string]any{"Fuse": uint8(data.Data.(*PassiveBehaviour).Fuse().Milliseconds() / 50)} } diff --git a/server/item/bottle_of_enchanting.go b/server/item/bottle_of_enchanting.go index ac0604095..46f36dafc 100644 --- a/server/item/bottle_of_enchanting.go +++ b/server/item/bottle_of_enchanting.go @@ -11,7 +11,8 @@ type BottleOfEnchanting struct{} // Use ... func (b BottleOfEnchanting) Use(tx *world.Tx, user User, ctx *UseContext) bool { create := tx.World().EntityRegistry().Config().BottleOfEnchanting - tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(0.7), user)) + opts := world.EntitySpawnOpts{Position: eyePosition(user), Velocity: user.Rotation().Vec3().Mul(0.7)} + tx.AddEntity(create(opts, user)) tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) diff --git a/server/item/bow.go b/server/item/bow.go index e7719d0a8..0826406c2 100644 --- a/server/item/bow.go +++ b/server/item/bow.go @@ -84,7 +84,8 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti } create := releaser.World().EntityRegistry().Config().Arrow - projectile := create(eyePosition(releaser), releaser.Rotation().Vec3().Mul(force*5), rot, damage, releaser, force >= 1, false, !creative && consume, punchLevel, tip) + opts := world.EntitySpawnOpts{Position: eyePosition(releaser), Velocity: releaser.Rotation().Vec3().Mul(force * 5), Rotation: rot} + projectile := tx.AddEntity(create(opts, damage, releaser, force >= 1, false, !creative && consume, punchLevel, tip)) if f, ok := projectile.(interface{ SetOnFire(duration time.Duration) }); ok { f.SetOnFire(burnDuration) } @@ -95,7 +96,6 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti } releaser.PlaySound(sound.BowShoot{}) - tx.AddEntity(projectile) } // EnchantmentValue ... diff --git a/server/item/egg.go b/server/item/egg.go index c2a121c08..b31de9ff6 100644 --- a/server/item/egg.go +++ b/server/item/egg.go @@ -16,7 +16,8 @@ func (e Egg) MaxCount() int { // Use ... func (e Egg) Use(tx *world.Tx, user User, ctx *UseContext) bool { create := tx.World().EntityRegistry().Config().Egg - tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(1.5), user)) + opts := world.EntitySpawnOpts{Position: eyePosition(user), Velocity: user.Rotation().Vec3().Mul(1.5)} + tx.AddEntity(create(opts, user)) tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) diff --git a/server/item/ender_pearl.go b/server/item/ender_pearl.go index 1ed29d5a1..061498a50 100644 --- a/server/item/ender_pearl.go +++ b/server/item/ender_pearl.go @@ -12,7 +12,8 @@ type EnderPearl struct{} // Use ... func (e EnderPearl) Use(tx *world.Tx, user User, ctx *UseContext) bool { create := tx.World().EntityRegistry().Config().EnderPearl - tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(1.5), user)) + opts := world.EntitySpawnOpts{Position: eyePosition(user), Velocity: user.Rotation().Vec3().Mul(1.5)} + tx.AddEntity(create(opts, user)) tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) diff --git a/server/item/firework.go b/server/item/firework.go index dcffb7073..6683dccf4 100644 --- a/server/item/firework.go +++ b/server/item/firework.go @@ -30,7 +30,8 @@ func (f Firework) Use(tx *world.Tx, user User, ctx *UseContext) bool { tx.PlaySound(pos, sound.FireworkLaunch{}) create := tx.World().EntityRegistry().Config().Firework - tx.AddEntity(create(pos, user.Rotation(), true, f, user)) + opts := world.EntitySpawnOpts{Position: pos, Rotation: user.Rotation()} + tx.AddEntity(create(opts, f, user, true)) ctx.SubtractFromCount(1) return true @@ -40,7 +41,8 @@ func (f Firework) Use(tx *world.Tx, user User, ctx *UseContext) bool { func (f Firework) UseOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec3, tx *world.Tx, user User, ctx *UseContext) bool { fpos := pos.Vec3().Add(clickPos) create := tx.World().EntityRegistry().Config().Firework - tx.AddEntity(create(fpos, cube.Rotation{rand.Float64() * 360, 90}, false, f, user)) + opts := world.EntitySpawnOpts{Position: fpos, Rotation: cube.Rotation{rand.Float64() * 360, 90}} + tx.AddEntity(create(opts, f, user, false)) tx.PlaySound(fpos, sound.FireworkLaunch{}) ctx.SubtractFromCount(1) diff --git a/server/item/lingering_potion.go b/server/item/lingering_potion.go index 0d5026f19..0f0e33cc3 100644 --- a/server/item/lingering_potion.go +++ b/server/item/lingering_potion.go @@ -21,7 +21,8 @@ func (l LingeringPotion) MaxCount() int { // Use ... func (l LingeringPotion) Use(tx *world.Tx, user User, ctx *UseContext) bool { create := tx.World().EntityRegistry().Config().LingeringPotion - tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(0.5), l.Type, user)) + opts := world.EntitySpawnOpts{Position: eyePosition(user), Velocity: user.Rotation().Vec3().Mul(0.5)} + tx.AddEntity(create(opts, l.Type, user)) tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) diff --git a/server/item/snowball.go b/server/item/snowball.go index 6a3e55d6b..a3a66e7e6 100644 --- a/server/item/snowball.go +++ b/server/item/snowball.go @@ -16,7 +16,8 @@ func (s Snowball) MaxCount() int { // Use ... func (s Snowball) Use(tx *world.Tx, user User, ctx *UseContext) bool { create := tx.World().EntityRegistry().Config().Snowball - tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(1.5), user)) + opts := world.EntitySpawnOpts{Position: eyePosition(user), Velocity: user.Rotation().Vec3().Mul(1.5)} + tx.AddEntity(create(opts, user)) tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) diff --git a/server/item/splash_potion.go b/server/item/splash_potion.go index 87a4a0895..3e356ad5d 100644 --- a/server/item/splash_potion.go +++ b/server/item/splash_potion.go @@ -20,7 +20,8 @@ func (s SplashPotion) MaxCount() int { // Use ... func (s SplashPotion) Use(tx *world.Tx, user User, ctx *UseContext) bool { create := tx.World().EntityRegistry().Config().SplashPotion - tx.AddEntity(create(eyePosition(user), user.Rotation().Vec3().Mul(0.5), s.Type, user)) + opts := world.EntitySpawnOpts{Position: eyePosition(user), Velocity: user.Rotation().Vec3().Mul(0.5)} + tx.AddEntity(create(opts, s.Type, user)) tx.PlaySound(user.Position(), sound.ItemThrow{}) ctx.SubtractFromCount(1) diff --git a/server/session/handler_grindstone.go b/server/session/handler_grindstone.go index 29ab2ea81..a89251294 100644 --- a/server/session/handler_grindstone.go +++ b/server/session/handler_grindstone.go @@ -9,7 +9,6 @@ import ( "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/entity" "github.com/df-mc/dragonfly/server/item" - "github.com/go-gl/mathgl/mgl64" "github.com/sandertv/gophertunnel/minecraft/protocol" ) @@ -60,7 +59,6 @@ func (h *ItemStackRequestHandler) handleGrindstoneCraft(s *Session, tx *world.Tx } for _, o := range entity.NewExperienceOrbs(entity.EyePosition(c), experienceFromEnchantments(resultStack)) { - o.SetVelocity(mgl64.Vec3{(rand.Float64()*0.2 - 0.1) * 2, rand.Float64() * 0.4, (rand.Float64()*0.2 - 0.1) * 2}) tx.AddEntity(o) } diff --git a/server/session/handler_item_stack_request.go b/server/session/handler_item_stack_request.go index 73b10d21d..be7e845c1 100644 --- a/server/session/handler_item_stack_request.go +++ b/server/session/handler_item_stack_request.go @@ -8,11 +8,9 @@ import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/item/inventory" "github.com/df-mc/dragonfly/server/world" - "github.com/go-gl/mathgl/mgl64" "github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" "math" - "math/rand" "time" ) @@ -211,7 +209,6 @@ func (h *ItemStackRequestHandler) collectRewards(s *Session, inv *inventory.Inve if inv == s.openedWindow.Load() && s.containerOpened.Load() && slot == inv.Size()-1 { if f, ok := tx.Block(*s.openedPos.Load()).(smelter); ok { for _, o := range entity.NewExperienceOrbs(entity.EyePosition(c), f.ResetExperience()) { - o.SetVelocity(mgl64.Vec3{(rand.Float64()*0.2 - 0.1) * 2, rand.Float64() * 0.4, (rand.Float64()*0.2 - 0.1) * 2}) tx.AddEntity(o) } } diff --git a/server/world/entity.go b/server/world/entity.go index 30ec01c6d..ee626a1f6 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -53,6 +53,8 @@ type EntitySpawnOpts struct { Velocity mgl64.Vec3 ID uuid.UUID + + NameTag string } func (opts EntitySpawnOpts) New(t EntityType, conf EntityConfig) *EntityHandle { @@ -64,6 +66,7 @@ func (opts EntitySpawnOpts) New(t EntityType, conf EntityConfig) *EntityHandle { } handle := &EntityHandle{id: opts.ID, t: t} handle.data.Pos, handle.data.Rot, handle.data.Vel = opts.Position, opts.Rotation, opts.Velocity + handle.data.Name = opts.NameTag conf.Apply(&handle.data) return handle } @@ -118,16 +121,20 @@ func (e *EntityHandle) decodeNBT(m map[string]any) { e.data.Pos = readVec3(m, "Pos") e.data.Vel = readVec3(m, "Motion") e.data.Rot = readRotation(m) + e.data.Age = time.Duration(readInt16(m, "Age")) * (time.Second / 20) e.data.FireDuration = time.Duration(readInt16(m, "Fire")) * time.Second / 20 + e.data.Name, _ = m["NameTag"].(string) } func (e *EntityHandle) encodeNBT(_ *Tx) map[string]any { return map[string]any{ - "Pos": []float32{float32(e.data.Pos[0]), float32(e.data.Pos[1]), float32(e.data.Pos[2])}, - "Motion": []float32{float32(e.data.Vel[0]), float32(e.data.Vel[1]), float32(e.data.Vel[2])}, - "Yaw": float32(e.data.Rot[0]), - "Pitch": float32(e.data.Rot[1]), - "Fire": int16(e.data.FireDuration.Seconds() * 20), + "Pos": []float32{float32(e.data.Pos[0]), float32(e.data.Pos[1]), float32(e.data.Pos[2])}, + "Motion": []float32{float32(e.data.Vel[0]), float32(e.data.Vel[1]), float32(e.data.Vel[2])}, + "Yaw": float32(e.data.Rot[0]), + "Pitch": float32(e.data.Rot[1]), + "Fire": int16(e.data.FireDuration.Seconds() * 20), + "Age": int16(e.data.Age / (time.Second * 20)), + "NameTag": e.data.Name, } } @@ -202,12 +209,12 @@ type EntityRegistry struct { type EntityRegistryConfig struct { Item func(opts EntitySpawnOpts, it any) *EntityHandle FallingBlock func(opts EntitySpawnOpts, bl Block) *EntityHandle - TNT func(opts EntitySpawnOpts, fuse time.Duration, igniter Entity) *EntityHandle + TNT func(opts EntitySpawnOpts, fuse time.Duration) *EntityHandle BottleOfEnchanting func(opts EntitySpawnOpts, owner Entity) *EntityHandle Arrow func(opts EntitySpawnOpts, damage float64, owner Entity, critical, disallowPickup, obtainArrowOnPickup bool, punchLevel int, tip any) *EntityHandle Egg func(opts EntitySpawnOpts, owner Entity) *EntityHandle EnderPearl func(opts EntitySpawnOpts, owner Entity) *EntityHandle - Firework func(opts EntitySpawnOpts, attached bool, firework Item, owner Entity) *EntityHandle + Firework func(opts EntitySpawnOpts, firework Item, owner Entity, attached bool) *EntityHandle LingeringPotion func(opts EntitySpawnOpts, t any, owner Entity) *EntityHandle Snowball func(opts EntitySpawnOpts, owner Entity) *EntityHandle SplashPotion func(opts EntitySpawnOpts, t any, owner Entity) *EntityHandle diff --git a/server/world/tx.go b/server/world/tx.go index 87a5af451..99e66f389 100644 --- a/server/world/tx.go +++ b/server/world/tx.go @@ -90,8 +90,8 @@ func (tx *Tx) PlaySound(pos mgl64.Vec3, s Sound) { tx.World().playSound(pos, s) } -func (tx *Tx) AddEntity(e Entity) { - tx.World().addEntity(e) +func (tx *Tx) AddEntity(e *EntityHandle) Entity { + return tx.World().addEntity(tx, e) } func (tx *Tx) RemoveEntity(e Entity) { diff --git a/server/world/weather.go b/server/world/weather.go index d2fc18c0c..8f5f82eac 100644 --- a/server/world/weather.go +++ b/server/world/weather.go @@ -175,7 +175,7 @@ func (w weather) tickLightning(tx *Tx) { // lightning strike will fail. func (w weather) strikeLightning(tx *Tx, c ChunkPos) { if pos := w.lightningPosition(tx, c); tx.ThunderingAt(cube.PosFromVec3(pos)) { - tx.AddEntity(w.w.conf.Entities.conf.Lightning(pos)) + tx.AddEntity(w.w.conf.Entities.conf.Lightning(EntitySpawnOpts{Position: pos})) } } diff --git a/server/world/world.go b/server/world/world.go index 335395560..f38b52d8a 100644 --- a/server/world/world.go +++ b/server/world/world.go @@ -648,8 +648,7 @@ func (w *World) playSound(pos mgl64.Vec3, s Sound) { // all viewers of the world that have the chunk of the entity loaded. // If the chunk that the entity is in is not yet loaded, it will first be loaded. // If the entity passed to AddEntity is currently in a world, it is first removed from that world. -func (w *World) addEntity(ent Entity) { - e := ent.Handle() +func (w *World) addEntity(tx *Tx, e *EntityHandle) Entity { e.w.Store(w) pos := chunkPosFromVec3(e.data.Pos) @@ -658,11 +657,13 @@ func (w *World) addEntity(ent Entity) { c := w.chunk(pos) c.Entities, c.modified = append(c.Entities, e), true + ent := e.Entity(tx) for _, v := range c.viewers { // We show the entity to all viewers currently in the chunk that the entity is spawned in. showEntity(ent, v) } w.Handler().HandleEntitySpawn(ent) + return ent } // RemoveEntity removes an entity from the world that is currently present in it. Any viewers of the entity