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

     1  package session
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/df-mc/dragonfly/server/block"
     6  	"github.com/df-mc/dragonfly/server/block/cube"
     7  	"github.com/df-mc/dragonfly/server/entity"
     8  	"github.com/go-gl/mathgl/mgl64"
     9  	"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
    10  	"strings"
    11  	"unicode/utf8"
    12  )
    13  
    14  // BlockActorDataHandler handles an incoming BlockActorData packet from the client, sent for some block entities like
    15  // signs when they are edited.
    16  type BlockActorDataHandler struct{}
    17  
    18  // Handle ...
    19  func (b BlockActorDataHandler) Handle(p packet.Packet, s *Session) error {
    20  	pk := p.(*packet.BlockActorData)
    21  	if id, ok := pk.NBTData["id"]; ok {
    22  		pos := blockPosFromProtocol(pk.Position)
    23  		if !canReach(s.c, pos.Vec3Middle()) {
    24  			return fmt.Errorf("block at %v is not within reach", pos)
    25  		}
    26  		switch id {
    27  		case "Sign":
    28  			return b.handleSign(pk, pos, s)
    29  		}
    30  		return fmt.Errorf("unhandled block actor data ID %v", id)
    31  	}
    32  	return fmt.Errorf("block actor data without 'id' tag: %v", pk.NBTData)
    33  }
    34  
    35  // handleSign handles the BlockActorData packet sent when editing a sign.
    36  func (b BlockActorDataHandler) handleSign(pk *packet.BlockActorData, pos cube.Pos, s *Session) error {
    37  	if _, ok := s.c.World().Block(pos).(block.Sign); !ok {
    38  		s.log.Debugf("sign block actor data for position without sign %v", pos)
    39  		return nil
    40  	}
    41  
    42  	frontText, err := b.textFromNBTData(pk.NBTData, true)
    43  	if err != nil {
    44  		return err
    45  	}
    46  	backText, err := b.textFromNBTData(pk.NBTData, false)
    47  	if err != nil {
    48  		return err
    49  	}
    50  	if err := s.c.EditSign(pos, frontText, backText); err != nil {
    51  		return err
    52  	}
    53  	return nil
    54  }
    55  
    56  // textFromNBTData attempts to retrieve the text from the NBT data of specific sign from the BlockActorData packet.
    57  func (b BlockActorDataHandler) textFromNBTData(data map[string]any, frontSide bool) (string, error) {
    58  	var sideData map[string]any
    59  	var side string
    60  	if frontSide {
    61  		frontSide, ok := data["FrontText"].(map[string]any)
    62  		if !ok {
    63  			return "", fmt.Errorf("sign block actor data 'FrontText' tag was not found or was not a map: %#v", data["FrontText"])
    64  		}
    65  		sideData = frontSide
    66  		side = "front"
    67  	} else {
    68  		backSide, ok := data["BackText"].(map[string]any)
    69  		if !ok {
    70  			return "", fmt.Errorf("sign block actor data 'BackText' tag was not found or was not a map: %#v", data["BackText"])
    71  		}
    72  		sideData = backSide
    73  		side = "back"
    74  	}
    75  	var text string
    76  	pkText, ok := sideData["Text"]
    77  	if !ok {
    78  		return "", fmt.Errorf("sign block actor data had no 'Text' tag for side %s", side)
    79  	}
    80  	if text, ok = pkText.(string); !ok {
    81  		return "", fmt.Errorf("sign block actor data 'Text' tag was not a string for side %s: %#v", side, pkText)
    82  	}
    83  
    84  	// Verify that the text was valid. It must be valid UTF8 and not more than 100 characters long.
    85  	text = strings.TrimRight(text, "\n")
    86  	if len(text) > 256 {
    87  		return "", fmt.Errorf("sign block actor data text was longer than 256 characters for side %s", side)
    88  	}
    89  	if !utf8.ValidString(text) {
    90  		return "", fmt.Errorf("sign block actor data text was not valid UTF8 for side %s", side)
    91  	}
    92  	return text, nil
    93  }
    94  
    95  // canReach checks if a player can reach a position with its current range. The range depends on if the player
    96  // is either survival or creative mode.
    97  func canReach(c Controllable, pos mgl64.Vec3) bool {
    98  	const (
    99  		creativeRange = 14.0
   100  		survivalRange = 8.0
   101  	)
   102  	if !c.GameMode().AllowsInteraction() {
   103  		return false
   104  	}
   105  
   106  	eyes := entity.EyePosition(c)
   107  	if c.GameMode().CreativeInventory() {
   108  		return eyes.Sub(pos).Len() <= creativeRange && !c.Dead()
   109  	}
   110  	return eyes.Sub(pos).Len() <= survivalRange && !c.Dead()
   111  }