Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Sandertv committed May 24, 2023
2 parents e587db5 + 9584785 commit 9f21c5c
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 61 deletions.
112 changes: 70 additions & 42 deletions server/block/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import (
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/particle"
"github.com/go-gl/mathgl/mgl64"
"github.com/google/uuid"
"image/color"
"strings"
"time"
)

// Sign is a non-solid block that can display text.
// Sign is a non-solid block that can display text on the front and back of the block.
type Sign struct {
transparent
empty
Expand All @@ -25,16 +24,27 @@ type Sign struct {
Wood WoodType
// Attach is the attachment of the Sign. It is either of the type WallAttachment or StandingAttachment.
Attach Attachment
// Text is the text displayed on the sign. The text is automatically wrapped if it does not fit on a line.
// Waxed specifies if the Sign has been waxed by a player. If set to true, the Sign can no longer be edited by
// anyone and must be destroyed if the text needs to be changed.
Waxed bool
// Front is the data of the front side of the sign. Anyone can edit this unless the sign is Waxed.
Front SignData
// Back is the data of the back side of the sign. Anyone can edit this unless the sign is Waxed.
Back SignData
}

// SignData represents the data for a single side of a sign. The sign can be edited on the front and back side.
type SignData struct {
// Text is the text displayed on this side of the sign. The text is automatically wrapped if it does not fit on a line.
Text string
// BaseColour is the base colour of the text on the sign, changed when using a dye on the sign. The default colour
// is black.
// BaseColour is the base colour of the text on this side of the sign, changed when using a dye on the sign. The default
// colour is black.
BaseColour color.RGBA
// Glowing specifies if the Sign has glowing text. If set to true, the text will be visible even in the dark, and it
// will have an outline to improve visibility.
// Glowing specifies if the Sign has glowing text on the current side. If set to true, the text will be visible even
// in the dark, and it will have an outline to improve visibility.
Glowing bool
// owner holds the UUID of the player that initially placed the sign.
owner uuid.UUID
// Owner holds the XUID of the player that most recently edited this side of the sign.
Owner string
}

// SideClosed ...
Expand Down Expand Up @@ -68,32 +78,28 @@ func (s Sign) BreakInfo() BreakInfo {
}

// Dye dyes the Sign, changing its base colour to that of the colour passed.
func (s Sign) Dye(c item.Colour) (world.Block, bool) {
if s.BaseColour == c.RGBA() {
func (s Sign) Dye(c item.Colour, face cube.Face) (world.Block, bool) {
// TODO: Dye for back side of the sign
if s.Front.BaseColour == c.RGBA() {
return s, false
}
s.BaseColour = c.RGBA()
s.Front.BaseColour = c.RGBA()
return s, true
}

// Ink inks the sign either glowing or non-glowing.
func (s Sign) Ink(glowing bool) (world.Block, bool) {
if s.Glowing == glowing {
// TODO: Ink for back side of the sign
if s.Front.Glowing == glowing {
return s, false
}
s.Glowing = glowing
s.Front.Glowing = glowing
return s, true
}

// SignEditor represents something that can edit a sign, typically players.
type SignEditor interface {
UUID() uuid.UUID
}

// EditableBy returns whether a SignEditor can edit the sign or not. This is based on whether the SignEditor
// placed the sign and the sign's chunk has yet to be unloaded.
func (s Sign) EditableBy(editor SignEditor) bool {
return editor.UUID() == s.owner
OpenSign(pos cube.Pos, frontSide bool)
}

// UseOnBlock ...
Expand All @@ -103,17 +109,15 @@ func (s Sign) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.Wo
return false
}

if editor, ok := user.(SignEditor); ok {
s.owner = editor.UUID()
}

if face == cube.FaceUp {
s.Attach = StandingAttachment(user.Rotation().Orientation().Opposite())
place(w, pos, s, user, ctx)
return
} else {
s.Attach = WallAttachment(face.Direction())
}
s.Attach = WallAttachment(face.Direction())
place(w, pos, s, user, ctx)
if editor, ok := user.(SignEditor); ok {
editor.OpenSign(pos, true)
}
return placed(ctx)
}

Expand Down Expand Up @@ -148,27 +152,51 @@ func (s Sign) EncodeBlock() (name string, properties map[string]any) {

// DecodeNBT ...
func (s Sign) DecodeNBT(data map[string]any) any {
s.Text = nbtconv.String(data, "Text")
s.BaseColour = nbtconv.RGBAFromInt32(nbtconv.Int32(data, "SignTextColor"))
s.Glowing = nbtconv.Bool(data, "IgnoreLighting") && nbtconv.Bool(data, "TextIgnoreLegacyBugResolved")
if nbtconv.String(data, "Text") != "" {
// The NBT format changed in 1.19.80 to have separate data for each side of the sign. The old format must still
// be supported for backwards compatibility.
s.Front.Text = nbtconv.String(data, "Text")
s.Front.BaseColour = nbtconv.RGBAFromInt32(nbtconv.Int32(data, "SignTextColor"))
s.Front.Glowing = nbtconv.Bool(data, "IgnoreLighting") && nbtconv.Bool(data, "TextIgnoreLegacyBugResolved")
return s
}

front, ok := data["FrontText"].(map[string]any)
if ok {
s.Front.BaseColour = nbtconv.RGBAFromInt32(nbtconv.Int32(front, "Color"))
s.Front.Glowing = nbtconv.Bool(front, "GlowingText")
s.Front.Text = nbtconv.String(front, "Text")
s.Front.Owner = nbtconv.String(front, "Owner")
}

back, ok := data["BackText"].(map[string]any)
if ok {
s.Back.BaseColour = nbtconv.RGBAFromInt32(nbtconv.Int32(back, "Color"))
s.Back.Glowing = nbtconv.Bool(back, "GlowingText")
s.Back.Text = nbtconv.String(back, "Text")
s.Back.Owner = nbtconv.String(back, "Owner")
}

return s
}

// EncodeNBT ...
func (s Sign) EncodeNBT() map[string]any {
m := map[string]any{
"id": "Sign",
"SignTextColor": nbtconv.Int32FromRGBA(s.BaseColour),
"IgnoreLighting": boolByte(s.Glowing),
// This is some top class Mojang garbage. The client needs it to render the glowing text. Omitting this field
// will just result in normal text being displayed.
"TextIgnoreLegacyBugResolved": boolByte(s.Glowing),
}
if s.Text != "" {
// The client does not display the editing GUI if this tag is already set when no text is present, so just don't
// send it while the text is empty.
m["Text"] = s.Text
"id": "Sign",
"IsWaxed": boolByte(s.Waxed),
"FrontText": map[string]any{
"SignTextColor": nbtconv.Int32FromRGBA(s.Front.BaseColour),
"IgnoreLighting": boolByte(s.Front.Glowing),
"Text": s.Front.Text,
"TextOwner": s.Front.Owner,
},
"BackText": map[string]any{
"SignTextColor": nbtconv.Int32FromRGBA(s.Back.BaseColour),
"IgnoreLighting": boolByte(s.Back.Glowing),
"Text": s.Back.Text,
"TextOwner": s.Back.Owner,
},
}
return m
}
Expand Down
4 changes: 2 additions & 2 deletions server/player/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ type Handler interface {
HandlePunchAir(ctx *event.Context)
// 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, oldText, newText string)
HandleSignEdit(ctx *event.Context, frontSide bool, oldText, newText string)
// 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 @@ -153,7 +153,7 @@ func (NopHandler) HandleStartBreak(*event.Context, cube.Pos)
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, string, string) {}
func (NopHandler) HandleSignEdit(*event.Context, bool, string, string) {}
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
30 changes: 24 additions & 6 deletions server/player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -2575,23 +2575,41 @@ func (p *Player) ShowParticle(pos mgl64.Vec3, particle world.Particle) {
p.session().ViewParticle(pos, particle)
}

// OpenSign makes the player open the sign at the cube.Pos passed, with the specific side provided. The client will not
// show the interface if it is not aware of a sign at the position.
func (p *Player) OpenSign(pos cube.Pos, frontSide bool) {
p.session().OpenSign(pos, frontSide)
}

// EditSign edits the sign at the cube.Pos passed and writes the text passed to a sign at that position. If no sign is
// present or if the Player cannot edit it, an error is returned
func (p *Player) EditSign(pos cube.Pos, text string) error {
func (p *Player) EditSign(pos cube.Pos, frontText, backText string) error {
w := p.World()
sign, ok := w.Block(pos).(block.Sign)
if !ok {
return fmt.Errorf("edit sign: no sign at position %v", pos)
}
if !sign.EditableBy(p) {
return fmt.Errorf("edit sign: sign text was already finalized")

if sign.Waxed {
return nil
} else if frontText == sign.Front.Text && backText == sign.Back.Text {
return nil
}

ctx := event.C()
if p.Handler().HandleSignEdit(ctx, sign.Text, text); ctx.Cancelled() {
return nil
if frontText != sign.Front.Text {
if p.Handler().HandleSignEdit(ctx, true, sign.Front.Text, frontText); ctx.Cancelled() {
return nil
}
sign.Front.Text = frontText
sign.Front.Owner = p.XUID()
} else {
if p.Handler().HandleSignEdit(ctx, false, sign.Back.Text, backText); ctx.Cancelled() {
return nil
}
sign.Back.Text = backText
sign.Back.Owner = p.XUID()
}
sign.Text = text
w.SetBlock(pos, sign, nil)
return nil
}
Expand Down
3 changes: 2 additions & 1 deletion server/session/controllable.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ type Controllable interface {

Exhaust(points float64)

EditSign(pos cube.Pos, text string) error
OpenSign(pos cube.Pos, frontSide bool)
EditSign(pos cube.Pos, frontText, backText string) error

EnderChestInventory() *inventory.Inventory

Expand Down
2 changes: 1 addition & 1 deletion server/session/enchantment_texts.go

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

48 changes: 39 additions & 9 deletions server/session/handler_block_actor_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,55 @@ func (b BlockActorDataHandler) handleSign(pk *packet.BlockActorData, pos cube.Po
return nil
}

frontText, err := b.textFromNBTData(pk.NBTData, true)
if err != nil {
return err
}
backText, err := b.textFromNBTData(pk.NBTData, false)
if err != nil {
return err
}
if err := s.c.EditSign(pos, frontText, backText); err != nil {
return err
}
return nil
}

// textFromNBTData attempts to retrieve the text from the NBT data of specific sign from the BlockActorData packet.
func (b BlockActorDataHandler) textFromNBTData(data map[string]any, frontSide bool) (string, error) {
var sideData map[string]any
var side string
if frontSide {
frontSide, ok := data["FrontText"].(map[string]any)
if !ok {
return "", fmt.Errorf("sign block actor data 'FrontText' tag was not found or was not a map: %#v", data["FrontText"])
}
sideData = frontSide
side = "front"
} else {
backSide, ok := data["BackText"].(map[string]any)
if !ok {
return "", fmt.Errorf("sign block actor data 'BackText' tag was not found or was not a map: %#v", data["BackText"])
}
sideData = backSide
side = "back"
}
var text string
pkText, ok := pk.NBTData["Text"]
pkText, ok := sideData["Text"]
if !ok {
return fmt.Errorf("sign block actor data had no 'Text' tag")
return "", fmt.Errorf("sign block actor data had no 'Text' tag for side %s", side)
}
if text, ok = pkText.(string); !ok {
return fmt.Errorf("sign block actor data 'Text' tag was not a string: %#v", pkText)
return "", fmt.Errorf("sign block actor data 'Text' tag was not a string for side %s: %#v", side, pkText)
}

// Verify that the text was valid. It must be valid UTF8 and not more than 100 characters long.
text = strings.TrimRight(text, "\n")
if len(text) > 256 {
return fmt.Errorf("sign block actor data text was longer than 256 characters")
return "", fmt.Errorf("sign block actor data text was longer than 256 characters for side %s", side)
}
if !utf8.ValidString(text) {
return fmt.Errorf("sign block actor data text was not valid UTF8")
return "", fmt.Errorf("sign block actor data text was not valid UTF8 for side %s", side)
}
if err := s.c.EditSign(pos, text); err != nil {
return err
}
return nil
return text, nil
}
9 changes: 9 additions & 0 deletions server/session/world.go
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,15 @@ func (s *Session) ViewSound(pos mgl64.Vec3, soundType world.Sound) {
s.playSound(pos, soundType, false)
}

// OpenSign ...
func (s *Session) OpenSign(pos cube.Pos, frontSide bool) {
blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}
s.writePacket(&packet.OpenSign{
Position: blockPos,
FrontSide: frontSide,
})
}

// ViewFurnaceUpdate updates a furnace for the associated session based on previous times.
func (s *Session) ViewFurnaceUpdate(prevCookTime, cookTime, prevRemainingFuelTime, remainingFuelTime, prevMaxFuelTime, maxFuelTime time.Duration) {
if prevCookTime != cookTime {
Expand Down

0 comments on commit 9f21c5c

Please sign in to comment.