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

     1  package block
     2  
     3  import (
     4  	"github.com/df-mc/dragonfly/server/block/cube"
     5  	"github.com/df-mc/dragonfly/server/internal/nbtconv"
     6  	"github.com/df-mc/dragonfly/server/item"
     7  	"github.com/df-mc/dragonfly/server/world"
     8  	"github.com/df-mc/dragonfly/server/world/particle"
     9  	"github.com/go-gl/mathgl/mgl64"
    10  	"image/color"
    11  	"strings"
    12  	"time"
    13  )
    14  
    15  // Sign is a non-solid block that can display text on the front and back of the block.
    16  type Sign struct {
    17  	transparent
    18  	empty
    19  	bass
    20  	sourceWaterDisplacer
    21  
    22  	// Wood is the type of wood of the sign. This field must have one of the values found in the material
    23  	// package.
    24  	Wood WoodType
    25  	// Attach is the attachment of the Sign. It is either of the type WallAttachment or StandingAttachment.
    26  	Attach Attachment
    27  	// Waxed specifies if the Sign has been waxed by a player. If set to true, the Sign can no longer be edited by
    28  	// anyone and must be destroyed if the text needs to be changed.
    29  	Waxed bool
    30  	// Front is the text of the front side of the sign. Anyone can edit this unless the sign is Waxed.
    31  	Front SignText
    32  	// Back is the text of the back side of the sign. Anyone can edit this unless the sign is Waxed.
    33  	Back SignText
    34  }
    35  
    36  // SignText represents the data for a single side of a sign. The sign can be edited on the front and back side.
    37  type SignText struct {
    38  	// Text is the text displayed on this side of the sign. The text is automatically wrapped if it does not fit on a line.
    39  	Text string
    40  	// BaseColour is the base colour of the text on this side of the sign, changed when using a dye on the sign. The default
    41  	// colour is black.
    42  	BaseColour color.RGBA
    43  	// Glowing specifies if the Sign has glowing text on the current side. If set to true, the text will be visible even
    44  	// in the dark, and it will have an outline to improve visibility.
    45  	Glowing bool
    46  	// Owner holds the XUID of the player that most recently edited this side of the sign.
    47  	Owner string
    48  }
    49  
    50  // SideClosed ...
    51  func (s Sign) SideClosed(cube.Pos, cube.Pos, *world.World) bool {
    52  	return false
    53  }
    54  
    55  // MaxCount ...
    56  func (s Sign) MaxCount() int {
    57  	return 16
    58  }
    59  
    60  // FlammabilityInfo ...
    61  func (s Sign) FlammabilityInfo() FlammabilityInfo {
    62  	return newFlammabilityInfo(0, 0, true)
    63  }
    64  
    65  // FuelInfo ...
    66  func (Sign) FuelInfo() item.FuelInfo {
    67  	return newFuelInfo(time.Second * 10)
    68  }
    69  
    70  // EncodeItem ...
    71  func (s Sign) EncodeItem() (name string, meta int16) {
    72  	return "minecraft:" + s.Wood.String() + "_sign", 0
    73  }
    74  
    75  // BreakInfo ...
    76  func (s Sign) BreakInfo() BreakInfo {
    77  	return newBreakInfo(1, alwaysHarvestable, axeEffective, oneOf(Sign{Wood: s.Wood}))
    78  }
    79  
    80  // Dye dyes the Sign, changing its base colour to that of the colour passed.
    81  func (s Sign) Dye(pos cube.Pos, userPos mgl64.Vec3, c item.Colour) (world.Block, bool) {
    82  	if s.EditingFrontSide(pos, userPos) {
    83  		if s.Front.BaseColour == c.RGBA() {
    84  			return s, false
    85  		}
    86  		s.Front.BaseColour = c.RGBA()
    87  	} else {
    88  		if s.Back.BaseColour == c.RGBA() {
    89  			return s, false
    90  		}
    91  		s.Back.BaseColour = c.RGBA()
    92  	}
    93  	return s, true
    94  }
    95  
    96  // Ink inks the sign either glowing or non-glowing.
    97  func (s Sign) Ink(pos cube.Pos, userPos mgl64.Vec3, glowing bool) (world.Block, bool) {
    98  	if s.EditingFrontSide(pos, userPos) {
    99  		if s.Front.Glowing == glowing {
   100  			return s, false
   101  		}
   102  		s.Front.Glowing = glowing
   103  	} else {
   104  		if s.Back.Glowing == glowing {
   105  			return s, false
   106  		}
   107  		s.Back.Glowing = glowing
   108  	}
   109  	return s, true
   110  }
   111  
   112  // Wax waxes a sign to prevent it from further editing.
   113  func (s Sign) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) {
   114  	if s.Waxed {
   115  		return s, false
   116  	}
   117  	s.Waxed = true
   118  	return s, true
   119  }
   120  
   121  // Activate ...
   122  func (s Sign) Activate(pos cube.Pos, _ cube.Face, _ *world.World, user item.User, _ *item.UseContext) bool {
   123  	if editor, ok := user.(SignEditor); ok && !s.Waxed {
   124  		editor.OpenSign(pos, s.EditingFrontSide(pos, user.Position()))
   125  	}
   126  	return true
   127  }
   128  
   129  // EditingFrontSide returns if the user is editing the front side of the sign based on their position relative to the
   130  // position and direction of the sign.
   131  func (s Sign) EditingFrontSide(pos cube.Pos, userPos mgl64.Vec3) bool {
   132  	return userPos.Sub(pos.Vec3Centre()).Dot(s.Attach.Rotation().Vec3()) > 0
   133  }
   134  
   135  // SignEditor represents something that can edit a sign, typically players.
   136  type SignEditor interface {
   137  	OpenSign(pos cube.Pos, frontSide bool)
   138  }
   139  
   140  // UseOnBlock ...
   141  func (s Sign) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) {
   142  	pos, face, used = firstReplaceable(w, pos, face, s)
   143  	if !used || face == cube.FaceDown {
   144  		return false
   145  	}
   146  
   147  	if face == cube.FaceUp {
   148  		s.Attach = StandingAttachment(user.Rotation().Orientation().Opposite())
   149  	} else {
   150  		s.Attach = WallAttachment(face.Direction())
   151  	}
   152  	place(w, pos, s, user, ctx)
   153  	if editor, ok := user.(SignEditor); ok {
   154  		editor.OpenSign(pos, true)
   155  	}
   156  	return placed(ctx)
   157  }
   158  
   159  // NeighbourUpdateTick ...
   160  func (s Sign) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) {
   161  	if s.Attach.hanging {
   162  		if _, ok := w.Block(pos.Side(s.Attach.facing.Opposite().Face())).(Air); ok {
   163  			w.SetBlock(pos, nil, nil)
   164  			w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s})
   165  			dropItem(w, item.NewStack(Sign{Wood: s.Wood}, 1), pos.Vec3Centre())
   166  		}
   167  		return
   168  	}
   169  	if _, ok := w.Block(pos.Side(cube.FaceDown)).(Air); ok {
   170  		w.SetBlock(pos, nil, nil)
   171  		w.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: s})
   172  		dropItem(w, item.NewStack(Sign{Wood: s.Wood}, 1), pos.Vec3Centre())
   173  	}
   174  }
   175  
   176  // EncodeBlock ...
   177  func (s Sign) EncodeBlock() (name string, properties map[string]any) {
   178  	woodType := strings.Replace(s.Wood.String(), "_", "", 1) + "_"
   179  	if woodType == "oak_" {
   180  		woodType = ""
   181  	}
   182  	if s.Attach.hanging {
   183  		return "minecraft:" + woodType + "wall_sign", map[string]any{"facing_direction": int32(s.Attach.facing + 2)}
   184  	}
   185  	return "minecraft:" + woodType + "standing_sign", map[string]any{"ground_sign_direction": int32(s.Attach.o)}
   186  }
   187  
   188  // DecodeNBT ...
   189  func (s Sign) DecodeNBT(data map[string]any) any {
   190  	if nbtconv.String(data, "Text") != "" {
   191  		// The NBT format changed in 1.19.80 to have separate data for each side of the sign. The old format must still
   192  		// be supported for backwards compatibility.
   193  		s.Front.Text = nbtconv.String(data, "Text")
   194  		s.Front.BaseColour = nbtconv.RGBAFromInt32(nbtconv.Int32(data, "SignTextColor"))
   195  		s.Front.Glowing = nbtconv.Bool(data, "IgnoreLighting") && nbtconv.Bool(data, "TextIgnoreLegacyBugResolved")
   196  		return s
   197  	}
   198  
   199  	front, ok := data["FrontText"].(map[string]any)
   200  	if ok {
   201  		s.Front.BaseColour = nbtconv.RGBAFromInt32(nbtconv.Int32(front, "Color"))
   202  		s.Front.Glowing = nbtconv.Bool(front, "GlowingText")
   203  		s.Front.Text = nbtconv.String(front, "Text")
   204  		s.Front.Owner = nbtconv.String(front, "Owner")
   205  	}
   206  
   207  	back, ok := data["BackText"].(map[string]any)
   208  	if ok {
   209  		s.Back.BaseColour = nbtconv.RGBAFromInt32(nbtconv.Int32(back, "Color"))
   210  		s.Back.Glowing = nbtconv.Bool(back, "GlowingText")
   211  		s.Back.Text = nbtconv.String(back, "Text")
   212  		s.Back.Owner = nbtconv.String(back, "Owner")
   213  	}
   214  
   215  	return s
   216  }
   217  
   218  // EncodeNBT ...
   219  func (s Sign) EncodeNBT() map[string]any {
   220  	m := map[string]any{
   221  		"id":      "Sign",
   222  		"IsWaxed": boolByte(s.Waxed),
   223  		"FrontText": map[string]any{
   224  			"SignTextColor":  nbtconv.Int32FromRGBA(s.Front.BaseColour),
   225  			"IgnoreLighting": boolByte(s.Front.Glowing),
   226  			"Text":           s.Front.Text,
   227  			"TextOwner":      s.Front.Owner,
   228  		},
   229  		"BackText": map[string]any{
   230  			"SignTextColor":  nbtconv.Int32FromRGBA(s.Back.BaseColour),
   231  			"IgnoreLighting": boolByte(s.Back.Glowing),
   232  			"Text":           s.Back.Text,
   233  			"TextOwner":      s.Back.Owner,
   234  		},
   235  	}
   236  	return m
   237  }
   238  
   239  // allSigns ...
   240  func allSigns() (signs []world.Block) {
   241  	for _, w := range WoodTypes() {
   242  		for _, d := range cube.Directions() {
   243  			signs = append(signs, Sign{Wood: w, Attach: WallAttachment(d)})
   244  		}
   245  		for o := cube.Orientation(0); o <= 15; o++ {
   246  			signs = append(signs, Sign{Wood: w, Attach: StandingAttachment(o)})
   247  		}
   248  	}
   249  	return
   250  }