Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented bells #645

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions cmd/blockhash/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,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", "TallGrassType":
return "uint64(" + s + ".Uint8())", 2
case "BellAttachment":
return "uint64(" + s + ".Uint8())", 2
case "OreType", "FireType", "DoubleTallGrassType":
return "uint64(" + s + ".Uint8())", 1
case "Direction", "Axis":
Expand Down
17 changes: 16 additions & 1 deletion server/block/action.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
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 }

// 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
}

Expand All @@ -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
}

Expand Down
164 changes: 164 additions & 0 deletions server/block/bell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
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 {
return model.Bell{Attach: b.Attach.String(), Facing: b.Facing}
}

// 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.Rotation().Direction().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.Rotation().Direction().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
}

// EntityInside ...
func (b Bell) EntityInside(pos cube.Pos, w *world.World, e world.Entity) {
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)
}
}

// 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{})
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
}
53 changes: 53 additions & 0 deletions server/block/bell_attachment.go
Original file line number Diff line number Diff line change
@@ -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")
}
12 changes: 12 additions & 0 deletions server/block/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ 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)
}

// EntityEjector represents a block that ejects entities when they are inside it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem accurate as it's the item entity that's implementing this...

type EntityEjector interface {
// EntityEject is called when an entity is inside the block's 1x1x1 axis aligned bounding box.
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
// 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 {
Expand Down
5 changes: 5 additions & 0 deletions server/block/hash.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions server/block/model/bell.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions server/block/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ func init() {
registerAll(allBarrels())
registerAll(allBasalt())
registerAll(allBeetroot())
registerAll(allBells())
registerAll(allBlackstone())
registerAll(allBlastFurnaces())
registerAll(allBoneBlock())
Expand Down Expand Up @@ -205,6 +206,7 @@ func init() {
world.RegisterItem(Beacon{})
world.RegisterItem(Bedrock{})
world.RegisterItem(BeetrootSeeds{})
world.RegisterItem(Bell{})
world.RegisterItem(BlastFurnace{})
world.RegisterItem(BlueIce{})
world.RegisterItem(Bone{})
Expand Down
11 changes: 9 additions & 2 deletions server/entity/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -32,7 +33,9 @@ var itemConf = ItemBehaviourConfig{
}

// 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 }
Expand Down Expand Up @@ -64,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)
}
Loading