From 834a164a48371d370445ea7023c6baec910ccf00 Mon Sep 17 00:00:00 2001 From: JustTal Date: Tue, 16 Aug 2022 21:22:52 -0500 Subject: [PATCH 1/5] server/block: Implemented bells. Currently missing bell models and ringing through projectiles. --- cmd/blockhash/main.go | 2 + server/block/action.go | 17 +++- server/block/bell.go | 150 ++++++++++++++++++++++++++++++++ server/block/bell_attachment.go | 53 +++++++++++ server/block/hash.go | 5 ++ server/block/register.go | 2 + server/session/world.go | 24 +++++ server/world/sound/block.go | 3 + 8 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 server/block/bell.go create mode 100644 server/block/bell_attachment.go diff --git a/cmd/blockhash/main.go b/cmd/blockhash/main.go index e8a39d688..6eb1766da 100644 --- a/cmd/blockhash/main.go +++ b/cmd/blockhash/main.go @@ -226,6 +226,8 @@ func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[ return "uint64(" + s + ".Uint8())", 3 case "AnvilType", "SandstoneType", "PrismarineType", "StoneBricksType", "NetherBricksType", "FroglightType", "WallConnectionType", "BlackstoneType", "DeepslateType": return "uint64(" + s + ".Uint8())", 2 + case "BellAttachment": + return "uint64(" + s + ".Uint8())", 2 case "OreType", "FireType", "GrassType": return "uint64(" + s + ".Uint8())", 1 case "Direction", "Axis": diff --git a/server/block/action.go b/server/block/action.go index 5242340e6..53595146f 100644 --- a/server/block/action.go +++ b/server/block/action.go @@ -1,6 +1,9 @@ package block -import "time" +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "time" +) // OpenAction is a world.BlockAction to open a block at a position. It is sent for blocks such as chests. type OpenAction struct{ action } @@ -8,10 +11,20 @@ type OpenAction struct{ action } // CloseAction is a world.BlockAction to close a block at a position, complementary to the OpenAction action. type CloseAction struct{ action } +// BellRing is a world.BlockAction to ring a bell at a position. +type BellRing struct { + action + + // Face is the face at which the bell was rung from. + Face cube.Face +} + // StartCrackAction is a world.BlockAction to make the cracks in a block start forming, following the break time set in // the action. type StartCrackAction struct { action + + // BreakTime ... BreakTime time.Duration } @@ -20,6 +33,8 @@ type StartCrackAction struct { // ground, submerged or is using a different item than at first. type ContinueCrackAction struct { action + + // BreakTime ... BreakTime time.Duration } diff --git a/server/block/bell.go b/server/block/bell.go new file mode 100644 index 000000000..dfacdabe3 --- /dev/null +++ b/server/block/bell.go @@ -0,0 +1,150 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/item" + "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" +) + +// Bell is a transparent, animated block entity that produces a sound when used. Unlike most utility blocks, bells +// cannot be crafted. +type Bell struct { + transparent + + // Attach represents the attachment type of the Bell. + Attach BellAttachment + // Facing represents the direction the Bell is facing. + Facing cube.Direction +} + +// Model ... +func (b Bell) Model() world.BlockModel { + // TODO: Use the actual bell model. + return model.Empty{} +} + +// BreakInfo ... +func (b Bell) BreakInfo() BreakInfo { + return newBreakInfo(1, pickaxeHarvestable, pickaxeEffective, oneOf(b)).withBlastResistance(15) +} + +// UseOnBlock ... +func (b Bell) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(w, pos, face, b) + if !used { + return false + } + b.Facing = user.Facing().Opposite() + if face == cube.FaceUp { + if _, ok := w.Block(pos.Side(cube.FaceDown)).Model().(model.Solid); !ok { + return false + } + } else if face == cube.FaceDown { + if _, ok := w.Block(pos.Side(cube.FaceUp)).Model().(model.Solid); !ok { + return false + } + b.Attach = HangingBellAttachment() + } else { + if _, ok := w.Block(pos.Side(face.Opposite())).Model().(model.Solid); !ok { + return false + } + b.Facing = face.Direction() + b.Attach = WallBellAttachment() + if _, ok := w.Block(pos.Side(face)).Model().(model.Solid); ok { + b.Attach = WallsBellAttachment() + } + } + place(w, pos, b, user, ctx) + return placed(ctx) +} + +// NeighbourUpdateTick ... +func (b Bell) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { + var supportFaces []cube.Face + switch b.Attach { + case HangingBellAttachment(): + supportFaces = append(supportFaces, cube.FaceUp) + case StandingBellAttachment(): + supportFaces = append(supportFaces, cube.FaceDown) + case WallBellAttachment(), WallsBellAttachment(): + supportFaces = append(supportFaces, b.Facing.Face().Opposite()) + if b.Attach == WallsBellAttachment() { + supportFaces = append(supportFaces, b.Facing.Face()) + } + } + for _, supportFace := range supportFaces { + if _, ok := w.Block(pos.Side(supportFace)).Model().(model.Solid); !ok { + w.SetBlock(pos, nil, nil) + w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: b}) + dropItem(w, item.NewStack(b, 1), pos.Vec3Centre()) + break + } + } +} + +// Activate ... +func (b Bell) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool { + s, f := u.Facing().Opposite().Face(), b.Facing.Face() + switch b.Attach { + case HangingBellAttachment(): + b.Ring(pos, s, w) + return true + case StandingBellAttachment(): + if s.Axis() == f.Axis() { + b.Ring(pos, s, w) + return true + } + case WallBellAttachment(), WallsBellAttachment(): + if s == f.RotateLeft() || s == f.RotateRight() { + b.Ring(pos, s, w) + return true + } + } + return false +} + +// Ring rings the bell on the face passed. +func (b Bell) Ring(pos cube.Pos, face cube.Face, w *world.World) { + w.PlaySound(pos.Vec3Centre(), sound.BellRing{}) + for _, v := range w.Viewers(pos.Vec3Centre()) { + v.ViewBlockAction(pos, BellRing{Face: face}) + } +} + +// EncodeNBT encodes the Bell's block entity ID. There are other properties, but we can skip those. +func (b Bell) EncodeNBT() map[string]any { + return map[string]any{"id": "Bell"} +} + +// DecodeNBT ... +func (b Bell) DecodeNBT(map[string]any) any { + return b +} + +// EncodeItem ... +func (b Bell) EncodeItem() (name string, meta int16) { + return "minecraft:bell", 0 +} + +// EncodeBlock ... +func (b Bell) EncodeBlock() (string, map[string]any) { + return "minecraft:bell", map[string]any{ + "toggle_bit": uint8(0), // Useless property, updated on ring in vanilla. + "attachment": b.Attach.String(), + "direction": int32(horizontalDirection(b.Facing)), + } +} + +// allBells ... +func allBells() (bells []world.Block) { + for _, a := range BellAttachments() { + for _, d := range cube.Directions() { + bells = append(bells, Bell{Attach: a, Facing: d}) + } + } + return +} diff --git a/server/block/bell_attachment.go b/server/block/bell_attachment.go new file mode 100644 index 000000000..e52a4652a --- /dev/null +++ b/server/block/bell_attachment.go @@ -0,0 +1,53 @@ +package block + +// BellAttachment represents a type of attachment for a Bell. +type BellAttachment struct { + bellAttachment +} + +// StandingBellAttachment is a type of attachment for a standing Bell. +func StandingBellAttachment() BellAttachment { + return BellAttachment{0} +} + +// HangingBellAttachment is a type of attachment for a hanging Bell. +func HangingBellAttachment() BellAttachment { + return BellAttachment{1} +} + +// WallBellAttachment is a type of attachment for a wall Bell. +func WallBellAttachment() BellAttachment { + return BellAttachment{2} +} + +// WallsBellAttachment is a type of attachment for a two-wall Bell. +func WallsBellAttachment() BellAttachment { + return BellAttachment{3} +} + +// BellAttachments returns all possible BellAttachments. +func BellAttachments() []BellAttachment { + return []BellAttachment{StandingBellAttachment(), HangingBellAttachment(), WallBellAttachment(), WallsBellAttachment()} +} + +type bellAttachment uint8 + +// Uint8 returns the BellAttachment as a uint8. +func (g bellAttachment) Uint8() uint8 { + return uint8(g) +} + +// String returns the BellAttachment as a string. +func (g bellAttachment) String() string { + switch g { + case 0: + return "standing" + case 1: + return "hanging" + case 2: + return "side" + case 3: + return "multiple" + } + panic("should never happen") +} diff --git a/server/block/hash.go b/server/block/hash.go index 4137a1d20..f3cd3fb04 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -15,6 +15,7 @@ const ( hashBeacon hashBedrock hashBeetrootSeeds + hashBell hashBlackstone hashBlastFurnace hashBlueIce @@ -216,6 +217,10 @@ func (b BeetrootSeeds) Hash() uint64 { return hashBeetrootSeeds | uint64(b.Growth)<<8 } +func (b Bell) Hash() uint64 { + return hashBell | uint64(b.Attach.Uint8())<<8 | uint64(b.Facing)<<10 +} + func (b Blackstone) Hash() uint64 { return hashBlackstone | uint64(b.Type.Uint8())<<8 } diff --git a/server/block/register.go b/server/block/register.go index 39de7f8ff..9c618e591 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -117,6 +117,7 @@ func init() { registerAll(allBarrels()) registerAll(allBasalt()) registerAll(allBeetroot()) + registerAll(allBells()) registerAll(allBlackstone()) registerAll(allBlastFurnaces()) registerAll(allBoneBlock()) @@ -202,6 +203,7 @@ func init() { world.RegisterItem(Beacon{}) world.RegisterItem(Bedrock{}) world.RegisterItem(BeetrootSeeds{}) + world.RegisterItem(Bell{}) world.RegisterItem(BlastFurnace{}) world.RegisterItem(BlueIce{}) world.RegisterItem(Bone{}) diff --git a/server/session/world.go b/server/session/world.go index d84dab11d..6c075dc94 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -521,6 +521,8 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) pk.SoundType = packet.SoundEventBlastFurnaceUse case sound.SmokerCrackle: pk.SoundType = packet.SoundEventSmokerUse + case sound.BellRing: + pk.SoundType = packet.SoundEventBell case sound.UseSpyglass: pk.SoundType = packet.SoundEventUseSpyglass case sound.StopUsingSpyglass: @@ -956,6 +958,28 @@ func (s *Session) ViewBlockAction(pos cube.Pos, a world.BlockAction) { Position: vec64To32(pos.Vec3()), EventData: int32(65535 / (t.BreakTime.Seconds() * 20)), }) + case block.BellRing: + d := int32(0) + switch t.Face.Direction() { + case cube.West: + d = 1 + case cube.North: + d = 2 + case cube.East: + d = 3 + } + s.writePacket(&packet.BlockActorData{ + Position: blockPos, + NBTData: map[string]any{ + "Direction": d, + "Ringing": uint8(1), + + "id": "Bell", + "x": blockPos.X(), + "y": blockPos.Y(), + "z": blockPos.Z(), + }, + }) } } diff --git a/server/world/sound/block.go b/server/world/sound/block.go index b0ef92a49..365140812 100644 --- a/server/world/sound/block.go +++ b/server/world/sound/block.go @@ -110,6 +110,9 @@ type BlastFurnaceCrackle struct{ sound } // SmokerCrackle is a sound played every one to five seconds from a smoker. type SmokerCrackle struct{ sound } +// BellRing is a sound played when a bell is rung. +type BellRing struct{ sound } + // sound implements the world.Sound interface. type sound struct{} From b6683b155a98dbea92b121083aea57bf5c0800d1 Mon Sep 17 00:00:00 2001 From: JustTal Date: Tue, 16 Aug 2022 23:29:55 -0500 Subject: [PATCH 2/5] block/bell.go: Projectile functionality. --- server/block/bell.go | 7 ++++++- server/block/block.go | 6 ++++++ server/entity/projectile.go | 9 +++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/server/block/bell.go b/server/block/bell.go index dfacdabe3..7f41d2e09 100644 --- a/server/block/bell.go +++ b/server/block/bell.go @@ -24,7 +24,7 @@ type Bell struct { // Model ... func (b Bell) Model() world.BlockModel { // TODO: Use the actual bell model. - return model.Empty{} + return model.Solid{} } // BreakInfo ... @@ -107,6 +107,11 @@ func (b Bell) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ return false } +// ProjectileHit ... +func (b Bell) ProjectileHit(w *world.World, _ world.Entity, pos cube.Pos, face cube.Face) { + b.Ring(pos, face, w) +} + // Ring rings the bell on the face passed. func (b Bell) Ring(pos cube.Pos, face cube.Face, w *world.World) { w.PlaySound(pos.Vec3Centre(), sound.BellRing{}) diff --git a/server/block/block.go b/server/block/block.go index 3438f9f6f..44b69c739 100644 --- a/server/block/block.go +++ b/server/block/block.go @@ -74,6 +74,12 @@ type EntityInsider interface { EntityInside(pos cube.Pos, w *world.World, e world.Entity) } +// ProjectileHitter represents a block that handles being hit by a projectile. +type ProjectileHitter interface { + // ProjectileHit is called when a projectile hits the block. + ProjectileHit(w *world.World, e world.Entity, pos cube.Pos, face cube.Face) +} + // Frictional represents a block that may have a custom friction value, friction is used for entity drag when the // entity is on ground. If a block does not implement this interface, it should be assumed that its friction is 0.6. type Frictional interface { diff --git a/server/entity/projectile.go b/server/entity/projectile.go index 9ae5f22c7..f9df677e3 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -1,6 +1,7 @@ package entity import ( + "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/block/cube/trace" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" @@ -32,8 +33,12 @@ func (c *ProjectileComputer) TickMovement(e world.Entity, pos, vel mgl64.Vec3, y }) } if ok { - vel = zeroVec3 - end = hit.Position() + vel, end = zeroVec3, hit.Position() + if r, ok := hit.(trace.BlockResult); ok { + if h, ok := w.Block(r.BlockPosition()).(block.ProjectileHitter); ok { + h.ProjectileHit(w, e, r.BlockPosition(), r.Face()) + } + } } else { yaw, pitch = mgl64.RadToDeg(math.Atan2(vel[0], vel[2])), mgl64.RadToDeg(math.Atan2(vel[1], math.Sqrt(vel[0]*vel[0]+vel[2]*vel[2]))) } From af30496d56ba847b1129fb397c2dad2c2e6c316d Mon Sep 17 00:00:00 2001 From: JustTal Date: Mon, 5 Dec 2022 18:49:48 -0600 Subject: [PATCH 3/5] block/bell.go: Item ejection. --- server/block/bell.go | 16 +++++++++++++--- server/block/block.go | 6 ++++++ server/entity/item.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/server/block/bell.go b/server/block/bell.go index 7f41d2e09..e7d8e8215 100644 --- a/server/block/bell.go +++ b/server/block/bell.go @@ -24,7 +24,7 @@ type Bell struct { // Model ... func (b Bell) Model() world.BlockModel { // TODO: Use the actual bell model. - return model.Solid{} + return model.Slab{} } // BreakInfo ... @@ -38,7 +38,7 @@ func (b Bell) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.Wo if !used { return false } - b.Facing = user.Facing().Opposite() + b.Facing = user.Rotation().Direction().Opposite() if face == cube.FaceUp { if _, ok := w.Block(pos.Side(cube.FaceDown)).Model().(model.Solid); !ok { return false @@ -88,7 +88,7 @@ func (b Bell) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) { // Activate ... func (b Bell) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool { - s, f := u.Facing().Opposite().Face(), b.Facing.Face() + s, f := u.Rotation().Direction().Opposite().Face(), b.Facing.Face() switch b.Attach { case HangingBellAttachment(): b.Ring(pos, s, w) @@ -107,6 +107,16 @@ func (b Bell) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ return false } +// EntityInside ... +func (b Bell) EntityInside(pos cube.Pos, w *world.World, e world.Entity) { + if e, ok := e.(EntityEjector); ok { + e.EntityEject(pos) + + // BDS always rings the bell on it's facing direction, even if the entity is ejected in a different direction. + b.Ring(pos, b.Facing.Face(), w) + } +} + // ProjectileHit ... func (b Bell) ProjectileHit(w *world.World, _ world.Entity, pos cube.Pos, face cube.Face) { b.Ring(pos, face, w) diff --git a/server/block/block.go b/server/block/block.go index 6f95b5201..6fdd23181 100644 --- a/server/block/block.go +++ b/server/block/block.go @@ -79,6 +79,12 @@ type ProjectileHitter interface { ProjectileHit(w *world.World, e world.Entity, pos cube.Pos, face cube.Face) } +// EntityEjector represents a block that ejects entities when they are inside it. +type EntityEjector interface { + // EntityEject is called when an entity is inside the block's 1x1x1 axis aligned bounding box. + EntityEject(pos cube.Pos) +} + // Frictional represents a block that may have a custom friction value, friction is used for entity drag when the // entity is on ground. If a block does not implement this interface, it should be assumed that its friction is 0.6. type Frictional interface { diff --git a/server/entity/item.go b/server/entity/item.go index ccb883b43..cb17dbb97 100644 --- a/server/entity/item.go +++ b/server/entity/item.go @@ -59,6 +59,21 @@ func (it *Item) SetPickupDelay(d time.Duration) { it.pickupDelay = ticks } +// EntityEject ejects the item entity from the block it is currently on. This is called when items are dropped on bells, +// for example, to make the item entity pop off the bell. +func (it *Item) EntityEject(pos cube.Pos) { + it.mu.Lock() + defer it.mu.Unlock() + + delta := it.pos.Sub(pos.Vec3Centre()) + if delta.Len() <= epsilon { + // There is no delta between the item entity and the block, so we can't eject it. + return + } + + it.vel = it.vel.Add(delta.Normalize()) +} + // Tick ticks the entity, performing movement. func (it *Item) Tick(w *world.World, current int64) { it.mu.Lock() @@ -77,6 +92,7 @@ func (it *Item) Tick(w *world.World, current int64) { return } + it.checkEntityInsiders(w, m.pos) if it.pickupDelay == 0 { it.checkNearby(w, m.pos) } else if it.pickupDelay != math.MaxInt16 { @@ -157,6 +173,33 @@ func (it *Item) collect(w *world.World, collector Collector, pos mgl64.Vec3) { _ = it.Close() } +// checkEntityInsiders checks if the player is colliding with any EntityInsider blocks. +func (it *Item) checkEntityInsiders(w *world.World, pos mgl64.Vec3) { + box := it.Type().BBox(it).Translate(pos).Grow(-0.0001) + min, max := cube.PosFromVec3(box.Min()), cube.PosFromVec3(box.Max()) + + for y := min[1]; y <= max[1]; y++ { + for x := min[0]; x <= max[0]; x++ { + for z := min[2]; z <= max[2]; z++ { + blockPos := cube.Pos{x, y, z} + b := w.Block(blockPos) + if collide, ok := b.(block.EntityInsider); ok { + collide.EntityInside(blockPos, w, it) + if _, liquid := b.(world.Liquid); liquid { + continue + } + } + + if l, ok := w.Liquid(blockPos); ok { + if collide, ok := l.(block.EntityInsider); ok { + collide.EntityInside(blockPos, w, it) + } + } + } + } + } +} + // New creates and returns an Item with the item.Stack, position, and velocity provided. It doesn't spawn the Item // by itself. func (it *Item) New(stack item.Stack, pos, vel mgl64.Vec3) world.Entity { From fef147941fd694246a96a67ef976db1f3f725a06 Mon Sep 17 00:00:00 2001 From: JustTal Date: Fri, 16 Dec 2022 13:22:32 -0600 Subject: [PATCH 4/5] model: Implemented bell models. --- server/block/bell.go | 3 +-- server/block/model/bell.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 server/block/model/bell.go diff --git a/server/block/bell.go b/server/block/bell.go index e7d8e8215..1b0d971e6 100644 --- a/server/block/bell.go +++ b/server/block/bell.go @@ -23,8 +23,7 @@ type Bell struct { // Model ... func (b Bell) Model() world.BlockModel { - // TODO: Use the actual bell model. - return model.Slab{} + return model.Bell{Attach: b.Attach.String(), Facing: b.Facing} } // BreakInfo ... diff --git a/server/block/model/bell.go b/server/block/model/bell.go new file mode 100644 index 000000000..0d3dc55fb --- /dev/null +++ b/server/block/model/bell.go @@ -0,0 +1,38 @@ +package model + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" +) + +// Bell represents the block model of a bell. +type Bell struct { + // Attach represents the attachment type of the Bell. + Attach string + // Facing represents the direction the Bell is facing. + Facing cube.Direction +} + +// BBox ... +func (b Bell) BBox(cube.Pos, *world.World) []cube.BBox { + if b.Attach == "standing" { + return []cube.BBox{full.Stretch(b.Facing.Face().Axis(), -0.25).ExtendTowards(cube.FaceUp, -0.1875)} + } + if b.Attach == "hanging" { + return []cube.BBox{full.GrowVec3(mgl64.Vec3{-0.25, 0, -0.25}).ExtendTowards(cube.FaceDown, -0.25)} + } + + box := full.Stretch(b.Facing.RotateLeft().Face().Axis(), -0.25). + ExtendTowards(cube.FaceUp, -0.0625). + ExtendTowards(cube.FaceDown, -0.25) + if b.Attach == "side" { + return []cube.BBox{box.ExtendTowards(b.Facing.Face(), -0.1875)} + } + return []cube.BBox{box} +} + +// FaceSolid ... +func (Bell) FaceSolid(cube.Pos, cube.Face, *world.World) bool { + return false +} From 9b2f96636e89181644de5f28611717009f731454 Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Wed, 5 Jul 2023 12:54:25 +0100 Subject: [PATCH 5/5] block/bell.go: Update to work with latest dragonfly --- server/block/bell.go | 4 +- server/block/block.go | 2 +- server/entity/item.go | 169 ++------------------------------ server/entity/item_behaviour.go | 53 +++++++++- server/entity/projectile.go | 2 +- 5 files changed, 64 insertions(+), 166 deletions(-) diff --git a/server/block/bell.go b/server/block/bell.go index 1b0d971e6..840976635 100644 --- a/server/block/bell.go +++ b/server/block/bell.go @@ -108,8 +108,8 @@ func (b Bell) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ // EntityInside ... func (b Bell) EntityInside(pos cube.Pos, w *world.World, e world.Entity) { - if e, ok := e.(EntityEjector); ok { - e.EntityEject(pos) + if ejector, ok := e.Type().(EntityEjector); ok { + ejector.EntityEject(e, pos) // BDS always rings the bell on it's facing direction, even if the entity is ejected in a different direction. b.Ring(pos, b.Facing.Face(), w) diff --git a/server/block/block.go b/server/block/block.go index 92d005301..13b411fa4 100644 --- a/server/block/block.go +++ b/server/block/block.go @@ -82,7 +82,7 @@ type ProjectileHitter interface { // EntityEjector represents a block that ejects entities when they are inside it. type EntityEjector interface { // EntityEject is called when an entity is inside the block's 1x1x1 axis aligned bounding box. - EntityEject(pos cube.Pos) + EntityEject(e world.Entity, pos cube.Pos) } // Frictional represents a block that may have a custom friction value, friction is used for entity drag when the diff --git a/server/entity/item.go b/server/entity/item.go index f80c1c06a..b667e5b56 100644 --- a/server/entity/item.go +++ b/server/entity/item.go @@ -23,7 +23,8 @@ func NewItem(i item.Stack, pos mgl64.Vec3) *Ent { 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) + behaviour := config.New(i) + return Config{Behaviour: behaviour}.New(ItemType{Behaviour: behaviour}, pos) } var itemConf = ItemBehaviourConfig{ @@ -31,166 +32,10 @@ var itemConf = ItemBehaviourConfig{ Drag: 0.02, } -// EntityEject ejects the item entity from the block it is currently on. This is called when items are dropped on bells, -// for example, to make the item entity pop off the bell. -func (it *Item) EntityEject(pos cube.Pos) { - it.mu.Lock() - defer it.mu.Unlock() - - delta := it.pos.Sub(pos.Vec3Centre()) - if delta.Len() <= epsilon { - // There is no delta between the item entity and the block, so we can't eject it. - return - } - - it.vel = it.vel.Add(delta.Normalize()) -} - -// Tick ticks the entity, performing movement. -func (it *Item) Tick(w *world.World, current int64) { - it.mu.Lock() - m := it.c.TickMovement(it, it.pos, it.vel, 0, 0) - it.pos, it.vel = m.pos, m.vel - it.mu.Unlock() - - m.Send() - - if m.pos[1] < float64(w.Range()[0]) && current%10 == 0 { - _ = it.Close() - return - } - if it.age++; it.age > 6000 { - _ = it.Close() - return - } - - it.checkEntityInsiders(w, m.pos) - if it.pickupDelay == 0 { - it.checkNearby(w, m.pos) - } else if it.pickupDelay != math.MaxInt16 { - it.pickupDelay-- - } -} - -// checkNearby checks the entities of the chunks around for item collectors and other item stacks. If a -// collector is found in range, the item will be picked up. If another item stack with the same item type is -// found in range, the item stacks will merge. -func (it *Item) checkNearby(w *world.World, pos mgl64.Vec3) { - bbox := it.Type().BBox(it) - grown := bbox.GrowVec3(mgl64.Vec3{1, 0.5, 1}).Translate(pos) - for _, e := range w.EntitiesWithin(bbox.Translate(pos).Grow(2), nil) { - if e == it { - // Skip the item entity itself. - continue - } - if e.Type().BBox(e).Translate(e.Position()).IntersectsWith(grown) { - if collector, ok := e.(Collector); ok { - // A collector was within range to pick up the entity. - it.collect(w, collector, pos) - return - } else if other, ok := e.(*Item); ok { - // Another item entity was in range to merge with. - if it.merge(w, other, pos) { - return - } - } - } - } -} - -// merge merges the item entity with another item entity. -func (it *Item) merge(w *world.World, other *Item, pos mgl64.Vec3) bool { - if other.i.Count() == other.i.MaxCount() || it.i.Count() == it.i.MaxCount() { - // Either stack is already filled up to the maximum, meaning we can't change anything any way. - return false - } - if !it.i.Comparable(other.i) { - return false - } - - a, b := other.i.AddStack(it.i) - - newA := NewItem(a, other.Position()) - newA.SetVelocity(other.Velocity()) - w.AddEntity(newA) - - if !b.Empty() { - newB := NewItem(b, pos) - newB.SetVelocity(it.vel) - w.AddEntity(newB) - } - _ = it.Close() - _ = other.Close() - return true -} - -// collect makes a collector collect the item (or at least part of it). -func (it *Item) collect(w *world.World, collector Collector, pos mgl64.Vec3) { - n := collector.Collect(it.i) - if n == 0 { - return - } - for _, viewer := range w.Viewers(pos) { - viewer.ViewEntityAction(it, PickedUpAction{Collector: collector}) - } - - if n == it.i.Count() { - // The collector picked up the entire stack. - _ = it.Close() - return - } - // Create a new item entity and shrink it by the amount of items that the collector collected. - w.AddEntity(NewItem(it.i.Grow(-n), pos)) - - _ = it.Close() -} - -// checkEntityInsiders checks if the player is colliding with any EntityInsider blocks. -func (it *Item) checkEntityInsiders(w *world.World, pos mgl64.Vec3) { - box := it.Type().BBox(it).Translate(pos).Grow(-0.0001) - min, max := cube.PosFromVec3(box.Min()), cube.PosFromVec3(box.Max()) - - for y := min[1]; y <= max[1]; y++ { - for x := min[0]; x <= max[0]; x++ { - for z := min[2]; z <= max[2]; z++ { - blockPos := cube.Pos{x, y, z} - b := w.Block(blockPos) - if collide, ok := b.(block.EntityInsider); ok { - collide.EntityInside(blockPos, w, it) - if _, liquid := b.(world.Liquid); liquid { - continue - } - } - - if l, ok := w.Liquid(blockPos); ok { - if collide, ok := l.(block.EntityInsider); ok { - collide.EntityInside(blockPos, w, it) - } - } - } - } - } -} - -// Explode ... -func (it *Item) Explode(mgl64.Vec3, float64, block.ExplosionConfig) { - _ = it.Close() -} - -// Collector represents an entity in the world that is able to collect an item, typically an entity such as -// a player or a zombie. -type Collector interface { - world.Entity - // Collect collects the stack passed. It is called if the Collector is standing near an item entity that - // may be picked up. - // The count of items collected from the stack n is returned. - Collect(stack item.Stack) (n int) - // GameMode returns the gamemode of the collector. - GameMode() world.GameMode -} - // ItemType is a world.EntityType implementation for Item. -type ItemType struct{} +type ItemType struct { + Behaviour *ItemBehaviour +} func (ItemType) EncodeEntity() string { return "minecraft:item" } func (ItemType) NetworkOffset() float64 { return 0.125 } @@ -222,3 +67,7 @@ func (ItemType) EncodeNBT(e world.Entity) map[string]any { "Item": nbtconv.WriteItem(b.Item(), true), } } + +func (t ItemType) EntityEject(e world.Entity, pos cube.Pos) { + t.Behaviour.EntityEject(e, pos) +} diff --git a/server/entity/item_behaviour.go b/server/entity/item_behaviour.go index 61de607d1..398afff8f 100644 --- a/server/entity/item_behaviour.go +++ b/server/entity/item_behaviour.go @@ -1,6 +1,8 @@ package entity import ( + "github.com/df-mc/dragonfly/server/block" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" @@ -62,9 +64,26 @@ func (i *ItemBehaviour) Item() item.Stack { return i.i } -// Tick moves the entity, checks if it should be picked up by a nearby collector -// or if it should merge with nearby item entities. +// EntityEject ejects the item entity from the block it is currently on. This is called when items are dropped on bells, +// for example, to make the item entity pop off the bell. +func (i *ItemBehaviour) EntityEject(e world.Entity, pos cube.Pos) { + ent, ok := e.(*Ent) + if !ok { + return + } + delta := e.Position().Sub(pos.Vec3Centre()) + if delta.Len() <= epsilon { + // There is no delta between the item entity and the block, so we can't eject it. + return + } + vel := delta.Normalize().Mul(0.4) + vel[1] = math.Max(0.15, vel[1]) + ent.SetVelocity(vel) +} + +// Tick ticks the entity, performing movement. func (i *ItemBehaviour) Tick(e *Ent) *Movement { + i.checkEntityInsiders(e) return i.passive.Tick(e) } @@ -152,6 +171,34 @@ func (i *ItemBehaviour) collect(e *Ent, collector Collector) { _ = e.Close() } +// checkEntityInsiders checks if the player is colliding with any EntityInsider blocks. +func (i *ItemBehaviour) checkEntityInsiders(e *Ent) { + w := e.World() + box := e.Type().BBox(e).Translate(e.Position()).Grow(-0.0001) + min, max := cube.PosFromVec3(box.Min()), cube.PosFromVec3(box.Max()) + + for y := min[1]; y <= max[1]; y++ { + for x := min[0]; x <= max[0]; x++ { + for z := min[2]; z <= max[2]; z++ { + blockPos := cube.Pos{x, y, z} + b := w.Block(blockPos) + if collide, ok := b.(block.EntityInsider); ok { + collide.EntityInside(blockPos, w, e) + if _, liquid := b.(world.Liquid); liquid { + continue + } + } + + if l, ok := w.Liquid(blockPos); ok { + if collide, ok := l.(block.EntityInsider); ok { + collide.EntityInside(blockPos, w, e) + } + } + } + } + } +} + // Collector represents an entity in the world that is able to collect an item, typically an entity such as // a player or a zombie. type Collector interface { @@ -160,4 +207,6 @@ type Collector interface { // may be picked up. // The count of items collected from the stack n is returned. Collect(stack item.Stack) (n int) + // GameMode returns the gamemode of the collector. + GameMode() world.GameMode } diff --git a/server/entity/projectile.go b/server/entity/projectile.go index d77ec33a4..f9a38a45f 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -300,7 +300,7 @@ func (lt *ProjectileBehaviour) tickMovement(e *Ent) (*Movement, trace.Result) { ) if !mgl64.FloatEqual(end.Sub(pos).LenSqr(), 0) { if hit, ok = trace.Perform(pos, end, w, e.Type().BBox(e).Grow(1.0), lt.ignores(e)); ok { - if _, ok := hit.(trace.BlockResult); ok { + if r, ok := hit.(trace.BlockResult); ok { // Undo the gravity because the velocity as a result of gravity // at the point of collision should be 0. vel[1] = (vel[1] + lt.mc.Gravity) / (1 - lt.mc.Drag)