Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented lecterns #651

Merged
merged 8 commits into from
Jul 4, 2023
2 changes: 1 addition & 1 deletion server/block/composter.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (c Composter) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.Us
if !ok {
return false
}
ctx.CountSub = 1
ctx.SubtractFromCount(1)
w.AddParticle(pos.Vec3(), particle.BoneMeal{})
if rand.Float64() > compostable.CompostChance() {
w.PlaySound(pos.Vec3(), sound.ComposterFill{})
Expand Down
5 changes: 5 additions & 0 deletions server/block/hash.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 5 additions & 9 deletions server/block/jukebox.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (j Jukebox) BreakInfo() BreakInfo {
}
return newBreakInfo(0.8, alwaysHarvestable, axeEffective, simpleDrops(d...)).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) {
if _, hasDisc := j.Disc(); hasDisc {
w.PlaySound(pos.Vec3(), sound.MusicDiscEnd{})
w.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{})
}
})
}
Expand All @@ -51,22 +51,21 @@ func (j Jukebox) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User

j.Item = item.Stack{}
w.SetBlock(pos, j, nil)
w.PlaySound(pos.Vec3(), sound.MusicDiscEnd{})
w.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{})
} else if held, _ := u.HeldItems(); !held.Empty() {
if m, ok := held.Item().(item.MusicDisc); ok {
j.Item = held

w.SetBlock(pos, j, nil)
w.PlaySound(pos.Vec3(), sound.MusicDiscEnd{})
ctx.CountSub = 1
w.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{})
ctx.SubtractFromCount(1)

w.PlaySound(pos.Vec3(), sound.MusicDiscPlay{DiscType: m.DiscType})
w.PlaySound(pos.Vec3Centre(), sound.MusicDiscPlay{DiscType: m.DiscType})
if u, ok := u.(jukeboxUser); ok {
u.SendJukeboxPopup(fmt.Sprintf("Now playing: %v - %v", m.DiscType.Author(), m.DiscType.DisplayName()))
}
}
}

return true
}

Expand All @@ -77,7 +76,6 @@ func (j Jukebox) Disc() (sound.DiscType, bool) {
return m.DiscType, true
}
}

return sound.DiscType{}, false
}

Expand All @@ -93,11 +91,9 @@ func (j Jukebox) EncodeNBT() map[string]any {
// DecodeNBT ...
func (j Jukebox) DecodeNBT(data map[string]any) any {
s := nbtconv.MapItem(data, "RecordItem")

if _, ok := s.Item().(item.MusicDisc); ok {
j.Item = s
}

return j
}

Expand Down
166 changes: 166 additions & 0 deletions server/block/lectern.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
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.World) 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, alwaysHarvestable, axeEffective, simpleDrops(d...))
}

// UseOnBlock ...
func (l Lectern) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) {
pos, _, used = firstReplaceable(w, pos, face, l)
if !used {
return false
}
l.Facing = user.Rotation().Direction().Opposite()
place(w, 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, w *world.World, u item.User, ctx *item.UseContext) bool {
if !l.Book.Empty() {
// We can't put a book on the lectern if it's full.
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
w.SetBlock(pos, l, nil)

w.PlaySound(pos.Vec3Centre(), sound.LecternBookPlace{})
ctx.SubtractFromCount(1)
return true
JustTalDevelops marked this conversation as resolved.
Show resolved Hide resolved
}

// Punch ...
func (l Lectern) Punch(pos cube.Pos, _ cube.Face, w *world.World, _ item.User) {
if l.Book.Empty() {
// We can't remove a book from the lectern if there isn't one.
return
}

dropItem(w, l.Book, pos.Side(cube.FaceUp).Vec3Middle())

l.Book = item.Stack{}
w.SetBlock(pos, l, nil)
w.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, w *world.World, 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
w.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{
"direction": int32(horizontalDirection(l.Facing)),
"powered_bit": uint8(0), // We don't support redstone, anyway.
}
}

// allLecterns ...
func allLecterns() (lecterns []world.Block) {
for _, d := range cube.Directions() {
lecterns = append(lecterns, Lectern{Facing: d})
}
return
}
19 changes: 19 additions & 0 deletions server/block/model/lectern.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package model

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
)

// Lectern is a model used by lecterns.
type Lectern struct{}

// BBox ...
func (Lectern) BBox(cube.Pos, *world.World) []cube.BBox {
return []cube.BBox{cube.Box(0, 0, 0, 1, 0.9, 1)}
}

// FaceSolid ...
func (Lectern) FaceSolid(cube.Pos, cube.Face, *world.World) bool {
return false
}
2 changes: 2 additions & 0 deletions server/block/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ func init() {
registerAll(allLanterns())
registerAll(allLava())
registerAll(allLeaves())
registerAll(allLecterns())
registerAll(allLight())
registerAll(allLitPumpkins())
registerAll(allLogs())
Expand Down Expand Up @@ -265,6 +266,7 @@ func init() {
world.RegisterItem(Kelp{})
world.RegisterItem(Ladder{})
world.RegisterItem(Lapis{})
world.RegisterItem(Lectern{})
world.RegisterItem(LitPumpkin{})
world.RegisterItem(Loom{})
world.RegisterItem(MelonSeeds{})
Expand Down
2 changes: 1 addition & 1 deletion server/block/wood_door.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func (d WoodDoor) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *worl
ctx.IgnoreBBox = true
place(w, pos, d, user, ctx)
place(w, pos.Side(cube.FaceUp), WoodDoor{Wood: d.Wood, Facing: d.Facing, Top: true, Right: d.Right}, user, ctx)
ctx.CountSub = 1
ctx.SubtractFromCount(1)
return placed(ctx)
}

Expand Down
2 changes: 1 addition & 1 deletion server/item/bone_meal.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type BoneMealAffected interface {
// UseOnBlock ...
func (b BoneMeal) UseOnBlock(pos cube.Pos, _ cube.Face, _ mgl64.Vec3, w *world.World, _ User, ctx *UseContext) bool {
if bm, ok := w.Block(pos).(BoneMealAffected); ok && bm.BoneMeal(pos, w) {
ctx.CountSub = 1
ctx.SubtractFromCount(1)
w.AddParticle(pos.Vec3(), particle.BoneMeal{})
return true
}
Expand Down
9 changes: 7 additions & 2 deletions server/item/book_and_quill.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ type BookAndQuill struct {
}

// MaxCount always returns 1.
func (b BookAndQuill) MaxCount() int {
func (BookAndQuill) MaxCount() int {
return 1
}

// TotalPages returns the total number of pages in the book.
func (b BookAndQuill) TotalPages() int {
return len(b.Pages)
}

// Page returns a specific page from the book and true when the page exists. It will otherwise return an empty string
// and false.
func (b BookAndQuill) Page(page int) (string, bool) {
Expand Down Expand Up @@ -105,7 +110,7 @@ func (b BookAndQuill) EncodeNBT() map[string]any {
}

// EncodeItem ...
func (b BookAndQuill) EncodeItem() (name string, meta int16) {
func (BookAndQuill) EncodeItem() (name string, meta int16) {
return "minecraft:writable_book", 0
}

Expand Down
2 changes: 1 addition & 1 deletion server/item/glass_bottle.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (g GlassBottle) UseOnBlock(pos cube.Pos, _ cube.Face, _ mgl64.Vec3, w *worl
if b, ok := bl.(bottleFiller); ok {
var res world.Block
if res, ctx.NewItem, ok = b.FillBottle(); ok {
ctx.CountSub = 1
ctx.SubtractFromCount(1)
if res != bl {
// Some blocks (think a cauldron) change when using a bottle on it.
w.SetBlock(pos, res, nil)
Expand Down
9 changes: 7 additions & 2 deletions server/item/written_book.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ type WrittenBook struct {
}

// MaxCount always returns 1.
func (w WrittenBook) MaxCount() int {
func (WrittenBook) MaxCount() int {
return 1
}

// TotalPages returns the total number of pages in the book.
func (w WrittenBook) TotalPages() int {
return len(w.Pages)
}

// Page returns a specific page from the book and true when the page exists. It will otherwise return an empty string
// and false.
func (w WrittenBook) Page(page int) (string, bool) {
Expand Down Expand Up @@ -66,6 +71,6 @@ func (w WrittenBook) EncodeNBT() map[string]any {
}

// EncodeItem ...
func (w WrittenBook) EncodeItem() (name string, meta int16) {
func (WrittenBook) EncodeItem() (name string, meta int16) {
return "minecraft:written_book", 0
}
4 changes: 4 additions & 0 deletions server/player/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ type Handler interface {
// HandleSignEdit handles the player editing a sign. It is called for every keystroke while editing a sign and
// has both the old text passed and the text after the edit. This typically only has a change of one character.
HandleSignEdit(ctx *event.Context, frontSide bool, oldText, newText string)
// HandleLecternPageTurn handles the player turning a page in a lectern. ctx.Cancel() may be called to cancel the
// page turn. The page number may be changed by assigning to *page.
HandleLecternPageTurn(ctx *event.Context, pos cube.Pos, oldPage int, newPage *int)
// HandleItemDamage handles the event wherein the item either held by the player or as armour takes
// damage through usage.
// The type of the item may be checked to determine whether it was armour or a tool used. The damage to
Expand Down Expand Up @@ -154,6 +157,7 @@ func (NopHandler) HandleBlockBreak(*event.Context, cube.Pos, *[]item.Stack, *int
func (NopHandler) HandleBlockPlace(*event.Context, cube.Pos, world.Block) {}
func (NopHandler) HandleBlockPick(*event.Context, cube.Pos, world.Block) {}
func (NopHandler) HandleSignEdit(*event.Context, bool, string, string) {}
func (NopHandler) HandleLecternPageTurn(*event.Context, cube.Pos, int, *int) {}
func (NopHandler) HandleItemPickup(*event.Context, *item.Stack) {}
func (NopHandler) HandleItemUse(*event.Context) {}
func (NopHandler) HandleItemUseOnBlock(*event.Context, cube.Pos, cube.Face, mgl64.Vec3) {}
Expand Down