Files
go-mc/bot/ingame.go
2020-09-14 20:03:57 -07:00

530 lines
12 KiB
Go

package bot
import (
"bytes"
"fmt"
"github.com/google/uuid"
"github.com/Tnze/go-mc/bot/world"
"github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/data"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/Tnze/go-mc/net/ptypes"
)
// //GetPosition return the player's position
// func (p *Player) GetPosition() (x, y, z float64) {
// return p.X, p.Y, p.Z
// }
// //GetBlockPos return the position of the Block at player's feet
// func (p *Player) GetBlockPos() (x, y, z int) {
// return int(math.Floor(p.X)), int(math.Floor(p.Y)), int(math.Floor(p.Z))
// }
// HandleGame receive server packet and response them correctly.
// Note that HandleGame will block if you don't receive from Events.
func (c *Client) HandleGame() error {
for {
select {
case task := <-c.Delegate:
if err := task(); err != nil {
return err
}
default:
//Read packets
p, err := c.conn.ReadPacket()
if err != nil {
return fmt.Errorf("bot: read packet fail: %w", err)
}
//handle packets
disconnect, err := c.handlePacket(p)
if err != nil {
return fmt.Errorf("handle packet 0x%X error: %w", p.ID, err)
}
if disconnect {
return nil
}
}
}
}
func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) {
if c.Events.ReceivePacket != nil {
pass, err := c.Events.ReceivePacket(p)
if err != nil {
return false, err
}
if pass {
return false, nil
}
}
switch data.PktID(p.ID) {
case data.Login:
err = handleJoinGamePacket(c, p)
case data.CustomPayloadClientbound:
err = handlePluginPacket(c, p)
case data.Difficulty:
err = handleServerDifficultyPacket(c, p)
case data.SpawnPosition:
err = handleSpawnPositionPacket(c, p)
if err2 := c.Events.updateSeenPackets(seenSpawnPos); err == nil {
err = err2
}
case data.AbilitiesClientbound:
err = handlePlayerAbilitiesPacket(c, p)
case data.HeldItemSlotClientbound:
err = handleHeldItemPacket(c, p)
case data.UpdateLight:
err = c.Events.updateSeenPackets(seenUpdateLight)
case data.MapChunk:
err = handleChunkDataPacket(c, p)
case data.PositionClientbound:
err = handlePlayerPositionAndLookPacket(c, p)
sendPlayerPositionAndLookPacket(c) // to confirm the position
if err2 := c.Events.updateSeenPackets(seenPlayerPositionAndLook); err == nil {
err = err2
}
case data.DeclareRecipes:
// handleDeclareRecipesPacket(g, reader)
case data.KeepAliveClientbound:
err = handleKeepAlivePacket(c, p)
case data.Entity:
//handleEntityPacket(g, reader)
case data.NamedEntitySpawn:
// err = handleSpawnPlayerPacket(g, reader)
case data.WindowItems:
err = handleWindowItemsPacket(c, p)
case data.UpdateHealth:
err = handleUpdateHealthPacket(c, p)
case data.ChatClientbound:
err = handleChatMessagePacket(c, p)
case data.BlockChange:
err = handleBlockChangePacket(c, p)
case data.MultiBlockChange:
err = handleMultiBlockChangePacket(c, p)
case data.KickDisconnect:
err = handleDisconnectPacket(c, p)
disconnect = true
case data.SetSlot:
err = handleSetSlotPacket(c, p)
case data.SoundEffect:
err = handleSoundEffect(c, p)
case data.NamedSoundEffect:
err = handleNamedSoundEffect(c, p)
case data.Experience:
err = handleSetExperience(c, p)
default:
// fmt.Printf("ignore pack id %X\n", p.ID)
}
return
}
func handleSoundEffect(c *Client, p pk.Packet) error {
var s ptypes.SoundEffect
if err := s.Decode(p); err != nil {
return err
}
if c.Events.SoundPlay != nil {
return c.Events.SoundPlay(
data.SoundNames[s.Sound], int(s.Category),
float64(s.X)/8, float64(s.Y)/8, float64(s.Z)/8,
float32(s.Volume), float32(s.Pitch))
}
return nil
}
func handleNamedSoundEffect(c *Client, p pk.Packet) error {
var s ptypes.NamedSoundEffect
if err := s.Decode(p); err != nil {
return err
}
if c.Events.SoundPlay != nil {
return c.Events.SoundPlay(
string(s.Sound), int(s.Category),
float64(s.X)/8, float64(s.Y)/8, float64(s.Z)/8,
float32(s.Volume), float32(s.Pitch))
}
return nil
}
func handleDisconnectPacket(c *Client, p pk.Packet) error {
var reason chat.Message
err := p.Scan(&reason)
if err != nil {
return err
}
if c.Events.Disconnect != nil {
return c.Events.Disconnect(reason)
}
return nil
}
func handleSetSlotPacket(c *Client, p pk.Packet) error {
if c.Events.WindowsItemChange == nil {
return nil
}
var pkt ptypes.SetSlot
if err := pkt.Decode(p); err != nil {
return err
}
return c.Events.WindowsItemChange(byte(pkt.WindowID), int(pkt.Slot), pkt.SlotData)
}
func handleMultiBlockChangePacket(c *Client, p pk.Packet) error {
if !c.settings.ReceiveMap {
return nil
}
r := bytes.NewReader(p.Data)
var (
loc pk.Long
dontTrustEdges pk.Boolean
sz pk.VarInt
)
if err := loc.Decode(r); err != nil {
return fmt.Errorf("packed location: %v", err)
}
if err := dontTrustEdges.Decode(r); err != nil {
return fmt.Errorf("unknown 1: %v", err)
}
if err := sz.Decode(r); err != nil {
return fmt.Errorf("array size: %v", err)
}
packedBlocks := make([]pk.VarLong, int(sz))
for i := 0; i < int(sz); i++ {
if err := packedBlocks[i].Decode(r); err != nil {
return fmt.Errorf("block[%d]: %v", i, err)
}
}
x, z, y := int((loc>>42)&((1<<22)-1)),
int((loc>>20)&((1<<22)-1)),
int(loc&((1<<20)-1))
// Apply transform into negative (these numbers are signed)
if x >= 1<<21 {
x -= 1 << 22
}
if z >= 1<<21 {
z -= 1 << 22
}
c.Wd.MultiBlockUpdate(world.ChunkLoc{X: x, Z: z}, y, packedBlocks)
return nil
}
func handleBlockChangePacket(c *Client, p pk.Packet) error {
if !c.settings.ReceiveMap {
return nil
}
var (
pos pk.Position
bID pk.VarInt
)
if err := p.Scan(&pos, &bID); err != nil {
return err
}
c.Wd.UnaryBlockUpdate(pos, world.BlockStatus(bID))
return nil
}
func handleChatMessagePacket(c *Client, p pk.Packet) (err error) {
var msg ptypes.ChatMessageClientbound
if err := msg.Decode(p); err != nil {
return err
}
if c.Events.ChatMsg != nil {
return c.Events.ChatMsg(msg.S, byte(msg.Pos), uuid.UUID(msg.Sender))
}
return nil
}
func handleUpdateHealthPacket(c *Client, p pk.Packet) error {
var pkt ptypes.UpdateHealth
if err := pkt.Decode(p); err != nil {
return err
}
c.Health = float32(pkt.Health)
c.Food = int32(pkt.Food)
c.FoodSaturation = float32(pkt.FoodSaturation)
if c.Events.HealthChange != nil {
if err := c.Events.HealthChange(); err != nil {
return err
}
}
if c.Health < 1 { //player is dead
sendPlayerPositionAndLookPacket(c)
if c.Events.Die != nil {
if err := c.Events.Die(); err != nil {
return err
}
}
}
return nil
}
func handleJoinGamePacket(c *Client, p pk.Packet) error {
var pkt ptypes.JoinGame
if err := pkt.Decode(p); err != nil {
return err
}
c.EntityID = int(pkt.PlayerEntity)
c.Gamemode = int(pkt.Gamemode & 0x7)
c.Hardcore = pkt.Gamemode&0x8 != 0
c.Dimension = int(pkt.Dimension)
c.WorldName = string(pkt.WorldName)
c.ViewDistance = int(pkt.ViewDistance)
c.ReducedDebugInfo = bool(pkt.RDI)
c.IsDebug = bool(pkt.IsDebug)
c.IsFlat = bool(pkt.IsFlat)
if c.Events.GameStart != nil {
if err := c.Events.GameStart(); err != nil {
return err
}
}
c.conn.WritePacket(
//PluginMessage packet (serverbound) - sending minecraft brand.
pk.Marshal(
data.CustomPayloadServerbound,
pk.Identifier("minecraft:brand"),
pk.String(c.settings.Brand),
),
)
if err := c.Events.updateSeenPackets(seenJoinGame); err != nil {
return err
}
return nil
}
func handlePluginPacket(c *Client, p pk.Packet) error {
var msg ptypes.PluginMessage
if err := msg.Decode(p); err != nil {
return err
}
switch msg.Channel {
case "minecraft:brand":
var brandRaw pk.String
if err := brandRaw.Decode(bytes.NewReader(msg.Data)); err != nil {
return err
}
c.ServInfo.Brand = string(brandRaw)
}
if c.Events.PluginMessage != nil {
return c.Events.PluginMessage(string(msg.Channel), []byte(msg.Data))
}
return nil
}
func handleServerDifficultyPacket(c *Client, p pk.Packet) error {
var difficulty pk.Byte
if err := p.Scan(&difficulty); err != nil {
return err
}
c.Difficulty = int(difficulty)
if c.Events.ServerDifficultyChange != nil {
if err := c.Events.ServerDifficultyChange(c.Difficulty); err != nil {
return err
}
}
return c.Events.updateSeenPackets(seenServerDifficulty)
}
func handleSpawnPositionPacket(c *Client, p pk.Packet) error {
var pos pk.Position
err := p.Scan(&pos)
if err != nil {
return err
}
c.SpawnPosition.X, c.SpawnPosition.Y, c.SpawnPosition.Z =
pos.X, pos.Y, pos.Z
return nil
}
func handlePlayerAbilitiesPacket(c *Client, p pk.Packet) error {
var (
flags pk.Byte
flySpeed pk.Float
viewMod pk.Float
)
err := p.Scan(&flags, &flySpeed, &viewMod)
if err != nil {
return err
}
c.abilities.Flags = int8(flags)
c.abilities.FlyingSpeed = float32(flySpeed)
c.abilities.FieldofViewModifier = float32(viewMod)
c.conn.WritePacket(
//ClientSettings packet (serverbound)
pk.Marshal(
data.Settings,
pk.String(c.settings.Locale),
pk.Byte(c.settings.ViewDistance),
pk.VarInt(c.settings.ChatMode),
pk.Boolean(c.settings.ChatColors),
pk.UnsignedByte(c.settings.DisplayedSkinParts),
pk.VarInt(c.settings.MainHand),
),
)
return c.Events.updateSeenPackets(seenPlayerAbilities)
}
func handleHeldItemPacket(c *Client, p pk.Packet) error {
var hi pk.Byte
if err := p.Scan(&hi); err != nil {
return err
}
c.HeldItem = int(hi)
if c.Events.HeldItemChange != nil {
return c.Events.HeldItemChange(c.HeldItem)
}
return nil
}
func handleChunkDataPacket(c *Client, p pk.Packet) error {
if err := c.Events.updateSeenPackets(seenChunkData); err != nil {
return err
}
if !c.settings.ReceiveMap {
return nil
}
var pkt ptypes.ChunkData
if err := pkt.Decode(p); err != nil {
return err
}
chunk, err := world.DecodeChunkColumn(int32(pkt.PrimaryBitMask), pkt.Data)
if err != nil {
return fmt.Errorf("decode chunk column: %w", err)
}
c.Wd.LoadChunk(int(pkt.X), int(pkt.Z), chunk)
return nil
}
func handlePlayerPositionAndLookPacket(c *Client, p pk.Packet) error {
var pkt ptypes.PositionAndLookClientbound
if err := pkt.Decode(p); err != nil {
return err
}
if pkt.RelativeX() {
c.X = float64(pkt.X)
} else {
c.X += float64(pkt.X)
}
if pkt.RelativeY() {
c.Y = float64(pkt.Y)
} else {
c.Y += float64(pkt.Y)
}
if pkt.RelativeZ() {
c.Z = float64(pkt.Z)
} else {
c.Z += float64(pkt.Z)
}
if pkt.RelativeYaw() {
c.Yaw = float32(pkt.Yaw)
} else {
c.Yaw += float32(pkt.Yaw)
}
if pkt.RelativePitch() {
c.Pitch = float32(pkt.Pitch)
} else {
c.Pitch += float32(pkt.Pitch)
}
//Confirm
return c.conn.WritePacket(pk.Marshal(
data.TeleportConfirm,
pk.VarInt(pkt.TeleportID),
))
}
func handleKeepAlivePacket(c *Client, p pk.Packet) error {
var KeepAliveID pk.Long
if err := p.Scan(&KeepAliveID); err != nil {
return err
}
//Response
return c.conn.WritePacket(pk.Marshal(
data.KeepAliveServerbound,
KeepAliveID,
))
}
func handleWindowItemsPacket(c *Client, p pk.Packet) error {
var pkt ptypes.WindowItems
if err := pkt.Decode(p); err != nil {
return err
}
if pkt.WindowID == 0 { // Window ID 0 is the players' inventory.
if err := c.Events.updateSeenPackets(seenPlayerInventory); err != nil {
return err
}
}
if c.Events.WindowsItem != nil {
return c.Events.WindowsItem(byte(pkt.WindowID), pkt.Slots)
}
return nil
}
func handleSetExperience(c *Client, p pk.Packet) (err error) {
var (
bar pk.Float
level pk.VarInt
total pk.VarInt
)
if err := p.Scan(&bar, &level, &total); err != nil {
return err
}
c.Level = int32(level)
if c.Events.ExperienceChange != nil {
return c.Events.ExperienceChange(float32(bar), int32(level), int32(total))
}
return nil
}
func sendPlayerPositionAndLookPacket(c *Client) {
c.conn.WritePacket(pk.Marshal(
data.PositionLook,
pk.Double(c.X),
pk.Double(c.Y),
pk.Double(c.Z),
pk.Float(c.Yaw),
pk.Float(c.Pitch),
pk.Boolean(c.OnGround),
))
}