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 }