up3
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
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
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user