Skip to content

Commit

Permalink
world/entity.go: More fixes, should make moving entities across world…
Browse files Browse the repository at this point in the history
…s significantly safer.
  • Loading branch information
Sandertv committed Nov 12, 2024
1 parent d595598 commit 1c6aa27
Show file tree
Hide file tree
Showing 27 changed files with 216 additions and 195 deletions.
8 changes: 4 additions & 4 deletions server/entity/area_effect_cloud_behaviour.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand All @@ -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.
Expand Down Expand Up @@ -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
})
Expand Down Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion server/entity/arrow.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
2 changes: 1 addition & 1 deletion server/entity/bottle_of_enchanting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
2 changes: 1 addition & 1 deletion server/entity/egg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
5 changes: 3 additions & 2 deletions server/entity/ender_pearl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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{})
Expand Down
39 changes: 18 additions & 21 deletions server/entity/experience_orb_behaviour.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -70,46 +70,43 @@ 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)
if dist := diff.LenSqr(); dist < 1 {
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()
}
}
Expand Down
2 changes: 1 addition & 1 deletion server/entity/firework.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
19 changes: 11 additions & 8 deletions server/entity/firework_behaviour.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion server/entity/lingering_potion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
9 changes: 5 additions & 4 deletions server/entity/projectile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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())
}
}
2 changes: 1 addition & 1 deletion server/entity/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion server/entity/snowball.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
2 changes: 1 addition & 1 deletion server/entity/splash_potion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion server/item/bow.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 7 additions & 4 deletions server/session/entity_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -201,7 +203,7 @@ type scaled interface {
}

type owned interface {
Owner() world.Entity
Owner() *world.EntityHandle
}

type named interface {
Expand Down Expand Up @@ -259,6 +261,7 @@ type tnt interface {
}

type living interface {
UUID() uuid.UUID
DeathPosition() (mgl64.Vec3, world.Dimension, bool)
}

Expand Down
15 changes: 10 additions & 5 deletions server/session/handler_inventory_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,23 +114,28 @@ 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
// no longer exists server side. This is expected, so we shouldn't kick the player.
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)
}
Expand Down
Loading

0 comments on commit 1c6aa27

Please sign in to comment.