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

implement beds , respawn anchors #938

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
242 changes: 242 additions & 0 deletions server/block/bed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
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/internal/nbtconv"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
"github.com/sandertv/gophertunnel/minecraft/text"
)

// Bed is a block, allowing players to sleep to set their spawns and skip the night.
type Bed struct {
transparent
sourceWaterDisplacer

// Colour is the colour of the bed.
Colour item.Colour
// Facing is the direction that the bed is Facing.
Facing cube.Direction
// Head is true if the bed is the head side.
Head bool
// User is the user that is using the bed. It is only set for the Head part of the bed.
User item.User
DaPigGuy marked this conversation as resolved.
Show resolved Hide resolved
}

// MaxCount always returns 1.
func (Bed) MaxCount() int {
return 1
}

// Model ...
func (Bed) Model() world.BlockModel {
return model.Bed{}
}

// SideClosed ...
func (Bed) SideClosed(cube.Pos, cube.Pos, *world.World) bool {
return false
}

// BreakInfo ...
func (b Bed) BreakInfo() BreakInfo {
return newBreakInfo(0.2, alwaysHarvestable, nothingEffective, oneOf(b)).withBreakHandler(func(pos cube.Pos, w *world.World, _ item.User) {
headSide, _, ok := b.head(pos, w)
if !ok {
return
}
if s, ok := headSide.User.(world.Sleeper); ok {
s.Wake()
}
})
}

// UseOnBlock ...
func (b Bed) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) {
if pos, _, used = firstReplaceable(w, pos, face, b); !used {
return
}
if _, ok := w.Block(pos.Side(cube.FaceDown)).Model().(model.Solid); !ok {
FDUTCH marked this conversation as resolved.
Show resolved Hide resolved
return
}

b.Facing = user.Rotation().Direction()

side, sidePos := b, pos.Side(b.Facing.Face())
side.Head = true

if !replaceableWith(w, sidePos, side) {
return
}
if _, ok := w.Block(sidePos.Side(cube.FaceDown)).Model().(model.Solid); !ok {
FDUTCH marked this conversation as resolved.
Show resolved Hide resolved
return
}

ctx.IgnoreBBox = true
place(w, sidePos, side, user, ctx)
place(w, pos, b, user, ctx)
return placed(ctx)
}

// Activate ...
func (b Bed) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool {
s, ok := u.(world.Sleeper)
if !ok {
return false
}

if w.Dimension() != world.Overworld {
w.SetBlock(pos, nil, nil)
ExplosionConfig{
Size: 5,
SpawnFire: true,
}.Explode(w, pos.Vec3Centre())
return true
}

_, sidePos, ok := b.side(pos, w)
if !ok {
return false
}

userPos := s.Position()
if sidePos.Vec3Middle().Sub(userPos).Len() > 4 && pos.Vec3Middle().Sub(userPos).Len() > 4 {
s.Messaget(text.Colourf("<grey>%%tile.bed.tooFar</grey>"))
return true
}

headSide, headPos, ok := b.head(pos, w)
if !ok {
return false
}
if _, ok = w.Liquid(headPos); ok {
return false
}

u.SetSpawnPos(pos, w)
s.Messaget(text.Colourf("<grey>%%tile.bed.respawnSet</grey>"))
time := w.Time() % world.TimeFull
if (time < world.TimeNight || time >= world.TimeSunrise) && !w.ThunderingAt(pos) {
s.Messaget(text.Colourf("<grey>%%tile.bed.noSleep</grey>"))
return true
}
if headSide.User != nil {
s.Messaget(text.Colourf("<grey>%%tile.bed.occupied</grey>"))
return true
}

s.Sleep(headPos)
return true
}

// EntityLand ...
func (b Bed) EntityLand(_ cube.Pos, _ *world.World, e world.Entity, distance *float64) {
if s, ok := e.(sneakingEntity); ok && s.Sneaking() {
// If the entity is sneaking, the fall distance and velocity stay the same.
return
}
DaPigGuy marked this conversation as resolved.
Show resolved Hide resolved
if _, ok := e.(fallDistanceEntity); ok {
*distance *= 0.5
}
if v, ok := e.(velocityEntity); ok {
vel := v.Velocity()
vel[1] = vel[1] * -3 / 4
DaPigGuy marked this conversation as resolved.
Show resolved Hide resolved
v.SetVelocity(vel)
}
}

// sneakingEntity represents an entity that can sneak.
type sneakingEntity interface {
// Sneaking returns true if the entity is currently sneaking.
Sneaking() bool
}

// velocityEntity represents an entity that can maintain a velocity.
type velocityEntity interface {
// Velocity returns the current velocity of the entity.
Velocity() mgl64.Vec3
// SetVelocity sets the velocity of the entity.
SetVelocity(mgl64.Vec3)
}

// NeighbourUpdateTick ...
func (b Bed) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) {
if _, _, ok := b.side(pos, w); !ok {
w.SetBlock(pos, nil, nil)
}
}

// EncodeItem ...
func (b Bed) EncodeItem() (name string, meta int16) {
return "minecraft:bed", int16(b.Colour.Uint8())
}

// EncodeBlock ...
func (b Bed) EncodeBlock() (name string, properties map[string]interface{}) {
return "minecraft:bed", map[string]interface{}{
"facing_bit": int32(horizontalDirection(b.Facing)),
DaPigGuy marked this conversation as resolved.
Show resolved Hide resolved
"occupied_bit": boolByte(b.User != nil),
"head_bit": boolByte(b.Head),
Copy link
Member

Choose a reason for hiding this comment

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

Both the wiki and in-game commands call this "head_piece_bit"

}
}

// EncodeNBT ...
func (b Bed) EncodeNBT() map[string]interface{} {
return map[string]interface{}{
"id": "Bed",
"color": b.Colour.Uint8(),
}
}

// DecodeNBT ...
func (b Bed) DecodeNBT(data map[string]interface{}) interface{} {
b.Colour = item.Colours()[nbtconv.Uint8(data, "color")]
return b
}

// head returns the head side of the bed. If neither side is a head side, the third return value is false.
func (b Bed) head(pos cube.Pos, w *world.World) (Bed, cube.Pos, bool) {
headSide, headPos, ok := b.side(pos, w)
if !ok {
return Bed{}, cube.Pos{}, false
}
if b.Head {
headSide, headPos = b, pos
}
return headSide, headPos, true
}

// side returns the other side of the bed. If the other side is not a bed, the third return value is false.
func (b Bed) side(pos cube.Pos, w *world.World) (Bed, cube.Pos, bool) {
face := b.Facing.Face()
if b.Head {
face = face.Opposite()
}

sidePos := pos.Side(face)
o, ok := w.Block(sidePos).(Bed)
return o, sidePos, ok
}

// allBeds returns all possible beds.
func allBeds() (beds []world.Block) {
for _, d := range cube.Directions() {
beds = append(beds, Bed{Facing: d})
beds = append(beds, Bed{Facing: d, Head: true})
}
Copy link
Member

Choose a reason for hiding this comment

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

Not having the occupied block states registered might be problematic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the problem is that colored versions were not taken into account?

Copy link
Member

Choose a reason for hiding this comment

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

Bed color is set through the tile, not a block state. Please undo your change.

If all beds are white in the creative inventory, it is probably the same issue with the creative item entries missing NBT that banners have. See a1c98da

return
}

func (Bed) SpawnBlock() bool {
return true
}

func (Bed) Update(pos cube.Pos, u item.User, w *world.World) {}

// SpawnBlock represents a block using which player can set his spawn point.
type SpawnBlock interface {
SpawnBlock() bool
Update(pos cube.Pos, u item.User, w *world.World)
}
Copy link
Member

Choose a reason for hiding this comment

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

Please use proper grammer in the docs and also document the methods of the interface. Update is a very ambiguous name and doesn't hint towards being related to spawn points

Copy link
Member

Choose a reason for hiding this comment

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

You have changed the method name but the documentation is still needing to be updated

10 changes: 10 additions & 0 deletions server/block/hash.go

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

18 changes: 18 additions & 0 deletions server/block/model/bed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package model

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
)

// Bed is a model used for beds. This model works for both parts of the bed.
type Bed struct{}

func (b Bed) BBox(cube.Pos, world.BlockSource) []cube.BBox {
return []cube.BBox{cube.Box(0, 0, 0, 1, 0.5625, 1)}
}

// FaceSolid ...
func (Bed) FaceSolid(cube.Pos, cube.Face, world.BlockSource) bool {
return false
}
6 changes: 6 additions & 0 deletions server/block/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func init() {
registerAll(allBanners())
registerAll(allBarrels())
registerAll(allBasalt())
registerAll(allBeds())
registerAll(allBeetroot())
registerAll(allBlackstone())
registerAll(allBlastFurnaces())
Expand Down Expand Up @@ -203,6 +204,7 @@ func init() {
registerAll(allCopperDoors())
registerAll(allCopperGrates())
registerAll(allCopperTrapdoors())
registerAll(allRespawnAnchors())
}

func init() {
Expand Down Expand Up @@ -374,6 +376,7 @@ func init() {
}
for _, c := range item.Colours() {
world.RegisterItem(Banner{Colour: c})
world.RegisterItem(Bed{Colour: c})
world.RegisterItem(Carpet{Colour: c})
world.RegisterItem(ConcretePowder{Colour: c})
world.RegisterItem(Concrete{Colour: c})
Expand Down Expand Up @@ -461,6 +464,9 @@ func init() {
world.RegisterItem(Copper{Type: c, Oxidation: o, Waxed: true})
}
}
for _, t := range allRespawnAnchorsItems() {
world.RegisterItem(t)
}
}

func registerAll(blocks []world.Block) {
Expand Down
Loading
Loading