Merge branch 'pull/62' into 1.16-dev

This commit is contained in:
Tnze
2020-07-09 13:42:44 +08:00
10 changed files with 276 additions and 42 deletions

View File

@ -49,13 +49,16 @@ func NewClient() (c *Client) {
//PlayInfo content player info in server. //PlayInfo content player info in server.
type PlayInfo struct { type PlayInfo struct {
Gamemode int //游戏模式 Gamemode int //游戏模式
Hardcore bool //是否是极限模式 Hardcore bool //是否是极限模式
Dimension int //维度 Dimension int //维度
Difficulty int //难度 Difficulty int //难度
LevelType string //地图类型 // LevelType string //地图类型 1.16删了
ViewDistance int //视距 ViewDistance int //视距
ReducedDebugInfo bool //减少调试信息 ReducedDebugInfo bool //减少调试信息
WorldName string //当前世界的名字
IsDebug bool //调试
IsFlat bool //超平坦世界
// SpawnPosition Position //主世界出生点 // SpawnPosition Position //主世界出生点
} }

View File

@ -9,7 +9,7 @@ import (
type eventBroker struct { type eventBroker struct {
GameStart func() error GameStart func() error
ChatMsg func(msg chat.Message, pos byte) error ChatMsg func(msg chat.Message, pos byte, sender string) error
Disconnect func(reason chat.Message) error Disconnect func(reason chat.Message) error
HealthChange func() error HealthChange func() error
Die func() error Die func() error

View File

@ -274,17 +274,18 @@ func handleSetSlotPacket(c *Client, p pk.Packet) error {
func handleChatMessagePacket(c *Client, p pk.Packet) (err error) { func handleChatMessagePacket(c *Client, p pk.Packet) (err error) {
var ( var (
s chat.Message s chat.Message
pos pk.Byte pos pk.Byte
sender pk.UUID
) )
err = p.Scan(&s, &pos) err = p.Scan(&s, &pos, &sender)
if err != nil { if err != nil {
return err return err
} }
if c.Events.ChatMsg != nil { if c.Events.ChatMsg != nil {
err = c.Events.ChatMsg(s, byte(pos)) err = c.Events.ChatMsg(s, byte(pos), string(sender.Encode()))
} }
return err return err
@ -326,17 +327,25 @@ func handleUpdateHealthPacket(c *Client, p pk.Packet) (err error) {
func handleJoinGamePacket(c *Client, p pk.Packet) error { func handleJoinGamePacket(c *Client, p pk.Packet) error {
var ( var (
eid pk.Int eid pk.Int
gamemode pk.UnsignedByte gamemode pk.UnsignedByte
previousGm pk.UnsignedByte
worldCount pk.VarInt
worldNames pk.Identifier
_ pk.NBT
//dimensionCodec pk.NBT
dimension pk.Int dimension pk.Int
worldName pk.Identifier
hashedSeed pk.Long hashedSeed pk.Long
maxPlayers pk.UnsignedByte maxPlayers pk.UnsignedByte
levelType pk.String
viewDistance pk.VarInt viewDistance pk.VarInt
rdi pk.Boolean // Reduced Debug Info rdi pk.Boolean // Reduced Debug Info
ers pk.Boolean // Enable respawn screen ers pk.Boolean // Enable respawn screen
isDebug pk.Boolean
isFlat pk.Boolean
) )
err := p.Scan(&eid, &gamemode, &dimension, &hashedSeed, &maxPlayers, &levelType, &rdi, &ers) err := p.Scan(&eid, &gamemode, &previousGm, &worldCount, &worldNames, &dimension, &worldName,
&hashedSeed, &maxPlayers, &rdi, &ers, &isDebug, &isFlat)
if err != nil { if err != nil {
return err return err
} }
@ -345,9 +354,12 @@ func handleJoinGamePacket(c *Client, p pk.Packet) error {
c.Gamemode = int(gamemode & 0x7) c.Gamemode = int(gamemode & 0x7)
c.Hardcore = gamemode&0x8 != 0 c.Hardcore = gamemode&0x8 != 0
c.Dimension = int(dimension) c.Dimension = int(dimension)
c.LevelType = string(levelType) c.WorldName = string(worldName)
c.ViewDistance = int(viewDistance) c.ViewDistance = int(viewDistance)
c.ReducedDebugInfo = bool(rdi) c.ReducedDebugInfo = bool(rdi)
c.IsDebug = bool(isDebug)
c.IsFlat = bool(isFlat)
return nil return nil
} }
@ -441,13 +453,14 @@ func handleChunkDataPacket(c *Client, p pk.Packet) error {
var ( var (
X, Z pk.Int X, Z pk.Int
FullChunk pk.Boolean FullChunk pk.Boolean
IgnoreOldData pk.Boolean
PrimaryBitMask pk.VarInt PrimaryBitMask pk.VarInt
Heightmaps struct{} Heightmaps struct{}
Biomes = biomesData{fullChunk: (*bool)(&FullChunk)} Biomes = biomesData{fullChunk: (*bool)(&FullChunk)}
Data chunkData Data chunkData
BlockEntities blockEntities BlockEntities blockEntities
) )
if err := p.Scan(&X, &Z, &FullChunk, &PrimaryBitMask, pk.NBT{V: &Heightmaps}, &Biomes, &Data, &BlockEntities); err != nil { if err := p.Scan(&X, &Z, &FullChunk, &IgnoreOldData, &PrimaryBitMask, pk.NBT{V: &Heightmaps}, &Biomes, &Data, &BlockEntities); err != nil {
return err return err
} }
chunk, err := world.DecodeChunkColumn(int32(PrimaryBitMask), Data) chunk, err := world.DecodeChunkColumn(int32(PrimaryBitMask), Data)

View File

@ -185,6 +185,7 @@ func loginAuth(AsTk, name, UUID string, shareSecret []byte, er encryptionRequest
} }
PostRequest.Header.Set("User-agent", "go-mc") PostRequest.Header.Set("User-agent", "go-mc")
PostRequest.Header.Set("Connection", "keep-alive") PostRequest.Header.Set("Connection", "keep-alive")
PostRequest.Header.Set("Content-Type", "application/json")
resp, err := client.Do(PostRequest) resp, err := client.Do(PostRequest)
if err != nil { if err != nil {
return fmt.Errorf("post fail: %v", err) return fmt.Errorf("post fail: %v", err)

View File

@ -13,7 +13,7 @@ import (
) )
// ProtocolVersion , the protocol version number of minecraft net protocol // ProtocolVersion , the protocol version number of minecraft net protocol
const ProtocolVersion = 578 const ProtocolVersion = 736
// JoinServer connect a Minecraft server for playing the game. // JoinServer connect a Minecraft server for playing the game.
func (c *Client) JoinServer(addr string, port int) (err error) { func (c *Client) JoinServer(addr string, port int) (err error) {

View File

@ -72,7 +72,7 @@ func onSound(name string, category int, x, y, z float64, volume, pitch float32)
return nil return nil
} }
func onChatMsg(c chat.Message, pos byte) error { func onChatMsg(c chat.Message, pos byte, uuid string) error {
log.Println("Chat:", c) log.Println("Chat:", c)
return nil return nil
} }

View File

@ -38,7 +38,7 @@ func onGameStart() error {
return nil //if err isn't nil, HandleGame() will return it. return nil //if err isn't nil, HandleGame() will return it.
} }
func onChatMsg(c chat.Message, pos byte) error { func onChatMsg(c chat.Message, pos byte, uuid string) error {
log.Println("Chat:", c.ClearString()) // output chat message without any format code (like color or bold) log.Println("Chat:", c.ClearString()) // output chat message without any format code (like color or bold)
return nil return nil
} }

153
cmd/simpleServer/main.go Normal file
View File

@ -0,0 +1,153 @@
// Example minecraft 1.15.2 server
package main
import (
"github.com/Tnze/go-mc/bot"
"github.com/Tnze/go-mc/data"
"github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/google/uuid"
"log"
)
const ProtocolVersion = 578
const Threshold = 256
const MaxPlayer = 200
func main() {
l, err := net.ListenMC(":25565")
if err != nil {
log.Fatalf("Listen error: %v", err)
}
for {
conn, err := l.Accept()
if err != nil {
log.Fatalf("Accept error: %v", err)
}
go acceptConn(conn)
}
}
func acceptConn(conn net.Conn) {
defer conn.Close()
// handshake
protocol, intention, err := handshake(conn)
if err != nil {
log.Printf("Handshake error: %v", err)
return
}
switch intention {
default: //unknown error
log.Printf("Unknown handshake intention: %v", intention)
case 1: //for status
acceptListPing(conn)
case 2: //for login
handlePlaying(conn, protocol)
}
}
func handlePlaying(conn net.Conn, protocol int32) {
// login, get player info
info, err := acceptLogin(conn)
if err != nil {
log.Print("Login failed")
return
}
// Write LoginSuccess packet
err = loginSuccess(conn, info.Name, info.UUID)
if err != nil {
log.Print("Login failed on success")
return
}
joinGame(conn)
conn.WritePacket(pk.Marshal(data.PlayerPositionAndLookClientbound,
// https://wiki.vg/Protocol#Player_Position_And_Look_.28clientbound.29
pk.Double(0), pk.Double(0), pk.Double(0), // XYZ
pk.Float(0), pk.Float(0), // Yaw Pitch
pk.Byte(0), // flag
pk.VarInt(0), // TP ID
))
// Just for block this goroutine. Keep the connection
for {
if _, err := conn.ReadPacket(); err != nil {
log.Printf("ReadPacket error: %v", err)
break
}
// KeepAlive packet is not handled, so client might
// exit because of "time out".
}
}
type PlayerInfo struct {
Name string
UUID uuid.UUID
OPLevel int
}
// acceptLogin check player's account
func acceptLogin(conn net.Conn) (info PlayerInfo, err error) {
//login start
var p pk.Packet
p, err = conn.ReadPacket()
if err != nil {
return
}
err = p.Scan((*pk.String)(&info.Name)) //decode username as pk.String
if err != nil {
return
}
//auth
const OnlineMode = false
if OnlineMode {
log.Panic("Not Implement")
} else {
// offline-mode UUID
info.UUID = bot.OfflineUUID(info.Name)
}
return
}
// handshake receive and parse Handshake packet
func handshake(conn net.Conn) (protocol, intention int32, err error) {
var (
Protocol, Intention pk.VarInt
ServerAddress pk.String // ignored
ServerPort pk.UnsignedShort // ignored
)
// receive handshake packet
p, err := conn.ReadPacket()
if err != nil {
return 0, 0, err
}
err = p.Scan(&Protocol, &ServerAddress, &ServerPort, &Intention)
return int32(Protocol), int32(Intention), err
}
// loginSuccess send LoginSuccess packet to client
func loginSuccess(conn net.Conn, name string, uuid uuid.UUID) error {
return conn.WritePacket(pk.Marshal(0x02,
pk.String(uuid.String()), //uuid as string with hyphens
pk.String(name),
))
}
func joinGame(conn net.Conn) error {
return conn.WritePacket(pk.Marshal(data.JoinGame,
pk.Int(0), // EntityID
pk.UnsignedByte(1), // Gamemode
pk.Int(0), // Dimension
pk.Long(0), // HashedSeed
pk.UnsignedByte(MaxPlayer), // MaxPlayer
pk.String("default"), // LevelType
pk.VarInt(15), // View Distance
pk.Boolean(false), // Reduced Debug Info
pk.Boolean(true), // Enable respawn screen
))
}

View File

@ -0,0 +1,64 @@
package main
import (
"encoding/json"
"github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/google/uuid"
"log"
)
func acceptListPing(conn net.Conn) {
for i := 0; i < 2; i++ { // ping or list. Only accept twice
p, err := conn.ReadPacket()
if err != nil {
return
}
switch p.ID {
case 0x00: //List
err = conn.WritePacket(pk.Marshal(0x00, pk.String(listResp())))
case 0x01: //Ping
err = conn.WritePacket(p)
}
if err != nil {
return
}
}
}
type player struct {
Name string `json:"name"`
ID uuid.UUID `json:"id"`
}
// listResp return server status as JSON string
func listResp() string {
var list struct {
Version struct {
Name string `json:"name"`
Protocol int `json:"protocol"`
} `json:"version"`
Players struct {
Max int `json:"max"`
Online int `json:"online"`
Sample []player `json:"sample"`
} `json:"players"`
Description chat.Message `json:"description"`
FavIcon string `json:"favicon,omitempty"`
}
list.Version.Name = "Chat Server"
list.Version.Protocol = ProtocolVersion
list.Players.Max = MaxPlayer
list.Players.Online = 123
list.Players.Sample = []player{} // must init. can't be nil
list.Description = chat.Message{Text: "Powered by go-mc", Color: "blue"}
data, err := json.Marshal(list)
if err != nil {
log.Panic("Marshal JSON for status checking fail")
}
return string(data)
}

View File

@ -4,26 +4,25 @@ package data
const ( const (
SpawnObject int32 = iota //0x00 SpawnObject int32 = iota //0x00
SpawnExperienceOrb SpawnExperienceOrb
SpawnGlobalEntity SpawnLivingEntity
SpawnMob
SpawnPainting SpawnPainting
SpawnPlayer SpawnPlayer
AnimationClientbound EntityAnimationClientbound
Statistics Statistics
AcknowledgePlayerDigging AcknowledgePlayerDigging
BlockBreakAnimation BlockBreakAnimation
UpdateBlockEntity BlockEntityData
BlockAction BlockAction
BlockChange BlockChange
BossBar BossBar
ServerDifficulty ServerDifficulty
ChatMessageClientbound ChatMessageClientbound
MultiBlockChange
MultiBlockChange //0x10 TabComplete //0x10
TabComplete
DeclareCommands DeclareCommands
ConfirmTransaction WindowConfirmationClientbound
CloseWindow CloseWindowClientbound
WindowItems WindowItems
WindowProperty WindowProperty
SetSlot SetSlot
@ -35,9 +34,9 @@ const (
Explosion Explosion
UnloadChunk UnloadChunk
ChangeGameState ChangeGameState
OpenHorseWindow
OpenHorseWindow //0x20 KeepAliveClientbound //0x20
KeepAliveClientbound
ChunkData ChunkData
Effect Effect
Particle Particle
@ -52,9 +51,9 @@ const (
VehicleMoveClientbound VehicleMoveClientbound
OpenBook OpenBook
OpenWindow OpenWindow
OpenSignEditor
OpenSignEditor //0x30 CraftRecipeResponse //0x30
CraftRecipeResponse
PlayerAbilitiesClientbound PlayerAbilitiesClientbound
CombatEvent CombatEvent
PlayerInfo PlayerInfo
@ -69,10 +68,11 @@ const (
SelectAdvancementTab SelectAdvancementTab
WorldBorder WorldBorder
Camera Camera
HeldItemChangeClientbound
HeldItemChangeClientbound //0x40 UpdateViewPosition //0x40
UpdateViewPosition
UpdateViewDistance UpdateViewDistance
SpawnPosition
DisplayScoreboard DisplayScoreboard
EntityMetadata EntityMetadata
AttachEntity AttachEntity
@ -84,11 +84,10 @@ const (
SetPassengers SetPassengers
Teams Teams
UpdateScore UpdateScore
SpawnPosition
TimeUpdate TimeUpdate
Title
Title //0x50 EntitySoundEffect //0x50
EntitySoundEffect
SoundEffect SoundEffect
StopSound StopSound
PlayerListHeaderAndFooter PlayerListHeaderAndFooter
@ -99,7 +98,7 @@ const (
EntityProperties EntityProperties
EntityEffect EntityEffect
DeclareRecipes DeclareRecipes
Tags //0x5C Tags //0x5B
) )
// Serverbound packet IDs // Serverbound packet IDs
@ -119,9 +118,10 @@ const (
EditBook EditBook
QueryEntityNBT QueryEntityNBT
UseEntity UseEntity
KeepAliveServerbound GenerateStructure
LockDifficulty //0x10 KeepAliveServerbound //0x10
LockDifficulty
PlayerPosition PlayerPosition
PlayerPositionAndLookServerbound PlayerPositionAndLookServerbound
PlayerLook PlayerLook
@ -136,9 +136,9 @@ const (
SteerVehicle SteerVehicle
RecipeBookData RecipeBookData
NameItem NameItem
ResourcePackStatus
AdvancementTab //0x20 ResourcePackStatus //0x20
AdvancementTab
SelectTrade SelectTrade
SetBeaconEffect SetBeaconEffect
HeldItemChangeServerbound HeldItemChangeServerbound
@ -151,5 +151,5 @@ const (
AnimationServerbound AnimationServerbound
Spectate Spectate
PlayerBlockPlacement PlayerBlockPlacement
UseItem //0x2D UseItem //0x2E
) )