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 }