1426 lines
37 KiB
Go
1426 lines
37 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"github.com/Tnze/go-mc/nbt"
|
|
"github.com/google/uuid"
|
|
"math/rand/v2"
|
|
"time"
|
|
)
|
|
|
|
func (player *Player) sendBundleDelimiter() error {
|
|
return player.sendPacket(0x00, &bytes.Buffer{})
|
|
}
|
|
|
|
func (player *Player) sendSpawnEntity(entity *Entity) error {
|
|
var buf bytes.Buffer
|
|
entity.add(&buf)
|
|
return player.sendPacket(0x01, &buf)
|
|
}
|
|
|
|
func (player *Player) sendSpawnXP(xpOrb XPOrb) error {
|
|
var buf bytes.Buffer
|
|
xpOrb.add(&buf)
|
|
return player.sendPacket(0x02, &buf)
|
|
}
|
|
|
|
func (player *Player) sendEntityAnimation(entity *Entity) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, entity.ID)
|
|
addByte(&buf, entity.Animation)
|
|
return player.sendPacket(0x03, &buf)
|
|
}
|
|
|
|
func (player *Player) sendAwardStatistics() error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, int32(len(player.statistics)))
|
|
for _, stat := range player.statistics {
|
|
addVarint(&buf, stat.CategoryID)
|
|
addVarint(&buf, stat.StatisticID)
|
|
addVarint(&buf, stat.Value)
|
|
}
|
|
return player.sendPacket(0x04, &buf)
|
|
}
|
|
|
|
func (player *Player) sendBlockChangeACK(seqID int32) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, seqID)
|
|
return player.sendPacket(0x05, &buf)
|
|
}
|
|
|
|
func (player *Player) sendBlockDestroyStage(entity *Entity, block *Block) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, entity.ID)
|
|
block.Position.add(&buf)
|
|
addByte(&buf, block.DestroyStage)
|
|
return player.sendPacket(0x06, &buf)
|
|
}
|
|
|
|
func (player *Player) sendBlockEntityData(entity *Entity, block *Block) error {
|
|
var buf bytes.Buffer
|
|
block.Position.add(&buf)
|
|
addVarint(&buf, entity.ID)
|
|
var nbtBuf bytes.Buffer
|
|
nbtWriter := bufio.NewWriter(&nbtBuf)
|
|
nbtEnc := nbt.NewEncoder(nbtWriter)
|
|
for key, val := range block.BlockEntityData {
|
|
err := nbtEnc.Encode(val, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
nbtWriter.Flush()
|
|
buf.ReadFrom(&nbtBuf)
|
|
return player.sendPacket(0x07, &buf)
|
|
}
|
|
|
|
func (player *Player) sendBlockAction(block *Block, actionID uint8, actionParam uint8) error {
|
|
var buf bytes.Buffer
|
|
block.Position.add(&buf)
|
|
addByte(&buf, actionID)
|
|
addByte(&buf, actionParam)
|
|
addVarint(&buf, block.BlockTypeID)
|
|
return player.sendPacket(0x08, &buf)
|
|
}
|
|
|
|
func (player *Player) sendBlockUpdate(block *Block) error {
|
|
var buf bytes.Buffer
|
|
block.Position.add(&buf)
|
|
addVarint(&buf, block.BlockStateID)
|
|
return player.sendPacket(0x09, &buf)
|
|
}
|
|
|
|
func (player *Player) sendDifficulty() error {
|
|
var buf bytes.Buffer
|
|
addByte(&buf, world.Difficulty)
|
|
addBool(&buf, world.DifficultyLocked)
|
|
return player.sendPacket(0x0B, &buf)
|
|
}
|
|
|
|
func (player *Player) sendChunks() error {
|
|
//var buf bytes.Buffer
|
|
var bufFinished bytes.Buffer
|
|
err := player.sendPacket(0x0D, &bytes.Buffer{}) //chunk batch start
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
//count:= int32(len(chunks))
|
|
count := int32(0)
|
|
addVarint(&bufFinished, count)
|
|
err = player.sendPacket(0x0C, &bufFinished) //chunk batch end
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//0x0E is chunk biomes, might implement later
|
|
//0x10 is Command Suggestions Response, might implement later
|
|
//0x11 is Commands, might implement later
|
|
|
|
func (player *Player) sendCloseContainer(windowID uint8) error {
|
|
var buf bytes.Buffer
|
|
addByte(&buf, windowID)
|
|
player.WindowID = 0
|
|
return player.sendPacket(0x12, &buf)
|
|
}
|
|
|
|
func (player *Player) sendContainerContent(window *Window, cursor *ItemStack) error {
|
|
var buf bytes.Buffer
|
|
addByte(&buf, window.ID)
|
|
addVarint(&buf, player.lastContainerStateID)
|
|
player.lastContainerStateID++
|
|
addVarint(&buf, int32(len(window.Slots)))
|
|
for _, slot := range window.Slots {
|
|
slot.Stack.add(&buf)
|
|
}
|
|
cursor.add(&buf)
|
|
return player.sendPacket(0x13, &buf)
|
|
}
|
|
|
|
func (player *Player) sendContainerProperty(windowID uint8, property int16, value int16) error {
|
|
var buf bytes.Buffer
|
|
addByte(&buf, windowID)
|
|
addInt16(&buf, property)
|
|
addInt16(&buf, value)
|
|
return player.sendPacket(0x14, &buf)
|
|
}
|
|
|
|
func (player *Player) sendContainerSlot(windowID uint8, slotIndex int16, slot *ItemStack) error {
|
|
var buf bytes.Buffer
|
|
addByte(&buf, windowID)
|
|
addVarint(&buf, player.lastContainerStateID)
|
|
player.lastContainerStateID++
|
|
addInt16(&buf, slotIndex)
|
|
slot.add(&buf)
|
|
return player.sendPacket(0x15, &buf)
|
|
}
|
|
|
|
//0x16 is cookie, might get implemented cross-state
|
|
|
|
func (player *Player) sendCooldown(itemID int32, cooldownTicks int32) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, itemID)
|
|
addVarint(&buf, cooldownTicks)
|
|
return player.sendPacket(0x17, &buf)
|
|
}
|
|
|
|
func (player *Player) sendChatSuggestions(action int32, entries []string) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, action)
|
|
addVarint(&buf, int32(len(entries)))
|
|
for _, entry := range entries {
|
|
addString(&buf, entry)
|
|
}
|
|
return player.sendPacket(0x18, &buf)
|
|
}
|
|
|
|
//0x19 is plugin message, should be implemented globally
|
|
|
|
func (player *Player) sendDamageEvent(damagedEntity *Entity, damageType int32, sourceEntity *Entity, sourceDirectEntity *Entity) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, damagedEntity.ID)
|
|
addVarint(&buf, damageType)
|
|
addVarint(&buf, sourceEntity.ID)
|
|
if sourceDirectEntity != nil {
|
|
addVarint(&buf, sourceDirectEntity.ID)
|
|
} else {
|
|
addVarint(&buf, sourceEntity.ID)
|
|
}
|
|
addBool(&buf, true)
|
|
sourceEntity.Position.add(&buf)
|
|
return player.sendPacket(0x1A, &buf)
|
|
}
|
|
|
|
func (player *Player) sendDebugSample(sampleType int32, sample []int64) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, int32(len(sample)))
|
|
for _, val := range sample {
|
|
addInt64(&buf, val)
|
|
}
|
|
addVarint(&buf, sampleType)
|
|
return player.sendPacket(0x1B, &buf)
|
|
}
|
|
|
|
func (player *Player) deleteMessage(messageID int32, signature []byte) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, messageID)
|
|
if messageID == 0 {
|
|
for _, sigPiece := range signature {
|
|
addByte(&buf, sigPiece)
|
|
}
|
|
}
|
|
return player.sendPacket(0x1C, &buf)
|
|
}
|
|
|
|
//0x1D is disconnect, should be implemented globally
|
|
|
|
func (player *Player) sendDisguisedChatMessage(message TextComponent, typeID int32, senderName TextComponent, targetName *TextComponent) error {
|
|
var buf bytes.Buffer
|
|
addTextComponent(&buf, message)
|
|
addVarint(&buf, typeID)
|
|
addTextComponent(&buf, senderName)
|
|
hasTargetName := targetName != nil
|
|
addBool(&buf, hasTargetName)
|
|
if hasTargetName {
|
|
addTextComponent(&buf, *targetName)
|
|
}
|
|
return player.sendPacket(0x1E, &buf)
|
|
}
|
|
|
|
func (player *Player) sendEntityEvent(entity *Entity) error {
|
|
var buf bytes.Buffer
|
|
addInt32(&buf, entity.ID)
|
|
addByte(&buf, byte(entity.status))
|
|
return player.sendPacket(0x1F, &buf)
|
|
}
|
|
|
|
func (player *Player) sendExplosion(explosion *Explosion) error {
|
|
var buf bytes.Buffer
|
|
explosion.Position.add(&buf)
|
|
addFloat32(&buf, explosion.Strength)
|
|
addVarint(&buf, int32(len(explosion.AffectedBlockRecords)))
|
|
for _, affectedBlockRecord := range explosion.AffectedBlockRecords {
|
|
affectedBlockRecord.add(&buf)
|
|
}
|
|
explosion.PlayerMotion.add(&buf)
|
|
addVarint(&buf, explosion.BlockInteraction)
|
|
explosion.SmallExplosionParticle.addExplosion(&buf)
|
|
explosion.LargeExplosionParticle.addExplosion(&buf)
|
|
explosion.ExplosionSound.add(&buf)
|
|
return player.sendPacket(0x20, &buf)
|
|
}
|
|
|
|
func (player *Player) sendUnloadChunk(chunk *Chunk) error {
|
|
var buf bytes.Buffer
|
|
addInt32(&buf, chunk.Z)
|
|
addInt32(&buf, chunk.X)
|
|
return player.sendPacket(0x21, &buf)
|
|
}
|
|
|
|
func (player *Player) sendGameEvent(event uint8, value float32) error {
|
|
var buf bytes.Buffer
|
|
addByte(&buf, event)
|
|
addFloat32(&buf, value)
|
|
return player.sendPacket(0x22, &buf)
|
|
}
|
|
|
|
func (player *Player) sendHorseScreen(windowID uint8, slotCount int32, entityID int32) error {
|
|
var buf bytes.Buffer
|
|
addByte(&buf, windowID)
|
|
addVarint(&buf, slotCount)
|
|
addInt32(&buf, entityID)
|
|
return player.sendPacket(0x23, &buf)
|
|
}
|
|
|
|
// Example usage in sendHurtAnimation
|
|
func (player *Player) sendHurtAnimation(attacker *Entity) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, attacker.ID)
|
|
|
|
// Calculate yaw based on attacker and player positions
|
|
attackYaw := calculateYaw(attacker.Position, player.Position)
|
|
|
|
addFloat32(&buf, attackYaw)
|
|
|
|
return player.sendPacket(0x24, &buf)
|
|
}
|
|
|
|
func (player *Player) sendWorldBorder() error {
|
|
var buf bytes.Buffer
|
|
addFloat64(&buf, player.Dimension.Border.X)
|
|
addFloat64(&buf, player.Dimension.Border.Z)
|
|
addFloat64(&buf, player.Dimension.Border.OldDiameter)
|
|
addFloat64(&buf, player.Dimension.Border.NewDiameter)
|
|
addVarlong(&buf, player.Dimension.Border.Speed)
|
|
addVarint(&buf, player.Dimension.Border.PortalTeleportBoundary)
|
|
addVarint(&buf, player.Dimension.Border.WarningBlocks)
|
|
addVarint(&buf, player.Dimension.Border.WarningTime)
|
|
return player.sendPacket(0x25, &buf)
|
|
}
|
|
|
|
//0x26 is keepalive
|
|
//0x27 is chunk and light //TODO LATER
|
|
|
|
func (player *Player) sendWorldEvent(event int32, position BlockPosition, data int32, disableRelativeVolume bool) error {
|
|
var buf bytes.Buffer
|
|
addInt32(&buf, event)
|
|
position.add(&buf)
|
|
addInt32(&buf, data)
|
|
addBool(&buf, disableRelativeVolume)
|
|
return player.sendPacket(0x28, &buf)
|
|
}
|
|
|
|
func (player *Player) sendParticle(particle *Particle, count int32) error {
|
|
var buf bytes.Buffer
|
|
particle.addPacket(&buf, count)
|
|
return player.sendPacket(0x29, &buf)
|
|
}
|
|
|
|
//0x2A is Update light levels //TODO LATER
|
|
|
|
func (player *Player) sendPlayStart() error {
|
|
var buf bytes.Buffer
|
|
addInt32(&buf, player.Entity.ID)
|
|
addBool(&buf, world.Hardcore)
|
|
addVarint(&buf, int32(len(world.Dimensions)))
|
|
for _, dimension := range world.Dimensions {
|
|
addString(&buf, dimension.Name)
|
|
}
|
|
addVarint(&buf, maxPlayers)
|
|
addVarint(&buf, world.ViewDistance)
|
|
addVarint(&buf, world.SimulationDistance)
|
|
addBool(&buf, world.ReducedDebugInfo)
|
|
addBool(&buf, world.EnableRespawnScreen)
|
|
addBool(&buf, world.DoLimitedCrafting)
|
|
addVarint(&buf, player.Dimension.ID)
|
|
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])
|
|
}
|
|
addByte(&buf, player.GameMode)
|
|
addByte(&buf, byte(player.PreviousGameMode))
|
|
addBool(&buf, world.Debug)
|
|
addBool(&buf, world.Flat)
|
|
hasDeathLocation := player.DeathDimension != nil
|
|
addBool(&buf, hasDeathLocation)
|
|
if hasDeathLocation {
|
|
addString(&buf, player.DeathDimension.Name)
|
|
player.DeathPosition.add(&buf)
|
|
}
|
|
addVarint(&buf, player.Entity.PortalCooldown)
|
|
addBool(&buf, world.EnforceSecureChat)
|
|
return player.sendPacket(0x2B, &buf)
|
|
|
|
}
|
|
|
|
func (player *Player) sendMapItemData(mapId int32) error {
|
|
var buf bytes.Buffer
|
|
mapEntry, found := world.Maps[mapId]
|
|
if found {
|
|
mapEntry.add(&buf)
|
|
}
|
|
return player.sendPacket(0x2C, &buf)
|
|
}
|
|
|
|
func (player *Player) sendMerchantOffers(windowID int32, entity *VillagerEntity) error {
|
|
var buf bytes.Buffer
|
|
entity.addOffers(&buf, windowID)
|
|
return player.sendPacket(0x2D, &buf)
|
|
}
|
|
|
|
func (player *Player) sendEntityPositionUpdate(entity *Entity, deltas []int16) error {
|
|
if len(deltas) != 3 {
|
|
return errors.New("invalid deltas")
|
|
}
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, entity.ID)
|
|
addInt16(&buf, deltas[0])
|
|
addInt16(&buf, deltas[1])
|
|
addInt16(&buf, deltas[2])
|
|
addBool(&buf, entity.onGround())
|
|
return player.sendPacket(0x2E, &buf)
|
|
}
|
|
|
|
func (player *Player) sendEntityPositionRotationUpdate(entity *Entity, deltas []int16) error {
|
|
if len(deltas) != 3 {
|
|
return errors.New("invalid deltas")
|
|
}
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, entity.ID)
|
|
addInt16(&buf, deltas[0])
|
|
addInt16(&buf, deltas[1])
|
|
addInt16(&buf, deltas[2])
|
|
addByte(&buf, entity.Rotation.Yaw)
|
|
addByte(&buf, entity.Rotation.Pitch)
|
|
addBool(&buf, entity.onGround())
|
|
return player.sendPacket(0x2F, &buf)
|
|
}
|
|
|
|
func (player *Player) sendEntityRotationUpdate(entity *Entity) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, entity.ID)
|
|
addByte(&buf, entity.Rotation.Yaw)
|
|
addByte(&buf, entity.Rotation.Pitch)
|
|
addBool(&buf, entity.onGround())
|
|
return player.sendPacket(0x30, &buf)
|
|
}
|
|
|
|
func (player *Player) sendVehicleMove() error {
|
|
if player.Entity.Vehicle != nil {
|
|
var buf bytes.Buffer
|
|
player.Entity.Vehicle.Position.add(&buf)
|
|
addFloat32(&buf, float32(player.Entity.Vehicle.Rotation.Yaw))
|
|
addFloat32(&buf, float32(player.Entity.Vehicle.Rotation.Pitch))
|
|
return player.sendPacket(0x31, &buf)
|
|
}
|
|
return errors.New("no vehicle")
|
|
}
|
|
|
|
func (player *Player) sendBookOpen(isOffHand bool) error {
|
|
var buf bytes.Buffer
|
|
hand := int32(0)
|
|
if isOffHand {
|
|
hand = 1
|
|
}
|
|
addVarint(&buf, hand)
|
|
return player.sendPacket(0x32, &buf)
|
|
}
|
|
|
|
func (player *Player) sendOpenScreen(windowID int32, window *Window, title TextComponent) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, windowID)
|
|
addVarint(&buf, window.Type)
|
|
addTextComponent(&buf, title)
|
|
return player.sendPacket(0x33, &buf)
|
|
}
|
|
|
|
func (player *Player) sendOpenSignEditor(blockPosition BlockPosition, front bool) error {
|
|
var buf bytes.Buffer
|
|
blockPosition.add(&buf)
|
|
addBool(&buf, front)
|
|
return player.sendPacket(0x34, &buf)
|
|
}
|
|
|
|
//0x35 is ping to client
|
|
//0x36 is pong
|
|
|
|
func (player *Player) sendGhostRecipe(windowID uint8, recipe string) error {
|
|
var buf bytes.Buffer
|
|
addByte(&buf, windowID)
|
|
addString(&buf, recipe)
|
|
return player.sendPacket(0x37, &buf)
|
|
}
|
|
|
|
func (player *Player) sendAbilities() error {
|
|
var buf bytes.Buffer
|
|
addByte(&buf, player.Abilites.export())
|
|
addFloat32(&buf, player.FlyingSpeed)
|
|
addFloat32(&buf, player.FOVModifier)
|
|
return player.sendPacket(0x38, &buf)
|
|
}
|
|
|
|
func (player *Player) sendChatMessage(sender *Player, message string) error {
|
|
var buf bytes.Buffer
|
|
//TODO IMPLEMENT
|
|
addUUID(&buf, sender.uuid)
|
|
addVarint(&buf, sender.MessageIndex)
|
|
addBool(&buf, false)
|
|
addString(&buf, message)
|
|
addInt64(&buf, time.Now().Unix())
|
|
addInt64(&buf, rand.Int64())
|
|
//not implementing secure chat
|
|
return player.sendPacket(0x39, &buf)
|
|
}
|
|
|
|
func (player *Player) sendDeath(message TextComponent) error {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, player.Entity.ID)
|
|
addTextComponent(&buf, message)
|
|
return player.sendPacket(0x3C, &buf)
|
|
}
|
|
|
|
func (player *Player) sendRemoveFromTab(playersToRemove []uuid.UUID) error {
|
|
if len(playersToRemove) > 0 {
|
|
var buf bytes.Buffer
|
|
addVarint(&buf, int32(len(playersToRemove)))
|
|
for _, playerToRemove := range playersToRemove {
|
|
addUUID(&buf, playerToRemove)
|
|
}
|
|
return player.sendPacket(0x3D, &buf)
|
|
}
|
|
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
|