Skip to content

implement beds , respawn anchors #938

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

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
88f1a8f
implement beds , respawn anchors
FDUTCH Nov 17, 2024
41e2831
run go generate
FDUTCH Nov 17, 2024
dea2dfd
remove hash methods in the files
FDUTCH Nov 17, 2024
198ddfc
fix github Staticcheck
FDUTCH Nov 17, 2024
0f9d7a2
remove useless comments
FDUTCH Nov 17, 2024
20f6e1c
some fixes
FDUTCH Nov 17, 2024
2c62bdb
fix int to int32 casting
FDUTCH Nov 17, 2024
ffc6f69
requested changes
FDUTCH Nov 17, 2024
2ba7934
requested changes
FDUTCH Nov 18, 2024
5342bc8
requested changes
FDUTCH Nov 24, 2024
c3ca7d0
requested changes
FDUTCH Nov 25, 2024
a29517f
Merge remote-tracking branch 'origin/master' into beds
FDUTCH Dec 2, 2024
7c87c40
fix merge conflicts
FDUTCH Dec 2, 2024
6716f5d
fix build
FDUTCH Dec 2, 2024
bce0754
fix formatting
FDUTCH Dec 2, 2024
5d575de
Merge branch 'master' into beds
FDUTCH Dec 15, 2024
b0d783c
fix formatting
FDUTCH Dec 15, 2024
3c06d62
requested changes
FDUTCH Dec 16, 2024
0c1faf7
Merge branch 'master' into beds
FDUTCH Dec 23, 2024
1adb42f
fix merge & update to use the translation changes
FDUTCH Dec 23, 2024
f884bfa
fix merge
FDUTCH Dec 24, 2024
ff3989d
Merge branch 'master' into beds
DaPigGuy Jan 7, 2025
107d256
requested changes
FDUTCH Jan 8, 2025
8df259d
use world.EntityHandle insted of world.Sleeper
FDUTCH Jan 8, 2025
ff6a673
requested changes
FDUTCH Jan 13, 2025
456aeed
fix register.go
FDUTCH Jan 13, 2025
737c961
fix names & Bed.Head trouble
FDUTCH Jan 13, 2025
3cbaf46
Revert "fix names & Bed.Head trouble"
FDUTCH Jan 13, 2025
d6611c1
fix deadlocks
FDUTCH Jan 16, 2025
726c857
Merge branch 'master' into beds
FDUTCH Jan 29, 2025
9265cf0
Merge branch 'master' into beds
FDUTCH Feb 13, 2025
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
249 changes: 249 additions & 0 deletions server/block/bed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
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/player/chat"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
)

// 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
// Sleeper is the user that is using the bed. It is only set for the Head part of the bed.
Sleeper world.Sleeper
}

// 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.Tx, _ item.User) {
headSide, _, ok := b.head(pos, w)
if !ok {
return
}

sleeper := headSide.Sleeper
if sleeper != nil {
sleeper.Wake()
}
})
}

// UseOnBlock ...
func (b Bed) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.Tx, user item.User, ctx *item.UseContext) (used bool) {
if pos, _, used = firstReplaceable(w, pos, face, b); !used {
return
}
if !supportedFromBelow(pos, w) {
return
}

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

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

if !replaceableWith(w, sidePos, side) {
return
}

if !supportedFromBelow(sidePos, w) {
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, tx *world.Tx, u item.User, _ *item.UseContext) bool {
s, ok := u.(world.Sleeper)
if !ok {
return false
}

w := tx.World()

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

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

userPos := s.Position()
if sidePos.Vec3Middle().Sub(userPos).Len() > 4 && pos.Vec3Middle().Sub(userPos).Len() > 4 {
s.Messaget(chat.MessageBedTooFar)
return true
}

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

previousSpawn := w.PlayerSpawn(s.UUID())
if previousSpawn != pos && previousSpawn != sidePos {
w.SetPlayerSpawn(s.UUID(), pos)
s.Messaget(chat.MessageRespawnPointSet)
}

time := w.Time() % world.TimeFull
if (time < world.TimeNight || time >= world.TimeSunrise) && !tx.ThunderingAt(pos) {
Copy link
Member

Choose a reason for hiding this comment

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

This logic does not match the behaviour documented on the wiki:

A player sleeps by using a bed during a thunderstorm, or at night (between 12542 and 23459 ticks in clear weather, when stars appear in the sky, or between 12010 and 23991 ticks in rainy weather). Players can sleep during a thunderstorm even if they are in a biome where it does not rain (i.e. desert)

s.Messaget(chat.MessageNoSleep)
return true
}
if headSide.Sleeper != nil {
s.Messaget(chat.MessageBedIsOccupied)
return true
}

s.Sleep(headPos)
return true
}

// EntityLand ...
func (b Bed) EntityLand(_ cube.Pos, _ *world.World, e world.Entity, distance *float64) {
if _, ok := e.(fallDistanceEntity); ok {
*distance *= 0.5
}
if v, ok := e.(velocityEntity); ok {
vel := v.Velocity()
vel[1] = vel[1] * -2 / 3
v.SetVelocity(vel)
}
}

// 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.Tx) {
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{}{
"direction": int32(horizontalDirection(b.Facing)),
"occupied_bit": boolByte(b.Sleeper != 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.Tx) (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.Tx) (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})
}
return
}

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

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

// RespawnBlock represents a block using which player can set his spawn point.
type RespawnBlock interface {
// CanRespawnOn defines if player can use this block to respawn.
CanRespawnOn() bool
// RespawnOn is called when a player decides to respawn using this block.
RespawnOn(pos cube.Pos, u item.User, tx *world.Tx)
}

// supportedFromBelow ...
func supportedFromBelow(pos cube.Pos, w *world.Tx) bool {
below := pos.Side(cube.FaceDown)
return w.Block(below).Model().FaceSolid(below, cube.FaceUp, w)
}
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 @@ -125,6 +125,7 @@ func init() {
registerAll(allBanners())
registerAll(allBarrels())
registerAll(allBasalt())
registerAll(allBeds())
registerAll(allBeetroot())
registerAll(allBlackstone())
registerAll(allBlastFurnaces())
Expand Down Expand Up @@ -209,6 +210,7 @@ func init() {
registerAll(allCopperDoors())
registerAll(allCopperGrates())
registerAll(allCopperTrapdoors())
registerAll(allRespawnAnchors())
}

func init() {
Expand Down Expand Up @@ -386,6 +388,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 @@ -473,6 +476,9 @@ func init() {
world.RegisterItem(Copper{Type: c, Oxidation: o, Waxed: true})
}
}
for c := range 5 {
world.RegisterItem(RespawnAnchor{Charge: c})
}
}

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