Skip to content

Commit

Permalink
server/block.go: Implement lecterns (#651)
Browse files Browse the repository at this point in the history
Co-authored-by: DaPigGuy <mcpepig123@gmail.com>
Co-authored-by: TwistedAsylumMC <twistedasylummc@gmail.com>
  • Loading branch information
3 people committed Jul 4, 2023
1 parent 35c9801 commit 24f3a3c
Show file tree
Hide file tree
Showing 19 changed files with 303 additions and 19 deletions.
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
}

// 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

0 comments on commit 24f3a3c

Please sign in to comment.