From 1c6aa271d2002d9ea9bff9551c2b2224b056f090 Mon Sep 17 00:00:00 2001 From: Sandertv Date: Tue, 12 Nov 2024 12:50:11 +0100 Subject: [PATCH] world/entity.go: More fixes, should make moving entities across worlds significantly safer. --- server/entity/area_effect_cloud_behaviour.go | 8 +- server/entity/arrow.go | 2 +- server/entity/bottle_of_enchanting.go | 2 +- server/entity/egg.go | 2 +- server/entity/ender_pearl.go | 5 +- server/entity/experience_orb_behaviour.go | 39 ++++---- server/entity/firework.go | 2 +- server/entity/firework_behaviour.go | 19 ++-- server/entity/lingering_potion.go | 2 +- server/entity/projectile.go | 9 +- server/entity/register.go | 2 +- server/entity/snowball.go | 2 +- server/entity/splash_potion.go | 2 +- server/item/bow.go | 2 +- server/session/entity_metadata.go | 11 ++- .../session/handler_inventory_transaction.go | 15 ++- server/session/world.go | 15 ++- server/world/block.go | 4 +- server/world/conf.go | 2 +- server/world/entity.go | 93 +++++++++++-------- server/world/handler.go | 8 +- server/world/position.go | 5 +- server/world/tick.go | 16 ++-- server/world/tx.go | 55 +++++------ server/world/viewer.go | 28 +++--- server/world/weather.go | 8 +- server/world/world.go | 53 ++++++----- 27 files changed, 216 insertions(+), 195 deletions(-) diff --git a/server/entity/area_effect_cloud_behaviour.go b/server/entity/area_effect_cloud_behaviour.go index b90dcc721..f545bc5a2 100644 --- a/server/entity/area_effect_cloud_behaviour.go +++ b/server/entity/area_effect_cloud_behaviour.go @@ -48,7 +48,7 @@ func (conf AreaEffectCloudBehaviourConfig) New() *AreaEffectCloudBehaviour { stationary: stationary.New(), duration: conf.Duration, radius: conf.Radius, - targets: make(map[world.Entity]time.Duration), + targets: make(map[*world.EntityHandle]time.Duration), } } @@ -62,7 +62,7 @@ type AreaEffectCloudBehaviour struct { duration time.Duration radius float64 - targets map[world.Entity]time.Duration + targets map[*world.EntityHandle]time.Duration } // Radius returns the current radius of the area effect cloud. @@ -103,7 +103,7 @@ func (a *AreaEffectCloudBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { } entities := tx.EntitiesWithin(e.Type().BBox(e).Translate(pos), func(entity world.Entity) bool { - _, target := a.targets[entity] + _, target := a.targets[entity.Handle()] _, living := entity.(Living) return !living || target || entity == e }) @@ -133,7 +133,7 @@ func (a *AreaEffectCloudBehaviour) applyEffects(pos mgl64.Vec3, ent *Ent, entiti l.AddEffect(eff) } - a.targets[e] = ent.Age() + a.conf.ReapplicationDelay + a.targets[e.Handle()] = ent.Age() + a.conf.ReapplicationDelay a.subtractUseDuration() a.subtractUseRadius() diff --git a/server/entity/arrow.go b/server/entity/arrow.go index 8866faaca..ce34d860c 100644 --- a/server/entity/arrow.go +++ b/server/entity/arrow.go @@ -33,7 +33,7 @@ func NewTippedArrowWithDamage(opts world.EntitySpawnOpts, damage float64, owner conf := arrowConf conf.Damage = damage conf.Potion = tip - conf.Owner = owner + conf.Owner = owner.Handle() return opts.New(ArrowType{}, conf) } diff --git a/server/entity/bottle_of_enchanting.go b/server/entity/bottle_of_enchanting.go index 9da29b8a3..4a3151a2d 100644 --- a/server/entity/bottle_of_enchanting.go +++ b/server/entity/bottle_of_enchanting.go @@ -12,7 +12,7 @@ import ( // NewBottleOfEnchanting ... func NewBottleOfEnchanting(opts world.EntitySpawnOpts, owner world.Entity) *world.EntityHandle { conf := bottleOfEnchantingConf - conf.Owner = owner + conf.Owner = owner.Handle() return opts.New(BottleOfEnchantingType{}, conf) } diff --git a/server/entity/egg.go b/server/entity/egg.go index 9e45823e8..c63258208 100644 --- a/server/entity/egg.go +++ b/server/entity/egg.go @@ -10,7 +10,7 @@ import ( // to spawn chicks. func NewEgg(opts world.EntitySpawnOpts, owner world.Entity) *world.EntityHandle { conf := eggConf - conf.Owner = owner + conf.Owner = owner.Handle() return opts.New(EggType{}, conf) } diff --git a/server/entity/ender_pearl.go b/server/entity/ender_pearl.go index 556b303ac..cd3a10fdd 100644 --- a/server/entity/ender_pearl.go +++ b/server/entity/ender_pearl.go @@ -13,7 +13,7 @@ import ( // blue item used to teleport. func NewEnderPearl(opts world.EntitySpawnOpts, owner world.Entity) *world.EntityHandle { conf := enderPearlConf - conf.Owner = owner + conf.Owner = owner.Handle() return opts.New(EnderPearlType{}, conf) } @@ -34,7 +34,8 @@ type teleporter interface { // teleport teleports the owner of an Ent to a trace.Result's position. func teleport(e *Ent, tx *world.Tx, target trace.Result) { - if user, ok := e.Behaviour().(*ProjectileBehaviour).Owner().(teleporter); ok { + owner, _ := e.Behaviour().(*ProjectileBehaviour).Owner().Entity(tx) + if user, ok := owner.(teleporter); ok { tx.PlaySound(user.Position(), sound.Teleport{}) user.Teleport(target.Position()) user.Hurt(5, FallDamageSource{}) diff --git a/server/entity/experience_orb_behaviour.go b/server/entity/experience_orb_behaviour.go index 3a1f5447a..826375953 100644 --- a/server/entity/experience_orb_behaviour.go +++ b/server/entity/experience_orb_behaviour.go @@ -52,7 +52,7 @@ type ExperienceOrbBehaviour struct { passive *PassiveBehaviour lastSearch time.Time - target experienceCollector + target *world.EntityHandle } // Experience returns the amount of experience the orb carries. @@ -70,38 +70,35 @@ var followBox = cube.Box(-8, -8, -8, 8, 8, 8) // tick finds a target for the experience orb and moves the orb towards it. func (exp *ExperienceOrbBehaviour) tick(e *Ent, tx *world.Tx) { - pos := e.Position() - if exp.target != nil && (exp.target.Dead() || exp.target.World() != tx.World() || pos.Sub(exp.target.Position()).Len() > 8) { - exp.target = nil - } + targetEnt, ok := exp.target.Entity(tx) + target := targetEnt.(experienceCollector) - if time.Since(exp.lastSearch) >= time.Second { + pos := e.Position() + if (!ok || target.Dead() || pos.Sub(target.Position()).Len() > 8) && time.Since(exp.lastSearch) >= time.Second { exp.findTarget(tx, pos) + return } - if exp.target != nil { - exp.moveToTarget(e) - } + exp.moveToTarget(e, target) } // findTarget attempts to find a target for an experience orb in w around pos. func (exp *ExperienceOrbBehaviour) findTarget(tx *world.Tx, pos mgl64.Vec3) { - if exp.target == nil { - collectors := tx.EntitiesWithin(followBox.Translate(pos), func(o world.Entity) bool { - _, ok := o.(experienceCollector) - return !ok - }) - if len(collectors) > 0 { - exp.target = collectors[0].(experienceCollector) - } + exp.target = nil + collectors := tx.EntitiesWithin(followBox.Translate(pos), func(o world.Entity) bool { + _, ok := o.(experienceCollector) + return !ok + }) + if len(collectors) > 0 { + exp.target = collectors[0].(experienceCollector).Handle() } exp.lastSearch = time.Now() } // moveToTarget applies velocity to the experience orb so that it moves towards // its current target. If it intersects with the target, the orb is collected. -func (exp *ExperienceOrbBehaviour) moveToTarget(e *Ent) { - pos, dst := e.Position(), exp.target.Position() - if o, ok := exp.target.(Eyed); ok { +func (exp *ExperienceOrbBehaviour) moveToTarget(e *Ent, target experienceCollector) { + pos, dst := e.Position(), target.Position() + if o, ok := target.(Eyed); ok { dst[1] += o.EyeHeight() / 2 } diff := dst.Sub(pos).Mul(0.125) @@ -109,7 +106,7 @@ func (exp *ExperienceOrbBehaviour) moveToTarget(e *Ent) { e.SetVelocity(e.Velocity().Add(diff.Normalize().Mul(0.2 * math.Pow(1-math.Sqrt(dist), 2)))) } - if e.Type().BBox(e).Translate(pos).IntersectsWith(exp.target.Type().BBox(exp.target).Translate(exp.target.Position())) && exp.target.CollectExperience(exp.conf.Experience) { + if e.Type().BBox(e).Translate(pos).IntersectsWith(target.Type().BBox(target).Translate(target.Position())) && target.CollectExperience(exp.conf.Experience) { _ = e.Close() } } diff --git a/server/entity/firework.go b/server/entity/firework.go index 5d18d0c7e..078e056bd 100644 --- a/server/entity/firework.go +++ b/server/entity/firework.go @@ -20,7 +20,7 @@ func NewFireworkAttached(opts world.EntitySpawnOpts, firework item.Firework, own conf := fireworkConf conf.ExistenceDuration = firework.RandomisedDuration() conf.Attached = attached - conf.Owner = owner + conf.Owner = owner.Handle() return world.NewEntity(FireworkType{}, conf) } diff --git a/server/entity/firework_behaviour.go b/server/entity/firework_behaviour.go index a40cb8fb0..b2d98481a 100644 --- a/server/entity/firework_behaviour.go +++ b/server/entity/firework_behaviour.go @@ -13,7 +13,7 @@ import ( // FireworkBehaviourConfig holds optional parameters for a FireworkBehaviour. type FireworkBehaviourConfig struct { Firework item.Firework - Owner world.Entity + Owner *world.EntityHandle // ExistenceDuration is the duration that an entity with this behaviour // should last. Once this time expires, the entity is closed. If // ExistenceDuration is 0, the entity will never expire automatically. @@ -62,7 +62,7 @@ func (f *FireworkBehaviour) Attached() bool { } // Owner returns the world.Entity that launched the firework. -func (f *FireworkBehaviour) Owner() world.Entity { +func (f *FireworkBehaviour) Owner() *world.EntityHandle { return f.conf.Owner } @@ -74,20 +74,22 @@ func (f *FireworkBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { // tick ticks the entity, updating its velocity either with a constant factor // or based on the owner's position and velocity if attached. -func (f *FireworkBehaviour) tick(e *Ent, _ *world.Tx) { +func (f *FireworkBehaviour) tick(e *Ent, tx *world.Tx) { + owner, ok := f.conf.Owner.Entity(tx) + var ownerVel mgl64.Vec3 - if o, ok := f.conf.Owner.(interface { + if o, ok := owner.(interface { Velocity() mgl64.Vec3 }); ok { ownerVel = o.Velocity() } - if f.conf.Attached { - dV := f.conf.Owner.Rotation().Vec3() + if f.conf.Attached && ok { + dV := owner.Rotation().Vec3() // The client will propel itself to match the firework's velocity since // we set the appropriate metadata. - e.data.Pos = f.conf.Owner.Position() + e.data.Pos = 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.data.Vel[0] *= f.conf.SidewaysVelocityMultiplier @@ -99,6 +101,7 @@ func (f *FireworkBehaviour) tick(e *Ent, _ *world.Tx) { // explode causes an explosion at the position of the firework, spawning // particles and damaging nearby entities. func (f *FireworkBehaviour) explode(e *Ent, tx *world.Tx) { + owner, _ := f.conf.Owner.Entity(tx) pos, explosions := e.Position(), f.conf.Firework.Explosions for _, v := range tx.Viewers(pos) { @@ -132,7 +135,7 @@ func (f *FireworkBehaviour) explode(e *Ent, tx *world.Tx) { continue } dmg := force * math.Sqrt((5.0-dist)/5.0) - src := ProjectileDamageSource{Owner: f.conf.Owner, Projectile: e} + src := ProjectileDamageSource{Owner: owner, Projectile: e} if pos == tpos { e.(Living).Hurt(dmg, src) diff --git a/server/entity/lingering_potion.go b/server/entity/lingering_potion.go index 2a5c1ee57..19caabe2b 100644 --- a/server/entity/lingering_potion.go +++ b/server/entity/lingering_potion.go @@ -19,7 +19,7 @@ func NewLingeringPotion(opts world.EntitySpawnOpts, t potion.Potion, owner world conf.Potion = t conf.Particle = particle.Splash{Colour: colour} conf.Hit = potionSplash(0.25, t, true) - conf.Owner = owner + conf.Owner = owner.Handle() return opts.New(LingeringPotionType{}, conf) } diff --git a/server/entity/projectile.go b/server/entity/projectile.go index 057310702..36159499b 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -17,7 +17,7 @@ import ( // ProjectileBehaviourConfig.New() creates a ProjectileBehaviour using these // settings. type ProjectileBehaviourConfig struct { - Owner world.Entity + Owner *world.EntityHandle // 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 @@ -110,7 +110,7 @@ type ProjectileBehaviour struct { } // Owner returns the owner of the projectile. -func (lt *ProjectileBehaviour) Owner() world.Entity { +func (lt *ProjectileBehaviour) Owner() *world.EntityHandle { return lt.conf.Owner } @@ -259,7 +259,8 @@ func (lt *ProjectileBehaviour) hitBlockSurviving(e *Ent, r trace.BlockResult, m // entity and knocks it back. Additionally, it applies any potion effects and // fire if applicable. func (lt *ProjectileBehaviour) hitEntity(l Living, e *Ent, origin, vel mgl64.Vec3) { - src := ProjectileDamageSource{Projectile: e, Owner: lt.conf.Owner} + owner, _ := lt.conf.Owner.Entity(e.tx) + src := ProjectileDamageSource{Projectile: e, Owner: owner} dmg := math.Ceil(lt.conf.Damage * vel.Len()) if lt.conf.Critical { dmg += rand.Float64() * dmg / 2 @@ -324,6 +325,6 @@ func (lt *ProjectileBehaviour) ignores(e *Ent) func(other world.Entity) bool { return func(other world.Entity) (ignored bool) { g, ok := other.(interface{ GameMode() world.GameMode }) _, living := other.(Living) - return (ok && !g.GameMode().HasCollision()) || e == other || !living || (e.data.Age < time.Second/4 && lt.conf.Owner == other) + return (ok && !g.GameMode().HasCollision()) || e == other || !living || (e.data.Age < time.Second/4 && lt.conf.Owner == other.Handle()) } } diff --git a/server/entity/register.go b/server/entity/register.go index 5bb000f0f..38a2025fa 100644 --- a/server/entity/register.go +++ b/server/entity/register.go @@ -49,7 +49,7 @@ var conf = world.EntityRegistryConfig{ }, Arrow: func(opts world.EntitySpawnOpts, damage float64, owner world.Entity, critical, disallowPickup, obtainArrowOnPickup bool, punchLevel int, tip any) *world.EntityHandle { conf := arrowConf - conf.Damage, conf.Potion, conf.Owner = damage, tip.(potion.Potion), owner + conf.Damage, conf.Potion, conf.Owner = damage, tip.(potion.Potion), owner.Handle() conf.KnockBackForceAddend = float64(punchLevel) * (enchantment.Punch{}).KnockBackMultiplier() conf.DisablePickup = disallowPickup if obtainArrowOnPickup { diff --git a/server/entity/snowball.go b/server/entity/snowball.go index 7ba7fab54..90dd7f0fe 100644 --- a/server/entity/snowball.go +++ b/server/entity/snowball.go @@ -9,7 +9,7 @@ import ( // NewSnowball creates a snowball entity at a position with an owner entity. func NewSnowball(opts world.EntitySpawnOpts, owner world.Entity) *world.EntityHandle { conf := snowballConf - conf.Owner = owner + conf.Owner = owner.Handle() return opts.New(SnowballType{}, conf) } diff --git a/server/entity/splash_potion.go b/server/entity/splash_potion.go index 8c14ff6f3..e7f575703 100644 --- a/server/entity/splash_potion.go +++ b/server/entity/splash_potion.go @@ -19,7 +19,7 @@ func NewSplashPotion(opts world.EntitySpawnOpts, t potion.Potion, owner world.En conf.Potion = t conf.Particle = particle.Splash{Colour: colour} conf.Hit = potionSplash(1, t, false) - conf.Owner = owner + conf.Owner = owner.Handle() return opts.New(SplashPotionType{}, conf) } diff --git a/server/item/bow.go b/server/item/bow.go index e756cbe14..bc9e14ed6 100644 --- a/server/item/bow.go +++ b/server/item/bow.go @@ -83,7 +83,7 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti } } - create := releaser.World().EntityRegistry().Config().Arrow + create := tx.World().EntityRegistry().Config().Arrow 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 { diff --git a/server/session/entity_metadata.go b/server/session/entity_metadata.go index 421d8a455..5d4d6bdd7 100644 --- a/server/session/entity_metadata.go +++ b/server/session/entity_metadata.go @@ -8,6 +8,7 @@ import ( "github.com/df-mc/dragonfly/server/item/potion" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" + "github.com/google/uuid" "github.com/sandertv/gophertunnel/minecraft/protocol" "math" "time" @@ -89,10 +90,10 @@ func (s *Session) addSpecificMetadata(e any, m protocol.EntityMetadata) { if f, ok := e.(firework); ok { m[protocol.EntityDataKeyDisplayTileRuntimeID] = nbtconv.WriteItem(item.NewStack(f.Firework(), 1), false) if o, ok := e.(owned); ok && f.Attached() { - m[protocol.EntityDataKeyCustomDisplay] = int64(s.entityRuntimeID(o.Owner())) + m[protocol.EntityDataKeyCustomDisplay] = int64(s.handleRuntimeID(o.Owner())) } } else if o, ok := e.(owned); ok { - m[protocol.EntityDataKeyOwner] = int64(s.entityRuntimeID(o.Owner())) + m[protocol.EntityDataKeyOwner] = int64(s.handleRuntimeID(o.Owner())) } if sc, ok := e.(scaled); ok { m[protocol.EntityDataKeyScale] = float32(sc.Scale()) @@ -126,7 +127,8 @@ func (s *Session) addSpecificMetadata(e any, m protocol.EntityMetadata) { m[protocol.EntityDataKeyEffectAmbience] = byte(0) } } - if l, ok := e.(living); ok && s.c == e { + + if l, ok := e.(living); ok && s.ent.UUID() == l.UUID() { deathPos, deathDimension, died := l.DeathPosition() if died { dim, _ := world.DimensionID(deathDimension) @@ -201,7 +203,7 @@ type scaled interface { } type owned interface { - Owner() world.Entity + Owner() *world.EntityHandle } type named interface { @@ -259,6 +261,7 @@ type tnt interface { } type living interface { + UUID() uuid.UUID DeathPosition() (mgl64.Vec3, world.Dimension, bool) } diff --git a/server/session/handler_inventory_transaction.go b/server/session/handler_inventory_transaction.go index 30e6f410c..a1dd007dc 100644 --- a/server/session/handler_inventory_transaction.go +++ b/server/session/handler_inventory_transaction.go @@ -114,6 +114,10 @@ func (h *InventoryTransactionHandler) handleUseItemOnEntityTransaction(data *pro s.swingingArm.Store(true) defer s.swingingArm.Store(false) + if data.TargetEntityRuntimeID == selfEntityRuntimeID { + return fmt.Errorf("invalid entity interaction: players cannot interact with themselves") + } + handle, ok := s.entityFromRuntimeID(data.TargetEntityRuntimeID) if !ok { // In some cases, for example when a falling block entity solidifies, latency may allow attacking an entity that @@ -121,16 +125,17 @@ func (h *InventoryTransactionHandler) handleUseItemOnEntityTransaction(data *pro s.conf.Log.Debug("invalid entity interaction: no entity with runtime ID", "ID", data.TargetEntityRuntimeID) return nil } - if data.TargetEntityRuntimeID == selfEntityRuntimeID { - return fmt.Errorf("invalid entity interaction: players cannot interact with themselves") + e, ok := handle.Entity(tx) + if !ok { + s.conf.Log.Debug("invalid entity interaction: entity is not in the same world (anymore)", "ID", data.TargetEntityRuntimeID) + return nil } - var valid bool switch data.ActionType { case protocol.UseItemOnEntityActionInteract: - valid = c.UseItemOnEntity(handle.Entity(tx)) + valid = c.UseItemOnEntity(e) case protocol.UseItemOnEntityActionAttack: - valid = c.AttackEntity(handle.Entity(tx)) + valid = c.AttackEntity(e) default: return fmt.Errorf("unhandled UseItemOnEntity ActionType %v", data.ActionType) } diff --git a/server/session/world.go b/server/session/world.go index a6e4030d4..8225a333f 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -1164,11 +1164,18 @@ func (s *Session) closeWindow() { // entityRuntimeID returns the runtime ID of the entity passed. // noinspection GoCommentLeadingSpace func (s *Session) entityRuntimeID(e world.Entity) uint64 { + return s.handleRuntimeID(e.Handle()) +} + +func (s *Session) handleRuntimeID(e *world.EntityHandle) uint64 { s.entityMutex.RLock() - //lint:ignore S1005 Double assignment is done explicitly to prevent panics. - id, _ := s.entityRuntimeIDs[e.Handle()] - s.entityMutex.RUnlock() - return id + defer s.entityMutex.RUnlock() + + if id, ok := s.entityRuntimeIDs[e]; ok { + return id + } + s.conf.Log.Error("entity runtime ID not found", "UUID", e.UUID().String()) + return 0 } // entityFromRuntimeID attempts to return an entity by its runtime ID. False is returned if no entity with the diff --git a/server/world/block.go b/server/world/block.go index 11338d447..88c9328f2 100644 --- a/server/world/block.go +++ b/server/world/block.go @@ -272,10 +272,10 @@ type NeighbourUpdateTicker interface { // NBTer represents either an item or a block which may decode NBT data and encode to NBT data. Typically, // this is done to store additional data. type NBTer interface { - // DecodeNBT returns the (new) item, block or entity, depending on which of those the NBTer was, with the NBT data + // DecodeNBT returns the (new) item, block or Entity, depending on which of those the NBTer was, with the NBT data // decoded into it. DecodeNBT(data map[string]any) any - // EncodeNBT encodes the entity into a map which can then be encoded as NBT to be written. + // EncodeNBT encodes the Entity into a map which can then be encoded as NBT to be written. EncodeNBT() map[string]any } diff --git a/server/world/conf.go b/server/world/conf.go index 1bf2d2e59..801d9a318 100644 --- a/server/world/conf.go +++ b/server/world/conf.go @@ -35,7 +35,7 @@ type Config struct { // tick or when deciding where to strike lightning. If set to nil, `rand.NewSource(time.Now().Unix())` will be used // to generate a new source. RandSource rand.Source - // Entities is an EntityRegistry with all entity types registered that may + // Entities is an EntityRegistry with all Entity types registered that may // be added to the World. Entities EntityRegistry } diff --git a/server/world/entity.go b/server/world/entity.go index 93d1fa275..6744d682b 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -8,16 +8,17 @@ import ( "golang.org/x/exp/maps" "io" "sync" + "sync/atomic" "time" ) -// EntityType is the type of Entity. It specifies the name, encoded entity +// EntityType is the type of Entity. It specifies the name, encoded Entity // ID and bounding box of an Entity. type EntityType interface { Open(tx *Tx, handle *EntityHandle, data *EntityData) Entity - // EncodeEntity converts the entity to its encoded representation: It - // returns the type of the Minecraft entity, for example + // EncodeEntity converts the Entity to its encoded representation: It + // returns the type of the Minecraft Entity, for example // 'minecraft:falling_block'. EncodeEntity() string // BBox returns the bounding box of an Entity with this EntityType. @@ -38,8 +39,9 @@ type EntityHandle struct { id uuid.UUID t EntityType - cond *sync.Cond - w *World + cond *sync.Cond + lockedTx atomic.Pointer[Tx] + w *World data EntityData @@ -99,11 +101,18 @@ func (e *EntityHandle) Type() EntityType { return e.t } -func (e *EntityHandle) Entity(tx *Tx) Entity { - if e.w != tx.World() { - panic("can't load entity with Tx of different world") +func (e *EntityHandle) Entity(tx *Tx) (Entity, bool) { + if e == nil || e.w != tx.World() { + return nil, false } - return e.t.Open(tx, e, &e.data) + return e.t.Open(tx, e, &e.data), true +} + +func (e *EntityHandle) mustEntity(tx *Tx) Entity { + if ent, ok := e.Entity(tx); ok { + return ent + } + panic("can't load Entity with Tx of different world") } func (e *EntityHandle) UUID() uuid.UUID { @@ -118,32 +127,38 @@ func (e *EntityHandle) ExecWorld(f func(tx *Tx, e Entity)) { e.cond.Wait() } <-e.w.Exec(func(tx *Tx) { - f(tx, e.Entity(tx)) + e.lockedTx.Store(tx) + f(tx, e.mustEntity(tx)) + e.lockedTx.Store(nil) + e.cond.Signal() }) } -func (e *EntityHandle) unsetAndLockWorld() { - e.cond.L.Lock() +func (e *EntityHandle) unsetAndLockWorld(tx *Tx) { + // If the Entity is in a tx created using ExecWorld, e.cond.L will already + // be locked. Don't try to lock again in that case. + if e.lockedTx.Load() != tx { + e.cond.L.Lock() + defer e.cond.L.Unlock() + } e.w = nil - e.cond.L.Unlock() } -func (e *EntityHandle) setAndUnlockWorld(w *World) { - e.cond.L.Lock() +func (e *EntityHandle) setAndUnlockWorld(w *World, tx *Tx) { + // If the Entity is in a tx created using ExecWorld, e.cond.L will already + // be locked. Don't try to lock again in that case. + if e.lockedTx.Load() != tx { + e.cond.L.Lock() + defer e.cond.L.Unlock() + } if e.w != nil { - panic("cannot add entity to new world before removing from old world") + panic("cannot add Entity to new world before removing from old world") } e.w = w - e.cond.L.Unlock() - e.cond.Signal() } -func (e *EntityHandle) Handle() *EntityHandle { - return e -} - func (e *EntityHandle) decodeNBT(m map[string]any) { e.data.Pos = readVec3(m, "Pos") e.data.Vel = readVec3(m, "Motion") @@ -165,9 +180,9 @@ func (e *EntityHandle) encodeNBT(_ *Tx) map[string]any { } } -// Entity represents an entity in the world, typically an object that may be moved around and can be +// Entity represents an Entity in the world, typically an object that may be moved around and can be // interacted with by other entities. -// Viewers of a world may view an entity when near it. +// Viewers of a world may view an Entity when near it. type Entity interface { io.Closer Handle() *EntityHandle @@ -175,49 +190,49 @@ type Entity interface { // Type returns the EntityType of the Entity. Type() EntityType - // Position returns the current position of the entity in the world. + // Position returns the current position of the Entity in the world. Position() mgl64.Vec3 - // Rotation returns the yaw and pitch of the entity in degrees. Yaw is horizontal rotation (rotation around the + // Rotation returns the yaw and pitch of the Entity in degrees. Yaw is horizontal rotation (rotation around the // vertical axis, 0 when facing forward), pitch is vertical rotation (rotation around the horizontal axis, also 0 // when facing forward). Rotation() cube.Rotation - // World returns the current world of the entity. This is always the world that the entity can actually be + // World returns the current world of the Entity. This is always the world that the Entity can actually be // found in. World() *World } -// TickerEntity represents an entity that has a Tick method which should be called every time the entity is +// TickerEntity represents an Entity that has a Tick method which should be called every time the Entity is // ticked every 20th of a second. type TickerEntity interface { Entity - // Tick ticks the entity with the current World and tick passed. + // Tick ticks the Entity with the current World and tick passed. Tick(tx *Tx, current int64) } -// EntityAction represents an action that may be performed by an entity. Typically, these actions are sent to +// EntityAction represents an action that may be performed by an Entity. Typically, these actions are sent to // viewers in a world so that they can see these actions. type EntityAction interface { EntityAction() } -// DamageSource represents the source of the damage dealt to an entity. This -// source may be passed to the Hurt() method of an entity in order to deal -// damage to an entity with a specific source. +// DamageSource represents the source of the damage dealt to an Entity. This +// source may be passed to the Hurt() method of an Entity in order to deal +// damage to an Entity with a specific source. type DamageSource interface { // ReducedByArmour checks if the source of damage may be reduced if the // receiver of the damage is wearing armour. ReducedByArmour() bool // ReducedByResistance specifies if the Source is affected by the resistance - // effect. If false, damage dealt to an entity with this source will not be - // lowered if the entity has the resistance effect. + // effect. If false, damage dealt to an Entity with this source will not be + // lowered if the Entity has the resistance effect. ReducedByResistance() bool // Fire specifies if the Source is fire related and should be ignored when - // an entity has the fire resistance effect. + // an Entity has the fire resistance effect. Fire() bool } -// HealingSource represents a source of healing for an entity. This source may -// be passed to the Heal() method of a living entity. +// HealingSource represents a source of healing for an Entity. This source may +// be passed to the Heal() method of a living Entity. type HealingSource interface { HealingSource() } @@ -254,7 +269,7 @@ func (conf EntityRegistryConfig) New(ent []EntityType) EntityRegistry { for _, e := range ent { name := e.EncodeEntity() if _, ok := m[name]; ok { - panic("cannot register the same entity (" + name + ") twice") + panic("cannot register the same Entity (" + name + ") twice") } m[name] = e } diff --git a/server/world/handler.go b/server/world/handler.go index 6997575d1..9d6bf5783 100644 --- a/server/world/handler.go +++ b/server/world/handler.go @@ -7,7 +7,7 @@ import ( ) // Handler handles events that are called by a world. Implementations of Handler may be used to listen to -// specific events such as when an entity is added to the world. +// specific events such as when an Entity is added to the world. type Handler interface { // HandleLiquidFlow handles the flowing of a liquid from one block position from into another block // position into. The liquid that will replace the block is also passed. This replaced block might @@ -35,11 +35,11 @@ type Handler interface { // wood, that can be broken by fire. HandleBlockBurn is often succeeded by HandleFireSpread, when fire spreads to // the position of the original block and the event.Context is not cancelled in HandleBlockBurn. HandleBlockBurn(ctx *event.Context, pos cube.Pos) - // HandleCropTrample handles an entity trampling a crop. + // HandleCropTrample handles an Entity trampling a crop. HandleCropTrample(ctx *event.Context, pos cube.Pos) - // HandleEntitySpawn handles an entity being spawned into a World through a call to World.AddEntity. + // HandleEntitySpawn handles an Entity being spawned into a World through a call to World.AddEntity. HandleEntitySpawn(e Entity) - // HandleEntityDespawn handles an entity being despawned from a World through a call to World.RemoveEntity. + // HandleEntityDespawn handles an Entity being despawned from a World through a call to World.RemoveEntity. HandleEntityDespawn(e Entity) // HandleClose handles the World being closed. HandleClose may be used as a moment to finish code running on other // goroutines that operates on the World specifically. HandleClose is called directly before the World stops diff --git a/server/world/position.go b/server/world/position.go index 49ea5edb2..b8bb05d61 100644 --- a/server/world/position.go +++ b/server/world/position.go @@ -57,10 +57,7 @@ func (p SubChunkPos) Z() int32 { // chunkPosFromVec3 returns a chunk position from the Vec3 passed. The coordinates of the chunk position are // those of the Vec3 divided by 16, then rounded down. func chunkPosFromVec3(vec3 mgl64.Vec3) ChunkPos { - return ChunkPos{ - int32(math.Floor(vec3[0])) >> 4, - int32(math.Floor(vec3[2])) >> 4, - } + return ChunkPos{int32(math.Floor(vec3[0])) >> 4, int32(math.Floor(vec3[2])) >> 4} } // chunkPosFromBlockPos returns the ChunkPos of the chunk that a block at a cube.Pos is in. diff --git a/server/world/tick.go b/server/world/tick.go index fea4a1bd5..cc2e90572 100644 --- a/server/world/tick.go +++ b/server/world/tick.go @@ -155,7 +155,7 @@ func (t ticker) tickBlocksRandomly(tx *Tx, loaders []*Loader, tick int64) { } // Generally we would want to make sure the block has its block entities, but provided blocks // with block entities are generally ticked already, we are safe to assume that blocks - // implementing the RandomTicker don't rely on additional block entity data. + // implementing the RandomTicker don't rely on additional block Entity data. if rid := sub.Layers()[0].At(x, y, z); randomTickBlocks[rid] { subY := (i + (tx.Range().Min() >> 4)) << 4 randomBlocks = append(randomBlocks, cube.Pos{cx + int(x), subY + int(y), cz + int(z)}) @@ -197,7 +197,7 @@ func (t ticker) anyWithinDistance(pos ChunkPos, loaded []ChunkPos, r int32) bool // updating where necessary. func (t ticker) tickEntities(tx *Tx, tick int64) { for handle, lastPos := range tx.World().entities { - e := handle.Entity(tx) + e := handle.mustEntity(tx) chunkPos := chunkPosFromVec3(handle.data.Pos) c, ok := tx.World().chunks[chunkPos] @@ -206,16 +206,16 @@ func (t ticker) tickEntities(tx *Tx, tick int64) { } if lastPos != chunkPos { - // The entity was stored using an outdated chunk position. We update it and make sure it is ready + // The Entity was stored using an outdated chunk position. We update it and make sure it is ready // for loaders to view it. tx.World().entities[handle] = chunkPos c.Entities = append(c.Entities, handle) var viewers []Viewer - // When changing an entity's world, then teleporting it immediately, we could end up in a situation - // where the old chunk of the entity was not loaded. In this case, it should be safe simply to ignore - // the loaders from the old chunk. We can assume they never saw the entity in the first place. + // When changing an Entity's world, then teleporting it immediately, we could end up in a situation + // where the old chunk of the Entity was not loaded. In this case, it should be safe simply to ignore + // the loaders from the old chunk. We can assume they never saw the Entity in the first place. if old, ok := tx.World().chunks[lastPos]; ok { old.Entities = sliceutil.DeleteVal(old.Entities, handle) viewers = old.viewers @@ -223,14 +223,14 @@ func (t ticker) tickEntities(tx *Tx, tick int64) { for _, viewer := range viewers { if sliceutil.Index(c.viewers, viewer) == -1 { - // First we hide the entity from all loaders that were previously viewing it, but no + // First we hide the Entity from all loaders that were previously viewing it, but no // longer are. viewer.HideEntity(e) } } for _, viewer := range c.viewers { if sliceutil.Index(viewers, viewer) == -1 { - // Then we show the entity to all loaders that are now viewing the entity in the new + // Then we show the Entity to all loaders that are now viewing the Entity in the new // chunk. showEntity(e, viewer) } diff --git a/server/world/tx.go b/server/world/tx.go index 99e66f389..16bc3922e 100644 --- a/server/world/tx.go +++ b/server/world/tx.go @@ -3,14 +3,10 @@ package world import ( "github.com/df-mc/dragonfly/server/block/cube" "github.com/go-gl/mathgl/mgl64" - "sync/atomic" "time" ) -type Tx struct { - complete atomic.Bool - w *World -} +type Tx struct{ w *World } // Range returns the lower and upper bounds of the World that the Tx is // operating on. @@ -19,102 +15,99 @@ func (tx *Tx) Range() cube.Range { } func (tx *Tx) SetBlock(pos cube.Pos, b Block, opts *SetOpts) { - tx.World().setBlock(pos, b, opts) + tx.w.setBlock(pos, b, opts) } func (tx *Tx) Block(pos cube.Pos) Block { - return tx.World().block(pos) + return tx.w.block(pos) } func (tx *Tx) Liquid(pos cube.Pos) (Liquid, bool) { - return tx.World().liquid(pos) + return tx.w.liquid(pos) } func (tx *Tx) SetLiquid(pos cube.Pos, b Liquid) { - tx.World().setLiquid(pos, b) + tx.w.setLiquid(pos, b) } func (tx *Tx) BuildStructure(pos cube.Pos, s Structure) { - tx.World().buildStructure(pos, s) + tx.w.buildStructure(pos, s) } func (tx *Tx) ScheduleBlockUpdate(pos cube.Pos, delay time.Duration) { - tx.World().scheduleBlockUpdate(pos, delay) + tx.w.scheduleBlockUpdate(pos, delay) } func (tx *Tx) HighestLightBlocker(x, z int) int { - return tx.World().highestLightBlocker(x, z) + return tx.w.highestLightBlocker(x, z) } func (tx *Tx) HighestBlock(x, z int) int { - return tx.World().highestBlock(x, z) + return tx.w.highestBlock(x, z) } func (tx *Tx) Light(pos cube.Pos) uint8 { - return tx.World().light(pos) + return tx.w.light(pos) } func (tx *Tx) Skylight(pos cube.Pos) uint8 { - return tx.World().skyLight(pos) + return tx.w.skyLight(pos) } func (tx *Tx) SetBiome(pos cube.Pos, b Biome) { - tx.World().setBiome(pos, b) + tx.w.setBiome(pos, b) } func (tx *Tx) Biome(pos cube.Pos) Biome { - return tx.World().biome(pos) + return tx.w.biome(pos) } func (tx *Tx) Temperature(pos cube.Pos) float64 { - return tx.World().temperature(pos) + return tx.w.temperature(pos) } func (tx *Tx) RainingAt(pos cube.Pos) bool { - return tx.World().rainingAt(pos) + return tx.w.rainingAt(pos) } func (tx *Tx) SnowingAt(pos cube.Pos) bool { - return tx.World().snowingAt(pos) + return tx.w.snowingAt(pos) } func (tx *Tx) ThunderingAt(pos cube.Pos) bool { - return tx.World().thunderingAt(pos) + return tx.w.thunderingAt(pos) } func (tx *Tx) AddParticle(pos mgl64.Vec3, p Particle) { - tx.World().addParticle(pos, p) + tx.w.addParticle(pos, p) } func (tx *Tx) PlaySound(pos mgl64.Vec3, s Sound) { - tx.World().playSound(pos, s) + tx.w.playSound(pos, s) } func (tx *Tx) AddEntity(e *EntityHandle) Entity { - return tx.World().addEntity(tx, e) + return tx.w.addEntity(tx, e) } func (tx *Tx) RemoveEntity(e Entity) { - tx.World().removeEntity(e) + tx.w.removeEntity(e, tx) } func (tx *Tx) EntitiesWithin(box cube.BBox, ignore func(Entity) bool) []Entity { - return tx.World().entitiesWithin(tx, box, ignore) + return tx.w.entitiesWithin(tx, box, ignore) } func (tx *Tx) Entities() []Entity { - return tx.World().allEntities(tx) + return tx.w.allEntities(tx) } func (tx *Tx) Viewers(pos mgl64.Vec3) []Viewer { - return tx.World().viewersOf(pos) + return tx.w.viewersOf(pos) } // World returns the World of the Tx. It panics if the transaction was already // marked complete. func (tx *Tx) World() *World { - if tx.complete.Load() { - panic("transaction already completed") - } return tx.w } diff --git a/server/world/viewer.go b/server/world/viewer.go index eae4c8562..0363045a0 100644 --- a/server/world/viewer.go +++ b/server/world/viewer.go @@ -11,22 +11,22 @@ import ( // Viewer is a viewer in the world. It can view changes that are made in the world, such as the addition of // entities and the changes of blocks. type Viewer interface { - // ViewEntity views the entity passed. It is called for every entity that the viewer may encounter in the + // ViewEntity views the Entity passed. It is called for every Entity that the viewer may encounter in the // world, either by moving entities or by moving the viewer using a world.Loader. ViewEntity(e Entity) - // HideEntity stops viewing the entity passed. It is called for every entity that leaves the viewing range + // HideEntity stops viewing the Entity passed. It is called for every Entity that leaves the viewing range // of the viewer, either by its movement or the movement of the viewer using a world.Loader. HideEntity(e Entity) - // ViewEntityGameMode views the game mode of the entity passed. This is necessary for game-modes like spectator, - // which may update how the entity is viewed for others. + // ViewEntityGameMode views the game mode of the Entity passed. This is necessary for game-modes like spectator, + // which may update how the Entity is viewed for others. ViewEntityGameMode(e Entity) - // ViewEntityMovement views the movement of an entity. The entity is moved with a delta position, yaw and - // pitch, which, when applied to the respective values of the entity, will result in the final values. + // ViewEntityMovement views the movement of an Entity. The Entity is moved with a delta position, yaw and + // pitch, which, when applied to the respective values of the Entity, will result in the final values. ViewEntityMovement(e Entity, pos mgl64.Vec3, rot cube.Rotation, onGround bool) - // ViewEntityVelocity views the velocity of an entity. It is called right before a call to + // ViewEntityVelocity views the velocity of an Entity. It is called right before a call to // ViewEntityMovement so that the Viewer may interpolate the movement itself. ViewEntityVelocity(e Entity, vel mgl64.Vec3) - // ViewEntityTeleport views the teleportation of an entity. The entity is immediately moved to a different + // ViewEntityTeleport views the teleportation of an Entity. The Entity is immediately moved to a different // target position. ViewEntityTeleport(e Entity, pos mgl64.Vec3) // ViewFurnaceUpdate updates a furnace for the associated session based on previous times. @@ -37,17 +37,17 @@ type Viewer interface { // ViewTime views the time of the world. It is called every time the time is changed or otherwise every // second. ViewTime(t int) - // ViewEntityItems views the items currently held by an entity that is able to equip items. + // ViewEntityItems views the items currently held by an Entity that is able to equip items. ViewEntityItems(e Entity) - // ViewEntityArmour views the items currently equipped as armour by the entity. + // ViewEntityArmour views the items currently equipped as armour by the Entity. ViewEntityArmour(e Entity) - // ViewEntityAction views an action performed by an entity. Available actions may be found in the `action` + // ViewEntityAction views an action performed by an Entity. Available actions may be found in the `action` // package, and include things such as swinging an arm. ViewEntityAction(e Entity, a EntityAction) - // ViewEntityState views the current state of an entity. It is called whenever an entity changes its + // ViewEntityState views the current state of an Entity. It is called whenever an Entity changes its // physical appearance, for example when sprinting. ViewEntityState(e Entity) - // ViewEntityAnimation starts viewing an animation performed by an entity. The animation has to be from a resource pack. + // ViewEntityAnimation starts viewing an animation performed by an Entity. The animation has to be from a resource pack. ViewEntityAnimation(e Entity, animationName string) // ViewParticle views a particle spawned at a given position in the world. It is called when a particle, // for example a block breaking particle, is spawned near the player. @@ -60,7 +60,7 @@ type Viewer interface { // ViewBlockAction views an action performed by a block. Available actions may be found in the `action` // package, and include things such as a chest opening. ViewBlockAction(pos cube.Pos, a BlockAction) - // ViewEmote views an emote being performed by another entity. + // ViewEmote views an emote being performed by another Entity. ViewEmote(e Entity, emote uuid.UUID) // ViewSkin views the current skin of a player. ViewSkin(e Entity) diff --git a/server/world/weather.go b/server/world/weather.go index 8f5f82eac..37a5ef13e 100644 --- a/server/world/weather.go +++ b/server/world/weather.go @@ -194,7 +194,7 @@ func (w weather) lightningPosition(tx *Tx, c ChunkPos) mgl64.Vec3 { return vec } -// adjustPositionToEntities adjusts the mgl64.Vec3 passed to the position of any entity found in the 3x3 column upwards +// adjustPositionToEntities adjusts the mgl64.Vec3 passed to the position of any Entity found in the 3x3 column upwards // from the mgl64.Vec3. If multiple entities are found, the position of one of the entities is selected randomly. func (w weather) adjustPositionToEntities(tx *Tx, vec mgl64.Vec3) mgl64.Vec3 { max := vec.Add(mgl64.Vec3{0, float64(w.w.Range().Max())}) @@ -203,8 +203,8 @@ func (w weather) adjustPositionToEntities(tx *Tx, vec mgl64.Vec3) mgl64.Vec3 { list := make([]mgl64.Vec3, 0, len(ent)/3) for _, e := range ent { if h, ok := e.(interface{ Health() float64 }); ok && h.Health() > 0 { - // Any (living) entity that is positioned higher than the highest block at its position is eligible to be - // struck by lightning. We first save all entity positions where this is the case. + // Any (living) Entity that is positioned higher than the highest block at its position is eligible to be + // struck by lightning. We first save all Entity positions where this is the case. pos := cube.PosFromVec3(e.Position()) if tx.HighestBlock(pos[0], pos[1]) < pos[2] { list = append(list, e.Position()) @@ -212,7 +212,7 @@ func (w weather) adjustPositionToEntities(tx *Tx, vec mgl64.Vec3) mgl64.Vec3 { } } // We then select one of the positions of entities higher than the highest block and adjust the position of the - // lightning to it, so that the entity is struck directly. + // lightning to it, so that the Entity is struck directly. if len(list) > 0 { vec = list[w.w.r.Intn(len(list))] } diff --git a/server/world/world.go b/server/world/world.go index de407fa83..432bf7d1e 100644 --- a/server/world/world.go +++ b/server/world/world.go @@ -56,7 +56,7 @@ type World struct { chunks map[ChunkPos]*Column // entities holds a map of entities currently loaded and the last ChunkPos that the Entity was in. - // These are tracked so that a call to RemoveEntity can find the correct entity. + // These are tracked so that a call to RemoveEntity can find the correct Entity. entities map[*EntityHandle]ChunkPos r *rand.Rand @@ -147,7 +147,7 @@ func (w *World) blockInChunk(c *Column, pos cube.Pos) Block { } rid := c.Block(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 0) if nbtBlocks[rid] { - // The block was also a block entity, so we look it up in the map. + // The block was also a block Entity, so we look it up in the map. if b, ok := c.BlockEntities[pos]; ok { return b } @@ -634,38 +634,38 @@ func (w *World) playSound(pos mgl64.Vec3, s Sound) { } } -// AddEntity adds an entity to the world at the position that the entity has. The entity will be visible to -// 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. +// AddEntity adds an Entity to the world at the position that the Entity has. The Entity will be visible to +// 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(tx *Tx, handle *EntityHandle) Entity { - handle.setAndUnlockWorld(w) + handle.setAndUnlockWorld(w, tx) pos := chunkPosFromVec3(handle.data.Pos) w.entities[handle] = pos c := w.chunk(pos) c.Entities, c.modified = append(c.Entities, handle), true - e := handle.Entity(tx) + e := handle.mustEntity(tx) for _, v := range c.viewers { - // We show the entity to all viewers currently in the chunk that the entity is spawned in. + // We show the Entity to all viewers currently in the chunk that the Entity is spawned in. showEntity(e, v) } w.Handler().HandleEntitySpawn(e) return e } -// RemoveEntity removes an entity from the world that is currently present in it. Any viewers of the entity +// RemoveEntity removes an Entity from the world that is currently present in it. Any viewers of the Entity // will no longer be able to see it. -// RemoveEntity operates assuming the position of the entity is the same as where it is currently in the +// RemoveEntity operates assuming the position of the Entity is the same as where it is currently in the // world. If it can not find it there, it will loop through all entities and try to find it. -// RemoveEntity assumes the entity is currently loaded and in a loaded chunk. If not, the function will not do +// RemoveEntity assumes the Entity is currently loaded and in a loaded chunk. If not, the function will not do // anything. -func (w *World) removeEntity(e Entity) *EntityHandle { +func (w *World) removeEntity(e Entity, tx *Tx) *EntityHandle { handle := e.Handle() pos, found := w.entities[handle] if !found { - // The entity currently isn't in this world. + // The Entity currently isn't in this world. return nil } w.Handler().HandleEntityDespawn(e) @@ -676,9 +676,8 @@ func (w *World) removeEntity(e Entity) *EntityHandle { for _, v := range c.viewers { v.HideEntity(e) } - - handle.unsetAndLockWorld() delete(w.entities, handle) + handle.unsetAndLockWorld(tx) return handle } @@ -698,12 +697,12 @@ func (w *World) entitiesWithin(tx *Tx, box cube.BBox, ignored func(Entity) bool) continue } for _, handle := range c.Entities { - e := handle.Entity(tx) + e := handle.mustEntity(tx) if ignored != nil && ignored(e) { continue } if box.Vec3Within(handle.data.Pos) { - // The entity position was within the BBox, so we add it to the slice to return. + // The Entity position was within the BBox, so we add it to the slice to return. entities = append(entities, e) } } @@ -716,7 +715,7 @@ func (w *World) entitiesWithin(tx *Tx, box cube.BBox, ignored func(Entity) bool) func (w *World) allEntities(tx *Tx) []Entity { m := make([]Entity, 0, len(w.entities)) for e := range w.entities { - m = append(m, e.Entity(tx)) + m = append(m, e.mustEntity(tx)) } return m } @@ -990,13 +989,13 @@ func (w *World) addWorldViewer(l *Loader) { } // addViewer adds a viewer to the world at a given position. Any events that happen in the chunk at that -// position, such as block changes, entity changes etc., will be sent to the viewer. +// position, such as block changes, Entity changes etc., will be sent to the viewer. func (w *World) addViewer(tx *Tx, c *Column, loader *Loader) { c.viewers = append(c.viewers, loader.viewer) c.loaders = append(c.loaders, loader) for _, entity := range c.Entities { - showEntity(entity.Entity(tx), loader.viewer) + showEntity(entity.mustEntity(tx), loader.viewer) } } @@ -1017,7 +1016,7 @@ func (w *World) removeViewer(tx *Tx, pos ChunkPos, loader *Loader) { // After removing the loader from the chunk, we also need to hide all entities from the viewer. for _, entity := range c.Entities { - loader.viewer.HideEntity(entity.Entity(tx)) + loader.viewer.HideEntity(entity.mustEntity(tx)) } } @@ -1036,7 +1035,7 @@ func (w *World) Handler() Handler { return *w.handler.Load() } -// showEntity shows an entity to a viewer of the world. It makes sure everything of the entity, including the +// showEntity shows an Entity to a viewer of the world. It makes sure everything of the Entity, including the // items held, is shown. func showEntity(e Entity, viewer Viewer) { viewer.ViewEntity(e) @@ -1074,7 +1073,7 @@ func (w *World) loadChunk(pos ChunkPos) (*Column, error) { w.chunks[pos] = col for _, e := range col.Entities { w.entities[e] = pos - e.w.Store(w) + e.w = w } return col, nil case errors.Is(err, leveldb.ErrNotFound): @@ -1137,7 +1136,7 @@ func (w *World) saveChunk(tx *Tx, pos ChunkPos, c *Column, closeEntities bool) { } if closeEntities { for _, e := range c.Entities { - _ = e.Entity(tx).Close() + _ = e.mustEntity(tx).Close() } clear(c.Entities) } @@ -1214,12 +1213,12 @@ func columnFrom(c *chunk.Column, w *World) *Column { for _, e := range c.Entities { eid, ok := e.Data["identifier"].(string) if !ok { - w.conf.Log.Error("read column: entity without identifier field", "ID", e.ID) + w.conf.Log.Error("read column: Entity without identifier field", "ID", e.ID) continue } t, ok := w.conf.Entities.Lookup(eid) if !ok { - w.conf.Log.Error("read column: unknown entity type", "ID", e.ID, "type", eid) + w.conf.Log.Error("read column: unknown Entity type", "ID", e.ID, "type", eid) continue } col.Entities = append(col.Entities, entityFromData(t, e.ID, e.Data))