diff --git a/server/block/fire.go b/server/block/fire.go index 2f986d585..cbb570380 100644 --- a/server/block/fire.go +++ b/server/block/fire.go @@ -69,7 +69,7 @@ func (f Fire) burn(from, to cube.Pos, w *world.World, r *rand.Rand, chanceBound return } if t, ok := flammable.(TNT); ok { - t.Ignite(to, w) + t.Ignite(to, w, nil) return } w.SetBlock(to, nil, nil) diff --git a/server/block/tnt.go b/server/block/tnt.go index 32f11ee7d..061ea1c20 100644 --- a/server/block/tnt.go +++ b/server/block/tnt.go @@ -14,13 +14,14 @@ import ( // TNT is an explosive block that can be primed to generate an explosion. type TNT struct { solid + igniter world.Entity } // Activate ... func (t TNT) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool { held, _ := u.HeldItems() if _, ok := held.Enchantment(enchantment.FireAspect{}); ok { - t.Ignite(pos, w) + t.Ignite(pos, w, u) ctx.DamageItem(1) return true } @@ -28,14 +29,21 @@ func (t TNT) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ct } // Ignite ... -func (t TNT) Ignite(pos cube.Pos, w *world.World) bool { - spawnTnt(pos, w, time.Second*4) +func (t TNT) Ignite(pos cube.Pos, w *world.World, igniter world.Entity) bool { + t.igniter = igniter + spawnTnt(pos, w, time.Second*4, t.igniter) 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(_ mgl64.Vec3, pos cube.Pos, w *world.World, _ ExplosionConfig) { - spawnTnt(pos, w, time.Second/2+time.Duration(rand.Intn(int(time.Second+time.Second/2)))) + spawnTnt(pos, w, time.Second/2+time.Duration(rand.Intn(int(time.Second+time.Second/2))), t.igniter) } // BreakInfo ... @@ -59,8 +67,8 @@ 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, w *world.World, fuse time.Duration) { +func spawnTnt(pos cube.Pos, w *world.World, fuse time.Duration, igniter world.Entity) { w.PlaySound(pos.Vec3Centre(), sound.TNT{}) w.SetBlock(pos, nil, nil) - w.AddEntity(w.EntityRegistry().Config().TNT(pos.Vec3Centre(), fuse)) + w.AddEntity(w.EntityRegistry().Config().TNT(pos.Vec3Centre(), fuse, igniter)) } diff --git a/server/entity/projectile.go b/server/entity/projectile.go index d9ecdd7dc..a6dfec91c 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -170,7 +170,7 @@ func (lt *ProjectileBehaviour) Tick(e *Ent) *Movement { case trace.BlockResult: bpos := r.BlockPosition() if t, ok := w.Block(bpos).(block.TNT); ok && e.OnFireDuration() > 0 { - t.Ignite(bpos, w) + t.Ignite(bpos, w, e) } if lt.conf.SurviveBlockCollision { lt.hitBlockSurviving(e, r, m) diff --git a/server/entity/register.go b/server/entity/register.go index a8353b978..1697fb152 100644 --- a/server/entity/register.go +++ b/server/entity/register.go @@ -39,8 +39,8 @@ var conf = world.EntityRegistryConfig{ FallingBlock: func(bl world.Block, pos mgl64.Vec3) world.Entity { return NewFallingBlock(bl, pos) }, - TNT: func(pos mgl64.Vec3, fuse time.Duration) world.Entity { - return NewTNT(pos, fuse) + TNT: func(pos mgl64.Vec3, fuse time.Duration, igniter world.Entity) world.Entity { + return NewTNT(pos, fuse, igniter) }, BottleOfEnchanting: func(pos, vel mgl64.Vec3, owner world.Entity) world.Entity { b := NewBottleOfEnchanting(pos, owner) diff --git a/server/entity/tnt.go b/server/entity/tnt.go index 4773ac2a1..d2d7d1c32 100644 --- a/server/entity/tnt.go +++ b/server/entity/tnt.go @@ -12,10 +12,10 @@ import ( ) // NewTNT creates a new primed TNT entity. -func NewTNT(pos mgl64.Vec3, fuse time.Duration) *Ent { +func NewTNT(pos mgl64.Vec3, fuse time.Duration, igniter world.Entity) *Ent { config := tntConf config.ExistenceDuration = fuse - ent := Config{Behaviour: config.New()}.New(TNTType{}, pos) + 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} @@ -35,7 +35,13 @@ func explodeTNT(e *Ent) { } // TNTType is a world.EntityType implementation for TNT. -type TNTType struct{} +type TNTType struct { + igniter world.Entity +} + +// 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 (TNTType) EncodeEntity() string { return "minecraft:tnt" } func (TNTType) NetworkOffset() float64 { return 0.49 } @@ -43,8 +49,8 @@ func (TNTType) BBox(world.Entity) cube.BBox { return cube.Box(-0.49, 0, -0.49, 0.49, 0.98, 0.49) } -func (TNTType) DecodeNBT(m map[string]any) world.Entity { - tnt := NewTNT(nbtconv.Vec3(m, "Pos"), nbtconv.TickDuration[uint8](m, "Fuse")) +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 } diff --git a/server/item/fire_charge.go b/server/item/fire_charge.go index ceffd7ace..7e1e0c70c 100644 --- a/server/item/fire_charge.go +++ b/server/item/fire_charge.go @@ -19,8 +19,8 @@ func (f FireCharge) EncodeItem() (name string, meta int16) { } // UseOnBlock ... -func (f FireCharge) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool { - if l, ok := w.Block(pos).(ignitable); ok && l.Ignite(pos, w) { +func (f FireCharge) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, u User, ctx *UseContext) bool { + if l, ok := w.Block(pos).(ignitable); ok && l.Ignite(pos, w, u) { ctx.SubtractFromCount(1) w.PlaySound(pos.Vec3Centre(), sound.FireCharge{}) return true diff --git a/server/item/flint_and_steel.go b/server/item/flint_and_steel.go index 2e7c19bfd..9f600b97f 100644 --- a/server/item/flint_and_steel.go +++ b/server/item/flint_and_steel.go @@ -28,13 +28,13 @@ func (f FlintAndSteel) DurabilityInfo() DurabilityInfo { // ignitable represents a block that can be lit by a fire emitter, such as flint and steel. type ignitable interface { // Ignite is called when the block is lit by flint and steel. - Ignite(pos cube.Pos, w *world.World) bool + Ignite(pos cube.Pos, w *world.World, igniter world.Entity) bool } // UseOnBlock ... -func (f FlintAndSteel) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool { +func (f FlintAndSteel) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, u User, ctx *UseContext) bool { ctx.DamageItem(1) - if l, ok := w.Block(pos).(ignitable); ok && l.Ignite(pos, w) { + if l, ok := w.Block(pos).(ignitable); ok && l.Ignite(pos, w, u) { return true } else if s := pos.Side(face); w.Block(s) == air() { w.PlaySound(s.Vec3Centre(), sound.Ignite{}) diff --git a/server/world/entity.go b/server/world/entity.go index 3bbf02cb5..0233388dc 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -101,7 +101,7 @@ type EntityRegistry struct { type EntityRegistryConfig struct { Item func(it any, pos, vel mgl64.Vec3) Entity FallingBlock func(bl Block, pos mgl64.Vec3) Entity - TNT func(pos mgl64.Vec3, fuse time.Duration) Entity + TNT func(pos mgl64.Vec3, fuse time.Duration, igniter Entity) Entity BottleOfEnchanting func(pos, vel mgl64.Vec3, owner Entity) Entity Arrow func(pos, vel mgl64.Vec3, rot cube.Rotation, damage float64, owner Entity, critical, disallowPickup, obtainArrowOnPickup bool, punchLevel int, tip any) Entity Egg func(pos, vel mgl64.Vec3, owner Entity) Entity