Files
TarnaWijaya 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
up3
2026-07-09 08:33:57 +08:00

170 lines
4.4 KiB
Go

package block
import (
"fmt"
"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/df-mc/dragonfly/server/world/sound"
"github.com/go-gl/mathgl/mgl64"
"time"
)
// Lectern is a librarian's job site block found in villages. It is used to hold books for multiple players to read in
// multiplayer.
// TODO: Redstone functionality.
type Lectern struct {
bass
sourceWaterDisplacer
// Facing represents the direction the Lectern is facing.
Facing cube.Direction
// Book is the book currently held by the Lectern.
Book item.Stack
// Page is the page the Lectern is currently on in the book.
Page int
}
// Model ...
func (Lectern) Model() world.BlockModel {
return model.Lectern{}
}
// FuelInfo ...
func (Lectern) FuelInfo() item.FuelInfo {
return newFuelInfo(time.Second * 15)
}
// SideClosed ...
func (Lectern) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool {
return false
}
// BreakInfo ...
func (l Lectern) BreakInfo() BreakInfo {
d := []item.Stack{item.NewStack(Lectern{}, 1)}
if !l.Book.Empty() {
d = append(d, l.Book)
}
return newBreakInfo(2.5, alwaysHarvestable, axeEffective, simpleDrops(d...))
}
// UseOnBlock ...
func (l Lectern) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) {
pos, _, used = firstReplaceable(tx, pos, face, l)
if !used {
return false
}
l.Facing = user.Rotation().Direction().Opposite()
place(tx, pos, l, user, ctx)
return placed(ctx)
}
// readableBook represents a book that can be read through a lectern.
type readableBook interface {
// TotalPages returns the total number of pages in the book.
TotalPages() int
// Page returns a specific page from the book and true when the page exists. It will otherwise return an empty string
// and false.
Page(page int) (string, bool)
}
// Activate ...
func (l Lectern) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool {
if !l.Book.Empty() {
if opener, ok := u.(ContainerOpener); ok {
opener.OpenBlockContainer(pos, tx)
return true
}
return false
}
held, _ := u.HeldItems()
if _, ok := held.Item().(readableBook); !ok {
// We can't put a non-book item on the lectern.
return false
}
l.Book, l.Page = held, 0
tx.SetBlock(pos, l, nil)
tx.PlaySound(pos.Vec3Centre(), sound.LecternBookPlace{})
ctx.SubtractFromCount(1)
return true
}
// Punch ...
func (l Lectern) Punch(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.User) {
if l.Book.Empty() {
// We can't remove a book from the lectern if there isn't one.
return
}
dropItem(tx, l.Book, pos.Side(cube.FaceUp).Vec3Middle())
l.Book = item.Stack{}
tx.SetBlock(pos, l, nil)
tx.PlaySound(pos.Vec3Centre(), sound.Attack{})
}
// TurnPage updates the page the lectern is currently on to the page given.
func (l Lectern) TurnPage(pos cube.Pos, tx *world.Tx, page int) error {
if page == l.Page {
// We're already on the correct page, so we don't need to do anything.
return nil
}
if l.Book.Empty() {
return fmt.Errorf("lectern at %v is empty", pos)
}
if r, ok := l.Book.Item().(readableBook); ok && (page >= r.TotalPages() || page < 0) {
return fmt.Errorf("page number %d is out of bounds", page)
}
l.Page = page
tx.SetBlock(pos, l, nil)
return nil
}
// EncodeNBT ...
func (l Lectern) EncodeNBT() map[string]any {
m := map[string]any{
"hasBook": boolByte(!l.Book.Empty()),
"page": int32(l.Page),
"id": "Lectern",
}
if r, ok := l.Book.Item().(readableBook); ok {
m["book"] = nbtconv.WriteItem(l.Book, true)
m["totalPages"] = int32(r.TotalPages())
}
return m
}
// DecodeNBT ...
func (l Lectern) DecodeNBT(m map[string]any) any {
l.Page = int(nbtconv.Int32(m, "page"))
l.Book = nbtconv.MapItem(m, "book")
return l
}
// EncodeItem ...
func (Lectern) EncodeItem() (name string, meta int16) {
return "minecraft:lectern", 0
}
// EncodeBlock ...
func (l Lectern) EncodeBlock() (string, map[string]any) {
return "minecraft:lectern", map[string]any{
"minecraft:cardinal_direction": l.Facing.String(),
"powered_bit": uint8(0), // We don't support redstone, anyway.
}
}
// allLecterns ...
func allLecterns() (lecterns []world.Block) {
for _, f := range cube.Directions() {
lecterns = append(lecterns, Lectern{Facing: f})
}
return
}