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 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 248 additions & 0 deletions server/block/bed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
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 {
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 {
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
}

previousSpawn := w.PlayerSpawn(u.UUID())
if previousSpawn != pos {
DaPigGuy marked this conversation as resolved.
Show resolved Hide resolved
w.SetPlayerSpawn(u.UUID(), pos)
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),
DaPigGuy marked this conversation as resolved.
Show resolved Hide resolved
}
}

// 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) CanSpawn() bool {
return true
}

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

// RespawnBlock represents a block using which player can set his spawn point.
type RespawnBlock interface {
// CanSpawn defines if player can use this block to respawn.
CanSpawn() bool
// SpawnOn is called when a player decides to respawn using this block.
SpawnOn(pos cube.Pos, u item.User, w *world.World)
}
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