This commit is contained in:
2024-10-26 12:41:37 +02:00
parent 78b2a2f022
commit a6a7a94c20
5 changed files with 2021 additions and 135 deletions

View File

@@ -5,9 +5,12 @@ import (
"bytes"
"crypto/cipher"
"encoding/binary"
"fmt"
"github.com/google/uuid"
"io"
"math"
"net"
"reflect"
"time"
)
@@ -62,9 +65,15 @@ func (abilities *PlayerAbilites) export() (out byte) {
return
}
type Inventory struct {
activeSlot uint8
slots []ItemStack
}
type Player struct {
conn net.Conn
PermissionLevel uint8
state int32
version int32
requestedAddress string
@@ -82,8 +91,11 @@ type Player struct {
Dimension *Dimension
DeathDimension *Dimension
DeathPosition BlockPosition
CursorStack ItemStack
Abilites PlayerAbilites
WindowID uint8
Window *Window
FlyingSpeed float32
FOVModifier float32
MessageIndex int32
@@ -92,6 +104,7 @@ type Player struct {
CombatEnd int64
Position EntityPosition
Rotation EntityRotation
GameMode uint8
PreviousGameMode int8
@@ -109,6 +122,7 @@ type Player struct {
keepAliveSentTimestamp int64
keepAliveReceivedTimestamp int64
completionID int32
pingID uint32
pingSentTimestamp int64
pingReceivedTimestamp int64
@@ -116,7 +130,18 @@ type Player struct {
lastContainerStateID int32
name string
teleportID int32
waitingForTeleportConfirm bool
portalCooldown int32
inventory Inventory
advancementTab string
XP int32
FoodLevel int32
FoodSaturation float32
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 {
ID int32
Name string
ID int32
Name string
Chunks [][][]Chunk
Border WorldBorder
RenderDistance int32
SpawnPosition EntityPosition
SpawnAngle float32
World *World
}
type World struct {
@@ -156,6 +220,9 @@ type World struct {
Flat bool
EnforceSecureChat bool
Age int64
Time int64
Entities []Entity
Players []Player
RegistryDataThings []RegistryData
@@ -165,6 +232,8 @@ type World struct {
ReportDetails map[string]string
ServerLinks []ServerLink
TickRate float32
Maps map[int32]Map
}
@@ -180,6 +249,18 @@ type EntityPosition struct {
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 {
X float32
Y float32
@@ -209,6 +290,17 @@ type ExplosionSound struct {
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) {
addString(buf, explosionSound.Name)
hasFixedRange := explosionSound.FixedRange > 0
@@ -395,13 +487,27 @@ type Explosion struct {
}
type Slot struct {
Stack *ItemStack
Number int16
}
func (s Slot) IsEmpty() bool {
return s.Stack.Count == 0
}
type ItemStack struct {
Count int32
ItemID int32
ComponentsToAdd map[int32]any
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)
if slot.Count > 0 {
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)
if slot.Count > 0 {
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.
type PalettedContainer struct {
BitsPerEntry uint8
@@ -636,14 +1000,66 @@ func (bossBar BossBar) updateFlags(player *Player) error {
}
func (blockPosition *BlockPosition) add(buf *bytes.Buffer) {
var xBuf, yBuf, zBuf []byte
binary.BigEndian.PutUint32(xBuf, uint32(blockPosition.X))
binary.BigEndian.PutUint16(yBuf, uint16(blockPosition.Y))
binary.BigEndian.PutUint32(zBuf, uint32(blockPosition.Z))
// Get the 26-bit values for X and Z
x := blockPosition.X
z := blockPosition.Z
y := blockPosition.Y
buf.Write(xBuf[0:26])
buf.Write(zBuf[0:26])
buf.Write(yBuf[0:12])
// Pack the values into a byte slice
var positionBuf [4]byte // Enough to hold both x and z (4 bytes total)
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) {
@@ -670,6 +1086,11 @@ func (entityRotation *EntityRotation) add(buf *bytes.Buffer) {
addByte(buf, entityRotation.HeadYaw)
}
func (entityRotation *EntityRotation) addBasic(buf *bytes.Buffer) {
addByte(buf, entityRotation.Pitch)
addByte(buf, entityRotation.Yaw)
}
type Velocity struct {
X float32
Y float32
@@ -682,10 +1103,31 @@ func (velocity *Velocity) add(buf *bytes.Buffer) {
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 {
World *World
ID int32
UUID uuid.UUID
Name string
Type int32
Position EntityPosition
Rotation EntityRotation
@@ -696,6 +1138,11 @@ type Entity struct {
PortalCooldown int32
Vehicle *Entity
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
@@ -763,6 +1210,128 @@ func (XPOrb *XPOrb) add(buf *bytes.Buffer) {
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 int32
X int8
@@ -824,9 +1393,9 @@ func (Map *Map) add(buf *bytes.Buffer) {
}
type VillageTrade struct {
In1 Slot
In2 *Slot
Out Slot
In1 ItemStack
In2 *ItemStack
Out ItemStack
Disabled bool
Uses int32
MaxUses int32
@@ -870,6 +1439,48 @@ func (VillagerEntity *VillagerEntity) addOffers(buf *bytes.Buffer, windowID int3
addBool(buf, VillagerEntity.CanRestock)
}
type Window struct {
ID int32
type WindowInteractible interface {
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
}