Replicate vanilla physics, make pathing movements smooth
This commit is contained in:
@ -32,7 +32,6 @@ func (c *Client) updateServerPos(pos player.Pos) error {
|
||||
sendPlayerLookPacket(c)
|
||||
case prev.OnGround != pos.OnGround:
|
||||
c.conn.WritePacket(
|
||||
//ClientSettings packet (serverbound)
|
||||
pk.Marshal(
|
||||
data.Flying,
|
||||
pk.Boolean(pos.OnGround),
|
||||
|
11
bot/mcbot.go
11
bot/mcbot.go
@ -9,6 +9,7 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Tnze/go-mc/data"
|
||||
mcnet "github.com/Tnze/go-mc/net"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
@ -124,3 +125,13 @@ type Dialer interface {
|
||||
func (c *Client) Conn() *mcnet.Conn {
|
||||
return c.conn
|
||||
}
|
||||
|
||||
// SendMessage sends a chat message.
|
||||
func (c *Client) SendMessage(msg string) error {
|
||||
return c.conn.WritePacket(
|
||||
pk.Marshal(
|
||||
data.ChatServerbound,
|
||||
pk.String(msg),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -33,6 +33,23 @@ var (
|
||||
block.LapisOre,
|
||||
block.Sandstone,
|
||||
block.RedstoneOre,
|
||||
block.OakStairs,
|
||||
block.AcaciaStairs,
|
||||
block.DarkOakStairs,
|
||||
block.RedSandstoneStairs,
|
||||
block.PolishedGraniteStairs,
|
||||
block.SmoothRedSandstoneStairs,
|
||||
block.MossyStoneBrickStairs,
|
||||
block.PolishedDioriteStairs,
|
||||
block.MossyCobblestoneStairs,
|
||||
block.EndStoneBrickStairs,
|
||||
block.StoneStairs,
|
||||
block.SmoothSandstoneStairs,
|
||||
block.SmoothQuartzStairs,
|
||||
block.GraniteStairs,
|
||||
block.AndesiteStairs,
|
||||
block.RedNetherBrickStairs,
|
||||
block.PolishedAndesiteStairs,
|
||||
}
|
||||
|
||||
safeWalkBlocks = make(map[world.BlockStatus]struct{}, 128)
|
||||
|
@ -3,6 +3,7 @@ package path
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world"
|
||||
"github.com/beefsack/go-astar"
|
||||
@ -96,12 +97,20 @@ func (t Tile) PathNeighbors() []astar.Pather {
|
||||
}
|
||||
|
||||
func (t Tile) Inputs(pos, deltaPos, vel Point) Inputs {
|
||||
|
||||
// Sufficient for simple movements.
|
||||
at := math.Atan2(-deltaPos.X, -deltaPos.Z)
|
||||
mdX, _, mdZ := t.Movement.Offset()
|
||||
wantYaw := -math.Atan2(float64(mdX), float64(mdZ)) * 180 / math.Pi
|
||||
out := Inputs{
|
||||
ThrottleX: math.Sin(at),
|
||||
ThrottleZ: math.Cos(at),
|
||||
Yaw: wantYaw,
|
||||
}
|
||||
if mdX == 0 && mdZ == 0 {
|
||||
out.Yaw = math.NaN()
|
||||
}
|
||||
if (rand.Int() % 14) == 0 {
|
||||
out.Pitch = float64((rand.Int() % 4) - 2)
|
||||
}
|
||||
|
||||
switch t.Movement {
|
||||
@ -127,6 +136,7 @@ func (t Tile) Inputs(pos, deltaPos, vel Point) Inputs {
|
||||
out = Inputs{
|
||||
ThrottleX: math.Sin(at),
|
||||
ThrottleZ: math.Cos(at),
|
||||
Yaw: math.NaN(),
|
||||
}
|
||||
|
||||
case AscendNorth, AscendSouth, AscendEast, AscendWest:
|
||||
@ -137,7 +147,6 @@ func (t Tile) Inputs(pos, deltaPos, vel Point) Inputs {
|
||||
if dist2 < 1 && deltaPos.Y < 0 && vel.Y == 0 {
|
||||
out.ThrottleX, out.ThrottleZ = 0, 0
|
||||
}
|
||||
out.Yaw = 0
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
192
bot/phy/phy.go
192
bot/phy/phy.go
@ -17,7 +17,7 @@ const (
|
||||
playerHeight = 1.8
|
||||
resetVel = 0.003
|
||||
|
||||
maxYawChange = 33
|
||||
maxYawChange = 18
|
||||
maxPitchChange = 11
|
||||
|
||||
stepHeight = 0.6
|
||||
@ -60,10 +60,11 @@ type State struct {
|
||||
}
|
||||
|
||||
func (s *State) ServerPositionUpdate(player player.Pos, w World) error {
|
||||
fmt.Printf("TELEPORT (y=%0.2f, velY=%0.3f): %0.2f, %0.2f, %0.2f\n", s.Pos.Y, s.Vel.Y, player.X-s.Pos.X, player.Y-s.Pos.Y, player.Z-s.Pos.Z)
|
||||
|
||||
s.Pos = path.Point{X: player.X, Y: player.Y, Z: player.Z}
|
||||
s.Yaw, s.Pitch = float64(player.Yaw), float64(player.Pitch)
|
||||
s.Vel = path.Point{}
|
||||
fmt.Println("TELEPORT!")
|
||||
s.onGround, s.collision.vertical, s.collision.horizontal = false, false, false
|
||||
s.Run = true
|
||||
return nil
|
||||
@ -127,68 +128,7 @@ func (s *State) Tick(input path.Inputs, w World) error {
|
||||
if !s.Run {
|
||||
return nil
|
||||
}
|
||||
s.tickVelocity(input, w)
|
||||
|
||||
player, newVel := s.computeCollision(s.BB(), s.BB().Extend(s.Vel.X, s.Vel.Y, s.Vel.Z), w)
|
||||
|
||||
bb := player.Extend(s.Vel.X, stepHeight, s.Vel.Z)
|
||||
surroundings := s.surroundings(bb, w)
|
||||
y := float64(0)
|
||||
for _, b := range surroundings {
|
||||
if b.Intersects(bb) && bb.Y.Max > b.Y.Min {
|
||||
y = math.Max(y, b.Y.Max)
|
||||
}
|
||||
}
|
||||
//fmt.Printf("pY = %.2f, maxblockY = %.1f (delta = %.1f)\n", bb.Y.Min, y, bb.Y.Min-y)
|
||||
if d := bb.Y.Min - y; d >= -stepHeight && d < stepHeight-1 {
|
||||
bb := player.Offset(0, -d, 0)
|
||||
player, newVel = s.computeCollision(bb, bb.Extend(s.Vel.X, s.Vel.Y, s.Vel.Z), w)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
if path.IsLadder(w.GetBlockStatus(int(math.Floor(s.Pos.X)), int(math.Floor(s.Pos.Y)), int(math.Floor(s.Pos.Z)))) && s.collision.horizontal {
|
||||
newVel.Y = ladderClimbSpeed
|
||||
}
|
||||
|
||||
s.Vel = newVel
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) applyLookInputs(input path.Inputs) {
|
||||
errYaw := math.Min(math.Max(input.Yaw-s.Yaw, -maxYawChange), maxYawChange)
|
||||
s.Yaw += errYaw
|
||||
errPitch := math.Min(math.Max(input.Pitch-s.Pitch, -maxPitchChange), maxPitchChange)
|
||||
s.Pitch += errPitch
|
||||
}
|
||||
|
||||
func (s *State) applyPosInputs(input path.Inputs, acceleration, inertia float64) {
|
||||
// fmt.Println(input.Jump, s.lastJump, s.onGround)
|
||||
if input.Jump && s.lastJump+minJumpTicks < s.tick {
|
||||
s.lastJump = s.tick
|
||||
s.Vel.Y += 0.42
|
||||
}
|
||||
|
||||
speed := math.Sqrt(input.ThrottleX*input.ThrottleX + input.ThrottleZ*input.ThrottleZ)
|
||||
if speed < 0.01 {
|
||||
return
|
||||
}
|
||||
speed = acceleration / math.Max(speed, 1)
|
||||
|
||||
input.ThrottleX *= speed
|
||||
input.ThrottleZ *= speed
|
||||
|
||||
s.Vel.X += input.ThrottleX
|
||||
s.Vel.Z += input.ThrottleZ
|
||||
}
|
||||
|
||||
func (s *State) tickVelocity(input path.Inputs, w World) {
|
||||
var inertia = inertia
|
||||
var acceleration = acceleration
|
||||
if below := w.GetBlockStatus(int(math.Floor(s.Pos.X)), int(math.Floor(s.Pos.Y))-1, int(math.Floor(s.Pos.Z))); s.onGround && !path.AirLikeBlock(below) {
|
||||
@ -196,6 +136,24 @@ func (s *State) tickVelocity(input path.Inputs, w World) {
|
||||
acceleration = 0.1 * (0.1627714 / (inertia * inertia * inertia))
|
||||
}
|
||||
|
||||
s.tickVelocity(input, inertia, acceleration, w)
|
||||
s.tickPosition(w)
|
||||
|
||||
if path.IsLadder(w.GetBlockStatus(int(math.Floor(s.Pos.X)), int(math.Floor(s.Pos.Y)), int(math.Floor(s.Pos.Z)))) && s.collision.horizontal {
|
||||
s.Vel.Y = ladderClimbSpeed
|
||||
}
|
||||
|
||||
// Gravity
|
||||
s.Vel.Y -= gravity
|
||||
// Drag & friction.
|
||||
s.Vel.Y *= drag
|
||||
s.Vel.X *= inertia
|
||||
s.Vel.Z *= inertia
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) tickVelocity(input path.Inputs, inertia, acceleration float64, w World) {
|
||||
|
||||
// Deadzone velocities when they get too low.
|
||||
if math.Abs(s.Vel.X) < resetVel {
|
||||
s.Vel.X = 0
|
||||
@ -216,18 +174,108 @@ func (s *State) tickVelocity(input path.Inputs, w World) {
|
||||
s.Vel.Z = math.Min(math.Max(-ladderMaxSpeed, s.Vel.Z), ladderMaxSpeed)
|
||||
s.Vel.Y = math.Min(math.Max(-ladderMaxSpeed, s.Vel.Y), ladderMaxSpeed)
|
||||
}
|
||||
|
||||
// Gravity
|
||||
s.Vel.Y -= gravity
|
||||
// Drag & friction.
|
||||
s.Vel.Y *= drag
|
||||
s.Vel.X *= inertia
|
||||
s.Vel.Z *= inertia
|
||||
}
|
||||
|
||||
func (s *State) computeCollision(bb, query AABB, w World) (outBB AABB, outVel path.Point) {
|
||||
func (s *State) applyLookInputs(input path.Inputs) {
|
||||
if !math.IsNaN(input.Yaw) {
|
||||
errYaw := math.Min(math.Max(modYaw(input.Yaw, s.Yaw), -maxYawChange), maxYawChange)
|
||||
s.Yaw += errYaw
|
||||
}
|
||||
errPitch := math.Min(math.Max(input.Pitch-s.Pitch, -maxPitchChange), maxPitchChange)
|
||||
s.Pitch += errPitch
|
||||
}
|
||||
|
||||
func (s *State) applyPosInputs(input path.Inputs, acceleration, inertia float64) {
|
||||
// fmt.Println(input.Jump, s.lastJump, s.onGround)
|
||||
if input.Jump && s.lastJump+minJumpTicks < s.tick && s.onGround {
|
||||
s.lastJump = s.tick
|
||||
s.Vel.Y = 0.42
|
||||
}
|
||||
|
||||
speed := math.Sqrt(input.ThrottleX*input.ThrottleX + input.ThrottleZ*input.ThrottleZ)
|
||||
if speed < 0.01 {
|
||||
return
|
||||
}
|
||||
speed = acceleration / math.Max(speed, 1)
|
||||
|
||||
input.ThrottleX *= speed
|
||||
input.ThrottleZ *= speed
|
||||
|
||||
s.Vel.X += input.ThrottleX
|
||||
s.Vel.Z += input.ThrottleZ
|
||||
}
|
||||
|
||||
func (s *State) tickPosition(w World) {
|
||||
// fmt.Printf("TICK POSITION: %0.2f, %0.2f, %0.2f - (%0.2f, %0.2f, %0.2f)\n", s.Pos.X, s.Pos.Y, s.Pos.Z, s.Vel.X, s.Vel.Y, s.Vel.Z)
|
||||
|
||||
player, newVel := s.computeCollisionYXZ(s.BB(), s.BB().Offset(s.Vel.X, s.Vel.Y, s.Vel.Z), s.Vel, w)
|
||||
//fmt.Printf("offset = %0.2f, %0.2f, %0.2f\n", player.X.Min-s.Pos.X, player.Y.Min-s.Pos.Y, player.Z.Min-s.Pos.Z)
|
||||
|
||||
//fmt.Printf("onGround = %v, s.Vel.Y = %0.3f, newVel.Y = %0.3f\n", s.onGround, s.Vel.Y, newVel.Y)
|
||||
if s.onGround || (s.Vel.Y != newVel.Y && s.Vel.Y < 0) {
|
||||
bb := s.BB()
|
||||
//fmt.Printf("Player pos = %0.2f, %0.2f, %0.2f\n", bb.X.Min, bb.Y.Min, bb.Z.Min)
|
||||
surroundings := s.surroundings(bb.Offset(s.Vel.X, stepHeight, s.Vel.Y), w)
|
||||
outVel := s.Vel
|
||||
|
||||
outVel.Y = stepHeight
|
||||
for _, b := range surroundings {
|
||||
outVel.Y = b.YOffset(bb, outVel.Y)
|
||||
}
|
||||
bb = bb.Offset(0, outVel.Y, 0)
|
||||
for _, b := range surroundings {
|
||||
outVel.X = b.XOffset(bb, outVel.X)
|
||||
}
|
||||
bb = bb.Offset(outVel.X, 0, 0)
|
||||
for _, b := range surroundings {
|
||||
outVel.Z = b.ZOffset(bb, outVel.Z)
|
||||
}
|
||||
bb = bb.Offset(0, 0, outVel.Z)
|
||||
//fmt.Printf("Post-collision = %0.2f, %0.2f, %0.2f\n", bb.X.Min, bb.Y.Min, bb.Z.Min)
|
||||
|
||||
outVel.Y *= -1
|
||||
// Lower the player back down to be on the ground.
|
||||
for _, b := range surroundings {
|
||||
outVel.Y = b.YOffset(bb, outVel.Y)
|
||||
}
|
||||
bb = bb.Offset(0, outVel.Y, 0)
|
||||
//fmt.Printf("Post-lower = %0.2f, %0.2f, %0.2f\n", bb.X.Min, bb.Y.Min, bb.Z.Min)
|
||||
|
||||
oldMove := newVel.X*newVel.X + newVel.Z*newVel.Z
|
||||
newMove := outVel.X*outVel.X + outVel.Z*outVel.Z
|
||||
// fmt.Printf("oldMove = %0.2f, newMove = %0.2f\n", oldMove*1000, newMove*1000)
|
||||
if oldMove >= newMove || outVel.Y <= (0.000002-stepHeight) {
|
||||
// fmt.Println("nope")
|
||||
} else {
|
||||
player = bb
|
||||
newVel = outVel
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func modYaw(new, old float64) float64 {
|
||||
delta := math.Mod(new-old, 360)
|
||||
if delta > 180 {
|
||||
delta = 180 - delta
|
||||
} else if delta < -180 {
|
||||
delta += 360
|
||||
}
|
||||
// fmt.Printf("(%.2f - %.2f) = %.2f\n", new, old, delta)
|
||||
return delta
|
||||
}
|
||||
|
||||
func (s *State) computeCollisionYXZ(bb, query AABB, vel path.Point, w World) (outBB AABB, outVel path.Point) {
|
||||
surroundings := s.surroundings(query, w)
|
||||
outVel = s.Vel
|
||||
outVel = vel
|
||||
|
||||
for _, b := range surroundings {
|
||||
outVel.Y = b.YOffset(bb, outVel.Y)
|
||||
|
Reference in New Issue
Block a user