Major update to implement basic collision
This commit is contained in:
@ -1,10 +1,15 @@
|
|||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/bot/phy"
|
||||||
"github.com/Tnze/go-mc/bot/world"
|
"github.com/Tnze/go-mc/bot/world"
|
||||||
"github.com/Tnze/go-mc/bot/world/entity"
|
"github.com/Tnze/go-mc/bot/world/entity"
|
||||||
"github.com/Tnze/go-mc/bot/world/entity/player"
|
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||||
"github.com/Tnze/go-mc/net"
|
"github.com/Tnze/go-mc/net"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is used to access Minecraft server
|
// Client is used to access Minecraft server
|
||||||
@ -17,12 +22,26 @@ type Client struct {
|
|||||||
ServInfo
|
ServInfo
|
||||||
abilities PlayerAbilities
|
abilities PlayerAbilities
|
||||||
settings Settings
|
settings Settings
|
||||||
|
|
||||||
Wd world.World //the map data
|
Wd world.World //the map data
|
||||||
|
Physics phy.State
|
||||||
|
lastPosTx time.Time
|
||||||
|
|
||||||
// Delegate allows you push a function to let HandleGame run.
|
// Delegate allows you push a function to let HandleGame run.
|
||||||
// Do not send at the same goroutine!
|
// Do not send at the same goroutine!
|
||||||
Delegate chan func() error
|
Delegate chan func() error
|
||||||
Events eventBroker
|
Events eventBroker
|
||||||
|
|
||||||
|
closing chan struct{}
|
||||||
|
inbound chan pk.Packet
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
close(c.closing)
|
||||||
|
err := c.disconnect()
|
||||||
|
c.wg.Wait()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient init and return a new Client.
|
// NewClient init and return a new Client.
|
||||||
@ -32,20 +51,18 @@ type Client struct {
|
|||||||
//
|
//
|
||||||
// For online-mode, you need login your Mojang account
|
// For online-mode, you need login your Mojang account
|
||||||
// and load your Name, UUID and AccessToken to client.
|
// and load your Name, UUID and AccessToken to client.
|
||||||
func NewClient() (c *Client) {
|
func NewClient() *Client {
|
||||||
c = new(Client)
|
return &Client{
|
||||||
|
settings: DefaultSettings,
|
||||||
//init Client
|
Auth: Auth{Name: "Steve"},
|
||||||
c.settings = DefaultSettings
|
Delegate: make(chan func() error),
|
||||||
c.Name = "Steve"
|
Wd: world.World{
|
||||||
c.Delegate = make(chan func() error)
|
Entities: make(map[int32]entity.Entity),
|
||||||
|
Chunks: make(map[world.ChunkLoc]*world.Chunk),
|
||||||
c.Wd = world.World{
|
},
|
||||||
Entities: make(map[int32]entity.Entity),
|
closing: make(chan struct{}),
|
||||||
Chunks: make(map[world.ChunkLoc]*world.Chunk),
|
inbound: make(chan pk.Packet, 5),
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//PlayInfo content player info in server.
|
//PlayInfo content player info in server.
|
||||||
|
@ -2,6 +2,7 @@ package bot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Tnze/go-mc/bot/world/entity"
|
"github.com/Tnze/go-mc/bot/world/entity"
|
||||||
|
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
@ -64,6 +65,9 @@ type eventBroker struct {
|
|||||||
// things.
|
// things.
|
||||||
GameReady func() error
|
GameReady func() error
|
||||||
|
|
||||||
|
// PositionChange is called whenever the player position is updated.
|
||||||
|
PositionChange func(pos player.Pos) error
|
||||||
|
|
||||||
// ReceivePacket will be called when new packets arrive.
|
// ReceivePacket will be called when new packets arrive.
|
||||||
// The default handler will run only if pass == false.
|
// The default handler will run only if pass == false.
|
||||||
ReceivePacket func(p pk.Packet) (pass bool, err error)
|
ReceivePacket func(p pk.Packet) (pass bool, err error)
|
||||||
|
159
bot/ingame.go
159
bot/ingame.go
@ -3,44 +3,101 @@ package bot
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/bot/world"
|
"github.com/Tnze/go-mc/bot/world"
|
||||||
|
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
"github.com/Tnze/go-mc/data"
|
"github.com/Tnze/go-mc/data"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
"github.com/Tnze/go-mc/net/ptypes"
|
"github.com/Tnze/go-mc/net/ptypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// //GetPosition return the player's position
|
func (c *Client) updateServerPos(pos player.Pos) error {
|
||||||
// func (p *Player) GetPosition() (x, y, z float64) {
|
prev := c.Player.Pos
|
||||||
// return p.X, p.Y, p.Z
|
c.Player.Pos = pos
|
||||||
// }
|
|
||||||
|
|
||||||
// //GetBlockPos return the position of the Block at player's feet
|
switch {
|
||||||
// func (p *Player) GetBlockPos() (x, y, z int) {
|
case !prev.LookEqual(pos) && !prev.PosEqual(pos):
|
||||||
// return int(math.Floor(p.X)), int(math.Floor(p.Y)), int(math.Floor(p.Z))
|
sendPlayerPositionAndLookPacket(c)
|
||||||
// }
|
case !prev.PosEqual(pos):
|
||||||
|
sendPlayerPositionPacket(c)
|
||||||
|
case !prev.LookEqual(pos):
|
||||||
|
sendPlayerLookPacket(c)
|
||||||
|
case prev.OnGround != pos.OnGround:
|
||||||
|
c.conn.WritePacket(
|
||||||
|
//ClientSettings packet (serverbound)
|
||||||
|
pk.Marshal(
|
||||||
|
data.Flying,
|
||||||
|
pk.Boolean(pos.OnGround),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case time.Now().Add(-time.Second).After(c.lastPosTx):
|
||||||
|
c.lastPosTx = time.Now()
|
||||||
|
sendPlayerPositionPacket(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Events.PositionChange != nil && !prev.Equal(pos) {
|
||||||
|
if err := c.Events.PositionChange(pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// HandleGame receive server packet and response them correctly.
|
// HandleGame receive server packet and response them correctly.
|
||||||
// Note that HandleGame will block if you don't receive from Events.
|
// Note that HandleGame will block if you don't receive from Events.
|
||||||
func (c *Client) HandleGame() error {
|
func (c *Client) HandleGame() error {
|
||||||
|
c.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer c.wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.closing:
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
//Read packets
|
||||||
|
p, err := c.conn.ReadPacket()
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*net.OpError); ok && e.Err.Error() != "use of closed network connection" {
|
||||||
|
fmt.Fprintf(os.Stderr, "ReadPacket error: %v\n", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.inbound <- p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cTick := time.NewTicker(time.Second / 10 / 2)
|
||||||
|
defer cTick.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case task := <-c.Delegate:
|
case <-c.closing:
|
||||||
if err := task(); err != nil {
|
return http.ErrServerClosed
|
||||||
|
case <-cTick.C:
|
||||||
|
if err := c.Physics.Tick(&c.Wd); err != nil {
|
||||||
|
c.disconnect()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
c.updateServerPos(c.Physics.Position())
|
||||||
//Read packets
|
|
||||||
p, err := c.conn.ReadPacket()
|
case task := <-c.Delegate:
|
||||||
if err != nil {
|
if err := task(); err != nil {
|
||||||
return fmt.Errorf("bot: read packet fail: %w", err)
|
c.disconnect()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
case p := <-c.inbound:
|
||||||
//handle packets
|
//handle packets
|
||||||
disconnect, err := c.handlePacket(p)
|
disconnect, err := c.handlePacket(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
c.disconnect()
|
||||||
return fmt.Errorf("handle packet 0x%X error: %w", p.ID, err)
|
return fmt.Errorf("handle packet 0x%X error: %w", p.ID, err)
|
||||||
}
|
}
|
||||||
if disconnect {
|
if disconnect {
|
||||||
@ -270,9 +327,9 @@ func handleUpdateHealthPacket(c *Client, p pk.Packet) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.Health < 1 { //player is dead
|
if c.Health < 1 { //player is dead
|
||||||
|
c.Physics.Run = false
|
||||||
sendPlayerPositionAndLookPacket(c)
|
sendPlayerPositionAndLookPacket(c)
|
||||||
if c.Events.Die != nil {
|
if c.Events.Die != nil {
|
||||||
|
|
||||||
if err := c.Events.Die(); err != nil {
|
if err := c.Events.Die(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -434,30 +491,41 @@ func handlePlayerPositionAndLookPacket(c *Client, p pk.Packet) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pp := c.Player.Pos
|
||||||
if pkt.RelativeX() {
|
if pkt.RelativeX() {
|
||||||
c.X = float64(pkt.X)
|
pp.X += float64(pkt.X)
|
||||||
} else {
|
} else {
|
||||||
c.X += float64(pkt.X)
|
pp.X = float64(pkt.X)
|
||||||
}
|
}
|
||||||
if pkt.RelativeY() {
|
if pkt.RelativeY() {
|
||||||
c.Y = float64(pkt.Y)
|
pp.Y += float64(pkt.Y)
|
||||||
} else {
|
} else {
|
||||||
c.Y += float64(pkt.Y)
|
pp.Y = float64(pkt.Y)
|
||||||
}
|
}
|
||||||
if pkt.RelativeZ() {
|
if pkt.RelativeZ() {
|
||||||
c.Z = float64(pkt.Z)
|
pp.Z += float64(pkt.Z)
|
||||||
} else {
|
} else {
|
||||||
c.Z += float64(pkt.Z)
|
pp.Z = float64(pkt.Z)
|
||||||
}
|
}
|
||||||
if pkt.RelativeYaw() {
|
if pkt.RelativeYaw() {
|
||||||
c.Yaw = float32(pkt.Yaw)
|
pp.Yaw += float32(pkt.Yaw)
|
||||||
} else {
|
} else {
|
||||||
c.Yaw += float32(pkt.Yaw)
|
pp.Yaw = float32(pkt.Yaw)
|
||||||
}
|
}
|
||||||
if pkt.RelativePitch() {
|
if pkt.RelativePitch() {
|
||||||
c.Pitch = float32(pkt.Pitch)
|
pp.Pitch += float32(pkt.Pitch)
|
||||||
} else {
|
} else {
|
||||||
c.Pitch += float32(pkt.Pitch)
|
pp.Pitch = float32(pkt.Pitch)
|
||||||
|
}
|
||||||
|
if err := c.Physics.ServerPositionUpdate(pp, &c.Wd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Player.Pos = pp
|
||||||
|
|
||||||
|
if c.Events.PositionChange != nil {
|
||||||
|
if err := c.Events.PositionChange(pp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Confirm
|
//Confirm
|
||||||
@ -516,14 +584,33 @@ func handleSetExperience(c *Client, p pk.Packet) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendPlayerPositionAndLookPacket(c *Client) {
|
func sendPlayerPositionAndLookPacket(c *Client) error {
|
||||||
c.conn.WritePacket(pk.Marshal(
|
// fmt.Println("PPL")
|
||||||
data.PositionLook,
|
return c.conn.WritePacket(ptypes.PositionAndLookServerbound{
|
||||||
pk.Double(c.X),
|
X: pk.Double(c.Pos.X),
|
||||||
pk.Double(c.Y),
|
Y: pk.Double(c.Pos.Y),
|
||||||
pk.Double(c.Z),
|
Z: pk.Double(c.Pos.Z),
|
||||||
pk.Float(c.Yaw),
|
Yaw: pk.Float(c.Pos.Yaw),
|
||||||
pk.Float(c.Pitch),
|
Pitch: pk.Float(c.Pos.Pitch),
|
||||||
pk.Boolean(c.OnGround),
|
OnGround: pk.Boolean(c.Pos.OnGround),
|
||||||
))
|
}.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPlayerPositionPacket(c *Client) error {
|
||||||
|
// fmt.Println("P")
|
||||||
|
return c.conn.WritePacket(ptypes.Position{
|
||||||
|
X: pk.Double(c.Pos.X),
|
||||||
|
Y: pk.Double(c.Pos.Y),
|
||||||
|
Z: pk.Double(c.Pos.Z),
|
||||||
|
OnGround: pk.Boolean(c.Pos.OnGround),
|
||||||
|
}.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPlayerLookPacket(c *Client) error {
|
||||||
|
// fmt.Println("L")
|
||||||
|
return c.conn.WritePacket(ptypes.Look{
|
||||||
|
Yaw: pk.Float(c.Pos.Yaw),
|
||||||
|
Pitch: pk.Float(c.Pos.Pitch),
|
||||||
|
OnGround: pk.Boolean(c.Pos.OnGround),
|
||||||
|
}.Encode())
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ func (c *Client) join(d Dialer, addr string) (err error) {
|
|||||||
case 0x02: //Login Success
|
case 0x02: //Login Success
|
||||||
// uuid, l := pk.UnpackString(pack.Data)
|
// uuid, l := pk.UnpackString(pack.Data)
|
||||||
// name, _ := unpackString(pack.Data[l:])
|
// name, _ := unpackString(pack.Data[l:])
|
||||||
return //switches the connection state to PLAY.
|
return nil
|
||||||
case 0x03: //Set Compression
|
case 0x03: //Set Compression
|
||||||
var threshold pk.VarInt
|
var threshold pk.VarInt
|
||||||
if err := pack.Scan(&threshold); err != nil {
|
if err := pack.Scan(&threshold); err != nil {
|
||||||
|
@ -180,7 +180,7 @@ func (c *Client) SwapItem() error {
|
|||||||
|
|
||||||
// Disconnect disconnect the server.
|
// Disconnect disconnect the server.
|
||||||
// Server will close the connection.
|
// Server will close the connection.
|
||||||
func (c *Client) Disconnect() error {
|
func (c *Client) disconnect() error {
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
128
bot/phy/aabb.go
Normal file
128
bot/phy/aabb.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package phy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/bot/world"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MinMax struct {
|
||||||
|
Min, Max float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm MinMax) Extend(delta float64) MinMax {
|
||||||
|
if delta < 0 {
|
||||||
|
return MinMax{
|
||||||
|
Min: mm.Min + delta,
|
||||||
|
Max: mm.Max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MinMax{
|
||||||
|
Min: mm.Min,
|
||||||
|
Max: mm.Max + delta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm MinMax) Contract(amt float64) MinMax {
|
||||||
|
return MinMax{
|
||||||
|
Min: mm.Min + amt,
|
||||||
|
Max: mm.Max - amt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm MinMax) Expand(amt float64) MinMax {
|
||||||
|
return MinMax{
|
||||||
|
Min: mm.Min - amt,
|
||||||
|
Max: mm.Max + amt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm MinMax) Offset(amt float64) MinMax {
|
||||||
|
return MinMax{
|
||||||
|
Min: mm.Min + amt,
|
||||||
|
Max: mm.Max + amt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AABB implements Axis Aligned Bounding Box operations.
|
||||||
|
type AABB struct {
|
||||||
|
X, Y, Z MinMax
|
||||||
|
Block world.BlockStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb AABB) Extend(dx, dy, dz float64) AABB {
|
||||||
|
return AABB{
|
||||||
|
X: bb.X.Extend(dx),
|
||||||
|
Y: bb.Y.Extend(dx),
|
||||||
|
Z: bb.Z.Extend(dx),
|
||||||
|
Block: bb.Block,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb AABB) Contract(x, y, z float64) AABB {
|
||||||
|
return AABB{
|
||||||
|
X: bb.X.Contract(x),
|
||||||
|
Y: bb.Y.Contract(y),
|
||||||
|
Z: bb.Z.Contract(z),
|
||||||
|
Block: bb.Block,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb AABB) Expand(x, y, z float64) AABB {
|
||||||
|
return AABB{
|
||||||
|
X: bb.X.Expand(x),
|
||||||
|
Y: bb.Y.Expand(y),
|
||||||
|
Z: bb.Z.Expand(z),
|
||||||
|
Block: bb.Block,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb AABB) Offset(x, y, z float64) AABB {
|
||||||
|
return AABB{
|
||||||
|
X: bb.X.Offset(x),
|
||||||
|
Y: bb.Y.Offset(y),
|
||||||
|
Z: bb.Z.Offset(z),
|
||||||
|
Block: bb.Block,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb AABB) XOffset(o AABB, xOffset float64) float64 {
|
||||||
|
if o.Y.Max > bb.Y.Min && o.Y.Min < bb.Y.Max && o.Z.Max > bb.Z.Min && o.Z.Min < bb.Z.Max {
|
||||||
|
if xOffset > 0.0 && o.X.Max <= bb.X.Min {
|
||||||
|
xOffset = math.Min(bb.X.Min-o.X.Max, xOffset)
|
||||||
|
} else if xOffset < 0.0 && o.X.Min >= bb.X.Max {
|
||||||
|
xOffset = math.Max(bb.X.Max-o.X.Min, xOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return xOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb AABB) YOffset(o AABB, yOffset float64) float64 {
|
||||||
|
if o.X.Max > bb.X.Min && o.X.Min < bb.X.Max && o.Z.Max > bb.Z.Min && o.Z.Min < bb.Z.Max {
|
||||||
|
if yOffset > 0.0 && o.Y.Max <= bb.Y.Min {
|
||||||
|
yOffset = math.Min(bb.Y.Min-o.Y.Max, yOffset)
|
||||||
|
} else if yOffset < 0.0 && o.Y.Min >= bb.Y.Max {
|
||||||
|
yOffset = math.Max(bb.Y.Max-o.Y.Min, yOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return yOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb AABB) ZOffset(o AABB, zOffset float64) float64 {
|
||||||
|
if o.X.Max > bb.X.Min && o.X.Min < bb.X.Max && o.Y.Max > bb.Y.Min && o.Y.Min < bb.Y.Max {
|
||||||
|
if zOffset > 0.0 && o.Z.Max <= bb.Z.Min {
|
||||||
|
zOffset = math.Min(bb.Z.Min-o.Z.Max, zOffset)
|
||||||
|
} else if zOffset < 0.0 && o.Z.Min >= bb.Z.Max {
|
||||||
|
zOffset = math.Max(bb.Z.Max-o.Z.Min, zOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return zOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb AABB) Intersects(o AABB) bool {
|
||||||
|
return true &&
|
||||||
|
bb.X.Min < o.X.Max && bb.X.Max > o.X.Min &&
|
||||||
|
bb.Y.Min < o.Y.Max && bb.Y.Max > o.Y.Min &&
|
||||||
|
bb.Z.Min < o.Z.Max && bb.Z.Max > o.Z.Min
|
||||||
|
}
|
145
bot/phy/phy.go
Normal file
145
bot/phy/phy.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// Package phy implements a minimal physics simulation necessary for realistic
|
||||||
|
// bot behavior.
|
||||||
|
package phy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/bot/world"
|
||||||
|
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
playerWidth = 0.6
|
||||||
|
playerHeight = 1.8
|
||||||
|
resetVel = 0.003
|
||||||
|
|
||||||
|
gravity = 0.08
|
||||||
|
)
|
||||||
|
|
||||||
|
// World represents a provider of information about the surrounding world.
|
||||||
|
type World interface {
|
||||||
|
GetBlockStatus(x, y, z int) world.BlockStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surrounds represents the blocks surrounding the player (Y, Z, X).
|
||||||
|
type Surrounds []AABB
|
||||||
|
|
||||||
|
// Point represents a point in 3D space.
|
||||||
|
type Point struct {
|
||||||
|
X, Y, Z float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// State tracks physics state.
|
||||||
|
type State struct {
|
||||||
|
// player state.
|
||||||
|
Pos Point
|
||||||
|
Vel Point
|
||||||
|
Yaw, Pitch float64
|
||||||
|
|
||||||
|
// player state flags.
|
||||||
|
onGround bool
|
||||||
|
collision struct {
|
||||||
|
vertical bool
|
||||||
|
horizontal bool
|
||||||
|
}
|
||||||
|
|
||||||
|
Run bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) ServerPositionUpdate(player player.Pos, w World) error {
|
||||||
|
s.Pos = Point{X: player.X, Y: player.Y, Z: player.Z}
|
||||||
|
s.Yaw, s.Pitch = float64(player.Yaw), float64(player.Pitch)
|
||||||
|
s.Vel = Point{}
|
||||||
|
s.onGround, s.collision.vertical, s.collision.horizontal = false, false, false
|
||||||
|
s.Run = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func abs(i1, i2 int) int {
|
||||||
|
if i1 < i2 {
|
||||||
|
return i2 - i1
|
||||||
|
}
|
||||||
|
return i1 - i2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) surroundings(query AABB, w World) Surrounds {
|
||||||
|
minY, maxY := int(math.Floor(query.Y.Min))-1, int(math.Floor(query.Y.Max))+1
|
||||||
|
minZ, maxZ := int(math.Floor(query.Z.Min)), int(math.Floor(query.Z.Max))+1
|
||||||
|
minX, maxX := int(math.Floor(query.X.Min)), int(math.Floor(query.X.Max))+1
|
||||||
|
|
||||||
|
out := Surrounds(make([]AABB, 0, abs(maxY, minY)*abs(maxZ, minZ)*abs(maxX, minX)))
|
||||||
|
for y := minY; y < maxY; y++ {
|
||||||
|
for z := minZ; z < maxZ; z++ {
|
||||||
|
for x := minX; x < maxX; x++ {
|
||||||
|
if block := w.GetBlockStatus(x, y, z); block > 0 {
|
||||||
|
out = append(out, AABB{X: MinMax{Max: 1}, Y: MinMax{Max: 1}, Z: MinMax{Max: 1}, Block: block}.Offset(float64(x), float64(y), float64(z)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) Tick(w World) error {
|
||||||
|
if !s.Run {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if math.Abs(s.Vel.X) < resetVel {
|
||||||
|
s.Vel.X = 0
|
||||||
|
}
|
||||||
|
if math.Abs(s.Vel.Y) < resetVel {
|
||||||
|
s.Vel.Y = 0
|
||||||
|
}
|
||||||
|
if math.Abs(s.Vel.Z) < resetVel {
|
||||||
|
s.Vel.Z = 0
|
||||||
|
}
|
||||||
|
s.Vel.Y -= gravity
|
||||||
|
|
||||||
|
// Apply collision.
|
||||||
|
var (
|
||||||
|
player = s.BB()
|
||||||
|
query = player.Extend(s.Vel.X, s.Vel.Y, s.Vel.Z)
|
||||||
|
surroundings = s.surroundings(query, w)
|
||||||
|
newVel = s.Vel
|
||||||
|
)
|
||||||
|
for _, b := range surroundings {
|
||||||
|
newVel.Y = b.YOffset(player, newVel.Y)
|
||||||
|
}
|
||||||
|
player = player.Offset(0, newVel.Y, 0)
|
||||||
|
for _, b := range surroundings {
|
||||||
|
newVel.X = b.XOffset(player, newVel.X)
|
||||||
|
}
|
||||||
|
player = player.Offset(newVel.X, 0, 0)
|
||||||
|
for _, b := range surroundings {
|
||||||
|
newVel.Z = b.ZOffset(player, newVel.Z)
|
||||||
|
}
|
||||||
|
player = player.Offset(0, 0, newVel.Z)
|
||||||
|
|
||||||
|
// Update flags.
|
||||||
|
s.Pos.X = player.X.Min + playerWidth/2
|
||||||
|
s.Pos.Y = player.Y.Min
|
||||||
|
s.Pos.Z = player.Z.Min + playerWidth/2
|
||||||
|
s.collision.horizontal = newVel.X != s.Vel.X || newVel.Z != s.Vel.Z
|
||||||
|
s.collision.vertical = newVel.Y != s.Vel.Y
|
||||||
|
s.onGround = s.collision.vertical && s.Vel.Y < 0
|
||||||
|
|
||||||
|
s.Vel = newVel
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) BB() AABB {
|
||||||
|
return AABB{
|
||||||
|
X: MinMax{Min: -playerWidth / 2, Max: playerWidth / 2},
|
||||||
|
Y: MinMax{Max: playerHeight},
|
||||||
|
Z: MinMax{Min: -playerWidth / 2, Max: playerWidth / 2},
|
||||||
|
}.Offset(s.Pos.X, s.Pos.Y, s.Pos.Z)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) Position() player.Pos {
|
||||||
|
return player.Pos{
|
||||||
|
X: s.Pos.X, Y: s.Pos.Y, Z: s.Pos.Z,
|
||||||
|
Yaw: float32(s.Yaw), Pitch: float32(s.Pitch),
|
||||||
|
OnGround: s.onGround,
|
||||||
|
}
|
||||||
|
}
|
@ -2,14 +2,28 @@ package player
|
|||||||
|
|
||||||
import "github.com/Tnze/go-mc/bot/world/entity"
|
import "github.com/Tnze/go-mc/bot/world/entity"
|
||||||
|
|
||||||
|
type Pos struct {
|
||||||
|
X, Y, Z float64
|
||||||
|
Yaw, Pitch float32
|
||||||
|
OnGround bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pos) PosEqual(other Pos) bool {
|
||||||
|
return p.X == other.X && p.Y == other.Y && p.Z == other.Z
|
||||||
|
}
|
||||||
|
func (p Pos) LookEqual(other Pos) bool {
|
||||||
|
return p.Yaw == other.Yaw && p.Pitch == other.Pitch
|
||||||
|
}
|
||||||
|
func (p Pos) Equal(other Pos) bool {
|
||||||
|
return p.PosEqual(other) && p.LookEqual(other) && p.OnGround == other.OnGround
|
||||||
|
}
|
||||||
|
|
||||||
// Player includes the player's status.
|
// Player includes the player's status.
|
||||||
type Player struct {
|
type Player struct {
|
||||||
entity.Entity
|
entity.Entity
|
||||||
UUID [2]int64 //128bit UUID
|
UUID [2]int64 //128bit UUID
|
||||||
|
|
||||||
X, Y, Z float64
|
Pos Pos
|
||||||
Yaw, Pitch float32
|
|
||||||
OnGround bool
|
|
||||||
|
|
||||||
HeldItem int //拿着的物品栏位
|
HeldItem int //拿着的物品栏位
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
package ptypes
|
package ptypes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/Tnze/go-mc/data"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,3 +34,54 @@ func (p *PositionAndLookClientbound) RelativePitch() bool {
|
|||||||
func (p *PositionAndLookClientbound) Decode(pkt pk.Packet) error {
|
func (p *PositionAndLookClientbound) Decode(pkt pk.Packet) error {
|
||||||
return pkt.Scan(&p.X, &p.Y, &p.Z, &p.Yaw, &p.Pitch, &p.Flags, &p.TeleportID)
|
return pkt.Scan(&p.X, &p.Y, &p.Z, &p.Yaw, &p.Pitch, &p.Flags, &p.TeleportID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PositionAndLookServerbound describes the location and orientation of
|
||||||
|
// the player.
|
||||||
|
type PositionAndLookServerbound struct {
|
||||||
|
X, Y, Z pk.Double
|
||||||
|
Yaw, Pitch pk.Float
|
||||||
|
OnGround pk.Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PositionAndLookServerbound) Encode() pk.Packet {
|
||||||
|
return pk.Marshal(
|
||||||
|
data.PositionLook,
|
||||||
|
pk.Double(p.X),
|
||||||
|
pk.Double(p.Y),
|
||||||
|
pk.Double(p.Z),
|
||||||
|
pk.Float(p.Yaw),
|
||||||
|
pk.Float(p.Pitch),
|
||||||
|
pk.Boolean(p.OnGround),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position describes the position of the player.
|
||||||
|
type Position struct {
|
||||||
|
X, Y, Z pk.Double
|
||||||
|
OnGround pk.Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) Encode() pk.Packet {
|
||||||
|
return pk.Marshal(
|
||||||
|
data.PositionServerbound,
|
||||||
|
pk.Double(p.X),
|
||||||
|
pk.Double(p.Y),
|
||||||
|
pk.Double(p.Z),
|
||||||
|
pk.Boolean(p.OnGround),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look describes the rotation of the player.
|
||||||
|
type Look struct {
|
||||||
|
Yaw, Pitch pk.Float
|
||||||
|
OnGround pk.Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Look) Encode() pk.Packet {
|
||||||
|
return pk.Marshal(
|
||||||
|
data.PositionLook,
|
||||||
|
pk.Float(p.Yaw),
|
||||||
|
pk.Float(p.Pitch),
|
||||||
|
pk.Boolean(p.OnGround),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user