github.com/df-mc/dragonfly@v0.9.13/server/block/lectern.go (about)

     1  package block
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/df-mc/dragonfly/server/block/cube"
     6  	"github.com/df-mc/dragonfly/server/block/model"
     7  	"github.com/df-mc/dragonfly/server/internal/nbtconv"
     8  	"github.com/df-mc/dragonfly/server/item"
     9  	"github.com/df-mc/dragonfly/server/world"
    10  	"github.com/df-mc/dragonfly/server/world/sound"
    11  	"github.com/go-gl/mathgl/mgl64"
    12  	"time"
    13  )
    14  
    15  // Lectern is a librarian's job site block found in villages. It is used to hold books for multiple players to read in
    16  // multiplayer.
    17  // TODO: Redstone functionality.
    18  type Lectern struct {
    19  	bass
    20  	sourceWaterDisplacer
    21  
    22  	// Facing represents the direction the Lectern is facing.
    23  	Facing cube.Face
    24  	// Book is the book currently held by the Lectern.
    25  	Book item.Stack
    26  	// Page is the page the Lectern is currently on in the book.
    27  	Page int
    28  }
    29  
    30  // Model ...
    31  func (Lectern) Model() world.BlockModel {
    32  	return model.Lectern{}
    33  }
    34  
    35  // FuelInfo ...
    36  func (Lectern) FuelInfo() item.FuelInfo {
    37  	return newFuelInfo(time.Second * 15)
    38  }
    39  
    40  // SideClosed ...
    41  func (Lectern) SideClosed(cube.Pos, cube.Pos, *world.World) bool {
    42  	return false
    43  }
    44  
    45  // BreakInfo ...
    46  func (l Lectern) BreakInfo() BreakInfo {
    47  	d := []item.Stack{item.NewStack(Lectern{}, 1)}
    48  	if !l.Book.Empty() {
    49  		d = append(d, l.Book)
    50  	}
    51  	return newBreakInfo(2, alwaysHarvestable, axeEffective, simpleDrops(d...))
    52  }
    53  
    54  // UseOnBlock ...
    55  func (l Lectern) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) {
    56  	pos, _, used = firstReplaceable(w, pos, face, l)
    57  	if !used {
    58  		return false
    59  	}
    60  	l.Facing = user.Rotation().Direction().Face().Opposite()
    61  	place(w, pos, l, user, ctx)
    62  	return placed(ctx)
    63  }
    64  
    65  // readableBook represents a book that can be read through a lectern.
    66  type readableBook interface {
    67  	// TotalPages returns the total number of pages in the book.
    68  	TotalPages() int
    69  	// Page returns a specific page from the book and true when the page exists. It will otherwise return an empty string
    70  	// and false.
    71  	Page(page int) (string, bool)
    72  }
    73  
    74  // Activate ...
    75  func (l Lectern) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, ctx *item.UseContext) bool {
    76  	if !l.Book.Empty() {
    77  		// We can't put a book on the lectern if it's full.
    78  		return false
    79  	}
    80  
    81  	held, _ := u.HeldItems()
    82  	if _, ok := held.Item().(readableBook); !ok {
    83  		// We can't put a non-book item on the lectern.
    84  		return false
    85  	}
    86  
    87  	l.Book, l.Page = held, 0
    88  	w.SetBlock(pos, l, nil)
    89  
    90  	w.PlaySound(pos.Vec3Centre(), sound.LecternBookPlace{})
    91  	ctx.SubtractFromCount(1)
    92  	return true
    93  }
    94  
    95  // Punch ...
    96  func (l Lectern) Punch(pos cube.Pos, _ cube.Face, w *world.World, _ item.User) {
    97  	if l.Book.Empty() {
    98  		// We can't remove a book from the lectern if there isn't one.
    99  		return
   100  	}
   101  
   102  	dropItem(w, l.Book, pos.Side(cube.FaceUp).Vec3Middle())
   103  
   104  	l.Book = item.Stack{}
   105  	w.SetBlock(pos, l, nil)
   106  	w.PlaySound(pos.Vec3Centre(), sound.Attack{})
   107  }
   108  
   109  // TurnPage updates the page the lectern is currently on to the page given.
   110  func (l Lectern) TurnPage(pos cube.Pos, w *world.World, page int) error {
   111  	if page == l.Page {
   112  		// We're already on the correct page, so we don't need to do anything.
   113  		return nil
   114  	}
   115  	if l.Book.Empty() {
   116  		return fmt.Errorf("lectern at %v is empty", pos)
   117  	}
   118  	if r, ok := l.Book.Item().(readableBook); ok && (page >= r.TotalPages() || page < 0) {
   119  		return fmt.Errorf("page number %d is out of bounds", page)
   120  	}
   121  	l.Page = page
   122  	w.SetBlock(pos, l, nil)
   123  	return nil
   124  }
   125  
   126  // EncodeNBT ...
   127  func (l Lectern) EncodeNBT() map[string]any {
   128  	m := map[string]any{
   129  		"hasBook": boolByte(!l.Book.Empty()),
   130  		"page":    int32(l.Page),
   131  		"id":      "Lectern",
   132  	}
   133  	if r, ok := l.Book.Item().(readableBook); ok {
   134  		m["book"] = nbtconv.WriteItem(l.Book, true)
   135  		m["totalPages"] = int32(r.TotalPages())
   136  	}
   137  	return m
   138  }
   139  
   140  // DecodeNBT ...
   141  func (l Lectern) DecodeNBT(m map[string]any) any {
   142  	l.Page = int(nbtconv.Int32(m, "page"))
   143  	l.Book = nbtconv.MapItem(m, "book")
   144  	return l
   145  }
   146  
   147  // EncodeItem ...
   148  func (Lectern) EncodeItem() (name string, meta int16) {
   149  	return "minecraft:lectern", 0
   150  }
   151  
   152  // EncodeBlock ...
   153  func (l Lectern) EncodeBlock() (string, map[string]any) {
   154  	return "minecraft:lectern", map[string]any{
   155  		"minecraft:cardinal_direction": l.Facing.String(),
   156  		"powered_bit":                  uint8(0), // We don't support redstone, anyway.
   157  	}
   158  }
   159  
   160  // allLecterns ...
   161  func allLecterns() (lecterns []world.Block) {
   162  	for _, f := range cube.HorizontalFaces() {
   163  		lecterns = append(lecterns, Lectern{Facing: f})
   164  	}
   165  	return
   166  }