Backup
This commit is contained in:
parent
78b2a2f022
commit
a6a7a94c20
350
main.go
350
main.go
@ -28,6 +28,7 @@ const (
|
|||||||
|
|
||||||
var maxPlayers int32
|
var maxPlayers int32
|
||||||
var icon string
|
var icon string
|
||||||
|
var iconData []byte
|
||||||
var serverID = make([]byte, 20)
|
var serverID = make([]byte, 20)
|
||||||
|
|
||||||
var serverPrivateKey *rsa.PrivateKey
|
var serverPrivateKey *rsa.PrivateKey
|
||||||
@ -38,7 +39,8 @@ var usernameRegex = regexp.MustCompile("^[a-zA-Z0-9_]{3,16}$")
|
|||||||
var world World
|
var world World
|
||||||
|
|
||||||
func readIcon(fileName string) (icon string) {
|
func readIcon(fileName string) (icon string) {
|
||||||
iconData, err := os.ReadFile(fileName)
|
var err error
|
||||||
|
iconData, err = os.ReadFile(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -85,6 +87,7 @@ func main() {
|
|||||||
LabelInt: 0,
|
LabelInt: 0,
|
||||||
URL: "https://brn.systems",
|
URL: "https://brn.systems",
|
||||||
}},
|
}},
|
||||||
|
TickRate: 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -125,7 +128,6 @@ func handleRequest(conn net.Conn) {
|
|||||||
var player = Player{
|
var player = Player{
|
||||||
state: 0,
|
state: 0,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
name: "",
|
|
||||||
version: 0,
|
version: 0,
|
||||||
requestedAddress: "",
|
requestedAddress: "",
|
||||||
requestedPort: 0,
|
requestedPort: 0,
|
||||||
@ -195,9 +197,9 @@ func handleRequest(conn net.Conn) {
|
|||||||
errOut = err
|
errOut = err
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
player.name = username
|
player.Entity.Name = username
|
||||||
player.uuid = playerUUID
|
player.uuid = playerUUID
|
||||||
if !usernameRegex.MatchString(player.name) {
|
if !usernameRegex.MatchString(player.Entity.Name) {
|
||||||
errOut = errors.New("bad username")
|
errOut = errors.New("bad username")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -235,7 +237,7 @@ func handleRequest(conn net.Conn) {
|
|||||||
errOut = errors.New("joined session not found")
|
errOut = errors.New("joined session not found")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if player.profile.ID != strings.Replace(player.uuid.String(), "-", "", -1) || player.profile.Name != player.name {
|
if player.profile.ID != strings.Replace(player.uuid.String(), "-", "", -1) || player.profile.Name != player.Entity.Name {
|
||||||
errOut = errors.New("wrong session name")
|
errOut = errors.New("wrong session name")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -354,6 +356,341 @@ func handleRequest(conn net.Conn) {
|
|||||||
case 4:
|
case 4:
|
||||||
switch packetID {
|
switch packetID {
|
||||||
|
|
||||||
|
case 0x00: // Confirm Teleportation
|
||||||
|
teleportID, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Handle teleport confirmation logic here
|
||||||
|
if player.teleportID == teleportID {
|
||||||
|
player.waitingForTeleportConfirm = false
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x01: // Query Block Entity Tag
|
||||||
|
transactionID, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var position BlockPosition
|
||||||
|
err = position.receivePosition(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Handle block entity query logic here
|
||||||
|
player.queryBlockEntityTag(transactionID, position)
|
||||||
|
|
||||||
|
case 0x02: // Change Difficulty
|
||||||
|
difficulty, err := packetData.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Change difficulty logic, ensure player is an operator
|
||||||
|
if player.PermissionLevel >= 2 {
|
||||||
|
world.Difficulty = difficulty
|
||||||
|
for _, iterPlayer := range world.Players {
|
||||||
|
iterPlayer.sendDifficulty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x03: // Acknowledge Message
|
||||||
|
messageCount, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Acknowledge message logic here
|
||||||
|
player.acknowledgeMessage(messageCount)
|
||||||
|
|
||||||
|
case 0x04, 0x05: // Chat Command & Signed Chat Command
|
||||||
|
command, err := receiveString(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.processChatCommand(command)
|
||||||
|
|
||||||
|
case 0x06: // Chat Message
|
||||||
|
message, err := receiveString(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ = receiveInt64(packetData)
|
||||||
|
_ = receiveInt64(packetData)
|
||||||
|
// Handle the chat message here
|
||||||
|
player.handleChatMessage(message)
|
||||||
|
|
||||||
|
case 0x07: // Player Session
|
||||||
|
//sessionId, err := receiveUUID(packetData)
|
||||||
|
//if err != nil {
|
||||||
|
// errOut = err
|
||||||
|
// break
|
||||||
|
//}
|
||||||
|
//expiresAt := receiveInt64(packetData)
|
||||||
|
//pubKeyLen, err := receiveVarint(packetData)
|
||||||
|
//
|
||||||
|
//maybe implement in the future
|
||||||
|
|
||||||
|
case 0x08: // Chunk Batch Received
|
||||||
|
_ = receiveFloat32(packetData) //chunks per tick
|
||||||
|
|
||||||
|
case 0x09: // Client Status
|
||||||
|
actionID, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch actionID {
|
||||||
|
case 0:
|
||||||
|
player.sendRespawn(true, true)
|
||||||
|
case 1:
|
||||||
|
player.sendAwardStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x0A: // Client Information
|
||||||
|
// Read Locale (16-character string)
|
||||||
|
locale, err := receiveString(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.locale = locale
|
||||||
|
|
||||||
|
// Read View Distance (1 byte)
|
||||||
|
viewDistance, err := packetData.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.viewDistance = int8(viewDistance)
|
||||||
|
|
||||||
|
// Read Chat Mode (VarInt Enum)
|
||||||
|
chatMode, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.chatMode = chatMode
|
||||||
|
|
||||||
|
// Read Chat Colors (Boolean)
|
||||||
|
chatColors, err := packetData.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.chatColors = chatColors != 0
|
||||||
|
|
||||||
|
// Read Displayed Skin Parts (Unsigned Byte)
|
||||||
|
displayedSkinParts, err := packetData.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.skinParts = displayedSkinParts
|
||||||
|
|
||||||
|
// Read Main Hand (VarInt Enum)
|
||||||
|
mainHand, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.isRightHanded = mainHand == 1
|
||||||
|
|
||||||
|
// Read Enable Text Filtering (Boolean)
|
||||||
|
enableTextFiltering, err := receiveBool(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.textFiltering = enableTextFiltering
|
||||||
|
|
||||||
|
// Read Allow Server Listings (Boolean)
|
||||||
|
allowServerListings, err := receiveBool(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.serverListing = allowServerListings
|
||||||
|
|
||||||
|
// Successfully parsed the packet
|
||||||
|
errOut = nil
|
||||||
|
|
||||||
|
case 0x0B: // Command Suggestions Request
|
||||||
|
transactionId, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
text, err := receiveString(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.requestCommandSuggestions(transactionId, text)
|
||||||
|
|
||||||
|
case 0x0C: // Acknowledge Configuration
|
||||||
|
player.state = 3
|
||||||
|
|
||||||
|
case 0x0D: // Click Container Button
|
||||||
|
windowID, err := packetData.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if windowID != player.WindowID {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buttonID, err := packetData.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ = buttonID
|
||||||
|
//TODO implement logic
|
||||||
|
|
||||||
|
case 0x0E: // Click Container
|
||||||
|
// Parse fields and handle click container logic here
|
||||||
|
windowID, err := packetData.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if windowID != player.WindowID {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read StateID (VarInt)
|
||||||
|
stateID, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Slot (Short)
|
||||||
|
var slot int16
|
||||||
|
slot = receiveInt16(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Button (Byte)
|
||||||
|
button, err := packetData.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Mode (VarInt)
|
||||||
|
mode, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read number of ChangedSlots (VarInt)
|
||||||
|
changedSlotsCount, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var packet ClickContainerPacket
|
||||||
|
|
||||||
|
// Initialize ChangedSlots slice
|
||||||
|
packet.ChangedSlots = make([]Slot, changedSlotsCount)
|
||||||
|
|
||||||
|
// Read each Slot in the ChangedSlots array
|
||||||
|
for i := 0; i < int(changedSlotsCount); i++ {
|
||||||
|
slotData, err := readSlot(data)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
packet.ChangedSlots[i] = slotData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read CarriedItem (ItemStack)
|
||||||
|
carriedItem, err := readSlot(data)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the packet struct
|
||||||
|
packet.WindowID = windowID
|
||||||
|
packet.StateID = stateID
|
||||||
|
packet.Slot = slot
|
||||||
|
packet.Button = int8(button)
|
||||||
|
packet.Mode = mode
|
||||||
|
packet.CarriedItem = carriedItem
|
||||||
|
player.handleClickContainer(packet)
|
||||||
|
|
||||||
|
case 0x0F: // Close Container
|
||||||
|
windowID, err := packetData.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.sendCloseContainer(windowID)
|
||||||
|
|
||||||
|
case 0x10: // Change Container ItemStack State
|
||||||
|
slotID, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
windowID, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
state, err := receiveBool(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.changeContainerSlotState(slotID, windowID, state)
|
||||||
|
|
||||||
|
case 0x11: // Cookie Response
|
||||||
|
key, err := receiveString(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
hasPayload, err := receiveBool(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Handle cookie response logic here
|
||||||
|
player.handleCookieResponse(key, hasPayload)
|
||||||
|
|
||||||
|
case 0x12: // Serverbound Plugin Message
|
||||||
|
channel, err := receiveString(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data := packetData // Rest is the byte array
|
||||||
|
player.handlePluginMessage(channel, data)
|
||||||
|
|
||||||
|
case 0x13: // Debug Sample Subscription
|
||||||
|
sampleType, err := receiveVarint(packetData)
|
||||||
|
if err != nil {
|
||||||
|
errOut = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
player.subscribeToDebugSample(sampleType)
|
||||||
|
|
||||||
|
case 0x14: // Edit Book
|
||||||
|
// Parse fields and handle book editing logic
|
||||||
|
player.handleEditBook(packetData)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
errOut = fmt.Errorf("unexpected packet %x in play state", packetID)
|
errOut = fmt.Errorf("unexpected packet %x in play state", packetID)
|
||||||
log.Println(errOut)
|
log.Println(errOut)
|
||||||
@ -368,7 +705,8 @@ func handleRequest(conn net.Conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if errOut != nil {
|
if //goland:noinspection GoDfaConstantCondition
|
||||||
|
errOut != nil {
|
||||||
log.Println(errOut.Error())
|
log.Println(errOut.Error())
|
||||||
_ = player.sendDisconnect(fmt.Sprintf("Your server thread crashed: %s", errOut.Error()))
|
_ = player.sendDisconnect(fmt.Sprintf("Your server thread crashed: %s", errOut.Error()))
|
||||||
}
|
}
|
||||||
|
159
packetLib.go
159
packetLib.go
@ -19,7 +19,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@ -34,22 +33,6 @@ func generateRandomBytes(size int) ([]byte, error) {
|
|||||||
return randomBytes, nil
|
return randomBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func serverIDCreate() ([]byte, error) {
|
|
||||||
result := make([]byte, 20)
|
|
||||||
charRange := 0x7E - 0x21 + 1
|
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
|
||||||
// Generate a random number in the range [charStart, charEnd]
|
|
||||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(charRange)))
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}, err
|
|
||||||
}
|
|
||||||
result[i] = byte(0x21 + num.Int64())
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TextPiece struct {
|
type TextPiece struct {
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitempty"`
|
||||||
Color string `json:"color,omitempty"`
|
Color string `json:"color,omitempty"`
|
||||||
@ -62,6 +45,14 @@ type TextComponent struct {
|
|||||||
CleanText string `json:"cleantext,omitempty"`
|
CleanText string `json:"cleantext,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTextComponent(text string) TextComponent {
|
||||||
|
return TextComponent{
|
||||||
|
Text: text,
|
||||||
|
Extra: nil,
|
||||||
|
CleanText: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type RegistryEntry struct {
|
type RegistryEntry struct {
|
||||||
EntryID string `json:"entryID,omitempty"`
|
EntryID string `json:"entryID,omitempty"`
|
||||||
HasNBT bool `json:"hasNBT,omitempty"`
|
HasNBT bool `json:"hasNBT,omitempty"`
|
||||||
@ -237,11 +228,51 @@ type MojangProperty struct {
|
|||||||
Signature string `json:"signature"`
|
Signature string `json:"signature"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mojangProperty *MojangProperty) add(buf *bytes.Buffer) {
|
||||||
|
addString(buf, mojangProperty.Name)
|
||||||
|
addString(buf, mojangProperty.Value)
|
||||||
|
hasSignature := mojangProperty.Signature != ""
|
||||||
|
addBool(buf, hasSignature)
|
||||||
|
if hasSignature {
|
||||||
|
addString(buf, mojangProperty.Signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerAction struct {
|
||||||
|
ActionMask byte
|
||||||
|
Name string
|
||||||
|
Properties []MojangProperty
|
||||||
|
GameMode int32
|
||||||
|
Listed bool
|
||||||
|
Ping int32
|
||||||
|
DisplayName string
|
||||||
|
HasDisplayName bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to add a player with properties
|
||||||
|
func (playerAction *PlayerAction) add(buf *bytes.Buffer) {
|
||||||
|
addString(buf, playerAction.Name)
|
||||||
|
|
||||||
|
// Add properties
|
||||||
|
addVarint(buf, int32(len(playerAction.Properties)))
|
||||||
|
for _, prop := range playerAction.Properties {
|
||||||
|
prop.add(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to update game mode
|
||||||
|
|
||||||
|
// Helper to update listed state
|
||||||
|
|
||||||
|
// Helper to update latency (ping)
|
||||||
|
|
||||||
|
// Helper to update display name
|
||||||
|
|
||||||
func (player *Player) hasJoinedSession(sharedSecret []byte) (bool, error) {
|
func (player *Player) hasJoinedSession(sharedSecret []byte) (bool, error) {
|
||||||
serverKey, err := ExportRSAPublicKey(serverPublicKey)
|
serverKey, err := ExportRSAPublicKey(serverPublicKey)
|
||||||
serverIDHash := AuthDigest([][]byte{serverID, sharedSecret, serverKey})
|
serverIDHash := AuthDigest([][]byte{serverID, sharedSecret, serverKey})
|
||||||
remoteAddr := strings.Split(player.conn.RemoteAddr().String(), ":")[0]
|
remoteAddr := strings.Split(player.conn.RemoteAddr().String(), ":")[0]
|
||||||
url := fmt.Sprintf("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s", player.name, serverIDHash, remoteAddr)
|
url := fmt.Sprintf("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s", player.Entity.Name, serverIDHash, remoteAddr)
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -567,37 +598,6 @@ func receiveVarint(buf *bytes.Buffer) (value int32, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func receiveVarlong(buf *bytes.Buffer) (currentValue int64) {
|
|
||||||
var (
|
|
||||||
b byte
|
|
||||||
shift uint64
|
|
||||||
uValue uint64
|
|
||||||
length uint32
|
|
||||||
)
|
|
||||||
|
|
||||||
for i := 0; i < buf.Len(); i++ {
|
|
||||||
bx, err := buf.ReadByte()
|
|
||||||
b = bx
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
length++
|
|
||||||
|
|
||||||
uValue |= (uint64(b) & 0x7F) << shift
|
|
||||||
shift += 7
|
|
||||||
|
|
||||||
if b&0x80 == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if shift < 8*uint64(length) && b&0x40 != 0 {
|
|
||||||
uValue |= ^uint64(0) << shift
|
|
||||||
}
|
|
||||||
currentValue = int64(uValue)
|
|
||||||
return currentValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func addUUID(buffer *bytes.Buffer, inUUID uuid.UUID) int32 {
|
func addUUID(buffer *bytes.Buffer, inUUID uuid.UUID) int32 {
|
||||||
binaryUUID, err := inUUID.MarshalBinary()
|
binaryUUID, err := inUUID.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -626,6 +626,10 @@ func addByte(buffer *bytes.Buffer, byte byte) {
|
|||||||
buffer.WriteByte(byte)
|
buffer.WriteByte(byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addBytes(buffer *bytes.Buffer, bytes []byte) {
|
||||||
|
buffer.Write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
func addBool(buffer *bytes.Buffer, boolean bool) {
|
func addBool(buffer *bytes.Buffer, boolean bool) {
|
||||||
var boolByte byte = 0x00
|
var boolByte byte = 0x00
|
||||||
if boolean {
|
if boolean {
|
||||||
@ -643,12 +647,6 @@ func receiveBool(buffer *bytes.Buffer) (boolean bool, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func addUint16(buffer *bytes.Buffer, value uint16) {
|
|
||||||
b := make([]byte, 2)
|
|
||||||
binary.BigEndian.PutUint16(b, value)
|
|
||||||
buffer.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decodes an uint16 from the buffer.
|
// Decodes an uint16 from the buffer.
|
||||||
func receiveUint16(buffer *bytes.Buffer) (value uint16) {
|
func receiveUint16(buffer *bytes.Buffer) (value uint16) {
|
||||||
// Create a byte slice to hold the 2 bytes for the uint16.
|
// Create a byte slice to hold the 2 bytes for the uint16.
|
||||||
@ -665,6 +663,22 @@ func receiveUint16(buffer *bytes.Buffer) (value uint16) {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decodes an uint16 from the buffer.
|
||||||
|
func receiveInt16(buffer *bytes.Buffer) (value int16) {
|
||||||
|
// Create a byte slice to hold the 2 bytes for the uint16.
|
||||||
|
b := make([]byte, 2)
|
||||||
|
|
||||||
|
// Read exactly 2 bytes from the buffer.
|
||||||
|
_, err := buffer.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the byte slice to uint16 using BigEndian.
|
||||||
|
value = int16(binary.BigEndian.Uint16(b))
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
func addInt64(buffer *bytes.Buffer, value int64) {
|
func addInt64(buffer *bytes.Buffer, value int64) {
|
||||||
b := make([]byte, 8)
|
b := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(b, uint64(value))
|
binary.BigEndian.PutUint64(b, uint64(value))
|
||||||
@ -707,15 +721,6 @@ func addFloat64(buffer *bytes.Buffer, value float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decodes a float64 from the buffer.
|
// Decodes a float64 from the buffer.
|
||||||
func receiveFloat64(buffer *bytes.Buffer) (value float64) {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
_, err := buffer.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
value = math.Float64frombits(binary.BigEndian.Uint64(b))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func addInt32(buffer *bytes.Buffer, value int32) {
|
func addInt32(buffer *bytes.Buffer, value int32) {
|
||||||
b := make([]byte, 4)
|
b := make([]byte, 4)
|
||||||
@ -724,15 +729,6 @@ func addInt32(buffer *bytes.Buffer, value int32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decodes an int32 from the buffer.
|
// Decodes an int32 from the buffer.
|
||||||
func receiveInt32(buffer *bytes.Buffer) (value int32) {
|
|
||||||
b := make([]byte, 4)
|
|
||||||
_, err := buffer.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
value = int32(binary.BigEndian.Uint32(b))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func addInt16(buffer *bytes.Buffer, value int16) {
|
func addInt16(buffer *bytes.Buffer, value int16) {
|
||||||
b := make([]byte, 2)
|
b := make([]byte, 2)
|
||||||
@ -793,20 +789,3 @@ func addTextComponent(buffer *bytes.Buffer, component TextComponent) {
|
|||||||
addString(buffer, jsonString)
|
addString(buffer, jsonString)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func receiveTextComponent(buf *bytes.Buffer) (component TextComponent, err error) {
|
|
||||||
jsonString, err := receiveString(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.ContainsAny(jsonString, "") {
|
|
||||||
}
|
|
||||||
jsonDecoderText := json.NewDecoder(strings.NewReader(jsonString))
|
|
||||||
err = jsonDecoderText.Decode(&component)
|
|
||||||
if err != nil {
|
|
||||||
err = nil
|
|
||||||
component.Text = jsonString
|
|
||||||
component.CleanText = cleanMinecraftFormatting(jsonString)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
958
playPackets.go
958
playPackets.go
@ -124,17 +124,18 @@ func (player *Player) sendChunks() error {
|
|||||||
func (player *Player) sendCloseContainer(windowID uint8) error {
|
func (player *Player) sendCloseContainer(windowID uint8) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
addByte(&buf, windowID)
|
addByte(&buf, windowID)
|
||||||
|
player.WindowID = 0
|
||||||
return player.sendPacket(0x12, &buf)
|
return player.sendPacket(0x12, &buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *Player) sendContainerContent(windowID uint8, slots []*Slot, cursor *Slot) error {
|
func (player *Player) sendContainerContent(window *Window, cursor *ItemStack) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
addByte(&buf, windowID)
|
addByte(&buf, window.ID)
|
||||||
addVarint(&buf, player.lastContainerStateID)
|
addVarint(&buf, player.lastContainerStateID)
|
||||||
player.lastContainerStateID++
|
player.lastContainerStateID++
|
||||||
addVarint(&buf, int32(len(slots)))
|
addVarint(&buf, int32(len(window.Slots)))
|
||||||
for _, slot := range slots {
|
for _, slot := range window.Slots {
|
||||||
slot.add(&buf)
|
slot.Stack.add(&buf)
|
||||||
}
|
}
|
||||||
cursor.add(&buf)
|
cursor.add(&buf)
|
||||||
return player.sendPacket(0x13, &buf)
|
return player.sendPacket(0x13, &buf)
|
||||||
@ -148,7 +149,7 @@ func (player *Player) sendContainerProperty(windowID uint8, property int16, valu
|
|||||||
return player.sendPacket(0x14, &buf)
|
return player.sendPacket(0x14, &buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *Player) sendContainerSlot(windowID uint8, slotIndex int16, slot *Slot) error {
|
func (player *Player) sendContainerSlot(windowID uint8, slotIndex int16, slot *ItemStack) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
addByte(&buf, windowID)
|
addByte(&buf, windowID)
|
||||||
addVarint(&buf, player.lastContainerStateID)
|
addVarint(&buf, player.lastContainerStateID)
|
||||||
@ -288,16 +289,16 @@ func (player *Player) sendHurtAnimation(attacker *Entity) error {
|
|||||||
return player.sendPacket(0x24, &buf)
|
return player.sendPacket(0x24, &buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *Player) sendWorldBorder(border *WorldBorder) error {
|
func (player *Player) sendWorldBorder() error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
addFloat64(&buf, border.X)
|
addFloat64(&buf, player.Dimension.Border.X)
|
||||||
addFloat64(&buf, border.Z)
|
addFloat64(&buf, player.Dimension.Border.Z)
|
||||||
addFloat64(&buf, border.OldDiameter)
|
addFloat64(&buf, player.Dimension.Border.OldDiameter)
|
||||||
addFloat64(&buf, border.NewDiameter)
|
addFloat64(&buf, player.Dimension.Border.NewDiameter)
|
||||||
addVarlong(&buf, border.Speed)
|
addVarlong(&buf, player.Dimension.Border.Speed)
|
||||||
addVarint(&buf, border.PortalTeleportBoundary)
|
addVarint(&buf, player.Dimension.Border.PortalTeleportBoundary)
|
||||||
addVarint(&buf, border.WarningBlocks)
|
addVarint(&buf, player.Dimension.Border.WarningBlocks)
|
||||||
addVarint(&buf, border.WarningTime)
|
addVarint(&buf, player.Dimension.Border.WarningTime)
|
||||||
return player.sendPacket(0x25, &buf)
|
return player.sendPacket(0x25, &buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,7 +437,7 @@ func (player *Player) sendBookOpen(isOffHand bool) error {
|
|||||||
func (player *Player) sendOpenScreen(windowID int32, window *Window, title TextComponent) error {
|
func (player *Player) sendOpenScreen(windowID int32, window *Window, title TextComponent) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
addVarint(&buf, windowID)
|
addVarint(&buf, windowID)
|
||||||
addVarint(&buf, window.ID)
|
addVarint(&buf, window.Type)
|
||||||
addTextComponent(&buf, title)
|
addTextComponent(&buf, title)
|
||||||
return player.sendPacket(0x33, &buf)
|
return player.sendPacket(0x33, &buf)
|
||||||
}
|
}
|
||||||
@ -497,3 +498,928 @@ func (player *Player) sendRemoveFromTab(playersToRemove []uuid.UUID) error {
|
|||||||
}
|
}
|
||||||
return errors.New("no playersToRemove")
|
return errors.New("no playersToRemove")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendPlayerInfoUpdate(actions byte, players []uuid.UUID, playerActions []PlayerAction) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Write the actions byte
|
||||||
|
addByte(&buf, actions)
|
||||||
|
|
||||||
|
// Write the number of players
|
||||||
|
addVarint(&buf, int32(len(players)))
|
||||||
|
|
||||||
|
// For each player, write their UUID and corresponding actions
|
||||||
|
for i, playerUUID := range players {
|
||||||
|
addUUID(&buf, playerUUID)
|
||||||
|
action := playerActions[i]
|
||||||
|
|
||||||
|
// Based on the action mask, we add the relevant player info
|
||||||
|
if actions&0x01 != 0 {
|
||||||
|
action.add(&buf)
|
||||||
|
}
|
||||||
|
if actions&0x04 != 0 {
|
||||||
|
addVarint(&buf, action.GameMode) // Update Game Mode
|
||||||
|
}
|
||||||
|
if actions&0x08 != 0 {
|
||||||
|
addBool(&buf, action.Listed) // Update Listed
|
||||||
|
}
|
||||||
|
if actions&0x10 != 0 {
|
||||||
|
addVarint(&buf, action.Ping) // Update Latency
|
||||||
|
}
|
||||||
|
if actions&0x20 != 0 {
|
||||||
|
addBool(&buf, action.HasDisplayName) // Update Display Name
|
||||||
|
if action.HasDisplayName {
|
||||||
|
addString(&buf, action.DisplayName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return player.sendPacket(0x3E, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendLookAtPos(pos EntityPosition, aimEyes bool) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addBool(&buf, aimEyes)
|
||||||
|
pos.add(&buf)
|
||||||
|
addBool(&buf, false)
|
||||||
|
return player.sendPacket(0x3F, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendLookAtEntity(entity *Entity, atEntityEyes bool, aimEyes bool) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addBool(&buf, aimEyes)
|
||||||
|
entity.Position.add(&buf)
|
||||||
|
addBool(&buf, true)
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
addBool(&buf, atEntityEyes)
|
||||||
|
return player.sendPacket(0x3F, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendPosition() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
player.Position.add(&buf)
|
||||||
|
player.Rotation.addBasic(&buf)
|
||||||
|
addByte(&buf, 0) //all absolute
|
||||||
|
addVarint(&buf, player.teleportID)
|
||||||
|
player.teleportID++
|
||||||
|
return player.sendPacket(0x40, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendUpdateRecipeBook(action int32, craftingOpen, craftingFilter, smeltingOpen, smeltingFilter, blastFurnaceOpen, blastFurnaceFilter, smokerOpen, smokerFilter bool, recipeIDs1 []string, recipeIDs2 []string) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Action
|
||||||
|
addVarint(&buf, action)
|
||||||
|
|
||||||
|
// Add boolean flags
|
||||||
|
addBool(&buf, craftingOpen)
|
||||||
|
addBool(&buf, craftingFilter)
|
||||||
|
addBool(&buf, smeltingOpen)
|
||||||
|
addBool(&buf, smeltingFilter)
|
||||||
|
addBool(&buf, blastFurnaceOpen)
|
||||||
|
addBool(&buf, blastFurnaceFilter)
|
||||||
|
addBool(&buf, smokerOpen)
|
||||||
|
addBool(&buf, smokerFilter)
|
||||||
|
|
||||||
|
// Add Array size 1 (for Recipe IDs)
|
||||||
|
addVarint(&buf, int32(len(recipeIDs1)))
|
||||||
|
for _, recipeID := range recipeIDs1 {
|
||||||
|
addString(&buf, recipeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If action is 0 (init), add Array size 2 and its Recipe IDs
|
||||||
|
if action == 0 {
|
||||||
|
addVarint(&buf, int32(len(recipeIDs2)))
|
||||||
|
for _, recipeID := range recipeIDs2 {
|
||||||
|
addString(&buf, recipeID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send packet with ID 0x41
|
||||||
|
return player.sendPacket(0x41, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendRemoveEntities(entities []*Entity) error {
|
||||||
|
if len(entities) > 0 {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, int32(len(entities)))
|
||||||
|
for _, entity := range entities {
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x42, &buf)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRemoveEntityEffect sends a packet to remove an entity's effect.
|
||||||
|
func (player *Player) sendRemoveEntityEffect(entity *Entity, effectID int32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Entity ID
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
|
||||||
|
// Add Effect ID
|
||||||
|
addVarint(&buf, effectID)
|
||||||
|
|
||||||
|
// Send packet with ID 0x43
|
||||||
|
return player.sendPacket(0x43, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendResetScore sends a packet to reset a player's score.
|
||||||
|
func (player *Player) sendResetScore(entityName string, objectiveName string) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Entity Name
|
||||||
|
addString(&buf, entityName)
|
||||||
|
|
||||||
|
// Add Has an Objective Name flag
|
||||||
|
addBool(&buf, len(objectiveName) > 0)
|
||||||
|
|
||||||
|
// If there is an Objective Name, add it
|
||||||
|
if len(objectiveName) > 0 {
|
||||||
|
addString(&buf, objectiveName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send packet with ID 0x44
|
||||||
|
return player.sendPacket(0x44, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRemoveResourcePack sends a packet to remove a resource pack.
|
||||||
|
func (player *Player) sendRemoveResourcePack(resourcePackUUID uuid.UUID) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Has UUID flag
|
||||||
|
addBool(&buf, resourcePackUUID != uuid.Nil)
|
||||||
|
|
||||||
|
// If there's a specific resource pack, add its UUID
|
||||||
|
if resourcePackUUID != uuid.Nil {
|
||||||
|
addUUID(&buf, resourcePackUUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a packet with ID 0x45
|
||||||
|
return player.sendPacket(0x45, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends a packet to add a resource pack.
|
||||||
|
func (player *Player) sendAddResourcePack(resourcePackUUID uuid.UUID, url string, hash string, forced bool, hasPrompt bool, promptMessage TextComponent) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add UUID
|
||||||
|
addUUID(&buf, resourcePackUUID)
|
||||||
|
|
||||||
|
// Add URL
|
||||||
|
addString(&buf, url)
|
||||||
|
|
||||||
|
// Add Hash (40-character hexadecimal string)
|
||||||
|
addString(&buf, hash)
|
||||||
|
|
||||||
|
// Add a Forced flag
|
||||||
|
addBool(&buf, forced)
|
||||||
|
|
||||||
|
// Add Has Prompt flag
|
||||||
|
addBool(&buf, hasPrompt)
|
||||||
|
|
||||||
|
// If there's a custom prompt, add it
|
||||||
|
if hasPrompt {
|
||||||
|
addTextComponent(&buf, promptMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send packet with ID 0x46
|
||||||
|
return player.sendPacket(0x46, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRespawn sends a respawn packet.
|
||||||
|
func (player *Player) sendRespawn(keepAttributes bool, keepMetadata bool) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Dimension Type
|
||||||
|
addVarint(&buf, player.Dimension.ID)
|
||||||
|
|
||||||
|
// Add Dimension Name
|
||||||
|
addString(&buf, player.Dimension.Name)
|
||||||
|
|
||||||
|
noise, err := world.Seed.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
addByte(&buf, noise[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Game Mode
|
||||||
|
addByte(&buf, player.GameMode)
|
||||||
|
|
||||||
|
// Add Previous Game Mode
|
||||||
|
addByte(&buf, byte(player.PreviousGameMode))
|
||||||
|
|
||||||
|
// Add Is Debug flag
|
||||||
|
addBool(&buf, world.Debug)
|
||||||
|
|
||||||
|
// Add Is Flat flag
|
||||||
|
addBool(&buf, world.Flat)
|
||||||
|
|
||||||
|
// Add Has a Death Location flag
|
||||||
|
addBool(&buf, player.DeathPosition != BlockPosition{})
|
||||||
|
|
||||||
|
// If the player has a death location, add the dimension and position
|
||||||
|
if player.DeathPosition.X != 0 && player.DeathPosition.Y != 0 && player.DeathPosition.Z != 0 {
|
||||||
|
addString(&buf, player.DeathDimension.Name)
|
||||||
|
player.DeathPosition.add(&buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Portal Cooldown
|
||||||
|
addVarint(&buf, player.portalCooldown)
|
||||||
|
|
||||||
|
var dataKept byte
|
||||||
|
|
||||||
|
if keepAttributes {
|
||||||
|
dataKept |= 0x01
|
||||||
|
}
|
||||||
|
|
||||||
|
if keepMetadata {
|
||||||
|
dataKept |= 0x02
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Data Kept bitmask
|
||||||
|
addByte(&buf, dataKept)
|
||||||
|
|
||||||
|
// Send packet with ID 0x47
|
||||||
|
return player.sendPacket(0x47, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendHeadRotation(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
addByte(&buf, entity.Rotation.Yaw)
|
||||||
|
return player.sendPacket(0x48, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendUpdateSectionBlocks sends a packet to update section blocks.
|
||||||
|
func (player *Player) sendUpdateSectionBlocks(chunkX, chunkY, chunkZ int, blocks []Block) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Chunk Section Position (calculated from chunkX, chunkY, chunkZ)
|
||||||
|
sectionPosition := ((chunkX & 0x3FFFFF) << 42) | (chunkZ&0x3FFFFF)<<20 | (chunkY & 0xFFFFF)
|
||||||
|
addInt64(&buf, int64(sectionPosition))
|
||||||
|
|
||||||
|
// Add number of blocks
|
||||||
|
addVarint(&buf, int32(len(blocks)))
|
||||||
|
|
||||||
|
// Add each block's state and position
|
||||||
|
for _, block := range blocks {
|
||||||
|
blockData := int64(block.BlockStateID<<12) | int64(block.Position.X<<8|block.Position.Z<<4|int32(block.Position.Y))
|
||||||
|
addVarlong(&buf, blockData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send packet with ID 0x49
|
||||||
|
return player.sendPacket(0x49, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSelectAdvancementsTab sends a packet to select an advancement tab.
|
||||||
|
func (player *Player) sendSelectAdvancementsTab() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Has ID flag
|
||||||
|
addBool(&buf, player.advancementTab != "")
|
||||||
|
|
||||||
|
// If there's an ID, add it
|
||||||
|
if player.advancementTab != "" {
|
||||||
|
addString(&buf, player.advancementTab)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a packet with ID 0x4A
|
||||||
|
return player.sendPacket(0x4A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendServerData sends a packet containing the server's MOTD and optional icon.
|
||||||
|
func (player *Player) sendServerData() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add MOTD
|
||||||
|
addTextComponent(&buf, TextComponent{
|
||||||
|
Text: "Hello there",
|
||||||
|
Extra: nil,
|
||||||
|
CleanText: "Hello there",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add Has Icon flag
|
||||||
|
addBool(&buf, len(iconData) > 0)
|
||||||
|
|
||||||
|
// If there is an icon, add it
|
||||||
|
if len(iconData) > 0 {
|
||||||
|
addBytes(&buf, iconData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a packet with ID 0x4B
|
||||||
|
return player.sendPacket(0x4B, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetActionBarText sends a packet to set the action bar text.
|
||||||
|
func (player *Player) sendSetActionBarText(text TextComponent) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Action Bar Text
|
||||||
|
addTextComponent(&buf, text)
|
||||||
|
|
||||||
|
// Send packet with ID 0x4C
|
||||||
|
return player.sendPacket(0x4C, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetBorderCenter sends a packet to set the border center.
|
||||||
|
func (player *Player) sendSetBorderCenter() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add X and Z coordinates
|
||||||
|
addFloat64(&buf, player.Dimension.Border.X)
|
||||||
|
addFloat64(&buf, player.Dimension.Border.Z)
|
||||||
|
|
||||||
|
// Send a packet with ID 0x4D
|
||||||
|
return player.sendPacket(0x4D, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetBorderLerpSize sends a packet to set the border lerp size.
|
||||||
|
func (player *Player) sendSetBorderLerpSize() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Old Diameter
|
||||||
|
addFloat64(&buf, player.Dimension.Border.OldDiameter)
|
||||||
|
|
||||||
|
// Add New Diameter
|
||||||
|
addFloat64(&buf, player.Dimension.Border.NewDiameter)
|
||||||
|
|
||||||
|
// Add Speed
|
||||||
|
addVarlong(&buf, player.Dimension.Border.Speed)
|
||||||
|
|
||||||
|
// Send packet with ID 0x4E
|
||||||
|
return player.sendPacket(0x4E, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetBorderSize sends a packet to set the border size.
|
||||||
|
func (player *Player) sendSetBorderSize() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Diameter
|
||||||
|
addFloat64(&buf, player.Dimension.Border.NewDiameter)
|
||||||
|
|
||||||
|
// Send packet with ID 0x4F
|
||||||
|
return player.sendPacket(0x4F, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetBorderWarningDelay sends a packet to set the border warning delay.
|
||||||
|
func (player *Player) sendSetBorderWarningDelay() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Warning Time (seconds)
|
||||||
|
addVarint(&buf, player.Dimension.Border.WarningTime)
|
||||||
|
|
||||||
|
// Send packet with ID 0x50
|
||||||
|
return player.sendPacket(0x50, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetBorderWarningDistance sends a packet to set the border warning distance.
|
||||||
|
func (player *Player) sendSetBorderWarningDistance() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Warning Distance (blocks)
|
||||||
|
addVarint(&buf, player.Dimension.Border.WarningBlocks)
|
||||||
|
|
||||||
|
// Send packet with ID 0x51
|
||||||
|
return player.sendPacket(0x51, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetCamera sends a packet to set the camera view to an entity.
|
||||||
|
func (player *Player) sendSetCamera(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Camera Entity ID
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
|
||||||
|
// Send packet with ID 0x52
|
||||||
|
return player.sendPacket(0x52, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetHeldItem sends a packet to set the player's held item slot.
|
||||||
|
func (player *Player) sendSetHeldItem() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add ItemStack
|
||||||
|
addByte(&buf, player.inventory.activeSlot)
|
||||||
|
|
||||||
|
// Send packet with ID 0x53
|
||||||
|
return player.sendPacket(0x53, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetCenterChunk sends a packet to set the player's center chunk.
|
||||||
|
func (player *Player) sendSetCenterChunk() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
chunkX, chunkZ := player.Position.toChunkPos()
|
||||||
|
// Add Chunk X and Chunk Z
|
||||||
|
addVarint(&buf, chunkX)
|
||||||
|
addVarint(&buf, chunkZ)
|
||||||
|
|
||||||
|
// Send packet with ID 0x54
|
||||||
|
return player.sendPacket(0x54, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetRenderDistance sends a packet to set the render distance.
|
||||||
|
func (player *Player) sendSetRenderDistance() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add View Distance
|
||||||
|
addVarint(&buf, player.Dimension.RenderDistance)
|
||||||
|
|
||||||
|
// Send packet with ID 0x55
|
||||||
|
return player.sendPacket(0x55, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetDefaultSpawnPosition sends a packet to set the default spawn position.
|
||||||
|
func (player *Player) sendSetDefaultSpawnPosition() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Position
|
||||||
|
player.Dimension.SpawnPosition.add(&buf)
|
||||||
|
|
||||||
|
// Add Angle
|
||||||
|
addFloat32(&buf, player.Dimension.SpawnAngle)
|
||||||
|
|
||||||
|
// Send packet with ID 0x56
|
||||||
|
return player.sendPacket(0x56, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendDisplayObjective sends a packet to display an objective.
|
||||||
|
func (player *Player) sendDisplayObjective(position byte, scoreName string) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Position
|
||||||
|
addByte(&buf, position)
|
||||||
|
|
||||||
|
// Add Score Name
|
||||||
|
addString(&buf, scoreName)
|
||||||
|
|
||||||
|
// Send a packet with ID 0x57
|
||||||
|
return player.sendPacket(0x57, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetEntityMetadata sends a packet to update entity metadata.
|
||||||
|
func (player *Player) sendSetEntityMetadata(entityID int32, metadata EntityMetadata) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Entity ID
|
||||||
|
addVarint(&buf, entityID)
|
||||||
|
|
||||||
|
// Add EntityMetadata
|
||||||
|
metadata.add(&buf)
|
||||||
|
|
||||||
|
// Send packet with ID 0x58
|
||||||
|
return player.sendPacket(0x58, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendLinkEntities sends a packet to link two entities together.
|
||||||
|
func (player *Player) sendLinkEntities(attached, holding *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Attached Entity ID
|
||||||
|
addVarint(&buf, attached.ID)
|
||||||
|
|
||||||
|
// Add Holding Entity ID
|
||||||
|
addVarint(&buf, holding.ID)
|
||||||
|
|
||||||
|
// Send packet with ID 0x59
|
||||||
|
return player.sendPacket(0x59, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetEntityVelocity sends a packet to set an entity's velocity.
|
||||||
|
func (player *Player) sendSetEntityVelocity(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Entity ID
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
|
||||||
|
// Add Velocities
|
||||||
|
addInt16(&buf, int16(entity.Velocity.X))
|
||||||
|
addInt16(&buf, int16(entity.Velocity.Y))
|
||||||
|
addInt16(&buf, int16(entity.Velocity.Z))
|
||||||
|
|
||||||
|
// Send a packet with ID 0x5A
|
||||||
|
return player.sendPacket(0x5A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetEquipment sends a packet to update an entity's equipment.
|
||||||
|
func (player *Player) sendSetEquipment(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Entity ID
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
|
||||||
|
for i := range 7 {
|
||||||
|
slot := i
|
||||||
|
if i == 6 {
|
||||||
|
slot |= 0x80
|
||||||
|
}
|
||||||
|
addByte(&buf, byte(slot))
|
||||||
|
player.inventory.slots[i].add(&buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a packet with ID 0x5B
|
||||||
|
return player.sendPacket(0x5B, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to send experience update packet
|
||||||
|
func (player *Player) sendSetExperience() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
baseExperience := int32(7)
|
||||||
|
experienceMultiplier := int32(3)
|
||||||
|
|
||||||
|
level := int32(0)
|
||||||
|
requiredExperience := int32(0)
|
||||||
|
nextLevelExperience := baseExperience
|
||||||
|
|
||||||
|
// Calculate level based on total experience
|
||||||
|
for {
|
||||||
|
// Calculate experience required to reach the next level
|
||||||
|
requiredExperience += nextLevelExperience
|
||||||
|
|
||||||
|
if player.XP < requiredExperience {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment level and calculate experience required for the next level
|
||||||
|
level++
|
||||||
|
nextLevelExperience = baseExperience + (level*experienceMultiplier*(level+2))/2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate experience bar as a percentage of the next level
|
||||||
|
var experienceBar float32
|
||||||
|
if level > 0 {
|
||||||
|
experienceBar = float32(player.XP-requiredExperience+nextLevelExperience) / float32(nextLevelExperience) // Calculate experience bar
|
||||||
|
} else {
|
||||||
|
experienceBar = float32(player.XP) / float32(baseExperience) // If the level is 0, baseExperience is used
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Experience Bar (percentage of next level)
|
||||||
|
addFloat32(&buf, experienceBar)
|
||||||
|
|
||||||
|
// Add Level
|
||||||
|
addVarint(&buf, level)
|
||||||
|
|
||||||
|
// Add Total Experience
|
||||||
|
addVarint(&buf, player.XP)
|
||||||
|
|
||||||
|
// Send a packet with ID 0x5C
|
||||||
|
return player.sendPacket(0x5C, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetHealth sends a packet to update the player's health and food levels.
|
||||||
|
func (player *Player) sendSetHealth() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Health
|
||||||
|
addFloat32(&buf, player.Entity.Health)
|
||||||
|
|
||||||
|
// Add Food Level
|
||||||
|
addVarint(&buf, player.FoodLevel)
|
||||||
|
|
||||||
|
// Add Food Saturation
|
||||||
|
addFloat32(&buf, player.FoodSaturation)
|
||||||
|
|
||||||
|
// Send a packet with ID 0x5D
|
||||||
|
return player.sendPacket(0x5D, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO 0x5E update objectives
|
||||||
|
|
||||||
|
func (player *Player) sendPassengers(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
addVarint(&buf, int32(len(entity.Passengers)))
|
||||||
|
for _, pass := range entity.Passengers {
|
||||||
|
addVarint(&buf, pass.ID)
|
||||||
|
}
|
||||||
|
return player.sendPacket(0x5F, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO 0X60 are teams
|
||||||
|
|
||||||
|
//TODO 0x61 is scoreboard update
|
||||||
|
|
||||||
|
func (player *Player) sendSimulationDistance() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, world.SimulationDistance)
|
||||||
|
return player.sendPacket(0x62, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendSubtitleText(text TextComponent) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addTextComponent(&buf, text)
|
||||||
|
return player.sendPacket(0x63, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendTime() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addInt64(&buf, player.Dimension.World.Age)
|
||||||
|
addInt64(&buf, player.Dimension.World.Time)
|
||||||
|
return player.sendPacket(0x64, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendTitleText(text TextComponent) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addTextComponent(&buf, text)
|
||||||
|
return player.sendPacket(0x65, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendTitleTimes(fadeIn, stay, fadeout int32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addInt32(&buf, fadeIn)
|
||||||
|
addInt32(&buf, stay)
|
||||||
|
addInt32(&buf, fadeout)
|
||||||
|
return player.sendPacket(0x66, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendEntitySound(sound Sound, entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, sound.ID)
|
||||||
|
if sound.ID == 0 {
|
||||||
|
addString(&buf, sound.Name)
|
||||||
|
addBool(&buf, sound.FixedRange)
|
||||||
|
if sound.FixedRange {
|
||||||
|
addFloat32(&buf, sound.Range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addVarint(&buf, sound.Category)
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
addFloat32(&buf, sound.Volume)
|
||||||
|
addFloat32(&buf, sound.Pitch)
|
||||||
|
addInt64(&buf, sound.Seed)
|
||||||
|
return player.sendPacket(0x67, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSoundEffect sends a sound effect packet to the player.
|
||||||
|
func (player *Player) sendSoundEffect(sound Sound, position EntityPosition) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Sound ID (Sound ID + 1; if 0, use Name)
|
||||||
|
addVarint(&buf, sound.ID+1)
|
||||||
|
|
||||||
|
// If Sound ID is 0, add the sound name, fixed range flag, and range
|
||||||
|
if sound.ID == 0 {
|
||||||
|
addString(&buf, sound.Name)
|
||||||
|
addBool(&buf, sound.FixedRange)
|
||||||
|
|
||||||
|
if sound.FixedRange {
|
||||||
|
addFloat32(&buf, sound.Range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Sound Category
|
||||||
|
addVarint(&buf, sound.Category)
|
||||||
|
|
||||||
|
// Add Effect Position X, Y, Z (multiplied by 8 to handle the fixed-point encoding)
|
||||||
|
position.encodeFixedPoint(&buf)
|
||||||
|
|
||||||
|
// Add Volume and Pitch
|
||||||
|
addFloat32(&buf, sound.Volume)
|
||||||
|
addFloat32(&buf, sound.Pitch)
|
||||||
|
|
||||||
|
// Add Seed
|
||||||
|
addInt64(&buf, sound.Seed)
|
||||||
|
|
||||||
|
// Send a packet with ID 0x68
|
||||||
|
return player.sendPacket(0x68, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) restartConfiguration() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
return player.sendPacket(0x69, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendStopSound sends a packet to stop sound effects for the player.
|
||||||
|
func (player *Player) sendStopSound(source int32, soundName string) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
var flags byte
|
||||||
|
|
||||||
|
if source >= 0 {
|
||||||
|
flags |= 0x01
|
||||||
|
}
|
||||||
|
|
||||||
|
if soundName != "" {
|
||||||
|
flags |= 0x02
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Flags
|
||||||
|
addByte(&buf, flags)
|
||||||
|
|
||||||
|
// If flags indicate that the Source is present (bit 0x1), add the Source
|
||||||
|
if flags&0x1 != 0 {
|
||||||
|
addVarint(&buf, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If flags indicate that the Sound Name is present (bit 0x2), add the Sound Name
|
||||||
|
if flags&0x2 != 0 {
|
||||||
|
addString(&buf, soundName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send packet with ID 0x6A
|
||||||
|
return player.sendPacket(0x6A, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO 0x6B is store cookie
|
||||||
|
|
||||||
|
func (player *Player) sendSystemChatMessage(content TextComponent, overlay bool) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addTextComponent(&buf, content)
|
||||||
|
addBool(&buf, overlay)
|
||||||
|
return player.sendPacket(0x6C, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) sendTabHeaderAndFooter(header, footer TextComponent) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addTextComponent(&buf, header)
|
||||||
|
addTextComponent(&buf, footer)
|
||||||
|
return player.sendPacket(0x6D, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendTagQueryResponse sends a response to a query for a block/entity NBT tag.
|
||||||
|
func (player *Player) sendTagQueryResponse(transactionID int32, nbtData []byte) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Transaction ID
|
||||||
|
addVarint(&buf, transactionID)
|
||||||
|
|
||||||
|
// Add NBT data (can be TAG_END if no NBT is present)
|
||||||
|
buf.Write(nbtData)
|
||||||
|
|
||||||
|
// Send packet with ID 0x6E
|
||||||
|
return player.sendPacket(0x6E, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendPickupItem sends a packet when an entity picks up an item.
|
||||||
|
func (player *Player) sendPickupItem(collectedEntityID, collectorEntityID, itemCount int32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Collected Entity ID
|
||||||
|
addVarint(&buf, collectedEntityID)
|
||||||
|
|
||||||
|
// Add Collector Entity ID
|
||||||
|
addVarint(&buf, collectorEntityID)
|
||||||
|
|
||||||
|
// Add Pickup Item Count
|
||||||
|
addVarint(&buf, itemCount)
|
||||||
|
|
||||||
|
// Send packet with ID 0x6F
|
||||||
|
return player.sendPacket(0x6F, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendTeleportEntity sends a teleport entity packet.
|
||||||
|
func (player *Player) sendTeleportEntity(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Entity ID
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
|
||||||
|
// Add Position (X, Y, Z)
|
||||||
|
entity.Position.add(&buf)
|
||||||
|
|
||||||
|
// Add Yaw and Pitch
|
||||||
|
entity.Rotation.addBasic(&buf)
|
||||||
|
|
||||||
|
// Add On a Ground flag
|
||||||
|
addBool(&buf, entity.onGround())
|
||||||
|
|
||||||
|
// Send packet with ID 0x70
|
||||||
|
return player.sendPacket(0x70, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSetTickingState sends a packet to adjust the client's ticking state.
|
||||||
|
func (player *Player) sendSetTickingState() error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Tick Rate
|
||||||
|
addFloat32(&buf, world.TickRate)
|
||||||
|
|
||||||
|
// Add Is Frozen flag
|
||||||
|
addBool(&buf, world.TickRate <= 0)
|
||||||
|
|
||||||
|
// Send a packet with ID 0x71
|
||||||
|
return player.sendPacket(0x71, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendStepTick sends a packet to advance the client's processing by a specified number of ticks.
|
||||||
|
func (player *Player) sendStepTick(tickSteps int32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Tick Steps
|
||||||
|
addVarint(&buf, tickSteps)
|
||||||
|
|
||||||
|
// Send a packet with ID 0x72
|
||||||
|
return player.sendPacket(0x72, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendTransferPlay sends a packet to notify the client to transfer to a new server.
|
||||||
|
func (player *Player) sendTransferPlay(host string, port int32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Host (server hostname or IP)
|
||||||
|
addString(&buf, host)
|
||||||
|
|
||||||
|
// Add Port
|
||||||
|
addVarint(&buf, port)
|
||||||
|
|
||||||
|
// Send a packet with ID 0x73
|
||||||
|
return player.sendPacket(0x73, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO 0x74 are advancements update
|
||||||
|
|
||||||
|
// sendUpdateAttributes sends an attribute update packet for the given entity.
|
||||||
|
func (player *Player) sendUpdateAttributes(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Entity ID
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
|
||||||
|
// Add Number Of Properties (Attributes)
|
||||||
|
addVarint(&buf, int32(len(entity.Attributes)))
|
||||||
|
|
||||||
|
// Iterate over all attributes and add them to the buffer
|
||||||
|
for _, attr := range entity.Attributes {
|
||||||
|
// Add EntityAttribute ID (VarInt Enum)
|
||||||
|
addVarint(&buf, attr.ID)
|
||||||
|
|
||||||
|
// Add EntityAttribute Value (Base Value)
|
||||||
|
addFloat64(&buf, attr.Value)
|
||||||
|
|
||||||
|
// Add Number Of Modifiers for this attribute
|
||||||
|
addVarint(&buf, int32(len(attr.Modifiers)))
|
||||||
|
|
||||||
|
// Add each modifier
|
||||||
|
for _, mod := range attr.Modifiers {
|
||||||
|
// Add Modifier ID (UUID or unique identifier)
|
||||||
|
addUUID(&buf, mod.ID)
|
||||||
|
|
||||||
|
// Add Modifier Amount
|
||||||
|
addFloat64(&buf, mod.Amount)
|
||||||
|
|
||||||
|
// Add Modifier Operation
|
||||||
|
addByte(&buf, mod.Operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send packet with ID 0x75
|
||||||
|
return player.sendPacket(0x75, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendEntityEffect sends an effect update packet to apply an effect on the given entity.
|
||||||
|
func (player *Player) sendEntityEffect(entityEffect *EntityEffect) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add Entity ID (VarInt)
|
||||||
|
addVarint(&buf, entityEffect.EntityID)
|
||||||
|
|
||||||
|
// Add Effect ID (VarInt)
|
||||||
|
addVarint(&buf, entityEffect.EffectID)
|
||||||
|
|
||||||
|
// Add Amplifier (VarInt)
|
||||||
|
addVarint(&buf, entityEffect.Amplifier)
|
||||||
|
|
||||||
|
// Add Duration (VarInt, use -1 for infinite)
|
||||||
|
addVarint(&buf, entityEffect.Duration)
|
||||||
|
|
||||||
|
// Add Flags (Byte, bitfield)
|
||||||
|
addByte(&buf, entityEffect.Flags)
|
||||||
|
|
||||||
|
// Send a packet with ID 0x76
|
||||||
|
return player.sendPacket(0x76, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendUpdateRecipes sends the Update Recipes packet to the client
|
||||||
|
func (player *Player) sendUpdateRecipes(packet *UpdateRecipesPacket) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Add number of recipes (VarInt)
|
||||||
|
addVarint(&buf, packet.NumRecipes)
|
||||||
|
|
||||||
|
// Add each recipe entry
|
||||||
|
for _, recipeEntry := range packet.Recipes {
|
||||||
|
addVarint(&buf, recipeEntry.TypeID) // Add the recipe type (VarInt)
|
||||||
|
recipeEntry.encode(&buf) // Add the recipe-specific data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the packet (0x77)
|
||||||
|
return player.sendPacket(0x77, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//0x78 is tags that are implemented cross-state
|
||||||
|
|
||||||
|
func (player *Player) sendProjectilePower(entity *Entity) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
addVarint(&buf, entity.ID)
|
||||||
|
addFloat64(&buf, entity.ProjectilePower)
|
||||||
|
return player.sendPacket(0x79, &buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
//0x7A are report details that are implemented cross-state
|
||||||
|
|
||||||
|
//0x7B are server links that are implemented cross-state
|
||||||
|
@ -232,7 +232,7 @@ func (player *Player) sendLoginSuccess() error {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
addUUID(&buf, player.uuid)
|
addUUID(&buf, player.uuid)
|
||||||
|
|
||||||
addString(&buf, player.name)
|
addString(&buf, player.Entity.Name)
|
||||||
|
|
||||||
addVarint(&buf, int32(len(player.profile.Properties)))
|
addVarint(&buf, int32(len(player.profile.Properties)))
|
||||||
|
|
||||||
@ -331,6 +331,12 @@ func (player *Player) removeResourcePack(packUUID uuid.UUID) (err error) {
|
|||||||
if hasUUID {
|
if hasUUID {
|
||||||
addUUID(&buf, packUUID)
|
addUUID(&buf, packUUID)
|
||||||
}
|
}
|
||||||
|
switch player.state {
|
||||||
|
case 3:
|
||||||
|
return player.sendPacket(0x08, &buf)
|
||||||
|
case 4:
|
||||||
|
return player.sendPacket(0x45, &buf)
|
||||||
|
}
|
||||||
return player.sendPacket(0x08, &buf)
|
return player.sendPacket(0x08, &buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,7 +353,13 @@ func (player *Player) addResourcePack(packUUID uuid.UUID, url string, hash strin
|
|||||||
addString(&buf, message)
|
addString(&buf, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return player.sendPacket(0x09, &buf)
|
switch player.state {
|
||||||
|
case 3:
|
||||||
|
return player.sendPacket(0x09, &buf)
|
||||||
|
case 4:
|
||||||
|
return player.sendPacket(0x46, &buf)
|
||||||
|
}
|
||||||
|
return errors.New("invalid resource pack state")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *Player) sendTransfer(host string, port uint16) (err error) {
|
func (player *Player) sendTransfer(host string, port uint16) (err error) {
|
||||||
@ -386,7 +398,13 @@ func (player *Player) sendUpdateTags() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return player.sendPacket(0x0D, &buf)
|
switch player.state {
|
||||||
|
case 3:
|
||||||
|
return player.sendPacket(0x0D, &buf)
|
||||||
|
case 4:
|
||||||
|
return player.sendPacket(0x78, &buf)
|
||||||
|
}
|
||||||
|
return errors.New("invalid update tags state")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *Player) sendDataPacks() error {
|
func (player *Player) sendDataPacks() error {
|
||||||
@ -407,7 +425,14 @@ func (player *Player) sendReportDetails() (err error) {
|
|||||||
addString(&buf, key)
|
addString(&buf, key)
|
||||||
addString(&buf, value)
|
addString(&buf, value)
|
||||||
}
|
}
|
||||||
return player.sendPacket(0x0F, &buf)
|
switch player.state {
|
||||||
|
case 3:
|
||||||
|
return player.sendPacket(0x0F, &buf)
|
||||||
|
case 4:
|
||||||
|
return player.sendPacket(0x7A, &buf)
|
||||||
|
}
|
||||||
|
return errors.New("invalid report details state")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *Player) sendServerLinks() error {
|
func (player *Player) sendServerLinks() error {
|
||||||
@ -423,7 +448,14 @@ func (player *Player) sendServerLinks() error {
|
|||||||
}
|
}
|
||||||
addString(&buf, serverLink.URL)
|
addString(&buf, serverLink.URL)
|
||||||
}
|
}
|
||||||
return player.sendPacket(0x10, &buf)
|
|
||||||
|
switch player.state {
|
||||||
|
case 3:
|
||||||
|
return player.sendPacket(0x10, &buf)
|
||||||
|
case 4:
|
||||||
|
return player.sendPacket(0x7B, &buf)
|
||||||
|
}
|
||||||
|
return errors.New("invalid server links state")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *Player) sendPluginMessage(channel string, data *bytes.Buffer) (err error) {
|
func (player *Player) sendPluginMessage(channel string, data *bytes.Buffer) (err error) {
|
||||||
@ -461,7 +493,7 @@ func (player *Player) sendStatusResponse(description string) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
playerSamples = append(playerSamples, PlayerSample{
|
playerSamples = append(playerSamples, PlayerSample{
|
||||||
Name: player.name,
|
Name: player.Entity.Name,
|
||||||
ID: player.uuid.String(),
|
ID: player.uuid.String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
645
structs.go
645
structs.go
@ -5,9 +5,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,9 +65,15 @@ func (abilities *PlayerAbilites) export() (out byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Inventory struct {
|
||||||
|
activeSlot uint8
|
||||||
|
slots []ItemStack
|
||||||
|
}
|
||||||
|
|
||||||
type Player struct {
|
type Player struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
|
|
||||||
|
PermissionLevel uint8
|
||||||
state int32
|
state int32
|
||||||
version int32
|
version int32
|
||||||
requestedAddress string
|
requestedAddress string
|
||||||
@ -82,8 +91,11 @@ type Player struct {
|
|||||||
Dimension *Dimension
|
Dimension *Dimension
|
||||||
DeathDimension *Dimension
|
DeathDimension *Dimension
|
||||||
DeathPosition BlockPosition
|
DeathPosition BlockPosition
|
||||||
|
CursorStack ItemStack
|
||||||
|
|
||||||
Abilites PlayerAbilites
|
Abilites PlayerAbilites
|
||||||
|
WindowID uint8
|
||||||
|
Window *Window
|
||||||
FlyingSpeed float32
|
FlyingSpeed float32
|
||||||
FOVModifier float32
|
FOVModifier float32
|
||||||
MessageIndex int32
|
MessageIndex int32
|
||||||
@ -92,6 +104,7 @@ type Player struct {
|
|||||||
CombatEnd int64
|
CombatEnd int64
|
||||||
|
|
||||||
Position EntityPosition
|
Position EntityPosition
|
||||||
|
Rotation EntityRotation
|
||||||
|
|
||||||
GameMode uint8
|
GameMode uint8
|
||||||
PreviousGameMode int8
|
PreviousGameMode int8
|
||||||
@ -109,6 +122,7 @@ type Player struct {
|
|||||||
keepAliveSentTimestamp int64
|
keepAliveSentTimestamp int64
|
||||||
keepAliveReceivedTimestamp int64
|
keepAliveReceivedTimestamp int64
|
||||||
|
|
||||||
|
completionID int32
|
||||||
pingID uint32
|
pingID uint32
|
||||||
pingSentTimestamp int64
|
pingSentTimestamp int64
|
||||||
pingReceivedTimestamp int64
|
pingReceivedTimestamp int64
|
||||||
@ -116,7 +130,18 @@ type Player struct {
|
|||||||
|
|
||||||
lastContainerStateID int32
|
lastContainerStateID int32
|
||||||
|
|
||||||
name string
|
teleportID int32
|
||||||
|
waitingForTeleportConfirm bool
|
||||||
|
portalCooldown int32
|
||||||
|
|
||||||
|
inventory Inventory
|
||||||
|
|
||||||
|
advancementTab string
|
||||||
|
|
||||||
|
XP int32
|
||||||
|
FoodLevel int32
|
||||||
|
FoodSaturation float32
|
||||||
|
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,9 +160,48 @@ func (player *Player) endCombat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (player *Player) processChatCommand(command string) {
|
||||||
|
for _, iterPlayer := range player.Dimension.World.Players {
|
||||||
|
iterPlayer.sendSystemChatMessage(getTextComponent(fmt.Sprintf("%s ran %s", player.Entity.Name, command)), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) handleChatMessage(message string) {
|
||||||
|
for _, iterPlayer := range player.Dimension.World.Players {
|
||||||
|
iterPlayer.sendChatMessage(player, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) requestCommandSuggestions(id int32, text string) {
|
||||||
|
player.completionID = id
|
||||||
|
//TODO implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) IsInSurvivalMode() bool {
|
||||||
|
return player.GameMode == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) IsInCreativeMode() bool {
|
||||||
|
return player.GameMode == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) IsInAdventureMode() bool {
|
||||||
|
return player.GameMode == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) IsInSpectatorMode() bool {
|
||||||
|
return player.GameMode == 3
|
||||||
|
}
|
||||||
|
|
||||||
type Dimension struct {
|
type Dimension struct {
|
||||||
ID int32
|
ID int32
|
||||||
Name string
|
Name string
|
||||||
|
Chunks [][][]Chunk
|
||||||
|
Border WorldBorder
|
||||||
|
RenderDistance int32
|
||||||
|
SpawnPosition EntityPosition
|
||||||
|
SpawnAngle float32
|
||||||
|
World *World
|
||||||
}
|
}
|
||||||
|
|
||||||
type World struct {
|
type World struct {
|
||||||
@ -156,6 +220,9 @@ type World struct {
|
|||||||
Flat bool
|
Flat bool
|
||||||
EnforceSecureChat bool
|
EnforceSecureChat bool
|
||||||
|
|
||||||
|
Age int64
|
||||||
|
Time int64
|
||||||
|
|
||||||
Entities []Entity
|
Entities []Entity
|
||||||
Players []Player
|
Players []Player
|
||||||
RegistryDataThings []RegistryData
|
RegistryDataThings []RegistryData
|
||||||
@ -165,6 +232,8 @@ type World struct {
|
|||||||
ReportDetails map[string]string
|
ReportDetails map[string]string
|
||||||
ServerLinks []ServerLink
|
ServerLinks []ServerLink
|
||||||
|
|
||||||
|
TickRate float32
|
||||||
|
|
||||||
Maps map[int32]Map
|
Maps map[int32]Map
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +249,18 @@ type EntityPosition struct {
|
|||||||
Z float64
|
Z float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (entityPosition *EntityPosition) encodeFixedPoint(buf *bytes.Buffer) {
|
||||||
|
addInt32(buf, int32(entityPosition.X*8))
|
||||||
|
addInt32(buf, int32(entityPosition.Y*8))
|
||||||
|
addInt32(buf, int32(entityPosition.Z*8))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entityPosition *EntityPosition) toChunkPos() (chunkX, chunkZ int32) {
|
||||||
|
chunkX = int32(int64(entityPosition.X) >> 4)
|
||||||
|
chunkZ = int32(int64(entityPosition.Z) >> 4)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type FloatOffset struct {
|
type FloatOffset struct {
|
||||||
X float32
|
X float32
|
||||||
Y float32
|
Y float32
|
||||||
@ -209,6 +290,17 @@ type ExplosionSound struct {
|
|||||||
FixedRange float32
|
FixedRange float32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Sound struct {
|
||||||
|
ID int32
|
||||||
|
Name string
|
||||||
|
FixedRange bool
|
||||||
|
Range float32
|
||||||
|
Category int32
|
||||||
|
Volume float32
|
||||||
|
Pitch float32
|
||||||
|
Seed int64
|
||||||
|
}
|
||||||
|
|
||||||
func (explosionSound *ExplosionSound) add(buf *bytes.Buffer) {
|
func (explosionSound *ExplosionSound) add(buf *bytes.Buffer) {
|
||||||
addString(buf, explosionSound.Name)
|
addString(buf, explosionSound.Name)
|
||||||
hasFixedRange := explosionSound.FixedRange > 0
|
hasFixedRange := explosionSound.FixedRange > 0
|
||||||
@ -395,13 +487,27 @@ type Explosion struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Slot struct {
|
type Slot struct {
|
||||||
|
Stack *ItemStack
|
||||||
|
Number int16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Slot) IsEmpty() bool {
|
||||||
|
return s.Stack.Count == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type ItemStack struct {
|
||||||
Count int32
|
Count int32
|
||||||
ItemID int32
|
ItemID int32
|
||||||
ComponentsToAdd map[int32]any
|
ComponentsToAdd map[int32]any
|
||||||
ComponentsToRemove []int32
|
ComponentsToRemove []int32
|
||||||
|
MaxCount int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (slot *Slot) add(buf *bytes.Buffer) {
|
func (slot *ItemStack) IsEmpty() bool {
|
||||||
|
return slot.Count == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slot *ItemStack) add(buf *bytes.Buffer) {
|
||||||
addVarint(buf, slot.Count)
|
addVarint(buf, slot.Count)
|
||||||
if slot.Count > 0 {
|
if slot.Count > 0 {
|
||||||
addVarint(buf, slot.ItemID)
|
addVarint(buf, slot.ItemID)
|
||||||
@ -417,7 +523,7 @@ func (slot *Slot) add(buf *bytes.Buffer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (slot *Slot) addTrade(buf *bytes.Buffer) {
|
func (slot *ItemStack) addTrade(buf *bytes.Buffer) {
|
||||||
addVarint(buf, slot.Count)
|
addVarint(buf, slot.Count)
|
||||||
if slot.Count > 0 {
|
if slot.Count > 0 {
|
||||||
addVarint(buf, slot.ItemID)
|
addVarint(buf, slot.ItemID)
|
||||||
@ -432,6 +538,264 @@ func (slot *Slot) addTrade(buf *bytes.Buffer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (slot *ItemStack) splitStack() ItemStack {
|
||||||
|
if slot.Count > 1 {
|
||||||
|
inNum := slot.Count
|
||||||
|
outNum := slot.Count / 2
|
||||||
|
inNum -= outNum
|
||||||
|
slot.Count = inNum
|
||||||
|
|
||||||
|
out := *slot
|
||||||
|
out.Count = outNum
|
||||||
|
return out
|
||||||
|
|
||||||
|
}
|
||||||
|
return ItemStack{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slot *ItemStack) takeOne() *ItemStack {
|
||||||
|
if slot.Count > 1 {
|
||||||
|
slot.Count -= 1
|
||||||
|
out := *slot
|
||||||
|
out.Count = 1
|
||||||
|
return &out
|
||||||
|
|
||||||
|
}
|
||||||
|
return &ItemStack{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slot *ItemStack) IsSameType(item *ItemStack) bool {
|
||||||
|
return slot.ItemID == item.ItemID && reflect.DeepEqual(slot.ComponentsToAdd, item.ComponentsToAdd) && reflect.DeepEqual(slot.ComponentsToRemove, item.ComponentsToRemove)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slot *ItemStack) Clone() ItemStack {
|
||||||
|
return *slot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slot *ItemStack) AddToStack(i *ItemStack) {
|
||||||
|
if slot.Count == 0 {
|
||||||
|
slot.Count = i.Count
|
||||||
|
slot.ItemID = i.ItemID
|
||||||
|
slot.ComponentsToAdd = i.ComponentsToAdd
|
||||||
|
slot.ComponentsToRemove = i.ComponentsToRemove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slot *ItemStack) IsFull() bool {
|
||||||
|
return slot.Count >= slot.MaxCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slot *ItemStack) TakeAll() *ItemStack {
|
||||||
|
newSlot := *slot
|
||||||
|
slot.Count = 0
|
||||||
|
return &newSlot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (player *Player) handleClickContainer(packet ClickContainerPacket) error {
|
||||||
|
if player.WindowID < 255 {
|
||||||
|
return fmt.Errorf("invalid window ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch packet.Mode {
|
||||||
|
case 0: // Normal click mode
|
||||||
|
switch packet.Button {
|
||||||
|
case 0: // Left click
|
||||||
|
handleLeftClick(player.Window, packet.Slot, packet.CarriedItem)
|
||||||
|
case 1: // Right click
|
||||||
|
handleRightClick(player.Window, packet.Slot, packet.CarriedItem)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown button: %d", packet.Button)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: // Shift-click mode
|
||||||
|
handleShiftClick(player.Window, packet.Slot, packet.Button)
|
||||||
|
|
||||||
|
case 2: // Number key
|
||||||
|
handleNumberKeyClick(player.Window, packet.Slot, packet.Button)
|
||||||
|
|
||||||
|
case 3: // Middle click (for creative mode)
|
||||||
|
if player.IsInCreativeMode() {
|
||||||
|
handleMiddleClick(player.Window, packet.Slot, player)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4: // Drop key
|
||||||
|
handleDropKey(player.Window, packet.Slot, packet.Button)
|
||||||
|
|
||||||
|
case 5: // Dragging items (painting mode)
|
||||||
|
handleDraggingMode(player.Window, packet)
|
||||||
|
|
||||||
|
case 6: // Double click
|
||||||
|
handleDoubleClick(player.Window, packet.Slot)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown mode: %d", packet.Mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleLeftClick(window *Window, slot int16, carriedItem ItemStack) {
|
||||||
|
if carriedItem.IsEmpty() {
|
||||||
|
// Pick up the stack from the slot
|
||||||
|
window.Slots[slot] = Slot{} // Clear the slot
|
||||||
|
} else {
|
||||||
|
// Place the carried item into the slot
|
||||||
|
window.setSlot(slot, &carriedItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRightClick(window *Window, slot int16, carriedItem ItemStack) {
|
||||||
|
slotData := window.GetSlot(slot)
|
||||||
|
|
||||||
|
if carriedItem.IsEmpty() {
|
||||||
|
// Pick up half of the stack in the slot
|
||||||
|
|
||||||
|
window.Player.CursorStack = slotData.splitStack()
|
||||||
|
window.setSlot(slot, slotData) // Update with remaining items
|
||||||
|
} else {
|
||||||
|
// Place one item from the carried stack into the slot
|
||||||
|
if slotData.IsEmpty() {
|
||||||
|
window.setSlot(slot, carriedItem.takeOne())
|
||||||
|
} else if slotData.IsSameType(&carriedItem) {
|
||||||
|
window.AddToSlot(slot, carriedItem.takeOne())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleShiftClick(window *Window, slot int16, button int8) {
|
||||||
|
item := window.GetSlot(slot)
|
||||||
|
|
||||||
|
if item.IsEmpty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if button == 0 {
|
||||||
|
// Move the item to the player's inventory or hotbar
|
||||||
|
window.MoveItemToOtherInventory(item, slot)
|
||||||
|
} else {
|
||||||
|
// Handle shift-right-click if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMiddleClick(window *Window, slot int16, player *Player) {
|
||||||
|
if !player.IsInCreativeMode() {
|
||||||
|
return // Middle click is only allowed in creative mode
|
||||||
|
}
|
||||||
|
|
||||||
|
itemInSlot := window.GetSlot(slot)
|
||||||
|
|
||||||
|
if itemInSlot.IsEmpty() {
|
||||||
|
return // No item in the slot to duplicate
|
||||||
|
}
|
||||||
|
|
||||||
|
clonedItem := itemInSlot.Clone() // Clone the item
|
||||||
|
|
||||||
|
// Give the player the cloned item
|
||||||
|
player.CursorStack = clonedItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDropKey(window *Window, slot int16, button int8) {
|
||||||
|
itemInSlot := window.GetSlot(slot)
|
||||||
|
|
||||||
|
if itemInSlot.IsEmpty() {
|
||||||
|
return // No item to drop
|
||||||
|
}
|
||||||
|
|
||||||
|
if button == 0 {
|
||||||
|
// Drop the entire stack
|
||||||
|
dropItem(itemInSlot, window.Player.Position)
|
||||||
|
window.setSlot(slot, &ItemStack{}) // Remove the stack from the slot
|
||||||
|
} else if button == 1 {
|
||||||
|
// Drop a single item (Ctrl + Drop)
|
||||||
|
singleItem := itemInSlot.takeOne()
|
||||||
|
dropItem(singleItem, window.Player.Position)
|
||||||
|
|
||||||
|
if itemInSlot.IsEmpty() {
|
||||||
|
window.setSlot(slot, &ItemStack{}) // Clear the slot if empty
|
||||||
|
} else {
|
||||||
|
window.setSlot(slot, itemInSlot) // Update the remaining stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dropItem(item *ItemStack, position EntityPosition) {
|
||||||
|
//TODO This function handles the actual item drop in the world
|
||||||
|
// It could be implemented to spawn an entity that represents the dropped item
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDoubleClick(window *Window, slot int16) {
|
||||||
|
itemInSlot := window.GetSlot(slot)
|
||||||
|
|
||||||
|
if itemInSlot.IsEmpty() {
|
||||||
|
return // Nothing to double-click on
|
||||||
|
}
|
||||||
|
|
||||||
|
carriedItem := itemInSlot.Clone() // Pick up the clicked stack
|
||||||
|
window.setSlot(slot, &ItemStack{}) // Clear the clicked slot
|
||||||
|
|
||||||
|
// Loop through the entire inventory and pick up items of the same type
|
||||||
|
for i := range window.Slots {
|
||||||
|
slotItem := window.GetSlot(int16(i))
|
||||||
|
|
||||||
|
if slotItem.IsSameType(&carriedItem) {
|
||||||
|
carriedItem.AddToStack(slotItem.TakeAll())
|
||||||
|
window.setSlot(int16(i), slotItem) // Update the slot with the remaining stack
|
||||||
|
}
|
||||||
|
|
||||||
|
if carriedItem.IsFull() {
|
||||||
|
break // Stop if the carried stack is full
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the player's carried item to the collected stack
|
||||||
|
window.Player.CursorStack = carriedItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleNumberKeyClick(window *Window, slot int16, button int8) {
|
||||||
|
hotbarIndex := button - 1 // The Button corresponds to number keys 1-9
|
||||||
|
|
||||||
|
if hotbarIndex < 0 || hotbarIndex >= 9 {
|
||||||
|
return // Invalid hotbar index
|
||||||
|
}
|
||||||
|
|
||||||
|
hotbarSlot := window.GetSlot(int16(hotbarIndex)) // Get the hotbar slot
|
||||||
|
clickedSlot := window.GetSlot(slot) // Get the clicked slot
|
||||||
|
|
||||||
|
// Swap the hotbar slot with the clicked slot
|
||||||
|
window.setSlot(int16(hotbarIndex), clickedSlot)
|
||||||
|
window.setSlot(slot, hotbarSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDraggingMode(window *Window, packet ClickContainerPacket) {
|
||||||
|
switch packet.Button {
|
||||||
|
case 0, 4, 8: // Start dragging
|
||||||
|
startDragging(packet)
|
||||||
|
|
||||||
|
case 1, 5, 9: // Add slot to drag
|
||||||
|
for _, changedSlot := range packet.ChangedSlots {
|
||||||
|
window.setSlot(changedSlot.Number, changedSlot.Stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2, 6, 10: // End dragging
|
||||||
|
endDragging(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func synchronizeContainer(player *Player, window *Window) {
|
||||||
|
// Compare slot data with the server's state and resync if necessary
|
||||||
|
player.sendContainerContent(window, &player.CursorStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClickContainerPacket struct {
|
||||||
|
WindowID uint8 // Unsigned Byte
|
||||||
|
StateID int32 // Last received State ID
|
||||||
|
Slot int16 // Clicked slot number
|
||||||
|
Button int8 // Byte: the button used
|
||||||
|
Mode int32 // Inventory operation mode
|
||||||
|
ChangedSlots []Slot // Array of changed slots
|
||||||
|
CarriedItem ItemStack // The item currently carried by the cursor
|
||||||
|
}
|
||||||
|
|
||||||
// PalettedContainer represents a palette-based storage of entries.
|
// PalettedContainer represents a palette-based storage of entries.
|
||||||
type PalettedContainer struct {
|
type PalettedContainer struct {
|
||||||
BitsPerEntry uint8
|
BitsPerEntry uint8
|
||||||
@ -636,14 +1000,66 @@ func (bossBar BossBar) updateFlags(player *Player) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (blockPosition *BlockPosition) add(buf *bytes.Buffer) {
|
func (blockPosition *BlockPosition) add(buf *bytes.Buffer) {
|
||||||
var xBuf, yBuf, zBuf []byte
|
// Get the 26-bit values for X and Z
|
||||||
binary.BigEndian.PutUint32(xBuf, uint32(blockPosition.X))
|
x := blockPosition.X
|
||||||
binary.BigEndian.PutUint16(yBuf, uint16(blockPosition.Y))
|
z := blockPosition.Z
|
||||||
binary.BigEndian.PutUint32(zBuf, uint32(blockPosition.Z))
|
y := blockPosition.Y
|
||||||
|
|
||||||
buf.Write(xBuf[0:26])
|
// Pack the values into a byte slice
|
||||||
buf.Write(zBuf[0:26])
|
var positionBuf [4]byte // Enough to hold both x and z (4 bytes total)
|
||||||
buf.Write(yBuf[0:12])
|
positionBuf[0] = byte((x & 0x3FFFFFF) >> 18) // Upper 8 bits of x
|
||||||
|
positionBuf[1] = byte((x & 0x3FFFF) >> 10) // Middle 8 bits of x
|
||||||
|
positionBuf[2] = byte((x & 0x3FF) >> 2) // Lower 8 bits of x
|
||||||
|
positionBuf[3] = byte(((x & 0x3) << 6) | ((z & 0x3FFFFFF) >> 20)) // Last 2 bits of x and upper 6 bits of z
|
||||||
|
|
||||||
|
// Write X and Z to the buffer
|
||||||
|
buf.Write(positionBuf[:])
|
||||||
|
|
||||||
|
// Continue packing Z into the next part of the byte slice
|
||||||
|
positionBuf[0] = byte((z & 0x3FFFFFF) >> 18) // Upper 8 bits of z
|
||||||
|
positionBuf[1] = byte((z & 0x3FFFF) >> 10) // Middle 8 bits of z
|
||||||
|
positionBuf[2] = byte((z & 0x3FF) >> 2) // Lower 8 bits of z
|
||||||
|
positionBuf[3] = byte((z & 0x3) << 6) // Last 2 bits of z
|
||||||
|
|
||||||
|
// Write Z to the buffer
|
||||||
|
buf.Write(positionBuf[:])
|
||||||
|
|
||||||
|
// Pack Y into the buffer (12-bit signed integer)
|
||||||
|
yBuf := [2]byte{
|
||||||
|
byte((y & 0xFFF) >> 4), // Upper 8 bits of y
|
||||||
|
byte((y & 0xF) << 4), // Last 4 bits of y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Y to the buffer
|
||||||
|
buf.Write(yBuf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (blockPosition *BlockPosition) receivePosition(buf *bytes.Buffer) error {
|
||||||
|
if buf.Len() < 10 { // 8 bytes for x and z, 2 bytes for y
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the first 8 bytes for x and z
|
||||||
|
positionBuf := make([]byte, 8)
|
||||||
|
if _, err := buf.Read(positionBuf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract X from the first 4 bytes
|
||||||
|
blockPosition.X = int32(positionBuf[0]&0x3F)<<18 | int32(positionBuf[1])<<10 | int32(positionBuf[2])<<2 | int32(positionBuf[3]&0x3F)
|
||||||
|
|
||||||
|
// Extract Z from the next 4 bytes
|
||||||
|
blockPosition.Z = int32(positionBuf[4]&0x3F)<<18 | int32(positionBuf[5])<<10 | int32(positionBuf[6])<<2 | int32(positionBuf[7]&0x3F)
|
||||||
|
|
||||||
|
// Read the next 2 bytes for y
|
||||||
|
yBuf := make([]byte, 2)
|
||||||
|
if _, err := buf.Read(yBuf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockPosition.Y = int16(int32(yBuf[0])<<4 | (int32(yBuf[1]) >> 4))
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entityPosition *EntityPosition) add(buf *bytes.Buffer) {
|
func (entityPosition *EntityPosition) add(buf *bytes.Buffer) {
|
||||||
@ -670,6 +1086,11 @@ func (entityRotation *EntityRotation) add(buf *bytes.Buffer) {
|
|||||||
addByte(buf, entityRotation.HeadYaw)
|
addByte(buf, entityRotation.HeadYaw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (entityRotation *EntityRotation) addBasic(buf *bytes.Buffer) {
|
||||||
|
addByte(buf, entityRotation.Pitch)
|
||||||
|
addByte(buf, entityRotation.Yaw)
|
||||||
|
}
|
||||||
|
|
||||||
type Velocity struct {
|
type Velocity struct {
|
||||||
X float32
|
X float32
|
||||||
Y float32
|
Y float32
|
||||||
@ -682,10 +1103,31 @@ func (velocity *Velocity) add(buf *bytes.Buffer) {
|
|||||||
addFloat32(buf, velocity.Z)
|
addFloat32(buf, velocity.Z)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Modifier struct {
|
||||||
|
ID uuid.UUID // UUID or another unique identifier for the modifier
|
||||||
|
Amount float64 // Modifier value (positive or negative)
|
||||||
|
Operation byte // Operation type (0 = Add, 1 = Add Percentage, 2 = Multiply Percentage)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityAttribute struct {
|
||||||
|
ID int32 // The ID of the attribute (e.g., generic.max_health = 16)
|
||||||
|
Value float64 // The base value of the attribute
|
||||||
|
Modifiers []Modifier // List of modifiers applied to the attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityEffect struct {
|
||||||
|
EntityID int32 // The ID of the entity being affected
|
||||||
|
EffectID int32 // ID of the effect (see effect table below)
|
||||||
|
Amplifier int32 // The effect level (amplifier + 1 is displayed)
|
||||||
|
Duration int32 // Duration in ticks (-1 for infinite)
|
||||||
|
Flags byte // Bitfield for ambient, particles, icon, and blend
|
||||||
|
}
|
||||||
|
|
||||||
type Entity struct {
|
type Entity struct {
|
||||||
World *World
|
World *World
|
||||||
ID int32
|
ID int32
|
||||||
UUID uuid.UUID
|
UUID uuid.UUID
|
||||||
|
Name string
|
||||||
Type int32
|
Type int32
|
||||||
Position EntityPosition
|
Position EntityPosition
|
||||||
Rotation EntityRotation
|
Rotation EntityRotation
|
||||||
@ -696,6 +1138,11 @@ type Entity struct {
|
|||||||
PortalCooldown int32
|
PortalCooldown int32
|
||||||
Vehicle *Entity
|
Vehicle *Entity
|
||||||
PlayersInformed []*Player
|
PlayersInformed []*Player
|
||||||
|
Health float32
|
||||||
|
Passengers []*Entity
|
||||||
|
Attributes []EntityAttribute // List of attributes for the entity
|
||||||
|
Effects []EntityEffect // List of effects applied to the entity
|
||||||
|
ProjectilePower float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to calculate the Euclidean distance between two points in 3D space
|
// Function to calculate the Euclidean distance between two points in 3D space
|
||||||
@ -763,6 +1210,128 @@ func (XPOrb *XPOrb) add(buf *bytes.Buffer) {
|
|||||||
addInt16(buf, XPOrb.Count)
|
addInt16(buf, XPOrb.Count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateRecipesPacket Base struct for the Update Recipes packet
|
||||||
|
type UpdateRecipesPacket struct {
|
||||||
|
NumRecipes int32 // Number of recipes
|
||||||
|
Recipes []RecipeEntry // List of recipes
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecipeEntry Base struct for different types of recipes
|
||||||
|
type RecipeEntry struct {
|
||||||
|
TypeID int32 // The recipe type (see recipe types table)
|
||||||
|
Data Recipe // Data for this recipe type
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recipe Different recipe types
|
||||||
|
type Recipe interface{}
|
||||||
|
|
||||||
|
// ShapedCraftingRecipe Shaped Crafting Recipe
|
||||||
|
type ShapedCraftingRecipe struct {
|
||||||
|
Group string // Group tag for similar recipes
|
||||||
|
Category int32 // Category: Building, Redstone, etc.
|
||||||
|
Width, Height int32 // Dimensions of the crafting grid
|
||||||
|
Ingredients []Ingredient // Ingredients for the recipe
|
||||||
|
Result ItemStack // Resulting item
|
||||||
|
ShowNotification bool // Whether to show a toast notification
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShapelessCraftingRecipe Shapeless Crafting Recipe
|
||||||
|
type ShapelessCraftingRecipe struct {
|
||||||
|
Group string // Group tag for similar recipes
|
||||||
|
Category int32 // Category: Building, Redstone, etc.
|
||||||
|
Ingredients []Ingredient // List of ingredients (unordered)
|
||||||
|
Result ItemStack // Resulting item
|
||||||
|
ShowNotification bool // Whether to show a toast notification
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmeltingRecipe Smelting, Blasting, Smoking, and Campfire Cooking Recipes
|
||||||
|
type SmeltingRecipe struct {
|
||||||
|
Group string // Group tag for similar recipes
|
||||||
|
Category int32 // Category: Food, Blocks, Misc
|
||||||
|
Ingredient Ingredient // Smelting ingredient
|
||||||
|
Result ItemStack // Resulting item
|
||||||
|
Experience float32 // XP reward
|
||||||
|
CookingTime int32 // Time to cook in ticks
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ingredient struct {
|
||||||
|
Count int32 // Number of possible items for this ingredient
|
||||||
|
Items []ItemStack // List of possible items (slots) that can satisfy this ingredient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (recipe *RecipeEntry) encode(buf *bytes.Buffer) {
|
||||||
|
switch recipe := recipe.Data.(type) {
|
||||||
|
case ShapedCraftingRecipe:
|
||||||
|
encodeShapedCrafting(buf, recipe)
|
||||||
|
case ShapelessCraftingRecipe:
|
||||||
|
encodeShapelessCrafting(buf, recipe)
|
||||||
|
case SmeltingRecipe:
|
||||||
|
encodeSmelting(buf, recipe)
|
||||||
|
// Other recipe types can be handled similarly...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ingredient *Ingredient) add(buf *bytes.Buffer) {
|
||||||
|
addVarint(buf, ingredient.Count) // Number of possible items
|
||||||
|
for _, slot := range ingredient.Items {
|
||||||
|
slot.add(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeShapedCrafting(buf *bytes.Buffer, recipe ShapedCraftingRecipe) {
|
||||||
|
addString(buf, recipe.Group)
|
||||||
|
addVarint(buf, recipe.Category)
|
||||||
|
addVarint(buf, recipe.Width)
|
||||||
|
addVarint(buf, recipe.Height)
|
||||||
|
|
||||||
|
// Add each ingredient
|
||||||
|
for _, ingredient := range recipe.Ingredients {
|
||||||
|
ingredient.add(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add result slot
|
||||||
|
recipe.Result.add(buf)
|
||||||
|
|
||||||
|
// Add a show notification flag (boolean)
|
||||||
|
addBool(buf, recipe.ShowNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeShapelessCrafting(buf *bytes.Buffer, recipe ShapelessCraftingRecipe) {
|
||||||
|
addString(buf, recipe.Group)
|
||||||
|
addVarint(buf, recipe.Category)
|
||||||
|
|
||||||
|
// Add number of ingredients (VarInt)
|
||||||
|
addVarint(buf, int32(len(recipe.Ingredients)))
|
||||||
|
|
||||||
|
// Add each ingredient
|
||||||
|
for _, ingredient := range recipe.Ingredients {
|
||||||
|
ingredient.add(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add result slot
|
||||||
|
recipe.Result.add(buf)
|
||||||
|
|
||||||
|
// Add a show notification flag (boolean)
|
||||||
|
addBool(buf, recipe.ShowNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeSmelting(buf *bytes.Buffer, recipe SmeltingRecipe) {
|
||||||
|
addString(buf, recipe.Group)
|
||||||
|
addVarint(buf, recipe.Category)
|
||||||
|
|
||||||
|
// Add ingredient
|
||||||
|
recipe.Ingredient.add(buf)
|
||||||
|
|
||||||
|
// Add result slot
|
||||||
|
recipe.Result.add(buf)
|
||||||
|
|
||||||
|
// Add experience (float)
|
||||||
|
addFloat32(buf, recipe.Experience)
|
||||||
|
|
||||||
|
// Add cooking time (VarInt)
|
||||||
|
addVarint(buf, recipe.CookingTime)
|
||||||
|
}
|
||||||
|
|
||||||
type MapIcon struct {
|
type MapIcon struct {
|
||||||
Type int32
|
Type int32
|
||||||
X int8
|
X int8
|
||||||
@ -824,9 +1393,9 @@ func (Map *Map) add(buf *bytes.Buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VillageTrade struct {
|
type VillageTrade struct {
|
||||||
In1 Slot
|
In1 ItemStack
|
||||||
In2 *Slot
|
In2 *ItemStack
|
||||||
Out Slot
|
Out ItemStack
|
||||||
Disabled bool
|
Disabled bool
|
||||||
Uses int32
|
Uses int32
|
||||||
MaxUses int32
|
MaxUses int32
|
||||||
@ -870,6 +1439,48 @@ func (VillagerEntity *VillagerEntity) addOffers(buf *bytes.Buffer, windowID int3
|
|||||||
addBool(buf, VillagerEntity.CanRestock)
|
addBool(buf, VillagerEntity.CanRestock)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Window struct {
|
type WindowInteractible interface {
|
||||||
ID int32
|
click()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Window struct {
|
||||||
|
ID uint8
|
||||||
|
Slots []Slot
|
||||||
|
Type int32
|
||||||
|
Player *Player
|
||||||
|
}
|
||||||
|
|
||||||
|
func (window *Window) setSlot(index int16, stack *ItemStack) {
|
||||||
|
window.Slots[index] = Slot{
|
||||||
|
Stack: stack,
|
||||||
|
Number: index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (window *Window) GetSlot(slot int16) *ItemStack {
|
||||||
|
return window.Slots[slot].Stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (window *Window) AddToSlot(slotIndex int16, stack *ItemStack) {
|
||||||
|
if window.Slots[slotIndex].Stack.Count == 0 {
|
||||||
|
window.Slots[slotIndex].Stack = stack
|
||||||
|
stack.Count = 0
|
||||||
|
} else if window.Slots[slotIndex].Stack.IsSameType(stack) && !window.Slots[slotIndex].Stack.IsFull() {
|
||||||
|
addedCount := stack.Count
|
||||||
|
leftOverCount := int32(0)
|
||||||
|
if addedCount > stack.MaxCount {
|
||||||
|
stack.MaxCount = addedCount
|
||||||
|
leftOverCount = stack.Count - addedCount
|
||||||
|
}
|
||||||
|
window.Slots[slotIndex].Stack.Count += addedCount
|
||||||
|
stack.Count = leftOverCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityMetadata struct {
|
||||||
|
//TODO IMPLEMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (metadata *EntityMetadata) add(buf *bytes.Buffer) {
|
||||||
|
//TODO IMPLEMENT
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user