26ed99fda6
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
299 lines
7.6 KiB
Go
299 lines
7.6 KiB
Go
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 dyeable utility block that allows a player in the Overworld to sleep through the night and reset
|
|
// their spawn point to within a few blocks of the bed, as long as it is not broken or obstructed.
|
|
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.EntityHandle
|
|
}
|
|
|
|
// 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.Tx) bool {
|
|
return false
|
|
}
|
|
|
|
// BreakInfo ...
|
|
func (b Bed) BreakInfo() BreakInfo {
|
|
return newBreakInfo(0.2, alwaysHarvestable, nothingEffective, oneOf(b)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) {
|
|
headSide, _, ok := b.head(pos, tx)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
s := headSide.Sleeper
|
|
if s == nil {
|
|
return
|
|
}
|
|
|
|
ent, ok := s.Entity(tx)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
sleeper, ok := ent.(world.Sleeper)
|
|
if ok {
|
|
sleeper.Wake()
|
|
}
|
|
})
|
|
}
|
|
|
|
// UseOnBlock ...
|
|
func (b Bed) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) {
|
|
if pos, _, used = firstReplaceable(tx, pos, face, b); !used {
|
|
return
|
|
}
|
|
if !supportedFromBelow(pos, tx) {
|
|
return
|
|
}
|
|
|
|
b.Facing = user.Rotation().Direction()
|
|
|
|
side, sidePos := b, pos.Side(b.Facing.Face())
|
|
side.Head = true
|
|
|
|
if !replaceableWith(tx, sidePos, side) {
|
|
return
|
|
}
|
|
if !supportedFromBelow(sidePos, tx) {
|
|
return
|
|
}
|
|
|
|
ctx.IgnoreBBox = true
|
|
place(tx, sidePos, side, user, ctx)
|
|
place(tx, 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() > 2 && pos.Vec3Middle().Sub(userPos).Len() > 2 {
|
|
s.Messaget(chat.MessageBedTooFar)
|
|
return true
|
|
}
|
|
|
|
headSide, headPos, ok := b.head(pos, tx)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
if _, safeSpawn := b.SafeSpawn(pos, tx); !safeSpawn {
|
|
s.Messaget(chat.MessageBedObstructed)
|
|
return false
|
|
}
|
|
|
|
if _, ok = tx.Liquid(headPos); ok {
|
|
return false
|
|
}
|
|
|
|
previousSpawn := w.PlayerSpawn(s.UUID())
|
|
if previousSpawn != headPos {
|
|
w.SetPlayerSpawn(s.UUID(), headPos)
|
|
s.Messaget(chat.MessageRespawnPointSet)
|
|
}
|
|
|
|
time := w.Time() % world.TimeFull
|
|
if !tx.Thundering() {
|
|
if !tx.Raining() && (time <= world.TimeSleep || time >= world.TimeWake) {
|
|
s.Messaget(chat.MessageNoSleep)
|
|
return true
|
|
}
|
|
if time <= world.TimeSleepWithRain || time >= world.TimeWakeWithRain {
|
|
s.Messaget(chat.MessageNoSleep)
|
|
return true
|
|
}
|
|
}
|
|
if headSide.Sleeper != nil {
|
|
s.Messaget(chat.MessageBedIsOccupied)
|
|
return true
|
|
}
|
|
|
|
// TODO: add a check for when monsters are nearby
|
|
|
|
s.Sleep(headPos)
|
|
return true
|
|
}
|
|
|
|
// EntityLand ...
|
|
func (b Bed) EntityLand(_ cube.Pos, _ *world.Tx, 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, tx *world.Tx) {
|
|
if _, _, ok := b.side(pos, tx); !ok {
|
|
breakBlockNoDrops(b, pos, tx)
|
|
}
|
|
}
|
|
|
|
// 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_piece_bit": boolByte(b.Head),
|
|
}
|
|
}
|
|
|
|
// 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, tx *world.Tx) (Bed, cube.Pos, bool) {
|
|
headSide, headPos, ok := b.side(pos, tx)
|
|
if !ok {
|
|
return Bed{}, cube.Pos{}, false
|
|
}
|
|
if b.Head {
|
|
return b, pos, true
|
|
}
|
|
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, tx *world.Tx) (Bed, cube.Pos, bool) {
|
|
face := b.Facing.Face()
|
|
if b.Head {
|
|
face = face.Opposite()
|
|
}
|
|
|
|
sidePos := pos.Side(face)
|
|
o, ok := tx.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
|
|
}
|
|
|
|
// CanRespawnOn ...
|
|
func (Bed) CanRespawnOn() bool {
|
|
return true
|
|
}
|
|
|
|
// bedOffsets is a map of offsets for each face of the bed. The offsets are relative to the heel side of the bed.
|
|
var bedOffsets = map[cube.Face][]cube.Pos{
|
|
cube.FaceNorth: {{-1, 0, 0}, {-1, 0, 1}, {0, 0, 1}, {1, 0, 1}, {1, 0, 0}, {1, 0, -1}, {1, 0, -2}, {0, 0, -2}, {-1, 0, -2}, {-1, 0, -1}, {0, 1, -1}, {0, 1, 0}},
|
|
cube.FaceEast: {{0, 0, -1}, {-1, 0, -1}, {-1, 0, 0}, {-1, 0, 1}, {-1, 0, 1}, {0, 0, 1}, {1, 0, 1}, {2, 0, 1}, {2, 0, 0}, {2, 0, -1}, {1, 0, -1}, {1, 1, 0}, {0, 1, 0}},
|
|
cube.FaceSouth: {{1, 0, 0}, {1, 0, -1}, {0, 0, -1}, {-1, 0, -1}, {-1, 0, 0}, {-1, 0, 1}, {-1, 0, 2}, {0, 0, 2}, {1, 0, 2}, {1, 0, 1}, {0, 1, 1}, {0, 1, 0}},
|
|
cube.FaceWest: {{0, 0, 1}, {1, 0, 1}, {1, 0, 0}, {1, 0, -1}, {1, 0, -1}, {0, 0, -1}, {-1, 0, -1}, {-2, 0, -1}, {-2, 0, 0}, {-2, 0, 1}, {-1, 0, 1}, {-1, 1, 0}, {0, 1, 0}},
|
|
}
|
|
|
|
// SafeSpawn ...
|
|
func (b Bed) SafeSpawn(pos cube.Pos, tx *world.Tx) (cube.Pos, bool) {
|
|
_, headPos, ok := b.head(pos, tx)
|
|
if !ok {
|
|
return cube.Pos{}, false
|
|
}
|
|
|
|
heelPos := headPos.Side(b.Facing.Opposite().Face())
|
|
|
|
for _, offset := range bedOffsets[b.Facing.Face()] {
|
|
offsetPos := heelPos.Add(offset)
|
|
|
|
if _, solidBlock := tx.Block(offsetPos).Model().(model.Solid); solidBlock {
|
|
if diffuser, ok := tx.Block(offsetPos).(LightDiffuser); !ok || diffuser.LightDiffusionLevel() != 0 {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if _, emptyBlock := tx.Block(offsetPos.Side(cube.FaceDown)).Model().(model.Empty); emptyBlock {
|
|
continue
|
|
}
|
|
|
|
return heelPos.Add(offset), true
|
|
}
|
|
return cube.Pos{}, false
|
|
}
|
|
|
|
// supportedFromBelow ...
|
|
func supportedFromBelow(pos cube.Pos, tx *world.Tx) bool {
|
|
below := pos.Side(cube.FaceDown)
|
|
return tx.Block(below).Model().FaceSolid(below, cube.FaceUp, tx)
|
|
}
|